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