power-focusable 4.3.28 → 4.3.30
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/README.md +10 -10
- package/dist/index.cjs +92 -139
- package/dist/index.d.cts +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +92 -139
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,24 +24,24 @@ import {
|
|
|
24
24
|
} from 'power-focusable';
|
|
25
25
|
|
|
26
26
|
// CDNs
|
|
27
|
-
import { ... } 'https://esm.sh/power-focusable@4.3.
|
|
27
|
+
import { ... } 'https://esm.sh/power-focusable@4.3.30';
|
|
28
28
|
// or
|
|
29
|
-
import { ... } 'https://cdn.jsdelivr.net/npm/power-focusable@4.3.
|
|
29
|
+
import { ... } 'https://cdn.jsdelivr.net/npm/power-focusable@4.3.30/+esm';
|
|
30
30
|
// or
|
|
31
|
-
import { ... } 'https://esm.unpkg.com/power-focusable@4.3.
|
|
31
|
+
import { ... } 'https://esm.unpkg.com/power-focusable@4.3.30';
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## 🪄 Options
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
interface PowerFocusableOptions {
|
|
38
|
-
anchor: Element | null;
|
|
39
|
-
composed: boolean;
|
|
40
|
-
filter: PredicateFunction;
|
|
41
|
-
include: PredicateFunction;
|
|
42
|
-
skipNegativeTabIndexCheck: boolean;
|
|
43
|
-
skipVisibilityCheck: boolean;
|
|
44
|
-
wrap: boolean;
|
|
38
|
+
anchor: Element | null; // default: DocumentOrShadowRoot.activeElement
|
|
39
|
+
composed: boolean; // default: false
|
|
40
|
+
filter: PredicateFunction | undefined; // default: undefined
|
|
41
|
+
include: PredicateFunction | undefined; // default: undefined
|
|
42
|
+
skipNegativeTabIndexCheck: boolean; // default: false
|
|
43
|
+
skipVisibilityCheck: boolean; // default: false
|
|
44
|
+
wrap: boolean; // default: false
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
type PredicateFunction = (element: Element) => boolean;
|
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
var FOCUSABLE_SELECTOR =
|
|
4
|
+
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"])';
|
|
5
5
|
var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX = FOCUSABLE_SELECTOR.replace(
|
|
6
6
|
/(,\s*)?\[tabindex="-1"\]/g,
|
|
7
7
|
""
|
|
@@ -64,37 +64,13 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
64
64
|
console.warn("Invalid container element. Fallback: <body> element.");
|
|
65
65
|
container = document.body;
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
composed
|
|
67
|
+
const {
|
|
68
|
+
composed,
|
|
69
69
|
filter,
|
|
70
70
|
include,
|
|
71
|
-
skipNegativeTabIndexCheck
|
|
72
|
-
skipVisibilityCheck
|
|
73
|
-
} = options;
|
|
74
|
-
if (typeof composed !== "boolean") {
|
|
75
|
-
console.warn("Invalid composed option. Fallback: false.");
|
|
76
|
-
composed = false;
|
|
77
|
-
}
|
|
78
|
-
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
79
|
-
console.warn(
|
|
80
|
-
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
81
|
-
);
|
|
82
|
-
filter = void 0;
|
|
83
|
-
}
|
|
84
|
-
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
85
|
-
console.warn(
|
|
86
|
-
"Invalid include function. Fallback: no include function (undefined)."
|
|
87
|
-
);
|
|
88
|
-
include = void 0;
|
|
89
|
-
}
|
|
90
|
-
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
91
|
-
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
92
|
-
skipNegativeTabIndexCheck = false;
|
|
93
|
-
}
|
|
94
|
-
if (typeof skipVisibilityCheck !== "boolean") {
|
|
95
|
-
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
96
|
-
skipVisibilityCheck = false;
|
|
97
|
-
}
|
|
71
|
+
skipNegativeTabIndexCheck,
|
|
72
|
+
skipVisibilityCheck
|
|
73
|
+
} = resolveOptions(options);
|
|
98
74
|
const candidates = [];
|
|
99
75
|
if (composed || include) {
|
|
100
76
|
let traverse2 = function(node) {
|
|
@@ -104,29 +80,19 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
104
80
|
}) || include?.(node)) {
|
|
105
81
|
candidates[candidates.length] = node;
|
|
106
82
|
}
|
|
107
|
-
|
|
108
|
-
for (let i = 0, l = children2.length; i < l; i++) {
|
|
109
|
-
const child = children2[i];
|
|
110
|
-
child && traverse2(child);
|
|
111
|
-
}
|
|
83
|
+
(composed ? getComposedChildren(node) : getChildren(node)).map(traverse2);
|
|
112
84
|
};
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
child && traverse2(child);
|
|
117
|
-
}
|
|
85
|
+
(composed ? getComposedChildren(container) : getChildren(container)).map(
|
|
86
|
+
traverse2
|
|
87
|
+
);
|
|
118
88
|
} else {
|
|
119
|
-
const
|
|
89
|
+
for (const match of container.querySelectorAll(
|
|
120
90
|
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
const matched = matches[i];
|
|
124
|
-
if (matched && isFocusable(matched, {
|
|
91
|
+
)) {
|
|
92
|
+
match && isFocusable(match, {
|
|
125
93
|
skipNegativeTabIndexCheck,
|
|
126
94
|
skipVisibilityCheck
|
|
127
|
-
}))
|
|
128
|
-
candidates[candidates.length] = matched;
|
|
129
|
-
}
|
|
95
|
+
}) && candidates.push(match);
|
|
130
96
|
}
|
|
131
97
|
}
|
|
132
98
|
return normalizeRadioGroup(
|
|
@@ -151,35 +117,20 @@ function inertOutside(element) {
|
|
|
151
117
|
function traverse(node, fn) {
|
|
152
118
|
const parent = getComposedParent(node);
|
|
153
119
|
if (parent) {
|
|
154
|
-
|
|
155
|
-
fn(sibling);
|
|
156
|
-
}
|
|
120
|
+
getComposedSiblings(node).map(fn);
|
|
157
121
|
traverse(parent, fn);
|
|
158
122
|
}
|
|
159
123
|
}
|
|
160
124
|
const elements = [];
|
|
161
125
|
traverse(element, (node) => node && applyInert(node) && elements.push(node));
|
|
162
|
-
return () =>
|
|
163
|
-
for (let i = 0, l = elements.length; i < l; i++) {
|
|
164
|
-
const element2 = elements[i];
|
|
165
|
-
element2 && restoreInert(element2);
|
|
166
|
-
}
|
|
167
|
-
};
|
|
126
|
+
return () => elements.map(restoreInert);
|
|
168
127
|
}
|
|
169
128
|
function isFocusable(element, options = {}) {
|
|
170
129
|
if (!(element instanceof Element)) {
|
|
171
130
|
console.warn("Invalid element");
|
|
172
131
|
return false;
|
|
173
132
|
}
|
|
174
|
-
|
|
175
|
-
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
176
|
-
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
177
|
-
skipNegativeTabIndexCheck = false;
|
|
178
|
-
}
|
|
179
|
-
if (typeof skipVisibilityCheck !== "boolean") {
|
|
180
|
-
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
181
|
-
skipVisibilityCheck = false;
|
|
182
|
-
}
|
|
133
|
+
const { skipNegativeTabIndexCheck, skipVisibilityCheck } = resolveOptions(options);
|
|
183
134
|
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
184
135
|
return false;
|
|
185
136
|
}
|
|
@@ -208,65 +159,30 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
208
159
|
console.warn("Invalid container element. Fallback: <body> element.");
|
|
209
160
|
container = document.body;
|
|
210
161
|
}
|
|
211
|
-
|
|
212
|
-
anchor
|
|
213
|
-
composed
|
|
162
|
+
const {
|
|
163
|
+
anchor,
|
|
164
|
+
composed,
|
|
214
165
|
filter,
|
|
215
166
|
include,
|
|
216
|
-
skipNegativeTabIndexCheck
|
|
217
|
-
skipVisibilityCheck
|
|
218
|
-
wrap
|
|
219
|
-
} = options;
|
|
167
|
+
skipNegativeTabIndexCheck,
|
|
168
|
+
skipVisibilityCheck,
|
|
169
|
+
wrap
|
|
170
|
+
} = resolveOptions(options);
|
|
220
171
|
if (!(anchor instanceof Element)) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
console.warn("Invalid anchor element. Fallback: active element.");
|
|
224
|
-
anchor = active;
|
|
225
|
-
} else {
|
|
226
|
-
console.warn("Invalid anchor element");
|
|
227
|
-
return null;
|
|
228
|
-
}
|
|
172
|
+
console.warn("Invalid anchor element");
|
|
173
|
+
return null;
|
|
229
174
|
}
|
|
230
175
|
if (!containsComposed(container, anchor)) {
|
|
231
176
|
console.warn("Anchor (active) element not within container");
|
|
232
177
|
return null;
|
|
233
178
|
}
|
|
234
|
-
|
|
235
|
-
console.warn("Invalid composed option. Fallback: false.");
|
|
236
|
-
composed = false;
|
|
237
|
-
}
|
|
238
|
-
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
239
|
-
console.warn(
|
|
240
|
-
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
241
|
-
);
|
|
242
|
-
filter = void 0;
|
|
243
|
-
}
|
|
244
|
-
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
245
|
-
console.warn(
|
|
246
|
-
"Invalid include function. Fallback: no include function (undefined)."
|
|
247
|
-
);
|
|
248
|
-
include = void 0;
|
|
249
|
-
}
|
|
250
|
-
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
251
|
-
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
252
|
-
skipNegativeTabIndexCheck = false;
|
|
253
|
-
}
|
|
254
|
-
if (typeof skipVisibilityCheck !== "boolean") {
|
|
255
|
-
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
256
|
-
skipVisibilityCheck = false;
|
|
257
|
-
}
|
|
258
|
-
if (typeof wrap !== "boolean") {
|
|
259
|
-
console.warn("Invalid wrap option. Fallback: false.");
|
|
260
|
-
wrap = false;
|
|
261
|
-
}
|
|
262
|
-
const settings = {
|
|
179
|
+
const focusables = getFocusables(container, {
|
|
263
180
|
composed,
|
|
181
|
+
filter,
|
|
182
|
+
include,
|
|
264
183
|
skipNegativeTabIndexCheck,
|
|
265
184
|
skipVisibilityCheck
|
|
266
|
-
};
|
|
267
|
-
filter && Object.assign(settings, { filter });
|
|
268
|
-
include && Object.assign(settings, { include });
|
|
269
|
-
const focusables = getFocusables(container, settings);
|
|
185
|
+
});
|
|
270
186
|
const { length } = focusables;
|
|
271
187
|
if (!length) {
|
|
272
188
|
return null;
|
|
@@ -312,8 +228,7 @@ function isDisabledDeep(element) {
|
|
|
312
228
|
}
|
|
313
229
|
function normalizeRadioGroup(elements) {
|
|
314
230
|
let map = null;
|
|
315
|
-
for (
|
|
316
|
-
const element = elements[i];
|
|
231
|
+
for (const element of elements) {
|
|
317
232
|
if (!(element instanceof HTMLInputElement)) {
|
|
318
233
|
continue;
|
|
319
234
|
}
|
|
@@ -363,23 +278,10 @@ function normalizeRadioGroup(elements) {
|
|
|
363
278
|
function sortByTabIndex(elements) {
|
|
364
279
|
const ordered = [];
|
|
365
280
|
const natural = [];
|
|
366
|
-
for (
|
|
367
|
-
|
|
368
|
-
if (element) {
|
|
369
|
-
const target = getTabIndex(element) > 0 ? ordered : natural;
|
|
370
|
-
target[target.length] = element;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
|
|
374
|
-
let count = 0;
|
|
375
|
-
const sorted = new Array(ordered.length + natural.length);
|
|
376
|
-
for (let i = 0, l = ordered.length; i < l; i++) {
|
|
377
|
-
sorted[count++] = ordered[i];
|
|
281
|
+
for (const element of elements) {
|
|
282
|
+
(getTabIndex(element) > 0 ? ordered : natural).push(element);
|
|
378
283
|
}
|
|
379
|
-
|
|
380
|
-
sorted[count++] = natural[i];
|
|
381
|
-
}
|
|
382
|
-
return sorted;
|
|
284
|
+
return ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b)).concat(natural);
|
|
383
285
|
}
|
|
384
286
|
function containsComposed(container, element) {
|
|
385
287
|
let current = element;
|
|
@@ -423,13 +325,9 @@ function getComposedSiblings(node) {
|
|
|
423
325
|
if (!parent) {
|
|
424
326
|
return [];
|
|
425
327
|
}
|
|
426
|
-
const siblings = parent instanceof HTMLSlotElement ? parent.assignedElements({ flatten: true }) : getComposedChildren(parent);
|
|
427
328
|
const filtered = [];
|
|
428
|
-
for (
|
|
429
|
-
|
|
430
|
-
if (sibling && sibling !== node) {
|
|
431
|
-
filtered[filtered.length] = sibling;
|
|
432
|
-
}
|
|
329
|
+
for (const sibling of (parent instanceof HTMLSlotElement ? parent.assignedElements({ flatten: true }) : getComposedChildren(parent)).filter((sibling2) => sibling2 !== node)) {
|
|
330
|
+
filtered.push(sibling);
|
|
433
331
|
}
|
|
434
332
|
return filtered;
|
|
435
333
|
}
|
|
@@ -489,12 +387,67 @@ function isInert(element) {
|
|
|
489
387
|
function isUngroupedRadio(element) {
|
|
490
388
|
return element.type === "radio" && !!element.name;
|
|
491
389
|
}
|
|
390
|
+
function resolveOptions(options) {
|
|
391
|
+
let {
|
|
392
|
+
anchor = getActiveElement(),
|
|
393
|
+
composed = false,
|
|
394
|
+
filter,
|
|
395
|
+
include,
|
|
396
|
+
skipNegativeTabIndexCheck = false,
|
|
397
|
+
skipVisibilityCheck = false,
|
|
398
|
+
wrap = false
|
|
399
|
+
} = options;
|
|
400
|
+
if (!(anchor instanceof Element)) {
|
|
401
|
+
const active = getActiveElement();
|
|
402
|
+
if (active instanceof Element) {
|
|
403
|
+
console.warn("Invalid anchor element. Fallback: active element.");
|
|
404
|
+
anchor = active;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (typeof composed !== "boolean") {
|
|
408
|
+
console.warn("Invalid composed option. Fallback: false.");
|
|
409
|
+
composed = false;
|
|
410
|
+
}
|
|
411
|
+
if (filter !== void 0 && typeof filter !== "function") {
|
|
412
|
+
console.warn(
|
|
413
|
+
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
414
|
+
);
|
|
415
|
+
filter = void 0;
|
|
416
|
+
}
|
|
417
|
+
if (include !== void 0 && typeof include !== "function") {
|
|
418
|
+
console.warn(
|
|
419
|
+
"Invalid include function. Fallback: no include function (undefined)."
|
|
420
|
+
);
|
|
421
|
+
include = void 0;
|
|
422
|
+
}
|
|
423
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
424
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
425
|
+
skipNegativeTabIndexCheck = false;
|
|
426
|
+
}
|
|
427
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
428
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
429
|
+
skipVisibilityCheck = false;
|
|
430
|
+
}
|
|
431
|
+
if (typeof wrap !== "boolean") {
|
|
432
|
+
console.warn("Invalid wrap option. Fallback: false.");
|
|
433
|
+
wrap = false;
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
anchor,
|
|
437
|
+
composed,
|
|
438
|
+
filter,
|
|
439
|
+
include,
|
|
440
|
+
skipNegativeTabIndexCheck,
|
|
441
|
+
skipVisibilityCheck,
|
|
442
|
+
wrap
|
|
443
|
+
};
|
|
444
|
+
}
|
|
492
445
|
/**
|
|
493
446
|
* Power Focusable
|
|
494
447
|
* High-precision focus management utility with full composed tree support.
|
|
495
448
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
496
449
|
*
|
|
497
|
-
* @version 4.3.
|
|
450
|
+
* @version 4.3.30
|
|
498
451
|
* @author Yusuke Kamiyamane
|
|
499
452
|
* @license MIT
|
|
500
453
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.d.cts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* High-precision focus management utility with full composed tree support.
|
|
4
4
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
5
5
|
*
|
|
6
|
-
* @version 4.3.
|
|
6
|
+
* @version 4.3.30
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
interface PowerFocusableOptions {
|
|
13
13
|
anchor: Element | null;
|
|
14
14
|
composed: boolean;
|
|
15
|
-
filter: PredicateFunction;
|
|
16
|
-
include: PredicateFunction;
|
|
15
|
+
filter: PredicateFunction | undefined;
|
|
16
|
+
include: PredicateFunction | undefined;
|
|
17
17
|
skipNegativeTabIndexCheck: boolean;
|
|
18
18
|
skipVisibilityCheck: boolean;
|
|
19
19
|
wrap: boolean;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* High-precision focus management utility with full composed tree support.
|
|
4
4
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
5
5
|
*
|
|
6
|
-
* @version 4.3.
|
|
6
|
+
* @version 4.3.30
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
interface PowerFocusableOptions {
|
|
13
13
|
anchor: Element | null;
|
|
14
14
|
composed: boolean;
|
|
15
|
-
filter: PredicateFunction;
|
|
16
|
-
include: PredicateFunction;
|
|
15
|
+
filter: PredicateFunction | undefined;
|
|
16
|
+
include: PredicateFunction | undefined;
|
|
17
17
|
skipNegativeTabIndexCheck: boolean;
|
|
18
18
|
skipVisibilityCheck: boolean;
|
|
19
19
|
wrap: boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
var FOCUSABLE_SELECTOR =
|
|
2
|
+
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"])';
|
|
3
3
|
var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX = FOCUSABLE_SELECTOR.replace(
|
|
4
4
|
/(,\s*)?\[tabindex="-1"\]/g,
|
|
5
5
|
""
|
|
@@ -62,37 +62,13 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
62
62
|
console.warn("Invalid container element. Fallback: <body> element.");
|
|
63
63
|
container = document.body;
|
|
64
64
|
}
|
|
65
|
-
|
|
66
|
-
composed
|
|
65
|
+
const {
|
|
66
|
+
composed,
|
|
67
67
|
filter,
|
|
68
68
|
include,
|
|
69
|
-
skipNegativeTabIndexCheck
|
|
70
|
-
skipVisibilityCheck
|
|
71
|
-
} = options;
|
|
72
|
-
if (typeof composed !== "boolean") {
|
|
73
|
-
console.warn("Invalid composed option. Fallback: false.");
|
|
74
|
-
composed = false;
|
|
75
|
-
}
|
|
76
|
-
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
77
|
-
console.warn(
|
|
78
|
-
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
79
|
-
);
|
|
80
|
-
filter = void 0;
|
|
81
|
-
}
|
|
82
|
-
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
83
|
-
console.warn(
|
|
84
|
-
"Invalid include function. Fallback: no include function (undefined)."
|
|
85
|
-
);
|
|
86
|
-
include = void 0;
|
|
87
|
-
}
|
|
88
|
-
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
89
|
-
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
90
|
-
skipNegativeTabIndexCheck = false;
|
|
91
|
-
}
|
|
92
|
-
if (typeof skipVisibilityCheck !== "boolean") {
|
|
93
|
-
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
94
|
-
skipVisibilityCheck = false;
|
|
95
|
-
}
|
|
69
|
+
skipNegativeTabIndexCheck,
|
|
70
|
+
skipVisibilityCheck
|
|
71
|
+
} = resolveOptions(options);
|
|
96
72
|
const candidates = [];
|
|
97
73
|
if (composed || include) {
|
|
98
74
|
let traverse2 = function(node) {
|
|
@@ -102,29 +78,19 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
102
78
|
}) || include?.(node)) {
|
|
103
79
|
candidates[candidates.length] = node;
|
|
104
80
|
}
|
|
105
|
-
|
|
106
|
-
for (let i = 0, l = children2.length; i < l; i++) {
|
|
107
|
-
const child = children2[i];
|
|
108
|
-
child && traverse2(child);
|
|
109
|
-
}
|
|
81
|
+
(composed ? getComposedChildren(node) : getChildren(node)).map(traverse2);
|
|
110
82
|
};
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
child && traverse2(child);
|
|
115
|
-
}
|
|
83
|
+
(composed ? getComposedChildren(container) : getChildren(container)).map(
|
|
84
|
+
traverse2
|
|
85
|
+
);
|
|
116
86
|
} else {
|
|
117
|
-
const
|
|
87
|
+
for (const match of container.querySelectorAll(
|
|
118
88
|
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
const matched = matches[i];
|
|
122
|
-
if (matched && isFocusable(matched, {
|
|
89
|
+
)) {
|
|
90
|
+
match && isFocusable(match, {
|
|
123
91
|
skipNegativeTabIndexCheck,
|
|
124
92
|
skipVisibilityCheck
|
|
125
|
-
}))
|
|
126
|
-
candidates[candidates.length] = matched;
|
|
127
|
-
}
|
|
93
|
+
}) && candidates.push(match);
|
|
128
94
|
}
|
|
129
95
|
}
|
|
130
96
|
return normalizeRadioGroup(
|
|
@@ -149,35 +115,20 @@ function inertOutside(element) {
|
|
|
149
115
|
function traverse(node, fn) {
|
|
150
116
|
const parent = getComposedParent(node);
|
|
151
117
|
if (parent) {
|
|
152
|
-
|
|
153
|
-
fn(sibling);
|
|
154
|
-
}
|
|
118
|
+
getComposedSiblings(node).map(fn);
|
|
155
119
|
traverse(parent, fn);
|
|
156
120
|
}
|
|
157
121
|
}
|
|
158
122
|
const elements = [];
|
|
159
123
|
traverse(element, (node) => node && applyInert(node) && elements.push(node));
|
|
160
|
-
return () =>
|
|
161
|
-
for (let i = 0, l = elements.length; i < l; i++) {
|
|
162
|
-
const element2 = elements[i];
|
|
163
|
-
element2 && restoreInert(element2);
|
|
164
|
-
}
|
|
165
|
-
};
|
|
124
|
+
return () => elements.map(restoreInert);
|
|
166
125
|
}
|
|
167
126
|
function isFocusable(element, options = {}) {
|
|
168
127
|
if (!(element instanceof Element)) {
|
|
169
128
|
console.warn("Invalid element");
|
|
170
129
|
return false;
|
|
171
130
|
}
|
|
172
|
-
|
|
173
|
-
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
174
|
-
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
175
|
-
skipNegativeTabIndexCheck = false;
|
|
176
|
-
}
|
|
177
|
-
if (typeof skipVisibilityCheck !== "boolean") {
|
|
178
|
-
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
179
|
-
skipVisibilityCheck = false;
|
|
180
|
-
}
|
|
131
|
+
const { skipNegativeTabIndexCheck, skipVisibilityCheck } = resolveOptions(options);
|
|
181
132
|
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
182
133
|
return false;
|
|
183
134
|
}
|
|
@@ -206,65 +157,30 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
206
157
|
console.warn("Invalid container element. Fallback: <body> element.");
|
|
207
158
|
container = document.body;
|
|
208
159
|
}
|
|
209
|
-
|
|
210
|
-
anchor
|
|
211
|
-
composed
|
|
160
|
+
const {
|
|
161
|
+
anchor,
|
|
162
|
+
composed,
|
|
212
163
|
filter,
|
|
213
164
|
include,
|
|
214
|
-
skipNegativeTabIndexCheck
|
|
215
|
-
skipVisibilityCheck
|
|
216
|
-
wrap
|
|
217
|
-
} = options;
|
|
165
|
+
skipNegativeTabIndexCheck,
|
|
166
|
+
skipVisibilityCheck,
|
|
167
|
+
wrap
|
|
168
|
+
} = resolveOptions(options);
|
|
218
169
|
if (!(anchor instanceof Element)) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
console.warn("Invalid anchor element. Fallback: active element.");
|
|
222
|
-
anchor = active;
|
|
223
|
-
} else {
|
|
224
|
-
console.warn("Invalid anchor element");
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
170
|
+
console.warn("Invalid anchor element");
|
|
171
|
+
return null;
|
|
227
172
|
}
|
|
228
173
|
if (!containsComposed(container, anchor)) {
|
|
229
174
|
console.warn("Anchor (active) element not within container");
|
|
230
175
|
return null;
|
|
231
176
|
}
|
|
232
|
-
|
|
233
|
-
console.warn("Invalid composed option. Fallback: false.");
|
|
234
|
-
composed = false;
|
|
235
|
-
}
|
|
236
|
-
if (typeof filter !== "undefined" && typeof filter !== "function") {
|
|
237
|
-
console.warn(
|
|
238
|
-
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
239
|
-
);
|
|
240
|
-
filter = void 0;
|
|
241
|
-
}
|
|
242
|
-
if (typeof include !== "undefined" && typeof include !== "function") {
|
|
243
|
-
console.warn(
|
|
244
|
-
"Invalid include function. Fallback: no include function (undefined)."
|
|
245
|
-
);
|
|
246
|
-
include = void 0;
|
|
247
|
-
}
|
|
248
|
-
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
249
|
-
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
250
|
-
skipNegativeTabIndexCheck = false;
|
|
251
|
-
}
|
|
252
|
-
if (typeof skipVisibilityCheck !== "boolean") {
|
|
253
|
-
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
254
|
-
skipVisibilityCheck = false;
|
|
255
|
-
}
|
|
256
|
-
if (typeof wrap !== "boolean") {
|
|
257
|
-
console.warn("Invalid wrap option. Fallback: false.");
|
|
258
|
-
wrap = false;
|
|
259
|
-
}
|
|
260
|
-
const settings = {
|
|
177
|
+
const focusables = getFocusables(container, {
|
|
261
178
|
composed,
|
|
179
|
+
filter,
|
|
180
|
+
include,
|
|
262
181
|
skipNegativeTabIndexCheck,
|
|
263
182
|
skipVisibilityCheck
|
|
264
|
-
};
|
|
265
|
-
filter && Object.assign(settings, { filter });
|
|
266
|
-
include && Object.assign(settings, { include });
|
|
267
|
-
const focusables = getFocusables(container, settings);
|
|
183
|
+
});
|
|
268
184
|
const { length } = focusables;
|
|
269
185
|
if (!length) {
|
|
270
186
|
return null;
|
|
@@ -310,8 +226,7 @@ function isDisabledDeep(element) {
|
|
|
310
226
|
}
|
|
311
227
|
function normalizeRadioGroup(elements) {
|
|
312
228
|
let map = null;
|
|
313
|
-
for (
|
|
314
|
-
const element = elements[i];
|
|
229
|
+
for (const element of elements) {
|
|
315
230
|
if (!(element instanceof HTMLInputElement)) {
|
|
316
231
|
continue;
|
|
317
232
|
}
|
|
@@ -361,23 +276,10 @@ function normalizeRadioGroup(elements) {
|
|
|
361
276
|
function sortByTabIndex(elements) {
|
|
362
277
|
const ordered = [];
|
|
363
278
|
const natural = [];
|
|
364
|
-
for (
|
|
365
|
-
|
|
366
|
-
if (element) {
|
|
367
|
-
const target = getTabIndex(element) > 0 ? ordered : natural;
|
|
368
|
-
target[target.length] = element;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
|
|
372
|
-
let count = 0;
|
|
373
|
-
const sorted = new Array(ordered.length + natural.length);
|
|
374
|
-
for (let i = 0, l = ordered.length; i < l; i++) {
|
|
375
|
-
sorted[count++] = ordered[i];
|
|
279
|
+
for (const element of elements) {
|
|
280
|
+
(getTabIndex(element) > 0 ? ordered : natural).push(element);
|
|
376
281
|
}
|
|
377
|
-
|
|
378
|
-
sorted[count++] = natural[i];
|
|
379
|
-
}
|
|
380
|
-
return sorted;
|
|
282
|
+
return ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b)).concat(natural);
|
|
381
283
|
}
|
|
382
284
|
function containsComposed(container, element) {
|
|
383
285
|
let current = element;
|
|
@@ -421,13 +323,9 @@ function getComposedSiblings(node) {
|
|
|
421
323
|
if (!parent) {
|
|
422
324
|
return [];
|
|
423
325
|
}
|
|
424
|
-
const siblings = parent instanceof HTMLSlotElement ? parent.assignedElements({ flatten: true }) : getComposedChildren(parent);
|
|
425
326
|
const filtered = [];
|
|
426
|
-
for (
|
|
427
|
-
|
|
428
|
-
if (sibling && sibling !== node) {
|
|
429
|
-
filtered[filtered.length] = sibling;
|
|
430
|
-
}
|
|
327
|
+
for (const sibling of (parent instanceof HTMLSlotElement ? parent.assignedElements({ flatten: true }) : getComposedChildren(parent)).filter((sibling2) => sibling2 !== node)) {
|
|
328
|
+
filtered.push(sibling);
|
|
431
329
|
}
|
|
432
330
|
return filtered;
|
|
433
331
|
}
|
|
@@ -487,12 +385,67 @@ function isInert(element) {
|
|
|
487
385
|
function isUngroupedRadio(element) {
|
|
488
386
|
return element.type === "radio" && !!element.name;
|
|
489
387
|
}
|
|
388
|
+
function resolveOptions(options) {
|
|
389
|
+
let {
|
|
390
|
+
anchor = getActiveElement(),
|
|
391
|
+
composed = false,
|
|
392
|
+
filter,
|
|
393
|
+
include,
|
|
394
|
+
skipNegativeTabIndexCheck = false,
|
|
395
|
+
skipVisibilityCheck = false,
|
|
396
|
+
wrap = false
|
|
397
|
+
} = options;
|
|
398
|
+
if (!(anchor instanceof Element)) {
|
|
399
|
+
const active = getActiveElement();
|
|
400
|
+
if (active instanceof Element) {
|
|
401
|
+
console.warn("Invalid anchor element. Fallback: active element.");
|
|
402
|
+
anchor = active;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (typeof composed !== "boolean") {
|
|
406
|
+
console.warn("Invalid composed option. Fallback: false.");
|
|
407
|
+
composed = false;
|
|
408
|
+
}
|
|
409
|
+
if (filter !== void 0 && typeof filter !== "function") {
|
|
410
|
+
console.warn(
|
|
411
|
+
"Invalid filter function. Fallback: no filter function (undefined)."
|
|
412
|
+
);
|
|
413
|
+
filter = void 0;
|
|
414
|
+
}
|
|
415
|
+
if (include !== void 0 && typeof include !== "function") {
|
|
416
|
+
console.warn(
|
|
417
|
+
"Invalid include function. Fallback: no include function (undefined)."
|
|
418
|
+
);
|
|
419
|
+
include = void 0;
|
|
420
|
+
}
|
|
421
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
422
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
423
|
+
skipNegativeTabIndexCheck = false;
|
|
424
|
+
}
|
|
425
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
426
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
427
|
+
skipVisibilityCheck = false;
|
|
428
|
+
}
|
|
429
|
+
if (typeof wrap !== "boolean") {
|
|
430
|
+
console.warn("Invalid wrap option. Fallback: false.");
|
|
431
|
+
wrap = false;
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
anchor,
|
|
435
|
+
composed,
|
|
436
|
+
filter,
|
|
437
|
+
include,
|
|
438
|
+
skipNegativeTabIndexCheck,
|
|
439
|
+
skipVisibilityCheck,
|
|
440
|
+
wrap
|
|
441
|
+
};
|
|
442
|
+
}
|
|
490
443
|
/**
|
|
491
444
|
* Power Focusable
|
|
492
445
|
* High-precision focus management utility with full composed tree support.
|
|
493
446
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
494
447
|
*
|
|
495
|
-
* @version 4.3.
|
|
448
|
+
* @version 4.3.30
|
|
496
449
|
* @author Yusuke Kamiyamane
|
|
497
450
|
* @license MIT
|
|
498
451
|
* @copyright Copyright (c) Yusuke Kamiyamane
|