@y14e/disclosure-css 1.3.4 → 1.3.6

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/disclosure-css
10
10
 
11
11
  ```ts
12
12
  // npm
13
- import Disclosure from '@y14e/disclosure-css@1.3.4';
13
+ import Disclosure from '@y14e/disclosure-css@1.3.6';
14
14
 
15
15
  // CDNs
16
- import Disclosure from 'https://esm.sh/@y14e/disclosure-css@1.3.4';
16
+ import Disclosure from 'https://esm.sh/@y14e/disclosure-css@1.3.6';
17
17
  // or
18
- import Disclosure from 'https://cdn.jsdelivr.net/npm/@y14e/disclosure-css@1.3.4/+esm';
18
+ import Disclosure from 'https://cdn.jsdelivr.net/npm/@y14e/disclosure-css@1.3.6/+esm';
19
19
  // or
20
- import Disclosure from 'https://esm.unpkg.com/@y14e/disclosure-css@1.3.4';
20
+ import Disclosure from 'https://esm.unpkg.com/@y14e/disclosure-css@1.3.6';
21
21
  ```
22
22
 
23
23
  ## Usage
@@ -30,15 +30,6 @@ new Disclosure(root);
30
30
 
31
31
  ## 📦 APIs
32
32
 
33
- ### `open`
34
-
35
- ```ts
36
- disclosure.open(details);
37
- // => void
38
- //
39
- // details: HTMLDetailsElement
40
- ```
41
-
42
33
  ### `close`
43
34
 
44
35
  ```ts
@@ -57,6 +48,15 @@ disclosure.destroy();
57
48
  // => void
58
49
  ```
59
50
 
51
+ ### `open`
52
+
53
+ ```ts
54
+ disclosure.open(details);
55
+ // => void
56
+ //
57
+ // details: HTMLDetailsElement
58
+ ```
59
+
60
60
  ## Demo
61
61
 
62
62
  - https://y14e.github.io/disclosure-css/
package/dist/index.cjs CHANGED
@@ -40,11 +40,10 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
40
40
  const tokens = value ? parse(value).filter(Boolean) : [];
41
41
  if (caseInsensitive) {
42
42
  const lower = token.toLowerCase();
43
- if (tokens.some((token2) => token2.toLowerCase() === lower)) {
44
- return;
43
+ if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
44
+ tokens.push(token);
45
+ element.setAttribute(attribute, serialize(tokens));
45
46
  }
46
- tokens.push(token);
47
- element.setAttribute(attribute, serialize(tokens));
48
47
  return;
49
48
  }
50
49
  const set = new Set(tokens);
@@ -323,6 +322,8 @@ function createRovingTabIndex(container, options = {}) {
323
322
  let {
324
323
  direction,
325
324
  navigationOnly = false,
325
+ noMemory = false,
326
+ noStart = false,
326
327
  selector,
327
328
  typeahead = false,
328
329
  wrap = false
@@ -335,6 +336,14 @@ function createRovingTabIndex(container, options = {}) {
335
336
  console.warn("Invalid navigationOnly option. Fallback: false.");
336
337
  navigationOnly = false;
337
338
  }
339
+ if (typeof noMemory !== "boolean") {
340
+ console.warn("Invalid noMemory option. Fallback: false.");
341
+ noMemory = false;
342
+ }
343
+ if (typeof noStart !== "boolean") {
344
+ console.warn("Invalid noStart option. Fallback: false.");
345
+ noStart = false;
346
+ }
338
347
  if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
339
348
  console.warn(
340
349
  "Invalid selector. Fallback: all focusable elements (undefined)."
@@ -349,13 +358,15 @@ function createRovingTabIndex(container, options = {}) {
349
358
  console.warn("Invalid wrap option. Fallback: false.");
350
359
  wrap = false;
351
360
  }
352
- const settings = { navigationOnly, typeahead, wrap };
353
- if (direction) {
354
- Object.assign(settings, { direction });
355
- }
356
- if (selector) {
357
- Object.assign(settings, { selector });
358
- }
361
+ const settings = {
362
+ navigationOnly,
363
+ noMemory,
364
+ noStart,
365
+ typeahead,
366
+ wrap
367
+ };
368
+ direction && Object.assign(settings, { direction });
369
+ selector && Object.assign(settings, { selector });
359
370
  const roving = new RovingTabIndex(container, settings);
360
371
  return () => roving.destroy();
361
372
  }
@@ -388,12 +399,29 @@ var RovingTabIndex = class {
388
399
  #initialize() {
389
400
  this.#update(document.activeElement);
390
401
  this.#controller = new AbortController();
402
+ const { signal } = this.#controller;
403
+ document.addEventListener("focusin", this.#onFocusIn, {
404
+ capture: true,
405
+ signal
406
+ });
391
407
  document.addEventListener("keydown", this.#onKeyDown, {
392
408
  capture: true,
393
- signal: this.#controller.signal
409
+ signal
394
410
  });
395
411
  this.#container.setAttribute("data-roving-tabindex-initialized", "");
396
412
  }
413
+ #onFocusIn = (event) => {
414
+ const { target } = event;
415
+ if (!(target instanceof Element)) {
416
+ return;
417
+ }
418
+ const isFocusable22 = this.#focusables.has(target);
419
+ if (this.#options.noMemory && !isFocusable22) {
420
+ this.#update(null);
421
+ return;
422
+ }
423
+ isFocusable22 && this.#update(target);
424
+ };
397
425
  #onKeyDown = (event) => {
398
426
  if (!event.composedPath().includes(this.#container)) {
399
427
  return;
@@ -424,7 +452,6 @@ var RovingTabIndex = class {
424
452
  return;
425
453
  }
426
454
  event.preventDefault();
427
- event.stopPropagation();
428
455
  const currentIndex = current.indexOf(active);
429
456
  let rawIndex;
430
457
  let newIndex = currentIndex;
@@ -455,11 +482,7 @@ var RovingTabIndex = class {
455
482
  }
456
483
  }
457
484
  const focusable = target.at(newIndex);
458
- if (!focusable) {
459
- return;
460
- }
461
- this.#update(focusable);
462
- focusElement(focusable);
485
+ focusable && focusElement(focusable);
463
486
  };
464
487
  #update(active) {
465
488
  const current = new Set(this.#getFocusables());
@@ -474,7 +497,7 @@ var RovingTabIndex = class {
474
497
  index >= 0 && focusables.splice(index, 1);
475
498
  });
476
499
  }
477
- const { navigationOnly } = this.#options;
500
+ const { navigationOnly, noStart, typeahead } = this.#options;
478
501
  for (const focusable of current) {
479
502
  if (this.#focusables.has(focusable)) {
480
503
  continue;
@@ -484,7 +507,7 @@ var RovingTabIndex = class {
484
507
  saveAttributes2([focusable], ["tabindex"]);
485
508
  focusable.setAttribute("tabindex", "-1");
486
509
  }
487
- if (!this.#options.typeahead) {
510
+ if (!typeahead) {
488
511
  continue;
489
512
  }
490
513
  const value = focusable.ariaKeyShortcuts?.trim();
@@ -515,7 +538,7 @@ var RovingTabIndex = class {
515
538
  return;
516
539
  }
517
540
  [...this.#focusables].forEach((focusable, i) => {
518
- focusable.setAttribute("tabindex", i ? "-1" : "0");
541
+ focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
519
542
  });
520
543
  }
521
544
  #createSelectorFilter() {
@@ -526,7 +549,8 @@ var RovingTabIndex = class {
526
549
  return getFocusables(this.#container, {
527
550
  composed: true,
528
551
  filter: this.#selectorFilter,
529
- skipNegativeTabIndexCheck: !this.#options.navigationOnly
552
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly,
553
+ skipVisibilityCheck: true
530
554
  });
531
555
  }
532
556
  };
@@ -600,16 +624,6 @@ var Disclosure = class {
600
624
  });
601
625
  this.#initialize();
602
626
  }
603
- open(details) {
604
- if (this.#isDestroyed) {
605
- return;
606
- }
607
- if (!(details instanceof HTMLDetailsElement) || !this.#bindings.has(details)) {
608
- console.warn("Invalid <details> element");
609
- return;
610
- }
611
- this.#toggle(details, true);
612
- }
613
627
  close(details) {
614
628
  if (this.#isDestroyed) {
615
629
  return;
@@ -633,6 +647,16 @@ var Disclosure = class {
633
647
  this.#contentElements.length = 0;
634
648
  this.#rootElement.removeAttribute("data-disclosure-initialized");
635
649
  }
650
+ open(details) {
651
+ if (this.#isDestroyed) {
652
+ return;
653
+ }
654
+ if (!(details instanceof HTMLDetailsElement) || !this.#bindings.has(details)) {
655
+ console.warn("Invalid <details> element");
656
+ return;
657
+ }
658
+ this.#toggle(details, true);
659
+ }
636
660
  #initialize() {
637
661
  saveAttributes(this.#summaryElements, [
638
662
  "aria-disabled",
@@ -669,7 +693,7 @@ function isFocusable2(element) {
669
693
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
670
694
  * Using the <details> and <summary> element.
671
695
  *
672
- * @version 1.3.4
696
+ * @version 1.3.6
673
697
  * @author Yusuke Kamiyamane
674
698
  * @license MIT
675
699
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -681,7 +705,7 @@ function isFocusable2(element) {
681
705
  (**
682
706
  * Attributes Utils
683
707
  *
684
- * @version 1.1.0
708
+ * @version 1.1.1
685
709
  * @author Yusuke Kamiyamane
686
710
  * @license MIT
687
711
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -694,7 +718,7 @@ function isFocusable2(element) {
694
718
  * Lightweight roving tabindex utility with fully focus management.
695
719
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
696
720
  *
697
- * @version 2.0.4
721
+ * @version 3.0.0
698
722
  * @author Yusuke Kamiyamane
699
723
  * @license MIT
700
724
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -706,7 +730,7 @@ function isFocusable2(element) {
706
730
  (**
707
731
  * Attributes Utils
708
732
  *
709
- * @version 1.1.0
733
+ * @version 1.1.1
710
734
  * @author Yusuke Kamiyamane
711
735
  * @license MIT
712
736
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -719,7 +743,7 @@ function isFocusable2(element) {
719
743
  * High-precision focus management utility with full composed tree support.
720
744
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
721
745
  *
722
- * @version 4.3.1
746
+ * @version 4.3.2
723
747
  * @author Yusuke Kamiyamane
724
748
  * @license MIT
725
749
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -3,7 +3,7 @@
3
3
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
4
4
  * Using the <details> and <summary> element.
5
5
  *
6
- * @version 1.3.4
6
+ * @version 1.3.6
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -12,9 +12,9 @@
12
12
  declare class Disclosure {
13
13
  #private;
14
14
  constructor(root: HTMLElement);
15
- open(details: HTMLDetailsElement): void;
16
15
  close(details: HTMLDetailsElement): void;
17
16
  destroy(): void;
17
+ open(details: HTMLDetailsElement): void;
18
18
  }
19
19
 
20
20
  export { Disclosure as default };
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
4
4
  * Using the <details> and <summary> element.
5
5
  *
6
- * @version 1.3.4
6
+ * @version 1.3.6
7
7
  * @author Yusuke Kamiyamane
8
8
  * @license MIT
9
9
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -12,9 +12,9 @@
12
12
  declare class Disclosure {
13
13
  #private;
14
14
  constructor(root: HTMLElement);
15
- open(details: HTMLDetailsElement): void;
16
15
  close(details: HTMLDetailsElement): void;
17
16
  destroy(): void;
17
+ open(details: HTMLDetailsElement): void;
18
18
  }
19
19
 
20
20
  export { Disclosure as default };
package/dist/index.js CHANGED
@@ -38,11 +38,10 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
38
38
  const tokens = value ? parse(value).filter(Boolean) : [];
39
39
  if (caseInsensitive) {
40
40
  const lower = token.toLowerCase();
41
- if (tokens.some((token2) => token2.toLowerCase() === lower)) {
42
- return;
41
+ if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
42
+ tokens.push(token);
43
+ element.setAttribute(attribute, serialize(tokens));
43
44
  }
44
- tokens.push(token);
45
- element.setAttribute(attribute, serialize(tokens));
46
45
  return;
47
46
  }
48
47
  const set = new Set(tokens);
@@ -321,6 +320,8 @@ function createRovingTabIndex(container, options = {}) {
321
320
  let {
322
321
  direction,
323
322
  navigationOnly = false,
323
+ noMemory = false,
324
+ noStart = false,
324
325
  selector,
325
326
  typeahead = false,
326
327
  wrap = false
@@ -333,6 +334,14 @@ function createRovingTabIndex(container, options = {}) {
333
334
  console.warn("Invalid navigationOnly option. Fallback: false.");
334
335
  navigationOnly = false;
335
336
  }
337
+ if (typeof noMemory !== "boolean") {
338
+ console.warn("Invalid noMemory option. Fallback: false.");
339
+ noMemory = false;
340
+ }
341
+ if (typeof noStart !== "boolean") {
342
+ console.warn("Invalid noStart option. Fallback: false.");
343
+ noStart = false;
344
+ }
336
345
  if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
337
346
  console.warn(
338
347
  "Invalid selector. Fallback: all focusable elements (undefined)."
@@ -347,13 +356,15 @@ function createRovingTabIndex(container, options = {}) {
347
356
  console.warn("Invalid wrap option. Fallback: false.");
348
357
  wrap = false;
349
358
  }
350
- const settings = { navigationOnly, typeahead, wrap };
351
- if (direction) {
352
- Object.assign(settings, { direction });
353
- }
354
- if (selector) {
355
- Object.assign(settings, { selector });
356
- }
359
+ const settings = {
360
+ navigationOnly,
361
+ noMemory,
362
+ noStart,
363
+ typeahead,
364
+ wrap
365
+ };
366
+ direction && Object.assign(settings, { direction });
367
+ selector && Object.assign(settings, { selector });
357
368
  const roving = new RovingTabIndex(container, settings);
358
369
  return () => roving.destroy();
359
370
  }
@@ -386,12 +397,29 @@ var RovingTabIndex = class {
386
397
  #initialize() {
387
398
  this.#update(document.activeElement);
388
399
  this.#controller = new AbortController();
400
+ const { signal } = this.#controller;
401
+ document.addEventListener("focusin", this.#onFocusIn, {
402
+ capture: true,
403
+ signal
404
+ });
389
405
  document.addEventListener("keydown", this.#onKeyDown, {
390
406
  capture: true,
391
- signal: this.#controller.signal
407
+ signal
392
408
  });
393
409
  this.#container.setAttribute("data-roving-tabindex-initialized", "");
394
410
  }
411
+ #onFocusIn = (event) => {
412
+ const { target } = event;
413
+ if (!(target instanceof Element)) {
414
+ return;
415
+ }
416
+ const isFocusable22 = this.#focusables.has(target);
417
+ if (this.#options.noMemory && !isFocusable22) {
418
+ this.#update(null);
419
+ return;
420
+ }
421
+ isFocusable22 && this.#update(target);
422
+ };
395
423
  #onKeyDown = (event) => {
396
424
  if (!event.composedPath().includes(this.#container)) {
397
425
  return;
@@ -422,7 +450,6 @@ var RovingTabIndex = class {
422
450
  return;
423
451
  }
424
452
  event.preventDefault();
425
- event.stopPropagation();
426
453
  const currentIndex = current.indexOf(active);
427
454
  let rawIndex;
428
455
  let newIndex = currentIndex;
@@ -453,11 +480,7 @@ var RovingTabIndex = class {
453
480
  }
454
481
  }
455
482
  const focusable = target.at(newIndex);
456
- if (!focusable) {
457
- return;
458
- }
459
- this.#update(focusable);
460
- focusElement(focusable);
483
+ focusable && focusElement(focusable);
461
484
  };
462
485
  #update(active) {
463
486
  const current = new Set(this.#getFocusables());
@@ -472,7 +495,7 @@ var RovingTabIndex = class {
472
495
  index >= 0 && focusables.splice(index, 1);
473
496
  });
474
497
  }
475
- const { navigationOnly } = this.#options;
498
+ const { navigationOnly, noStart, typeahead } = this.#options;
476
499
  for (const focusable of current) {
477
500
  if (this.#focusables.has(focusable)) {
478
501
  continue;
@@ -482,7 +505,7 @@ var RovingTabIndex = class {
482
505
  saveAttributes2([focusable], ["tabindex"]);
483
506
  focusable.setAttribute("tabindex", "-1");
484
507
  }
485
- if (!this.#options.typeahead) {
508
+ if (!typeahead) {
486
509
  continue;
487
510
  }
488
511
  const value = focusable.ariaKeyShortcuts?.trim();
@@ -513,7 +536,7 @@ var RovingTabIndex = class {
513
536
  return;
514
537
  }
515
538
  [...this.#focusables].forEach((focusable, i) => {
516
- focusable.setAttribute("tabindex", i ? "-1" : "0");
539
+ focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
517
540
  });
518
541
  }
519
542
  #createSelectorFilter() {
@@ -524,7 +547,8 @@ var RovingTabIndex = class {
524
547
  return getFocusables(this.#container, {
525
548
  composed: true,
526
549
  filter: this.#selectorFilter,
527
- skipNegativeTabIndexCheck: !this.#options.navigationOnly
550
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly,
551
+ skipVisibilityCheck: true
528
552
  });
529
553
  }
530
554
  };
@@ -598,16 +622,6 @@ var Disclosure = class {
598
622
  });
599
623
  this.#initialize();
600
624
  }
601
- open(details) {
602
- if (this.#isDestroyed) {
603
- return;
604
- }
605
- if (!(details instanceof HTMLDetailsElement) || !this.#bindings.has(details)) {
606
- console.warn("Invalid <details> element");
607
- return;
608
- }
609
- this.#toggle(details, true);
610
- }
611
625
  close(details) {
612
626
  if (this.#isDestroyed) {
613
627
  return;
@@ -631,6 +645,16 @@ var Disclosure = class {
631
645
  this.#contentElements.length = 0;
632
646
  this.#rootElement.removeAttribute("data-disclosure-initialized");
633
647
  }
648
+ open(details) {
649
+ if (this.#isDestroyed) {
650
+ return;
651
+ }
652
+ if (!(details instanceof HTMLDetailsElement) || !this.#bindings.has(details)) {
653
+ console.warn("Invalid <details> element");
654
+ return;
655
+ }
656
+ this.#toggle(details, true);
657
+ }
634
658
  #initialize() {
635
659
  saveAttributes(this.#summaryElements, [
636
660
  "aria-disabled",
@@ -667,7 +691,7 @@ function isFocusable2(element) {
667
691
  * WAI-ARIA compliant disclosure pattern implementation in TypeScript.
668
692
  * Using the <details> and <summary> element.
669
693
  *
670
- * @version 1.3.4
694
+ * @version 1.3.6
671
695
  * @author Yusuke Kamiyamane
672
696
  * @license MIT
673
697
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -679,7 +703,7 @@ function isFocusable2(element) {
679
703
  (**
680
704
  * Attributes Utils
681
705
  *
682
- * @version 1.1.0
706
+ * @version 1.1.1
683
707
  * @author Yusuke Kamiyamane
684
708
  * @license MIT
685
709
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -692,7 +716,7 @@ function isFocusable2(element) {
692
716
  * Lightweight roving tabindex utility with fully focus management.
693
717
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
694
718
  *
695
- * @version 2.0.4
719
+ * @version 3.0.0
696
720
  * @author Yusuke Kamiyamane
697
721
  * @license MIT
698
722
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -704,7 +728,7 @@ function isFocusable2(element) {
704
728
  (**
705
729
  * Attributes Utils
706
730
  *
707
- * @version 1.1.0
731
+ * @version 1.1.1
708
732
  * @author Yusuke Kamiyamane
709
733
  * @license MIT
710
734
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -717,7 +741,7 @@ function isFocusable2(element) {
717
741
  * High-precision focus management utility with full composed tree support.
718
742
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
719
743
  *
720
- * @version 4.3.1
744
+ * @version 4.3.2
721
745
  * @author Yusuke Kamiyamane
722
746
  * @license MIT
723
747
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/disclosure-css",
3
- "version": "1.3.4",
3
+ "version": "1.3.6",
4
4
  "description": "WAI-ARIA compliant disclosure pattern implementation in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "homepage": "https://github.com/y14e/disclosure-css#readme",
45
45
  "devDependencies": {
46
- "@y14e/attributes-utils": "^1.1.0",
47
- "@y14e/roving-tabindex": "^2.0.4",
46
+ "@y14e/attributes-utils": "^1.1.1",
47
+ "@y14e/roving-tabindex": "^3.0.0",
48
48
  "bun-types": "latest",
49
49
  "tsup": "^8.0.0",
50
50
  "typescript": "^5.6.0"