power-focusable 4.3.1 → 4.3.3

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
@@ -21,14 +21,14 @@ import {
21
21
  hasFocusable,
22
22
  inertOutside,
23
23
  isFocusable,
24
- } from 'power-focusable';
24
+ } from 'power-focusable@4.3.3';
25
25
 
26
26
  // CDNs
27
- import { ... } 'https://esm.sh/power-focusable'
27
+ import { ... } 'https://esm.sh/power-focusable@4.3.3';
28
28
  // or
29
- import { ... } 'https://cdn.jsdelivr.net/npm/power-focusable/+esm';
29
+ import { ... } 'https://cdn.jsdelivr.net/npm/power-focusable@4.3.3/+esm';
30
30
  // or
31
- import { ... } 'https://unpkg.com/power-focusable/dist/index.js';
31
+ import { ... } 'https://esm.unpkg.com/power-focusable@4.3.3';
32
32
  ```
33
33
 
34
34
  ## 🪄 Options
package/dist/index.cjs CHANGED
@@ -4,7 +4,7 @@
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
5
  function createFocusTrap(container = document.body) {
6
6
  if (!(container instanceof Element)) {
7
- throw new Error("Invalid container element");
7
+ throw new TypeError("Invalid container element");
8
8
  }
9
9
  focusElement(container);
10
10
  if (getActiveElement() !== container) {
@@ -12,22 +12,21 @@ function createFocusTrap(container = document.body) {
12
12
  first && focusElement(first);
13
13
  }
14
14
  function onKeyDown(event) {
15
- const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
16
- if (key !== "Tab" || altKey || ctrlKey || metaKey) {
15
+ if (!event.composedPath().includes(container)) {
17
16
  return;
18
17
  }
19
- if (!event.composedPath().includes(container)) {
18
+ const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
19
+ if (key !== "Tab" || altKey || ctrlKey || metaKey) {
20
20
  return;
21
21
  }
22
22
  const focusable = getRelativeFocusable(container, shiftKey ? -1 : 1, {
23
23
  composed: true,
24
24
  wrap: true
25
25
  });
26
- if (!focusable) {
27
- return;
26
+ if (focusable) {
27
+ event.preventDefault();
28
+ focusElement(focusable);
28
29
  }
29
- event.preventDefault();
30
- focusElement(focusable);
31
30
  }
32
31
  let controller = new AbortController();
33
32
  document.addEventListener("keydown", onKeyDown, {
@@ -78,21 +77,16 @@ function getFocusables(container = document.body, options = {}) {
78
77
  const elements = [];
79
78
  if (composed || include) {
80
79
  let traverse2 = function(node) {
81
- if (node instanceof Element) {
82
- if (isFocusable(node, {
83
- skipNegativeTabIndexCheck,
84
- skipVisibilityCheck
85
- }) || include?.(node)) {
86
- elements[elements.length] = node;
87
- }
80
+ if (!(node instanceof Element)) {
81
+ return;
82
+ }
83
+ if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
84
+ elements[elements.length] = node;
88
85
  }
89
86
  const children = getComposedChildren(node);
90
87
  for (let i = 0, l = children.length; i < l; i++) {
91
88
  const child = children[i];
92
- if (!child) {
93
- continue;
94
- }
95
- traverse2(child);
89
+ child && traverse2(child);
96
90
  }
97
91
  };
98
92
  traverse2(container);
@@ -100,10 +94,7 @@ function getFocusables(container = document.body, options = {}) {
100
94
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
101
95
  for (let i = 0, l = candidates.length; i < l; i++) {
102
96
  const candidate = candidates[i];
103
- if (!(candidate instanceof Element)) {
104
- continue;
105
- }
106
- if (isFocusable(candidate, {
97
+ if (candidate && isFocusable(candidate, {
107
98
  skipNegativeTabIndexCheck,
108
99
  skipVisibilityCheck
109
100
  })) {
@@ -131,21 +122,15 @@ function inertOutside(element) {
131
122
  }
132
123
  function traverse(node, callback) {
133
124
  const parent = getComposedParent(node);
134
- if (!parent) {
135
- return;
136
- }
137
- for (const sibling of getComposedSiblings(node)) {
138
- callback(sibling);
125
+ if (parent) {
126
+ for (const sibling of getComposedSiblings(node)) {
127
+ callback(sibling);
128
+ }
129
+ traverse(parent, callback);
139
130
  }
140
- traverse(parent, callback);
141
131
  }
142
132
  const elements = [];
143
- traverse(element, (node) => {
144
- if (!(node instanceof Element)) {
145
- return;
146
- }
147
- applyInert(node) && elements.push(node);
148
- });
133
+ traverse(element, (node) => node && applyInert(node) && elements.push(node));
149
134
  return () => {
150
135
  elements.forEach((element2) => {
151
136
  restoreInert(element2);
@@ -322,23 +307,19 @@ function normalizeRadioGroup(elements) {
322
307
  for (const group of map.values()) {
323
308
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
324
309
  }
325
- return elements.filter((element) => {
326
- if (isUngroupedRadio(element)) {
327
- return placeholder.has(element);
328
- }
329
- return true;
330
- });
310
+ return elements.filter(
311
+ (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
312
+ );
331
313
  }
332
314
  function sortByTabIndex(elements) {
333
315
  const ordered = [];
334
316
  const natural = [];
335
317
  for (let i = 0, l = elements.length; i < l; i++) {
336
318
  const element = elements[i];
337
- if (!element) {
338
- continue;
319
+ if (element) {
320
+ const target = getTabIndex(element) > 0 ? ordered : natural;
321
+ target[target.length] = element;
339
322
  }
340
- const target = getTabIndex(element) > 0 ? ordered : natural;
341
- target[target.length] = element;
342
323
  }
343
324
  ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
344
325
  let count = 0;
@@ -356,8 +337,9 @@ function containsComposed(container, element) {
356
337
  while (current) {
357
338
  if (current === container) {
358
339
  return true;
340
+ } else {
341
+ current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
359
342
  }
360
- current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
361
343
  }
362
344
  return false;
363
345
  }
@@ -382,12 +364,10 @@ function getComposedChildren(node) {
382
364
  function getComposedParent(node) {
383
365
  if (node instanceof Element && node.assignedSlot) {
384
366
  return node.assignedSlot;
367
+ } else {
368
+ const parent = node.parentNode;
369
+ return parent instanceof ShadowRoot ? parent.host : parent instanceof Element ? parent : null;
385
370
  }
386
- const parent = node.parentNode;
387
- if (parent instanceof ShadowRoot) {
388
- return parent.host;
389
- }
390
- return parent instanceof Element ? parent : null;
391
371
  }
392
372
  function getComposedSiblings(node) {
393
373
  if (node.assignedSlot) {
@@ -395,30 +375,25 @@ function getComposedSiblings(node) {
395
375
  const filtered = [];
396
376
  for (let i = 0, l = siblings.length; i < l; i++) {
397
377
  const sibling = siblings[i];
398
- if (sibling !== node) {
399
- if (!(sibling instanceof Element)) {
400
- continue;
401
- }
378
+ if (sibling && sibling !== node) {
402
379
  filtered[filtered.length] = sibling;
403
380
  }
404
381
  }
405
382
  return filtered;
383
+ } else {
384
+ return getComposedParent(node) ? getSiblings(node) : [];
406
385
  }
407
- const parent = getComposedParent(node);
408
- if (!parent) {
409
- return [];
410
- }
411
- return getSiblings(node);
412
386
  }
413
387
  var inertRefCounts = /* @__PURE__ */ new WeakMap();
414
388
  function applyInert(element) {
415
- if (isInert(element) && !inertRefCounts.has(element)) {
389
+ if (!isInert(element) || inertRefCounts.has(element)) {
390
+ const count = inertRefCounts.get(element) ?? 0;
391
+ inertRefCounts.set(element, count + 1);
392
+ count === 0 && element.setAttribute("inert", "");
393
+ return true;
394
+ } else {
416
395
  return false;
417
396
  }
418
- const count = inertRefCounts.get(element) ?? 0;
419
- inertRefCounts.set(element, count + 1);
420
- count === 0 && element.setAttribute("inert", "");
421
- return true;
422
397
  }
423
398
  function restoreInert(element) {
424
399
  const count = inertRefCounts.get(element);
@@ -428,9 +403,9 @@ function restoreInert(element) {
428
403
  if (count === 1) {
429
404
  inertRefCounts.delete(element);
430
405
  element.removeAttribute("inert");
431
- return;
406
+ } else {
407
+ inertRefCounts.set(element, count - 1);
432
408
  }
433
- inertRefCounts.set(element, count - 1);
434
409
  }
435
410
  function focusElement(element) {
436
411
  "focus" in element && typeof element.focus === "function" && element.focus();
@@ -483,7 +458,7 @@ function isUngroupedRadio(element) {
483
458
  * High-precision focus management utility with full composed tree support.
484
459
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
485
460
  *
486
- * @version 4.3.1
461
+ * @version 4.3.3
487
462
  * @author Yusuke Kamiyamane
488
463
  * @license MIT
489
464
  * @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.1
6
+ * @version 4.3.3
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.1
6
+ * @version 4.3.3
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
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
3
  function createFocusTrap(container = document.body) {
4
4
  if (!(container instanceof Element)) {
5
- throw new Error("Invalid container element");
5
+ throw new TypeError("Invalid container element");
6
6
  }
7
7
  focusElement(container);
8
8
  if (getActiveElement() !== container) {
@@ -10,22 +10,21 @@ function createFocusTrap(container = document.body) {
10
10
  first && focusElement(first);
11
11
  }
12
12
  function onKeyDown(event) {
13
- const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
14
- if (key !== "Tab" || altKey || ctrlKey || metaKey) {
13
+ if (!event.composedPath().includes(container)) {
15
14
  return;
16
15
  }
17
- if (!event.composedPath().includes(container)) {
16
+ const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
17
+ if (key !== "Tab" || altKey || ctrlKey || metaKey) {
18
18
  return;
19
19
  }
20
20
  const focusable = getRelativeFocusable(container, shiftKey ? -1 : 1, {
21
21
  composed: true,
22
22
  wrap: true
23
23
  });
24
- if (!focusable) {
25
- return;
24
+ if (focusable) {
25
+ event.preventDefault();
26
+ focusElement(focusable);
26
27
  }
27
- event.preventDefault();
28
- focusElement(focusable);
29
28
  }
30
29
  let controller = new AbortController();
31
30
  document.addEventListener("keydown", onKeyDown, {
@@ -76,21 +75,16 @@ function getFocusables(container = document.body, options = {}) {
76
75
  const elements = [];
77
76
  if (composed || include) {
78
77
  let traverse2 = function(node) {
79
- if (node instanceof Element) {
80
- if (isFocusable(node, {
81
- skipNegativeTabIndexCheck,
82
- skipVisibilityCheck
83
- }) || include?.(node)) {
84
- elements[elements.length] = node;
85
- }
78
+ if (!(node instanceof Element)) {
79
+ return;
80
+ }
81
+ if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
82
+ elements[elements.length] = node;
86
83
  }
87
84
  const children = getComposedChildren(node);
88
85
  for (let i = 0, l = children.length; i < l; i++) {
89
86
  const child = children[i];
90
- if (!child) {
91
- continue;
92
- }
93
- traverse2(child);
87
+ child && traverse2(child);
94
88
  }
95
89
  };
96
90
  traverse2(container);
@@ -98,10 +92,7 @@ function getFocusables(container = document.body, options = {}) {
98
92
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
99
93
  for (let i = 0, l = candidates.length; i < l; i++) {
100
94
  const candidate = candidates[i];
101
- if (!(candidate instanceof Element)) {
102
- continue;
103
- }
104
- if (isFocusable(candidate, {
95
+ if (candidate && isFocusable(candidate, {
105
96
  skipNegativeTabIndexCheck,
106
97
  skipVisibilityCheck
107
98
  })) {
@@ -129,21 +120,15 @@ function inertOutside(element) {
129
120
  }
130
121
  function traverse(node, callback) {
131
122
  const parent = getComposedParent(node);
132
- if (!parent) {
133
- return;
134
- }
135
- for (const sibling of getComposedSiblings(node)) {
136
- callback(sibling);
123
+ if (parent) {
124
+ for (const sibling of getComposedSiblings(node)) {
125
+ callback(sibling);
126
+ }
127
+ traverse(parent, callback);
137
128
  }
138
- traverse(parent, callback);
139
129
  }
140
130
  const elements = [];
141
- traverse(element, (node) => {
142
- if (!(node instanceof Element)) {
143
- return;
144
- }
145
- applyInert(node) && elements.push(node);
146
- });
131
+ traverse(element, (node) => node && applyInert(node) && elements.push(node));
147
132
  return () => {
148
133
  elements.forEach((element2) => {
149
134
  restoreInert(element2);
@@ -320,23 +305,19 @@ function normalizeRadioGroup(elements) {
320
305
  for (const group of map.values()) {
321
306
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
322
307
  }
323
- return elements.filter((element) => {
324
- if (isUngroupedRadio(element)) {
325
- return placeholder.has(element);
326
- }
327
- return true;
328
- });
308
+ return elements.filter(
309
+ (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
310
+ );
329
311
  }
330
312
  function sortByTabIndex(elements) {
331
313
  const ordered = [];
332
314
  const natural = [];
333
315
  for (let i = 0, l = elements.length; i < l; i++) {
334
316
  const element = elements[i];
335
- if (!element) {
336
- continue;
317
+ if (element) {
318
+ const target = getTabIndex(element) > 0 ? ordered : natural;
319
+ target[target.length] = element;
337
320
  }
338
- const target = getTabIndex(element) > 0 ? ordered : natural;
339
- target[target.length] = element;
340
321
  }
341
322
  ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
342
323
  let count = 0;
@@ -354,8 +335,9 @@ function containsComposed(container, element) {
354
335
  while (current) {
355
336
  if (current === container) {
356
337
  return true;
338
+ } else {
339
+ current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
357
340
  }
358
- current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
359
341
  }
360
342
  return false;
361
343
  }
@@ -380,12 +362,10 @@ function getComposedChildren(node) {
380
362
  function getComposedParent(node) {
381
363
  if (node instanceof Element && node.assignedSlot) {
382
364
  return node.assignedSlot;
365
+ } else {
366
+ const parent = node.parentNode;
367
+ return parent instanceof ShadowRoot ? parent.host : parent instanceof Element ? parent : null;
383
368
  }
384
- const parent = node.parentNode;
385
- if (parent instanceof ShadowRoot) {
386
- return parent.host;
387
- }
388
- return parent instanceof Element ? parent : null;
389
369
  }
390
370
  function getComposedSiblings(node) {
391
371
  if (node.assignedSlot) {
@@ -393,30 +373,25 @@ function getComposedSiblings(node) {
393
373
  const filtered = [];
394
374
  for (let i = 0, l = siblings.length; i < l; i++) {
395
375
  const sibling = siblings[i];
396
- if (sibling !== node) {
397
- if (!(sibling instanceof Element)) {
398
- continue;
399
- }
376
+ if (sibling && sibling !== node) {
400
377
  filtered[filtered.length] = sibling;
401
378
  }
402
379
  }
403
380
  return filtered;
381
+ } else {
382
+ return getComposedParent(node) ? getSiblings(node) : [];
404
383
  }
405
- const parent = getComposedParent(node);
406
- if (!parent) {
407
- return [];
408
- }
409
- return getSiblings(node);
410
384
  }
411
385
  var inertRefCounts = /* @__PURE__ */ new WeakMap();
412
386
  function applyInert(element) {
413
- if (isInert(element) && !inertRefCounts.has(element)) {
387
+ if (!isInert(element) || inertRefCounts.has(element)) {
388
+ const count = inertRefCounts.get(element) ?? 0;
389
+ inertRefCounts.set(element, count + 1);
390
+ count === 0 && element.setAttribute("inert", "");
391
+ return true;
392
+ } else {
414
393
  return false;
415
394
  }
416
- const count = inertRefCounts.get(element) ?? 0;
417
- inertRefCounts.set(element, count + 1);
418
- count === 0 && element.setAttribute("inert", "");
419
- return true;
420
395
  }
421
396
  function restoreInert(element) {
422
397
  const count = inertRefCounts.get(element);
@@ -426,9 +401,9 @@ function restoreInert(element) {
426
401
  if (count === 1) {
427
402
  inertRefCounts.delete(element);
428
403
  element.removeAttribute("inert");
429
- return;
404
+ } else {
405
+ inertRefCounts.set(element, count - 1);
430
406
  }
431
- inertRefCounts.set(element, count - 1);
432
407
  }
433
408
  function focusElement(element) {
434
409
  "focus" in element && typeof element.focus === "function" && element.focus();
@@ -481,7 +456,7 @@ function isUngroupedRadio(element) {
481
456
  * High-precision focus management utility with full composed tree support.
482
457
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
483
458
  *
484
- * @version 4.3.1
459
+ * @version 4.3.3
485
460
  * @author Yusuke Kamiyamane
486
461
  * @license MIT
487
462
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "power-focusable",
3
- "version": "4.3.1",
3
+ "version": "4.3.3",
4
4
  "description": "High-precision focus management utility with full composed tree support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -52,6 +52,9 @@
52
52
  "typescript": "^5.6.0",
53
53
  "vitest": "^4.1.5"
54
54
  },
55
+ "overrides": {
56
+ "@emnapi/wasi-threads": "1.2.2"
57
+ },
55
58
  "engines": {
56
59
  "node": ">=18"
57
60
  }