power-focusable 4.2.0 → 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 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. Useful for initializing.
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-roving-tabindex]') });
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-roving-tabindex]') });
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-roving-tabindex]') });
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-roving-tabindex]') });
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,11 +67,22 @@ 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
+ }
74
+ if (typeof skipVisibilityCheck !== "boolean") {
75
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
76
+ skipVisibilityCheck = false;
77
+ }
69
78
  const elements = [];
70
79
  if (composed || include) {
71
80
  let traverse2 = function(node) {
72
81
  if (node instanceof Element) {
73
- if (isFocusable(node, { skipVisibilityCheck }) || include?.(node)) {
82
+ if (isFocusable(node, {
83
+ skipNegativeTabIndexCheck,
84
+ skipVisibilityCheck
85
+ }) || include?.(node)) {
74
86
  elements[elements.length] = node;
75
87
  }
76
88
  }
@@ -91,7 +103,10 @@ function getFocusables(container = document.body, options = {}) {
91
103
  if (!(candidate instanceof Element)) {
92
104
  continue;
93
105
  }
94
- if (isFocusable(candidate, { skipVisibilityCheck })) {
106
+ if (isFocusable(candidate, {
107
+ skipNegativeTabIndexCheck,
108
+ skipVisibilityCheck
109
+ })) {
95
110
  elements[elements.length] = candidate;
96
111
  }
97
112
  }
@@ -142,7 +157,11 @@ function isFocusable(element, options = {}) {
142
157
  console.warn("Invalid element");
143
158
  return false;
144
159
  }
145
- 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
+ }
146
165
  if (typeof skipVisibilityCheck !== "boolean") {
147
166
  console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
148
167
  skipVisibilityCheck = false;
@@ -150,10 +169,12 @@ function isFocusable(element, options = {}) {
150
169
  if (element.hasAttribute("hidden") || isInert(element)) {
151
170
  return false;
152
171
  }
153
- if (getTabIndex(element) < 0) {
172
+ if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
154
173
  return false;
155
174
  }
156
- if (!element.matches(FOCUSABLE_SELECTOR)) {
175
+ if (!element.matches(
176
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
177
+ )) {
157
178
  return false;
158
179
  }
159
180
  if (isDisabledDeep(element)) {
@@ -178,6 +199,7 @@ function getRelativeFocusable(container, offset, options) {
178
199
  composed = false,
179
200
  filter,
180
201
  include,
202
+ skipNegativeTabIndexCheck = false,
181
203
  skipVisibilityCheck = false,
182
204
  wrap = false
183
205
  } = options;
@@ -211,6 +233,10 @@ function getRelativeFocusable(container, offset, options) {
211
233
  );
212
234
  include = void 0;
213
235
  }
236
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
237
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
238
+ skipNegativeTabIndexCheck = false;
239
+ }
214
240
  if (typeof skipVisibilityCheck !== "boolean") {
215
241
  console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
216
242
  skipVisibilityCheck = false;
@@ -223,6 +249,7 @@ function getRelativeFocusable(container, offset, options) {
223
249
  composed,
224
250
  filter,
225
251
  include,
252
+ skipNegativeTabIndexCheck,
226
253
  skipVisibilityCheck
227
254
  });
228
255
  const { length } = focusables;
@@ -455,7 +482,7 @@ function isUngroupedRadio(element) {
455
482
  * High-precision focus management utility with full composed tree support.
456
483
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
457
484
  *
458
- * @version 4.2.0
485
+ * @version 4.3.0
459
486
  * @author Yusuke Kamiyamane
460
487
  * @license MIT
461
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.2.0
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.2.0
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,11 +65,22 @@ 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
+ }
72
+ if (typeof skipVisibilityCheck !== "boolean") {
73
+ console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
74
+ skipVisibilityCheck = false;
75
+ }
67
76
  const elements = [];
68
77
  if (composed || include) {
69
78
  let traverse2 = function(node) {
70
79
  if (node instanceof Element) {
71
- if (isFocusable(node, { skipVisibilityCheck }) || include?.(node)) {
80
+ if (isFocusable(node, {
81
+ skipNegativeTabIndexCheck,
82
+ skipVisibilityCheck
83
+ }) || include?.(node)) {
72
84
  elements[elements.length] = node;
73
85
  }
74
86
  }
@@ -89,7 +101,10 @@ function getFocusables(container = document.body, options = {}) {
89
101
  if (!(candidate instanceof Element)) {
90
102
  continue;
91
103
  }
92
- if (isFocusable(candidate, { skipVisibilityCheck })) {
104
+ if (isFocusable(candidate, {
105
+ skipNegativeTabIndexCheck,
106
+ skipVisibilityCheck
107
+ })) {
93
108
  elements[elements.length] = candidate;
94
109
  }
95
110
  }
@@ -140,7 +155,11 @@ function isFocusable(element, options = {}) {
140
155
  console.warn("Invalid element");
141
156
  return false;
142
157
  }
143
- 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
+ }
144
163
  if (typeof skipVisibilityCheck !== "boolean") {
145
164
  console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
146
165
  skipVisibilityCheck = false;
@@ -148,10 +167,12 @@ function isFocusable(element, options = {}) {
148
167
  if (element.hasAttribute("hidden") || isInert(element)) {
149
168
  return false;
150
169
  }
151
- if (getTabIndex(element) < 0) {
170
+ if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
152
171
  return false;
153
172
  }
154
- if (!element.matches(FOCUSABLE_SELECTOR)) {
173
+ if (!element.matches(
174
+ skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
175
+ )) {
155
176
  return false;
156
177
  }
157
178
  if (isDisabledDeep(element)) {
@@ -176,6 +197,7 @@ function getRelativeFocusable(container, offset, options) {
176
197
  composed = false,
177
198
  filter,
178
199
  include,
200
+ skipNegativeTabIndexCheck = false,
179
201
  skipVisibilityCheck = false,
180
202
  wrap = false
181
203
  } = options;
@@ -209,6 +231,10 @@ function getRelativeFocusable(container, offset, options) {
209
231
  );
210
232
  include = void 0;
211
233
  }
234
+ if (typeof skipNegativeTabIndexCheck !== "boolean") {
235
+ console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
236
+ skipNegativeTabIndexCheck = false;
237
+ }
212
238
  if (typeof skipVisibilityCheck !== "boolean") {
213
239
  console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
214
240
  skipVisibilityCheck = false;
@@ -221,6 +247,7 @@ function getRelativeFocusable(container, offset, options) {
221
247
  composed,
222
248
  filter,
223
249
  include,
250
+ skipNegativeTabIndexCheck,
224
251
  skipVisibilityCheck
225
252
  });
226
253
  const { length } = focusables;
@@ -453,7 +480,7 @@ function isUngroupedRadio(element) {
453
480
  * High-precision focus management utility with full composed tree support.
454
481
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
455
482
  *
456
- * @version 4.2.0
483
+ * @version 4.3.0
457
484
  * @author Yusuke Kamiyamane
458
485
  * @license MIT
459
486
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "power-focusable",
3
- "version": "4.2.0",
3
+ "version": "4.3.0",
4
4
  "description": "High-precision focus management utility with full composed tree support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",