# 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);