/** * Content Tabs Component * Tabbed interface for displaying different content sections */ class ContentTabs extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.activeTab = 0; } connectedCallback() { this.render(); this.setupEventListeners(); } get tabs() { const tabsAttr = this.getAttribute("tabs"); return tabsAttr ? tabsAttr.split(",").map((t) => t.trim()) : []; } setupEventListeners() { const tabButtons = this.shadowRoot.querySelectorAll(".tab-button"); tabButtons.forEach((button, index) => { button.addEventListener("click", () => { this.setActiveTab(index); }); // Keyboard navigation button.addEventListener("keydown", (e) => { const tabCount = this.tabs.length; let newIndex = this.activeTab; switch (e.key) { case "ArrowLeft": newIndex = (this.activeTab - 1 + tabCount) % tabCount; break; case "ArrowRight": newIndex = (this.activeTab + 1) % tabCount; break; case "Home": newIndex = 0; break; case "End": newIndex = tabCount - 1; break; default: return; } e.preventDefault(); this.setActiveTab(newIndex); this.shadowRoot.querySelectorAll(".tab-button")[newIndex]?.focus(); }); }); } setActiveTab(index) { this.activeTab = index; this.updateTabs(); this.dispatchEvent( new CustomEvent("tab-change", { bubbles: true, composed: true, detail: { index, tab: this.tabs[index] }, }) ); } updateTabs() { // Update tab buttons const tabButtons = this.shadowRoot.querySelectorAll(".tab-button"); tabButtons.forEach((button, index) => { const isActive = index === this.activeTab; button.classList.toggle("active", isActive); button.setAttribute("aria-selected", isActive.toString()); button.setAttribute("tabindex", isActive ? "0" : "-1"); }); // Update panels in shadow DOM const shadowPanels = this.shadowRoot.querySelectorAll("[role='tabpanel']"); shadowPanels.forEach((panel, index) => { panel.style.display = index === this.activeTab ? "block" : "none"; }); // Update slotted panels const panels = this.querySelectorAll("[slot^='panel-']"); panels.forEach((panel, index) => { panel.style.display = index === this.activeTab ? "block" : "none"; }); } render() { const tabsHtml = this.tabs .map( (tab, index) => ` ` ) .join(""); this.shadowRoot.innerHTML = `
${tabsHtml}
`; // Initialize panel visibility setTimeout(() => this.updateTabs(), 0); } } customElements.define("content-tabs", ContentTabs);