@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  # Tab Group
2
2
 
3
3
  ![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
4
- ![Version](https://img.shields.io/badge/version-0.1.0-brightgreen)
4
+ ![Version](https://img.shields.io/badge/version-1.0.0-brightgreen)
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 with magic by <a href="https://github.com/cory-schulz">Cory Schulz</a>
192
+ Made by <a href="https://github.com/coryschulz">Cory Schulz</a>
147
193
  </p>
@@ -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
- // update each tab-button
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":"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"}
@@ -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
- // update each tab-button
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":"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.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
- // update each tab-button
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
  /**
@@ -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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"}
@@ -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 s=e.length-t.length;let n=this.querySelector(":scope > tab-list");n||(n=document.createElement("tab-list"),this.insertBefore(n,this.firstChild));for(let t=0;t<s;t++){const t=document.createElement("tab-button");t.textContent="default tab",n.appendChild(t)}}else if(t.length>e.length){const s=t.length-e.length;for(let t=0;t<s;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,s)=>{const n=`${t}-tab-${s}`,a=`${t}-panel-${s}`;e.id=n,e.setAttribute("role","tab"),e.setAttribute("aria-controls",a),0===s?(e.setAttribute("aria-selected","true"),e.setAttribute("tabindex","0")):(e.setAttribute("aria-selected","false"),e.setAttribute("tabindex","-1"))}),this.tabPanels.forEach((e,s)=>{const n=`${t}-panel-${s}`;e.id=n,e.setAttribute("role","tabpanel"),e.setAttribute("aria-labelledby",`${t}-tab-${s}`),e.hidden=0!==s}),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.tabList&&this._onKeyDown&&(this.tabList.removeEventListener("keydown",this._onKeyDown),this.tabList.removeEventListener("click",this._onClick))}setActiveTab(t){if(t<0||t>=this.tabButtons.length)return;const e=this.tabButtons.findIndex(t=>"true"===t.getAttribute("aria-selected"));if(this.tabButtons.forEach((e,s)=>{const n=s===t;e.setAttribute("aria-selected",n?"true":"false"),e.setAttribute("tabindex",n?"0":"-1"),n&&e.focus()}),this.tabPanels.forEach((e,s)=>{e.hidden=s!==t}),e!==t){const s={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:s,bubbles:!0}))}}onClick(t){const e=t.target.closest("tab-button");if(!e)return;const s=this.tabButtons.indexOf(e);-1!==s&&this.setActiveTab(s)}onKeyDown(t){const e=this.tabButtons.indexOf(t.target);if(-1===e)return;let s=e;switch(t.key){case"ArrowLeft":case"ArrowUp":s=e>0?e-1:this.tabButtons.length-1,t.preventDefault();break;case"ArrowRight":case"ArrowDown":s=(e+1)%this.tabButtons.length,t.preventDefault();break;case"Home":s=0,t.preventDefault();break;case"End":s=this.tabButtons.length-1,t.preventDefault();break;default:return}this.setActiveTab(s)}}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})});
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-spells/tab-group",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Tab group and tab list html components.",
5
5
  "license": "MIT",
6
6
  "author": "Cory Schulz",
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;