@y14e/accordion 1.4.5 → 1.4.7

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/accordion
10
10
 
11
11
  ```ts
12
12
  // npm
13
- import Accordion from '@y14e/accordion@1.4.5';
13
+ import Accordion from '@y14e/accordion@1.4.7';
14
14
 
15
15
  // CDNs
16
- import Accordion from 'https://esm.sh/@y14e/accordion@1.4.5';
16
+ import Accordion from 'https://esm.sh/@y14e/accordion@1.4.7';
17
17
  // or
18
- import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion@1.4.5/+esm';
18
+ import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion@1.4.7/+esm';
19
19
  // or
20
- import Accordion from 'https://esm.unpkg.com/@y14e/accordion@1.4.5';
20
+ import Accordion from 'https://esm.unpkg.com/@y14e/accordion@1.4.7';
21
21
  ```
22
22
 
23
23
  ## Usage
package/dist/index.cjs CHANGED
@@ -13,11 +13,10 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
13
13
  const tokens = value ? parse(value).filter(Boolean) : [];
14
14
  if (caseInsensitive) {
15
15
  const lower = token.toLowerCase();
16
- if (tokens.some((token2) => token2.toLowerCase() === lower)) {
17
- return;
16
+ if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
17
+ tokens.push(token);
18
+ element.setAttribute(attribute, serialize(tokens));
18
19
  }
19
- tokens.push(token);
20
- element.setAttribute(attribute, serialize(tokens));
21
20
  return;
22
21
  }
23
22
  const set = new Set(tokens);
@@ -119,11 +118,10 @@ function addTokenToAttribute2(element, attribute, token, options = {}) {
119
118
  const tokens = value ? parse(value).filter(Boolean) : [];
120
119
  if (caseInsensitive) {
121
120
  const lower = token.toLowerCase();
122
- if (tokens.some((token2) => token2.toLowerCase() === lower)) {
123
- return;
121
+ if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
122
+ tokens.push(token);
123
+ element.setAttribute(attribute, serialize(tokens));
124
124
  }
125
- tokens.push(token);
126
- element.setAttribute(attribute, serialize(tokens));
127
125
  return;
128
126
  }
129
127
  const set = new Set(tokens);
@@ -402,6 +400,8 @@ function createRovingTabIndex(container, options = {}) {
402
400
  let {
403
401
  direction,
404
402
  navigationOnly = false,
403
+ noMemory = false,
404
+ noStart = false,
405
405
  selector,
406
406
  typeahead = false,
407
407
  wrap = false
@@ -414,6 +414,14 @@ function createRovingTabIndex(container, options = {}) {
414
414
  console.warn("Invalid navigationOnly option. Fallback: false.");
415
415
  navigationOnly = false;
416
416
  }
417
+ if (typeof noMemory !== "boolean") {
418
+ console.warn("Invalid noMemory option. Fallback: false.");
419
+ noMemory = false;
420
+ }
421
+ if (typeof noStart !== "boolean") {
422
+ console.warn("Invalid noStart option. Fallback: false.");
423
+ noStart = false;
424
+ }
417
425
  if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
418
426
  console.warn(
419
427
  "Invalid selector. Fallback: all focusable elements (undefined)."
@@ -428,13 +436,15 @@ function createRovingTabIndex(container, options = {}) {
428
436
  console.warn("Invalid wrap option. Fallback: false.");
429
437
  wrap = false;
430
438
  }
431
- const settings = { navigationOnly, typeahead, wrap };
432
- if (direction) {
433
- Object.assign(settings, { direction });
434
- }
435
- if (selector) {
436
- Object.assign(settings, { selector });
437
- }
439
+ const settings = {
440
+ navigationOnly,
441
+ noMemory,
442
+ noStart,
443
+ typeahead,
444
+ wrap
445
+ };
446
+ direction && Object.assign(settings, { direction });
447
+ selector && Object.assign(settings, { selector });
438
448
  const roving = new RovingTabIndex(container, settings);
439
449
  return () => roving.destroy();
440
450
  }
@@ -467,12 +477,29 @@ var RovingTabIndex = class {
467
477
  #initialize() {
468
478
  this.#update(document.activeElement);
469
479
  this.#controller = new AbortController();
480
+ const { signal } = this.#controller;
481
+ document.addEventListener("focusin", this.#onFocusIn, {
482
+ capture: true,
483
+ signal
484
+ });
470
485
  document.addEventListener("keydown", this.#onKeyDown, {
471
486
  capture: true,
472
- signal: this.#controller.signal
487
+ signal
473
488
  });
474
489
  this.#container.setAttribute("data-roving-tabindex-initialized", "");
475
490
  }
491
+ #onFocusIn = (event) => {
492
+ const { target } = event;
493
+ if (!(target instanceof Element)) {
494
+ return;
495
+ }
496
+ const isFocusable22 = this.#focusables.has(target);
497
+ if (this.#options.noMemory && !isFocusable22) {
498
+ this.#update(null);
499
+ return;
500
+ }
501
+ isFocusable22 && this.#update(target);
502
+ };
476
503
  #onKeyDown = (event) => {
477
504
  if (!event.composedPath().includes(this.#container)) {
478
505
  return;
@@ -503,7 +530,6 @@ var RovingTabIndex = class {
503
530
  return;
504
531
  }
505
532
  event.preventDefault();
506
- event.stopPropagation();
507
533
  const currentIndex = current.indexOf(active);
508
534
  let rawIndex;
509
535
  let newIndex = currentIndex;
@@ -534,11 +560,7 @@ var RovingTabIndex = class {
534
560
  }
535
561
  }
536
562
  const focusable = target.at(newIndex);
537
- if (!focusable) {
538
- return;
539
- }
540
- this.#update(focusable);
541
- focusElement(focusable);
563
+ focusable && focusElement(focusable);
542
564
  };
543
565
  #update(active) {
544
566
  const current = new Set(this.#getFocusables());
@@ -553,7 +575,7 @@ var RovingTabIndex = class {
553
575
  index >= 0 && focusables.splice(index, 1);
554
576
  });
555
577
  }
556
- const { navigationOnly } = this.#options;
578
+ const { navigationOnly, noStart, typeahead } = this.#options;
557
579
  for (const focusable of current) {
558
580
  if (this.#focusables.has(focusable)) {
559
581
  continue;
@@ -563,7 +585,7 @@ var RovingTabIndex = class {
563
585
  saveAttributes2([focusable], ["tabindex"]);
564
586
  focusable.setAttribute("tabindex", "-1");
565
587
  }
566
- if (!this.#options.typeahead) {
588
+ if (!typeahead) {
567
589
  continue;
568
590
  }
569
591
  const value = focusable.ariaKeyShortcuts?.trim();
@@ -594,7 +616,7 @@ var RovingTabIndex = class {
594
616
  return;
595
617
  }
596
618
  [...this.#focusables].forEach((focusable, i) => {
597
- focusable.setAttribute("tabindex", i ? "-1" : "0");
619
+ focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
598
620
  });
599
621
  }
600
622
  #createSelectorFilter() {
@@ -605,7 +627,8 @@ var RovingTabIndex = class {
605
627
  return getFocusables(this.#container, {
606
628
  composed: true,
607
629
  filter: this.#selectorFilter,
608
- skipNegativeTabIndexCheck: !this.#options.navigationOnly
630
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly,
631
+ skipVisibilityCheck: true
609
632
  });
610
633
  }
611
634
  };
@@ -749,7 +772,7 @@ var Accordion = class _Accordion {
749
772
  addTokenToAttribute(trigger2, "aria-controls", content2.id);
750
773
  trigger2.setAttribute(
751
774
  "aria-expanded",
752
- trigger2.ariaExpanded === "true" ? "true" : "false"
775
+ String(trigger2.ariaExpanded === "true")
753
776
  );
754
777
  trigger2.id ||= `accordion-trigger-${id}`;
755
778
  if (!isFocusable2(trigger2)) {
@@ -840,6 +863,9 @@ var Accordion = class _Accordion {
840
863
  animation.addEventListener(
841
864
  "finish",
842
865
  () => {
866
+ if (binding?.animation !== animation) {
867
+ return;
868
+ }
843
869
  this.#onAnimationFinish(content);
844
870
  cleanup();
845
871
  },
@@ -892,7 +918,7 @@ function waitAnimationFinish(animation) {
892
918
  * Accordion
893
919
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
894
920
  *
895
- * @version 1.4.5
921
+ * @version 1.4.7
896
922
  * @author Yusuke Kamiyamane
897
923
  * @license MIT
898
924
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -904,7 +930,7 @@ function waitAnimationFinish(animation) {
904
930
  (**
905
931
  * Attributes Utils
906
932
  *
907
- * @version 1.1.0
933
+ * @version 1.1.1
908
934
  * @author Yusuke Kamiyamane
909
935
  * @license MIT
910
936
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -928,7 +954,7 @@ function waitAnimationFinish(animation) {
928
954
  * Lightweight roving tabindex utility with fully focus management.
929
955
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
930
956
  *
931
- * @version 2.0.4
957
+ * @version 3.0.0
932
958
  * @author Yusuke Kamiyamane
933
959
  * @license MIT
934
960
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -940,7 +966,7 @@ function waitAnimationFinish(animation) {
940
966
  (**
941
967
  * Attributes Utils
942
968
  *
943
- * @version 1.1.0
969
+ * @version 1.1.1
944
970
  * @author Yusuke Kamiyamane
945
971
  * @license MIT
946
972
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -953,7 +979,7 @@ function waitAnimationFinish(animation) {
953
979
  * High-precision focus management utility with full composed tree support.
954
980
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
955
981
  *
956
- * @version 4.3.1
982
+ * @version 4.3.2
957
983
  * @author Yusuke Kamiyamane
958
984
  * @license MIT
959
985
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Accordion
3
3
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
4
4
  *
5
- * @version 1.4.5
5
+ * @version 1.4.7
6
6
  * @author Yusuke Kamiyamane
7
7
  * @license MIT
8
8
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  * Accordion
3
3
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
4
4
  *
5
- * @version 1.4.5
5
+ * @version 1.4.7
6
6
  * @author Yusuke Kamiyamane
7
7
  * @license MIT
8
8
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.js CHANGED
@@ -11,11 +11,10 @@ function addTokenToAttribute(element, attribute, token, options = {}) {
11
11
  const tokens = value ? parse(value).filter(Boolean) : [];
12
12
  if (caseInsensitive) {
13
13
  const lower = token.toLowerCase();
14
- if (tokens.some((token2) => token2.toLowerCase() === lower)) {
15
- return;
14
+ if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
15
+ tokens.push(token);
16
+ element.setAttribute(attribute, serialize(tokens));
16
17
  }
17
- tokens.push(token);
18
- element.setAttribute(attribute, serialize(tokens));
19
18
  return;
20
19
  }
21
20
  const set = new Set(tokens);
@@ -117,11 +116,10 @@ function addTokenToAttribute2(element, attribute, token, options = {}) {
117
116
  const tokens = value ? parse(value).filter(Boolean) : [];
118
117
  if (caseInsensitive) {
119
118
  const lower = token.toLowerCase();
120
- if (tokens.some((token2) => token2.toLowerCase() === lower)) {
121
- return;
119
+ if (tokens.every((token2) => token2.toLowerCase() !== lower)) {
120
+ tokens.push(token);
121
+ element.setAttribute(attribute, serialize(tokens));
122
122
  }
123
- tokens.push(token);
124
- element.setAttribute(attribute, serialize(tokens));
125
123
  return;
126
124
  }
127
125
  const set = new Set(tokens);
@@ -400,6 +398,8 @@ function createRovingTabIndex(container, options = {}) {
400
398
  let {
401
399
  direction,
402
400
  navigationOnly = false,
401
+ noMemory = false,
402
+ noStart = false,
403
403
  selector,
404
404
  typeahead = false,
405
405
  wrap = false
@@ -412,6 +412,14 @@ function createRovingTabIndex(container, options = {}) {
412
412
  console.warn("Invalid navigationOnly option. Fallback: false.");
413
413
  navigationOnly = false;
414
414
  }
415
+ if (typeof noMemory !== "boolean") {
416
+ console.warn("Invalid noMemory option. Fallback: false.");
417
+ noMemory = false;
418
+ }
419
+ if (typeof noStart !== "boolean") {
420
+ console.warn("Invalid noStart option. Fallback: false.");
421
+ noStart = false;
422
+ }
415
423
  if (typeof selector !== "undefined" && (typeof selector !== "string" || !selector.trim())) {
416
424
  console.warn(
417
425
  "Invalid selector. Fallback: all focusable elements (undefined)."
@@ -426,13 +434,15 @@ function createRovingTabIndex(container, options = {}) {
426
434
  console.warn("Invalid wrap option. Fallback: false.");
427
435
  wrap = false;
428
436
  }
429
- const settings = { navigationOnly, typeahead, wrap };
430
- if (direction) {
431
- Object.assign(settings, { direction });
432
- }
433
- if (selector) {
434
- Object.assign(settings, { selector });
435
- }
437
+ const settings = {
438
+ navigationOnly,
439
+ noMemory,
440
+ noStart,
441
+ typeahead,
442
+ wrap
443
+ };
444
+ direction && Object.assign(settings, { direction });
445
+ selector && Object.assign(settings, { selector });
436
446
  const roving = new RovingTabIndex(container, settings);
437
447
  return () => roving.destroy();
438
448
  }
@@ -465,12 +475,29 @@ var RovingTabIndex = class {
465
475
  #initialize() {
466
476
  this.#update(document.activeElement);
467
477
  this.#controller = new AbortController();
478
+ const { signal } = this.#controller;
479
+ document.addEventListener("focusin", this.#onFocusIn, {
480
+ capture: true,
481
+ signal
482
+ });
468
483
  document.addEventListener("keydown", this.#onKeyDown, {
469
484
  capture: true,
470
- signal: this.#controller.signal
485
+ signal
471
486
  });
472
487
  this.#container.setAttribute("data-roving-tabindex-initialized", "");
473
488
  }
489
+ #onFocusIn = (event) => {
490
+ const { target } = event;
491
+ if (!(target instanceof Element)) {
492
+ return;
493
+ }
494
+ const isFocusable22 = this.#focusables.has(target);
495
+ if (this.#options.noMemory && !isFocusable22) {
496
+ this.#update(null);
497
+ return;
498
+ }
499
+ isFocusable22 && this.#update(target);
500
+ };
474
501
  #onKeyDown = (event) => {
475
502
  if (!event.composedPath().includes(this.#container)) {
476
503
  return;
@@ -501,7 +528,6 @@ var RovingTabIndex = class {
501
528
  return;
502
529
  }
503
530
  event.preventDefault();
504
- event.stopPropagation();
505
531
  const currentIndex = current.indexOf(active);
506
532
  let rawIndex;
507
533
  let newIndex = currentIndex;
@@ -532,11 +558,7 @@ var RovingTabIndex = class {
532
558
  }
533
559
  }
534
560
  const focusable = target.at(newIndex);
535
- if (!focusable) {
536
- return;
537
- }
538
- this.#update(focusable);
539
- focusElement(focusable);
561
+ focusable && focusElement(focusable);
540
562
  };
541
563
  #update(active) {
542
564
  const current = new Set(this.#getFocusables());
@@ -551,7 +573,7 @@ var RovingTabIndex = class {
551
573
  index >= 0 && focusables.splice(index, 1);
552
574
  });
553
575
  }
554
- const { navigationOnly } = this.#options;
576
+ const { navigationOnly, noStart, typeahead } = this.#options;
555
577
  for (const focusable of current) {
556
578
  if (this.#focusables.has(focusable)) {
557
579
  continue;
@@ -561,7 +583,7 @@ var RovingTabIndex = class {
561
583
  saveAttributes2([focusable], ["tabindex"]);
562
584
  focusable.setAttribute("tabindex", "-1");
563
585
  }
564
- if (!this.#options.typeahead) {
586
+ if (!typeahead) {
565
587
  continue;
566
588
  }
567
589
  const value = focusable.ariaKeyShortcuts?.trim();
@@ -592,7 +614,7 @@ var RovingTabIndex = class {
592
614
  return;
593
615
  }
594
616
  [...this.#focusables].forEach((focusable, i) => {
595
- focusable.setAttribute("tabindex", i ? "-1" : "0");
617
+ focusable.setAttribute("tabindex", i || noStart ? "-1" : "0");
596
618
  });
597
619
  }
598
620
  #createSelectorFilter() {
@@ -603,7 +625,8 @@ var RovingTabIndex = class {
603
625
  return getFocusables(this.#container, {
604
626
  composed: true,
605
627
  filter: this.#selectorFilter,
606
- skipNegativeTabIndexCheck: !this.#options.navigationOnly
628
+ skipNegativeTabIndexCheck: !this.#options.navigationOnly,
629
+ skipVisibilityCheck: true
607
630
  });
608
631
  }
609
632
  };
@@ -747,7 +770,7 @@ var Accordion = class _Accordion {
747
770
  addTokenToAttribute(trigger2, "aria-controls", content2.id);
748
771
  trigger2.setAttribute(
749
772
  "aria-expanded",
750
- trigger2.ariaExpanded === "true" ? "true" : "false"
773
+ String(trigger2.ariaExpanded === "true")
751
774
  );
752
775
  trigger2.id ||= `accordion-trigger-${id}`;
753
776
  if (!isFocusable2(trigger2)) {
@@ -838,6 +861,9 @@ var Accordion = class _Accordion {
838
861
  animation.addEventListener(
839
862
  "finish",
840
863
  () => {
864
+ if (binding?.animation !== animation) {
865
+ return;
866
+ }
841
867
  this.#onAnimationFinish(content);
842
868
  cleanup();
843
869
  },
@@ -890,7 +916,7 @@ function waitAnimationFinish(animation) {
890
916
  * Accordion
891
917
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
892
918
  *
893
- * @version 1.4.5
919
+ * @version 1.4.7
894
920
  * @author Yusuke Kamiyamane
895
921
  * @license MIT
896
922
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -902,7 +928,7 @@ function waitAnimationFinish(animation) {
902
928
  (**
903
929
  * Attributes Utils
904
930
  *
905
- * @version 1.1.0
931
+ * @version 1.1.1
906
932
  * @author Yusuke Kamiyamane
907
933
  * @license MIT
908
934
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -926,7 +952,7 @@ function waitAnimationFinish(animation) {
926
952
  * Lightweight roving tabindex utility with fully focus management.
927
953
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
928
954
  *
929
- * @version 2.0.4
955
+ * @version 3.0.0
930
956
  * @author Yusuke Kamiyamane
931
957
  * @license MIT
932
958
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -938,7 +964,7 @@ function waitAnimationFinish(animation) {
938
964
  (**
939
965
  * Attributes Utils
940
966
  *
941
- * @version 1.1.0
967
+ * @version 1.1.1
942
968
  * @author Yusuke Kamiyamane
943
969
  * @license MIT
944
970
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -951,7 +977,7 @@ function waitAnimationFinish(animation) {
951
977
  * High-precision focus management utility with full composed tree support.
952
978
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
953
979
  *
954
- * @version 4.3.1
980
+ * @version 4.3.2
955
981
  * @author Yusuke Kamiyamane
956
982
  * @license MIT
957
983
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/accordion",
3
- "version": "1.4.5",
3
+ "version": "1.4.7",
4
4
  "description": "WAI-ARIA compliant accordion pattern implementation in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -43,9 +43,9 @@
43
43
  },
44
44
  "homepage": "https://github.com/y14e/accordion#readme",
45
45
  "devDependencies": {
46
- "@y14e/attributes-utils": "^1.1.0",
46
+ "@y14e/attributes-utils": "^1.1.1",
47
47
  "@y14e/button": "^1.0.0",
48
- "@y14e/roving-tabindex": "^2.0.4",
48
+ "@y14e/roving-tabindex": "^3.0.0",
49
49
  "bun-types": "latest",
50
50
  "tsup": "^8.0.0",
51
51
  "typescript": "^5.6.0",