@y14e/tabs 1.3.11 → 1.4.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/dist/index.cjs +581 -89
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +581 -89
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,506 @@ function saveAttributes(elements, attributes) {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// node_modules/@y14e/roving-tabindex/dist/index.js
|
|
54
|
+
var defaultParser2 = (value) => value.split(/\s+/);
|
|
55
|
+
var defaultSerializer2 = (tokens) => tokens.join(" ");
|
|
56
|
+
function addTokenToAttribute2(element, attribute, token, options = {}) {
|
|
57
|
+
const {
|
|
58
|
+
caseInsensitive = false,
|
|
59
|
+
parse = defaultParser2,
|
|
60
|
+
serialize = defaultSerializer2
|
|
61
|
+
} = options;
|
|
62
|
+
const value = element.getAttribute(attribute)?.trim();
|
|
63
|
+
const tokens = value ? parse(value).filter(Boolean) : [];
|
|
64
|
+
if (caseInsensitive) {
|
|
65
|
+
const lower = token.toLowerCase();
|
|
66
|
+
if (tokens.some((token2) => token2.toLowerCase() === lower)) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
tokens.push(token);
|
|
70
|
+
element.setAttribute(attribute, serialize(tokens));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const set = new Set(tokens);
|
|
74
|
+
set.add(token);
|
|
75
|
+
element.setAttribute(attribute, serialize([...set]));
|
|
76
|
+
}
|
|
77
|
+
var snapshots2 = /* @__PURE__ */ new WeakMap();
|
|
78
|
+
function restoreAttributes2(elements) {
|
|
79
|
+
for (const element of elements) {
|
|
80
|
+
const snapshot = snapshots2.get(element);
|
|
81
|
+
if (!snapshot) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
for (const [attribute, value] of snapshot.entries()) {
|
|
85
|
+
value === null ? element.removeAttribute(attribute) : element.setAttribute(attribute, value);
|
|
86
|
+
}
|
|
87
|
+
snapshots2.delete(element);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function saveAttributes2(elements, attributes) {
|
|
91
|
+
elements.forEach((element) => {
|
|
92
|
+
let snapshot = snapshots2.get(element);
|
|
93
|
+
if (!snapshot) {
|
|
94
|
+
snapshot = /* @__PURE__ */ new Map();
|
|
95
|
+
snapshots2.set(element, snapshot);
|
|
96
|
+
}
|
|
97
|
+
attributes.forEach((attribute) => {
|
|
98
|
+
snapshot.set(attribute, element.getAttribute(attribute));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
var FOCUSABLE_SELECTOR = `:is(a[href], area[href], button, embed, iframe, input:not([type="hidden" i]), object, select, details > summary:first-of-type, textarea, [contenteditable]:not([contenteditable="false" i]), [controls], [tabindex]):not(:disabled, [hidden], [inert], [tabindex="-1"])`;
|
|
103
|
+
function getFocusables(container = document.body, options = {}) {
|
|
104
|
+
if (!(container instanceof Element)) {
|
|
105
|
+
console.warn("Invalid container element. Fallback: <body> element.");
|
|
106
|
+
container = document.body;
|
|
107
|
+
}
|
|
108
|
+
let {
|
|
109
|
+
composed = false,
|
|
110
|
+
filter,
|
|
111
|
+
include,
|
|
112
|
+
skipVisibilityCheck = false
|
|
113
|
+
} = options;
|
|
114
|
+
if (typeof composed !== "boolean") {
|
|
115
|
+
console.warn("Invalid composed option. Fallback: false.");
|
|
116
|
+
composed = false;
|
|
117
|
+
}
|
|
118
|
+
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
119
|
+
console.warn(
|
|
120
|
+
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
121
|
+
);
|
|
122
|
+
filter = void 0;
|
|
123
|
+
}
|
|
124
|
+
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
125
|
+
console.warn(
|
|
126
|
+
"Invalid include function. Fallback: no include function (undefined)."
|
|
127
|
+
);
|
|
128
|
+
include = void 0;
|
|
129
|
+
}
|
|
130
|
+
const elements = [];
|
|
131
|
+
if (composed || include) {
|
|
132
|
+
let traverse2 = function(node) {
|
|
133
|
+
if (node instanceof Element) {
|
|
134
|
+
if (isFocusable(node, { skipVisibilityCheck }) || include?.(node)) {
|
|
135
|
+
elements[elements.length] = node;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const children = getComposedChildren(node);
|
|
139
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
140
|
+
const child = children[i];
|
|
141
|
+
if (!child) {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
traverse2(child);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
traverse2(container);
|
|
148
|
+
} else {
|
|
149
|
+
const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
150
|
+
for (let i = 0, l = candidates.length; i < l; i++) {
|
|
151
|
+
const candidate = candidates[i];
|
|
152
|
+
if (!(candidate instanceof Element)) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (isFocusable(candidate, { skipVisibilityCheck })) {
|
|
156
|
+
elements[elements.length] = candidate;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
const unfiltered = normalizeRadioGroup(sortByTabIndex(elements));
|
|
161
|
+
return filter ? unfiltered.filter(filter) : unfiltered;
|
|
162
|
+
}
|
|
163
|
+
function isFocusable(element, options = {}) {
|
|
164
|
+
if (!(element instanceof Element)) {
|
|
165
|
+
console.warn("Invalid element");
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
let { skipVisibilityCheck = false } = options;
|
|
169
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
170
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
171
|
+
skipVisibilityCheck = false;
|
|
172
|
+
}
|
|
173
|
+
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
if (getTabIndex(element) < 0) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (!element.matches(FOCUSABLE_SELECTOR)) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (isDisabledDeep(element)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
if (!skipVisibilityCheck && !element.checkVisibility({
|
|
186
|
+
contentVisibilityAuto: true,
|
|
187
|
+
opacityProperty: true,
|
|
188
|
+
visibilityProperty: true
|
|
189
|
+
})) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
function isDisabledDeep(element) {
|
|
195
|
+
let current = element;
|
|
196
|
+
while (current) {
|
|
197
|
+
if (current instanceof ShadowRoot) {
|
|
198
|
+
if (current.mode !== "open") {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
current = current.host;
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!(current instanceof Element)) {
|
|
205
|
+
current = current.parentNode;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (current === element && isFormControl(current) && isDisabled(current)) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
if (isInert(current)) {
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
if (isFormControl(element) && current.tagName === "FIELDSET" && isDisabled(current)) {
|
|
215
|
+
if (!current.querySelector(":scope > legend:first-of-type")?.contains(element)) {
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
current = current.parentNode;
|
|
220
|
+
}
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
function normalizeRadioGroup(elements) {
|
|
224
|
+
let map = null;
|
|
225
|
+
for (let i = 0, l = elements.length; i < l; i++) {
|
|
226
|
+
const element = elements[i];
|
|
227
|
+
if (!(element instanceof HTMLInputElement)) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!isUngroupedRadio(element)) {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (!map) {
|
|
234
|
+
map = /* @__PURE__ */ new Map();
|
|
235
|
+
}
|
|
236
|
+
const key = `${element.form?.id ?? "no-form"}::${element.name}`;
|
|
237
|
+
const group = map.get(key) ?? map.set(key, []).get(key);
|
|
238
|
+
if (group) {
|
|
239
|
+
group[group.length] = element;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (!map) {
|
|
243
|
+
return elements;
|
|
244
|
+
}
|
|
245
|
+
const placeholder = /* @__PURE__ */ new Set();
|
|
246
|
+
for (const group of map.values()) {
|
|
247
|
+
placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
|
|
248
|
+
}
|
|
249
|
+
return elements.filter((element) => {
|
|
250
|
+
if (isUngroupedRadio(element)) {
|
|
251
|
+
return placeholder.has(element);
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function sortByTabIndex(elements) {
|
|
257
|
+
const ordered = [];
|
|
258
|
+
const natural = [];
|
|
259
|
+
for (let i = 0, l = elements.length; i < l; i++) {
|
|
260
|
+
const element = elements[i];
|
|
261
|
+
if (!element) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
const target = getTabIndex(element) > 0 ? ordered : natural;
|
|
265
|
+
target[target.length] = element;
|
|
266
|
+
}
|
|
267
|
+
ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
|
|
268
|
+
let count = 0;
|
|
269
|
+
const sorted = new Array(ordered.length + natural.length);
|
|
270
|
+
for (let i = 0, l = ordered.length; i < l; i++) {
|
|
271
|
+
sorted[count++] = ordered[i];
|
|
272
|
+
}
|
|
273
|
+
for (let i = 0, l = natural.length; i < l; i++) {
|
|
274
|
+
sorted[count++] = natural[i];
|
|
275
|
+
}
|
|
276
|
+
return sorted;
|
|
277
|
+
}
|
|
278
|
+
function getComposedChildren(node) {
|
|
279
|
+
if (node instanceof ShadowRoot) {
|
|
280
|
+
return getChildren(node);
|
|
281
|
+
}
|
|
282
|
+
if (!(node instanceof Element)) {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
if (node instanceof HTMLSlotElement) {
|
|
286
|
+
const assigned = node.assignedElements({ flatten: true });
|
|
287
|
+
if (assigned.length) {
|
|
288
|
+
return assigned;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (node instanceof HTMLElement && node.shadowRoot?.mode === "open") {
|
|
292
|
+
return getChildren(node.shadowRoot);
|
|
293
|
+
}
|
|
294
|
+
return getChildren(node);
|
|
295
|
+
}
|
|
296
|
+
function getChildren(node) {
|
|
297
|
+
const elements = [];
|
|
298
|
+
for (let child = node.firstElementChild; child; child = child.nextElementSibling) {
|
|
299
|
+
elements[elements.length] = child;
|
|
300
|
+
}
|
|
301
|
+
return elements;
|
|
302
|
+
}
|
|
303
|
+
function getTabIndex(element) {
|
|
304
|
+
return "tabIndex" in element ? Number(element.tabIndex) : 0;
|
|
305
|
+
}
|
|
306
|
+
function isDisabled(element) {
|
|
307
|
+
return "disabled" in element && !!element.disabled;
|
|
308
|
+
}
|
|
309
|
+
function isFormControl(element) {
|
|
310
|
+
const name = element.tagName;
|
|
311
|
+
return name === "BUTTON" || name === "INPUT" || name === "SELECT" || name === "TEXTAREA";
|
|
312
|
+
}
|
|
313
|
+
function isInert(element) {
|
|
314
|
+
return "inert" in element && !!element.inert;
|
|
315
|
+
}
|
|
316
|
+
function isUngroupedRadio(element) {
|
|
317
|
+
return element instanceof HTMLInputElement && element.type === "radio" && !!element.name;
|
|
318
|
+
}
|
|
319
|
+
function createRovingTabIndex(container, options = {}) {
|
|
320
|
+
if (!(container instanceof Element)) {
|
|
321
|
+
console.warn("Invalid container element");
|
|
322
|
+
return () => {
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
let {
|
|
326
|
+
direction,
|
|
327
|
+
navigationOnly = false,
|
|
328
|
+
selector,
|
|
329
|
+
typeahead = false,
|
|
330
|
+
wrap = false
|
|
331
|
+
} = options;
|
|
332
|
+
if (typeof direction !== "undefined" && !["horizontal", "vertical"].includes(direction)) {
|
|
333
|
+
console.warn("Invalid direction option. Fallback: both (undefined).");
|
|
334
|
+
direction = void 0;
|
|
335
|
+
}
|
|
336
|
+
if (typeof navigationOnly !== "boolean") {
|
|
337
|
+
console.warn("Invalid navigationOnly option. Fallback: false.");
|
|
338
|
+
navigationOnly = false;
|
|
339
|
+
}
|
|
340
|
+
if (typeof selector !== "undefined" && typeof selector !== "string") {
|
|
341
|
+
console.warn(
|
|
342
|
+
"Invalid selector. Fallback: all focusable elements (undefined)."
|
|
343
|
+
);
|
|
344
|
+
selector = void 0;
|
|
345
|
+
}
|
|
346
|
+
if (typeof typeahead !== "boolean") {
|
|
347
|
+
console.warn("Invalid typeahead option. Fallback: false.");
|
|
348
|
+
typeahead = false;
|
|
349
|
+
}
|
|
350
|
+
if (typeof wrap !== "boolean") {
|
|
351
|
+
console.warn("Invalid wrap option. Fallback: false.");
|
|
352
|
+
wrap = false;
|
|
353
|
+
}
|
|
354
|
+
const roving = new RovingTabIndex(container, {
|
|
355
|
+
direction,
|
|
356
|
+
navigationOnly,
|
|
357
|
+
selector,
|
|
358
|
+
typeahead,
|
|
359
|
+
wrap
|
|
360
|
+
});
|
|
361
|
+
return () => roving.destroy();
|
|
362
|
+
}
|
|
363
|
+
var RovingTabIndex = class {
|
|
364
|
+
#container;
|
|
365
|
+
#options;
|
|
366
|
+
#focusables = /* @__PURE__ */ new Set();
|
|
367
|
+
#focusablesByFirstChar = /* @__PURE__ */ new Map();
|
|
368
|
+
#selectorFilter;
|
|
369
|
+
#controller = null;
|
|
370
|
+
#isDestroyed = false;
|
|
371
|
+
constructor(container, options = {}) {
|
|
372
|
+
this.#container = container;
|
|
373
|
+
this.#options = options;
|
|
374
|
+
this.#selectorFilter = this.#createSelectorFilter();
|
|
375
|
+
this.#initialize();
|
|
376
|
+
}
|
|
377
|
+
destroy() {
|
|
378
|
+
if (this.#isDestroyed) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
this.#isDestroyed = true;
|
|
382
|
+
this.#controller?.abort();
|
|
383
|
+
this.#controller = null;
|
|
384
|
+
restoreAttributes2([...this.#focusables]);
|
|
385
|
+
this.#focusables.clear();
|
|
386
|
+
this.#focusablesByFirstChar.clear();
|
|
387
|
+
this.#container.removeAttribute("data-roving-tabindex-initialized");
|
|
388
|
+
}
|
|
389
|
+
#initialize() {
|
|
390
|
+
this.#update(document.activeElement);
|
|
391
|
+
this.#controller = new AbortController();
|
|
392
|
+
document.addEventListener("keydown", this.#onKeyDown, {
|
|
393
|
+
capture: true,
|
|
394
|
+
signal: this.#controller.signal
|
|
395
|
+
});
|
|
396
|
+
this.#container.setAttribute("data-roving-tabindex-initialized", "");
|
|
397
|
+
}
|
|
398
|
+
#onKeyDown = (event) => {
|
|
399
|
+
if (!event.composedPath().includes(this.#container)) {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const { key, altKey, ctrlKey, metaKey } = event;
|
|
403
|
+
if (altKey || ctrlKey || metaKey) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const { direction, typeahead, wrap } = this.#options;
|
|
407
|
+
const isBoth = !direction;
|
|
408
|
+
const isHorizontal = direction === "horizontal";
|
|
409
|
+
if (![
|
|
410
|
+
"End",
|
|
411
|
+
"Home",
|
|
412
|
+
...isBoth ? ["ArrowLeft", "ArrowUp"] : [`Arrow${isHorizontal ? "Left" : "Up"}`],
|
|
413
|
+
...isBoth ? ["ArrowRight", "ArrowDown"] : [`Arrow${isHorizontal ? "Right" : "Down"}`]
|
|
414
|
+
].includes(key)) {
|
|
415
|
+
if (!typeahead || !/^\S$/i.test(key) || !this.#focusablesByFirstChar.has(key.toUpperCase())) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const active = getActiveElement();
|
|
420
|
+
if (!(active instanceof HTMLElement)) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
const current = this.#getFocusables();
|
|
424
|
+
if (!current.includes(active)) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
event.stopPropagation();
|
|
429
|
+
const currentIndex = current.indexOf(active);
|
|
430
|
+
let rawIndex;
|
|
431
|
+
let newIndex = currentIndex;
|
|
432
|
+
let target = current;
|
|
433
|
+
switch (key) {
|
|
434
|
+
case "End":
|
|
435
|
+
newIndex = -1;
|
|
436
|
+
break;
|
|
437
|
+
case "Home":
|
|
438
|
+
newIndex = 0;
|
|
439
|
+
break;
|
|
440
|
+
case "ArrowLeft":
|
|
441
|
+
case "ArrowUp":
|
|
442
|
+
rawIndex = currentIndex - 1;
|
|
443
|
+
newIndex = wrap ? rawIndex : Math.max(rawIndex, 0);
|
|
444
|
+
break;
|
|
445
|
+
case "ArrowRight":
|
|
446
|
+
case "ArrowDown":
|
|
447
|
+
rawIndex = currentIndex + 1;
|
|
448
|
+
newIndex = wrap ? rawIndex % current.length : Math.min(rawIndex, current.length - 1);
|
|
449
|
+
break;
|
|
450
|
+
default: {
|
|
451
|
+
target = this.#focusablesByFirstChar.get(key.toUpperCase()) ?? [];
|
|
452
|
+
const foundIndex = target.findIndex(
|
|
453
|
+
(focusable2) => current.indexOf(focusable2) > currentIndex
|
|
454
|
+
);
|
|
455
|
+
newIndex = foundIndex >= 0 ? foundIndex : 0;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const focusable = target.at(newIndex);
|
|
459
|
+
if (!focusable) {
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
this.#update(focusable);
|
|
463
|
+
focusElement(focusable);
|
|
464
|
+
};
|
|
465
|
+
#update(active) {
|
|
466
|
+
const current = /* @__PURE__ */ new Set([
|
|
467
|
+
...this.#getFocusables(),
|
|
468
|
+
...getFocusables(this.#container, {
|
|
469
|
+
composed: true,
|
|
470
|
+
filter: this.#selectorFilter,
|
|
471
|
+
skipVisibilityCheck: true
|
|
472
|
+
})
|
|
473
|
+
]);
|
|
474
|
+
for (const focusable of this.#focusables) {
|
|
475
|
+
if (current.has(focusable)) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
focusable.isConnected && restoreAttributes2([focusable]);
|
|
479
|
+
this.#focusables.delete(focusable);
|
|
480
|
+
this.#focusablesByFirstChar.forEach((focusables) => {
|
|
481
|
+
const index = focusables.indexOf(focusable);
|
|
482
|
+
index >= 0 && focusables.splice(index, 1);
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
const { navigationOnly } = this.#options;
|
|
486
|
+
for (const focusable of current) {
|
|
487
|
+
if (this.#focusables.has(focusable)) {
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
this.#focusables.add(focusable);
|
|
491
|
+
if (!navigationOnly) {
|
|
492
|
+
saveAttributes2([focusable], ["tabindex"]);
|
|
493
|
+
focusable.setAttribute("tabindex", "-1");
|
|
494
|
+
}
|
|
495
|
+
if (!this.#options.typeahead) {
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
const value = focusable.ariaKeyShortcuts?.trim();
|
|
499
|
+
const keys = new Set(
|
|
500
|
+
value ? value.split(/\s+/).filter((key) => /^\S$/i.test(key)).map((key) => key.toUpperCase()) : []
|
|
501
|
+
);
|
|
502
|
+
const char = focusable.textContent?.trim()?.at(0)?.toUpperCase();
|
|
503
|
+
if (char) {
|
|
504
|
+
keys.add(char);
|
|
505
|
+
saveAttributes2([focusable], ["aria-keyshortcuts"]);
|
|
506
|
+
addTokenToAttribute2(focusable, "aria-keyshortcuts", char, {
|
|
507
|
+
caseInsensitive: true
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
keys.forEach((key) => {
|
|
511
|
+
const focusables = this.#focusablesByFirstChar.get(key) ?? [];
|
|
512
|
+
focusables.push(focusable);
|
|
513
|
+
this.#focusablesByFirstChar.set(key, focusables);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
if (navigationOnly) {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (active && this.#focusables.has(active)) {
|
|
520
|
+
this.#focusables.forEach((focusable) => {
|
|
521
|
+
focusable.setAttribute("tabindex", focusable === active ? "0" : "-1");
|
|
522
|
+
});
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
[...this.#focusables].forEach((focusable, i) => {
|
|
526
|
+
focusable.setAttribute("tabindex", i ? "-1" : "0");
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
#createSelectorFilter() {
|
|
530
|
+
const { selector } = this.#options;
|
|
531
|
+
return (element) => !selector || [...this.#container.querySelectorAll(selector)].includes(element);
|
|
532
|
+
}
|
|
533
|
+
#getFocusables() {
|
|
534
|
+
return getFocusables(this.#container, {
|
|
535
|
+
composed: true,
|
|
536
|
+
filter: this.#selectorFilter,
|
|
537
|
+
include: (element) => this.#focusables.has(element),
|
|
538
|
+
skipVisibilityCheck: true
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
function focusElement(element) {
|
|
543
|
+
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
544
|
+
}
|
|
545
|
+
function getActiveElement() {
|
|
546
|
+
let current = document.activeElement;
|
|
547
|
+
while (current?.shadowRoot?.activeElement) {
|
|
548
|
+
current = current.shadowRoot.activeElement;
|
|
549
|
+
}
|
|
550
|
+
return current;
|
|
551
|
+
}
|
|
552
|
+
|
|
53
553
|
// src/index.ts
|
|
54
554
|
var Tabs = class _Tabs {
|
|
55
555
|
static defaults = {};
|
|
@@ -87,6 +587,7 @@ var Tabs = class _Tabs {
|
|
|
87
587
|
#bindings = /* @__PURE__ */ new WeakMap();
|
|
88
588
|
#eventController = null;
|
|
89
589
|
#animationController = null;
|
|
590
|
+
#cleanupsRovingTabIndex = [];
|
|
90
591
|
#animation = null;
|
|
91
592
|
#indicators = [];
|
|
92
593
|
#isDestroyed = false;
|
|
@@ -101,12 +602,10 @@ var Tabs = class _Tabs {
|
|
|
101
602
|
this.#rootElement = root;
|
|
102
603
|
this.#defaults = this.#mergeOptions(this.#defaults, _Tabs.defaults);
|
|
103
604
|
this.#settings = this.#mergeOptions(this.#defaults, options);
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
});
|
|
109
|
-
}
|
|
605
|
+
matchMedia("(prefers-reduced-motion: reduce)").matches && Object.assign(this.#settings.animation, {
|
|
606
|
+
content: { duration: 0 },
|
|
607
|
+
indicator: { duration: 0 }
|
|
608
|
+
});
|
|
110
609
|
const NOT_NESTED = `:not(:scope ${this.#settings.selector.panel} *)`;
|
|
111
610
|
this.#listElements = [
|
|
112
611
|
...this.#rootElement.querySelectorAll(
|
|
@@ -160,9 +659,7 @@ var Tabs = class _Tabs {
|
|
|
160
659
|
}
|
|
161
660
|
const binding = createBinding(tabsByIndex, panel);
|
|
162
661
|
this.#bindings.set(tab, binding);
|
|
163
|
-
|
|
164
|
-
this.#bindings.set(panel, binding);
|
|
165
|
-
}
|
|
662
|
+
i < length && this.#bindings.set(panel, binding);
|
|
166
663
|
});
|
|
167
664
|
this.#initialize();
|
|
168
665
|
}
|
|
@@ -207,11 +704,7 @@ var Tabs = class _Tabs {
|
|
|
207
704
|
}
|
|
208
705
|
style2.setProperty("inline-size", "100%");
|
|
209
706
|
style2.setProperty("position", "absolute");
|
|
210
|
-
|
|
211
|
-
p.setAttribute("tabindex", "0");
|
|
212
|
-
} else {
|
|
213
|
-
p.removeAttribute("tabindex");
|
|
214
|
-
}
|
|
707
|
+
p === panel && !hasFocusable(p) ? p.setAttribute("tabindex", "0") : p.removeAttribute("tabindex");
|
|
215
708
|
});
|
|
216
709
|
this.#panelElements.forEach((p, i) => {
|
|
217
710
|
if (p === panel) {
|
|
@@ -221,7 +714,7 @@ var Tabs = class _Tabs {
|
|
|
221
714
|
if (!tab2) {
|
|
222
715
|
return;
|
|
223
716
|
}
|
|
224
|
-
p.setAttribute("hidden",
|
|
717
|
+
p.setAttribute("hidden", isFocusable2(tab2) ? "until-found" : "");
|
|
225
718
|
}
|
|
226
719
|
});
|
|
227
720
|
this.#animation?.cancel();
|
|
@@ -294,6 +787,10 @@ var Tabs = class _Tabs {
|
|
|
294
787
|
this.#isDestroyed = true;
|
|
295
788
|
this.#eventController?.abort();
|
|
296
789
|
this.#eventController = null;
|
|
790
|
+
this.#cleanupsRovingTabIndex.forEach((cleanup) => {
|
|
791
|
+
cleanup();
|
|
792
|
+
});
|
|
793
|
+
this.#cleanupsRovingTabIndex.length = 0;
|
|
297
794
|
this.#indicators.forEach((indicator) => {
|
|
298
795
|
indicator.destroy(force);
|
|
299
796
|
});
|
|
@@ -346,12 +843,8 @@ var Tabs = class _Tabs {
|
|
|
346
843
|
"style"
|
|
347
844
|
]);
|
|
348
845
|
this.#listElements.forEach((list, i) => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
if (this.#settings.vertical) {
|
|
353
|
-
list.setAttribute("aria-orientation", "vertical");
|
|
354
|
-
}
|
|
846
|
+
this.#settings.avoidDuplicates && i && list.setAttribute("aria-hidden", "true");
|
|
847
|
+
this.#settings.vertical && list.setAttribute("aria-orientation", "vertical");
|
|
355
848
|
list.setAttribute("role", "tablist");
|
|
356
849
|
});
|
|
357
850
|
saveAttributes(this.#tabElements, [
|
|
@@ -367,29 +860,19 @@ var Tabs = class _Tabs {
|
|
|
367
860
|
if (!panel) {
|
|
368
861
|
return;
|
|
369
862
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
);
|
|
375
|
-
}
|
|
863
|
+
i < this.#panelElements.length && saveAttributes(
|
|
864
|
+
[panel],
|
|
865
|
+
["aria-controls", "aria-labelledby", "id", "role", "tabindex"]
|
|
866
|
+
);
|
|
376
867
|
panel.id ||= `tabs-panel-${id}`;
|
|
377
868
|
addTokenToAttribute(tab, "aria-controls", panel.id);
|
|
378
|
-
|
|
379
|
-
tab.setAttribute("aria-selected", "false");
|
|
380
|
-
}
|
|
869
|
+
!tab.hasAttribute("aria-selected") && tab.setAttribute("aria-selected", "false");
|
|
381
870
|
const isAvoided = this.#isAvoidedTab(tab);
|
|
382
871
|
if (!isAvoided) {
|
|
383
872
|
tab.id ||= `tabs-tab-${id}`;
|
|
384
873
|
}
|
|
385
874
|
tab.setAttribute("role", "tab");
|
|
386
|
-
tab.
|
|
387
|
-
"tabindex",
|
|
388
|
-
tab.ariaSelected === "true" && !isAvoided ? "0" : "-1"
|
|
389
|
-
);
|
|
390
|
-
if (!isFocusable(tab)) {
|
|
391
|
-
tab.style.setProperty("pointer-events", "none");
|
|
392
|
-
}
|
|
875
|
+
!isFocusable2(tab) && tab.style.setProperty("pointer-events", "none");
|
|
393
876
|
addTokenToAttribute(panel, "aria-labelledby", tab.id);
|
|
394
877
|
tab.addEventListener("click", this.#onTabClick, { signal });
|
|
395
878
|
tab.addEventListener("focus", this.#onTabFocus, { signal });
|
|
@@ -405,13 +888,26 @@ var Tabs = class _Tabs {
|
|
|
405
888
|
});
|
|
406
889
|
this.#panelElements.forEach((panel) => {
|
|
407
890
|
panel.setAttribute("role", "tabpanel");
|
|
408
|
-
|
|
409
|
-
panel.setAttribute("tabindex", "0");
|
|
410
|
-
}
|
|
891
|
+
!panel.hasAttribute("hidden") && !hasFocusable(panel) && panel.setAttribute("tabindex", "0");
|
|
411
892
|
panel.addEventListener("beforematch", this.#onPanelBeforeMatch, {
|
|
412
893
|
signal
|
|
413
894
|
});
|
|
414
895
|
});
|
|
896
|
+
this.#listElements.forEach((list) => {
|
|
897
|
+
this.#cleanupsRovingTabIndex.push(
|
|
898
|
+
createRovingTabIndex(list, {
|
|
899
|
+
direction: list.ariaOrientation === "undefined" ? void 0 : this.#settings.vertical ? "vertical" : "horizontal",
|
|
900
|
+
selector: this.#settings.selector.tab,
|
|
901
|
+
wrap: true
|
|
902
|
+
})
|
|
903
|
+
);
|
|
904
|
+
list.querySelectorAll(this.#settings.selector.tab).forEach((tab) => {
|
|
905
|
+
tab.setAttribute(
|
|
906
|
+
"tabindex",
|
|
907
|
+
tab.ariaSelected === "true" && !this.#isAvoidedTab(tab) ? "0" : "-1"
|
|
908
|
+
);
|
|
909
|
+
});
|
|
910
|
+
});
|
|
415
911
|
this.#rootElement.setAttribute("data-tabs-initialized", "");
|
|
416
912
|
}
|
|
417
913
|
#onTabClick = (event) => {
|
|
@@ -427,6 +923,7 @@ var Tabs = class _Tabs {
|
|
|
427
923
|
if (!(tab instanceof HTMLElement)) {
|
|
428
924
|
return;
|
|
429
925
|
}
|
|
926
|
+
!this.#settings.manual && tab.click();
|
|
430
927
|
this.#isAvoidedTab(tab) && tab.blur();
|
|
431
928
|
};
|
|
432
929
|
#onTabKeyDown = (event) => {
|
|
@@ -434,63 +931,19 @@ var Tabs = class _Tabs {
|
|
|
434
931
|
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
435
932
|
return;
|
|
436
933
|
}
|
|
437
|
-
|
|
438
|
-
if (!(currentTab instanceof HTMLElement)) {
|
|
934
|
+
if (!["Enter", " "].includes(key)) {
|
|
439
935
|
return;
|
|
440
936
|
}
|
|
441
|
-
const
|
|
442
|
-
if (!list) {
|
|
443
|
-
return;
|
|
444
|
-
}
|
|
445
|
-
const isBoth = list.ariaOrientation === "undefined";
|
|
446
|
-
const isHorizontal = list.ariaOrientation !== "vertical";
|
|
447
|
-
if (![
|
|
448
|
-
"Enter",
|
|
449
|
-
" ",
|
|
450
|
-
"End",
|
|
451
|
-
"Home",
|
|
452
|
-
...isBoth ? ["ArrowLeft", "ArrowUp"] : [`Arrow${isHorizontal ? "Left" : "Up"}`],
|
|
453
|
-
...isBoth ? ["ArrowRight", "ArrowDown"] : [`Arrow${isHorizontal ? "Right" : "Down"}`]
|
|
454
|
-
].includes(key)) {
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const focusables = [
|
|
458
|
-
...list.querySelectorAll(this.#settings.selector.tab)
|
|
459
|
-
].filter(isFocusable);
|
|
460
|
-
const active = getActiveElement();
|
|
937
|
+
const active = getActiveElement2();
|
|
461
938
|
if (!(active instanceof HTMLElement)) {
|
|
462
939
|
return;
|
|
463
940
|
}
|
|
464
941
|
event.preventDefault();
|
|
465
|
-
const currentIndex = focusables.indexOf(active);
|
|
466
|
-
let newIndex = currentIndex;
|
|
467
942
|
switch (key) {
|
|
468
943
|
case "Enter":
|
|
469
944
|
case " ":
|
|
470
945
|
active.click();
|
|
471
946
|
return;
|
|
472
|
-
case "End":
|
|
473
|
-
newIndex = -1;
|
|
474
|
-
break;
|
|
475
|
-
case "Home":
|
|
476
|
-
newIndex = 0;
|
|
477
|
-
break;
|
|
478
|
-
case "ArrowLeft":
|
|
479
|
-
case "ArrowUp":
|
|
480
|
-
newIndex = currentIndex - 1;
|
|
481
|
-
break;
|
|
482
|
-
case "ArrowRight":
|
|
483
|
-
case "ArrowDown":
|
|
484
|
-
newIndex = (currentIndex + 1) % focusables.length;
|
|
485
|
-
break;
|
|
486
|
-
}
|
|
487
|
-
const newTab = focusables.at(newIndex);
|
|
488
|
-
if (!newTab) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
newTab.focus();
|
|
492
|
-
if (!this.#settings.manual) {
|
|
493
|
-
newTab.click();
|
|
494
947
|
}
|
|
495
948
|
};
|
|
496
949
|
#onPanelBeforeMatch = (event) => {
|
|
@@ -621,7 +1074,7 @@ var TabsIndicator = class {
|
|
|
621
1074
|
function createBinding(tabs, panel) {
|
|
622
1075
|
return { tabs, panel, animation: null };
|
|
623
1076
|
}
|
|
624
|
-
function
|
|
1077
|
+
function getActiveElement2() {
|
|
625
1078
|
let current = document.activeElement;
|
|
626
1079
|
while (current?.shadowRoot?.activeElement) {
|
|
627
1080
|
current = current.shadowRoot.activeElement;
|
|
@@ -635,14 +1088,14 @@ function hasFocusable(container) {
|
|
|
635
1088
|
)
|
|
636
1089
|
].filter((element) => element.checkVisibility()).length;
|
|
637
1090
|
}
|
|
638
|
-
function
|
|
1091
|
+
function isFocusable2(element) {
|
|
639
1092
|
return !element.hasAttribute("disabled");
|
|
640
1093
|
}
|
|
641
1094
|
/**
|
|
642
1095
|
* Tabs
|
|
643
1096
|
* WAI-ARIA compliant tabs pattern implementation in TypeScript.
|
|
644
1097
|
*
|
|
645
|
-
* @version 1.
|
|
1098
|
+
* @version 1.4.0
|
|
646
1099
|
* @author Yusuke Kamiyamane
|
|
647
1100
|
* @license MIT
|
|
648
1101
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -660,6 +1113,45 @@ function isFocusable(element) {
|
|
|
660
1113
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
661
1114
|
* @see {@link https://github.com/y14e/attributes-utils}
|
|
662
1115
|
*)
|
|
1116
|
+
|
|
1117
|
+
@y14e/roving-tabindex/dist/index.js:
|
|
1118
|
+
(**
|
|
1119
|
+
* Roving Tabindex
|
|
1120
|
+
* Lightweight roving tabindex utility with fully focus management.
|
|
1121
|
+
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
1122
|
+
*
|
|
1123
|
+
* @version 1.3.1
|
|
1124
|
+
* @author Yusuke Kamiyamane
|
|
1125
|
+
* @license MIT
|
|
1126
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
1127
|
+
* @see {@link https://github.com/y14e/roving-tabindex}
|
|
1128
|
+
*)
|
|
1129
|
+
(*! Bundled license information:
|
|
1130
|
+
|
|
1131
|
+
@y14e/attributes-utils/dist/index.js:
|
|
1132
|
+
(**
|
|
1133
|
+
* Attributes Utils
|
|
1134
|
+
*
|
|
1135
|
+
* @version 1.0.5
|
|
1136
|
+
* @author Yusuke Kamiyamane
|
|
1137
|
+
* @license MIT
|
|
1138
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
1139
|
+
* @see {@link https://github.com/y14e/attributes-utils}
|
|
1140
|
+
*)
|
|
1141
|
+
|
|
1142
|
+
power-focusable/dist/index.js:
|
|
1143
|
+
(**
|
|
1144
|
+
* Power Focusable
|
|
1145
|
+
* High-precision focus management utility with full composed tree support.
|
|
1146
|
+
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
1147
|
+
*
|
|
1148
|
+
* @version 4.2.0
|
|
1149
|
+
* @author Yusuke Kamiyamane
|
|
1150
|
+
* @license MIT
|
|
1151
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
1152
|
+
* @see {@link https://github.com/y14e/power-focusable}
|
|
1153
|
+
*)
|
|
1154
|
+
*)
|
|
663
1155
|
*/
|
|
664
1156
|
|
|
665
1157
|
module.exports = Tabs;
|