@magic-spells/tab-group 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ /**
6
+ * @module TabGroup
7
+ * A fully accessible tab group web component
8
+ */
9
+
10
+ /**
11
+ * @class TabGroup
12
+ * the parent container that coordinates tabs and panels
13
+ */
14
+ class TabGroup extends HTMLElement {
15
+ // static counter to ensure global unique ids for tabs and panels
16
+ static tabCount = 0;
17
+ static panelCount = 0;
18
+
19
+ constructor() {
20
+ super();
21
+ // ensure that the number of <tab-button> and <tab-panel> elements match
22
+ // note: in some scenarios the child elements might not be available in the constructor,
23
+ // so adjust as necessary or consider running this check in connectedCallback()
24
+ this.ensureConsistentTabsAndPanels();
25
+ }
26
+
27
+ /**
28
+ * @function ensureConsistentTabsAndPanels
29
+ * makes sure there is an equal number of <tab-button> and <tab-panel> elements.
30
+ * if there are more panels than tabs, inject extra tab buttons.
31
+ * if there are more tabs than panels, inject extra panels.
32
+ */
33
+ ensureConsistentTabsAndPanels() {
34
+ // get current tabs and panels within the tab group
35
+ let tabs = this.querySelectorAll("tab-button");
36
+ let panels = this.querySelectorAll("tab-panel");
37
+
38
+ // if there are more panels than tabs
39
+ if (panels.length > tabs.length) {
40
+ const difference = panels.length - tabs.length;
41
+ // try to find a <tab-list> to insert new tabs
42
+ let tabList = this.querySelector("tab-list");
43
+ if (!tabList) {
44
+ // if not present, create one and insert it at the beginning
45
+ tabList = document.createElement("tab-list");
46
+ this.insertBefore(tabList, this.firstChild);
47
+ }
48
+ // inject extra <tab-button> elements into the tab list
49
+ for (let i = 0; i < difference; i++) {
50
+ const newTab = document.createElement("tab-button");
51
+ newTab.textContent = "default tab";
52
+ tabList.appendChild(newTab);
53
+ }
54
+ }
55
+ // if there are more tabs than panels
56
+ else if (tabs.length > panels.length) {
57
+ const difference = tabs.length - panels.length;
58
+ // inject extra <tab-panel> elements at the end of the tab group
59
+ for (let i = 0; i < difference; i++) {
60
+ const newPanel = document.createElement("tab-panel");
61
+ newPanel.innerHTML = "<p>default panel content</p>";
62
+ this.appendChild(newPanel);
63
+ }
64
+ }
65
+ }
66
+
67
+ /**
68
+ * called when the element is connected to the dom
69
+ */
70
+ connectedCallback() {
71
+ const _ = this;
72
+
73
+ // find the <tab-list> element (should be exactly one)
74
+ _.tabList = _.querySelector("tab-list");
75
+ if (!_.tabList) return;
76
+
77
+ // find all <tab-button> elements inside the <tab-list>
78
+ _.tabButtons = Array.from(_.tabList.querySelectorAll("tab-button"));
79
+
80
+ // find all <tab-panel> elements inside the <tab-group>
81
+ _.tabPanels = Array.from(_.querySelectorAll("tab-panel"));
82
+
83
+ // initialize each tab-button with roles, ids and aria attributes
84
+ _.tabButtons.forEach((tab, index) => {
85
+ const tabIndex = TabGroup.tabCount++;
86
+
87
+ // generate a unique id for each tab, e.g. "tab-0", "tab-1", ...
88
+ const tabId = `tab-${tabIndex}`;
89
+ tab.id = tabId;
90
+
91
+ // generate a corresponding panel id, e.g. "panel-0"
92
+ const panelId = `panel-${tabIndex}`;
93
+ tab.setAttribute("role", "tab");
94
+ tab.setAttribute("aria-controls", panelId);
95
+
96
+ // first tab is active by default
97
+ if (index === 0) {
98
+ tab.setAttribute("aria-selected", "true");
99
+ tab.setAttribute("tabindex", "0");
100
+ } else {
101
+ tab.setAttribute("aria-selected", "false");
102
+ tab.setAttribute("tabindex", "-1");
103
+ }
104
+ });
105
+
106
+ // initialize each tab-panel with roles, ids and aria attributes
107
+ _.tabPanels.forEach((panel, index) => {
108
+ const panelIndex = TabGroup.panelCount++;
109
+ const panelId = `panel-${panelIndex}`;
110
+ panel.id = panelId;
111
+
112
+ panel.setAttribute("role", "tabpanel");
113
+ panel.setAttribute("aria-labelledby", `tab-${panelIndex}`);
114
+
115
+ // hide panels except for the first one
116
+ panel.hidden = index !== 0;
117
+ });
118
+
119
+ // set up keyboard navigation and click delegation on the <tab-list>
120
+ _.tabList.setAttribute("role", "tablist");
121
+ _.tabList.addEventListener("keydown", (e) => _.onKeyDown(e));
122
+ _.tabList.addEventListener("click", (e) => _.onClick(e));
123
+ }
124
+
125
+ /**
126
+ * @function setActiveTab
127
+ * activates a tab and updates aria attributes
128
+ * @param {number} index - index of the tab to activate
129
+ */
130
+ setActiveTab(index) {
131
+ const _ = this;
132
+ const previousIndex = _.tabButtons.findIndex(tab => tab.getAttribute("aria-selected") === "true");
133
+
134
+ // update each tab-button
135
+ _.tabButtons.forEach((tab, i) => {
136
+ const isActive = i === index;
137
+ tab.setAttribute("aria-selected", isActive ? "true" : "false");
138
+ tab.setAttribute("tabindex", isActive ? "0" : "-1");
139
+ if (isActive) {
140
+ tab.focus();
141
+ }
142
+ });
143
+
144
+ // update each tab-panel
145
+ _.tabPanels.forEach((panel, i) => {
146
+ panel.hidden = i !== index;
147
+ });
148
+
149
+ // dispatch event only if the tab actually changed
150
+ if (previousIndex !== index) {
151
+ const detail = {
152
+ previousIndex,
153
+ currentIndex: index,
154
+ previousTab: _.tabButtons[previousIndex],
155
+ currentTab: _.tabButtons[index],
156
+ previousPanel: _.tabPanels[previousIndex],
157
+ currentPanel: _.tabPanels[index]
158
+ };
159
+ _.dispatchEvent(new CustomEvent('tabchange', { detail, bubbles: true }));
160
+ }
161
+ }
162
+
163
+ /**
164
+ * @function onClick
165
+ * handles click events on the <tab-list> via event delegation
166
+ * @param {MouseEvent} e - the click event
167
+ */
168
+ onClick(e) {
169
+ const _ = this;
170
+ // check if the click occurred on or within a <tab-button>
171
+ const tabButton = e.target.closest("tab-button");
172
+ if (!tabButton) return;
173
+
174
+ // determine the index of the clicked tab-button
175
+ const index = _.tabButtons.indexOf(tabButton);
176
+ if (index === -1) return;
177
+
178
+ // activate the tab with the corresponding index
179
+ _.setActiveTab(index);
180
+ }
181
+
182
+ /**
183
+ * @function onKeyDown
184
+ * handles keyboard navigation for the tabs
185
+ * @param {KeyboardEvent} e - the keydown event
186
+ */
187
+ onKeyDown(e) {
188
+ const _ = this;
189
+ // only process keys if focus is on a <tab-button>
190
+ const targetIndex = _.tabButtons.indexOf(e.target);
191
+ if (targetIndex === -1) return;
192
+
193
+ let newIndex = targetIndex;
194
+ switch (e.key) {
195
+ case "ArrowLeft":
196
+ case "ArrowUp":
197
+ // move to the previous tab (wrap around if necessary)
198
+ newIndex = targetIndex > 0 ? targetIndex - 1 : _.tabButtons.length - 1;
199
+ e.preventDefault();
200
+ break;
201
+ case "ArrowRight":
202
+ case "ArrowDown":
203
+ // move to the next tab (wrap around if necessary)
204
+ newIndex = (targetIndex + 1) % _.tabButtons.length;
205
+ e.preventDefault();
206
+ break;
207
+ case "Home":
208
+ // jump to the first tab
209
+ newIndex = 0;
210
+ e.preventDefault();
211
+ break;
212
+ case "End":
213
+ // jump to the last tab
214
+ newIndex = _.tabButtons.length - 1;
215
+ e.preventDefault();
216
+ break;
217
+ default:
218
+ return; // ignore other keys
219
+ }
220
+ _.setActiveTab(newIndex);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * @class TabList
226
+ * a container for the <tab-button> elements
227
+ */
228
+ class TabList extends HTMLElement {
229
+ constructor() {
230
+ super();
231
+ }
232
+
233
+ connectedCallback() {
234
+ // additional logic or styling can be added here if desired
235
+ }
236
+ }
237
+
238
+ /**
239
+ * @class TabButton
240
+ * a single tab button element
241
+ */
242
+ class TabButton extends HTMLElement {
243
+ constructor() {
244
+ super();
245
+ }
246
+
247
+ connectedCallback() {
248
+ // note: role and other attributes are handled by the parent
249
+ }
250
+ }
251
+
252
+ /**
253
+ * @class TabPanel
254
+ * a single tab panel element
255
+ */
256
+ class TabPanel extends HTMLElement {
257
+ constructor() {
258
+ super();
259
+ }
260
+
261
+ connectedCallback() {
262
+ // note: role and other attributes are handled by the parent
263
+ }
264
+ }
265
+
266
+ // define the custom elements
267
+ customElements.define("tab-group", TabGroup);
268
+ customElements.define("tab-list", TabList);
269
+ customElements.define("tab-button", TabButton);
270
+ customElements.define("tab-panel", TabPanel);
271
+
272
+ exports.TabGroup = TabGroup;
273
+ exports.default = TabGroup;
274
+ //# sourceMappingURL=tab-group.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tab-group.cjs.js","sources":["../src/tab-group.js"],"sourcesContent":["import './index.scss';\n\n/**\n * @module TabGroup\n * A fully accessible tab group web component\n */\n\n/**\n * @class TabGroup\n * the parent container that coordinates tabs and panels\n */\nexport default class TabGroup extends HTMLElement {\n\t// static counter to ensure global unique ids for tabs and panels\n\tstatic tabCount = 0;\n\tstatic panelCount = 0;\n\n\tconstructor() {\n\t\tsuper();\n\t\t// ensure that the number of <tab-button> and <tab-panel> elements match\n\t\t// note: in some scenarios the child elements might not be available in the constructor,\n\t\t// so adjust as necessary or consider running this check in connectedCallback()\n\t\tthis.ensureConsistentTabsAndPanels();\n\t}\n\n\t/**\n\t * @function ensureConsistentTabsAndPanels\n\t * makes sure there is an equal number of <tab-button> and <tab-panel> elements.\n\t * if there are more panels than tabs, inject extra tab buttons.\n\t * if there are more tabs than panels, inject extra panels.\n\t */\n\tensureConsistentTabsAndPanels() {\n\t\t// get current tabs and panels within the tab group\n\t\tlet tabs = this.querySelectorAll(\"tab-button\");\n\t\tlet panels = this.querySelectorAll(\"tab-panel\");\n\n\t\t// if there are more panels than tabs\n\t\tif (panels.length > tabs.length) {\n\t\t\tconst difference = panels.length - tabs.length;\n\t\t\t// try to find a <tab-list> to insert new tabs\n\t\t\tlet tabList = this.querySelector(\"tab-list\");\n\t\t\tif (!tabList) {\n\t\t\t\t// if not present, create one and insert it at the beginning\n\t\t\t\ttabList = document.createElement(\"tab-list\");\n\t\t\t\tthis.insertBefore(tabList, this.firstChild);\n\t\t\t}\n\t\t\t// inject extra <tab-button> elements into the tab list\n\t\t\tfor (let i = 0; i < difference; i++) {\n\t\t\t\tconst newTab = document.createElement(\"tab-button\");\n\t\t\t\tnewTab.textContent = \"default tab\";\n\t\t\t\ttabList.appendChild(newTab);\n\t\t\t}\n\t\t}\n\t\t// if there are more tabs than panels\n\t\telse if (tabs.length > panels.length) {\n\t\t\tconst difference = tabs.length - panels.length;\n\t\t\t// inject extra <tab-panel> elements at the end of the tab group\n\t\t\tfor (let i = 0; i < difference; i++) {\n\t\t\t\tconst newPanel = document.createElement(\"tab-panel\");\n\t\t\t\tnewPanel.innerHTML = \"<p>default panel content</p>\";\n\t\t\t\tthis.appendChild(newPanel);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * called when the element is connected to the dom\n\t */\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// find the <tab-list> element (should be exactly one)\n\t\t_.tabList = _.querySelector(\"tab-list\");\n\t\tif (!_.tabList) return;\n\n\t\t// find all <tab-button> elements inside the <tab-list>\n\t\t_.tabButtons = Array.from(_.tabList.querySelectorAll(\"tab-button\"));\n\n\t\t// find all <tab-panel> elements inside the <tab-group>\n\t\t_.tabPanels = Array.from(_.querySelectorAll(\"tab-panel\"));\n\n\t\t// initialize each tab-button with roles, ids and aria attributes\n\t\t_.tabButtons.forEach((tab, index) => {\n\t\t\tconst tabIndex = TabGroup.tabCount++;\n\n\t\t\t// generate a unique id for each tab, e.g. \"tab-0\", \"tab-1\", ...\n\t\t\tconst tabId = `tab-${tabIndex}`;\n\t\t\ttab.id = tabId;\n\n\t\t\t// generate a corresponding panel id, e.g. \"panel-0\"\n\t\t\tconst panelId = `panel-${tabIndex}`;\n\t\t\ttab.setAttribute(\"role\", \"tab\");\n\t\t\ttab.setAttribute(\"aria-controls\", panelId);\n\n\t\t\t// first tab is active by default\n\t\t\tif (index === 0) {\n\t\t\t\ttab.setAttribute(\"aria-selected\", \"true\");\n\t\t\t\ttab.setAttribute(\"tabindex\", \"0\");\n\t\t\t} else {\n\t\t\t\ttab.setAttribute(\"aria-selected\", \"false\");\n\t\t\t\ttab.setAttribute(\"tabindex\", \"-1\");\n\t\t\t}\n\t\t});\n\n\t\t// initialize each tab-panel with roles, ids and aria attributes\n\t\t_.tabPanels.forEach((panel, index) => {\n\t\t\tconst panelIndex = TabGroup.panelCount++;\n\t\t\tconst panelId = `panel-${panelIndex}`;\n\t\t\tpanel.id = panelId;\n\n\t\t\tpanel.setAttribute(\"role\", \"tabpanel\");\n\t\t\tpanel.setAttribute(\"aria-labelledby\", `tab-${panelIndex}`);\n\n\t\t\t// hide panels except for the first one\n\t\t\tpanel.hidden = index !== 0;\n\t\t});\n\n\t\t// set up keyboard navigation and click delegation on the <tab-list>\n\t\t_.tabList.setAttribute(\"role\", \"tablist\");\n\t\t_.tabList.addEventListener(\"keydown\", (e) => _.onKeyDown(e));\n\t\t_.tabList.addEventListener(\"click\", (e) => _.onClick(e));\n\t}\n\n\t/**\n\t * @function setActiveTab\n\t * activates a tab and updates aria attributes\n\t * @param {number} index - index of the tab to activate\n\t */\n\tsetActiveTab(index) {\n\t\tconst _ = this;\n\t\tconst previousIndex = _.tabButtons.findIndex(tab => tab.getAttribute(\"aria-selected\") === \"true\");\n\n\t\t// update each tab-button\n\t\t_.tabButtons.forEach((tab, i) => {\n\t\t\tconst isActive = i === index;\n\t\t\ttab.setAttribute(\"aria-selected\", isActive ? \"true\" : \"false\");\n\t\t\ttab.setAttribute(\"tabindex\", isActive ? \"0\" : \"-1\");\n\t\t\tif (isActive) {\n\t\t\t\ttab.focus();\n\t\t\t}\n\t\t});\n\n\t\t// update each tab-panel\n\t\t_.tabPanels.forEach((panel, i) => {\n\t\t\tpanel.hidden = i !== index;\n\t\t});\n\n\t\t// dispatch event only if the tab actually changed\n\t\tif (previousIndex !== index) {\n\t\t\tconst detail = {\n\t\t\t\tpreviousIndex,\n\t\t\t\tcurrentIndex: index,\n\t\t\t\tpreviousTab: _.tabButtons[previousIndex],\n\t\t\t\tcurrentTab: _.tabButtons[index],\n\t\t\t\tpreviousPanel: _.tabPanels[previousIndex],\n\t\t\t\tcurrentPanel: _.tabPanels[index]\n\t\t\t};\n\t\t\t_.dispatchEvent(new CustomEvent('tabchange', { detail, bubbles: true }));\n\t\t}\n\t}\n\n\t/**\n\t * @function onClick\n\t * handles click events on the <tab-list> via event delegation\n\t * @param {MouseEvent} e - the click event\n\t */\n\tonClick(e) {\n\t\tconst _ = this;\n\t\t// check if the click occurred on or within a <tab-button>\n\t\tconst tabButton = e.target.closest(\"tab-button\");\n\t\tif (!tabButton) return;\n\n\t\t// determine the index of the clicked tab-button\n\t\tconst index = _.tabButtons.indexOf(tabButton);\n\t\tif (index === -1) return;\n\n\t\t// activate the tab with the corresponding index\n\t\t_.setActiveTab(index);\n\t}\n\n\t/**\n\t * @function onKeyDown\n\t * handles keyboard navigation for the tabs\n\t * @param {KeyboardEvent} e - the keydown event\n\t */\n\tonKeyDown(e) {\n\t\tconst _ = this;\n\t\t// only process keys if focus is on a <tab-button>\n\t\tconst targetIndex = _.tabButtons.indexOf(e.target);\n\t\tif (targetIndex === -1) return;\n\n\t\tlet newIndex = targetIndex;\n\t\tswitch (e.key) {\n\t\t\tcase \"ArrowLeft\":\n\t\t\tcase \"ArrowUp\":\n\t\t\t\t// move to the previous tab (wrap around if necessary)\n\t\t\t\tnewIndex = targetIndex > 0 ? targetIndex - 1 : _.tabButtons.length - 1;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tcase \"ArrowRight\":\n\t\t\tcase \"ArrowDown\":\n\t\t\t\t// move to the next tab (wrap around if necessary)\n\t\t\t\tnewIndex = (targetIndex + 1) % _.tabButtons.length;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tcase \"Home\":\n\t\t\t\t// jump to the first tab\n\t\t\t\tnewIndex = 0;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tcase \"End\":\n\t\t\t\t// jump to the last tab\n\t\t\t\tnewIndex = _.tabButtons.length - 1;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn; // ignore other keys\n\t\t}\n\t\t_.setActiveTab(newIndex);\n\t}\n}\n\n/**\n * @class TabList\n * a container for the <tab-button> elements\n */\nclass TabList extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t}\n\n\tconnectedCallback() {\n\t\tconst _ = this;\n\t\t// additional logic or styling can be added here if desired\n\t}\n}\n\n/**\n * @class TabButton\n * a single tab button element\n */\nclass TabButton extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t}\n\n\tconnectedCallback() {\n\t\tconst _ = this;\n\t\t// note: role and other attributes are handled by the parent\n\t}\n}\n\n/**\n * @class TabPanel\n * a single tab panel element\n */\nclass TabPanel extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t}\n\n\tconnectedCallback() {\n\t\tconst _ = this;\n\t\t// note: role and other attributes are handled by the parent\n\t}\n}\n\n// define the custom elements\ncustomElements.define(\"tab-group\", TabGroup);\ncustomElements.define(\"tab-list\", TabList);\ncustomElements.define(\"tab-button\", TabButton);\ncustomElements.define(\"tab-panel\", TabPanel);\n\n// export the main component\nexport { TabGroup };\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,95 @@
1
+ tab-group {
2
+ display: block;
3
+ width: 100%;
4
+ font-family: var(--font-family);
5
+ font-size: var(--font-size-base);
6
+ line-height: var(--line-height);
7
+ color: var(--color-text);
8
+ }
9
+
10
+ tab-list {
11
+ display: flex;
12
+ border-bottom: var(--tab-list-border-bottom, 1px solid var(--color-border));
13
+ margin-bottom: var(--spacing-md);
14
+ overflow-x: auto;
15
+ overflow-y: hidden;
16
+ padding: var(--tab-list-padding, 0.5rem 0.25rem 0);
17
+ gap: var(--tab-list-gap, 0.25rem);
18
+ justify-content: var(--tab-list-justify, flex-start);
19
+ background-color: var(--tab-list-background, transparent);
20
+ background-image: var(--tab-list-background-image, none);
21
+ border-radius: var(--tab-list-radius, 0);
22
+ }
23
+
24
+ tab-button {
25
+ display: block;
26
+ padding: var(--spacing-sm) var(--spacing-md);
27
+ margin: var(--spacing-xs);
28
+ border: 1px solid transparent;
29
+ border-bottom: none;
30
+ background-color: var(--color-background);
31
+ color: var(--color-text);
32
+ cursor: pointer;
33
+ border-radius: var(--tab-button-radius, var(--border-radius) var(--border-radius) 0 0);
34
+ white-space: nowrap;
35
+ user-select: none;
36
+ transition: all var(--transition-duration);
37
+ margin-bottom: -1px;
38
+ position: relative;
39
+ font-size: var(--font-size-base);
40
+ font-weight: var(--tab-button-font-weight, normal);
41
+ text-align: center;
42
+ min-width: 100px;
43
+ }
44
+ tab-button:hover {
45
+ background-color: var(--color-hover);
46
+ }
47
+ tab-button:focus {
48
+ outline: none;
49
+ box-shadow: var(--tab-button-focus-shadow, 0 0 0 2px var(--color-primary));
50
+ }
51
+ tab-button[aria-selected=true] {
52
+ background-color: var(--tab-active-background, var(--color-background));
53
+ border-color: var(--color-border);
54
+ border-bottom: 1px solid var(--color-background);
55
+ font-weight: var(--tab-active-font-weight, 600);
56
+ color: var(--tab-active-color, var(--color-primary));
57
+ box-shadow: var(--tab-active-shadow, none);
58
+ transform: var(--tab-active-transform, none);
59
+ }
60
+ tab-button[aria-selected=true]::after {
61
+ content: "";
62
+ position: absolute;
63
+ bottom: 0;
64
+ left: var(--tab-indicator-left, 0);
65
+ right: var(--tab-indicator-right, 0);
66
+ height: var(--tab-indicator-height, 2px);
67
+ background-color: var(--tab-indicator-color, var(--color-primary));
68
+ border-radius: var(--tab-indicator-radius, 0);
69
+ }
70
+
71
+ tab-panel {
72
+ display: block;
73
+ padding: var(--panel-padding, var(--spacing-md));
74
+ background-color: var(--panel-background, var(--color-background));
75
+ border-radius: var(--panel-radius, 0 0 var(--border-radius) var(--border-radius));
76
+ border: var(--panel-border, none);
77
+ border-top: var(--panel-border-top, none);
78
+ box-shadow: var(--panel-shadow, none);
79
+ transition: var(--panel-transition, all 0.3s ease);
80
+ margin: var(--panel-margin, 0);
81
+ min-height: var(--panel-min-height, 150px);
82
+ }
83
+ tab-panel[hidden] {
84
+ display: none;
85
+ }
86
+
87
+ @media (max-width: 768px) {
88
+ tab-list {
89
+ flex-wrap: wrap;
90
+ }
91
+ tab-button {
92
+ flex: 1 0 auto;
93
+ text-align: center;
94
+ }
95
+ }
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @module TabGroup
3
+ * A fully accessible tab group web component
4
+ */
5
+
6
+ /**
7
+ * @class TabGroup
8
+ * the parent container that coordinates tabs and panels
9
+ */
10
+ class TabGroup extends HTMLElement {
11
+ // static counter to ensure global unique ids for tabs and panels
12
+ static tabCount = 0;
13
+ static panelCount = 0;
14
+
15
+ constructor() {
16
+ super();
17
+ // ensure that the number of <tab-button> and <tab-panel> elements match
18
+ // note: in some scenarios the child elements might not be available in the constructor,
19
+ // so adjust as necessary or consider running this check in connectedCallback()
20
+ this.ensureConsistentTabsAndPanels();
21
+ }
22
+
23
+ /**
24
+ * @function ensureConsistentTabsAndPanels
25
+ * makes sure there is an equal number of <tab-button> and <tab-panel> elements.
26
+ * if there are more panels than tabs, inject extra tab buttons.
27
+ * if there are more tabs than panels, inject extra panels.
28
+ */
29
+ ensureConsistentTabsAndPanels() {
30
+ // get current tabs and panels within the tab group
31
+ let tabs = this.querySelectorAll("tab-button");
32
+ let panels = this.querySelectorAll("tab-panel");
33
+
34
+ // if there are more panels than tabs
35
+ if (panels.length > tabs.length) {
36
+ const difference = panels.length - tabs.length;
37
+ // try to find a <tab-list> to insert new tabs
38
+ let tabList = this.querySelector("tab-list");
39
+ if (!tabList) {
40
+ // if not present, create one and insert it at the beginning
41
+ tabList = document.createElement("tab-list");
42
+ this.insertBefore(tabList, this.firstChild);
43
+ }
44
+ // inject extra <tab-button> elements into the tab list
45
+ for (let i = 0; i < difference; i++) {
46
+ const newTab = document.createElement("tab-button");
47
+ newTab.textContent = "default tab";
48
+ tabList.appendChild(newTab);
49
+ }
50
+ }
51
+ // if there are more tabs than panels
52
+ else if (tabs.length > panels.length) {
53
+ const difference = tabs.length - panels.length;
54
+ // inject extra <tab-panel> elements at the end of the tab group
55
+ for (let i = 0; i < difference; i++) {
56
+ const newPanel = document.createElement("tab-panel");
57
+ newPanel.innerHTML = "<p>default panel content</p>";
58
+ this.appendChild(newPanel);
59
+ }
60
+ }
61
+ }
62
+
63
+ /**
64
+ * called when the element is connected to the dom
65
+ */
66
+ connectedCallback() {
67
+ const _ = this;
68
+
69
+ // find the <tab-list> element (should be exactly one)
70
+ _.tabList = _.querySelector("tab-list");
71
+ if (!_.tabList) return;
72
+
73
+ // find all <tab-button> elements inside the <tab-list>
74
+ _.tabButtons = Array.from(_.tabList.querySelectorAll("tab-button"));
75
+
76
+ // find all <tab-panel> elements inside the <tab-group>
77
+ _.tabPanels = Array.from(_.querySelectorAll("tab-panel"));
78
+
79
+ // initialize each tab-button with roles, ids and aria attributes
80
+ _.tabButtons.forEach((tab, index) => {
81
+ const tabIndex = TabGroup.tabCount++;
82
+
83
+ // generate a unique id for each tab, e.g. "tab-0", "tab-1", ...
84
+ const tabId = `tab-${tabIndex}`;
85
+ tab.id = tabId;
86
+
87
+ // generate a corresponding panel id, e.g. "panel-0"
88
+ const panelId = `panel-${tabIndex}`;
89
+ tab.setAttribute("role", "tab");
90
+ tab.setAttribute("aria-controls", panelId);
91
+
92
+ // first tab is active by default
93
+ if (index === 0) {
94
+ tab.setAttribute("aria-selected", "true");
95
+ tab.setAttribute("tabindex", "0");
96
+ } else {
97
+ tab.setAttribute("aria-selected", "false");
98
+ tab.setAttribute("tabindex", "-1");
99
+ }
100
+ });
101
+
102
+ // initialize each tab-panel with roles, ids and aria attributes
103
+ _.tabPanels.forEach((panel, index) => {
104
+ const panelIndex = TabGroup.panelCount++;
105
+ const panelId = `panel-${panelIndex}`;
106
+ panel.id = panelId;
107
+
108
+ panel.setAttribute("role", "tabpanel");
109
+ panel.setAttribute("aria-labelledby", `tab-${panelIndex}`);
110
+
111
+ // hide panels except for the first one
112
+ panel.hidden = index !== 0;
113
+ });
114
+
115
+ // set up keyboard navigation and click delegation on the <tab-list>
116
+ _.tabList.setAttribute("role", "tablist");
117
+ _.tabList.addEventListener("keydown", (e) => _.onKeyDown(e));
118
+ _.tabList.addEventListener("click", (e) => _.onClick(e));
119
+ }
120
+
121
+ /**
122
+ * @function setActiveTab
123
+ * activates a tab and updates aria attributes
124
+ * @param {number} index - index of the tab to activate
125
+ */
126
+ setActiveTab(index) {
127
+ const _ = this;
128
+ const previousIndex = _.tabButtons.findIndex(tab => tab.getAttribute("aria-selected") === "true");
129
+
130
+ // update each tab-button
131
+ _.tabButtons.forEach((tab, i) => {
132
+ const isActive = i === index;
133
+ tab.setAttribute("aria-selected", isActive ? "true" : "false");
134
+ tab.setAttribute("tabindex", isActive ? "0" : "-1");
135
+ if (isActive) {
136
+ tab.focus();
137
+ }
138
+ });
139
+
140
+ // update each tab-panel
141
+ _.tabPanels.forEach((panel, i) => {
142
+ panel.hidden = i !== index;
143
+ });
144
+
145
+ // dispatch event only if the tab actually changed
146
+ if (previousIndex !== index) {
147
+ const detail = {
148
+ previousIndex,
149
+ currentIndex: index,
150
+ previousTab: _.tabButtons[previousIndex],
151
+ currentTab: _.tabButtons[index],
152
+ previousPanel: _.tabPanels[previousIndex],
153
+ currentPanel: _.tabPanels[index]
154
+ };
155
+ _.dispatchEvent(new CustomEvent('tabchange', { detail, bubbles: true }));
156
+ }
157
+ }
158
+
159
+ /**
160
+ * @function onClick
161
+ * handles click events on the <tab-list> via event delegation
162
+ * @param {MouseEvent} e - the click event
163
+ */
164
+ onClick(e) {
165
+ const _ = this;
166
+ // check if the click occurred on or within a <tab-button>
167
+ const tabButton = e.target.closest("tab-button");
168
+ if (!tabButton) return;
169
+
170
+ // determine the index of the clicked tab-button
171
+ const index = _.tabButtons.indexOf(tabButton);
172
+ if (index === -1) return;
173
+
174
+ // activate the tab with the corresponding index
175
+ _.setActiveTab(index);
176
+ }
177
+
178
+ /**
179
+ * @function onKeyDown
180
+ * handles keyboard navigation for the tabs
181
+ * @param {KeyboardEvent} e - the keydown event
182
+ */
183
+ onKeyDown(e) {
184
+ const _ = this;
185
+ // only process keys if focus is on a <tab-button>
186
+ const targetIndex = _.tabButtons.indexOf(e.target);
187
+ if (targetIndex === -1) return;
188
+
189
+ let newIndex = targetIndex;
190
+ switch (e.key) {
191
+ case "ArrowLeft":
192
+ case "ArrowUp":
193
+ // move to the previous tab (wrap around if necessary)
194
+ newIndex = targetIndex > 0 ? targetIndex - 1 : _.tabButtons.length - 1;
195
+ e.preventDefault();
196
+ break;
197
+ case "ArrowRight":
198
+ case "ArrowDown":
199
+ // move to the next tab (wrap around if necessary)
200
+ newIndex = (targetIndex + 1) % _.tabButtons.length;
201
+ e.preventDefault();
202
+ break;
203
+ case "Home":
204
+ // jump to the first tab
205
+ newIndex = 0;
206
+ e.preventDefault();
207
+ break;
208
+ case "End":
209
+ // jump to the last tab
210
+ newIndex = _.tabButtons.length - 1;
211
+ e.preventDefault();
212
+ break;
213
+ default:
214
+ return; // ignore other keys
215
+ }
216
+ _.setActiveTab(newIndex);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * @class TabList
222
+ * a container for the <tab-button> elements
223
+ */
224
+ class TabList extends HTMLElement {
225
+ constructor() {
226
+ super();
227
+ }
228
+
229
+ connectedCallback() {
230
+ // additional logic or styling can be added here if desired
231
+ }
232
+ }
233
+
234
+ /**
235
+ * @class TabButton
236
+ * a single tab button element
237
+ */
238
+ class TabButton extends HTMLElement {
239
+ constructor() {
240
+ super();
241
+ }
242
+
243
+ connectedCallback() {
244
+ // note: role and other attributes are handled by the parent
245
+ }
246
+ }
247
+
248
+ /**
249
+ * @class TabPanel
250
+ * a single tab panel element
251
+ */
252
+ class TabPanel extends HTMLElement {
253
+ constructor() {
254
+ super();
255
+ }
256
+
257
+ connectedCallback() {
258
+ // note: role and other attributes are handled by the parent
259
+ }
260
+ }
261
+
262
+ // define the custom elements
263
+ customElements.define("tab-group", TabGroup);
264
+ customElements.define("tab-list", TabList);
265
+ customElements.define("tab-button", TabButton);
266
+ customElements.define("tab-panel", TabPanel);
267
+
268
+ export { TabGroup, TabGroup as default };
269
+ //# sourceMappingURL=tab-group.esm.js.map