@magic-spells/tab-group 0.1.0

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