@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
package/dist/tab-group.js
CHANGED
|
@@ -9,23 +9,13 @@
|
|
|
9
9
|
* A fully accessible tab group web component
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
let instanceCount = 0;
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* @class TabGroup
|
|
14
16
|
* the parent container that coordinates tabs and panels
|
|
15
17
|
*/
|
|
16
18
|
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
19
|
/**
|
|
30
20
|
* @function ensureConsistentTabsAndPanels
|
|
31
21
|
* makes sure there is an equal number of <tab-button> and <tab-panel> elements.
|
|
@@ -33,24 +23,24 @@
|
|
|
33
23
|
* if there are more tabs than panels, inject extra panels.
|
|
34
24
|
*/
|
|
35
25
|
ensureConsistentTabsAndPanels() {
|
|
36
|
-
// get current tabs and panels
|
|
37
|
-
let tabs = this.querySelectorAll(
|
|
38
|
-
let panels = this.querySelectorAll(
|
|
26
|
+
// get current tabs and panels scoped to direct children only
|
|
27
|
+
let tabs = this.querySelectorAll(':scope > tab-list > tab-button');
|
|
28
|
+
let panels = this.querySelectorAll(':scope > tab-panel');
|
|
39
29
|
|
|
40
30
|
// if there are more panels than tabs
|
|
41
31
|
if (panels.length > tabs.length) {
|
|
42
32
|
const difference = panels.length - tabs.length;
|
|
43
33
|
// try to find a <tab-list> to insert new tabs
|
|
44
|
-
let tabList = this.querySelector(
|
|
34
|
+
let tabList = this.querySelector(':scope > tab-list');
|
|
45
35
|
if (!tabList) {
|
|
46
36
|
// if not present, create one and insert it at the beginning
|
|
47
|
-
tabList = document.createElement(
|
|
37
|
+
tabList = document.createElement('tab-list');
|
|
48
38
|
this.insertBefore(tabList, this.firstChild);
|
|
49
39
|
}
|
|
50
40
|
// inject extra <tab-button> elements into the tab list
|
|
51
41
|
for (let i = 0; i < difference; i++) {
|
|
52
|
-
const newTab = document.createElement(
|
|
53
|
-
newTab.textContent =
|
|
42
|
+
const newTab = document.createElement('tab-button');
|
|
43
|
+
newTab.textContent = 'default tab';
|
|
54
44
|
tabList.appendChild(newTab);
|
|
55
45
|
}
|
|
56
46
|
}
|
|
@@ -59,8 +49,8 @@
|
|
|
59
49
|
const difference = tabs.length - panels.length;
|
|
60
50
|
// inject extra <tab-panel> elements at the end of the tab group
|
|
61
51
|
for (let i = 0; i < difference; i++) {
|
|
62
|
-
const newPanel = document.createElement(
|
|
63
|
-
newPanel.innerHTML =
|
|
52
|
+
const newPanel = document.createElement('tab-panel');
|
|
53
|
+
newPanel.innerHTML = '<p>default panel content</p>';
|
|
64
54
|
this.appendChild(newPanel);
|
|
65
55
|
}
|
|
66
56
|
}
|
|
@@ -70,58 +60,77 @@
|
|
|
70
60
|
* called when the element is connected to the dom
|
|
71
61
|
*/
|
|
72
62
|
connectedCallback() {
|
|
73
|
-
|
|
63
|
+
// assign a stable instance id on first connect
|
|
64
|
+
if (!this._instanceId) {
|
|
65
|
+
this._instanceId = `tg-${instanceCount++}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ensure that the number of <tab-button> and <tab-panel> elements match
|
|
69
|
+
this.ensureConsistentTabsAndPanels();
|
|
74
70
|
|
|
75
71
|
// find the <tab-list> element (should be exactly one)
|
|
76
|
-
|
|
77
|
-
if (!
|
|
72
|
+
this.tabList = this.querySelector(':scope > tab-list');
|
|
73
|
+
if (!this.tabList) return;
|
|
78
74
|
|
|
79
75
|
// find all <tab-button> elements inside the <tab-list>
|
|
80
|
-
|
|
76
|
+
this.tabButtons = Array.from(
|
|
77
|
+
this.tabList.querySelectorAll('tab-button')
|
|
78
|
+
);
|
|
81
79
|
|
|
82
80
|
// find all <tab-panel> elements inside the <tab-group>
|
|
83
|
-
|
|
81
|
+
this.tabPanels = Array.from(this.querySelectorAll(':scope > tab-panel'));
|
|
84
82
|
|
|
85
|
-
|
|
86
|
-
_.tabButtons.forEach((tab, index) => {
|
|
87
|
-
const tabIndex = TabGroup.tabCount++;
|
|
83
|
+
const prefix = this._instanceId;
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
// initialize each tab-button with roles, ids and aria attributes
|
|
86
|
+
this.tabButtons.forEach((tab, index) => {
|
|
87
|
+
const tabId = `${prefix}-tab-${index}`;
|
|
88
|
+
const panelId = `${prefix}-panel-${index}`;
|
|
91
89
|
tab.id = tabId;
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const panelId = `panel-${tabIndex}`;
|
|
95
|
-
tab.setAttribute("role", "tab");
|
|
96
|
-
tab.setAttribute("aria-controls", panelId);
|
|
90
|
+
tab.setAttribute('role', 'tab');
|
|
91
|
+
tab.setAttribute('aria-controls', panelId);
|
|
97
92
|
|
|
98
93
|
// first tab is active by default
|
|
99
94
|
if (index === 0) {
|
|
100
|
-
tab.setAttribute(
|
|
101
|
-
tab.setAttribute(
|
|
95
|
+
tab.setAttribute('aria-selected', 'true');
|
|
96
|
+
tab.setAttribute('tabindex', '0');
|
|
102
97
|
} else {
|
|
103
|
-
tab.setAttribute(
|
|
104
|
-
tab.setAttribute(
|
|
98
|
+
tab.setAttribute('aria-selected', 'false');
|
|
99
|
+
tab.setAttribute('tabindex', '-1');
|
|
105
100
|
}
|
|
106
101
|
});
|
|
107
102
|
|
|
108
103
|
// initialize each tab-panel with roles, ids and aria attributes
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
const panelId = `panel-${panelIndex}`;
|
|
104
|
+
this.tabPanels.forEach((panel, index) => {
|
|
105
|
+
const panelId = `${prefix}-panel-${index}`;
|
|
112
106
|
panel.id = panelId;
|
|
113
|
-
|
|
114
|
-
panel.setAttribute(
|
|
115
|
-
panel.setAttribute("aria-labelledby", `tab-${panelIndex}`);
|
|
107
|
+
panel.setAttribute('role', 'tabpanel');
|
|
108
|
+
panel.setAttribute('aria-labelledby', `${prefix}-tab-${index}`);
|
|
116
109
|
|
|
117
110
|
// hide panels except for the first one
|
|
118
111
|
panel.hidden = index !== 0;
|
|
119
112
|
});
|
|
120
113
|
|
|
121
114
|
// set up keyboard navigation and click delegation on the <tab-list>
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
115
|
+
this.tabList.setAttribute('role', 'tablist');
|
|
116
|
+
|
|
117
|
+
// store bound handlers so we can remove them in disconnectedCallback
|
|
118
|
+
if (!this._onKeyDown) {
|
|
119
|
+
this._onKeyDown = (e) => this.onKeyDown(e);
|
|
120
|
+
this._onClick = (e) => this.onClick(e);
|
|
121
|
+
}
|
|
122
|
+
this.tabList.addEventListener('keydown', this._onKeyDown);
|
|
123
|
+
this.tabList.addEventListener('click', this._onClick);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* called when the element is disconnected from the dom
|
|
128
|
+
*/
|
|
129
|
+
disconnectedCallback() {
|
|
130
|
+
if (this.tabList && this._onKeyDown) {
|
|
131
|
+
this.tabList.removeEventListener('keydown', this._onKeyDown);
|
|
132
|
+
this.tabList.removeEventListener('click', this._onClick);
|
|
133
|
+
}
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
/**
|
|
@@ -130,21 +139,23 @@
|
|
|
130
139
|
* @param {number} index - index of the tab to activate
|
|
131
140
|
*/
|
|
132
141
|
setActiveTab(index) {
|
|
133
|
-
|
|
134
|
-
const previousIndex =
|
|
142
|
+
if (index < 0 || index >= this.tabButtons.length) return;
|
|
143
|
+
const previousIndex = this.tabButtons.findIndex(
|
|
144
|
+
(tab) => tab.getAttribute('aria-selected') === 'true'
|
|
145
|
+
);
|
|
135
146
|
|
|
136
147
|
// update each tab-button
|
|
137
|
-
|
|
148
|
+
this.tabButtons.forEach((tab, i) => {
|
|
138
149
|
const isActive = i === index;
|
|
139
|
-
tab.setAttribute(
|
|
140
|
-
tab.setAttribute(
|
|
150
|
+
tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
|
151
|
+
tab.setAttribute('tabindex', isActive ? '0' : '-1');
|
|
141
152
|
if (isActive) {
|
|
142
153
|
tab.focus();
|
|
143
154
|
}
|
|
144
155
|
});
|
|
145
156
|
|
|
146
157
|
// update each tab-panel
|
|
147
|
-
|
|
158
|
+
this.tabPanels.forEach((panel, i) => {
|
|
148
159
|
panel.hidden = i !== index;
|
|
149
160
|
});
|
|
150
161
|
|
|
@@ -153,12 +164,14 @@
|
|
|
153
164
|
const detail = {
|
|
154
165
|
previousIndex,
|
|
155
166
|
currentIndex: index,
|
|
156
|
-
previousTab:
|
|
157
|
-
currentTab:
|
|
158
|
-
previousPanel:
|
|
159
|
-
currentPanel:
|
|
167
|
+
previousTab: this.tabButtons[previousIndex],
|
|
168
|
+
currentTab: this.tabButtons[index],
|
|
169
|
+
previousPanel: this.tabPanels[previousIndex],
|
|
170
|
+
currentPanel: this.tabPanels[index],
|
|
160
171
|
};
|
|
161
|
-
|
|
172
|
+
this.dispatchEvent(
|
|
173
|
+
new CustomEvent('tabchange', { detail, bubbles: true })
|
|
174
|
+
);
|
|
162
175
|
}
|
|
163
176
|
}
|
|
164
177
|
|
|
@@ -168,17 +181,16 @@
|
|
|
168
181
|
* @param {MouseEvent} e - the click event
|
|
169
182
|
*/
|
|
170
183
|
onClick(e) {
|
|
171
|
-
const _ = this;
|
|
172
184
|
// check if the click occurred on or within a <tab-button>
|
|
173
|
-
const tabButton = e.target.closest(
|
|
185
|
+
const tabButton = e.target.closest('tab-button');
|
|
174
186
|
if (!tabButton) return;
|
|
175
187
|
|
|
176
188
|
// determine the index of the clicked tab-button
|
|
177
|
-
const index =
|
|
189
|
+
const index = this.tabButtons.indexOf(tabButton);
|
|
178
190
|
if (index === -1) return;
|
|
179
191
|
|
|
180
192
|
// activate the tab with the corresponding index
|
|
181
|
-
|
|
193
|
+
this.setActiveTab(index);
|
|
182
194
|
}
|
|
183
195
|
|
|
184
196
|
/**
|
|
@@ -187,39 +199,39 @@
|
|
|
187
199
|
* @param {KeyboardEvent} e - the keydown event
|
|
188
200
|
*/
|
|
189
201
|
onKeyDown(e) {
|
|
190
|
-
const _ = this;
|
|
191
202
|
// only process keys if focus is on a <tab-button>
|
|
192
|
-
const targetIndex =
|
|
203
|
+
const targetIndex = this.tabButtons.indexOf(e.target);
|
|
193
204
|
if (targetIndex === -1) return;
|
|
194
205
|
|
|
195
206
|
let newIndex = targetIndex;
|
|
196
207
|
switch (e.key) {
|
|
197
|
-
case
|
|
198
|
-
case
|
|
208
|
+
case 'ArrowLeft':
|
|
209
|
+
case 'ArrowUp':
|
|
199
210
|
// move to the previous tab (wrap around if necessary)
|
|
200
|
-
newIndex =
|
|
211
|
+
newIndex =
|
|
212
|
+
targetIndex > 0 ? targetIndex - 1 : this.tabButtons.length - 1;
|
|
201
213
|
e.preventDefault();
|
|
202
214
|
break;
|
|
203
|
-
case
|
|
204
|
-
case
|
|
215
|
+
case 'ArrowRight':
|
|
216
|
+
case 'ArrowDown':
|
|
205
217
|
// move to the next tab (wrap around if necessary)
|
|
206
|
-
newIndex = (targetIndex + 1) %
|
|
218
|
+
newIndex = (targetIndex + 1) % this.tabButtons.length;
|
|
207
219
|
e.preventDefault();
|
|
208
220
|
break;
|
|
209
|
-
case
|
|
221
|
+
case 'Home':
|
|
210
222
|
// jump to the first tab
|
|
211
223
|
newIndex = 0;
|
|
212
224
|
e.preventDefault();
|
|
213
225
|
break;
|
|
214
|
-
case
|
|
226
|
+
case 'End':
|
|
215
227
|
// jump to the last tab
|
|
216
|
-
newIndex =
|
|
228
|
+
newIndex = this.tabButtons.length - 1;
|
|
217
229
|
e.preventDefault();
|
|
218
230
|
break;
|
|
219
231
|
default:
|
|
220
232
|
return; // ignore other keys
|
|
221
233
|
}
|
|
222
|
-
|
|
234
|
+
this.setActiveTab(newIndex);
|
|
223
235
|
}
|
|
224
236
|
}
|
|
225
237
|
|
|
@@ -227,51 +239,32 @@
|
|
|
227
239
|
* @class TabList
|
|
228
240
|
* a container for the <tab-button> elements
|
|
229
241
|
*/
|
|
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
|
-
}
|
|
242
|
+
class TabList extends HTMLElement {}
|
|
239
243
|
|
|
240
244
|
/**
|
|
241
245
|
* @class TabButton
|
|
242
246
|
* a single tab button element
|
|
243
247
|
*/
|
|
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
|
-
}
|
|
248
|
+
class TabButton extends HTMLElement {}
|
|
253
249
|
|
|
254
250
|
/**
|
|
255
251
|
* @class TabPanel
|
|
256
252
|
* a single tab panel element
|
|
257
253
|
*/
|
|
258
|
-
class TabPanel extends HTMLElement {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
254
|
+
class TabPanel extends HTMLElement {}
|
|
255
|
+
|
|
256
|
+
// define the custom elements (guarded against double-registration and SSR)
|
|
257
|
+
if (typeof window !== 'undefined' && window.customElements) {
|
|
258
|
+
if (!customElements.get('tab-group'))
|
|
259
|
+
customElements.define('tab-group', TabGroup);
|
|
260
|
+
if (!customElements.get('tab-list'))
|
|
261
|
+
customElements.define('tab-list', TabList);
|
|
262
|
+
if (!customElements.get('tab-button'))
|
|
263
|
+
customElements.define('tab-button', TabButton);
|
|
264
|
+
if (!customElements.get('tab-panel'))
|
|
265
|
+
customElements.define('tab-panel', TabPanel);
|
|
266
266
|
}
|
|
267
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
268
|
exports.default = TabGroup;
|
|
276
269
|
|
|
277
270
|
Object.defineProperty(exports, '__esModule', { value: true });
|
package/dist/tab-group.js.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"file":"tab-group.js","sources":["../src/tab-group.js"],"sourcesContent":["import './tab-group.css';\n\n/**\n * @module TabGroup\n * A fully accessible tab group web component\n */\n\nlet instanceCount = 0;\n\n/**\n * @class TabGroup\n * the parent container that coordinates tabs and panels\n */\nexport default class TabGroup extends HTMLElement {\n\t/**\n\t * @function ensureConsistentTabsAndPanels\n\t * makes sure there is an equal number of <tab-button> and <tab-panel> elements.\n\t * if there are more panels than tabs, inject extra tab buttons.\n\t * if there are more tabs than panels, inject extra panels.\n\t */\n\tensureConsistentTabsAndPanels() {\n\t\t// get current tabs and panels scoped to direct children only\n\t\tlet tabs = this.querySelectorAll(':scope > tab-list > tab-button');\n\t\tlet panels = this.querySelectorAll(':scope > tab-panel');\n\n\t\t// if there are more panels than tabs\n\t\tif (panels.length > tabs.length) {\n\t\t\tconst difference = panels.length - tabs.length;\n\t\t\t// try to find a <tab-list> to insert new tabs\n\t\t\tlet tabList = this.querySelector(':scope > tab-list');\n\t\t\tif (!tabList) {\n\t\t\t\t// if not present, create one and insert it at the beginning\n\t\t\t\ttabList = document.createElement('tab-list');\n\t\t\t\tthis.insertBefore(tabList, this.firstChild);\n\t\t\t}\n\t\t\t// inject extra <tab-button> elements into the tab list\n\t\t\tfor (let i = 0; i < difference; i++) {\n\t\t\t\tconst newTab = document.createElement('tab-button');\n\t\t\t\tnewTab.textContent = 'default tab';\n\t\t\t\ttabList.appendChild(newTab);\n\t\t\t}\n\t\t}\n\t\t// if there are more tabs than panels\n\t\telse if (tabs.length > panels.length) {\n\t\t\tconst difference = tabs.length - panels.length;\n\t\t\t// inject extra <tab-panel> elements at the end of the tab group\n\t\t\tfor (let i = 0; i < difference; i++) {\n\t\t\t\tconst newPanel = document.createElement('tab-panel');\n\t\t\t\tnewPanel.innerHTML = '<p>default panel content</p>';\n\t\t\t\tthis.appendChild(newPanel);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * called when the element is connected to the dom\n\t */\n\tconnectedCallback() {\n\t\t// assign a stable instance id on first connect\n\t\tif (!this._instanceId) {\n\t\t\tthis._instanceId = `tg-${instanceCount++}`;\n\t\t}\n\n\t\t// ensure that the number of <tab-button> and <tab-panel> elements match\n\t\tthis.ensureConsistentTabsAndPanels();\n\n\t\t// find the <tab-list> element (should be exactly one)\n\t\tthis.tabList = this.querySelector(':scope > tab-list');\n\t\tif (!this.tabList) return;\n\n\t\t// find all <tab-button> elements inside the <tab-list>\n\t\tthis.tabButtons = Array.from(\n\t\t\tthis.tabList.querySelectorAll('tab-button')\n\t\t);\n\n\t\t// find all <tab-panel> elements inside the <tab-group>\n\t\tthis.tabPanels = Array.from(this.querySelectorAll(':scope > tab-panel'));\n\n\t\tconst prefix = this._instanceId;\n\n\t\t// initialize each tab-button with roles, ids and aria attributes\n\t\tthis.tabButtons.forEach((tab, index) => {\n\t\t\tconst tabId = `${prefix}-tab-${index}`;\n\t\t\tconst panelId = `${prefix}-panel-${index}`;\n\t\t\ttab.id = tabId;\n\t\t\ttab.setAttribute('role', 'tab');\n\t\t\ttab.setAttribute('aria-controls', panelId);\n\n\t\t\t// first tab is active by default\n\t\t\tif (index === 0) {\n\t\t\t\ttab.setAttribute('aria-selected', 'true');\n\t\t\t\ttab.setAttribute('tabindex', '0');\n\t\t\t} else {\n\t\t\t\ttab.setAttribute('aria-selected', 'false');\n\t\t\t\ttab.setAttribute('tabindex', '-1');\n\t\t\t}\n\t\t});\n\n\t\t// initialize each tab-panel with roles, ids and aria attributes\n\t\tthis.tabPanels.forEach((panel, index) => {\n\t\t\tconst panelId = `${prefix}-panel-${index}`;\n\t\t\tpanel.id = panelId;\n\t\t\tpanel.setAttribute('role', 'tabpanel');\n\t\t\tpanel.setAttribute('aria-labelledby', `${prefix}-tab-${index}`);\n\n\t\t\t// hide panels except for the first one\n\t\t\tpanel.hidden = index !== 0;\n\t\t});\n\n\t\t// set up keyboard navigation and click delegation on the <tab-list>\n\t\tthis.tabList.setAttribute('role', 'tablist');\n\n\t\t// store bound handlers so we can remove them in disconnectedCallback\n\t\tif (!this._onKeyDown) {\n\t\t\tthis._onKeyDown = (e) => this.onKeyDown(e);\n\t\t\tthis._onClick = (e) => this.onClick(e);\n\t\t}\n\t\tthis.tabList.addEventListener('keydown', this._onKeyDown);\n\t\tthis.tabList.addEventListener('click', this._onClick);\n\t}\n\n\t/**\n\t * called when the element is disconnected from the dom\n\t */\n\tdisconnectedCallback() {\n\t\tif (this.tabList && this._onKeyDown) {\n\t\t\tthis.tabList.removeEventListener('keydown', this._onKeyDown);\n\t\t\tthis.tabList.removeEventListener('click', this._onClick);\n\t\t}\n\t}\n\n\t/**\n\t * @function setActiveTab\n\t * activates a tab and updates aria attributes\n\t * @param {number} index - index of the tab to activate\n\t */\n\tsetActiveTab(index) {\n\t\tif (index < 0 || index >= this.tabButtons.length) return;\n\t\tconst previousIndex = this.tabButtons.findIndex(\n\t\t\t(tab) => tab.getAttribute('aria-selected') === 'true'\n\t\t);\n\n\t\t// update each tab-button\n\t\tthis.tabButtons.forEach((tab, i) => {\n\t\t\tconst isActive = i === index;\n\t\t\ttab.setAttribute('aria-selected', isActive ? 'true' : 'false');\n\t\t\ttab.setAttribute('tabindex', isActive ? '0' : '-1');\n\t\t\tif (isActive) {\n\t\t\t\ttab.focus();\n\t\t\t}\n\t\t});\n\n\t\t// update each tab-panel\n\t\tthis.tabPanels.forEach((panel, i) => {\n\t\t\tpanel.hidden = i !== index;\n\t\t});\n\n\t\t// dispatch event only if the tab actually changed\n\t\tif (previousIndex !== index) {\n\t\t\tconst detail = {\n\t\t\t\tpreviousIndex,\n\t\t\t\tcurrentIndex: index,\n\t\t\t\tpreviousTab: this.tabButtons[previousIndex],\n\t\t\t\tcurrentTab: this.tabButtons[index],\n\t\t\t\tpreviousPanel: this.tabPanels[previousIndex],\n\t\t\t\tcurrentPanel: this.tabPanels[index],\n\t\t\t};\n\t\t\tthis.dispatchEvent(\n\t\t\t\tnew CustomEvent('tabchange', { detail, bubbles: true })\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * @function onClick\n\t * handles click events on the <tab-list> via event delegation\n\t * @param {MouseEvent} e - the click event\n\t */\n\tonClick(e) {\n\t\t// check if the click occurred on or within a <tab-button>\n\t\tconst tabButton = e.target.closest('tab-button');\n\t\tif (!tabButton) return;\n\n\t\t// determine the index of the clicked tab-button\n\t\tconst index = this.tabButtons.indexOf(tabButton);\n\t\tif (index === -1) return;\n\n\t\t// activate the tab with the corresponding index\n\t\tthis.setActiveTab(index);\n\t}\n\n\t/**\n\t * @function onKeyDown\n\t * handles keyboard navigation for the tabs\n\t * @param {KeyboardEvent} e - the keydown event\n\t */\n\tonKeyDown(e) {\n\t\t// only process keys if focus is on a <tab-button>\n\t\tconst targetIndex = this.tabButtons.indexOf(e.target);\n\t\tif (targetIndex === -1) return;\n\n\t\tlet newIndex = targetIndex;\n\t\tswitch (e.key) {\n\t\t\tcase 'ArrowLeft':\n\t\t\tcase 'ArrowUp':\n\t\t\t\t// move to the previous tab (wrap around if necessary)\n\t\t\t\tnewIndex =\n\t\t\t\t\ttargetIndex > 0 ? targetIndex - 1 : this.tabButtons.length - 1;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tcase 'ArrowRight':\n\t\t\tcase 'ArrowDown':\n\t\t\t\t// move to the next tab (wrap around if necessary)\n\t\t\t\tnewIndex = (targetIndex + 1) % this.tabButtons.length;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tcase 'Home':\n\t\t\t\t// jump to the first tab\n\t\t\t\tnewIndex = 0;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tcase 'End':\n\t\t\t\t// jump to the last tab\n\t\t\t\tnewIndex = this.tabButtons.length - 1;\n\t\t\t\te.preventDefault();\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\treturn; // ignore other keys\n\t\t}\n\t\tthis.setActiveTab(newIndex);\n\t}\n}\n\n/**\n * @class TabList\n * a container for the <tab-button> elements\n */\nclass TabList extends HTMLElement {}\n\n/**\n * @class TabButton\n * a single tab button element\n */\nclass TabButton extends HTMLElement {}\n\n/**\n * @class TabPanel\n * a single tab panel element\n */\nclass TabPanel extends HTMLElement {}\n\n// define the custom elements (guarded against double-registration and SSR)\nif (typeof window !== 'undefined' && window.customElements) {\n\tif (!customElements.get('tab-group'))\n\t\tcustomElements.define('tab-group', TabGroup);\n\tif (!customElements.get('tab-list'))\n\t\tcustomElements.define('tab-list', TabList);\n\tif (!customElements.get('tab-button'))\n\t\tcustomElements.define('tab-button', TabButton);\n\tif (!customElements.get('tab-panel'))\n\t\tcustomElements.define('tab-panel', TabPanel);\n}\n"],"names":[],"mappings":"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"}
|
package/dist/tab-group.min.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
tab-group{
|
|
1
|
+
tab-group{display:block}tab-list{display:flex;overflow-x:auto;overflow-y:hidden}tab-button{cursor:pointer;display:block;user-select:none}tab-panel[hidden]{display:none}
|
package/dist/tab-group.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TabGroup={})}(this,
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).TabGroup={})}(this,function(t){"use strict";let e=0;class TabGroup extends HTMLElement{ensureConsistentTabsAndPanels(){let t=this.querySelectorAll(":scope > tab-list > tab-button"),e=this.querySelectorAll(":scope > tab-panel");if(e.length>t.length){const s=e.length-t.length;let n=this.querySelector(":scope > tab-list");n||(n=document.createElement("tab-list"),this.insertBefore(n,this.firstChild));for(let t=0;t<s;t++){const t=document.createElement("tab-button");t.textContent="default tab",n.appendChild(t)}}else if(t.length>e.length){const s=t.length-e.length;for(let t=0;t<s;t++){const t=document.createElement("tab-panel");t.innerHTML="<p>default panel content</p>",this.appendChild(t)}}}connectedCallback(){if(this._instanceId||(this._instanceId="tg-"+e++),this.ensureConsistentTabsAndPanels(),this.tabList=this.querySelector(":scope > tab-list"),!this.tabList)return;this.tabButtons=Array.from(this.tabList.querySelectorAll("tab-button")),this.tabPanels=Array.from(this.querySelectorAll(":scope > tab-panel"));const t=this._instanceId;this.tabButtons.forEach((e,s)=>{const n=`${t}-tab-${s}`,a=`${t}-panel-${s}`;e.id=n,e.setAttribute("role","tab"),e.setAttribute("aria-controls",a),0===s?(e.setAttribute("aria-selected","true"),e.setAttribute("tabindex","0")):(e.setAttribute("aria-selected","false"),e.setAttribute("tabindex","-1"))}),this.tabPanels.forEach((e,s)=>{const n=`${t}-panel-${s}`;e.id=n,e.setAttribute("role","tabpanel"),e.setAttribute("aria-labelledby",`${t}-tab-${s}`),e.hidden=0!==s}),this.tabList.setAttribute("role","tablist"),this._onKeyDown||(this._onKeyDown=t=>this.onKeyDown(t),this._onClick=t=>this.onClick(t)),this.tabList.addEventListener("keydown",this._onKeyDown),this.tabList.addEventListener("click",this._onClick)}disconnectedCallback(){this.tabList&&this._onKeyDown&&(this.tabList.removeEventListener("keydown",this._onKeyDown),this.tabList.removeEventListener("click",this._onClick))}setActiveTab(t){if(t<0||t>=this.tabButtons.length)return;const e=this.tabButtons.findIndex(t=>"true"===t.getAttribute("aria-selected"));if(this.tabButtons.forEach((e,s)=>{const n=s===t;e.setAttribute("aria-selected",n?"true":"false"),e.setAttribute("tabindex",n?"0":"-1"),n&&e.focus()}),this.tabPanels.forEach((e,s)=>{e.hidden=s!==t}),e!==t){const s={previousIndex:e,currentIndex:t,previousTab:this.tabButtons[e],currentTab:this.tabButtons[t],previousPanel:this.tabPanels[e],currentPanel:this.tabPanels[t]};this.dispatchEvent(new CustomEvent("tabchange",{detail:s,bubbles:!0}))}}onClick(t){const e=t.target.closest("tab-button");if(!e)return;const s=this.tabButtons.indexOf(e);-1!==s&&this.setActiveTab(s)}onKeyDown(t){const e=this.tabButtons.indexOf(t.target);if(-1===e)return;let s=e;switch(t.key){case"ArrowLeft":case"ArrowUp":s=e>0?e-1:this.tabButtons.length-1,t.preventDefault();break;case"ArrowRight":case"ArrowDown":s=(e+1)%this.tabButtons.length,t.preventDefault();break;case"Home":s=0,t.preventDefault();break;case"End":s=this.tabButtons.length-1,t.preventDefault();break;default:return}this.setActiveTab(s)}}class TabList extends HTMLElement{}class TabButton extends HTMLElement{}class TabPanel extends HTMLElement{}"undefined"!=typeof window&&window.customElements&&(customElements.get("tab-group")||customElements.define("tab-group",TabGroup),customElements.get("tab-list")||customElements.define("tab-list",TabList),customElements.get("tab-button")||customElements.define("tab-button",TabButton),customElements.get("tab-panel")||customElements.define("tab-panel",TabPanel)),t.default=TabGroup,Object.defineProperty(t,"__esModule",{value:!0})});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic-spells/tab-group",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Tab group and tab list html components.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Cory Schulz",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"main": "dist/tab-group.cjs.js",
|
|
9
9
|
"module": "dist/tab-group.esm.js",
|
|
10
10
|
"unpkg": "dist/tab-group.min.js",
|
|
11
|
+
"types": "tab-group.d.ts",
|
|
11
12
|
"style": "dist/tab-group.min.css",
|
|
12
|
-
"sass": "dist/tab-group.scss",
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
15
|
"import": "./dist/tab-group.esm.js",
|
|
@@ -17,9 +17,7 @@
|
|
|
17
17
|
"default": "./dist/tab-group.esm.js"
|
|
18
18
|
},
|
|
19
19
|
"./css": "./dist/tab-group.css",
|
|
20
|
-
"./css/min": "./dist/tab-group.min.css"
|
|
21
|
-
"./scss": "./dist/tab-group.scss",
|
|
22
|
-
"./scss/*": "./dist/scss/*"
|
|
20
|
+
"./css/min": "./dist/tab-group.min.css"
|
|
23
21
|
},
|
|
24
22
|
"sideEffects": true,
|
|
25
23
|
"repository": {
|
|
@@ -28,7 +26,7 @@
|
|
|
28
26
|
},
|
|
29
27
|
"files": [
|
|
30
28
|
"dist/",
|
|
31
|
-
"
|
|
29
|
+
"tab-group.d.ts"
|
|
32
30
|
],
|
|
33
31
|
"publishConfig": {
|
|
34
32
|
"access": "public",
|
|
@@ -40,20 +38,21 @@
|
|
|
40
38
|
"not ie <= 11"
|
|
41
39
|
],
|
|
42
40
|
"devDependencies": {
|
|
43
|
-
"@eslint/js": "^
|
|
41
|
+
"@eslint/js": "^9.38.0",
|
|
44
42
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
|
45
|
-
"@rollup/plugin-terser": "^0.
|
|
46
|
-
"
|
|
47
|
-
"
|
|
43
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
44
|
+
"caniuse-lite": "^1.0.30001777",
|
|
45
|
+
"eslint": "^9.38.0",
|
|
46
|
+
"globals": "^15.15.0",
|
|
48
47
|
"prettier": "^3.3.3",
|
|
49
48
|
"rollup": "^3.0.0",
|
|
50
49
|
"rollup-plugin-copy": "^3.5.0",
|
|
51
50
|
"rollup-plugin-postcss": "^4.0.2",
|
|
52
|
-
"rollup-plugin-serve": "^1.1.1"
|
|
53
|
-
"sass": "^1.86.3"
|
|
51
|
+
"rollup-plugin-serve": "^1.1.1"
|
|
54
52
|
},
|
|
55
53
|
"scripts": {
|
|
56
|
-
"
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"build": "npm run clean && rollup -c",
|
|
57
56
|
"lint": "eslint src/ rollup.config.mjs",
|
|
58
57
|
"format": "prettier --write .",
|
|
59
58
|
"prepublishOnly": "npm run build",
|
package/tab-group.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface TabChangeEventDetail {
|
|
2
|
+
previousIndex: number;
|
|
3
|
+
currentIndex: number;
|
|
4
|
+
previousTab: HTMLElement;
|
|
5
|
+
currentTab: HTMLElement;
|
|
6
|
+
previousPanel: HTMLElement;
|
|
7
|
+
currentPanel: HTMLElement;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export default class TabGroup extends HTMLElement {
|
|
11
|
+
/** The tab-list element within this tab group. */
|
|
12
|
+
tabList: HTMLElement | null;
|
|
13
|
+
|
|
14
|
+
/** Array of tab-button elements. */
|
|
15
|
+
tabButtons: HTMLElement[];
|
|
16
|
+
|
|
17
|
+
/** Array of tab-panel elements. */
|
|
18
|
+
tabPanels: HTMLElement[];
|
|
19
|
+
|
|
20
|
+
/** Activates the tab at the given index. */
|
|
21
|
+
setActiveTab(index: number): void;
|
|
22
|
+
|
|
23
|
+
addEventListener(
|
|
24
|
+
type: 'tabchange',
|
|
25
|
+
listener: (event: CustomEvent<TabChangeEventDetail>) => void,
|
|
26
|
+
options?: boolean | AddEventListenerOptions,
|
|
27
|
+
): void;
|
|
28
|
+
addEventListener(
|
|
29
|
+
type: string,
|
|
30
|
+
listener: EventListenerOrEventListenerObject,
|
|
31
|
+
options?: boolean | AddEventListenerOptions,
|
|
32
|
+
): void;
|
|
33
|
+
}
|