@y14e/portal 1.2.13 → 1.2.15

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
@@ -10,14 +10,14 @@ npm i @y14e/portal
10
10
 
11
11
  ```ts
12
12
  // npm
13
- import { createPortal } from '@y14e/portal@1.2.13';
13
+ import { createPortal } from '@y14e/portal@1.2.15';
14
14
 
15
15
  // CDNs
16
- import { createPortal } from 'https://esm.sh/@y14e/portal@1.2.13';
16
+ import { createPortal } from 'https://esm.sh/@y14e/portal@1.2.15';
17
17
  // or
18
- import { createPortal } from 'https://cdn.jsdelivr.net/npm/@y14e/portal@1.2.13/+esm';
18
+ import { createPortal } from 'https://cdn.jsdelivr.net/npm/@y14e/portal@1.2.15/+esm';
19
19
  // or
20
- import { createPortal } from 'https://esm.unpkg.com/@y14e/portal@1.2.13';
20
+ import { createPortal } from 'https://esm.unpkg.com/@y14e/portal@1.2.15';
21
21
  ```
22
22
 
23
23
  ## 📦 APIs
package/dist/index.cjs CHANGED
@@ -68,21 +68,16 @@ function getFocusables(container = document.body, options = {}) {
68
68
  const elements = [];
69
69
  if (composed || include) {
70
70
  let traverse2 = function(node) {
71
- if (node instanceof Element) {
72
- if (isFocusable(node, {
73
- skipNegativeTabIndexCheck,
74
- skipVisibilityCheck
75
- }) || include?.(node)) {
76
- elements[elements.length] = node;
77
- }
71
+ if (!(node instanceof Element)) {
72
+ return;
73
+ }
74
+ if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
75
+ elements[elements.length] = node;
78
76
  }
79
77
  const children = getComposedChildren(node);
80
78
  for (let i = 0, l = children.length; i < l; i++) {
81
79
  const child = children[i];
82
- if (!child) {
83
- continue;
84
- }
85
- traverse2(child);
80
+ child && traverse2(child);
86
81
  }
87
82
  };
88
83
  traverse2(container);
@@ -90,10 +85,7 @@ function getFocusables(container = document.body, options = {}) {
90
85
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
91
86
  for (let i = 0, l = candidates.length; i < l; i++) {
92
87
  const candidate = candidates[i];
93
- if (!(candidate instanceof Element)) {
94
- continue;
95
- }
96
- if (isFocusable(candidate, {
88
+ if (candidate && isFocusable(candidate, {
97
89
  skipNegativeTabIndexCheck,
98
90
  skipVisibilityCheck
99
91
  })) {
@@ -280,23 +272,19 @@ function normalizeRadioGroup(elements) {
280
272
  for (const group of map.values()) {
281
273
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
282
274
  }
283
- return elements.filter((element) => {
284
- if (isUngroupedRadio(element)) {
285
- return placeholder.has(element);
286
- }
287
- return true;
288
- });
275
+ return elements.filter(
276
+ (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
277
+ );
289
278
  }
290
279
  function sortByTabIndex(elements) {
291
280
  const ordered = [];
292
281
  const natural = [];
293
282
  for (let i = 0, l = elements.length; i < l; i++) {
294
283
  const element = elements[i];
295
- if (!element) {
296
- continue;
284
+ if (element) {
285
+ const target = getTabIndex(element) > 0 ? ordered : natural;
286
+ target[target.length] = element;
297
287
  }
298
- const target = getTabIndex(element) > 0 ? ordered : natural;
299
- target[target.length] = element;
300
288
  }
301
289
  ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
302
290
  let count = 0;
@@ -314,8 +302,9 @@ function containsComposed(container, element) {
314
302
  while (current) {
315
303
  if (current === container) {
316
304
  return true;
305
+ } else {
306
+ current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
317
307
  }
318
- current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
319
308
  }
320
309
  return false;
321
310
  }
@@ -457,7 +446,15 @@ var Portal = class {
457
446
  }
458
447
  this.#update();
459
448
  const first = [...this.#focusables][0];
460
- first && focusElement(first);
449
+ if (first) {
450
+ focusElement(first);
451
+ } else {
452
+ const next = getNextFocusable(document.body, {
453
+ anchor: this.#exitSentinel,
454
+ composed: true
455
+ });
456
+ next && focusElement(next);
457
+ }
461
458
  return;
462
459
  }
463
460
  if (current === this.#exitSentinel) {
@@ -467,7 +464,15 @@ var Portal = class {
467
464
  }
468
465
  this.#update();
469
466
  const last = [...this.#focusables].at(-1);
470
- last && focusElement(last);
467
+ if (last) {
468
+ focusElement(last);
469
+ } else {
470
+ const previous = getPreviousFocusable(document.body, {
471
+ anchor: this.#entranceSentinel,
472
+ composed: true
473
+ });
474
+ previous && focusElement(previous);
475
+ }
471
476
  return;
472
477
  }
473
478
  };
@@ -485,18 +490,17 @@ var Portal = class {
485
490
  }
486
491
  this.#update();
487
492
  const focusables = this.#getFocusables();
488
- if (!focusables.length) {
493
+ if (focusables.length) {
494
+ const index = focusables.indexOf(active);
495
+ if (index !== -1) {
496
+ event.preventDefault();
497
+ const focusable = focusables[index + (shiftKey ? -1 : 1)];
498
+ focusable ? focusElement(focusable) : this.#focusSentinel(shiftKey);
499
+ }
500
+ } else {
489
501
  event.preventDefault();
490
502
  this.#moveFocus(shiftKey ? "previous" : "next");
491
- return;
492
- }
493
- const index = focusables.indexOf(active);
494
- if (index === -1) {
495
- return;
496
503
  }
497
- event.preventDefault();
498
- const focusable = focusables[index + (shiftKey ? -1 : 1)];
499
- focusable ? focusElement(focusable) : this.#focusSentinel(shiftKey);
500
504
  };
501
505
  #update() {
502
506
  const current = /* @__PURE__ */ new Set([
@@ -504,19 +508,17 @@ var Portal = class {
504
508
  ...getFocusables(this.#host, { composed: true })
505
509
  ]);
506
510
  for (const focusable of this.#focusables) {
507
- if (current.has(focusable)) {
508
- continue;
511
+ if (!current.has(focusable)) {
512
+ focusable.isConnected && restoreAttributes([focusable]);
513
+ this.#focusables.delete(focusable);
509
514
  }
510
- focusable.isConnected && restoreAttributes([focusable]);
511
- this.#focusables.delete(focusable);
512
515
  }
513
516
  for (const focusable of current) {
514
- if (this.#focusables.has(focusable)) {
515
- continue;
517
+ if (!this.#focusables.has(focusable)) {
518
+ this.#focusables.add(focusable);
519
+ saveAttributes([focusable], ["tabindex"]);
520
+ focusable.setAttribute("tabindex", "-1");
516
521
  }
517
- this.#focusables.add(focusable);
518
- saveAttributes([focusable], ["tabindex"]);
519
- focusable.setAttribute("tabindex", "-1");
520
522
  }
521
523
  }
522
524
  #createSentinel() {
@@ -573,7 +575,7 @@ function getActiveElement2() {
573
575
  * Lightweight DOM portal (teleport) utility with fully focus management.
574
576
  * Designed for accessible dialogs, menus, overlays, popovers.
575
577
  *
576
- * @version 1.2.13
578
+ * @version 1.2.15
577
579
  * @author Yusuke Kamiyamane
578
580
  * @license MIT
579
581
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -585,7 +587,7 @@ function getActiveElement2() {
585
587
  (**
586
588
  * Attributes Utils
587
589
  *
588
- * @version 1.1.1
590
+ * @version 1.1.2
589
591
  * @author Yusuke Kamiyamane
590
592
  * @license MIT
591
593
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -598,7 +600,7 @@ power-focusable/dist/index.js:
598
600
  * High-precision focus management utility with full composed tree support.
599
601
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
600
602
  *
601
- * @version 4.3.2
603
+ * @version 4.3.3
602
604
  * @author Yusuke Kamiyamane
603
605
  * @license MIT
604
606
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@
3
3
  * Lightweight DOM portal (teleport) utility with fully focus management.
4
4
  * Designed for accessible dialogs, menus, overlays, popovers.
5
5
  *
6
- * @version 1.2.13
6
+ * @version 1.2.15
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
  * Lightweight DOM portal (teleport) utility with fully focus management.
4
4
  * Designed for accessible dialogs, menus, overlays, popovers.
5
5
  *
6
- * @version 1.2.13
6
+ * @version 1.2.15
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -66,21 +66,16 @@ function getFocusables(container = document.body, options = {}) {
66
66
  const elements = [];
67
67
  if (composed || include) {
68
68
  let traverse2 = function(node) {
69
- if (node instanceof Element) {
70
- if (isFocusable(node, {
71
- skipNegativeTabIndexCheck,
72
- skipVisibilityCheck
73
- }) || include?.(node)) {
74
- elements[elements.length] = node;
75
- }
69
+ if (!(node instanceof Element)) {
70
+ return;
71
+ }
72
+ if (isFocusable(node, { skipNegativeTabIndexCheck, skipVisibilityCheck }) || include?.(node)) {
73
+ elements[elements.length] = node;
76
74
  }
77
75
  const children = getComposedChildren(node);
78
76
  for (let i = 0, l = children.length; i < l; i++) {
79
77
  const child = children[i];
80
- if (!child) {
81
- continue;
82
- }
83
- traverse2(child);
78
+ child && traverse2(child);
84
79
  }
85
80
  };
86
81
  traverse2(container);
@@ -88,10 +83,7 @@ function getFocusables(container = document.body, options = {}) {
88
83
  const candidates = container.querySelectorAll(FOCUSABLE_SELECTOR);
89
84
  for (let i = 0, l = candidates.length; i < l; i++) {
90
85
  const candidate = candidates[i];
91
- if (!(candidate instanceof Element)) {
92
- continue;
93
- }
94
- if (isFocusable(candidate, {
86
+ if (candidate && isFocusable(candidate, {
95
87
  skipNegativeTabIndexCheck,
96
88
  skipVisibilityCheck
97
89
  })) {
@@ -278,23 +270,19 @@ function normalizeRadioGroup(elements) {
278
270
  for (const group of map.values()) {
279
271
  placeholder.add(group.find((radio) => radio.checked) ?? group[0]);
280
272
  }
281
- return elements.filter((element) => {
282
- if (isUngroupedRadio(element)) {
283
- return placeholder.has(element);
284
- }
285
- return true;
286
- });
273
+ return elements.filter(
274
+ (element) => isUngroupedRadio(element) ? placeholder.has(element) : true
275
+ );
287
276
  }
288
277
  function sortByTabIndex(elements) {
289
278
  const ordered = [];
290
279
  const natural = [];
291
280
  for (let i = 0, l = elements.length; i < l; i++) {
292
281
  const element = elements[i];
293
- if (!element) {
294
- continue;
282
+ if (element) {
283
+ const target = getTabIndex(element) > 0 ? ordered : natural;
284
+ target[target.length] = element;
295
285
  }
296
- const target = getTabIndex(element) > 0 ? ordered : natural;
297
- target[target.length] = element;
298
286
  }
299
287
  ordered.sort((a, b) => getTabIndex(a) - getTabIndex(b));
300
288
  let count = 0;
@@ -312,8 +300,9 @@ function containsComposed(container, element) {
312
300
  while (current) {
313
301
  if (current === container) {
314
302
  return true;
303
+ } else {
304
+ current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
315
305
  }
316
- current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
317
306
  }
318
307
  return false;
319
308
  }
@@ -455,7 +444,15 @@ var Portal = class {
455
444
  }
456
445
  this.#update();
457
446
  const first = [...this.#focusables][0];
458
- first && focusElement(first);
447
+ if (first) {
448
+ focusElement(first);
449
+ } else {
450
+ const next = getNextFocusable(document.body, {
451
+ anchor: this.#exitSentinel,
452
+ composed: true
453
+ });
454
+ next && focusElement(next);
455
+ }
459
456
  return;
460
457
  }
461
458
  if (current === this.#exitSentinel) {
@@ -465,7 +462,15 @@ var Portal = class {
465
462
  }
466
463
  this.#update();
467
464
  const last = [...this.#focusables].at(-1);
468
- last && focusElement(last);
465
+ if (last) {
466
+ focusElement(last);
467
+ } else {
468
+ const previous = getPreviousFocusable(document.body, {
469
+ anchor: this.#entranceSentinel,
470
+ composed: true
471
+ });
472
+ previous && focusElement(previous);
473
+ }
469
474
  return;
470
475
  }
471
476
  };
@@ -483,18 +488,17 @@ var Portal = class {
483
488
  }
484
489
  this.#update();
485
490
  const focusables = this.#getFocusables();
486
- if (!focusables.length) {
491
+ if (focusables.length) {
492
+ const index = focusables.indexOf(active);
493
+ if (index !== -1) {
494
+ event.preventDefault();
495
+ const focusable = focusables[index + (shiftKey ? -1 : 1)];
496
+ focusable ? focusElement(focusable) : this.#focusSentinel(shiftKey);
497
+ }
498
+ } else {
487
499
  event.preventDefault();
488
500
  this.#moveFocus(shiftKey ? "previous" : "next");
489
- return;
490
- }
491
- const index = focusables.indexOf(active);
492
- if (index === -1) {
493
- return;
494
501
  }
495
- event.preventDefault();
496
- const focusable = focusables[index + (shiftKey ? -1 : 1)];
497
- focusable ? focusElement(focusable) : this.#focusSentinel(shiftKey);
498
502
  };
499
503
  #update() {
500
504
  const current = /* @__PURE__ */ new Set([
@@ -502,19 +506,17 @@ var Portal = class {
502
506
  ...getFocusables(this.#host, { composed: true })
503
507
  ]);
504
508
  for (const focusable of this.#focusables) {
505
- if (current.has(focusable)) {
506
- continue;
509
+ if (!current.has(focusable)) {
510
+ focusable.isConnected && restoreAttributes([focusable]);
511
+ this.#focusables.delete(focusable);
507
512
  }
508
- focusable.isConnected && restoreAttributes([focusable]);
509
- this.#focusables.delete(focusable);
510
513
  }
511
514
  for (const focusable of current) {
512
- if (this.#focusables.has(focusable)) {
513
- continue;
515
+ if (!this.#focusables.has(focusable)) {
516
+ this.#focusables.add(focusable);
517
+ saveAttributes([focusable], ["tabindex"]);
518
+ focusable.setAttribute("tabindex", "-1");
514
519
  }
515
- this.#focusables.add(focusable);
516
- saveAttributes([focusable], ["tabindex"]);
517
- focusable.setAttribute("tabindex", "-1");
518
520
  }
519
521
  }
520
522
  #createSentinel() {
@@ -571,7 +573,7 @@ function getActiveElement2() {
571
573
  * Lightweight DOM portal (teleport) utility with fully focus management.
572
574
  * Designed for accessible dialogs, menus, overlays, popovers.
573
575
  *
574
- * @version 1.2.13
576
+ * @version 1.2.15
575
577
  * @author Yusuke Kamiyamane
576
578
  * @license MIT
577
579
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -583,7 +585,7 @@ function getActiveElement2() {
583
585
  (**
584
586
  * Attributes Utils
585
587
  *
586
- * @version 1.1.1
588
+ * @version 1.1.2
587
589
  * @author Yusuke Kamiyamane
588
590
  * @license MIT
589
591
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -596,7 +598,7 @@ power-focusable/dist/index.js:
596
598
  * High-precision focus management utility with full composed tree support.
597
599
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
598
600
  *
599
- * @version 4.3.2
601
+ * @version 4.3.3
600
602
  * @author Yusuke Kamiyamane
601
603
  * @license MIT
602
604
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/portal",
3
- "version": "1.2.13",
3
+ "version": "1.2.15",
4
4
  "description": "Lightweight DOM portal (teleport) utility with fully focus management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -47,9 +47,9 @@
47
47
  },
48
48
  "homepage": "https://github.com/y14e/portal#readme",
49
49
  "devDependencies": {
50
- "@y14e/attributes-utils": "^1.1.1",
50
+ "@y14e/attributes-utils": "^1.1.2",
51
51
  "bun-types": "latest",
52
- "power-focusable": "^4.3.2",
52
+ "power-focusable": "^4.3.3",
53
53
  "tsup": "^8.0.0",
54
54
  "typescript": "^5.6.0"
55
55
  },