From eb0c1a52db557c2f52413f65ca46f3b8ffce978b Mon Sep 17 00:00:00 2001 From: mwiegand Date: Sun, 17 May 2026 21:14:34 +0200 Subject: [PATCH] =?UTF-8?q?feat(js):=20tabs.js=20=E2=80=94=20tab=20activat?= =?UTF-8?q?ion=20+=20expand-to-active-modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- l4d2web/l4d2web/static/js/tabs.js | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 l4d2web/l4d2web/static/js/tabs.js diff --git a/l4d2web/l4d2web/static/js/tabs.js b/l4d2web/l4d2web/static/js/tabs.js new file mode 100644 index 0000000..619b5b8 --- /dev/null +++ b/l4d2web/l4d2web/static/js/tabs.js @@ -0,0 +1,62 @@ +// l4d2web/l4d2web/static/js/tabs.js +// Tabbed strips: any element with [data-tab-strip] activates the first +// [role="tab"] in DOM order (or the one carrying [aria-selected="true"] +// if present) on load, and switches panes on click. The strip's +// [data-active-tab] attribute mirrors the active tab name and is read +// by the expand-to-modal handler. + +(function () { + function activateTab(strip, name) { + strip.querySelectorAll('[role="tab"]').forEach((t) => { + const on = t.dataset.tab === name; + t.setAttribute("aria-selected", on ? "true" : "false"); + t.tabIndex = on ? 0 : -1; + }); + strip.querySelectorAll('[role="tabpanel"]').forEach((p) => { + p.hidden = p.dataset.tab !== name; + }); + strip.dataset.activeTab = name; + } + + function activeTabName(strip) { + const selected = strip.querySelector('[role="tab"][aria-selected="true"]'); + if (selected) return selected.dataset.tab; + const first = strip.querySelector('[role="tab"]'); + return first ? first.dataset.tab : null; + } + + function initStrips(root) { + (root || document).querySelectorAll("[data-tab-strip]").forEach((strip) => { + // Initialise active tab on load. + const name = activeTabName(strip); + if (name) activateTab(strip, name); + + // Bind tab clicks. + strip.addEventListener("click", (ev) => { + const tab = ev.target.closest('[role="tab"]'); + if (tab && strip.contains(tab)) { + activateTab(strip, tab.dataset.tab); + } + }); + + // Bind expand button: opens . + const expand = strip.querySelector(".strip-expand"); + if (expand) { + expand.addEventListener("click", () => { + const name = strip.dataset.activeTab; + if (!name) return; + const dlg = document.getElementById(`${name}-modal`); + if (dlg && typeof dlg.showModal === "function" && !dlg.open) { + dlg.showModal(); + } + }); + } + }); + } + + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => initStrips()); + } else { + initStrips(); + } +})();