const initializedRoots = new WeakSet();
function getTabs(root) {
return [...root.querySelectorAll('[role="tab"], .tab')];
}
function getPanels(root) {
return [...root.querySelectorAll('[role="tabpanel"], .tab-panel')];
}
function getPanel(root, tab) {
const panelId = tab.getAttribute("aria-controls");
if(!panelId) {
return null;
}
return root.querySelector(`#${CSS.escape(panelId)}`);
}
function setTabState(tab, isActive) {
tab.classList.toggle("tab-active", isActive);
tab.setAttribute("aria-selected", isActive ? "true" : "false");
tab.setAttribute("tabindex", isActive ? "0" : "-1");
}
function setPanelState(panel, isActive) {
panel.classList.toggle("tab-panel-active", isActive);
panel.toggleAttribute("hidden", !isActive);
}
function activate(tab, options = {}) {
if(!tab || tab.disabled || tab.getAttribute("aria-disabled") === "true") {
return;
}
const root = tab.closest(".tabs") || tab.closest('[role="tablist"]')?.parentElement;
if(!root) {
return;
}
getTabs(root).forEach(item => setTabState(item, item === tab));
getPanels(root).forEach(panel => setPanelState(panel, false));
const panel = getPanel(root, tab);
if(panel) {
setPanelState(panel, true);
}
if(options.focus !== false) {
tab.focus();
}
}
function getNextEnabledTab(tabs, activeIndex, direction) {
for(let offset = 1; offset <= tabs.length; offset++) {
const index = (activeIndex + (offset * direction) + tabs.length) % tabs.length;
const tab = tabs[index];
if(!tab.disabled && tab.getAttribute("aria-disabled") !== "true") {
return tab;
}
}
return tabs[activeIndex];
}
function handleKeydown(event) {
const tab = event.target.closest('[role="tab"], .tab');
if(!tab) {
return;
}
const root = tab.closest(".tabs") || tab.closest('[role="tablist"]')?.parentElement;
const tabs = root ? getTabs(root) : [];
const activeIndex = tabs.indexOf(tab);
if(activeIndex < 0) {
return;
}
let nextTab = null;
if(event.key === "ArrowRight" || event.key === "ArrowDown") {
nextTab = getNextEnabledTab(tabs, activeIndex, 1);
} else if(event.key === "ArrowLeft" || event.key === "ArrowUp") {
nextTab = getNextEnabledTab(tabs, activeIndex, -1);
} else if(event.key === "Home") {
nextTab = getNextEnabledTab(tabs, -1, 1);
} else if(event.key === "End") {
nextTab = getNextEnabledTab(tabs, 0, -1);
}
if(!nextTab) {
return;
}
event.preventDefault();
activate(nextTab);
}
function prepare(root) {
const tabs = getTabs(root);
const activeTab = tabs.find(tab => tab.classList.contains("tab-active") || tab.getAttribute("aria-selected") === "true")
|| tabs.find(tab => !tab.disabled && tab.getAttribute("aria-disabled") !== "true");
tabs.forEach(tab => {
tab.setAttribute("role", "tab");
setTabState(tab, tab === activeTab);
});
root.querySelectorAll(".tabs-list").forEach(list => {
list.setAttribute("role", "tablist");
});
getPanels(root).forEach(panel => {
panel.setAttribute("role", "tabpanel");
setPanelState(panel, activeTab ? panel === getPanel(root, activeTab) : panel.classList.contains("tab-panel-active"));
});
}
function init(root = document) {
if(initializedRoots.has(root)) {
return;
}
root.querySelectorAll(".tabs").forEach(prepare);
root.addEventListener("click", event => {
const tab = event.target.closest('[role="tab"], .tab');
if(!tab || !root.contains(tab)) {
return;
}
event.preventDefault();
activate(tab, { focus: false });
});
root.addEventListener("keydown", handleKeydown);
initializedRoots.add(root);
}
export default {
init,
activate
};