Review an accessible tabs widget

src/tabs-widget.tsTypeScript
@@ -0,0 +1,51 @@
1+export interface TabDefinition {
2+ id: string;
3+ label: string;
4+ content: string;
5+ disabled?: boolean;
6+}
7+
8+export function mountTabs(root: HTMLElement, definitions: TabDefinition[]): void {
9+ const tabList = document.createElement("div");
10+ tabList.setAttribute("role", "tablist");
11+
12+ const buttons: HTMLButtonElement[] = [];
13+ const panels: HTMLElement[] = [];
14+
15+ function select(selectedIndex: number): void {
16+ buttons.forEach((button, index) => {
17+ button.setAttribute("aria-selected", String(index === selectedIndex));
18+ });
19+ panels.forEach((panel, index) => {
20+ panel.hidden = index !== selectedIndex;
21+ });
22+ }
23+
24+ definitions.forEach((definition, index) => {
25+ const button = document.createElement("button");
26+ button.type = "button";
27+ button.id = `tab-${definition.id}`;
28+ button.textContent = definition.label;
29+ button.setAttribute("role", "tab");
30+ button.setAttribute("aria-controls", "tab-panel");
31+ button.tabIndex = 0;
32+ button.addEventListener("click", () => select(index));
33+ button.addEventListener("keydown", (event) => {
34+ if (event.key === "ArrowRight") {
35+ buttons[index + 1]?.focus();
36+ }
37+ });
38+
39+ const panel = document.createElement("section");
40+ panel.id = "tab-panel";
41+ panel.textContent = definition.content;
42+
43+ buttons.push(button);
44+ panels.push(panel);
45+ tabList.append(button);
46+ root.append(panel);
47+ });
48+
49+ root.prepend(tabList);
50+ select(0);
51+}