power-focusable 4.3.11 → 4.3.12
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 +4 -4
- package/dist/index.cjs +68 -51
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +68 -51
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,18 +24,18 @@ 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.12';
|
|
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.12/+esm';
|
|
30
30
|
// or
|
|
31
|
-
import { ... } 'https://esm.unpkg.com/power-focusable@4.3.
|
|
31
|
+
import { ... } 'https://esm.unpkg.com/power-focusable@4.3.12';
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
## 🪄 Options
|
|
35
35
|
|
|
36
36
|
```ts
|
|
37
37
|
interface PowerFocusableOptions {
|
|
38
|
-
anchor: Element | null; // default: DocumentOrShadowRoot
|
|
38
|
+
anchor: Element | null; // default: DocumentOrShadowRoot.activeElement
|
|
39
39
|
composed: boolean; // default: false
|
|
40
40
|
filter: PowerFocusableFunction; // default: undefined
|
|
41
41
|
include: PowerFocusableFunction; // default: undefined
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
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
|
+
var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX = FOCUSABLE_SELECTOR.replace(
|
|
6
|
+
/(,\s*)?\[tabindex="-1"\]/g,
|
|
7
|
+
""
|
|
8
|
+
);
|
|
5
9
|
function createFocusTrap(container = document.body) {
|
|
6
10
|
if (!(container instanceof Element)) {
|
|
7
11
|
console.warn("Invalid container element");
|
|
@@ -95,21 +99,27 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
95
99
|
const elements = [];
|
|
96
100
|
if (composed || include) {
|
|
97
101
|
let traverse2 = function(node) {
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
if (isFocusable(node, {
|
|
103
|
+
skipNegativeTabIndexCheck,
|
|
104
|
+
skipVisibilityCheck
|
|
105
|
+
}) || include?.(node)) {
|
|
102
106
|
elements[elements.length] = node;
|
|
103
107
|
}
|
|
104
|
-
const
|
|
105
|
-
for (let i = 0, l =
|
|
106
|
-
const child =
|
|
108
|
+
const children2 = composed ? getComposedChildren(node) : getChildren(node);
|
|
109
|
+
for (let i = 0, l = children2.length; i < l; i++) {
|
|
110
|
+
const child = children2[i];
|
|
107
111
|
child && traverse2(child);
|
|
108
112
|
}
|
|
109
113
|
};
|
|
110
|
-
|
|
114
|
+
const children = composed ? getComposedChildren(container) : getChildren(container);
|
|
115
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
116
|
+
const child = children[i];
|
|
117
|
+
child && traverse2(child);
|
|
118
|
+
}
|
|
111
119
|
} else {
|
|
112
|
-
const candidates = container.querySelectorAll(
|
|
120
|
+
const candidates = container.querySelectorAll(
|
|
121
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
|
|
122
|
+
);
|
|
113
123
|
for (let i = 0, l = candidates.length; i < l; i++) {
|
|
114
124
|
const candidate = candidates[i];
|
|
115
125
|
if (candidate && isFocusable(candidate, {
|
|
@@ -120,8 +130,8 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
120
130
|
}
|
|
121
131
|
}
|
|
122
132
|
}
|
|
123
|
-
const
|
|
124
|
-
return
|
|
133
|
+
const filtered = filter ? elements.filter(filter) : elements;
|
|
134
|
+
return normalizeRadioGroup(sortByTabIndex(filtered));
|
|
125
135
|
}
|
|
126
136
|
function getNextFocusable(container = document.body, options = {}) {
|
|
127
137
|
return getRelativeFocusable(container, 1, options);
|
|
@@ -176,7 +186,7 @@ function isFocusable(element, options = {}) {
|
|
|
176
186
|
return false;
|
|
177
187
|
}
|
|
178
188
|
if (!element.matches(
|
|
179
|
-
skipNegativeTabIndexCheck ?
|
|
189
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
|
|
180
190
|
)) {
|
|
181
191
|
return false;
|
|
182
192
|
}
|
|
@@ -300,7 +310,7 @@ function isDisabledDeep(element) {
|
|
|
300
310
|
return false;
|
|
301
311
|
}
|
|
302
312
|
function normalizeRadioGroup(elements) {
|
|
303
|
-
let
|
|
313
|
+
let rootMap = null;
|
|
304
314
|
for (let i = 0, l = elements.length; i < l; i++) {
|
|
305
315
|
const element = elements[i];
|
|
306
316
|
if (!(element instanceof HTMLInputElement)) {
|
|
@@ -309,25 +319,45 @@ function normalizeRadioGroup(elements) {
|
|
|
309
319
|
if (!isUngroupedRadio(element)) {
|
|
310
320
|
continue;
|
|
311
321
|
}
|
|
312
|
-
if (!
|
|
313
|
-
|
|
322
|
+
if (!rootMap) {
|
|
323
|
+
rootMap = /* @__PURE__ */ new Map();
|
|
324
|
+
}
|
|
325
|
+
const root = element.getRootNode();
|
|
326
|
+
let formMap = rootMap.get(root);
|
|
327
|
+
if (!formMap) {
|
|
328
|
+
formMap = /* @__PURE__ */ new Map();
|
|
329
|
+
rootMap.set(root, formMap);
|
|
330
|
+
}
|
|
331
|
+
let nameMap = formMap.get(element.form);
|
|
332
|
+
if (!nameMap) {
|
|
333
|
+
nameMap = /* @__PURE__ */ new Map();
|
|
334
|
+
formMap.set(element.form, nameMap);
|
|
314
335
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
336
|
+
let group = nameMap.get(element.name);
|
|
337
|
+
if (!group) {
|
|
338
|
+
group = [];
|
|
339
|
+
nameMap.set(element.name, group);
|
|
319
340
|
}
|
|
341
|
+
group[group.length] = element;
|
|
320
342
|
}
|
|
321
|
-
if (!
|
|
343
|
+
if (!rootMap) {
|
|
322
344
|
return elements;
|
|
323
345
|
}
|
|
324
346
|
const placeholder = /* @__PURE__ */ new Set();
|
|
325
|
-
for (const
|
|
326
|
-
|
|
347
|
+
for (const formMap of rootMap.values()) {
|
|
348
|
+
for (const nameMap of formMap.values()) {
|
|
349
|
+
for (const group of nameMap.values()) {
|
|
350
|
+
const radio = group.find((element) => element.checked) ?? group[0];
|
|
351
|
+
radio && placeholder.add(radio);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
327
354
|
}
|
|
328
|
-
return elements.filter(
|
|
329
|
-
|
|
330
|
-
|
|
355
|
+
return elements.filter((element) => {
|
|
356
|
+
if (!(element instanceof HTMLInputElement)) {
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
return !isUngroupedRadio(element) || placeholder.has(element);
|
|
360
|
+
});
|
|
331
361
|
}
|
|
332
362
|
function sortByTabIndex(elements) {
|
|
333
363
|
const ordered = [];
|
|
@@ -388,19 +418,19 @@ function getComposedParent(node) {
|
|
|
388
418
|
}
|
|
389
419
|
}
|
|
390
420
|
function getComposedSiblings(node) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
421
|
+
const parent = getComposedParent(node);
|
|
422
|
+
if (!parent) {
|
|
423
|
+
return [];
|
|
424
|
+
}
|
|
425
|
+
const siblings = parent instanceof HTMLSlotElement ? parent.assignedElements({ flatten: true }) : getComposedChildren(parent);
|
|
426
|
+
const filtered = [];
|
|
427
|
+
for (let i = 0, l = siblings.length; i < l; i++) {
|
|
428
|
+
const sibling = siblings[i];
|
|
429
|
+
if (sibling && sibling !== node) {
|
|
430
|
+
filtered[filtered.length] = sibling;
|
|
399
431
|
}
|
|
400
|
-
return filtered;
|
|
401
|
-
} else {
|
|
402
|
-
return getComposedParent(node) ? getSiblings(node) : [];
|
|
403
432
|
}
|
|
433
|
+
return filtered;
|
|
404
434
|
}
|
|
405
435
|
var inertRefCounts = /* @__PURE__ */ new WeakMap();
|
|
406
436
|
function applyInert(element) {
|
|
@@ -442,19 +472,6 @@ function getChildren(node) {
|
|
|
442
472
|
}
|
|
443
473
|
return elements;
|
|
444
474
|
}
|
|
445
|
-
function getSiblings(node) {
|
|
446
|
-
const parent = getComposedParent(node);
|
|
447
|
-
if (!parent) {
|
|
448
|
-
return [];
|
|
449
|
-
}
|
|
450
|
-
const elements = [];
|
|
451
|
-
for (let child = parent.firstElementChild; child; child = child.nextElementSibling) {
|
|
452
|
-
if (child !== node) {
|
|
453
|
-
elements[elements.length] = child;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
return elements;
|
|
457
|
-
}
|
|
458
475
|
function getTabIndex(element) {
|
|
459
476
|
return "tabIndex" in element ? Number(element.tabIndex) : 0;
|
|
460
477
|
}
|
|
@@ -469,14 +486,14 @@ function isInert(element) {
|
|
|
469
486
|
return "inert" in element && !!element.inert;
|
|
470
487
|
}
|
|
471
488
|
function isUngroupedRadio(element) {
|
|
472
|
-
return element
|
|
489
|
+
return element.type === "radio" && !!element.name;
|
|
473
490
|
}
|
|
474
491
|
/**
|
|
475
492
|
* Power Focusable
|
|
476
493
|
* High-precision focus management utility with full composed tree support.
|
|
477
494
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
478
495
|
*
|
|
479
|
-
* @version 4.3.
|
|
496
|
+
* @version 4.3.12
|
|
480
497
|
* @author Yusuke Kamiyamane
|
|
481
498
|
* @license MIT
|
|
482
499
|
* @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.12
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
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.12
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
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
|
+
var FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX = FOCUSABLE_SELECTOR.replace(
|
|
4
|
+
/(,\s*)?\[tabindex="-1"\]/g,
|
|
5
|
+
""
|
|
6
|
+
);
|
|
3
7
|
function createFocusTrap(container = document.body) {
|
|
4
8
|
if (!(container instanceof Element)) {
|
|
5
9
|
console.warn("Invalid container element");
|
|
@@ -93,21 +97,27 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
93
97
|
const elements = [];
|
|
94
98
|
if (composed || include) {
|
|
95
99
|
let traverse2 = function(node) {
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
if (isFocusable(node, {
|
|
101
|
+
skipNegativeTabIndexCheck,
|
|
102
|
+
skipVisibilityCheck
|
|
103
|
+
}) || include?.(node)) {
|
|
100
104
|
elements[elements.length] = node;
|
|
101
105
|
}
|
|
102
|
-
const
|
|
103
|
-
for (let i = 0, l =
|
|
104
|
-
const child =
|
|
106
|
+
const children2 = composed ? getComposedChildren(node) : getChildren(node);
|
|
107
|
+
for (let i = 0, l = children2.length; i < l; i++) {
|
|
108
|
+
const child = children2[i];
|
|
105
109
|
child && traverse2(child);
|
|
106
110
|
}
|
|
107
111
|
};
|
|
108
|
-
|
|
112
|
+
const children = composed ? getComposedChildren(container) : getChildren(container);
|
|
113
|
+
for (let i = 0, l = children.length; i < l; i++) {
|
|
114
|
+
const child = children[i];
|
|
115
|
+
child && traverse2(child);
|
|
116
|
+
}
|
|
109
117
|
} else {
|
|
110
|
-
const candidates = container.querySelectorAll(
|
|
118
|
+
const candidates = container.querySelectorAll(
|
|
119
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
|
|
120
|
+
);
|
|
111
121
|
for (let i = 0, l = candidates.length; i < l; i++) {
|
|
112
122
|
const candidate = candidates[i];
|
|
113
123
|
if (candidate && isFocusable(candidate, {
|
|
@@ -118,8 +128,8 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
}
|
|
121
|
-
const
|
|
122
|
-
return
|
|
131
|
+
const filtered = filter ? elements.filter(filter) : elements;
|
|
132
|
+
return normalizeRadioGroup(sortByTabIndex(filtered));
|
|
123
133
|
}
|
|
124
134
|
function getNextFocusable(container = document.body, options = {}) {
|
|
125
135
|
return getRelativeFocusable(container, 1, options);
|
|
@@ -174,7 +184,7 @@ function isFocusable(element, options = {}) {
|
|
|
174
184
|
return false;
|
|
175
185
|
}
|
|
176
186
|
if (!element.matches(
|
|
177
|
-
skipNegativeTabIndexCheck ?
|
|
187
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TAB_INDEX : FOCUSABLE_SELECTOR
|
|
178
188
|
)) {
|
|
179
189
|
return false;
|
|
180
190
|
}
|
|
@@ -298,7 +308,7 @@ function isDisabledDeep(element) {
|
|
|
298
308
|
return false;
|
|
299
309
|
}
|
|
300
310
|
function normalizeRadioGroup(elements) {
|
|
301
|
-
let
|
|
311
|
+
let rootMap = null;
|
|
302
312
|
for (let i = 0, l = elements.length; i < l; i++) {
|
|
303
313
|
const element = elements[i];
|
|
304
314
|
if (!(element instanceof HTMLInputElement)) {
|
|
@@ -307,25 +317,45 @@ function normalizeRadioGroup(elements) {
|
|
|
307
317
|
if (!isUngroupedRadio(element)) {
|
|
308
318
|
continue;
|
|
309
319
|
}
|
|
310
|
-
if (!
|
|
311
|
-
|
|
320
|
+
if (!rootMap) {
|
|
321
|
+
rootMap = /* @__PURE__ */ new Map();
|
|
322
|
+
}
|
|
323
|
+
const root = element.getRootNode();
|
|
324
|
+
let formMap = rootMap.get(root);
|
|
325
|
+
if (!formMap) {
|
|
326
|
+
formMap = /* @__PURE__ */ new Map();
|
|
327
|
+
rootMap.set(root, formMap);
|
|
328
|
+
}
|
|
329
|
+
let nameMap = formMap.get(element.form);
|
|
330
|
+
if (!nameMap) {
|
|
331
|
+
nameMap = /* @__PURE__ */ new Map();
|
|
332
|
+
formMap.set(element.form, nameMap);
|
|
312
333
|
}
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
334
|
+
let group = nameMap.get(element.name);
|
|
335
|
+
if (!group) {
|
|
336
|
+
group = [];
|
|
337
|
+
nameMap.set(element.name, group);
|
|
317
338
|
}
|
|
339
|
+
group[group.length] = element;
|
|
318
340
|
}
|
|
319
|
-
if (!
|
|
341
|
+
if (!rootMap) {
|
|
320
342
|
return elements;
|
|
321
343
|
}
|
|
322
344
|
const placeholder = /* @__PURE__ */ new Set();
|
|
323
|
-
for (const
|
|
324
|
-
|
|
345
|
+
for (const formMap of rootMap.values()) {
|
|
346
|
+
for (const nameMap of formMap.values()) {
|
|
347
|
+
for (const group of nameMap.values()) {
|
|
348
|
+
const radio = group.find((element) => element.checked) ?? group[0];
|
|
349
|
+
radio && placeholder.add(radio);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
325
352
|
}
|
|
326
|
-
return elements.filter(
|
|
327
|
-
|
|
328
|
-
|
|
353
|
+
return elements.filter((element) => {
|
|
354
|
+
if (!(element instanceof HTMLInputElement)) {
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
return !isUngroupedRadio(element) || placeholder.has(element);
|
|
358
|
+
});
|
|
329
359
|
}
|
|
330
360
|
function sortByTabIndex(elements) {
|
|
331
361
|
const ordered = [];
|
|
@@ -386,19 +416,19 @@ function getComposedParent(node) {
|
|
|
386
416
|
}
|
|
387
417
|
}
|
|
388
418
|
function getComposedSiblings(node) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
419
|
+
const parent = getComposedParent(node);
|
|
420
|
+
if (!parent) {
|
|
421
|
+
return [];
|
|
422
|
+
}
|
|
423
|
+
const siblings = parent instanceof HTMLSlotElement ? parent.assignedElements({ flatten: true }) : getComposedChildren(parent);
|
|
424
|
+
const filtered = [];
|
|
425
|
+
for (let i = 0, l = siblings.length; i < l; i++) {
|
|
426
|
+
const sibling = siblings[i];
|
|
427
|
+
if (sibling && sibling !== node) {
|
|
428
|
+
filtered[filtered.length] = sibling;
|
|
397
429
|
}
|
|
398
|
-
return filtered;
|
|
399
|
-
} else {
|
|
400
|
-
return getComposedParent(node) ? getSiblings(node) : [];
|
|
401
430
|
}
|
|
431
|
+
return filtered;
|
|
402
432
|
}
|
|
403
433
|
var inertRefCounts = /* @__PURE__ */ new WeakMap();
|
|
404
434
|
function applyInert(element) {
|
|
@@ -440,19 +470,6 @@ function getChildren(node) {
|
|
|
440
470
|
}
|
|
441
471
|
return elements;
|
|
442
472
|
}
|
|
443
|
-
function getSiblings(node) {
|
|
444
|
-
const parent = getComposedParent(node);
|
|
445
|
-
if (!parent) {
|
|
446
|
-
return [];
|
|
447
|
-
}
|
|
448
|
-
const elements = [];
|
|
449
|
-
for (let child = parent.firstElementChild; child; child = child.nextElementSibling) {
|
|
450
|
-
if (child !== node) {
|
|
451
|
-
elements[elements.length] = child;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
return elements;
|
|
455
|
-
}
|
|
456
473
|
function getTabIndex(element) {
|
|
457
474
|
return "tabIndex" in element ? Number(element.tabIndex) : 0;
|
|
458
475
|
}
|
|
@@ -467,14 +484,14 @@ function isInert(element) {
|
|
|
467
484
|
return "inert" in element && !!element.inert;
|
|
468
485
|
}
|
|
469
486
|
function isUngroupedRadio(element) {
|
|
470
|
-
return element
|
|
487
|
+
return element.type === "radio" && !!element.name;
|
|
471
488
|
}
|
|
472
489
|
/**
|
|
473
490
|
* Power Focusable
|
|
474
491
|
* High-precision focus management utility with full composed tree support.
|
|
475
492
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
476
493
|
*
|
|
477
|
-
* @version 4.3.
|
|
494
|
+
* @version 4.3.12
|
|
478
495
|
* @author Yusuke Kamiyamane
|
|
479
496
|
* @license MIT
|
|
480
497
|
* @copyright Copyright (c) Yusuke Kamiyamane
|