@y14e/portal 1.2.32 → 1.3.1

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
@@ -13,11 +13,11 @@ npm i @y14e/portal
13
13
  import { createPortal } from '@y14e/portal';
14
14
 
15
15
  // CDNs
16
- import { createPortal } from 'https://esm.sh/@y14e/portal@1.2.32';
16
+ import { createPortal } from 'https://esm.sh/@y14e/portal@1.3.1';
17
17
  // or
18
- import { createPortal } from 'https://cdn.jsdelivr.net/npm/@y14e/portal@1.2.32/+esm';
18
+ import { createPortal } from 'https://cdn.jsdelivr.net/npm/@y14e/portal@1.3.1/+esm';
19
19
  // or
20
- import { createPortal } from 'https://esm.unpkg.com/@y14e/portal@1.2.32';
20
+ import { createPortal } from 'https://esm.unpkg.com/@y14e/portal@1.3.1';
21
21
  ```
22
22
 
23
23
  ## 📦 APIs
@@ -27,11 +27,20 @@ import { createPortal } from 'https://esm.unpkg.com/@y14e/portal@1.2.32';
27
27
  Creates a portal and preserves keyboard focus order between the original DOM and the portal.
28
28
 
29
29
  ```ts
30
- const cleanup = createPortal(host, container);
30
+ const cleanup = createPortal(host, container, options);
31
31
  // => () => void
32
32
  //
33
33
  // host: Element
34
34
  // container (optional): Element (default: <body>)
35
+ // options (optional): PortalOptions
36
+ ```
37
+
38
+ ## 🪄 Options
39
+
40
+ ```ts
41
+ interface PortalOptions {
42
+ noInlineStyle: boolean; // default: false
43
+ }
35
44
  ```
36
45
 
37
46
  ## Demo
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // node_modules/@y14e/attributes-utils/dist/index.js
3
+ // node_modules/@y14e/attribute-util/dist/index.js
4
4
  var snapshots = /* @__PURE__ */ new WeakMap();
5
5
  function restoreAttributes(element_or_elements) {
6
6
  for (const element of Array.isArray(element_or_elements) ? element_or_elements : [element_or_elements]) {
@@ -8,22 +8,22 @@ function restoreAttributes(element_or_elements) {
8
8
  if (!snapshot) {
9
9
  continue;
10
10
  }
11
- for (const [attribute, value] of snapshot.entries()) {
12
- value === null ? element.removeAttribute(attribute) : element.setAttribute(attribute, value);
11
+ for (const [name, value] of snapshot.entries()) {
12
+ value === null ? element.removeAttribute(name) : element.setAttribute(name, value);
13
13
  }
14
14
  snapshots.delete(element);
15
15
  }
16
16
  }
17
- function saveAttributes(element_or_elements, attribute_or_attributes) {
18
- const attributes = Array.isArray(attribute_or_attributes) ? attribute_or_attributes : [attribute_or_attributes];
17
+ function saveAttributes(element_or_elements, name_or_names) {
18
+ const names = Array.isArray(name_or_names) ? name_or_names : [name_or_names];
19
19
  (Array.isArray(element_or_elements) ? element_or_elements : [element_or_elements]).forEach((element) => {
20
20
  let snapshot = snapshots.get(element);
21
21
  if (!snapshot) {
22
22
  snapshot = /* @__PURE__ */ new Map();
23
23
  snapshots.set(element, snapshot);
24
24
  }
25
- attributes.forEach((attribute) => {
26
- snapshot.set(attribute, element.getAttribute(attribute));
25
+ names.forEach((name) => {
26
+ snapshot.set(name, element.getAttribute(name));
27
27
  });
28
28
  });
29
29
  }
@@ -394,7 +394,7 @@ function isUngroupedRadio(element) {
394
394
 
395
395
  // src/index.ts
396
396
  var VISUALLY_HIDDEN_CSS = `border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; user-select: none; white-space: nowrap; width: 1px;`;
397
- function createPortal(host, container = document.body) {
397
+ function createPortal(host, container = document.body, options = {}) {
398
398
  if (!(host instanceof Element)) {
399
399
  console.warn("Invalid host element");
400
400
  return () => {
@@ -410,24 +410,31 @@ function createPortal(host, container = document.body) {
410
410
  return () => {
411
411
  };
412
412
  }
413
- const portal = new Portal(host, container);
413
+ const portal = new Portal(host, container, options);
414
414
  return () => portal.destroy();
415
415
  }
416
416
  var Portal = class {
417
417
  #host;
418
418
  #container;
419
+ #settings;
419
420
  #entranceSentinel;
420
421
  #exitSentinel;
421
422
  #focusables = /* @__PURE__ */ new Set();
422
423
  #controller = null;
423
424
  #isDestroyed = false;
424
- constructor(host, container) {
425
+ constructor(host, container, options = {}) {
425
426
  this.#host = host;
426
427
  if (!(container instanceof Element)) {
427
428
  console.warn("Invalid container element. Fallback: <body> element.");
428
429
  container = document.body;
429
430
  }
430
431
  this.#container = container;
432
+ let { noInlineStyle = false } = options;
433
+ if (typeof noInlineStyle !== "boolean") {
434
+ console.warn("Invalid noInlineStyle option. Fallback: false.");
435
+ noInlineStyle = false;
436
+ }
437
+ this.#settings = { noInlineStyle };
431
438
  this.#entranceSentinel = this.#createSentinel();
432
439
  this.#exitSentinel = this.#createSentinel();
433
440
  this.#initialize();
@@ -495,7 +502,7 @@ var Portal = class {
495
502
  return;
496
503
  }
497
504
  this.#update();
498
- const focusables = this.#getFocusables();
505
+ const focusables = getFocusables(this.#host);
499
506
  if (focusables.length) {
500
507
  const index = focusables.indexOf(active);
501
508
  if (index >= 0) {
@@ -532,7 +539,9 @@ var Portal = class {
532
539
  sentinel.setAttribute("aria-hidden", "true");
533
540
  sentinel.setAttribute("data-portal-sentinel", "");
534
541
  sentinel.setAttribute("tabindex", "0");
535
- sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
542
+ if (!this.#settings.noInlineStyle) {
543
+ sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
544
+ }
536
545
  return sentinel;
537
546
  }
538
547
  #focusSentinel(isPrevious) {
@@ -568,7 +577,7 @@ function containsComposed2(container, element) {
568
577
  * Lightweight DOM portal (teleport) utility with fully focus management.
569
578
  * Designed for accessible dialogs, menus, overlays, popovers.
570
579
  *
571
- * @version 1.2.32
580
+ * @version 1.3.1
572
581
  * @author Yusuke Kamiyamane
573
582
  * @license MIT
574
583
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -576,15 +585,15 @@ function containsComposed2(container, element) {
576
585
  */
577
586
  /*! Bundled license information:
578
587
 
579
- @y14e/attributes-utils/dist/index.js:
588
+ @y14e/attribute-util/dist/index.js:
580
589
  (**
581
- * Attributes Utils
590
+ * Attribute Util
582
591
  *
583
- * @version 1.1.4
592
+ * @version 2.0.0
584
593
  * @author Yusuke Kamiyamane
585
594
  * @license MIT
586
595
  * @copyright Copyright (c) Yusuke Kamiyamane
587
- * @see {@link https://github.com/y14e/attributes-utils}
596
+ * @see {@link https://github.com/y14e/attribute-util}
588
597
  *)
589
598
 
590
599
  power-focusable/dist/index.js:
@@ -593,7 +602,7 @@ power-focusable/dist/index.js:
593
602
  * High-precision focus management utility with full composed tree support.
594
603
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
595
604
  *
596
- * @version 4.3.21
605
+ * @version 4.3.23
597
606
  * @author Yusuke Kamiyamane
598
607
  * @license MIT
599
608
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -1,4 +1,4 @@
1
- // node_modules/@y14e/attributes-utils/dist/index.js
1
+ // node_modules/@y14e/attribute-util/dist/index.js
2
2
  var snapshots = /* @__PURE__ */ new WeakMap();
3
3
  function restoreAttributes(element_or_elements) {
4
4
  for (const element of Array.isArray(element_or_elements) ? element_or_elements : [element_or_elements]) {
@@ -6,22 +6,22 @@ function restoreAttributes(element_or_elements) {
6
6
  if (!snapshot) {
7
7
  continue;
8
8
  }
9
- for (const [attribute, value] of snapshot.entries()) {
10
- value === null ? element.removeAttribute(attribute) : element.setAttribute(attribute, value);
9
+ for (const [name, value] of snapshot.entries()) {
10
+ value === null ? element.removeAttribute(name) : element.setAttribute(name, value);
11
11
  }
12
12
  snapshots.delete(element);
13
13
  }
14
14
  }
15
- function saveAttributes(element_or_elements, attribute_or_attributes) {
16
- const attributes = Array.isArray(attribute_or_attributes) ? attribute_or_attributes : [attribute_or_attributes];
15
+ function saveAttributes(element_or_elements, name_or_names) {
16
+ const names = Array.isArray(name_or_names) ? name_or_names : [name_or_names];
17
17
  (Array.isArray(element_or_elements) ? element_or_elements : [element_or_elements]).forEach((element) => {
18
18
  let snapshot = snapshots.get(element);
19
19
  if (!snapshot) {
20
20
  snapshot = /* @__PURE__ */ new Map();
21
21
  snapshots.set(element, snapshot);
22
22
  }
23
- attributes.forEach((attribute) => {
24
- snapshot.set(attribute, element.getAttribute(attribute));
23
+ names.forEach((name) => {
24
+ snapshot.set(name, element.getAttribute(name));
25
25
  });
26
26
  });
27
27
  }
@@ -392,7 +392,7 @@ function isUngroupedRadio(element) {
392
392
 
393
393
  // src/index.ts
394
394
  var VISUALLY_HIDDEN_CSS = `border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; user-select: none; white-space: nowrap; width: 1px;`;
395
- function createPortal(host, container = document.body) {
395
+ function createPortal(host, container = document.body, options = {}) {
396
396
  if (!(host instanceof Element)) {
397
397
  console.warn("Invalid host element");
398
398
  return () => {
@@ -408,24 +408,31 @@ function createPortal(host, container = document.body) {
408
408
  return () => {
409
409
  };
410
410
  }
411
- const portal = new Portal(host, container);
411
+ const portal = new Portal(host, container, options);
412
412
  return () => portal.destroy();
413
413
  }
414
414
  var Portal = class {
415
415
  #host;
416
416
  #container;
417
+ #settings;
417
418
  #entranceSentinel;
418
419
  #exitSentinel;
419
420
  #focusables = /* @__PURE__ */ new Set();
420
421
  #controller = null;
421
422
  #isDestroyed = false;
422
- constructor(host, container) {
423
+ constructor(host, container, options = {}) {
423
424
  this.#host = host;
424
425
  if (!(container instanceof Element)) {
425
426
  console.warn("Invalid container element. Fallback: <body> element.");
426
427
  container = document.body;
427
428
  }
428
429
  this.#container = container;
430
+ let { noInlineStyle = false } = options;
431
+ if (typeof noInlineStyle !== "boolean") {
432
+ console.warn("Invalid noInlineStyle option. Fallback: false.");
433
+ noInlineStyle = false;
434
+ }
435
+ this.#settings = { noInlineStyle };
429
436
  this.#entranceSentinel = this.#createSentinel();
430
437
  this.#exitSentinel = this.#createSentinel();
431
438
  this.#initialize();
@@ -493,7 +500,7 @@ var Portal = class {
493
500
  return;
494
501
  }
495
502
  this.#update();
496
- const focusables = this.#getFocusables();
503
+ const focusables = getFocusables(this.#host);
497
504
  if (focusables.length) {
498
505
  const index = focusables.indexOf(active);
499
506
  if (index >= 0) {
@@ -530,7 +537,9 @@ var Portal = class {
530
537
  sentinel.setAttribute("aria-hidden", "true");
531
538
  sentinel.setAttribute("data-portal-sentinel", "");
532
539
  sentinel.setAttribute("tabindex", "0");
533
- sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
540
+ if (!this.#settings.noInlineStyle) {
541
+ sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
542
+ }
534
543
  return sentinel;
535
544
  }
536
545
  #focusSentinel(isPrevious) {
@@ -566,7 +575,7 @@ function containsComposed2(container, element) {
566
575
  * Lightweight DOM portal (teleport) utility with fully focus management.
567
576
  * Designed for accessible dialogs, menus, overlays, popovers.
568
577
  *
569
- * @version 1.2.32
578
+ * @version 1.3.1
570
579
  * @author Yusuke Kamiyamane
571
580
  * @license MIT
572
581
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -574,15 +583,15 @@ function containsComposed2(container, element) {
574
583
  */
575
584
  /*! Bundled license information:
576
585
 
577
- @y14e/attributes-utils/dist/index.js:
586
+ @y14e/attribute-util/dist/index.js:
578
587
  (**
579
- * Attributes Utils
588
+ * Attribute Util
580
589
  *
581
- * @version 1.1.4
590
+ * @version 2.0.0
582
591
  * @author Yusuke Kamiyamane
583
592
  * @license MIT
584
593
  * @copyright Copyright (c) Yusuke Kamiyamane
585
- * @see {@link https://github.com/y14e/attributes-utils}
594
+ * @see {@link https://github.com/y14e/attribute-util}
586
595
  *)
587
596
 
588
597
  power-focusable/dist/index.js:
@@ -591,7 +600,7 @@ power-focusable/dist/index.js:
591
600
  * High-precision focus management utility with full composed tree support.
592
601
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
593
602
  *
594
- * @version 4.3.21
603
+ * @version 4.3.23
595
604
  * @author Yusuke Kamiyamane
596
605
  * @license MIT
597
606
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.cjs CHANGED
@@ -1,11 +1,32 @@
1
1
  'use strict';
2
2
 
3
- var attributesUtils = require('@y14e/attributes-utils');
4
- var powerFocusable = require('power-focusable');
3
+ var util = require('@y14e/attribute-util');
4
+ var pf = require('power-focusable');
5
+
6
+ function _interopNamespace(e) {
7
+ if (e && e.__esModule) return e;
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var util__namespace = /*#__PURE__*/_interopNamespace(util);
25
+ var pf__namespace = /*#__PURE__*/_interopNamespace(pf);
5
26
 
6
27
  // src/index.ts
7
28
  var VISUALLY_HIDDEN_CSS = `border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; user-select: none; white-space: nowrap; width: 1px;`;
8
- function createPortal(host, container = document.body) {
29
+ function createPortal(host, container = document.body, options = {}) {
9
30
  if (!(host instanceof Element)) {
10
31
  console.warn("Invalid host element");
11
32
  return () => {
@@ -21,24 +42,31 @@ function createPortal(host, container = document.body) {
21
42
  return () => {
22
43
  };
23
44
  }
24
- const portal = new Portal(host, container);
45
+ const portal = new Portal(host, container, options);
25
46
  return () => portal.destroy();
26
47
  }
27
48
  var Portal = class {
28
49
  #host;
29
50
  #container;
51
+ #settings;
30
52
  #entranceSentinel;
31
53
  #exitSentinel;
32
54
  #focusables = /* @__PURE__ */ new Set();
33
55
  #controller = null;
34
56
  #isDestroyed = false;
35
- constructor(host, container) {
57
+ constructor(host, container, options = {}) {
36
58
  this.#host = host;
37
59
  if (!(container instanceof Element)) {
38
60
  console.warn("Invalid container element. Fallback: <body> element.");
39
61
  container = document.body;
40
62
  }
41
63
  this.#container = container;
64
+ let { noInlineStyle = false } = options;
65
+ if (typeof noInlineStyle !== "boolean") {
66
+ console.warn("Invalid noInlineStyle option. Fallback: false.");
67
+ noInlineStyle = false;
68
+ }
69
+ this.#settings = { noInlineStyle };
42
70
  this.#entranceSentinel = this.#createSentinel();
43
71
  this.#exitSentinel = this.#createSentinel();
44
72
  this.#initialize();
@@ -50,7 +78,7 @@ var Portal = class {
50
78
  this.#isDestroyed = true;
51
79
  this.#controller?.abort();
52
80
  this.#controller = null;
53
- attributesUtils.restoreAttributes([...this.#focusables]);
81
+ util__namespace.restoreAttributes([...this.#focusables]);
54
82
  this.#focusables.clear();
55
83
  this.#exitSentinel.after(this.#host);
56
84
  this.#entranceSentinel.remove();
@@ -82,7 +110,7 @@ var Portal = class {
82
110
  }
83
111
  this.#update();
84
112
  const first = [...this.#focusables][0];
85
- first ? powerFocusable.focusElement(first) : this.#moveFocus("next");
113
+ first ? pf__namespace.focusElement(first) : this.#moveFocus("next");
86
114
  } else {
87
115
  if (this.#host.contains(previous)) {
88
116
  this.#moveFocus("next");
@@ -90,7 +118,7 @@ var Portal = class {
90
118
  }
91
119
  this.#update();
92
120
  const last = [...this.#focusables].at(-1);
93
- last ? powerFocusable.focusElement(last) : this.#moveFocus("previous");
121
+ last ? pf__namespace.focusElement(last) : this.#moveFocus("previous");
94
122
  }
95
123
  };
96
124
  #onKeyDown = (event) => {
@@ -101,18 +129,18 @@ var Portal = class {
101
129
  if (key !== "Tab" || altKey || ctrlKey || metaKey) {
102
130
  return;
103
131
  }
104
- const active = powerFocusable.getActiveElement();
132
+ const active = pf__namespace.getActiveElement();
105
133
  if (!(active instanceof Element)) {
106
134
  return;
107
135
  }
108
136
  this.#update();
109
- const focusables = this.#getFocusables();
137
+ const focusables = pf__namespace.getFocusables(this.#host);
110
138
  if (focusables.length) {
111
139
  const index = focusables.indexOf(active);
112
140
  if (index >= 0) {
113
141
  event.preventDefault();
114
142
  const focusable = focusables[index + (shiftKey ? -1 : 1)];
115
- focusable ? powerFocusable.focusElement(focusable) : this.#focusSentinel(shiftKey);
143
+ focusable ? pf__namespace.focusElement(focusable) : this.#focusSentinel(shiftKey);
116
144
  }
117
145
  } else {
118
146
  event.preventDefault();
@@ -122,18 +150,18 @@ var Portal = class {
122
150
  #update() {
123
151
  const current = /* @__PURE__ */ new Set([
124
152
  ...this.#getFocusables(),
125
- ...powerFocusable.getFocusables(this.#host, { composed: true })
153
+ ...pf__namespace.getFocusables(this.#host, { composed: true })
126
154
  ]);
127
155
  for (const focusable of this.#focusables) {
128
156
  if (!current.has(focusable)) {
129
- attributesUtils.restoreAttributes(focusable);
157
+ util__namespace.restoreAttributes(focusable);
130
158
  this.#focusables.delete(focusable);
131
159
  }
132
160
  }
133
161
  for (const focusable of current) {
134
162
  if (!this.#focusables.has(focusable)) {
135
163
  this.#focusables.add(focusable);
136
- attributesUtils.saveAttributes(focusable, "tabindex");
164
+ util__namespace.saveAttributes(focusable, "tabindex");
137
165
  focusable.setAttribute("tabindex", "-1");
138
166
  }
139
167
  }
@@ -143,14 +171,16 @@ var Portal = class {
143
171
  sentinel.setAttribute("aria-hidden", "true");
144
172
  sentinel.setAttribute("data-portal-sentinel", "");
145
173
  sentinel.setAttribute("tabindex", "0");
146
- sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
174
+ if (!this.#settings.noInlineStyle) {
175
+ sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
176
+ }
147
177
  return sentinel;
148
178
  }
149
179
  #focusSentinel(isPrevious) {
150
180
  (isPrevious ? this.#entranceSentinel : this.#exitSentinel).focus();
151
181
  }
152
182
  #getFocusables() {
153
- return powerFocusable.getFocusables(this.#host, {
183
+ return pf__namespace.getFocusables(this.#host, {
154
184
  composed: true,
155
185
  include: (element) => this.#focusables.has(element)
156
186
  });
@@ -160,8 +190,8 @@ var Portal = class {
160
190
  anchor: direction === "previous" ? this.#entranceSentinel : this.#exitSentinel,
161
191
  composed: true
162
192
  };
163
- const focusable = direction === "previous" ? powerFocusable.getPreviousFocusable(document.body, options) : powerFocusable.getNextFocusable(document.body, options);
164
- focusable && powerFocusable.focusElement(focusable);
193
+ const focusable = direction === "previous" ? pf__namespace.getPreviousFocusable(document.body, options) : pf__namespace.getNextFocusable(document.body, options);
194
+ focusable && pf__namespace.focusElement(focusable);
165
195
  }
166
196
  };
167
197
  function containsComposed(container, element) {
@@ -179,7 +209,7 @@ function containsComposed(container, element) {
179
209
  * Lightweight DOM portal (teleport) utility with fully focus management.
180
210
  * Designed for accessible dialogs, menus, overlays, popovers.
181
211
  *
182
- * @version 1.2.32
212
+ * @version 1.3.1
183
213
  * @author Yusuke Kamiyamane
184
214
  * @license MIT
185
215
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,12 +3,15 @@
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.32
6
+ * @version 1.3.1
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
10
10
  * @see {@link https://github.com/y14e/portal}
11
11
  */
12
- declare function createPortal(host: Element, container?: HTMLElement): () => void;
12
+ interface PortalOptions {
13
+ noInlineStyle: boolean;
14
+ }
15
+ declare function createPortal(host: Element, container?: HTMLElement, options?: Partial<PortalOptions>): () => void;
13
16
 
14
- export { createPortal };
17
+ export { type PortalOptions, createPortal };
package/dist/index.d.ts CHANGED
@@ -3,12 +3,15 @@
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.32
6
+ * @version 1.3.1
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
10
10
  * @see {@link https://github.com/y14e/portal}
11
11
  */
12
- declare function createPortal(host: Element, container?: HTMLElement): () => void;
12
+ interface PortalOptions {
13
+ noInlineStyle: boolean;
14
+ }
15
+ declare function createPortal(host: Element, container?: HTMLElement, options?: Partial<PortalOptions>): () => void;
13
16
 
14
- export { createPortal };
17
+ export { type PortalOptions, createPortal };
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { restoreAttributes, saveAttributes } from '@y14e/attributes-utils';
2
- import { focusElement, getActiveElement, getFocusables, getPreviousFocusable, getNextFocusable } from 'power-focusable';
1
+ import * as util from '@y14e/attribute-util';
2
+ import * as pf from 'power-focusable';
3
3
 
4
4
  // src/index.ts
5
5
  var VISUALLY_HIDDEN_CSS = `border: 0; clip: rect(0, 0, 0, 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; user-select: none; white-space: nowrap; width: 1px;`;
6
- function createPortal(host, container = document.body) {
6
+ function createPortal(host, container = document.body, options = {}) {
7
7
  if (!(host instanceof Element)) {
8
8
  console.warn("Invalid host element");
9
9
  return () => {
@@ -19,24 +19,31 @@ function createPortal(host, container = document.body) {
19
19
  return () => {
20
20
  };
21
21
  }
22
- const portal = new Portal(host, container);
22
+ const portal = new Portal(host, container, options);
23
23
  return () => portal.destroy();
24
24
  }
25
25
  var Portal = class {
26
26
  #host;
27
27
  #container;
28
+ #settings;
28
29
  #entranceSentinel;
29
30
  #exitSentinel;
30
31
  #focusables = /* @__PURE__ */ new Set();
31
32
  #controller = null;
32
33
  #isDestroyed = false;
33
- constructor(host, container) {
34
+ constructor(host, container, options = {}) {
34
35
  this.#host = host;
35
36
  if (!(container instanceof Element)) {
36
37
  console.warn("Invalid container element. Fallback: <body> element.");
37
38
  container = document.body;
38
39
  }
39
40
  this.#container = container;
41
+ let { noInlineStyle = false } = options;
42
+ if (typeof noInlineStyle !== "boolean") {
43
+ console.warn("Invalid noInlineStyle option. Fallback: false.");
44
+ noInlineStyle = false;
45
+ }
46
+ this.#settings = { noInlineStyle };
40
47
  this.#entranceSentinel = this.#createSentinel();
41
48
  this.#exitSentinel = this.#createSentinel();
42
49
  this.#initialize();
@@ -48,7 +55,7 @@ var Portal = class {
48
55
  this.#isDestroyed = true;
49
56
  this.#controller?.abort();
50
57
  this.#controller = null;
51
- restoreAttributes([...this.#focusables]);
58
+ util.restoreAttributes([...this.#focusables]);
52
59
  this.#focusables.clear();
53
60
  this.#exitSentinel.after(this.#host);
54
61
  this.#entranceSentinel.remove();
@@ -80,7 +87,7 @@ var Portal = class {
80
87
  }
81
88
  this.#update();
82
89
  const first = [...this.#focusables][0];
83
- first ? focusElement(first) : this.#moveFocus("next");
90
+ first ? pf.focusElement(first) : this.#moveFocus("next");
84
91
  } else {
85
92
  if (this.#host.contains(previous)) {
86
93
  this.#moveFocus("next");
@@ -88,7 +95,7 @@ var Portal = class {
88
95
  }
89
96
  this.#update();
90
97
  const last = [...this.#focusables].at(-1);
91
- last ? focusElement(last) : this.#moveFocus("previous");
98
+ last ? pf.focusElement(last) : this.#moveFocus("previous");
92
99
  }
93
100
  };
94
101
  #onKeyDown = (event) => {
@@ -99,18 +106,18 @@ var Portal = class {
99
106
  if (key !== "Tab" || altKey || ctrlKey || metaKey) {
100
107
  return;
101
108
  }
102
- const active = getActiveElement();
109
+ const active = pf.getActiveElement();
103
110
  if (!(active instanceof Element)) {
104
111
  return;
105
112
  }
106
113
  this.#update();
107
- const focusables = this.#getFocusables();
114
+ const focusables = pf.getFocusables(this.#host);
108
115
  if (focusables.length) {
109
116
  const index = focusables.indexOf(active);
110
117
  if (index >= 0) {
111
118
  event.preventDefault();
112
119
  const focusable = focusables[index + (shiftKey ? -1 : 1)];
113
- focusable ? focusElement(focusable) : this.#focusSentinel(shiftKey);
120
+ focusable ? pf.focusElement(focusable) : this.#focusSentinel(shiftKey);
114
121
  }
115
122
  } else {
116
123
  event.preventDefault();
@@ -120,18 +127,18 @@ var Portal = class {
120
127
  #update() {
121
128
  const current = /* @__PURE__ */ new Set([
122
129
  ...this.#getFocusables(),
123
- ...getFocusables(this.#host, { composed: true })
130
+ ...pf.getFocusables(this.#host, { composed: true })
124
131
  ]);
125
132
  for (const focusable of this.#focusables) {
126
133
  if (!current.has(focusable)) {
127
- restoreAttributes(focusable);
134
+ util.restoreAttributes(focusable);
128
135
  this.#focusables.delete(focusable);
129
136
  }
130
137
  }
131
138
  for (const focusable of current) {
132
139
  if (!this.#focusables.has(focusable)) {
133
140
  this.#focusables.add(focusable);
134
- saveAttributes(focusable, "tabindex");
141
+ util.saveAttributes(focusable, "tabindex");
135
142
  focusable.setAttribute("tabindex", "-1");
136
143
  }
137
144
  }
@@ -141,14 +148,16 @@ var Portal = class {
141
148
  sentinel.setAttribute("aria-hidden", "true");
142
149
  sentinel.setAttribute("data-portal-sentinel", "");
143
150
  sentinel.setAttribute("tabindex", "0");
144
- sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
151
+ if (!this.#settings.noInlineStyle) {
152
+ sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
153
+ }
145
154
  return sentinel;
146
155
  }
147
156
  #focusSentinel(isPrevious) {
148
157
  (isPrevious ? this.#entranceSentinel : this.#exitSentinel).focus();
149
158
  }
150
159
  #getFocusables() {
151
- return getFocusables(this.#host, {
160
+ return pf.getFocusables(this.#host, {
152
161
  composed: true,
153
162
  include: (element) => this.#focusables.has(element)
154
163
  });
@@ -158,8 +167,8 @@ var Portal = class {
158
167
  anchor: direction === "previous" ? this.#entranceSentinel : this.#exitSentinel,
159
168
  composed: true
160
169
  };
161
- const focusable = direction === "previous" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
162
- focusable && focusElement(focusable);
170
+ const focusable = direction === "previous" ? pf.getPreviousFocusable(document.body, options) : pf.getNextFocusable(document.body, options);
171
+ focusable && pf.focusElement(focusable);
163
172
  }
164
173
  };
165
174
  function containsComposed(container, element) {
@@ -177,7 +186,7 @@ function containsComposed(container, element) {
177
186
  * Lightweight DOM portal (teleport) utility with fully focus management.
178
187
  * Designed for accessible dialogs, menus, overlays, popovers.
179
188
  *
180
- * @version 1.2.32
189
+ * @version 1.3.1
181
190
  * @author Yusuke Kamiyamane
182
191
  * @license MIT
183
192
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/portal",
3
- "version": "1.2.32",
3
+ "version": "1.3.1",
4
4
  "description": "Lightweight DOM portal (teleport) utility with fully focus management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -55,7 +55,7 @@
55
55
  "node": ">=18"
56
56
  },
57
57
  "dependencies": {
58
- "@y14e/attributes-utils": "^1.1.4",
59
- "power-focusable": "^4.3.21"
58
+ "@y14e/attribute-util": "^2.0.0",
59
+ "power-focusable": "^4.3.23"
60
60
  }
61
61
  }