power-focusable 4.2.1 → 4.3.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/README.md +12 -5
- package/dist/index.cjs +29 -6
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +29 -6
- 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;
|
|
@@ -227,6 +249,7 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
227
249
|
composed,
|
|
228
250
|
filter,
|
|
229
251
|
include,
|
|
252
|
+
skipNegativeTabIndexCheck,
|
|
230
253
|
skipVisibilityCheck
|
|
231
254
|
});
|
|
232
255
|
const { length } = focusables;
|
|
@@ -459,7 +482,7 @@ function isUngroupedRadio(element) {
|
|
|
459
482
|
* High-precision focus management utility with full composed tree support.
|
|
460
483
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
461
484
|
*
|
|
462
|
-
* @version 4.
|
|
485
|
+
* @version 4.3.0
|
|
463
486
|
* @author Yusuke Kamiyamane
|
|
464
487
|
* @license MIT
|
|
465
488
|
* @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.0
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -14,6 +14,7 @@ interface PowerFocusableOptions {
|
|
|
14
14
|
readonly composed?: boolean;
|
|
15
15
|
readonly filter?: ((element: Element) => boolean) | undefined;
|
|
16
16
|
readonly include?: ((element: Element) => boolean) | undefined;
|
|
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.0
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -14,6 +14,7 @@ interface PowerFocusableOptions {
|
|
|
14
14
|
readonly composed?: boolean;
|
|
15
15
|
readonly filter?: ((element: Element) => boolean) | undefined;
|
|
16
16
|
readonly include?: ((element: Element) => boolean) | undefined;
|
|
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;
|
|
@@ -225,6 +247,7 @@ function getRelativeFocusable(container, offset, options) {
|
|
|
225
247
|
composed,
|
|
226
248
|
filter,
|
|
227
249
|
include,
|
|
250
|
+
skipNegativeTabIndexCheck,
|
|
228
251
|
skipVisibilityCheck
|
|
229
252
|
});
|
|
230
253
|
const { length } = focusables;
|
|
@@ -457,7 +480,7 @@ function isUngroupedRadio(element) {
|
|
|
457
480
|
* High-precision focus management utility with full composed tree support.
|
|
458
481
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
459
482
|
*
|
|
460
|
-
* @version 4.
|
|
483
|
+
* @version 4.3.0
|
|
461
484
|
* @author Yusuke Kamiyamane
|
|
462
485
|
* @license MIT
|
|
463
486
|
* @copyright Copyright (c) Yusuke Kamiyamane
|