@magic-spells/tab-group 0.1.0 → 1.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.
- package/LICENSE +1 -1
- package/README.md +78 -183
- package/dist/tab-group.cjs.js +226 -114
- package/dist/tab-group.cjs.js.map +1 -1
- package/dist/tab-group.css +0 -76
- package/dist/tab-group.esm.js +227 -114
- package/dist/tab-group.esm.js.map +1 -1
- package/dist/tab-group.js +226 -114
- package/dist/tab-group.js.map +1 -1
- package/dist/tab-group.min.css +1 -1
- package/dist/tab-group.min.js +1 -1
- package/package.json +12 -13
- package/tab-group.d.ts +46 -0
- package/dist/scss/tab-group.scss +0 -125
- package/dist/scss/variables.scss +0 -0
- package/dist/tab-group.scss +0 -2
- package/src/index.scss +0 -2
- package/src/scss/tab-group.scss +0 -125
- package/src/scss/variables.scss +0 -0
- package/src/tab-group.js +0 -277
package/dist/tab-group.cjs.js
CHANGED
|
@@ -7,23 +7,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
7
7
|
* A fully accessible tab group web component
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
let instanceCount = 0;
|
|
11
|
+
|
|
10
12
|
/**
|
|
11
13
|
* @class TabGroup
|
|
12
14
|
* the parent container that coordinates tabs and panels
|
|
13
15
|
*/
|
|
14
16
|
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
17
|
/**
|
|
28
18
|
* @function ensureConsistentTabsAndPanels
|
|
29
19
|
* makes sure there is an equal number of <tab-button> and <tab-panel> elements.
|
|
@@ -31,24 +21,24 @@ class TabGroup extends HTMLElement {
|
|
|
31
21
|
* if there are more tabs than panels, inject extra panels.
|
|
32
22
|
*/
|
|
33
23
|
ensureConsistentTabsAndPanels() {
|
|
34
|
-
// get current tabs and panels
|
|
35
|
-
let tabs = this.querySelectorAll(
|
|
36
|
-
let panels = this.querySelectorAll(
|
|
24
|
+
// get current tabs and panels scoped to direct children only
|
|
25
|
+
let tabs = this.querySelectorAll(':scope > tab-list > tab-button');
|
|
26
|
+
let panels = this.querySelectorAll(':scope > tab-panel');
|
|
37
27
|
|
|
38
28
|
// if there are more panels than tabs
|
|
39
29
|
if (panels.length > tabs.length) {
|
|
40
30
|
const difference = panels.length - tabs.length;
|
|
41
31
|
// try to find a <tab-list> to insert new tabs
|
|
42
|
-
let tabList = this.querySelector(
|
|
32
|
+
let tabList = this.querySelector(':scope > tab-list');
|
|
43
33
|
if (!tabList) {
|
|
44
34
|
// if not present, create one and insert it at the beginning
|
|
45
|
-
tabList = document.createElement(
|
|
35
|
+
tabList = document.createElement('tab-list');
|
|
46
36
|
this.insertBefore(tabList, this.firstChild);
|
|
47
37
|
}
|
|
48
38
|
// inject extra <tab-button> elements into the tab list
|
|
49
39
|
for (let i = 0; i < difference; i++) {
|
|
50
|
-
const newTab = document.createElement(
|
|
51
|
-
newTab.textContent =
|
|
40
|
+
const newTab = document.createElement('tab-button');
|
|
41
|
+
newTab.textContent = 'default tab';
|
|
52
42
|
tabList.appendChild(newTab);
|
|
53
43
|
}
|
|
54
44
|
}
|
|
@@ -57,8 +47,8 @@ class TabGroup extends HTMLElement {
|
|
|
57
47
|
const difference = tabs.length - panels.length;
|
|
58
48
|
// inject extra <tab-panel> elements at the end of the tab group
|
|
59
49
|
for (let i = 0; i < difference; i++) {
|
|
60
|
-
const newPanel = document.createElement(
|
|
61
|
-
newPanel.innerHTML =
|
|
50
|
+
const newPanel = document.createElement('tab-panel');
|
|
51
|
+
newPanel.innerHTML = '<p>default panel content</p>';
|
|
62
52
|
this.appendChild(newPanel);
|
|
63
53
|
}
|
|
64
54
|
}
|
|
@@ -68,58 +58,149 @@ class TabGroup extends HTMLElement {
|
|
|
68
58
|
* called when the element is connected to the dom
|
|
69
59
|
*/
|
|
70
60
|
connectedCallback() {
|
|
71
|
-
|
|
61
|
+
// assign a stable instance id on first connect
|
|
62
|
+
if (!this._instanceId) {
|
|
63
|
+
this._instanceId = `tg-${instanceCount++}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ensure that the number of <tab-button> and <tab-panel> elements match
|
|
67
|
+
this.ensureConsistentTabsAndPanels();
|
|
72
68
|
|
|
73
69
|
// find the <tab-list> element (should be exactly one)
|
|
74
|
-
|
|
75
|
-
if (!
|
|
70
|
+
this.tabList = this.querySelector(':scope > tab-list');
|
|
71
|
+
if (!this.tabList) return;
|
|
76
72
|
|
|
77
73
|
// find all <tab-button> elements inside the <tab-list>
|
|
78
|
-
|
|
74
|
+
this.tabButtons = Array.from(
|
|
75
|
+
this.tabList.querySelectorAll('tab-button')
|
|
76
|
+
);
|
|
79
77
|
|
|
80
78
|
// find all <tab-panel> elements inside the <tab-group>
|
|
81
|
-
|
|
79
|
+
this.tabPanels = Array.from(this.querySelectorAll(':scope > tab-panel'));
|
|
82
80
|
|
|
83
|
-
|
|
84
|
-
_.tabButtons.forEach((tab, index) => {
|
|
85
|
-
const tabIndex = TabGroup.tabCount++;
|
|
81
|
+
const prefix = this._instanceId;
|
|
86
82
|
|
|
87
|
-
|
|
88
|
-
|
|
83
|
+
// initialize each tab-button with roles, ids and aria attributes
|
|
84
|
+
this.tabButtons.forEach((tab, index) => {
|
|
85
|
+
const tabId = `${prefix}-tab-${index}`;
|
|
86
|
+
const panelId = `${prefix}-panel-${index}`;
|
|
89
87
|
tab.id = tabId;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const panelId = `panel-${tabIndex}`;
|
|
93
|
-
tab.setAttribute("role", "tab");
|
|
94
|
-
tab.setAttribute("aria-controls", panelId);
|
|
88
|
+
tab.setAttribute('role', 'tab');
|
|
89
|
+
tab.setAttribute('aria-controls', panelId);
|
|
95
90
|
|
|
96
91
|
// first tab is active by default
|
|
97
92
|
if (index === 0) {
|
|
98
|
-
tab.setAttribute(
|
|
99
|
-
tab.setAttribute(
|
|
93
|
+
tab.setAttribute('aria-selected', 'true');
|
|
94
|
+
tab.setAttribute('tabindex', '0');
|
|
100
95
|
} else {
|
|
101
|
-
tab.setAttribute(
|
|
102
|
-
tab.setAttribute(
|
|
96
|
+
tab.setAttribute('aria-selected', 'false');
|
|
97
|
+
tab.setAttribute('tabindex', '-1');
|
|
103
98
|
}
|
|
104
99
|
});
|
|
105
100
|
|
|
106
101
|
// initialize each tab-panel with roles, ids and aria attributes
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
const panelId = `panel-${panelIndex}`;
|
|
102
|
+
this.tabPanels.forEach((panel, index) => {
|
|
103
|
+
const panelId = `${prefix}-panel-${index}`;
|
|
110
104
|
panel.id = panelId;
|
|
111
|
-
|
|
112
|
-
panel.setAttribute(
|
|
113
|
-
panel.setAttribute("aria-labelledby", `tab-${panelIndex}`);
|
|
105
|
+
panel.setAttribute('role', 'tabpanel');
|
|
106
|
+
panel.setAttribute('aria-labelledby', `${prefix}-tab-${index}`);
|
|
114
107
|
|
|
115
108
|
// hide panels except for the first one
|
|
116
109
|
panel.hidden = index !== 0;
|
|
117
110
|
});
|
|
118
111
|
|
|
119
112
|
// set up keyboard navigation and click delegation on the <tab-list>
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
113
|
+
this.tabList.setAttribute('role', 'tablist');
|
|
114
|
+
|
|
115
|
+
// store bound handlers so we can remove them in disconnectedCallback
|
|
116
|
+
if (!this._onKeyDown) {
|
|
117
|
+
this._onKeyDown = (e) => this.onKeyDown(e);
|
|
118
|
+
this._onClick = (e) => this.onClick(e);
|
|
119
|
+
}
|
|
120
|
+
this.tabList.addEventListener('keydown', this._onKeyDown);
|
|
121
|
+
this.tabList.addEventListener('click', this._onClick);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* called when the element is disconnected from the dom
|
|
126
|
+
*/
|
|
127
|
+
disconnectedCallback() {
|
|
128
|
+
if (this._animationController) {
|
|
129
|
+
this._animationController.abort();
|
|
130
|
+
this._animationController = null;
|
|
131
|
+
}
|
|
132
|
+
if (this.tabList && this._onKeyDown) {
|
|
133
|
+
this.tabList.removeEventListener('keydown', this._onKeyDown);
|
|
134
|
+
this.tabList.removeEventListener('click', this._onClick);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* reads animation attributes from the element
|
|
140
|
+
*/
|
|
141
|
+
_getAnimateConfig() {
|
|
142
|
+
const outClass = this.getAttribute('animate-out-class');
|
|
143
|
+
const inClass = this.getAttribute('animate-in-class');
|
|
144
|
+
const timeout = parseInt(this.getAttribute('animate-timeout'), 10) || 500;
|
|
145
|
+
return { outClass, inClass, timeout, hasAnimation: !!(outClass || inClass) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* adds a class and waits for animationend (or timeout), with abort support
|
|
150
|
+
*/
|
|
151
|
+
_waitForAnimation(element, className, timeout, signal) {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
if (signal.aborted) {
|
|
154
|
+
resolve();
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
element.classList.add(className);
|
|
159
|
+
|
|
160
|
+
let timer;
|
|
161
|
+
const cleanup = () => {
|
|
162
|
+
element.classList.remove(className);
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
element.removeEventListener('animationend', onEnd);
|
|
165
|
+
signal.removeEventListener('abort', onAbort);
|
|
166
|
+
resolve();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const onEnd = (e) => {
|
|
170
|
+
if (e.target === element) cleanup();
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const onAbort = () => cleanup();
|
|
174
|
+
|
|
175
|
+
element.addEventListener('animationend', onEnd);
|
|
176
|
+
signal.addEventListener('abort', onAbort);
|
|
177
|
+
timer = setTimeout(cleanup, timeout);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* orchestrates out-animation → swap → in-animation
|
|
183
|
+
*/
|
|
184
|
+
async _animateTransition(oldPanel, newPanel, config, controller) {
|
|
185
|
+
const { signal } = controller;
|
|
186
|
+
|
|
187
|
+
// Phase 1: animate out
|
|
188
|
+
if (config.outClass && oldPanel) {
|
|
189
|
+
await this._waitForAnimation(oldPanel, config.outClass, config.timeout, signal);
|
|
190
|
+
}
|
|
191
|
+
if (signal.aborted) return;
|
|
192
|
+
|
|
193
|
+
// Phase 2: swap hidden
|
|
194
|
+
if (oldPanel) oldPanel.hidden = true;
|
|
195
|
+
newPanel.hidden = false;
|
|
196
|
+
|
|
197
|
+
// Phase 3: animate in
|
|
198
|
+
if (config.inClass) {
|
|
199
|
+
if (signal.aborted) return;
|
|
200
|
+
// force reflow so the browser sees the element before animating
|
|
201
|
+
newPanel.offsetHeight;
|
|
202
|
+
await this._waitForAnimation(newPanel, config.inClass, config.timeout, signal);
|
|
203
|
+
}
|
|
123
204
|
}
|
|
124
205
|
|
|
125
206
|
/**
|
|
@@ -128,35 +209,86 @@ class TabGroup extends HTMLElement {
|
|
|
128
209
|
* @param {number} index - index of the tab to activate
|
|
129
210
|
*/
|
|
130
211
|
setActiveTab(index) {
|
|
131
|
-
|
|
132
|
-
const previousIndex =
|
|
212
|
+
if (index < 0 || index >= this.tabButtons.length) return;
|
|
213
|
+
const previousIndex = this.tabButtons.findIndex(
|
|
214
|
+
(tab) => tab.getAttribute('aria-selected') === 'true'
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
// cancel any in-flight animation
|
|
218
|
+
if (this._animationController) {
|
|
219
|
+
this._animationController.abort();
|
|
220
|
+
this._animationController = null;
|
|
221
|
+
// force-hide all panels (clean slate)
|
|
222
|
+
this.tabPanels.forEach((panel) => {
|
|
223
|
+
panel.hidden = true;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
133
226
|
|
|
134
|
-
// update each tab-button
|
|
135
|
-
|
|
227
|
+
// update each tab-button (ARIA updates fire immediately)
|
|
228
|
+
this.tabButtons.forEach((tab, i) => {
|
|
136
229
|
const isActive = i === index;
|
|
137
|
-
tab.setAttribute(
|
|
138
|
-
tab.setAttribute(
|
|
230
|
+
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
231
|
+
tab.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
139
232
|
if (isActive) {
|
|
140
233
|
tab.focus();
|
|
141
234
|
}
|
|
142
235
|
});
|
|
143
236
|
|
|
144
|
-
// update each tab-panel
|
|
145
|
-
_.tabPanels.forEach((panel, i) => {
|
|
146
|
-
panel.hidden = i !== index;
|
|
147
|
-
});
|
|
148
|
-
|
|
149
237
|
// dispatch event only if the tab actually changed
|
|
150
238
|
if (previousIndex !== index) {
|
|
151
239
|
const detail = {
|
|
152
240
|
previousIndex,
|
|
153
241
|
currentIndex: index,
|
|
154
|
-
previousTab:
|
|
155
|
-
currentTab:
|
|
156
|
-
previousPanel:
|
|
157
|
-
currentPanel:
|
|
242
|
+
previousTab: this.tabButtons[previousIndex],
|
|
243
|
+
currentTab: this.tabButtons[index],
|
|
244
|
+
previousPanel: this.tabPanels[previousIndex],
|
|
245
|
+
currentPanel: this.tabPanels[index],
|
|
158
246
|
};
|
|
159
|
-
|
|
247
|
+
this.dispatchEvent(
|
|
248
|
+
new CustomEvent('tabchange', { detail, bubbles: true })
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const config = this._getAnimateConfig();
|
|
253
|
+
const oldPanel = previousIndex >= 0 ? this.tabPanels[previousIndex] : null;
|
|
254
|
+
const newPanel = this.tabPanels[index];
|
|
255
|
+
|
|
256
|
+
if (!config.hasAnimation || previousIndex === index) {
|
|
257
|
+
// instant switch (original behavior)
|
|
258
|
+
this.tabPanels.forEach((panel, i) => {
|
|
259
|
+
panel.hidden = i !== index;
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// animated transition
|
|
265
|
+
const controller = new AbortController();
|
|
266
|
+
this._animationController = controller;
|
|
267
|
+
|
|
268
|
+
// old panel was already force-hidden by abort above, so if we aborted
|
|
269
|
+
// a previous animation, skip animate-out (old panel is already gone)
|
|
270
|
+
const skipOut = oldPanel && oldPanel.hidden;
|
|
271
|
+
|
|
272
|
+
if (skipOut) {
|
|
273
|
+
// just animate in the new panel
|
|
274
|
+
newPanel.hidden = false;
|
|
275
|
+
if (config.inClass) {
|
|
276
|
+
newPanel.offsetHeight;
|
|
277
|
+
this._waitForAnimation(newPanel, config.inClass, config.timeout, controller.signal).then(() => {
|
|
278
|
+
if (this._animationController === controller) {
|
|
279
|
+
this._animationController = null;
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
this._animationController = null;
|
|
284
|
+
}
|
|
285
|
+
} else {
|
|
286
|
+
// full out → swap → in sequence
|
|
287
|
+
this._animateTransition(oldPanel, newPanel, config, controller).then(() => {
|
|
288
|
+
if (this._animationController === controller) {
|
|
289
|
+
this._animationController = null;
|
|
290
|
+
}
|
|
291
|
+
});
|
|
160
292
|
}
|
|
161
293
|
}
|
|
162
294
|
|
|
@@ -166,17 +298,16 @@ class TabGroup extends HTMLElement {
|
|
|
166
298
|
* @param {MouseEvent} e - the click event
|
|
167
299
|
*/
|
|
168
300
|
onClick(e) {
|
|
169
|
-
const _ = this;
|
|
170
301
|
// check if the click occurred on or within a <tab-button>
|
|
171
|
-
const tabButton = e.target.closest(
|
|
302
|
+
const tabButton = e.target.closest('tab-button');
|
|
172
303
|
if (!tabButton) return;
|
|
173
304
|
|
|
174
305
|
// determine the index of the clicked tab-button
|
|
175
|
-
const index =
|
|
306
|
+
const index = this.tabButtons.indexOf(tabButton);
|
|
176
307
|
if (index === -1) return;
|
|
177
308
|
|
|
178
309
|
// activate the tab with the corresponding index
|
|
179
|
-
|
|
310
|
+
this.setActiveTab(index);
|
|
180
311
|
}
|
|
181
312
|
|
|
182
313
|
/**
|
|
@@ -185,39 +316,39 @@ class TabGroup extends HTMLElement {
|
|
|
185
316
|
* @param {KeyboardEvent} e - the keydown event
|
|
186
317
|
*/
|
|
187
318
|
onKeyDown(e) {
|
|
188
|
-
const _ = this;
|
|
189
319
|
// only process keys if focus is on a <tab-button>
|
|
190
|
-
const targetIndex =
|
|
320
|
+
const targetIndex = this.tabButtons.indexOf(e.target);
|
|
191
321
|
if (targetIndex === -1) return;
|
|
192
322
|
|
|
193
323
|
let newIndex = targetIndex;
|
|
194
324
|
switch (e.key) {
|
|
195
|
-
case
|
|
196
|
-
case
|
|
325
|
+
case 'ArrowLeft':
|
|
326
|
+
case 'ArrowUp':
|
|
197
327
|
// move to the previous tab (wrap around if necessary)
|
|
198
|
-
newIndex =
|
|
328
|
+
newIndex =
|
|
329
|
+
targetIndex > 0 ? targetIndex - 1 : this.tabButtons.length - 1;
|
|
199
330
|
e.preventDefault();
|
|
200
331
|
break;
|
|
201
|
-
case
|
|
202
|
-
case
|
|
332
|
+
case 'ArrowRight':
|
|
333
|
+
case 'ArrowDown':
|
|
203
334
|
// move to the next tab (wrap around if necessary)
|
|
204
|
-
newIndex = (targetIndex + 1) %
|
|
335
|
+
newIndex = (targetIndex + 1) % this.tabButtons.length;
|
|
205
336
|
e.preventDefault();
|
|
206
337
|
break;
|
|
207
|
-
case
|
|
338
|
+
case 'Home':
|
|
208
339
|
// jump to the first tab
|
|
209
340
|
newIndex = 0;
|
|
210
341
|
e.preventDefault();
|
|
211
342
|
break;
|
|
212
|
-
case
|
|
343
|
+
case 'End':
|
|
213
344
|
// jump to the last tab
|
|
214
|
-
newIndex =
|
|
345
|
+
newIndex = this.tabButtons.length - 1;
|
|
215
346
|
e.preventDefault();
|
|
216
347
|
break;
|
|
217
348
|
default:
|
|
218
349
|
return; // ignore other keys
|
|
219
350
|
}
|
|
220
|
-
|
|
351
|
+
this.setActiveTab(newIndex);
|
|
221
352
|
}
|
|
222
353
|
}
|
|
223
354
|
|
|
@@ -225,50 +356,31 @@ class TabGroup extends HTMLElement {
|
|
|
225
356
|
* @class TabList
|
|
226
357
|
* a container for the <tab-button> elements
|
|
227
358
|
*/
|
|
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
|
-
}
|
|
359
|
+
class TabList extends HTMLElement {}
|
|
237
360
|
|
|
238
361
|
/**
|
|
239
362
|
* @class TabButton
|
|
240
363
|
* a single tab button element
|
|
241
364
|
*/
|
|
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
|
-
}
|
|
365
|
+
class TabButton extends HTMLElement {}
|
|
251
366
|
|
|
252
367
|
/**
|
|
253
368
|
* @class TabPanel
|
|
254
369
|
* a single tab panel element
|
|
255
370
|
*/
|
|
256
|
-
class TabPanel extends HTMLElement {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
371
|
+
class TabPanel extends HTMLElement {}
|
|
372
|
+
|
|
373
|
+
// define the custom elements (guarded against double-registration and SSR)
|
|
374
|
+
if (typeof window !== 'undefined' && window.customElements) {
|
|
375
|
+
if (!customElements.get('tab-group'))
|
|
376
|
+
customElements.define('tab-group', TabGroup);
|
|
377
|
+
if (!customElements.get('tab-list'))
|
|
378
|
+
customElements.define('tab-list', TabList);
|
|
379
|
+
if (!customElements.get('tab-button'))
|
|
380
|
+
customElements.define('tab-button', TabButton);
|
|
381
|
+
if (!customElements.get('tab-panel'))
|
|
382
|
+
customElements.define('tab-panel', TabPanel);
|
|
264
383
|
}
|
|
265
384
|
|
|
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
385
|
exports.default = TabGroup;
|
|
274
386
|
//# sourceMappingURL=tab-group.cjs.js.map
|
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"tab-group.cjs.js","sources":["../src/tab-group.js"],"sourcesContent":["import './tab-group.css';\n\n/**\n * @module TabGroup\n * A fully accessible tab group web component\n */\n\nlet instanceCount = 0;\n\n/**\n * @class TabGroup\n * the parent container that coordinates tabs and panels\n */\nexport default class TabGroup extends HTMLElement {\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 scoped to direct children only\n\t\tlet tabs = this.querySelectorAll(':scope > tab-list > tab-button');\n\t\tlet panels = this.querySelectorAll(':scope > 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(':scope > 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\t// assign a stable instance id on first connect\n\t\tif (!this._instanceId) {\n\t\t\tthis._instanceId = `tg-${instanceCount++}`;\n\t\t}\n\n\t\t// ensure that the number of <tab-button> and <tab-panel> elements match\n\t\tthis.ensureConsistentTabsAndPanels();\n\n\t\t// find the <tab-list> element (should be exactly one)\n\t\tthis.tabList = this.querySelector(':scope > tab-list');\n\t\tif (!this.tabList) return;\n\n\t\t// find all <tab-button> elements inside the <tab-list>\n\t\tthis.tabButtons = Array.from(\n\t\t\tthis.tabList.querySelectorAll('tab-button')\n\t\t);\n\n\t\t// find all <tab-panel> elements inside the <tab-group>\n\t\tthis.tabPanels = Array.from(this.querySelectorAll(':scope > tab-panel'));\n\n\t\tconst prefix = this._instanceId;\n\n\t\t// initialize each tab-button with roles, ids and aria attributes\n\t\tthis.tabButtons.forEach((tab, index) => {\n\t\t\tconst tabId = `${prefix}-tab-${index}`;\n\t\t\tconst panelId = `${prefix}-panel-${index}`;\n\t\t\ttab.id = tabId;\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\tthis.tabPanels.forEach((panel, index) => {\n\t\t\tconst panelId = `${prefix}-panel-${index}`;\n\t\t\tpanel.id = panelId;\n\t\t\tpanel.setAttribute('role', 'tabpanel');\n\t\t\tpanel.setAttribute('aria-labelledby', `${prefix}-tab-${index}`);\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\tthis.tabList.setAttribute('role', 'tablist');\n\n\t\t// store bound handlers so we can remove them in disconnectedCallback\n\t\tif (!this._onKeyDown) {\n\t\t\tthis._onKeyDown = (e) => this.onKeyDown(e);\n\t\t\tthis._onClick = (e) => this.onClick(e);\n\t\t}\n\t\tthis.tabList.addEventListener('keydown', this._onKeyDown);\n\t\tthis.tabList.addEventListener('click', this._onClick);\n\t}\n\n\t/**\n\t * called when the element is disconnected from the dom\n\t */\n\tdisconnectedCallback() {\n\t\tif (this._animationController) {\n\t\t\tthis._animationController.abort();\n\t\t\tthis._animationController = null;\n\t\t}\n\t\tif (this.tabList && this._onKeyDown) {\n\t\t\tthis.tabList.removeEventListener('keydown', this._onKeyDown);\n\t\t\tthis.tabList.removeEventListener('click', this._onClick);\n\t\t}\n\t}\n\n\t/**\n\t * reads animation attributes from the element\n\t */\n\t_getAnimateConfig() {\n\t\tconst outClass = this.getAttribute('animate-out-class');\n\t\tconst inClass = this.getAttribute('animate-in-class');\n\t\tconst timeout = parseInt(this.getAttribute('animate-timeout'), 10) || 500;\n\t\treturn { outClass, inClass, timeout, hasAnimation: !!(outClass || inClass) };\n\t}\n\n\t/**\n\t * adds a class and waits for animationend (or timeout), with abort support\n\t */\n\t_waitForAnimation(element, className, timeout, signal) {\n\t\treturn new Promise((resolve) => {\n\t\t\tif (signal.aborted) {\n\t\t\t\tresolve();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\telement.classList.add(className);\n\n\t\t\tlet timer;\n\t\t\tconst cleanup = () => {\n\t\t\t\telement.classList.remove(className);\n\t\t\t\tclearTimeout(timer);\n\t\t\t\telement.removeEventListener('animationend', onEnd);\n\t\t\t\tsignal.removeEventListener('abort', onAbort);\n\t\t\t\tresolve();\n\t\t\t};\n\n\t\t\tconst onEnd = (e) => {\n\t\t\t\tif (e.target === element) cleanup();\n\t\t\t};\n\n\t\t\tconst onAbort = () => cleanup();\n\n\t\t\telement.addEventListener('animationend', onEnd);\n\t\t\tsignal.addEventListener('abort', onAbort);\n\t\t\ttimer = setTimeout(cleanup, timeout);\n\t\t});\n\t}\n\n\t/**\n\t * orchestrates out-animation → swap → in-animation\n\t */\n\tasync _animateTransition(oldPanel, newPanel, config, controller) {\n\t\tconst { signal } = controller;\n\n\t\t// Phase 1: animate out\n\t\tif (config.outClass && oldPanel) {\n\t\t\tawait this._waitForAnimation(oldPanel, config.outClass, config.timeout, signal);\n\t\t}\n\t\tif (signal.aborted) return;\n\n\t\t// Phase 2: swap hidden\n\t\tif (oldPanel) oldPanel.hidden = true;\n\t\tnewPanel.hidden = false;\n\n\t\t// Phase 3: animate in\n\t\tif (config.inClass) {\n\t\t\tif (signal.aborted) return;\n\t\t\t// force reflow so the browser sees the element before animating\n\t\t\tnewPanel.offsetHeight;\n\t\t\tawait this._waitForAnimation(newPanel, config.inClass, config.timeout, signal);\n\t\t}\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\tif (index < 0 || index >= this.tabButtons.length) return;\n\t\tconst previousIndex = this.tabButtons.findIndex(\n\t\t\t(tab) => tab.getAttribute('aria-selected') === 'true'\n\t\t);\n\n\t\t// cancel any in-flight animation\n\t\tif (this._animationController) {\n\t\t\tthis._animationController.abort();\n\t\t\tthis._animationController = null;\n\t\t\t// force-hide all panels (clean slate)\n\t\t\tthis.tabPanels.forEach((panel) => {\n\t\t\t\tpanel.hidden = true;\n\t\t\t});\n\t\t}\n\n\t\t// update each tab-button (ARIA updates fire immediately)\n\t\tthis.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// 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: this.tabButtons[previousIndex],\n\t\t\t\tcurrentTab: this.tabButtons[index],\n\t\t\t\tpreviousPanel: this.tabPanels[previousIndex],\n\t\t\t\tcurrentPanel: this.tabPanels[index],\n\t\t\t};\n\t\t\tthis.dispatchEvent(\n\t\t\t\tnew CustomEvent('tabchange', { detail, bubbles: true })\n\t\t\t);\n\t\t}\n\n\t\tconst config = this._getAnimateConfig();\n\t\tconst oldPanel = previousIndex >= 0 ? this.tabPanels[previousIndex] : null;\n\t\tconst newPanel = this.tabPanels[index];\n\n\t\tif (!config.hasAnimation || previousIndex === index) {\n\t\t\t// instant switch (original behavior)\n\t\t\tthis.tabPanels.forEach((panel, i) => {\n\t\t\t\tpanel.hidden = i !== index;\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// animated transition\n\t\tconst controller = new AbortController();\n\t\tthis._animationController = controller;\n\n\t\t// old panel was already force-hidden by abort above, so if we aborted\n\t\t// a previous animation, skip animate-out (old panel is already gone)\n\t\tconst skipOut = oldPanel && oldPanel.hidden;\n\n\t\tif (skipOut) {\n\t\t\t// just animate in the new panel\n\t\t\tnewPanel.hidden = false;\n\t\t\tif (config.inClass) {\n\t\t\t\tnewPanel.offsetHeight;\n\t\t\t\tthis._waitForAnimation(newPanel, config.inClass, config.timeout, controller.signal).then(() => {\n\t\t\t\t\tif (this._animationController === controller) {\n\t\t\t\t\t\tthis._animationController = null;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t} else {\n\t\t\t\tthis._animationController = null;\n\t\t\t}\n\t\t} else {\n\t\t\t// full out → swap → in sequence\n\t\t\tthis._animateTransition(oldPanel, newPanel, config, controller).then(() => {\n\t\t\t\tif (this._animationController === controller) {\n\t\t\t\t\tthis._animationController = null;\n\t\t\t\t}\n\t\t\t});\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\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 = this.tabButtons.indexOf(tabButton);\n\t\tif (index === -1) return;\n\n\t\t// activate the tab with the corresponding index\n\t\tthis.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\t// only process keys if focus is on a <tab-button>\n\t\tconst targetIndex = this.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 =\n\t\t\t\t\ttargetIndex > 0 ? targetIndex - 1 : this.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) % this.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 = this.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\tthis.setActiveTab(newIndex);\n\t}\n}\n\n/**\n * @class TabList\n * a container for the <tab-button> elements\n */\nclass TabList extends HTMLElement {}\n\n/**\n * @class TabButton\n * a single tab button element\n */\nclass TabButton extends HTMLElement {}\n\n/**\n * @class TabPanel\n * a single tab panel element\n */\nclass TabPanel extends HTMLElement {}\n\n// define the custom elements (guarded against double-registration and SSR)\nif (typeof window !== 'undefined' && window.customElements) {\n\tif (!customElements.get('tab-group'))\n\t\tcustomElements.define('tab-group', TabGroup);\n\tif (!customElements.get('tab-list'))\n\t\tcustomElements.define('tab-list', TabList);\n\tif (!customElements.get('tab-button'))\n\t\tcustomElements.define('tab-button', TabButton);\n\tif (!customElements.get('tab-panel'))\n\t\tcustomElements.define('tab-panel', TabPanel);\n}\n"],"names":[],"mappings":"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"}
|