@y14e/portal 1.0.1 → 1.0.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/dist/index.cjs +38 -17
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +38 -17
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -91,22 +91,25 @@ function isFocusable(element) {
|
|
|
91
91
|
return true;
|
|
92
92
|
}
|
|
93
93
|
function getRelativeFocusable(container, offset, options) {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
94
|
+
let anchor = options.anchor ?? getActiveElement();
|
|
95
|
+
const { composed = false, filter, include, wrap = false } = options;
|
|
96
|
+
if (!(anchor instanceof Element)) {
|
|
97
|
+
const active = getActiveElement();
|
|
98
|
+
if (active instanceof Element) {
|
|
99
|
+
console.warn("Invalid anchor element. Fallback: active element.");
|
|
100
|
+
anchor = active;
|
|
101
|
+
} else {
|
|
102
|
+
console.warn("Invalid anchor element");
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
105
|
}
|
|
106
|
-
if (!
|
|
106
|
+
if (!containsComposed(container, anchor)) {
|
|
107
|
+
console.warn("Mismatch elements");
|
|
107
108
|
return null;
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
+
const focusables = getFocusables(container, { composed, filter, include });
|
|
111
|
+
const { length } = focusables;
|
|
112
|
+
if (!length) {
|
|
110
113
|
return null;
|
|
111
114
|
}
|
|
112
115
|
const currentIndex = focusables.indexOf(anchor);
|
|
@@ -272,6 +275,9 @@ function createPortal(host, container = document.body) {
|
|
|
272
275
|
console.warn("Invalid container element. Fallback: <body> element.");
|
|
273
276
|
container = document.body;
|
|
274
277
|
}
|
|
278
|
+
if (containsComposed2(host, container)) {
|
|
279
|
+
throw new Error("Host element cannot contain container");
|
|
280
|
+
}
|
|
275
281
|
const portal = new Portal(host, container);
|
|
276
282
|
return () => portal.destroy();
|
|
277
283
|
}
|
|
@@ -284,6 +290,10 @@ var Portal = class {
|
|
|
284
290
|
#controller = null;
|
|
285
291
|
#isDestroyed = false;
|
|
286
292
|
constructor(host, container) {
|
|
293
|
+
if (host.hasAttribute("data-portaled")) {
|
|
294
|
+
console.warn("Already portaled");
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
287
297
|
this.#host = host;
|
|
288
298
|
this.#container = container;
|
|
289
299
|
this.#entranceSentinel = this.#createSentinel();
|
|
@@ -311,7 +321,7 @@ var Portal = class {
|
|
|
311
321
|
this.#exitSentinel.after(this.#host);
|
|
312
322
|
this.#entranceSentinel.remove();
|
|
313
323
|
this.#exitSentinel.remove();
|
|
314
|
-
this.#host.removeAttribute("data-
|
|
324
|
+
this.#host.removeAttribute("data-portaled");
|
|
315
325
|
}
|
|
316
326
|
#initialize() {
|
|
317
327
|
this.#host.before(this.#entranceSentinel);
|
|
@@ -331,7 +341,7 @@ var Portal = class {
|
|
|
331
341
|
capture: true,
|
|
332
342
|
signal
|
|
333
343
|
});
|
|
334
|
-
this.#host.setAttribute("data-
|
|
344
|
+
this.#host.setAttribute("data-portaled", "");
|
|
335
345
|
}
|
|
336
346
|
#onFocusIn = (event) => {
|
|
337
347
|
const current = event.target;
|
|
@@ -385,6 +395,7 @@ var Portal = class {
|
|
|
385
395
|
#createSentinel() {
|
|
386
396
|
const sentinel = document.createElement("span");
|
|
387
397
|
sentinel.setAttribute("aria-hidden", "true");
|
|
398
|
+
sentinel.setAttribute("data-portal-sentinel", "");
|
|
388
399
|
sentinel.setAttribute("tabindex", "0");
|
|
389
400
|
sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
|
|
390
401
|
return sentinel;
|
|
@@ -404,6 +415,16 @@ var Portal = class {
|
|
|
404
415
|
focusable && focus(focusable);
|
|
405
416
|
}
|
|
406
417
|
};
|
|
418
|
+
function containsComposed2(container, element) {
|
|
419
|
+
let current = element;
|
|
420
|
+
while (current) {
|
|
421
|
+
if (current === container) {
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
|
|
425
|
+
}
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
407
428
|
function focus(element) {
|
|
408
429
|
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
409
430
|
}
|
|
@@ -419,7 +440,7 @@ function getActiveElement2() {
|
|
|
419
440
|
* Lightweight DOM portal (teleport) utility with fully focus management.
|
|
420
441
|
* Designed for accessible dialogs, menus, overlays, popovers.
|
|
421
442
|
*
|
|
422
|
-
* @version 1.0.
|
|
443
|
+
* @version 1.0.3
|
|
423
444
|
* @author Yusuke Kamiyamane
|
|
424
445
|
* @license MIT
|
|
425
446
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -433,7 +454,7 @@ power-focusable/dist/index.js:
|
|
|
433
454
|
* High-precision focus management utility with full composed tree support.
|
|
434
455
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
435
456
|
*
|
|
436
|
-
* @version 4.1.
|
|
457
|
+
* @version 4.1.3
|
|
437
458
|
* @author Yusuke Kamiyamane
|
|
438
459
|
* @license MIT
|
|
439
460
|
* @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.0.
|
|
6
|
+
* @version 1.0.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
|
* Lightweight DOM portal (teleport) utility with fully focus management.
|
|
4
4
|
* Designed for accessible dialogs, menus, overlays, popovers.
|
|
5
5
|
*
|
|
6
|
-
* @version 1.0.
|
|
6
|
+
* @version 1.0.3
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.js
CHANGED
|
@@ -89,22 +89,25 @@ function isFocusable(element) {
|
|
|
89
89
|
return true;
|
|
90
90
|
}
|
|
91
91
|
function getRelativeFocusable(container, offset, options) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
92
|
+
let anchor = options.anchor ?? getActiveElement();
|
|
93
|
+
const { composed = false, filter, include, wrap = false } = options;
|
|
94
|
+
if (!(anchor instanceof Element)) {
|
|
95
|
+
const active = getActiveElement();
|
|
96
|
+
if (active instanceof Element) {
|
|
97
|
+
console.warn("Invalid anchor element. Fallback: active element.");
|
|
98
|
+
anchor = active;
|
|
99
|
+
} else {
|
|
100
|
+
console.warn("Invalid anchor element");
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
103
|
}
|
|
104
|
-
if (!
|
|
104
|
+
if (!containsComposed(container, anchor)) {
|
|
105
|
+
console.warn("Mismatch elements");
|
|
105
106
|
return null;
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
const focusables = getFocusables(container, { composed, filter, include });
|
|
109
|
+
const { length } = focusables;
|
|
110
|
+
if (!length) {
|
|
108
111
|
return null;
|
|
109
112
|
}
|
|
110
113
|
const currentIndex = focusables.indexOf(anchor);
|
|
@@ -270,6 +273,9 @@ function createPortal(host, container = document.body) {
|
|
|
270
273
|
console.warn("Invalid container element. Fallback: <body> element.");
|
|
271
274
|
container = document.body;
|
|
272
275
|
}
|
|
276
|
+
if (containsComposed2(host, container)) {
|
|
277
|
+
throw new Error("Host element cannot contain container");
|
|
278
|
+
}
|
|
273
279
|
const portal = new Portal(host, container);
|
|
274
280
|
return () => portal.destroy();
|
|
275
281
|
}
|
|
@@ -282,6 +288,10 @@ var Portal = class {
|
|
|
282
288
|
#controller = null;
|
|
283
289
|
#isDestroyed = false;
|
|
284
290
|
constructor(host, container) {
|
|
291
|
+
if (host.hasAttribute("data-portaled")) {
|
|
292
|
+
console.warn("Already portaled");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
285
295
|
this.#host = host;
|
|
286
296
|
this.#container = container;
|
|
287
297
|
this.#entranceSentinel = this.#createSentinel();
|
|
@@ -309,7 +319,7 @@ var Portal = class {
|
|
|
309
319
|
this.#exitSentinel.after(this.#host);
|
|
310
320
|
this.#entranceSentinel.remove();
|
|
311
321
|
this.#exitSentinel.remove();
|
|
312
|
-
this.#host.removeAttribute("data-
|
|
322
|
+
this.#host.removeAttribute("data-portaled");
|
|
313
323
|
}
|
|
314
324
|
#initialize() {
|
|
315
325
|
this.#host.before(this.#entranceSentinel);
|
|
@@ -329,7 +339,7 @@ var Portal = class {
|
|
|
329
339
|
capture: true,
|
|
330
340
|
signal
|
|
331
341
|
});
|
|
332
|
-
this.#host.setAttribute("data-
|
|
342
|
+
this.#host.setAttribute("data-portaled", "");
|
|
333
343
|
}
|
|
334
344
|
#onFocusIn = (event) => {
|
|
335
345
|
const current = event.target;
|
|
@@ -383,6 +393,7 @@ var Portal = class {
|
|
|
383
393
|
#createSentinel() {
|
|
384
394
|
const sentinel = document.createElement("span");
|
|
385
395
|
sentinel.setAttribute("aria-hidden", "true");
|
|
396
|
+
sentinel.setAttribute("data-portal-sentinel", "");
|
|
386
397
|
sentinel.setAttribute("tabindex", "0");
|
|
387
398
|
sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
|
|
388
399
|
return sentinel;
|
|
@@ -402,6 +413,16 @@ var Portal = class {
|
|
|
402
413
|
focusable && focus(focusable);
|
|
403
414
|
}
|
|
404
415
|
};
|
|
416
|
+
function containsComposed2(container, element) {
|
|
417
|
+
let current = element;
|
|
418
|
+
while (current) {
|
|
419
|
+
if (current === container) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
current = current instanceof ShadowRoot ? current.mode === "open" ? current.host : null : current.parentNode;
|
|
423
|
+
}
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
405
426
|
function focus(element) {
|
|
406
427
|
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
407
428
|
}
|
|
@@ -417,7 +438,7 @@ function getActiveElement2() {
|
|
|
417
438
|
* Lightweight DOM portal (teleport) utility with fully focus management.
|
|
418
439
|
* Designed for accessible dialogs, menus, overlays, popovers.
|
|
419
440
|
*
|
|
420
|
-
* @version 1.0.
|
|
441
|
+
* @version 1.0.3
|
|
421
442
|
* @author Yusuke Kamiyamane
|
|
422
443
|
* @license MIT
|
|
423
444
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -431,7 +452,7 @@ power-focusable/dist/index.js:
|
|
|
431
452
|
* High-precision focus management utility with full composed tree support.
|
|
432
453
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
433
454
|
*
|
|
434
|
-
* @version 4.1.
|
|
455
|
+
* @version 4.1.3
|
|
435
456
|
* @author Yusuke Kamiyamane
|
|
436
457
|
* @license MIT
|
|
437
458
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@y14e/portal",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Lightweight DOM portal (teleport) utility with fully focus management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"homepage": "https://github.com/y14e/portal#readme",
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"bun-types": "latest",
|
|
51
|
-
"power-focusable": "^4.1.
|
|
51
|
+
"power-focusable": "^4.1.3",
|
|
52
52
|
"tsup": "^8.0.0",
|
|
53
53
|
"typescript": "^5.6.0"
|
|
54
54
|
},
|