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