@y14e/accordion 2.0.1 → 2.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/README.md CHANGED
@@ -13,11 +13,11 @@ npm i @y14e/accordion
13
13
  import Accordion from '@y14e/accordion';
14
14
 
15
15
  // CDNs
16
- import Accordion from 'https://esm.sh/@y14e/accordion@2.0.1';
16
+ import Accordion from 'https://esm.sh/@y14e/accordion@2.0.3';
17
17
  // or
18
- import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion@2.0.1/+esm';
18
+ import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion@2.0.3/+esm';
19
19
  // or
20
- import Accordion from 'https://esm.unpkg.com/@y14e/accordion@2.0.1';
20
+ import Accordion from 'https://esm.unpkg.com/@y14e/accordion@2.0.3';
21
21
  ```
22
22
 
23
23
  ## Usage
@@ -35,13 +35,14 @@ new Accordion(root, options);
35
35
 
36
36
  ```ts
37
37
  interface AccordionOptions {
38
- animation?: {
39
- duration?: number; // ms (default: 300)
40
- easing?: string; // <easing-function> (default: 'ease')
38
+ animation: {
39
+ duration: number; // ms (default: 300)
40
+ easing: string; // <easing-function> (default: 'ease')
41
41
  };
42
- selector?: {
43
- content?: string; // default: '[data-accordion-content]'
44
- trigger?: string; // default: '[data-accordion-trigger]'
42
+ collapsible: boolean; // default: true
43
+ selector: {
44
+ content: string; // default: '[data-accordion-content]'
45
+ trigger: string; // default: '[data-accordion-trigger]'
45
46
  };
46
47
  }
47
48
  ```
@@ -4,12 +4,12 @@
4
4
  var DEFAULT_PARSER = (value) => value.split(/\s+/);
5
5
  var DEFAULT_SERIALIZER = (tokens) => tokens.join(" ");
6
6
  function addTokenToAttribute(element, attribute, token, options = {}) {
7
+ const value = element.getAttribute(attribute)?.trim();
7
8
  const {
8
9
  caseInsensitive = false,
9
10
  parse = DEFAULT_PARSER,
10
11
  serialize = DEFAULT_SERIALIZER
11
12
  } = options;
12
- const value = element.getAttribute(attribute)?.trim();
13
13
  const tokens = value ? parse(value).filter(Boolean) : [];
14
14
  if (caseInsensitive) {
15
15
  const lower = token.toLowerCase();
@@ -421,29 +421,32 @@ var RovingTabIndex = class _RovingTabIndex {
421
421
  }
422
422
  #initialize() {
423
423
  this.#update(getActiveElement());
424
- if (!(this.#container instanceof HTMLElement)) {
425
- return;
426
- }
427
424
  this.#controller = new AbortController();
428
425
  const { signal } = this.#controller;
429
- document.addEventListener("focusin", this.#onFocusIn, { signal });
430
- this.#settings.noMemory && document.addEventListener("focusout", this.#onFocusOut, { signal });
426
+ this.#container.addEventListener("focusin", this.#onFocusIn, { signal });
427
+ this.#settings.noMemory && this.#container.addEventListener("focusout", this.#onFocusOut, {
428
+ signal
429
+ });
431
430
  this.#container.addEventListener("keydown", this.#onKeyDown, { signal });
432
431
  }
433
432
  #onFocusIn = (event) => {
434
- const { target } = event;
435
- if (!(target instanceof Element)) {
433
+ if (!(event instanceof FocusEvent)) {
436
434
  return;
437
435
  }
438
- const isFocusable2 = this.#focusables.has(target);
439
- this.#settings.noMemory && !isFocusable2 ? this.#update() : isFocusable2 && this.#update(target);
436
+ const { target } = event;
437
+ target instanceof Element && this.#update(target);
440
438
  };
441
439
  #onFocusOut = (event) => {
442
- if (!event.relatedTarget) {
443
- this.#update();
440
+ if (!(event instanceof FocusEvent)) {
441
+ return;
444
442
  }
443
+ const target = event.relatedTarget;
444
+ (!(target instanceof Element) || !this.#focusables.has(target)) && this.#update();
445
445
  };
446
446
  #onKeyDown = (event) => {
447
+ if (!(event instanceof KeyboardEvent)) {
448
+ return;
449
+ }
447
450
  const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
448
451
  if (altKey || ctrlKey || metaKey || shiftKey) {
449
452
  return;
@@ -462,7 +465,7 @@ var RovingTabIndex = class _RovingTabIndex {
462
465
  }
463
466
  }
464
467
  const active = getActiveElement();
465
- if (!(active instanceof HTMLElement)) {
468
+ if (!(active instanceof Element)) {
466
469
  return;
467
470
  }
468
471
  const current = this.#getFocusables();
@@ -582,6 +585,7 @@ var Accordion = class _Accordion {
582
585
  #rootElement;
583
586
  #defaults = {
584
587
  animation: { duration: 300, easing: "ease" },
588
+ collapsible: true,
585
589
  selector: {
586
590
  content: "[data-accordion-content]",
587
591
  trigger: "[data-accordion-trigger]"
@@ -759,16 +763,19 @@ var Accordion = class _Accordion {
759
763
  }
760
764
  binding.trigger.ariaExpanded === "false" && this.#toggle(binding.trigger, true, true);
761
765
  };
762
- #toggle(trigger, isExpand, isMatch = false) {
766
+ #toggle(trigger, isExpand, isMatch = false, isProgrammatic = false) {
763
767
  if (trigger.ariaExpanded === String(isExpand)) {
764
768
  return;
765
769
  }
770
+ if (!isExpand && !isProgrammatic && !this.#settings.collapsible && this.#triggerElements.filter((trigger2) => trigger2.ariaExpanded === "true").length <= 1) {
771
+ return;
772
+ }
766
773
  const name = trigger.getAttribute("data-accordion-name");
767
774
  if (name && isExpand) {
768
775
  const expanded = this.#triggerElements.find(
769
776
  (t) => t !== trigger && t.getAttribute("data-accordion-name") === name && t.ariaExpanded === "true"
770
777
  );
771
- expanded && this.#toggle(expanded, false, isMatch);
778
+ expanded && this.#toggle(expanded, false, isMatch, true);
772
779
  }
773
780
  trigger.setAttribute(
774
781
  "aria-label",
@@ -822,6 +829,8 @@ var Accordion = class _Accordion {
822
829
  }
823
830
  #mergeOptions(target, source) {
824
831
  return {
832
+ ...target,
833
+ ...source,
825
834
  animation: { ...target.animation, ...source.animation ?? {} },
826
835
  selector: { ...target.selector, ...source.selector ?? {} }
827
836
  };
@@ -844,7 +853,7 @@ function waitAnimationFinish(animation) {
844
853
  * Accordion
845
854
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
846
855
  *
847
- * @version 2.0.1
856
+ * @version 2.0.3
848
857
  * @author Yusuke Kamiyamane
849
858
  * @license MIT
850
859
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -856,7 +865,7 @@ function waitAnimationFinish(animation) {
856
865
  (**
857
866
  * Attributes Utils
858
867
  *
859
- * @version 1.1.2
868
+ * @version 1.1.3
860
869
  * @author Yusuke Kamiyamane
861
870
  * @license MIT
862
871
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -869,7 +878,7 @@ power-focusable/dist/index.js:
869
878
  * High-precision focus management utility with full composed tree support.
870
879
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
871
880
  *
872
- * @version 4.3.6
881
+ * @version 4.3.10
873
882
  * @author Yusuke Kamiyamane
874
883
  * @license MIT
875
884
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -893,7 +902,7 @@ power-focusable/dist/index.js:
893
902
  * Lightweight roving tabindex utility with fully focus management.
894
903
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
895
904
  *
896
- * @version 3.1.3
905
+ * @version 3.1.9
897
906
  * @author Yusuke Kamiyamane
898
907
  * @license MIT
899
908
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -2,12 +2,12 @@
2
2
  var DEFAULT_PARSER = (value) => value.split(/\s+/);
3
3
  var DEFAULT_SERIALIZER = (tokens) => tokens.join(" ");
4
4
  function addTokenToAttribute(element, attribute, token, options = {}) {
5
+ const value = element.getAttribute(attribute)?.trim();
5
6
  const {
6
7
  caseInsensitive = false,
7
8
  parse = DEFAULT_PARSER,
8
9
  serialize = DEFAULT_SERIALIZER
9
10
  } = options;
10
- const value = element.getAttribute(attribute)?.trim();
11
11
  const tokens = value ? parse(value).filter(Boolean) : [];
12
12
  if (caseInsensitive) {
13
13
  const lower = token.toLowerCase();
@@ -419,29 +419,32 @@ var RovingTabIndex = class _RovingTabIndex {
419
419
  }
420
420
  #initialize() {
421
421
  this.#update(getActiveElement());
422
- if (!(this.#container instanceof HTMLElement)) {
423
- return;
424
- }
425
422
  this.#controller = new AbortController();
426
423
  const { signal } = this.#controller;
427
- document.addEventListener("focusin", this.#onFocusIn, { signal });
428
- this.#settings.noMemory && document.addEventListener("focusout", this.#onFocusOut, { signal });
424
+ this.#container.addEventListener("focusin", this.#onFocusIn, { signal });
425
+ this.#settings.noMemory && this.#container.addEventListener("focusout", this.#onFocusOut, {
426
+ signal
427
+ });
429
428
  this.#container.addEventListener("keydown", this.#onKeyDown, { signal });
430
429
  }
431
430
  #onFocusIn = (event) => {
432
- const { target } = event;
433
- if (!(target instanceof Element)) {
431
+ if (!(event instanceof FocusEvent)) {
434
432
  return;
435
433
  }
436
- const isFocusable2 = this.#focusables.has(target);
437
- this.#settings.noMemory && !isFocusable2 ? this.#update() : isFocusable2 && this.#update(target);
434
+ const { target } = event;
435
+ target instanceof Element && this.#update(target);
438
436
  };
439
437
  #onFocusOut = (event) => {
440
- if (!event.relatedTarget) {
441
- this.#update();
438
+ if (!(event instanceof FocusEvent)) {
439
+ return;
442
440
  }
441
+ const target = event.relatedTarget;
442
+ (!(target instanceof Element) || !this.#focusables.has(target)) && this.#update();
443
443
  };
444
444
  #onKeyDown = (event) => {
445
+ if (!(event instanceof KeyboardEvent)) {
446
+ return;
447
+ }
445
448
  const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
446
449
  if (altKey || ctrlKey || metaKey || shiftKey) {
447
450
  return;
@@ -460,7 +463,7 @@ var RovingTabIndex = class _RovingTabIndex {
460
463
  }
461
464
  }
462
465
  const active = getActiveElement();
463
- if (!(active instanceof HTMLElement)) {
466
+ if (!(active instanceof Element)) {
464
467
  return;
465
468
  }
466
469
  const current = this.#getFocusables();
@@ -580,6 +583,7 @@ var Accordion = class _Accordion {
580
583
  #rootElement;
581
584
  #defaults = {
582
585
  animation: { duration: 300, easing: "ease" },
586
+ collapsible: true,
583
587
  selector: {
584
588
  content: "[data-accordion-content]",
585
589
  trigger: "[data-accordion-trigger]"
@@ -757,16 +761,19 @@ var Accordion = class _Accordion {
757
761
  }
758
762
  binding.trigger.ariaExpanded === "false" && this.#toggle(binding.trigger, true, true);
759
763
  };
760
- #toggle(trigger, isExpand, isMatch = false) {
764
+ #toggle(trigger, isExpand, isMatch = false, isProgrammatic = false) {
761
765
  if (trigger.ariaExpanded === String(isExpand)) {
762
766
  return;
763
767
  }
768
+ if (!isExpand && !isProgrammatic && !this.#settings.collapsible && this.#triggerElements.filter((trigger2) => trigger2.ariaExpanded === "true").length <= 1) {
769
+ return;
770
+ }
764
771
  const name = trigger.getAttribute("data-accordion-name");
765
772
  if (name && isExpand) {
766
773
  const expanded = this.#triggerElements.find(
767
774
  (t) => t !== trigger && t.getAttribute("data-accordion-name") === name && t.ariaExpanded === "true"
768
775
  );
769
- expanded && this.#toggle(expanded, false, isMatch);
776
+ expanded && this.#toggle(expanded, false, isMatch, true);
770
777
  }
771
778
  trigger.setAttribute(
772
779
  "aria-label",
@@ -820,6 +827,8 @@ var Accordion = class _Accordion {
820
827
  }
821
828
  #mergeOptions(target, source) {
822
829
  return {
830
+ ...target,
831
+ ...source,
823
832
  animation: { ...target.animation, ...source.animation ?? {} },
824
833
  selector: { ...target.selector, ...source.selector ?? {} }
825
834
  };
@@ -842,7 +851,7 @@ function waitAnimationFinish(animation) {
842
851
  * Accordion
843
852
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
844
853
  *
845
- * @version 2.0.1
854
+ * @version 2.0.3
846
855
  * @author Yusuke Kamiyamane
847
856
  * @license MIT
848
857
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -854,7 +863,7 @@ function waitAnimationFinish(animation) {
854
863
  (**
855
864
  * Attributes Utils
856
865
  *
857
- * @version 1.1.2
866
+ * @version 1.1.3
858
867
  * @author Yusuke Kamiyamane
859
868
  * @license MIT
860
869
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -867,7 +876,7 @@ power-focusable/dist/index.js:
867
876
  * High-precision focus management utility with full composed tree support.
868
877
  * Handles complex focus rules including tabindex ordering, radio groups, inert.
869
878
  *
870
- * @version 4.3.6
879
+ * @version 4.3.10
871
880
  * @author Yusuke Kamiyamane
872
881
  * @license MIT
873
882
  * @copyright Copyright (c) Yusuke Kamiyamane
@@ -891,7 +900,7 @@ power-focusable/dist/index.js:
891
900
  * Lightweight roving tabindex utility with fully focus management.
892
901
  * Designed for accessible menus, tabs, toolbars, and composite widgets.
893
902
  *
894
- * @version 3.1.3
903
+ * @version 3.1.9
895
904
  * @author Yusuke Kamiyamane
896
905
  * @license MIT
897
906
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.cjs CHANGED
@@ -14,6 +14,7 @@ var Accordion = class _Accordion {
14
14
  #rootElement;
15
15
  #defaults = {
16
16
  animation: { duration: 300, easing: "ease" },
17
+ collapsible: true,
17
18
  selector: {
18
19
  content: "[data-accordion-content]",
19
20
  trigger: "[data-accordion-trigger]"
@@ -191,16 +192,19 @@ var Accordion = class _Accordion {
191
192
  }
192
193
  binding.trigger.ariaExpanded === "false" && this.#toggle(binding.trigger, true, true);
193
194
  };
194
- #toggle(trigger, isExpand, isMatch = false) {
195
+ #toggle(trigger, isExpand, isMatch = false, isProgrammatic = false) {
195
196
  if (trigger.ariaExpanded === String(isExpand)) {
196
197
  return;
197
198
  }
199
+ if (!isExpand && !isProgrammatic && !this.#settings.collapsible && this.#triggerElements.filter((trigger2) => trigger2.ariaExpanded === "true").length <= 1) {
200
+ return;
201
+ }
198
202
  const name = trigger.getAttribute("data-accordion-name");
199
203
  if (name && isExpand) {
200
204
  const expanded = this.#triggerElements.find(
201
205
  (t) => t !== trigger && t.getAttribute("data-accordion-name") === name && t.ariaExpanded === "true"
202
206
  );
203
- expanded && this.#toggle(expanded, false, isMatch);
207
+ expanded && this.#toggle(expanded, false, isMatch, true);
204
208
  }
205
209
  trigger.setAttribute(
206
210
  "aria-label",
@@ -254,6 +258,8 @@ var Accordion = class _Accordion {
254
258
  }
255
259
  #mergeOptions(target, source) {
256
260
  return {
261
+ ...target,
262
+ ...source,
257
263
  animation: { ...target.animation, ...source.animation ?? {} },
258
264
  selector: { ...target.selector, ...source.selector ?? {} }
259
265
  };
@@ -276,7 +282,7 @@ function waitAnimationFinish(animation) {
276
282
  * Accordion
277
283
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
278
284
  *
279
- * @version 2.0.1
285
+ * @version 2.0.3
280
286
  * @author Yusuke Kamiyamane
281
287
  * @license MIT
282
288
  * @copyright Copyright (c) Yusuke Kamiyamane
package/dist/index.d.cts CHANGED
@@ -2,26 +2,27 @@
2
2
  * Accordion
3
3
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
4
4
  *
5
- * @version 2.0.1
5
+ * @version 2.0.3
6
6
  * @author Yusuke Kamiyamane
7
7
  * @license MIT
8
8
  * @copyright Copyright (c) Yusuke Kamiyamane
9
9
  * @see {@link https://github.com/y14e/accordion}
10
10
  */
11
11
  interface AccordionOptions {
12
- readonly animation?: {
13
- readonly duration?: number;
14
- readonly easing?: string;
12
+ animation: {
13
+ duration: number;
14
+ easing: string;
15
15
  };
16
- readonly selector?: {
17
- readonly content?: string;
18
- readonly trigger?: string;
16
+ collapsible: boolean;
17
+ selector: {
18
+ content: string;
19
+ trigger: string;
19
20
  };
20
21
  }
21
22
  declare class Accordion {
22
23
  #private;
23
- static defaults: AccordionOptions;
24
- constructor(root: HTMLElement, options?: AccordionOptions);
24
+ static defaults: Partial<AccordionOptions>;
25
+ constructor(root: HTMLElement, options?: Partial<AccordionOptions>);
25
26
  collapse(trigger: HTMLElement): void;
26
27
  destroy(force?: boolean): Promise<void>;
27
28
  expand(trigger: HTMLElement): void;
package/dist/index.d.ts CHANGED
@@ -2,26 +2,27 @@
2
2
  * Accordion
3
3
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
4
4
  *
5
- * @version 2.0.1
5
+ * @version 2.0.3
6
6
  * @author Yusuke Kamiyamane
7
7
  * @license MIT
8
8
  * @copyright Copyright (c) Yusuke Kamiyamane
9
9
  * @see {@link https://github.com/y14e/accordion}
10
10
  */
11
11
  interface AccordionOptions {
12
- readonly animation?: {
13
- readonly duration?: number;
14
- readonly easing?: string;
12
+ animation: {
13
+ duration: number;
14
+ easing: string;
15
15
  };
16
- readonly selector?: {
17
- readonly content?: string;
18
- readonly trigger?: string;
16
+ collapsible: boolean;
17
+ selector: {
18
+ content: string;
19
+ trigger: string;
19
20
  };
20
21
  }
21
22
  declare class Accordion {
22
23
  #private;
23
- static defaults: AccordionOptions;
24
- constructor(root: HTMLElement, options?: AccordionOptions);
24
+ static defaults: Partial<AccordionOptions>;
25
+ constructor(root: HTMLElement, options?: Partial<AccordionOptions>);
25
26
  collapse(trigger: HTMLElement): void;
26
27
  destroy(force?: boolean): Promise<void>;
27
28
  expand(trigger: HTMLElement): void;
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var Accordion = class _Accordion {
8
8
  #rootElement;
9
9
  #defaults = {
10
10
  animation: { duration: 300, easing: "ease" },
11
+ collapsible: true,
11
12
  selector: {
12
13
  content: "[data-accordion-content]",
13
14
  trigger: "[data-accordion-trigger]"
@@ -185,16 +186,19 @@ var Accordion = class _Accordion {
185
186
  }
186
187
  binding.trigger.ariaExpanded === "false" && this.#toggle(binding.trigger, true, true);
187
188
  };
188
- #toggle(trigger, isExpand, isMatch = false) {
189
+ #toggle(trigger, isExpand, isMatch = false, isProgrammatic = false) {
189
190
  if (trigger.ariaExpanded === String(isExpand)) {
190
191
  return;
191
192
  }
193
+ if (!isExpand && !isProgrammatic && !this.#settings.collapsible && this.#triggerElements.filter((trigger2) => trigger2.ariaExpanded === "true").length <= 1) {
194
+ return;
195
+ }
192
196
  const name = trigger.getAttribute("data-accordion-name");
193
197
  if (name && isExpand) {
194
198
  const expanded = this.#triggerElements.find(
195
199
  (t) => t !== trigger && t.getAttribute("data-accordion-name") === name && t.ariaExpanded === "true"
196
200
  );
197
- expanded && this.#toggle(expanded, false, isMatch);
201
+ expanded && this.#toggle(expanded, false, isMatch, true);
198
202
  }
199
203
  trigger.setAttribute(
200
204
  "aria-label",
@@ -248,6 +252,8 @@ var Accordion = class _Accordion {
248
252
  }
249
253
  #mergeOptions(target, source) {
250
254
  return {
255
+ ...target,
256
+ ...source,
251
257
  animation: { ...target.animation, ...source.animation ?? {} },
252
258
  selector: { ...target.selector, ...source.selector ?? {} }
253
259
  };
@@ -270,7 +276,7 @@ function waitAnimationFinish(animation) {
270
276
  * Accordion
271
277
  * WAI-ARIA compliant accordion pattern implementation in TypeScript.
272
278
  *
273
- * @version 2.0.1
279
+ * @version 2.0.3
274
280
  * @author Yusuke Kamiyamane
275
281
  * @license MIT
276
282
  * @copyright Copyright (c) Yusuke Kamiyamane
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y14e/accordion",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "WAI-ARIA compliant accordion pattern implementation in TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -45,15 +45,14 @@
45
45
  "devDependencies": {
46
46
  "bun-types": "latest",
47
47
  "tsup": "^8.0.0",
48
- "typescript": "^5.6.0",
49
- "utility-types": "^3.11.0"
48
+ "typescript": "^5.6.0"
50
49
  },
51
50
  "engines": {
52
51
  "node": ">=18"
53
52
  },
54
53
  "dependencies": {
55
- "@y14e/attributes-utils": "^1.1.2",
54
+ "@y14e/attributes-utils": "^1.1.3",
56
55
  "@y14e/button": "^1.0.6",
57
- "@y14e/roving-tabindex": "^3.1.3"
56
+ "@y14e/roving-tabindex": "^3.1.9"
58
57
  }
59
58
  }