/** * Initializes tab navigation * Usage: * - Tab buttons: * - Tab panels:
...
* - Container:
...
* - URL param: ?tab=system */ export function initTabs(root = document) { const containers = root.querySelectorAll('[data-tabs]'); containers.forEach((container) => { if (container.dataset.tabsBound === '1') return; container.dataset.tabsBound = '1'; const tabs = Array.from(container.querySelectorAll('[data-tab]')); const panels = Array.from(container.querySelectorAll('[data-tab-panel]')); if (!tabs.length || !panels.length) return; const storageKey = container.dataset.tabsStorageKey || (container.id ? `tabs:${container.id}` : null); const urlParamName = container.dataset.tabsParam || 'tab'; // Helper to get URL parameter const getUrlParam = (name) => { const params = new URLSearchParams(window.location.search); return params.get(name); }; // Helper to set URL parameter const setUrlParam = (name, value) => { const url = new URL(window.location); url.searchParams.set(name, value); window.history.replaceState({}, '', url); }; // Get initial active tab from URL param, localStorage, or data-tab-default let activeTab = null; const urlTab = getUrlParam(urlParamName); if (urlTab && panels.some(p => p.dataset.tabPanel === urlTab)) { activeTab = urlTab; } else if (storageKey) { try { const stored = localStorage.getItem(storageKey); if (stored && panels.some(p => p.dataset.tabPanel === stored)) { activeTab = stored; } } catch (e) { // localStorage not available } } if (!activeTab) { const defaultTab = container.querySelector('[data-tab-default]'); activeTab = defaultTab?.dataset.tab || tabs[0]?.dataset.tab; } const activateTab = (tabName) => { tabs.forEach(tab => { const isActive = tab.dataset.tab === tabName; tab.classList.toggle('is-active', isActive); tab.setAttribute('aria-selected', isActive ? 'true' : 'false'); }); panels.forEach(panel => { const isVisible = panel.dataset.tabPanel === tabName; panel.hidden = !isVisible; panel.setAttribute('aria-hidden', isVisible ? 'false' : 'true'); }); // Update URL parameter setUrlParam(urlParamName, tabName); // Save to localStorage if (storageKey) { try { localStorage.setItem(storageKey, tabName); } catch (e) { // localStorage not available } } }; // Initialize tabs tabs.forEach(tab => { tab.setAttribute('role', 'tab'); tab.addEventListener('click', (e) => { e.preventDefault(); activateTab(tab.dataset.tab); }); }); panels.forEach(panel => { panel.setAttribute('role', 'tabpanel'); }); container.setAttribute('role', 'tablist'); // Activate initial tab if (activeTab) { activateTab(activeTab); } // Listen for popstate (back/forward navigation) window.addEventListener('popstate', () => { const newTab = getUrlParam(urlParamName); if (newTab && panels.some(p => p.dataset.tabPanel === newTab)) { activateTab(newTab); } }); }); } // Auto-initialize on load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => initTabs()); } else { initTabs(); }