Source: Tabs.js

/**
 * A simple tabs component.
 * <p>Part of the XUI module which, for now, has an undocumented API.
 */
export class Tabs extends HTMLElement {
    constructor() {
        super();
        
        this._closeableTabs = null; // Null or contains a possibly empty string.
        this._newTabButton = null;
        this._selectTab = this.selectTab.bind(this);
        this._closeTab = this.closeTab.bind(this);
        this._head = null;
        this._body = null;
        this._tabPairs = null;
    }
    
    connectedCallback() {
        let head = this.firstElementChild;
        if (head !== null && head.classList.contains("xui-tabs-head")) {
            // Already connected.
            return;
        }
        
        // User code may have already added its own classes.
        this.classList.add("xui-control");
        // tabindex="0" makes this element focusable and tab-able.
        this.setAttribute("tabindex", "0");
        
        this._closeableTabs = this.getAttribute("closeabletabs");
        this._newTabButton = this.getAttribute("newtabbutton");

        let childElements = [];
        let child = this.firstChild;
        while (child !== null) {
            const nextChild = child.nextSibling;

            if (child.nodeType === Node.ELEMENT_NODE) {
                childElements.push(child);
            }

            this.removeChild(child);
            child = nextChild;
        }

        this._head = document.createElement("div");
        this._head.setAttribute("class", "xui-control xui-tabs-head");
        this.appendChild(this._head);
        
        this._body = document.createElement("div");
        this._body.setAttribute("class", "xui-tabs-body");
        this.appendChild(this._body);
        
        const childCount = 2 * Math.floor(childElements.length / 2);
        for (let i = 0; i < childCount; i += 2) {
            const label = childElements[i];
            const component = childElements[i+1];

            const [tab, host] = this.createTab(label, component);
            this._head.appendChild(tab);
            this._body.appendChild(host);
        }

        if (this._newTabButton !== null) {
            const tab = document.createElement("span");
            let tooltip = this._newTabButton;
            if (tooltip.trim().length === 0) {
                tooltip = "Open a new tab";
            }
            tab.setAttribute("title", tooltip);
            tab.addEventListener("click", this.newTab.bind(this));
            tab.setAttribute("class", "xui-tabs-new-tab");
            
            const button = document.createElement("span");
            button.setAttribute("class",
                              "xui-control xui-small-icon xui-tabs-new-button");
            button.textContent = StockIcon["plus"];
            tab.appendChild(button);
            
            this._head.appendChild(tab);
        }
        
        if (childCount > 0) {
            this.selected = 0;
        }
    }

    createTab(label, component) {
        const tab = document.createElement("span");
        tab.setAttribute("class", "xui-tabs-tab");
        tab.addEventListener("click", this._selectTab);
        tab.appendChild(label);
        
        if (this._closeableTabs !== null) {
            const button = document.createElement("span");
            button.setAttribute("class",
                            "xui-control xui-small-icon xui-tabs-close-button");
            let tooltip = this._closeableTabs;
            if (tooltip.trim().length === 0) {
                tooltip = "Close tab";
            }
            button.setAttribute("title", tooltip);
            button.textContent = StockIcon["cancel"];
            button.addEventListener("click", this._closeTab);
            tab.appendChild(button);
        }
        
        const host = document.createElement("div");
        host.setAttribute("class", "xui-tabs-host");
        host.appendChild(component);

        return [tab, host];
    }
    
    selectTab(event) {
        if (event.button === 0 && event.detail === 1) { 
            // First click on primary button.
            Util.consumeEvent(event);
            
            const tab = event.currentTarget;
            const label = tab.firstElementChild;
            const index = this.indexOfLabel(label);
            if (index >= 0 && this.selected !== index) {
                const tabPair = this.get(index);
                assertOrError(tabPair !== null && Object.is(tabPair[0], label));
                const component = tabPair[1];
                const tabsEvent = Tabs.newEvent("selectingtab",
                                                index, label, component);
                this.dispatchEvent(tabsEvent);
                
                if (tabsEvent.defaultPrevented) {
                    return;
                }
                this.selected = index;
                
                this.dispatchEventLater(Tabs.newEvent("tabselected",
                                                      index, label, component));
            }
        }
    }

    static newEvent(type, index, label, component) {
        return new CustomEvent(type,
                               { bubbles: false, composed: false,
                                 cancelable: type.endsWith("tab"),
                                 detail: { index: index, label: label,
                                           component: component }});
    }
    
    dispatchEventLater(event) {
        window.setTimeout(() => {
            this.dispatchEvent(event);
        });
    }
    
    indexOfLabel(label) {
        const tabs = this.all;
        const count = tabs.length;
        for (let i = 0; i < count; ++i) {
            let [l, c] = tabs[i];
            if (Object.is(l, label)) {
                return i;
            }
        }
        return -1;
    }

    newTab(event) {
        if (event.button === 0 && event.detail === 1) { 
            // First click on primary button.
            Util.consumeEvent(event);

            const index = this.count;
            const tabsEvent = Tabs.newEvent("addingtab", index, null, null);
            this.dispatchEvent(tabsEvent);

            let label = null, component = null;
            if (tabsEvent.defaultPrevented ||
                (label = tabsEvent.detail.label) === null ||
                (component = tabsEvent.detail.component) === null) {
                return;
            }
            this.add(label, component);

            this.dispatchEventLater(Tabs.newEvent("tabadded",
                                                  index, label, component));
        }
    }
    
    closeTab(event) {
        if (event.button === 0 && event.detail === 1) { 
            Util.consumeEvent(event);
            
            const button = event.currentTarget;
            const label = button.parentElement.firstElementChild;
            const index = this.indexOfLabel(label);
            if (index >= 0) {
                const tabPair = this.get(index);
                assertOrError(tabPair !== null && Object.is(tabPair[0], label));
                const component = tabPair[1];
                const tabsEvent = Tabs.newEvent("closingtab",
                                                index, label, component);
                this.dispatchEvent(tabsEvent);
                
                if (tabsEvent.defaultPrevented) {
                    return;
                }
                this.remove(index);
                
                this.dispatchEventLater(Tabs.newEvent("tabclosed",
                                                      index, label, component));
            }
        }
    }

    get selected() {
        let child = this._body.firstElementChild;
        let i = 0;
        while (child !== null) {
            if (child.classList.contains("xui-tabs-selected")) {
                return i;
            }
            child = child.nextElementSibling;
            ++i;
        }
        return -1;
    }

    set selected(index) {
        const tabs = this._head.children;
        const hosts = this._body.children;
        const count = hosts.length; // Not tabs.length if closeabletabs!==null.
        for (let i = 0; i < count; ++i) {
            const tab = tabs[i];
            const host = hosts[i];

            if (i === index) {
                tab.classList.add("xui-tabs-selected");
                host.classList.add("xui-tabs-selected");
            } else {
                tab.classList.remove("xui-tabs-selected");
                host.classList.remove("xui-tabs-selected");
            }
        }
    }
    
    get all() {
        if (this._tabPairs === null) {
            let tabPairs = [];
            
            const tabs = this._head.children;
            const hosts = this._body.children;
            const count = hosts.length;
            for (let i = 0; i < count; ++i) {
                tabPairs.push([ tabs.item(i).firstElementChild,
                                hosts.item(i).firstElementChild ]);
            }
            
            this._tabPairs = tabPairs;
        }
        return this._tabPairs;
    }

    get count() {
        return this.all.length;
    }
    
    get(index) {
        const tabPairs = this.all;
        if (index < 0 || index >= tabPairs.length) {
            return null;
        } else {
            return tabPairs[index];
        }
    }
    
    add(label, component) {
        this.insert(this.count, label, component);
    }

    insert(index, label, component) {
        const tabs = this._head.children;
        const hosts = this._body.children;
        const count = hosts.length;
        if (index >= 0 && index <= count) {
            let beforeTab, beforeHost;
            if (index < count) {
                beforeTab = tabs[index];
                beforeHost = hosts[index];
            } else {
                // If newTabButton!==null then tabs[count] is the "plus" button.
                beforeTab = (this._newTabButton !== null)? tabs[count] : null;
                beforeHost = null;
            }
            
            const [tab, host] = this.createTab(label, component);
            this._head.insertBefore(tab, beforeTab);
            this._body.insertBefore(host, beforeHost);
            this._tabPairs = null;

            this.selected = index;
        }
    }

    remove(index) {
        let removed = null;
        
        const tabs = this._head.children;
        const hosts = this._body.children;
        const count = hosts.length;
        if (index >= 0 && index < count) {
            let select = -1;
            if (index + 1 < count) {
                select = index; // Select following tab.
            } else {
                select = index - 1; // Select preceding tab, if any (select=-1).
            }

            removed = [ tabs[index].firstElementChild,
                        hosts[index].firstElementChild ];
            this._head.removeChild(tabs[index]);
            this._body.removeChild(hosts[index]);
            this._tabPairs = null;

            if (select >= 0) {
                this.selected = select;
            }
        }

        return removed;
    }
}

window.customElements.define("xui-tabs", Tabs);