@y14e/portal 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -30,9 +30,9 @@ import { createPortal } from 'https://unpkg.com/@y14e/portal/dist/index.js';
30
30
  Creates a portal and preserves keyboard focus order between the original DOM and the portal.
31
31
 
32
32
  ```ts
33
- const cleanup = createPortal(source, target);
33
+ const cleanup = createPortal(host, container);
34
34
  // => () => void
35
35
  //
36
- // source: Element
37
- // target (optional): Element (default: <body>)
36
+ // host: Element
37
+ // container (optional): Element (default: <body>)
38
38
  ```
package/dist/index.cjs CHANGED
@@ -254,28 +254,28 @@ function isUngroupedRadio(element) {
254
254
 
255
255
  // src/index.ts
256
256
  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;`;
257
- function createPortal(source, target = document.body) {
258
- if (!(source instanceof Element)) {
259
- throw new Error("Invalid source element");
257
+ function createPortal(host, container = document.body) {
258
+ if (!(host instanceof Element)) {
259
+ throw new Error("Invalid host element");
260
260
  }
261
- if (!(target instanceof Element)) {
262
- console.warn("Invalid target element. Fallback: <body> element.");
263
- target = document.body;
261
+ if (!(container instanceof Element)) {
262
+ console.warn("Invalid container element. Fallback: <body> element.");
263
+ container = document.body;
264
264
  }
265
- const portal = new Portal(source, target);
265
+ const portal = new Portal(host, container);
266
266
  return () => portal.destroy();
267
267
  }
268
268
  var Portal = class {
269
- #source;
270
- #target;
269
+ #host;
270
+ #container;
271
271
  #entranceSentinel;
272
272
  #exitSentinel;
273
273
  #tabIndexes = /* @__PURE__ */ new WeakMap();
274
274
  #controller = null;
275
275
  #isDestroyed = false;
276
- constructor(source, target) {
277
- this.#source = source;
278
- this.#target = target;
276
+ constructor(host, container) {
277
+ this.#host = host;
278
+ this.#container = container;
279
279
  this.#entranceSentinel = this.#createSentinel();
280
280
  this.#exitSentinel = this.#createSentinel();
281
281
  this.#initialize();
@@ -292,24 +292,23 @@ var Portal = class {
292
292
  return;
293
293
  }
294
294
  const index = this.#tabIndexes.get(focusable);
295
- if (index === null) {
295
+ if (index == null) {
296
296
  focusable.removeAttribute("tabindex");
297
297
  } else {
298
- focusable.setAttribute("tabindex", String(index));
298
+ focusable.setAttribute("tabindex", index);
299
299
  }
300
300
  });
301
- this.#exitSentinel.after(this.#source);
301
+ this.#exitSentinel.after(this.#host);
302
302
  this.#entranceSentinel.remove();
303
303
  this.#exitSentinel.remove();
304
- this.#source.removeAttribute("data-portal");
304
+ this.#host.removeAttribute("data-portal");
305
305
  }
306
306
  #initialize() {
307
- this.#source.before(this.#entranceSentinel);
307
+ this.#host.before(this.#entranceSentinel);
308
308
  this.#entranceSentinel.after(this.#exitSentinel);
309
- this.#target.append(this.#source);
309
+ this.#container.append(this.#host);
310
310
  this.#getFocusables().forEach((focusable) => {
311
- const index = focusable.getAttribute("tabindex")?.trim();
312
- this.#tabIndexes.set(focusable, index === null ? null : Number(index));
311
+ this.#tabIndexes.set(focusable, focusable.getAttribute("tabindex"));
313
312
  focusable.setAttribute("tabindex", "-1");
314
313
  });
315
314
  this.#controller = new AbortController();
@@ -322,7 +321,7 @@ var Portal = class {
322
321
  capture: true,
323
322
  signal
324
323
  });
325
- this.#source.setAttribute("data-portal", "");
324
+ this.#host.setAttribute("data-portal", "");
326
325
  }
327
326
  #onFocusIn = (event) => {
328
327
  const current = event.target;
@@ -331,15 +330,15 @@ var Portal = class {
331
330
  return;
332
331
  }
333
332
  if (current === this.#entranceSentinel) {
334
- if (this.#source.contains(before)) {
335
- this.#focusOutside("backward");
333
+ if (this.#host.contains(before)) {
334
+ this.#moveFocusOutside("previous");
336
335
  } else {
337
336
  const first = this.#getFocusables()[0];
338
337
  first && focus(first);
339
338
  }
340
339
  } else if (current === this.#exitSentinel) {
341
- if (this.#source.contains(before)) {
342
- this.#focusOutside("forward");
340
+ if (this.#host.contains(before)) {
341
+ this.#moveFocusOutside("next");
343
342
  } else {
344
343
  const last = this.#getFocusables().at(-1);
345
344
  last && focus(last);
@@ -354,7 +353,7 @@ var Portal = class {
354
353
  if (!(active instanceof Element)) {
355
354
  return;
356
355
  }
357
- if (!this.#source.contains(active)) {
356
+ if (!this.#host.contains(active)) {
358
357
  return;
359
358
  }
360
359
  if (!this.#getFocusables().length) {
@@ -380,20 +379,20 @@ var Portal = class {
380
379
  sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
381
380
  return sentinel;
382
381
  }
383
- #focusOutside(direction) {
384
- const options = {
385
- anchor: direction === "backward" ? this.#entranceSentinel : this.#exitSentinel,
386
- composed: true
387
- };
388
- const focusable = direction === "backward" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
389
- focusable && focus(focusable);
390
- }
391
382
  #getFocusables() {
392
- return getFocusables(this.#source, {
383
+ return getFocusables(this.#host, {
393
384
  composed: true,
394
385
  include: (element) => this.#tabIndexes.has(element)
395
386
  });
396
387
  }
388
+ #moveFocusOutside(direction) {
389
+ const options = {
390
+ anchor: direction === "previous" ? this.#entranceSentinel : this.#exitSentinel,
391
+ composed: true
392
+ };
393
+ const focusable = direction === "previous" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
394
+ focusable && focus(focusable);
395
+ }
397
396
  };
398
397
  function focus(element) {
399
398
  "focus" in element && typeof element.focus === "function" && element.focus();
@@ -410,7 +409,7 @@ function getActiveElement2() {
410
409
  * Lightweight DOM portal (teleport) utility with fully focus management.
411
410
  * Designed for accessible dialogs, menus, overlays, popovers.
412
411
  *
413
- * @version 0.1.0
412
+ * @version 1.0.0
414
413
  * @author Yusuke Kamiyamane
415
414
  * @license MIT
416
415
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,16 +3,16 @@
3
3
  * Lightweight DOM portal (teleport) utility with fully focus management.
4
4
  * Designed for accessible dialogs, menus, overlays, popovers.
5
5
  *
6
- * @version 0.1.0
6
+ * @version 1.0.0
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(source: Element, target?: HTMLElement): () => void;
12
+ declare function createPortal(host: Element, container?: HTMLElement): () => void;
13
13
  declare class Portal {
14
14
  #private;
15
- constructor(source: Element, target: Element);
15
+ constructor(host: Element, container: Element);
16
16
  destroy(): void;
17
17
  }
18
18
 
package/dist/index.d.ts CHANGED
@@ -3,16 +3,16 @@
3
3
  * Lightweight DOM portal (teleport) utility with fully focus management.
4
4
  * Designed for accessible dialogs, menus, overlays, popovers.
5
5
  *
6
- * @version 0.1.0
6
+ * @version 1.0.0
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(source: Element, target?: HTMLElement): () => void;
12
+ declare function createPortal(host: Element, container?: HTMLElement): () => void;
13
13
  declare class Portal {
14
14
  #private;
15
- constructor(source: Element, target: Element);
15
+ constructor(host: Element, container: Element);
16
16
  destroy(): void;
17
17
  }
18
18
 
package/dist/index.js CHANGED
@@ -252,28 +252,28 @@ function isUngroupedRadio(element) {
252
252
 
253
253
  // src/index.ts
254
254
  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;`;
255
- function createPortal(source, target = document.body) {
256
- if (!(source instanceof Element)) {
257
- throw new Error("Invalid source element");
255
+ function createPortal(host, container = document.body) {
256
+ if (!(host instanceof Element)) {
257
+ throw new Error("Invalid host element");
258
258
  }
259
- if (!(target instanceof Element)) {
260
- console.warn("Invalid target element. Fallback: <body> element.");
261
- target = document.body;
259
+ if (!(container instanceof Element)) {
260
+ console.warn("Invalid container element. Fallback: <body> element.");
261
+ container = document.body;
262
262
  }
263
- const portal = new Portal(source, target);
263
+ const portal = new Portal(host, container);
264
264
  return () => portal.destroy();
265
265
  }
266
266
  var Portal = class {
267
- #source;
268
- #target;
267
+ #host;
268
+ #container;
269
269
  #entranceSentinel;
270
270
  #exitSentinel;
271
271
  #tabIndexes = /* @__PURE__ */ new WeakMap();
272
272
  #controller = null;
273
273
  #isDestroyed = false;
274
- constructor(source, target) {
275
- this.#source = source;
276
- this.#target = target;
274
+ constructor(host, container) {
275
+ this.#host = host;
276
+ this.#container = container;
277
277
  this.#entranceSentinel = this.#createSentinel();
278
278
  this.#exitSentinel = this.#createSentinel();
279
279
  this.#initialize();
@@ -290,24 +290,23 @@ var Portal = class {
290
290
  return;
291
291
  }
292
292
  const index = this.#tabIndexes.get(focusable);
293
- if (index === null) {
293
+ if (index == null) {
294
294
  focusable.removeAttribute("tabindex");
295
295
  } else {
296
- focusable.setAttribute("tabindex", String(index));
296
+ focusable.setAttribute("tabindex", index);
297
297
  }
298
298
  });
299
- this.#exitSentinel.after(this.#source);
299
+ this.#exitSentinel.after(this.#host);
300
300
  this.#entranceSentinel.remove();
301
301
  this.#exitSentinel.remove();
302
- this.#source.removeAttribute("data-portal");
302
+ this.#host.removeAttribute("data-portal");
303
303
  }
304
304
  #initialize() {
305
- this.#source.before(this.#entranceSentinel);
305
+ this.#host.before(this.#entranceSentinel);
306
306
  this.#entranceSentinel.after(this.#exitSentinel);
307
- this.#target.append(this.#source);
307
+ this.#container.append(this.#host);
308
308
  this.#getFocusables().forEach((focusable) => {
309
- const index = focusable.getAttribute("tabindex")?.trim();
310
- this.#tabIndexes.set(focusable, index === null ? null : Number(index));
309
+ this.#tabIndexes.set(focusable, focusable.getAttribute("tabindex"));
311
310
  focusable.setAttribute("tabindex", "-1");
312
311
  });
313
312
  this.#controller = new AbortController();
@@ -320,7 +319,7 @@ var Portal = class {
320
319
  capture: true,
321
320
  signal
322
321
  });
323
- this.#source.setAttribute("data-portal", "");
322
+ this.#host.setAttribute("data-portal", "");
324
323
  }
325
324
  #onFocusIn = (event) => {
326
325
  const current = event.target;
@@ -329,15 +328,15 @@ var Portal = class {
329
328
  return;
330
329
  }
331
330
  if (current === this.#entranceSentinel) {
332
- if (this.#source.contains(before)) {
333
- this.#focusOutside("backward");
331
+ if (this.#host.contains(before)) {
332
+ this.#moveFocusOutside("previous");
334
333
  } else {
335
334
  const first = this.#getFocusables()[0];
336
335
  first && focus(first);
337
336
  }
338
337
  } else if (current === this.#exitSentinel) {
339
- if (this.#source.contains(before)) {
340
- this.#focusOutside("forward");
338
+ if (this.#host.contains(before)) {
339
+ this.#moveFocusOutside("next");
341
340
  } else {
342
341
  const last = this.#getFocusables().at(-1);
343
342
  last && focus(last);
@@ -352,7 +351,7 @@ var Portal = class {
352
351
  if (!(active instanceof Element)) {
353
352
  return;
354
353
  }
355
- if (!this.#source.contains(active)) {
354
+ if (!this.#host.contains(active)) {
356
355
  return;
357
356
  }
358
357
  if (!this.#getFocusables().length) {
@@ -378,20 +377,20 @@ var Portal = class {
378
377
  sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
379
378
  return sentinel;
380
379
  }
381
- #focusOutside(direction) {
382
- const options = {
383
- anchor: direction === "backward" ? this.#entranceSentinel : this.#exitSentinel,
384
- composed: true
385
- };
386
- const focusable = direction === "backward" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
387
- focusable && focus(focusable);
388
- }
389
380
  #getFocusables() {
390
- return getFocusables(this.#source, {
381
+ return getFocusables(this.#host, {
391
382
  composed: true,
392
383
  include: (element) => this.#tabIndexes.has(element)
393
384
  });
394
385
  }
386
+ #moveFocusOutside(direction) {
387
+ const options = {
388
+ anchor: direction === "previous" ? this.#entranceSentinel : this.#exitSentinel,
389
+ composed: true
390
+ };
391
+ const focusable = direction === "previous" ? getPreviousFocusable(document.body, options) : getNextFocusable(document.body, options);
392
+ focusable && focus(focusable);
393
+ }
395
394
  };
396
395
  function focus(element) {
397
396
  "focus" in element && typeof element.focus === "function" && element.focus();
@@ -408,7 +407,7 @@ function getActiveElement2() {
408
407
  * Lightweight DOM portal (teleport) utility with fully focus management.
409
408
  * Designed for accessible dialogs, menus, overlays, popovers.
410
409
  *
411
- * @version 0.1.0
410
+ * @version 1.0.0
412
411
  * @author Yusuke Kamiyamane
413
412
  * @license MIT
414
413
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/portal",
3
- "version": "0.1.0",
3
+ "version": "1.0.0",
4
4
  "description": "Lightweight DOM portal (teleport) utility with fully focus management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",