npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@kevinbalicot/custom-element

v1.3.0

Published

Tiny library to make custom elements

Downloads

2

Readme

Custom Element

Tiny library to make custom HTML elements

Live demo https://jsfiddle.net/kevinbalicot/L08q4k21/

Tests : Build Status

Installation

$ npm install --save @kevinbalicot/custom-element

Get started

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>My site</title>

    <script src="/node_modules/@kevinbalicot/custom-element/dist/custom-element.min.js" charset="utf-8"></script>
</head>
<body>
    <my-component></my-component>

    <script type="application/javascript">
        class MyComponent extends CustomElement {
            constructor() {
                super();

                this.name = 'Jean';
            }

            static get template() {
                return '<h1>Hello <span [innerHTML]="this.name"></span> !</h1>';
            }

            static get styles() {
                return [':host(span) { color: red; }'];
            }
        }
	    
	window.customElements.define('my-component', MyComponent);
    </script>
</body>
</html>

Features

Input binding

class DemoComponent extends CustomElement {
    constructor() {
        super();

        this.text = null;
    }

    /**
    * Call when custom element call connectedCallback (see HTML custom element life cycle)
    */
    onConnected() {
        this.element('input[name=text]').on('input', event => {
            this.text = event.target.value;
            this.update(); // Refresh template

            // You can also to use this.update({ text: event.target.value });
        });
    }

    static get template() {
        return `
            <h2>Demo</h2>
            <hr>

            <h3>Input binding</h3>
            <label id="text">Type something</label><input type="text" name="text">
            <p [innerHTML]="'You tipping: ' + this.text"></p>
        `;
    }
}

window.customElements.define('demo-component', DemoComponent);

For binding

class DemoComponent extends CustomElement {
    constructor() {
        super();

        this.items = [];
    }

    onConnected() {
        this.element('form[rol=list]').on('submit', event => {
            event.preventDefault();

            const input = event.target.querySelector('input[name=item]');

            if (input.value) {
                this.items.push(input.value);
                input.value = null;

                this.update();
            }
        });
    }

    onDeleteItem(index) {
        this.items.splice(index, 1);
        this.update();
    }

    static get template() {
        return `
            <h2>Demo</h2>
            <hr>

            <h3>For binding</h3>
            <form rol="list">
                <input type="text" name="item" required>
                <button type="submit">Add</button>
            </form>

            <ul>
                <!-- use #for attribute to make a loop -->
                <li #for="let i of this.items">
                    <span [innerHTML]="i" [class.stronger]="i.length > 5"></span>
                    <button (click)="this.onDeleteItem($index)">Delete</button>
                </li>
            </ul>
        `;
    }

    static get styles() {
        return [
            '.stronger { font-weight: bold }'
        ];
    }
}

window.customElements.define('demo-component', DemoComponent);

If binding

class DemoComponent extends CustomElement {
    constructor() {
        super();

        this.show = true;
    }

    toggle(value) {
        this.update({ show: value });
    }

    static get template() {
        return `
            <h2>Demo</h2>
            <hr>

            <h3>If binding</h3>
            <button (click)="this.toggle(true)">Show</button>
            <button (click)="this.toggle(false)">Hide</button>

            <!-- use #if attribute to make a condition -->
            <p #if="this.show">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
            </p>
        `;
    }
}

window.customElements.define('demo-component', DemoComponent);

Attribute binding

class ColorComponent extends CustomElement {
    constructor() {
        super();
        this.color = '#fff';
    }

    /**
     * Call every time observed attribute change
     */
    onChanges(attributeName, oldValue, newValue) {
        if ('color' === attributeName) {
            // this.color is already set with new value, but you can abort and set the old value

            this.update();
        }
    }

    // Define ouputs
    static get observedAttributes() {
        return ['color'];
    }

    static get template() {
        return '<span [style.background]="this.color" [innerHTML]="this.color"></span>';
    }
}

class DemoComponent extends CustomElement {
    constructor() {
        super();

        this.color = '#000';
    }

    onConnected() {
        this.el('input[name=color]').on('change', event => {
            this.update({ color: event.target.value });
        });
    }

    static get template() {
        return `
            <h2>Demo</h2>
            <hr>

            <h3>Attribute binding</h3>
            <label [style.color]="this.color">Choose your color</label>
            <input [value]="this.color" type="color" name="color">

            <h5>Sub component</h5>
            <!-- use another custom component with outputs -->
            <color-component [attr.color]="this.color"></color-component>
        `;
    }
}

window.customElements.define('color-component', ColorComponent);
window.customElements.define('demo-component', DemoComponent);

Container and Injectable services

class Http {
    get(url) {
        return fetch(url).then(result => result.text());
    }
}

class MarkdownDecoder {
    constructor() {
        this.converter = new showdown.Converter();
    }

    decode(markdown) {
        return this.converter.makeHtml(markdown);
    }
}

// Define class is injectable in another class with injects() static method
class ReadmeLoader extends Injectable {
    constructor(http, markdownDecoder) {
        super();

        this.http = http;
        this.decoder = markdownDecoder;
    }

    load(url) {
        return this.http.get(url).then(data => this.decoder.decode(data));
    }

    static get injects() {
        return [Http, MarkdownDecoder]; // Another services
    }
}

class ReadmeComponent extends CustomElement {
    constructor() {
        super();

        this.url = 'https://raw.githubusercontent.com/kevinbalicot/custom-element/master/README.md';
        this.readmeLoader = this.get('ReadmeLoader');

        this.content = null;
    }

    onConnected() {
        this.readmeLoader.load(this.url).then(content => {
            this.update({ content });
        });
    }

    static get template() {
        return '<div [innerHTML]="this.content"></div>';
    }

    static get injects() {
        return [Http, MarkdownDecoder, ReadmeLoader]; // Need to inject all services
    }
}

Event binding

Sometime to use AddEventListener into onConnected can't work after the template refreshing, because target element is not the same after the refresh. So instead of AddEventListener, please use event binding.

class MyComponent extends CustomElement {

    onClick(event) {
        // No need to use event.stopPropagation() with (*.stop)
        // make something with button
    }

    onInput(event) {
        // make something with input
    }

    onSumbit(event) {
        // No need to use event.preventDefault() with (*.prevent)
        // make something with form
    }

    onSelect(event) {
        // make something with select
    }

    static get template() {
        return `
            <button (click.stop)="this.onClick($event)">Click on me <3</button>

            <form (submit.prevent)="this.onSumbit($event)">
                <input (input)="this.onInput($event)" type="text" name="text">
                <select (change)="this.onSelect($event)">
                    <option value="1">One</option>
                    <option value="2">Two</option>
                    <option value="3">Three</option>
                </select>
            </form>
        `;
    }
}

Slots

You can use slot system, explanation on MDN web doc.

class ChildComponent extends CustomElement {
    constructor() {
        super();
        this.childParam = 'child';
    }
   
    static get template() {
        return `
            <span [innerHTML]="this.childParam"></span>
            <slot name="title">Default title</slot>
            <slot>Default content</slot>
        `;
    }
}

window.customElements.define('child-component', ChildComponent);

class ParentComponent extends CustomElement {
    static get template() {
        return `
            <child-component>
                <h4 slot="title">Override default title of child component</h4>
                <ul>
                    <li>Override default content of child component</li>
                    <li>Can get child scope <span [innerHTML]="this.childParam"></span></li>
                    <li>But we can always get parent scope <span [innerHTML]="$parent.parentParam"></span></li>
                </ul>
            </child-component>
        `;
    }
}

window.customElements.define('parent-component', ChildComponent);

API

class CustomElement extends HTMLElement {
    constructor() {}

    connectedCallback() {
        // Call this.onConnected()
    }

    /**
     * @param {string} name - Attribute name
     * @param {string|number} oldValue - old attribute value
     * @param {string|number} newValue - new attribute value
     */
    attributeChangedCallback(name, oldValue, newValue) {
        // Call this.onChanges()
    }

    disconnectedCallback() {
        // Call this.onDisconnected()
    }

    /**
     * @param {Object} [details={}] - this attributes to update
     */
    update(details = {}) {}

    /**
     * @param {string} key - Get element into container
     * @return {*}
     */
    get(key) {}

    /**
     * @param {string} key - Key of element to store into container
     * @param {*} value - Element to store into container
     * @param {Array<*>} [parameters=[]] - Parameters to inject into element constructor
     */
    set(key, value, parameters = []) {}

    /**
     * @param {string} key - Element key into container
     * @return {boolean}
     */
    has(key) {}

    /**
     * @param {string} event - Event name to attach at this custom element
     * @param {function} callback
     * @param {boolean} [options=false] - Option for AddEventListener function
     */
    on(event, callback, options = false) {}

    /**
     * @param {string} selector - CSS selector to get HTML Element
     * @param {HTMLElement|null}
     */
    el(selector) {}

    /**
     * @param {string} selector - CSS selector to get a list of HTML Elements
     * @param {Array<HTMLElement>|[]}
     */
    all(selector) {}

    /**
     * @param {string} event - Event name to emit from this custom element
     */
    emit(event) {}

    /**
     * @return {CustomElement|null} Return CustomElement parent is exists. $parent into template
     */
    get parent() {}

    /**
     * @return {CustomElement} Return the top level of CustomElement tree. $root into template
     */
    get root() {}

    static get template() {
        return '<slot></slot>';
    }

    static get injects() {
        return [];
    }

    static get observedAttributes() {
        return [];
    }

    static get styles() {
        return [];
    }
}

class Injectable {
	static get injects() {
		return [];
	}
}