48 lines
1.7 KiB
JavaScript
48 lines
1.7 KiB
JavaScript
/**
|
|
* Coordinate tabs and their aria-controls panels inside one tab list.
|
|
*
|
|
* Tabs identify their logical name with data-tab. Selecting a tab updates the
|
|
* ARIA state, panel visibility and roving keyboard focus.
|
|
*/
|
|
export class TabSet {
|
|
constructor(element) {
|
|
this.element = element;
|
|
this.tabs = Array.from(element.querySelectorAll('[role="tab"]'));
|
|
this.selectionHandlers = [];
|
|
|
|
this.element.addEventListener("click", (event) => {
|
|
const tab = event.target.closest('[role="tab"]');
|
|
if (tab && this.element.contains(tab)) this.select(tab.dataset.tab);
|
|
});
|
|
this.element.addEventListener("keydown", (event) => this.handleKeydown(event));
|
|
}
|
|
|
|
/** Select a named tab and show the panel named by its aria-controls value. */
|
|
select(name) {
|
|
for (const tab of this.tabs) {
|
|
const selected = tab.dataset.tab === name;
|
|
tab.setAttribute("aria-selected", String(selected));
|
|
tab.tabIndex = selected ? 0 : -1;
|
|
const panel = document.getElementById(tab.getAttribute("aria-controls"));
|
|
if (panel) panel.classList.toggle("hidden", !selected);
|
|
}
|
|
for (const handler of this.selectionHandlers) handler(name);
|
|
}
|
|
|
|
onSelect(handler) {
|
|
this.selectionHandlers.push(handler);
|
|
return this;
|
|
}
|
|
|
|
handleKeydown(event) {
|
|
if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) return;
|
|
const current = this.tabs.indexOf(event.target.closest('[role="tab"]'));
|
|
if (current < 0) return;
|
|
event.preventDefault();
|
|
const next = event.key === "Home" ? 0 : event.key === "End" ? this.tabs.length - 1 :
|
|
(current + (event.key === "ArrowRight" ? 1 : -1) + this.tabs.length) % this.tabs.length;
|
|
this.select(this.tabs[next].dataset.tab);
|
|
this.tabs[next].focus();
|
|
}
|
|
}
|