@magic-spells/tab-group 1.0.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/README.md +48 -2
- package/dist/tab-group.cjs.js +125 -6
- package/dist/tab-group.cjs.js.map +1 -1
- package/dist/tab-group.esm.js +125 -6
- package/dist/tab-group.esm.js.map +1 -1
- package/dist/tab-group.js +125 -6
- package/dist/tab-group.js.map +1 -1
- package/dist/tab-group.min.js +1 -1
- package/package.json +1 -1
- package/tab-group.d.ts +13 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Tab Group
|
|
2
2
|
|
|
3
3
|

|
|
4
|
-

|
|
5
5
|
|
|
6
6
|
A lightweight, accessible tab interface web component with keyboard navigation. Ships only structural CSS — you bring your own styles.
|
|
7
7
|
|
|
@@ -103,6 +103,44 @@ tab-panel[hidden] { display: none; }
|
|
|
103
103
|
|
|
104
104
|
That's it. No fonts, colors, spacing, borders, transitions, shadows, or media queries.
|
|
105
105
|
|
|
106
|
+
## Animated Transitions
|
|
107
|
+
|
|
108
|
+
Add CSS animation classes to smoothly transition between panels. The component orchestrates the lifecycle — you define the animations.
|
|
109
|
+
|
|
110
|
+
```html
|
|
111
|
+
<tab-group animate-out-class="fade-out" animate-in-class="fade-in" animate-timeout="500">
|
|
112
|
+
...
|
|
113
|
+
</tab-group>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
| Attribute | Description | Default |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `animate-out-class` | CSS class added to the outgoing panel during exit | none (instant) |
|
|
119
|
+
| `animate-in-class` | CSS class added to the incoming panel during enter | none (instant) |
|
|
120
|
+
| `animate-timeout` | Fallback timeout in ms if `animationend` never fires | `500` |
|
|
121
|
+
|
|
122
|
+
```css
|
|
123
|
+
@keyframes fade-out {
|
|
124
|
+
from { opacity: 1; }
|
|
125
|
+
to { opacity: 0; }
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes fade-in {
|
|
129
|
+
from { opacity: 0; }
|
|
130
|
+
to { opacity: 1; }
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
tab-panel.fade-out {
|
|
134
|
+
animation: fade-out 0.25s ease-out forwards;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
tab-panel.fade-in {
|
|
138
|
+
animation: fade-in 0.25s ease-out forwards;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Either attribute works independently. No attributes = original instant behavior. ARIA updates and the `tabchange` event always fire immediately, before any animation. Rapid clicks cancel cleanly via AbortController.
|
|
143
|
+
|
|
106
144
|
## Accessibility
|
|
107
145
|
|
|
108
146
|
The Tab Group component follows the [WAI-ARIA Tabs Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tabs/) with:
|
|
@@ -126,6 +164,14 @@ The Tab Group component follows the [WAI-ARIA Tabs Pattern](https://www.w3.org/W
|
|
|
126
164
|
| `<tab-button>` | Individual tab trigger |
|
|
127
165
|
| `<tab-panel>` | Content container for each tab |
|
|
128
166
|
|
|
167
|
+
### Attributes
|
|
168
|
+
|
|
169
|
+
| Attribute | Element | Description | Default |
|
|
170
|
+
|---|---|---|---|
|
|
171
|
+
| `animate-out-class` | `<tab-group>` | CSS class for exit animation on outgoing panel | none |
|
|
172
|
+
| `animate-in-class` | `<tab-group>` | CSS class for enter animation on incoming panel | none |
|
|
173
|
+
| `animate-timeout` | `<tab-group>` | Fallback timeout (ms) if `animationend` doesn't fire | `500` |
|
|
174
|
+
|
|
129
175
|
### Events
|
|
130
176
|
|
|
131
177
|
| Event | Detail | Description |
|
|
@@ -143,5 +189,5 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
143
189
|
---
|
|
144
190
|
|
|
145
191
|
<p align="center">
|
|
146
|
-
Made
|
|
192
|
+
Made by <a href="https://github.com/coryschulz">Cory Schulz</a>
|
|
147
193
|
</p>
|
package/dist/tab-group.cjs.js
CHANGED
|
@@ -125,12 +125,84 @@ class TabGroup extends HTMLElement {
|
|
|
125
125
|
* called when the element is disconnected from the dom
|
|
126
126
|
*/
|
|
127
127
|
disconnectedCallback() {
|
|
128
|
+
if (this._animationController) {
|
|
129
|
+
this._animationController.abort();
|
|
130
|
+
this._animationController = null;
|
|
131
|
+
}
|
|
128
132
|
if (this.tabList && this._onKeyDown) {
|
|
129
133
|
this.tabList.removeEventListener('keydown', this._onKeyDown);
|
|
130
134
|
this.tabList.removeEventListener('click', this._onClick);
|
|
131
135
|
}
|
|
132
136
|
}
|
|
133
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
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
134
206
|
/**
|
|
135
207
|
* @function setActiveTab
|
|
136
208
|
* activates a tab and updates aria attributes
|
|
@@ -142,7 +214,17 @@ class TabGroup extends HTMLElement {
|
|
|
142
214
|
(tab) => tab.getAttribute('aria-selected') === 'true'
|
|
143
215
|
);
|
|
144
216
|
|
|
145
|
-
//
|
|
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
|
+
}
|
|
226
|
+
|
|
227
|
+
// update each tab-button (ARIA updates fire immediately)
|
|
146
228
|
this.tabButtons.forEach((tab, i) => {
|
|
147
229
|
const isActive = i === index;
|
|
148
230
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
@@ -152,11 +234,6 @@ class TabGroup extends HTMLElement {
|
|
|
152
234
|
}
|
|
153
235
|
});
|
|
154
236
|
|
|
155
|
-
// update each tab-panel
|
|
156
|
-
this.tabPanels.forEach((panel, i) => {
|
|
157
|
-
panel.hidden = i !== index;
|
|
158
|
-
});
|
|
159
|
-
|
|
160
237
|
// dispatch event only if the tab actually changed
|
|
161
238
|
if (previousIndex !== index) {
|
|
162
239
|
const detail = {
|
|
@@ -171,6 +248,48 @@ class TabGroup extends HTMLElement {
|
|
|
171
248
|
new CustomEvent('tabchange', { detail, bubbles: true })
|
|
172
249
|
);
|
|
173
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
|
+
});
|
|
292
|
+
}
|
|
174
293
|
}
|
|
175
294
|
|
|
176
295
|
/**
|
|
@@ -1 +1 @@
|
|
|
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.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 * @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// update each tab-button\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// update each tab-panel\n\t\tthis.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: 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\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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"}
|
|
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":";;;;AAEA;AACA;AACA;AACA;AACA;AACA,IAAI,aAAa,GAAG,CAAC,CAAC;AACtB;AACA;AACA;AACA;AACA;AACe,MAAM,QAAQ,SAAS,WAAW,CAAC;AAClD;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,6BAA6B,GAAG;AACjC;AACA,EAAE,IAAI,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,gCAAgC,CAAC,CAAC;AACrE,EAAE,IAAI,MAAM,GAAG,IAAI,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC;AAC3D;AACA;AACA,EAAE,IAAI,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE;AACnC,GAAG,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;AAClD;AACA,GAAG,IAAI,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;AACzD,GAAG,IAAI,CAAC,OAAO,EAAE;AACjB;AACA,IAAI,OAAO,GAAG,QAAQ,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;AACjD,IAAI,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChD,IAAI;AACJ;AACA,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACxC,IAAI,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;AACxD,IAAI,MAAM,CAAC,WAAW,GAAG,aAAa,CAAC;AACvC,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;AAChC,IAAI;AACJ,GAAG;AACH;AACA,OAAO,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE;AACxC,GAAG,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;AAClD;AACA,GAAG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;AACxC,IAAI,MAAM,QAAQ,GAAG,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC;AACzD,IAAI,QAAQ,CAAC,SAAS,GAAG,8BAA8B,CAAC;AACxD,IAAI,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;AAC/B,IAAI;AACJ,GAAG;AACH,EAAE;AACF;AACA;AACA;AACA;AACA,CAAC,iBAAiB,GAAG;AACrB;AACA,EAAE,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;AACzB,GAAG,IAAI,CAAC,WAAW,GAAG,CAAC,GAAG,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC;AAC9C,GAAG;AACH;AACA;AACA,EAAE,IAAI,CAAC,6BAA6B,EAAE,CAAC;AACvC;AACA;AACA,EAAE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,mBAAmB,CAAC,CAAC;AACzD,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO;AAC5B;AACA;AACA,EAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,IAAI;AAC9B,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,YAAY,CAAC;AAC9C,GAAG,CAAC;AACJ;AACA;AACA,EAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,oBAAoB,CAAC,CAAC,CAAC;AAC3E;AACA,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC;AAClC;AACA;AACA,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,KAAK,KAAK;AAC1C,GAAG,MAAM,KAAK,GAAG,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAC1C,GAAG,MAAM,OAAO,GAAG,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9C,GAAG,GAAG,CAAC,EAAE,GAAG,KAAK,CAAC;AAClB,GAAG,GAAG,CAAC,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACnC,GAAG,GAAG,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAC9C;AACA;AACA,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE;AACpB,IAAI,GAAG,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;AAC9C,IAAI,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;AACtC,IAAI,MAAM;AACV,IAAI,GAAG,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AAC/C,IAAI,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACvC,IAAI;AACJ,GAAG,CAAC,CAAC;AACL;AACA;AACA,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK;AAC3C,GAAG,MAAM,OAAO,GAAG,CAAC,EAAE,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAC9C,GAAG,KAAK,CAAC,EAAE,GAAG,OAAO,CAAC;AACtB,GAAG,KAAK,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AAC1C,GAAG,KAAK,CAAC,YAAY,CAAC,iBAAiB,EAAE,CAAC,EAAE,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;AACnE;AACA;AACA,GAAG,KAAK,CAAC,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC;AAC9B,GAAG,CAAC,CAAC;AACL;AACA;AACA,EAAE,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAC/C;AACA;AACA,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;AACxB,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AAC9C,GAAG,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAC1C,GAAG;AACH,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAC5D,EAAE,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AACxD,EAAE;AACF;AACA;AACA;AACA;AACA,CAAC,oBAAoB,GAAG;AACxB,EAAE,IAAI,IAAI,CAAC,oBAAoB,EAAE;AACjC,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,CAAC;AACrC,GAAG,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;AACpC,GAAG;AACH,EAAE,IAAI,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE;AACvC,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,SAAS,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;AAChE,GAAG,IAAI,CAAC,OAAO,CAAC,mBAAmB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;AAC5D,GAAG;AACH,EAAE;AACF;AACA;AACA;AACA;AACA,CAAC,iBAAiB,GAAG;AACrB,EAAE,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,CAAC,mBAAmB,CAAC,CAAC;AAC1D,EAAE,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC;AACxD,EAAE,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AAC5E,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;AAC/E,EAAE;AACF;AACA;AACA;AACA;AACA,CAAC,iBAAiB,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,EAAE;AACxD,EAAE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK;AAClC,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE;AACvB,IAAI,OAAO,EAAE,CAAC;AACd,IAAI,OAAO;AACX,IAAI;AACJ;AACA,GAAG,OAAO,CAAC,SAAS,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AACpC;AACA,GAAG,IAAI,KAAK,CAAC;AACb,GAAG,MAAM,OAAO,GAAG,MAAM;AACzB,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACxC,IAAI,YAAY,CAAC,KAAK,CAAC,CAAC;AACxB,IAAI,OAAO,CAAC,mBAAmB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;AACvD,IAAI,MAAM,CAAC,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACjD,IAAI,OAAO,EAAE,CAAC;AACd,IAAI,CAAC;AACL;AACA,GAAG,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK;AACxB,IAAI,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,OAAO,EAAE,CAAC;AACxC,IAAI,CAAC;AACL;AACA,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAC;AACnC;AACA,GAAG,OAAO,CAAC,gBAAgB,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;AACnD,GAAG,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAC7C,GAAG,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AACxC,GAAG,CAAC,CAAC;AACL,EAAE;AACF;AACA;AACA;AACA;AACA,CAAC,MAAM,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE;AAClE,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;AAChC;AACA;AACA,EAAE,IAAI,MAAM,CAAC,QAAQ,IAAI,QAAQ,EAAE;AACnC,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AACnF,GAAG;AACH,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO;AAC7B;AACA;AACA,EAAE,IAAI,QAAQ,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC;AACvC,EAAE,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC;AAC1B;AACA;AACA,EAAE,IAAI,MAAM,CAAC,OAAO,EAAE;AACtB,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,OAAO;AAC9B;AACA,GAAG,QAAQ,CAAC,YAAY,CAAC;AACzB,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;AAClF,GAAG;AACH,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,YAAY,CAAC,KAAK,EAAE;AACrB,EAAE,IAAI,KAAK,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO;AAC3D,EAAE,MAAM,aAAa,GAAG,IAAI,CAAC,UAAU,CAAC,SAAS;AACjD,GAAG,CAAC,GAAG,KAAK,GAAG,CAAC,YAAY,CAAC,eAAe,CAAC,KAAK,MAAM;AACxD,GAAG,CAAC;AACJ;AACA;AACA,EAAE,IAAI,IAAI,CAAC,oBAAoB,EAAE;AACjC,GAAG,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,CAAC;AACrC,GAAG,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;AACpC;AACA,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK;AACrC,IAAI,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;AACxB,IAAI,CAAC,CAAC;AACN,GAAG;AACH;AACA;AACA,EAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK;AACtC,GAAG,MAAM,QAAQ,GAAG,CAAC,KAAK,KAAK,CAAC;AAChC,GAAG,GAAG,CAAC,YAAY,CAAC,eAAe,EAAE,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAClE,GAAG,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,QAAQ,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC;AACvD,GAAG,IAAI,QAAQ,EAAE;AACjB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;AAChB,IAAI;AACJ,GAAG,CAAC,CAAC;AACL;AACA;AACA,EAAE,IAAI,aAAa,KAAK,KAAK,EAAE;AAC/B,GAAG,MAAM,MAAM,GAAG;AAClB,IAAI,aAAa;AACjB,IAAI,YAAY,EAAE,KAAK;AACvB,IAAI,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;AAC/C,IAAI,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC;AACtC,IAAI,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC;AAChD,IAAI,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;AACvC,IAAI,CAAC;AACL,GAAG,IAAI,CAAC,aAAa;AACrB,IAAI,IAAI,WAAW,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3D,IAAI,CAAC;AACL,GAAG;AACH;AACA,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC,iBAAiB,EAAE,CAAC;AAC1C,EAAE,MAAM,QAAQ,GAAG,aAAa,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,GAAG,IAAI,CAAC;AAC7E,EAAE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;AACzC;AACA,EAAE,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,aAAa,KAAK,KAAK,EAAE;AACvD;AACA,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,KAAK;AACxC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,KAAK,KAAK,CAAC;AAC/B,IAAI,CAAC,CAAC;AACN,GAAG,OAAO;AACV,GAAG;AACH;AACA;AACA,EAAE,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;AAC3C,EAAE,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC;AACzC;AACA;AACA;AACA,EAAE,MAAM,OAAO,GAAG,QAAQ,IAAI,QAAQ,CAAC,MAAM,CAAC;AAC9C;AACA,EAAE,IAAI,OAAO,EAAE;AACf;AACA,GAAG,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC;AAC3B,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE;AACvB,IAAI,QAAQ,CAAC,YAAY,CAAC;AAC1B,IAAI,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM;AACnG,KAAK,IAAI,IAAI,CAAC,oBAAoB,KAAK,UAAU,EAAE;AACnD,MAAM,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;AACvC,MAAM;AACN,KAAK,CAAC,CAAC;AACP,IAAI,MAAM;AACV,IAAI,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;AACrC,IAAI;AACJ,GAAG,MAAM;AACT;AACA,GAAG,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,CAAC,CAAC,IAAI,CAAC,MAAM;AAC9E,IAAI,IAAI,IAAI,CAAC,oBAAoB,KAAK,UAAU,EAAE;AAClD,KAAK,IAAI,CAAC,oBAAoB,GAAG,IAAI,CAAC;AACtC,KAAK;AACL,IAAI,CAAC,CAAC;AACN,GAAG;AACH,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,OAAO,CAAC,CAAC,EAAE;AACZ;AACA,EAAE,MAAM,SAAS,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;AACnD,EAAE,IAAI,CAAC,SAAS,EAAE,OAAO;AACzB;AACA;AACA,EAAE,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AACnD,EAAE,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,OAAO;AAC3B;AACA;AACA,EAAE,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;AAC3B,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,SAAS,CAAC,CAAC,EAAE;AACd;AACA,EAAE,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxD,EAAE,IAAI,WAAW,KAAK,CAAC,CAAC,EAAE,OAAO;AACjC;AACA,EAAE,IAAI,QAAQ,GAAG,WAAW,CAAC;AAC7B,EAAE,QAAQ,CAAC,CAAC,GAAG;AACf,GAAG,KAAK,WAAW,CAAC;AACpB,GAAG,KAAK,SAAS;AACjB;AACA,IAAI,QAAQ;AACZ,KAAK,WAAW,GAAG,CAAC,GAAG,WAAW,GAAG,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;AACpE,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AACvB,IAAI,MAAM;AACV,GAAG,KAAK,YAAY,CAAC;AACrB,GAAG,KAAK,WAAW;AACnB;AACA,IAAI,QAAQ,GAAG,CAAC,WAAW,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;AAC1D,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AACvB,IAAI,MAAM;AACV,GAAG,KAAK,MAAM;AACd;AACA,IAAI,QAAQ,GAAG,CAAC,CAAC;AACjB,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AACvB,IAAI,MAAM;AACV,GAAG,KAAK,KAAK;AACb;AACA,IAAI,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC;AAC1C,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AACvB,IAAI,MAAM;AACV,GAAG;AACH,IAAI,OAAO;AACX,GAAG;AACH,EAAE,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;AAC9B,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA,MAAM,OAAO,SAAS,WAAW,CAAC,EAAE;AACpC;AACA;AACA;AACA;AACA;AACA,MAAM,SAAS,SAAS,WAAW,CAAC,EAAE;AACtC;AACA;AACA;AACA;AACA;AACA,MAAM,QAAQ,SAAS,WAAW,CAAC,EAAE;AACrC;AACA;AACA,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,cAAc,EAAE;AAC5D,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC;AACrC,EAAE,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC;AACpC,EAAE,cAAc,CAAC,MAAM,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC7C,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC;AACtC,EAAE,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;AACjD,CAAC,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,WAAW,CAAC;AACrC,EAAE,cAAc,CAAC,MAAM,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;AAC/C;;;;"}
|
package/dist/tab-group.esm.js
CHANGED
|
@@ -121,12 +121,84 @@ class TabGroup extends HTMLElement {
|
|
|
121
121
|
* called when the element is disconnected from the dom
|
|
122
122
|
*/
|
|
123
123
|
disconnectedCallback() {
|
|
124
|
+
if (this._animationController) {
|
|
125
|
+
this._animationController.abort();
|
|
126
|
+
this._animationController = null;
|
|
127
|
+
}
|
|
124
128
|
if (this.tabList && this._onKeyDown) {
|
|
125
129
|
this.tabList.removeEventListener('keydown', this._onKeyDown);
|
|
126
130
|
this.tabList.removeEventListener('click', this._onClick);
|
|
127
131
|
}
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
/**
|
|
135
|
+
* reads animation attributes from the element
|
|
136
|
+
*/
|
|
137
|
+
_getAnimateConfig() {
|
|
138
|
+
const outClass = this.getAttribute('animate-out-class');
|
|
139
|
+
const inClass = this.getAttribute('animate-in-class');
|
|
140
|
+
const timeout = parseInt(this.getAttribute('animate-timeout'), 10) || 500;
|
|
141
|
+
return { outClass, inClass, timeout, hasAnimation: !!(outClass || inClass) };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* adds a class and waits for animationend (or timeout), with abort support
|
|
146
|
+
*/
|
|
147
|
+
_waitForAnimation(element, className, timeout, signal) {
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
if (signal.aborted) {
|
|
150
|
+
resolve();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
element.classList.add(className);
|
|
155
|
+
|
|
156
|
+
let timer;
|
|
157
|
+
const cleanup = () => {
|
|
158
|
+
element.classList.remove(className);
|
|
159
|
+
clearTimeout(timer);
|
|
160
|
+
element.removeEventListener('animationend', onEnd);
|
|
161
|
+
signal.removeEventListener('abort', onAbort);
|
|
162
|
+
resolve();
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const onEnd = (e) => {
|
|
166
|
+
if (e.target === element) cleanup();
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const onAbort = () => cleanup();
|
|
170
|
+
|
|
171
|
+
element.addEventListener('animationend', onEnd);
|
|
172
|
+
signal.addEventListener('abort', onAbort);
|
|
173
|
+
timer = setTimeout(cleanup, timeout);
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* orchestrates out-animation → swap → in-animation
|
|
179
|
+
*/
|
|
180
|
+
async _animateTransition(oldPanel, newPanel, config, controller) {
|
|
181
|
+
const { signal } = controller;
|
|
182
|
+
|
|
183
|
+
// Phase 1: animate out
|
|
184
|
+
if (config.outClass && oldPanel) {
|
|
185
|
+
await this._waitForAnimation(oldPanel, config.outClass, config.timeout, signal);
|
|
186
|
+
}
|
|
187
|
+
if (signal.aborted) return;
|
|
188
|
+
|
|
189
|
+
// Phase 2: swap hidden
|
|
190
|
+
if (oldPanel) oldPanel.hidden = true;
|
|
191
|
+
newPanel.hidden = false;
|
|
192
|
+
|
|
193
|
+
// Phase 3: animate in
|
|
194
|
+
if (config.inClass) {
|
|
195
|
+
if (signal.aborted) return;
|
|
196
|
+
// force reflow so the browser sees the element before animating
|
|
197
|
+
newPanel.offsetHeight;
|
|
198
|
+
await this._waitForAnimation(newPanel, config.inClass, config.timeout, signal);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
130
202
|
/**
|
|
131
203
|
* @function setActiveTab
|
|
132
204
|
* activates a tab and updates aria attributes
|
|
@@ -138,7 +210,17 @@ class TabGroup extends HTMLElement {
|
|
|
138
210
|
(tab) => tab.getAttribute('aria-selected') === 'true'
|
|
139
211
|
);
|
|
140
212
|
|
|
141
|
-
//
|
|
213
|
+
// cancel any in-flight animation
|
|
214
|
+
if (this._animationController) {
|
|
215
|
+
this._animationController.abort();
|
|
216
|
+
this._animationController = null;
|
|
217
|
+
// force-hide all panels (clean slate)
|
|
218
|
+
this.tabPanels.forEach((panel) => {
|
|
219
|
+
panel.hidden = true;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// update each tab-button (ARIA updates fire immediately)
|
|
142
224
|
this.tabButtons.forEach((tab, i) => {
|
|
143
225
|
const isActive = i === index;
|
|
144
226
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
@@ -148,11 +230,6 @@ class TabGroup extends HTMLElement {
|
|
|
148
230
|
}
|
|
149
231
|
});
|
|
150
232
|
|
|
151
|
-
// update each tab-panel
|
|
152
|
-
this.tabPanels.forEach((panel, i) => {
|
|
153
|
-
panel.hidden = i !== index;
|
|
154
|
-
});
|
|
155
|
-
|
|
156
233
|
// dispatch event only if the tab actually changed
|
|
157
234
|
if (previousIndex !== index) {
|
|
158
235
|
const detail = {
|
|
@@ -167,6 +244,48 @@ class TabGroup extends HTMLElement {
|
|
|
167
244
|
new CustomEvent('tabchange', { detail, bubbles: true })
|
|
168
245
|
);
|
|
169
246
|
}
|
|
247
|
+
|
|
248
|
+
const config = this._getAnimateConfig();
|
|
249
|
+
const oldPanel = previousIndex >= 0 ? this.tabPanels[previousIndex] : null;
|
|
250
|
+
const newPanel = this.tabPanels[index];
|
|
251
|
+
|
|
252
|
+
if (!config.hasAnimation || previousIndex === index) {
|
|
253
|
+
// instant switch (original behavior)
|
|
254
|
+
this.tabPanels.forEach((panel, i) => {
|
|
255
|
+
panel.hidden = i !== index;
|
|
256
|
+
});
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// animated transition
|
|
261
|
+
const controller = new AbortController();
|
|
262
|
+
this._animationController = controller;
|
|
263
|
+
|
|
264
|
+
// old panel was already force-hidden by abort above, so if we aborted
|
|
265
|
+
// a previous animation, skip animate-out (old panel is already gone)
|
|
266
|
+
const skipOut = oldPanel && oldPanel.hidden;
|
|
267
|
+
|
|
268
|
+
if (skipOut) {
|
|
269
|
+
// just animate in the new panel
|
|
270
|
+
newPanel.hidden = false;
|
|
271
|
+
if (config.inClass) {
|
|
272
|
+
newPanel.offsetHeight;
|
|
273
|
+
this._waitForAnimation(newPanel, config.inClass, config.timeout, controller.signal).then(() => {
|
|
274
|
+
if (this._animationController === controller) {
|
|
275
|
+
this._animationController = null;
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
} else {
|
|
279
|
+
this._animationController = null;
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
// full out → swap → in sequence
|
|
283
|
+
this._animateTransition(oldPanel, newPanel, config, controller).then(() => {
|
|
284
|
+
if (this._animationController === controller) {
|
|
285
|
+
this._animationController = null;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
}
|
|
170
289
|
}
|
|
171
290
|
|
|
172
291
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tab-group.esm.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.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 * @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// update each tab-button\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// update each tab-panel\n\t\tthis.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: 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\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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"}
|
|
1
|
+
{"version":3,"file":"tab-group.esm.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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"}
|
package/dist/tab-group.js
CHANGED
|
@@ -127,12 +127,84 @@
|
|
|
127
127
|
* called when the element is disconnected from the dom
|
|
128
128
|
*/
|
|
129
129
|
disconnectedCallback() {
|
|
130
|
+
if (this._animationController) {
|
|
131
|
+
this._animationController.abort();
|
|
132
|
+
this._animationController = null;
|
|
133
|
+
}
|
|
130
134
|
if (this.tabList && this._onKeyDown) {
|
|
131
135
|
this.tabList.removeEventListener('keydown', this._onKeyDown);
|
|
132
136
|
this.tabList.removeEventListener('click', this._onClick);
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
|
|
140
|
+
/**
|
|
141
|
+
* reads animation attributes from the element
|
|
142
|
+
*/
|
|
143
|
+
_getAnimateConfig() {
|
|
144
|
+
const outClass = this.getAttribute('animate-out-class');
|
|
145
|
+
const inClass = this.getAttribute('animate-in-class');
|
|
146
|
+
const timeout = parseInt(this.getAttribute('animate-timeout'), 10) || 500;
|
|
147
|
+
return { outClass, inClass, timeout, hasAnimation: !!(outClass || inClass) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* adds a class and waits for animationend (or timeout), with abort support
|
|
152
|
+
*/
|
|
153
|
+
_waitForAnimation(element, className, timeout, signal) {
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
if (signal.aborted) {
|
|
156
|
+
resolve();
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
element.classList.add(className);
|
|
161
|
+
|
|
162
|
+
let timer;
|
|
163
|
+
const cleanup = () => {
|
|
164
|
+
element.classList.remove(className);
|
|
165
|
+
clearTimeout(timer);
|
|
166
|
+
element.removeEventListener('animationend', onEnd);
|
|
167
|
+
signal.removeEventListener('abort', onAbort);
|
|
168
|
+
resolve();
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const onEnd = (e) => {
|
|
172
|
+
if (e.target === element) cleanup();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const onAbort = () => cleanup();
|
|
176
|
+
|
|
177
|
+
element.addEventListener('animationend', onEnd);
|
|
178
|
+
signal.addEventListener('abort', onAbort);
|
|
179
|
+
timer = setTimeout(cleanup, timeout);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* orchestrates out-animation → swap → in-animation
|
|
185
|
+
*/
|
|
186
|
+
async _animateTransition(oldPanel, newPanel, config, controller) {
|
|
187
|
+
const { signal } = controller;
|
|
188
|
+
|
|
189
|
+
// Phase 1: animate out
|
|
190
|
+
if (config.outClass && oldPanel) {
|
|
191
|
+
await this._waitForAnimation(oldPanel, config.outClass, config.timeout, signal);
|
|
192
|
+
}
|
|
193
|
+
if (signal.aborted) return;
|
|
194
|
+
|
|
195
|
+
// Phase 2: swap hidden
|
|
196
|
+
if (oldPanel) oldPanel.hidden = true;
|
|
197
|
+
newPanel.hidden = false;
|
|
198
|
+
|
|
199
|
+
// Phase 3: animate in
|
|
200
|
+
if (config.inClass) {
|
|
201
|
+
if (signal.aborted) return;
|
|
202
|
+
// force reflow so the browser sees the element before animating
|
|
203
|
+
newPanel.offsetHeight;
|
|
204
|
+
await this._waitForAnimation(newPanel, config.inClass, config.timeout, signal);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
136
208
|
/**
|
|
137
209
|
* @function setActiveTab
|
|
138
210
|
* activates a tab and updates aria attributes
|
|
@@ -144,7 +216,17 @@
|
|
|
144
216
|
(tab) => tab.getAttribute('aria-selected') === 'true'
|
|
145
217
|
);
|
|
146
218
|
|
|
147
|
-
//
|
|
219
|
+
// cancel any in-flight animation
|
|
220
|
+
if (this._animationController) {
|
|
221
|
+
this._animationController.abort();
|
|
222
|
+
this._animationController = null;
|
|
223
|
+
// force-hide all panels (clean slate)
|
|
224
|
+
this.tabPanels.forEach((panel) => {
|
|
225
|
+
panel.hidden = true;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// update each tab-button (ARIA updates fire immediately)
|
|
148
230
|
this.tabButtons.forEach((tab, i) => {
|
|
149
231
|
const isActive = i === index;
|
|
150
232
|
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
@@ -154,11 +236,6 @@
|
|
|
154
236
|
}
|
|
155
237
|
});
|
|
156
238
|
|
|
157
|
-
// update each tab-panel
|
|
158
|
-
this.tabPanels.forEach((panel, i) => {
|
|
159
|
-
panel.hidden = i !== index;
|
|
160
|
-
});
|
|
161
|
-
|
|
162
239
|
// dispatch event only if the tab actually changed
|
|
163
240
|
if (previousIndex !== index) {
|
|
164
241
|
const detail = {
|
|
@@ -173,6 +250,48 @@
|
|
|
173
250
|
new CustomEvent('tabchange', { detail, bubbles: true })
|
|
174
251
|
);
|
|
175
252
|
}
|
|
253
|
+
|
|
254
|
+
const config = this._getAnimateConfig();
|
|
255
|
+
const oldPanel = previousIndex >= 0 ? this.tabPanels[previousIndex] : null;
|
|
256
|
+
const newPanel = this.tabPanels[index];
|
|
257
|
+
|
|
258
|
+
if (!config.hasAnimation || previousIndex === index) {
|
|
259
|
+
// instant switch (original behavior)
|
|
260
|
+
this.tabPanels.forEach((panel, i) => {
|
|
261
|
+
panel.hidden = i !== index;
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// animated transition
|
|
267
|
+
const controller = new AbortController();
|
|
268
|
+
this._animationController = controller;
|
|
269
|
+
|
|
270
|
+
// old panel was already force-hidden by abort above, so if we aborted
|
|
271
|
+
// a previous animation, skip animate-out (old panel is already gone)
|
|
272
|
+
const skipOut = oldPanel && oldPanel.hidden;
|
|
273
|
+
|
|
274
|
+
if (skipOut) {
|
|
275
|
+
// just animate in the new panel
|
|
276
|
+
newPanel.hidden = false;
|
|
277
|
+
if (config.inClass) {
|
|
278
|
+
newPanel.offsetHeight;
|
|
279
|
+
this._waitForAnimation(newPanel, config.inClass, config.timeout, controller.signal).then(() => {
|
|
280
|
+
if (this._animationController === controller) {
|
|
281
|
+
this._animationController = null;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
} else {
|
|
285
|
+
this._animationController = null;
|
|
286
|
+
}
|
|
287
|
+
} else {
|
|
288
|
+
// full out → swap → in sequence
|
|
289
|
+
this._animateTransition(oldPanel, newPanel, config, controller).then(() => {
|
|
290
|
+
if (this._animationController === controller) {
|
|
291
|
+
this._animationController = null;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
176
295
|
}
|
|
177
296
|
|
|
178
297
|
/**
|
package/dist/tab-group.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tab-group.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.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 * @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// update each tab-button\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// update each tab-panel\n\t\tthis.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: 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\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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"}
|
|
1
|
+
{"version":3,"file":"tab-group.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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"}
|
package/dist/tab-group.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TabGroup={})}(this,function(t){"use strict";let e=0;class TabGroup extends HTMLElement{ensureConsistentTabsAndPanels(){let t=this.querySelectorAll(":scope > tab-list > tab-button"),e=this.querySelectorAll(":scope > tab-panel");if(e.length>t.length){const
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TabGroup={})}(this,function(t){"use strict";let e=0;class TabGroup extends HTMLElement{ensureConsistentTabsAndPanels(){let t=this.querySelectorAll(":scope > tab-list > tab-button"),e=this.querySelectorAll(":scope > tab-panel");if(e.length>t.length){const n=e.length-t.length;let i=this.querySelector(":scope > tab-list");i||(i=document.createElement("tab-list"),this.insertBefore(i,this.firstChild));for(let t=0;t<n;t++){const t=document.createElement("tab-button");t.textContent="default tab",i.appendChild(t)}}else if(t.length>e.length){const n=t.length-e.length;for(let t=0;t<n;t++){const t=document.createElement("tab-panel");t.innerHTML="<p>default panel content</p>",this.appendChild(t)}}}connectedCallback(){if(this._instanceId||(this._instanceId="tg-"+e++),this.ensureConsistentTabsAndPanels(),this.tabList=this.querySelector(":scope > tab-list"),!this.tabList)return;this.tabButtons=Array.from(this.tabList.querySelectorAll("tab-button")),this.tabPanels=Array.from(this.querySelectorAll(":scope > tab-panel"));const t=this._instanceId;this.tabButtons.forEach((e,n)=>{const i=`${t}-tab-${n}`,s=`${t}-panel-${n}`;e.id=i,e.setAttribute("role","tab"),e.setAttribute("aria-controls",s),0===n?(e.setAttribute("aria-selected","true"),e.setAttribute("tabindex","0")):(e.setAttribute("aria-selected","false"),e.setAttribute("tabindex","-1"))}),this.tabPanels.forEach((e,n)=>{const i=`${t}-panel-${n}`;e.id=i,e.setAttribute("role","tabpanel"),e.setAttribute("aria-labelledby",`${t}-tab-${n}`),e.hidden=0!==n}),this.tabList.setAttribute("role","tablist"),this._onKeyDown||(this._onKeyDown=t=>this.onKeyDown(t),this._onClick=t=>this.onClick(t)),this.tabList.addEventListener("keydown",this._onKeyDown),this.tabList.addEventListener("click",this._onClick)}disconnectedCallback(){this._animationController&&(this._animationController.abort(),this._animationController=null),this.tabList&&this._onKeyDown&&(this.tabList.removeEventListener("keydown",this._onKeyDown),this.tabList.removeEventListener("click",this._onClick))}_getAnimateConfig(){const t=this.getAttribute("animate-out-class"),e=this.getAttribute("animate-in-class");return{outClass:t,inClass:e,timeout:parseInt(this.getAttribute("animate-timeout"),10)||500,hasAnimation:!(!t&&!e)}}_waitForAnimation(t,e,n,i){return new Promise(s=>{if(i.aborted)return void s();let a;t.classList.add(e);const o=()=>{t.classList.remove(e),clearTimeout(a),t.removeEventListener("animationend",l),i.removeEventListener("abort",r),s()},l=e=>{e.target===t&&o()},r=()=>o();t.addEventListener("animationend",l),i.addEventListener("abort",r),a=setTimeout(o,n)})}async _animateTransition(t,e,n,i){const{signal:s}=i;if(n.outClass&&t&&await this._waitForAnimation(t,n.outClass,n.timeout,s),!s.aborted&&(t&&(t.hidden=!0),e.hidden=!1,n.inClass)){if(s.aborted)return;e.offsetHeight,await this._waitForAnimation(e,n.inClass,n.timeout,s)}}setActiveTab(t){if(t<0||t>=this.tabButtons.length)return;const e=this.tabButtons.findIndex(t=>"true"===t.getAttribute("aria-selected"));if(this._animationController&&(this._animationController.abort(),this._animationController=null,this.tabPanels.forEach(t=>{t.hidden=!0})),this.tabButtons.forEach((e,n)=>{const i=n===t;e.setAttribute("aria-selected",i?"true":"false"),e.setAttribute("tabindex",i?"0":"-1"),i&&e.focus()}),e!==t){const n={previousIndex:e,currentIndex:t,previousTab:this.tabButtons[e],currentTab:this.tabButtons[t],previousPanel:this.tabPanels[e],currentPanel:this.tabPanels[t]};this.dispatchEvent(new CustomEvent("tabchange",{detail:n,bubbles:!0}))}const n=this._getAnimateConfig(),i=e>=0?this.tabPanels[e]:null,s=this.tabPanels[t];if(!n.hasAnimation||e===t)return void this.tabPanels.forEach((e,n)=>{e.hidden=n!==t});const a=new AbortController;this._animationController=a;i&&i.hidden?(s.hidden=!1,n.inClass?(s.offsetHeight,this._waitForAnimation(s,n.inClass,n.timeout,a.signal).then(()=>{this._animationController===a&&(this._animationController=null)})):this._animationController=null):this._animateTransition(i,s,n,a).then(()=>{this._animationController===a&&(this._animationController=null)})}onClick(t){const e=t.target.closest("tab-button");if(!e)return;const n=this.tabButtons.indexOf(e);-1!==n&&this.setActiveTab(n)}onKeyDown(t){const e=this.tabButtons.indexOf(t.target);if(-1===e)return;let n=e;switch(t.key){case"ArrowLeft":case"ArrowUp":n=e>0?e-1:this.tabButtons.length-1,t.preventDefault();break;case"ArrowRight":case"ArrowDown":n=(e+1)%this.tabButtons.length,t.preventDefault();break;case"Home":n=0,t.preventDefault();break;case"End":n=this.tabButtons.length-1,t.preventDefault();break;default:return}this.setActiveTab(n)}}class TabList extends HTMLElement{}class TabButton extends HTMLElement{}class TabPanel extends HTMLElement{}"undefined"!=typeof window&&window.customElements&&(customElements.get("tab-group")||customElements.define("tab-group",TabGroup),customElements.get("tab-list")||customElements.define("tab-list",TabList),customElements.get("tab-button")||customElements.define("tab-button",TabButton),customElements.get("tab-panel")||customElements.define("tab-panel",TabPanel)),t.default=TabGroup,Object.defineProperty(t,"__esModule",{value:!0})});
|
package/package.json
CHANGED
package/tab-group.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ export interface TabChangeEventDetail {
|
|
|
7
7
|
currentPanel: HTMLElement;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Accessible tab group web component.
|
|
12
|
+
*
|
|
13
|
+
* **Animation attributes** (set on the `<tab-group>` element):
|
|
14
|
+
*
|
|
15
|
+
* - `animate-out-class` — CSS class added to the outgoing panel during exit animation.
|
|
16
|
+
* Omit for instant hide.
|
|
17
|
+
* - `animate-in-class` — CSS class added to the incoming panel during enter animation.
|
|
18
|
+
* Omit for instant show.
|
|
19
|
+
* - `animate-timeout` — Fallback timeout in ms if `animationend` never fires (default `500`).
|
|
20
|
+
*
|
|
21
|
+
* Either animation attribute works independently. No attributes = original instant behavior.
|
|
22
|
+
*/
|
|
10
23
|
export default class TabGroup extends HTMLElement {
|
|
11
24
|
/** The tab-list element within this tab group. */
|
|
12
25
|
tabList: HTMLElement | null;
|