@y14e/portal 1.1.0 → 1.1.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/dist/index.cjs CHANGED
@@ -295,8 +295,7 @@ var Portal = class {
295
295
  #container;
296
296
  #entranceSentinel;
297
297
  #exitSentinel;
298
- #focusables = /* @__PURE__ */ new Set();
299
- #tabIndexes = /* @__PURE__ */ new Map();
298
+ #tabIndexes = /* @__PURE__ */ new WeakMap();
300
299
  #controller = null;
301
300
  #isDestroyed = false;
302
301
  constructor(host, container) {
@@ -313,7 +312,10 @@ var Portal = class {
313
312
  this.#isDestroyed = true;
314
313
  this.#controller?.abort();
315
314
  this.#controller = null;
316
- this.#focusables.forEach((focusable) => {
315
+ this.#getFocusables().forEach((focusable) => {
316
+ if (!this.#tabIndexes.has(focusable)) {
317
+ return;
318
+ }
317
319
  const index = this.#tabIndexes.get(focusable);
318
320
  if (index == null) {
319
321
  focusable.removeAttribute("tabindex");
@@ -321,8 +323,6 @@ var Portal = class {
321
323
  focusable.setAttribute("tabindex", index);
322
324
  }
323
325
  });
324
- this.#focusables.clear();
325
- this.#tabIndexes.clear();
326
326
  this.#exitSentinel.after(this.#host);
327
327
  this.#entranceSentinel.remove();
328
328
  this.#exitSentinel.remove();
@@ -332,7 +332,10 @@ var Portal = class {
332
332
  this.#host.before(this.#entranceSentinel);
333
333
  this.#entranceSentinel.after(this.#exitSentinel);
334
334
  this.#container.append(this.#host);
335
- this.#update();
335
+ this.#getFocusables().forEach((focusable) => {
336
+ this.#tabIndexes.set(focusable, focusable.getAttribute("tabindex"));
337
+ focusable.setAttribute("tabindex", "-1");
338
+ });
336
339
  this.#controller = new AbortController();
337
340
  const { signal } = this.#controller;
338
341
  document.addEventListener("focusin", this.#onFocusIn, {
@@ -355,16 +358,14 @@ var Portal = class {
355
358
  if (this.#host.contains(before)) {
356
359
  this.#moveFocus("previous");
357
360
  } else {
358
- this.#update();
359
- const first = [...this.#focusables][0];
361
+ const first = this.#getFocusables()[0];
360
362
  first && focusElement(first);
361
363
  }
362
364
  } else if (current === this.#exitSentinel) {
363
365
  if (this.#host.contains(before)) {
364
366
  this.#moveFocus("next");
365
367
  } else {
366
- this.#update();
367
- const last = [...this.#focusables].at(-1);
368
+ const last = this.#getFocusables().at(-1);
368
369
  last && focusElement(last);
369
370
  }
370
371
  }
@@ -380,53 +381,22 @@ var Portal = class {
380
381
  if (!this.#host.contains(active)) {
381
382
  return;
382
383
  }
383
- this.#update();
384
- const focusables = [...this.#focusables];
385
- if (!focusables.length) {
384
+ if (!this.#getFocusables().length) {
386
385
  event.preventDefault();
387
386
  (event.shiftKey ? this.#entranceSentinel : this.#exitSentinel).focus();
388
- return;
389
387
  }
390
- const index = focusables.indexOf(active);
388
+ const index = this.#getFocusables().indexOf(active);
391
389
  if (index === -1) {
392
390
  return;
393
391
  }
394
392
  event.preventDefault();
395
- const focusable = focusables[index + (event.shiftKey ? -1 : 1)];
393
+ const focusable = this.#getFocusables()[index + (event.shiftKey ? -1 : 1)];
396
394
  if (focusable) {
397
395
  focusElement(focusable);
398
396
  } else {
399
397
  (event.shiftKey ? this.#entranceSentinel : this.#exitSentinel).focus();
400
398
  }
401
399
  };
402
- #update() {
403
- const current = new Set(
404
- getFocusables(this.#host, { composed: true })
405
- );
406
- this.#focusables.forEach((focusable) => {
407
- if (current.has(focusable)) {
408
- return;
409
- }
410
- if (focusable.isConnected) {
411
- const index = this.#tabIndexes.get(focusable);
412
- if (index == null) {
413
- focusable.removeAttribute("tabindex");
414
- } else {
415
- focusable.setAttribute("tabindex", index);
416
- }
417
- }
418
- this.#focusables.delete(focusable);
419
- this.#tabIndexes.delete(focusable);
420
- });
421
- const active = getActiveElement2();
422
- current.forEach((c) => {
423
- if (!this.#tabIndexes.has(c)) {
424
- this.#tabIndexes.set(c, c.getAttribute("tabindex"));
425
- }
426
- c.setAttribute("tabindex", c === active ? "0" : "-1");
427
- this.#focusables.add(c);
428
- });
429
- }
430
400
  #createSentinel() {
431
401
  const sentinel = document.createElement("span");
432
402
  sentinel.setAttribute("aria-hidden", "true");
@@ -435,6 +405,12 @@ var Portal = class {
435
405
  sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
436
406
  return sentinel;
437
407
  }
408
+ #getFocusables() {
409
+ return getFocusables(this.#host, {
410
+ composed: true,
411
+ include: (element) => this.#tabIndexes.has(element)
412
+ });
413
+ }
438
414
  #moveFocus(direction) {
439
415
  const options = {
440
416
  anchor: direction === "previous" ? this.#entranceSentinel : this.#exitSentinel,
@@ -469,7 +445,7 @@ function getActiveElement2() {
469
445
  * Lightweight DOM portal (teleport) utility with fully focus management.
470
446
  * Designed for accessible dialogs, menus, overlays, popovers.
471
447
  *
472
- * @version 1.1.0
448
+ * @version 1.1.1
473
449
  * @author Yusuke Kamiyamane
474
450
  * @license MIT
475
451
  * @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.1.0
6
+ * @version 1.1.1
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.1.0
6
+ * @version 1.1.1
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -293,8 +293,7 @@ var Portal = class {
293
293
  #container;
294
294
  #entranceSentinel;
295
295
  #exitSentinel;
296
- #focusables = /* @__PURE__ */ new Set();
297
- #tabIndexes = /* @__PURE__ */ new Map();
296
+ #tabIndexes = /* @__PURE__ */ new WeakMap();
298
297
  #controller = null;
299
298
  #isDestroyed = false;
300
299
  constructor(host, container) {
@@ -311,7 +310,10 @@ var Portal = class {
311
310
  this.#isDestroyed = true;
312
311
  this.#controller?.abort();
313
312
  this.#controller = null;
314
- this.#focusables.forEach((focusable) => {
313
+ this.#getFocusables().forEach((focusable) => {
314
+ if (!this.#tabIndexes.has(focusable)) {
315
+ return;
316
+ }
315
317
  const index = this.#tabIndexes.get(focusable);
316
318
  if (index == null) {
317
319
  focusable.removeAttribute("tabindex");
@@ -319,8 +321,6 @@ var Portal = class {
319
321
  focusable.setAttribute("tabindex", index);
320
322
  }
321
323
  });
322
- this.#focusables.clear();
323
- this.#tabIndexes.clear();
324
324
  this.#exitSentinel.after(this.#host);
325
325
  this.#entranceSentinel.remove();
326
326
  this.#exitSentinel.remove();
@@ -330,7 +330,10 @@ var Portal = class {
330
330
  this.#host.before(this.#entranceSentinel);
331
331
  this.#entranceSentinel.after(this.#exitSentinel);
332
332
  this.#container.append(this.#host);
333
- this.#update();
333
+ this.#getFocusables().forEach((focusable) => {
334
+ this.#tabIndexes.set(focusable, focusable.getAttribute("tabindex"));
335
+ focusable.setAttribute("tabindex", "-1");
336
+ });
334
337
  this.#controller = new AbortController();
335
338
  const { signal } = this.#controller;
336
339
  document.addEventListener("focusin", this.#onFocusIn, {
@@ -353,16 +356,14 @@ var Portal = class {
353
356
  if (this.#host.contains(before)) {
354
357
  this.#moveFocus("previous");
355
358
  } else {
356
- this.#update();
357
- const first = [...this.#focusables][0];
359
+ const first = this.#getFocusables()[0];
358
360
  first && focusElement(first);
359
361
  }
360
362
  } else if (current === this.#exitSentinel) {
361
363
  if (this.#host.contains(before)) {
362
364
  this.#moveFocus("next");
363
365
  } else {
364
- this.#update();
365
- const last = [...this.#focusables].at(-1);
366
+ const last = this.#getFocusables().at(-1);
366
367
  last && focusElement(last);
367
368
  }
368
369
  }
@@ -378,53 +379,22 @@ var Portal = class {
378
379
  if (!this.#host.contains(active)) {
379
380
  return;
380
381
  }
381
- this.#update();
382
- const focusables = [...this.#focusables];
383
- if (!focusables.length) {
382
+ if (!this.#getFocusables().length) {
384
383
  event.preventDefault();
385
384
  (event.shiftKey ? this.#entranceSentinel : this.#exitSentinel).focus();
386
- return;
387
385
  }
388
- const index = focusables.indexOf(active);
386
+ const index = this.#getFocusables().indexOf(active);
389
387
  if (index === -1) {
390
388
  return;
391
389
  }
392
390
  event.preventDefault();
393
- const focusable = focusables[index + (event.shiftKey ? -1 : 1)];
391
+ const focusable = this.#getFocusables()[index + (event.shiftKey ? -1 : 1)];
394
392
  if (focusable) {
395
393
  focusElement(focusable);
396
394
  } else {
397
395
  (event.shiftKey ? this.#entranceSentinel : this.#exitSentinel).focus();
398
396
  }
399
397
  };
400
- #update() {
401
- const current = new Set(
402
- getFocusables(this.#host, { composed: true })
403
- );
404
- this.#focusables.forEach((focusable) => {
405
- if (current.has(focusable)) {
406
- return;
407
- }
408
- if (focusable.isConnected) {
409
- const index = this.#tabIndexes.get(focusable);
410
- if (index == null) {
411
- focusable.removeAttribute("tabindex");
412
- } else {
413
- focusable.setAttribute("tabindex", index);
414
- }
415
- }
416
- this.#focusables.delete(focusable);
417
- this.#tabIndexes.delete(focusable);
418
- });
419
- const active = getActiveElement2();
420
- current.forEach((c) => {
421
- if (!this.#tabIndexes.has(c)) {
422
- this.#tabIndexes.set(c, c.getAttribute("tabindex"));
423
- }
424
- c.setAttribute("tabindex", c === active ? "0" : "-1");
425
- this.#focusables.add(c);
426
- });
427
- }
428
398
  #createSentinel() {
429
399
  const sentinel = document.createElement("span");
430
400
  sentinel.setAttribute("aria-hidden", "true");
@@ -433,6 +403,12 @@ var Portal = class {
433
403
  sentinel.style.cssText += VISUALLY_HIDDEN_CSS;
434
404
  return sentinel;
435
405
  }
406
+ #getFocusables() {
407
+ return getFocusables(this.#host, {
408
+ composed: true,
409
+ include: (element) => this.#tabIndexes.has(element)
410
+ });
411
+ }
436
412
  #moveFocus(direction) {
437
413
  const options = {
438
414
  anchor: direction === "previous" ? this.#entranceSentinel : this.#exitSentinel,
@@ -467,7 +443,7 @@ function getActiveElement2() {
467
443
  * Lightweight DOM portal (teleport) utility with fully focus management.
468
444
  * Designed for accessible dialogs, menus, overlays, popovers.
469
445
  *
470
- * @version 1.1.0
446
+ * @version 1.1.1
471
447
  * @author Yusuke Kamiyamane
472
448
  * @license MIT
473
449
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/portal",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Lightweight DOM portal (teleport) utility with fully focus management",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",