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 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.28';
27
+ import { ... } 'https://esm.sh/power-focusable@4.3.30';
28
28
  // or
29
- import { ... } 'https://cdn.jsdelivr.net/npm/power-focusable@4.3.28/+esm';
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.28';
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; // default: DocumentOrShadowRoot.activeElement
39
- composed: boolean; // default: false
40
- filter: PredicateFunction; // default: undefined
41
- include: PredicateFunction; // default: undefined
42
- skipNegativeTabIndexCheck: boolean; // default: false
43
- skipVisibilityCheck: boolean; // default: false
44
- wrap: boolean; // default: false
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 = `: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"])`;
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
- let {
68
- composed = false,
67
+ const {
68
+ composed,
69
69
  filter,
70
70
  include,
71
- skipNegativeTabIndexCheck = false,
72
- skipVisibilityCheck = false
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
- const children2 = composed ? getComposedChildren(node) : getChildren(node);
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
- const children = composed ? getComposedChildren(container) : getChildren(container);
114
- for (let i = 0, l = children.length; i < l; i++) {
115
- const child = children[i];
116
- child && traverse2(child);
117
- }
85
+ (composed ? getComposedChildren(container) : getChildren(container)).map(
86
+ traverse2
87
+ );
118
88
  } else {
119
- const matches = container.querySelectorAll(
89
+ for (const match of container.querySelectorAll(
120
90
  skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
121
- );
122
- for (let i = 0, l = matches.length; i < l; i++) {
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
- for (const sibling of getComposedSiblings(node)) {
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
- let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
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
- let {
212
- anchor = getActiveElement(),
213
- composed = false,
162
+ const {
163
+ anchor,
164
+ composed,
214
165
  filter,
215
166
  include,
216
- skipNegativeTabIndexCheck = false,
217
- skipVisibilityCheck = false,
218
- wrap = false
219
- } = options;
167
+ skipNegativeTabIndexCheck,
168
+ skipVisibilityCheck,
169
+ wrap
170
+ } = resolveOptions(options);
220
171
  if (!(anchor instanceof Element)) {
221
- const active = getActiveElement();
222
- if (active instanceof Element) {
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
- if (typeof composed !== "boolean") {
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 (let i = 0, l = elements.length; i < l; i++) {
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 (let i = 0, l = elements.length; i < l; i++) {
367
- const element = elements[i];
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
- for (let i = 0, l = natural.length; i < l; i++) {
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 (let i = 0, l = siblings.length; i < l; i++) {
429
- const sibling = siblings[i];
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.28
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.28
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.28
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 = `: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"])`;
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
- let {
66
- composed = false,
65
+ const {
66
+ composed,
67
67
  filter,
68
68
  include,
69
- skipNegativeTabIndexCheck = false,
70
- skipVisibilityCheck = false
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
- const children2 = composed ? getComposedChildren(node) : getChildren(node);
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
- const children = composed ? getComposedChildren(container) : getChildren(container);
112
- for (let i = 0, l = children.length; i < l; i++) {
113
- const child = children[i];
114
- child && traverse2(child);
115
- }
83
+ (composed ? getComposedChildren(container) : getChildren(container)).map(
84
+ traverse2
85
+ );
116
86
  } else {
117
- const matches = container.querySelectorAll(
87
+ for (const match of container.querySelectorAll(
118
88
  skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR_WITH_NEGATIVE_TABINDEX : FOCUSABLE_SELECTOR
119
- );
120
- for (let i = 0, l = matches.length; i < l; i++) {
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
- for (const sibling of getComposedSiblings(node)) {
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
- let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
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
- let {
210
- anchor = getActiveElement(),
211
- composed = false,
160
+ const {
161
+ anchor,
162
+ composed,
212
163
  filter,
213
164
  include,
214
- skipNegativeTabIndexCheck = false,
215
- skipVisibilityCheck = false,
216
- wrap = false
217
- } = options;
165
+ skipNegativeTabIndexCheck,
166
+ skipVisibilityCheck,
167
+ wrap
168
+ } = resolveOptions(options);
218
169
  if (!(anchor instanceof Element)) {
219
- const active = getActiveElement();
220
- if (active instanceof Element) {
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
- if (typeof composed !== "boolean") {
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 (let i = 0, l = elements.length; i < l; i++) {
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 (let i = 0, l = elements.length; i < l; i++) {
365
- const element = elements[i];
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
- for (let i = 0, l = natural.length; i < l; i++) {
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 (let i = 0, l = siblings.length; i < l; i++) {
427
- const sibling = siblings[i];
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.28
448
+ * @version 4.3.30
496
449
  * @author Yusuke Kamiyamane
497
450
  * @license MIT
498
451
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "power-focusable",
3
- "version": "4.3.28",
3
+ "version": "4.3.30",
4
4
  "description": "High-precision focus management utility with full composed tree support",
5
5
  "keywords": [
6
6
  "a11y",