power-focusable 4.2.1 → 4.3.1
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 +12 -5
- package/dist/index.cjs +36 -12
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +36 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ interface PowerFocusableOptions {
|
|
|
39
39
|
composed?: boolean; // default: false
|
|
40
40
|
filter?: (element: Element) => boolean;
|
|
41
41
|
include?: (element: Element) => boolean;
|
|
42
|
+
skipNegativeTabIndexCheck?: boolean; // default: false
|
|
42
43
|
skipVisibilityCheck?: boolean; // default: false
|
|
43
44
|
wrap?: boolean; // default: false
|
|
44
45
|
}
|
|
@@ -72,9 +73,15 @@ The function should return `true` to include the element, or `false` to ignore i
|
|
|
72
73
|
|
|
73
74
|
Used by `getFocusables`, `getNextFocusable`, `getPreviousFocusable`, and `hasFocusable`.
|
|
74
75
|
|
|
76
|
+
### `skipNegativeTabIndexCheck`
|
|
77
|
+
|
|
78
|
+
If `true`, skips the negative `tabindex` check when determining focusability.
|
|
79
|
+
|
|
80
|
+
Used by `getFocusables`, `getNextFocusable`, `getPreviousFocusable`, `hasFocusable`, and `isFocusable`.
|
|
81
|
+
|
|
75
82
|
### `skipVisibilityCheck`
|
|
76
83
|
|
|
77
|
-
If `true`, skips `checkVisibility()` when determining focusability.
|
|
84
|
+
If `true`, skips `checkVisibility()` when determining focusability.
|
|
78
85
|
|
|
79
86
|
Used by `getFocusables`, `getNextFocusable`, `getPreviousFocusable`, `hasFocusable`, and `isFocusable`.
|
|
80
87
|
|
|
@@ -114,7 +121,7 @@ getFocusables(container, { composed: true });
|
|
|
114
121
|
getFocusables(container, { filter: (element) => !element.matches('[data-skip-focus]') });
|
|
115
122
|
|
|
116
123
|
// Uses custom include function
|
|
117
|
-
getFocusables(container, { filter: (element) => element.matches('[data-
|
|
124
|
+
getFocusables(container, { filter: (element) => element.matches('[data-force-focusable]') });
|
|
118
125
|
```
|
|
119
126
|
|
|
120
127
|
### `getNextFocusable`
|
|
@@ -137,7 +144,7 @@ getNextFocusable(container, { composed: true });
|
|
|
137
144
|
getNextFocusable(container, { filter: (element) => !element.matches('[data-skip-focus]') });
|
|
138
145
|
|
|
139
146
|
// Uses custom include function
|
|
140
|
-
getNextFocusable(container, { filter: (element) => element.matches('[data-
|
|
147
|
+
getNextFocusable(container, { filter: (element) => element.matches('[data-force-focusable]') });
|
|
141
148
|
|
|
142
149
|
// Wraps around to the first element when reaching the end
|
|
143
150
|
getNextFocusable(container, { wrap: true });
|
|
@@ -163,7 +170,7 @@ getPreviousFocusable(container, { composed: true });
|
|
|
163
170
|
getPreviousFocusable(container, { filter: (element) => !element.matches('[data-skip-focus]') });
|
|
164
171
|
|
|
165
172
|
// Uses custom include function
|
|
166
|
-
getPreviousFocusable(container, { filter: (element) => element.matches('[data-
|
|
173
|
+
getPreviousFocusable(container, { filter: (element) => element.matches('[data-force-focusable]') });
|
|
167
174
|
|
|
168
175
|
// Wraps around to the last element when reaching the end
|
|
169
176
|
getPreviousFocusable(container, { wrap: true });
|
|
@@ -187,7 +194,7 @@ hasFocusable(container, { composed: true });
|
|
|
187
194
|
hasFocusable(container, { filter: (element) => !element.matches('[data-skip-focus]') });
|
|
188
195
|
|
|
189
196
|
// Uses custom include function
|
|
190
|
-
hasFocusable(container, { filter: (element) => element.matches('[data-
|
|
197
|
+
hasFocusable(container, { filter: (element) => element.matches('[data-force-focusable]') });
|
|
191
198
|
```
|
|
192
199
|
|
|
193
200
|
### `inertOutside`
|
package/dist/index.cjs
CHANGED
|
@@ -48,6 +48,7 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
48
48
|
composed = false,
|
|
49
49
|
filter,
|
|
50
50
|
include,
|
|
51
|
+
skipNegativeTabIndexCheck = false,
|
|
51
52
|
skipVisibilityCheck = false
|
|
52
53
|
} = options;
|
|
53
54
|
if (typeof composed !== "boolean") {
|
|
@@ -66,6 +67,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
66
67
|
);
|
|
67
68
|
include = void 0;
|
|
68
69
|
}
|
|
70
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
71
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
72
|
+
skipNegativeTabIndexCheck = false;
|
|
73
|
+
}
|
|
69
74
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
70
75
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
71
76
|
skipVisibilityCheck = false;
|
|
@@ -74,7 +79,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
74
79
|
if (composed || include) {
|
|
75
80
|
let traverse2 = function(node) {
|
|
76
81
|
if (node instanceof Element) {
|
|
77
|
-
if (isFocusable(node, {
|
|
82
|
+
if (isFocusable(node, {
|
|
83
|
+
skipNegativeTabIndexCheck,
|
|
84
|
+
skipVisibilityCheck
|
|
85
|
+
}) || include?.(node)) {
|
|
78
86
|
elements[elements.length] = node;
|
|
79
87
|
}
|
|
80
88
|
}
|
|
@@ -95,7 +103,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
95
103
|
if (!(candidate instanceof Element)) {
|
|
96
104
|
continue;
|
|
97
105
|
}
|
|
98
|
-
if (isFocusable(candidate, {
|
|
106
|
+
if (isFocusable(candidate, {
|
|
107
|
+
skipNegativeTabIndexCheck,
|
|
108
|
+
skipVisibilityCheck
|
|
109
|
+
})) {
|
|
99
110
|
elements[elements.length] = candidate;
|
|
100
111
|
}
|
|
101
112
|
}
|
|
@@ -146,7 +157,11 @@ function isFocusable(element, options = {}) {
|
|
|
146
157
|
console.warn("Invalid element");
|
|
147
158
|
return false;
|
|
148
159
|
}
|
|
149
|
-
let { skipVisibilityCheck = false } = options;
|
|
160
|
+
let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
|
|
161
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
162
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
163
|
+
skipNegativeTabIndexCheck = false;
|
|
164
|
+
}
|
|
150
165
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
151
166
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
152
167
|
skipVisibilityCheck = false;
|
|
@@ -154,10 +169,12 @@ function isFocusable(element, options = {}) {
|
|
|
154
169
|
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
155
170
|
return false;
|
|
156
171
|
}
|
|
157
|
-
if (getTabIndex(element) < 0) {
|
|
172
|
+
if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
|
|
158
173
|
return false;
|
|
159
174
|
}
|
|
160
|
-
if (!element.matches(
|
|
175
|
+
if (!element.matches(
|
|
176
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
|
|
177
|
+
)) {
|
|
161
178
|
return false;
|
|
162
179
|
}
|
|
163
180
|
if (isDisabledDeep(element)) {
|
|
@@ -182,6 +199,7 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
182
199
|
composed = false,
|
|
183
200
|
filter,
|
|
184
201
|
include,
|
|
202
|
+
skipNegativeTabIndexCheck = false,
|
|
185
203
|
skipVisibilityCheck = false,
|
|
186
204
|
wrap = false
|
|
187
205
|
} = options;
|
|
@@ -215,6 +233,10 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
215
233
|
);
|
|
216
234
|
include = void 0;
|
|
217
235
|
}
|
|
236
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
237
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
238
|
+
skipNegativeTabIndexCheck = false;
|
|
239
|
+
}
|
|
218
240
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
219
241
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
220
242
|
skipVisibilityCheck = false;
|
|
@@ -223,12 +245,14 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
223
245
|
console.warn("Invalid wrap option. Fallback: false.");
|
|
224
246
|
wrap = false;
|
|
225
247
|
}
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
filter
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
248
|
+
const settings = { composed, skipNegativeTabIndexCheck, skipVisibilityCheck };
|
|
249
|
+
if (filter !== void 0) {
|
|
250
|
+
Object.assign(settings, { filter });
|
|
251
|
+
}
|
|
252
|
+
if (include !== void 0) {
|
|
253
|
+
Object.assign(settings, { include });
|
|
254
|
+
}
|
|
255
|
+
const focusables = getFocusables(container, settings);
|
|
232
256
|
const { length } = focusables;
|
|
233
257
|
if (!length) {
|
|
234
258
|
return null;
|
|
@@ -459,7 +483,7 @@ function isUngroupedRadio(element) {
|
|
|
459
483
|
* High-precision focus management utility with full composed tree support.
|
|
460
484
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
461
485
|
*
|
|
462
|
-
* @version 4.
|
|
486
|
+
* @version 4.3.1
|
|
463
487
|
* @author Yusuke Kamiyamane
|
|
464
488
|
* @license MIT
|
|
465
489
|
* @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.
|
|
6
|
+
* @version 4.3.1
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
interface PowerFocusableOptions {
|
|
13
13
|
readonly anchor?: Element | null;
|
|
14
14
|
readonly composed?: boolean;
|
|
15
|
-
readonly filter?: (
|
|
16
|
-
readonly include?: (
|
|
15
|
+
readonly filter?: (element: Element) => boolean;
|
|
16
|
+
readonly include?: (element: Element) => boolean;
|
|
17
|
+
readonly skipNegativeTabIndexCheck?: boolean;
|
|
17
18
|
readonly skipVisibilityCheck?: boolean;
|
|
18
19
|
readonly wrap?: boolean;
|
|
19
20
|
}
|
|
@@ -24,6 +25,7 @@ declare function getPreviousFocusable(container?: Element, options?: PowerFocusa
|
|
|
24
25
|
declare function hasFocusable(container?: Element, options?: Omit<PowerFocusableOptions, 'anchor' | 'wrap'>): boolean;
|
|
25
26
|
declare function inertOutside(element: Element): () => void;
|
|
26
27
|
declare function isFocusable(element: Element, options?: {
|
|
28
|
+
skipNegativeTabIndexCheck?: boolean;
|
|
27
29
|
skipVisibilityCheck?: boolean;
|
|
28
30
|
}): boolean;
|
|
29
31
|
|
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.
|
|
6
|
+
* @version 4.3.1
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
interface PowerFocusableOptions {
|
|
13
13
|
readonly anchor?: Element | null;
|
|
14
14
|
readonly composed?: boolean;
|
|
15
|
-
readonly filter?: (
|
|
16
|
-
readonly include?: (
|
|
15
|
+
readonly filter?: (element: Element) => boolean;
|
|
16
|
+
readonly include?: (element: Element) => boolean;
|
|
17
|
+
readonly skipNegativeTabIndexCheck?: boolean;
|
|
17
18
|
readonly skipVisibilityCheck?: boolean;
|
|
18
19
|
readonly wrap?: boolean;
|
|
19
20
|
}
|
|
@@ -24,6 +25,7 @@ declare function getPreviousFocusable(container?: Element, options?: PowerFocusa
|
|
|
24
25
|
declare function hasFocusable(container?: Element, options?: Omit<PowerFocusableOptions, 'anchor' | 'wrap'>): boolean;
|
|
25
26
|
declare function inertOutside(element: Element): () => void;
|
|
26
27
|
declare function isFocusable(element: Element, options?: {
|
|
28
|
+
skipNegativeTabIndexCheck?: boolean;
|
|
27
29
|
skipVisibilityCheck?: boolean;
|
|
28
30
|
}): boolean;
|
|
29
31
|
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
46
46
|
composed = false,
|
|
47
47
|
filter,
|
|
48
48
|
include,
|
|
49
|
+
skipNegativeTabIndexCheck = false,
|
|
49
50
|
skipVisibilityCheck = false
|
|
50
51
|
} = options;
|
|
51
52
|
if (typeof composed !== "boolean") {
|
|
@@ -64,6 +65,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
64
65
|
);
|
|
65
66
|
include = void 0;
|
|
66
67
|
}
|
|
68
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
69
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
70
|
+
skipNegativeTabIndexCheck = false;
|
|
71
|
+
}
|
|
67
72
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
68
73
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
69
74
|
skipVisibilityCheck = false;
|
|
@@ -72,7 +77,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
72
77
|
if (composed || include) {
|
|
73
78
|
let traverse2 = function(node) {
|
|
74
79
|
if (node instanceof Element) {
|
|
75
|
-
if (isFocusable(node, {
|
|
80
|
+
if (isFocusable(node, {
|
|
81
|
+
skipNegativeTabIndexCheck,
|
|
82
|
+
skipVisibilityCheck
|
|
83
|
+
}) || include?.(node)) {
|
|
76
84
|
elements[elements.length] = node;
|
|
77
85
|
}
|
|
78
86
|
}
|
|
@@ -93,7 +101,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
93
101
|
if (!(candidate instanceof Element)) {
|
|
94
102
|
continue;
|
|
95
103
|
}
|
|
96
|
-
if (isFocusable(candidate, {
|
|
104
|
+
if (isFocusable(candidate, {
|
|
105
|
+
skipNegativeTabIndexCheck,
|
|
106
|
+
skipVisibilityCheck
|
|
107
|
+
})) {
|
|
97
108
|
elements[elements.length] = candidate;
|
|
98
109
|
}
|
|
99
110
|
}
|
|
@@ -144,7 +155,11 @@ function isFocusable(element, options = {}) {
|
|
|
144
155
|
console.warn("Invalid element");
|
|
145
156
|
return false;
|
|
146
157
|
}
|
|
147
|
-
let { skipVisibilityCheck = false } = options;
|
|
158
|
+
let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
|
|
159
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
160
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
161
|
+
skipNegativeTabIndexCheck = false;
|
|
162
|
+
}
|
|
148
163
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
149
164
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
150
165
|
skipVisibilityCheck = false;
|
|
@@ -152,10 +167,12 @@ function isFocusable(element, options = {}) {
|
|
|
152
167
|
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
153
168
|
return false;
|
|
154
169
|
}
|
|
155
|
-
if (getTabIndex(element) < 0) {
|
|
170
|
+
if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
|
|
156
171
|
return false;
|
|
157
172
|
}
|
|
158
|
-
if (!element.matches(
|
|
173
|
+
if (!element.matches(
|
|
174
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
|
|
175
|
+
)) {
|
|
159
176
|
return false;
|
|
160
177
|
}
|
|
161
178
|
if (isDisabledDeep(element)) {
|
|
@@ -180,6 +197,7 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
180
197
|
composed = false,
|
|
181
198
|
filter,
|
|
182
199
|
include,
|
|
200
|
+
skipNegativeTabIndexCheck = false,
|
|
183
201
|
skipVisibilityCheck = false,
|
|
184
202
|
wrap = false
|
|
185
203
|
} = options;
|
|
@@ -213,6 +231,10 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
213
231
|
);
|
|
214
232
|
include = void 0;
|
|
215
233
|
}
|
|
234
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
235
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
236
|
+
skipNegativeTabIndexCheck = false;
|
|
237
|
+
}
|
|
216
238
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
217
239
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
218
240
|
skipVisibilityCheck = false;
|
|
@@ -221,12 +243,14 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
221
243
|
console.warn("Invalid wrap option. Fallback: false.");
|
|
222
244
|
wrap = false;
|
|
223
245
|
}
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
filter
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
246
|
+
const settings = { composed, skipNegativeTabIndexCheck, skipVisibilityCheck };
|
|
247
|
+
if (filter !== void 0) {
|
|
248
|
+
Object.assign(settings, { filter });
|
|
249
|
+
}
|
|
250
|
+
if (include !== void 0) {
|
|
251
|
+
Object.assign(settings, { include });
|
|
252
|
+
}
|
|
253
|
+
const focusables = getFocusables(container, settings);
|
|
230
254
|
const { length } = focusables;
|
|
231
255
|
if (!length) {
|
|
232
256
|
return null;
|
|
@@ -457,7 +481,7 @@ function isUngroupedRadio(element) {
|
|
|
457
481
|
* High-precision focus management utility with full composed tree support.
|
|
458
482
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
459
483
|
*
|
|
460
|
-
* @version 4.
|
|
484
|
+
* @version 4.3.1
|
|
461
485
|
* @author Yusuke Kamiyamane
|
|
462
486
|
* @license MIT
|
|
463
487
|
* @copyright Copyright (c) Yusuke Kamiyamane
|