type TabGroup = HTMLDivElement[]; const TAB_CLASS = 'tab'; const TAB_CONTROL_CLASS = 'tab-control'; const TAB_CONTROL_WRAPPER_CLASS = 'tab-controls'; const CONTAINER_CLASS = 'tab-container'; const ACTIVE_CLASS = 'active'; export function initTabs() { const tabs = document.querySelectorAll(`.${TAB_CLASS}`); const tabGroups = groupTabs(Array.from(tabs)); const containers = tabGroups.map(g => wrapTabGroup(g)); containers.forEach(container => { container.addEventListener('click', e => { const target = e.target as HTMLElement; if (target.classList.contains(TAB_CONTROL_CLASS)) { const tabId = target.dataset.id; // deactivate all sibling tabs & controls const tabsInGroup = Array.from(container.querySelectorAll(`.${TAB_CLASS}`)); const controlsInGroup = Array.from(container.querySelectorAll(`.${TAB_CONTROL_CLASS}`)); [...tabsInGroup, ...controlsInGroup].forEach(tab => tab.classList.remove(ACTIVE_CLASS)); // activate the newly selected tab & control target.classList.add(ACTIVE_CLASS); tabsInGroup.filter(t => t.id === tabId).forEach(tab => tab.classList.add(ACTIVE_CLASS)); } }); }); } /** * Groups sibling tabs together. */ function groupTabs(tabs: HTMLDivElement[]): TabGroup[] { const tabGroups: TabGroup[] = []; const remainingTabs = tabs.slice(); let next: HTMLDivElement | undefined; do { next = remainingTabs.shift(); if (next) { const group: TabGroup = [next]; let nextSibling = next.nextElementSibling; while (nextSibling === remainingTabs[0]) { if (nextSibling) { next = remainingTabs.shift(); // tslint:disable-next-line:no-non-null-assertion group.push(next!); } // tslint:disable-next-line:no-non-null-assertion nextSibling = next!.nextElementSibling; } tabGroups.push(group); } } while (next); return tabGroups; } /** * Wrap the tabs of the group in a container div and add the tab controls */ function wrapTabGroup(tabGroup: TabGroup): HTMLDivElement { const tabControls = tabGroup.map(({ title }, i) => { return ``; }).join(''); const wrapper = document.createElement('div'); wrapper.classList.add(CONTAINER_CLASS); wrapper.innerHTML = `
${tabControls}
`; const parent = tabGroup[0].parentElement; if (parent) { parent.insertBefore(wrapper, tabGroup[0]); } tabGroup.forEach((tab, i) => { tab.id = toId(tab.title); if (i === 0) { tab.classList.add(ACTIVE_CLASS); } wrapper.appendChild(tab); }); return wrapper; } /** * Generate a normalized ID based on the title */ function toId(title: string): string { return 'tab-' + title.toLowerCase().replace(/[^a-zA-Z]/g, '-'); }