@y14e/disclosure-css 2.0.0 → 2.0.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/README.md +25 -1
- package/dist/index.bundle.cjs +61 -25
- package/dist/index.bundle.js +61 -25
- package/dist/index.cjs +42 -9
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +42 -9
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -23,9 +23,33 @@ import Disclosure from 'https://esm.unpkg.com/@y14e/disclosure-css@2.0.0';
|
|
|
23
23
|
## Usage
|
|
24
24
|
|
|
25
25
|
```ts
|
|
26
|
-
new Disclosure(root);
|
|
26
|
+
new Disclosure(root, options);
|
|
27
27
|
// => Disclosure
|
|
28
|
+
//
|
|
29
|
+
// root: HTMLElement
|
|
30
|
+
// options (optional): DisclosureOptions
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🪄 Options
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
interface DisclosureOptions {
|
|
37
|
+
collapsible?: boolean; // default: true
|
|
38
|
+
}
|
|
39
|
+
```
|
|
28
40
|
|
|
41
|
+
### ⚙️ Customize defaults
|
|
42
|
+
|
|
43
|
+
Override the global default settings applied to all disclosure instances.
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
import Disclosure from '@y14e/disclosure-css';
|
|
47
|
+
|
|
48
|
+
Disclosure.defaults = {
|
|
49
|
+
collapsible: false,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
new Disclosure(root);
|
|
29
53
|
```
|
|
30
54
|
|
|
31
55
|
## 📦 APIs
|
package/dist/index.bundle.cjs
CHANGED
|
@@ -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();
|
|
@@ -372,29 +372,32 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
372
372
|
}
|
|
373
373
|
#initialize() {
|
|
374
374
|
this.#update(getActiveElement());
|
|
375
|
-
if (!(this.#container instanceof HTMLElement)) {
|
|
376
|
-
return;
|
|
377
|
-
}
|
|
378
375
|
this.#controller = new AbortController();
|
|
379
376
|
const { signal } = this.#controller;
|
|
380
|
-
|
|
381
|
-
this.#settings.noMemory &&
|
|
377
|
+
this.#container.addEventListener("focusin", this.#onFocusIn, { signal });
|
|
378
|
+
this.#settings.noMemory && this.#container.addEventListener("focusout", this.#onFocusOut, {
|
|
379
|
+
signal
|
|
380
|
+
});
|
|
382
381
|
this.#container.addEventListener("keydown", this.#onKeyDown, { signal });
|
|
383
382
|
}
|
|
384
383
|
#onFocusIn = (event) => {
|
|
385
|
-
|
|
386
|
-
if (!(target instanceof Element)) {
|
|
384
|
+
if (!(event instanceof FocusEvent)) {
|
|
387
385
|
return;
|
|
388
386
|
}
|
|
389
|
-
const
|
|
390
|
-
|
|
387
|
+
const { target } = event;
|
|
388
|
+
target instanceof Element && this.#update(target);
|
|
391
389
|
};
|
|
392
390
|
#onFocusOut = (event) => {
|
|
393
|
-
if (!event
|
|
394
|
-
|
|
391
|
+
if (!(event instanceof FocusEvent)) {
|
|
392
|
+
return;
|
|
395
393
|
}
|
|
394
|
+
const target = event.relatedTarget;
|
|
395
|
+
(!(target instanceof Element) || !this.#focusables.has(target)) && this.#update();
|
|
396
396
|
};
|
|
397
397
|
#onKeyDown = (event) => {
|
|
398
|
+
if (!(event instanceof KeyboardEvent)) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
398
401
|
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
399
402
|
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
400
403
|
return;
|
|
@@ -413,7 +416,7 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
413
416
|
}
|
|
414
417
|
}
|
|
415
418
|
const active = getActiveElement();
|
|
416
|
-
if (!(active instanceof
|
|
419
|
+
if (!(active instanceof Element)) {
|
|
417
420
|
return;
|
|
418
421
|
}
|
|
419
422
|
const current = this.#getFocusables();
|
|
@@ -528,15 +531,19 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
528
531
|
};
|
|
529
532
|
|
|
530
533
|
// src/index.ts
|
|
531
|
-
var Disclosure = class {
|
|
534
|
+
var Disclosure = class _Disclosure {
|
|
535
|
+
static defaults = {};
|
|
532
536
|
#rootElement;
|
|
537
|
+
#defaults = { collapsible: true };
|
|
538
|
+
#settings;
|
|
533
539
|
#detailsElements;
|
|
534
540
|
#summaryElements;
|
|
535
541
|
#contentElements;
|
|
536
542
|
#bindings = /* @__PURE__ */ new WeakMap();
|
|
543
|
+
#controller = null;
|
|
537
544
|
#cleanupRovingTabIndex = null;
|
|
538
545
|
#isDestroyed = false;
|
|
539
|
-
constructor(root) {
|
|
546
|
+
constructor(root, options = {}) {
|
|
540
547
|
if (!(root instanceof HTMLElement)) {
|
|
541
548
|
throw new TypeError("Invalid root element");
|
|
542
549
|
}
|
|
@@ -545,6 +552,8 @@ var Disclosure = class {
|
|
|
545
552
|
return;
|
|
546
553
|
}
|
|
547
554
|
this.#rootElement = root;
|
|
555
|
+
this.#defaults = this.#mergeOptions(this.#defaults, _Disclosure.defaults);
|
|
556
|
+
this.#settings = this.#mergeOptions(this.#defaults, options);
|
|
548
557
|
const NOT_NESTED = ":not(:scope summary + * *)";
|
|
549
558
|
this.#detailsElements = [
|
|
550
559
|
...this.#rootElement.querySelectorAll(
|
|
@@ -601,6 +610,8 @@ var Disclosure = class {
|
|
|
601
610
|
return;
|
|
602
611
|
}
|
|
603
612
|
this.#isDestroyed = true;
|
|
613
|
+
this.#controller?.abort();
|
|
614
|
+
this.#controller = null;
|
|
604
615
|
this.#cleanupRovingTabIndex?.();
|
|
605
616
|
this.#cleanupRovingTabIndex = null;
|
|
606
617
|
this.#detailsElements.length = 0;
|
|
@@ -625,10 +636,15 @@ var Disclosure = class {
|
|
|
625
636
|
"style",
|
|
626
637
|
"tabindex"
|
|
627
638
|
]);
|
|
628
|
-
this.#
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
summary
|
|
639
|
+
this.#controller = new AbortController();
|
|
640
|
+
const { signal } = this.#controller;
|
|
641
|
+
this.#summaryElements.forEach((summary) => {
|
|
642
|
+
if (!this.#isFocusable(summary)) {
|
|
643
|
+
summary.setAttribute("aria-disabled", "true");
|
|
644
|
+
summary.setAttribute("tabindex", "-1");
|
|
645
|
+
summary.style.setProperty("pointer-events", "none");
|
|
646
|
+
}
|
|
647
|
+
summary.addEventListener("click", this.#onSummaryClick, { signal });
|
|
632
648
|
});
|
|
633
649
|
this.#cleanupRovingTabIndex = createRovingTabIndex(this.#rootElement, {
|
|
634
650
|
direction: "vertical",
|
|
@@ -638,10 +654,27 @@ var Disclosure = class {
|
|
|
638
654
|
});
|
|
639
655
|
this.#rootElement.setAttribute("data-disclosure-initialized", "");
|
|
640
656
|
}
|
|
657
|
+
#onSummaryClick = (event) => {
|
|
658
|
+
event.preventDefault();
|
|
659
|
+
const summary = event.currentTarget;
|
|
660
|
+
if (!(summary instanceof HTMLElement)) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const binding = this.#bindings.get(summary);
|
|
664
|
+
if (!binding) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const { details } = binding;
|
|
668
|
+
this.#toggle(details, !details.open);
|
|
669
|
+
};
|
|
641
670
|
#toggle(details, isExpand) {
|
|
642
|
-
if (details.open
|
|
643
|
-
|
|
671
|
+
if (details.open === isExpand) {
|
|
672
|
+
return;
|
|
644
673
|
}
|
|
674
|
+
if (!isExpand && !this.#settings.collapsible && this.#detailsElements.filter((details2) => details2.open).length <= 1) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
details.open = isExpand;
|
|
645
678
|
}
|
|
646
679
|
#createBinding(details, summary, content) {
|
|
647
680
|
return { details, summary, content };
|
|
@@ -649,13 +682,16 @@ var Disclosure = class {
|
|
|
649
682
|
#isFocusable(element) {
|
|
650
683
|
return element.tabIndex >= 0;
|
|
651
684
|
}
|
|
685
|
+
#mergeOptions(target, source) {
|
|
686
|
+
return { ...target, ...source };
|
|
687
|
+
}
|
|
652
688
|
};
|
|
653
689
|
/**
|
|
654
690
|
* Disclosure (CSS Animation)
|
|
655
691
|
* WAI-ARIA compliant disclosure pattern implementation in TypeScript.
|
|
656
692
|
* Using the <details> and <summary> element.
|
|
657
693
|
*
|
|
658
|
-
* @version 2.0.
|
|
694
|
+
* @version 2.0.1
|
|
659
695
|
* @author Yusuke Kamiyamane
|
|
660
696
|
* @license MIT
|
|
661
697
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -667,7 +703,7 @@ var Disclosure = class {
|
|
|
667
703
|
(**
|
|
668
704
|
* Attributes Utils
|
|
669
705
|
*
|
|
670
|
-
* @version 1.1.
|
|
706
|
+
* @version 1.1.3
|
|
671
707
|
* @author Yusuke Kamiyamane
|
|
672
708
|
* @license MIT
|
|
673
709
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -680,7 +716,7 @@ power-focusable/dist/index.js:
|
|
|
680
716
|
* High-precision focus management utility with full composed tree support.
|
|
681
717
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
682
718
|
*
|
|
683
|
-
* @version 4.3.
|
|
719
|
+
* @version 4.3.8
|
|
684
720
|
* @author Yusuke Kamiyamane
|
|
685
721
|
* @license MIT
|
|
686
722
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -693,7 +729,7 @@ power-focusable/dist/index.js:
|
|
|
693
729
|
* Lightweight roving tabindex utility with fully focus management.
|
|
694
730
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
695
731
|
*
|
|
696
|
-
* @version 3.1.
|
|
732
|
+
* @version 3.1.6
|
|
697
733
|
* @author Yusuke Kamiyamane
|
|
698
734
|
* @license MIT
|
|
699
735
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.bundle.js
CHANGED
|
@@ -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();
|
|
@@ -370,29 +370,32 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
370
370
|
}
|
|
371
371
|
#initialize() {
|
|
372
372
|
this.#update(getActiveElement());
|
|
373
|
-
if (!(this.#container instanceof HTMLElement)) {
|
|
374
|
-
return;
|
|
375
|
-
}
|
|
376
373
|
this.#controller = new AbortController();
|
|
377
374
|
const { signal } = this.#controller;
|
|
378
|
-
|
|
379
|
-
this.#settings.noMemory &&
|
|
375
|
+
this.#container.addEventListener("focusin", this.#onFocusIn, { signal });
|
|
376
|
+
this.#settings.noMemory && this.#container.addEventListener("focusout", this.#onFocusOut, {
|
|
377
|
+
signal
|
|
378
|
+
});
|
|
380
379
|
this.#container.addEventListener("keydown", this.#onKeyDown, { signal });
|
|
381
380
|
}
|
|
382
381
|
#onFocusIn = (event) => {
|
|
383
|
-
|
|
384
|
-
if (!(target instanceof Element)) {
|
|
382
|
+
if (!(event instanceof FocusEvent)) {
|
|
385
383
|
return;
|
|
386
384
|
}
|
|
387
|
-
const
|
|
388
|
-
|
|
385
|
+
const { target } = event;
|
|
386
|
+
target instanceof Element && this.#update(target);
|
|
389
387
|
};
|
|
390
388
|
#onFocusOut = (event) => {
|
|
391
|
-
if (!event
|
|
392
|
-
|
|
389
|
+
if (!(event instanceof FocusEvent)) {
|
|
390
|
+
return;
|
|
393
391
|
}
|
|
392
|
+
const target = event.relatedTarget;
|
|
393
|
+
(!(target instanceof Element) || !this.#focusables.has(target)) && this.#update();
|
|
394
394
|
};
|
|
395
395
|
#onKeyDown = (event) => {
|
|
396
|
+
if (!(event instanceof KeyboardEvent)) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
396
399
|
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
397
400
|
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
398
401
|
return;
|
|
@@ -411,7 +414,7 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
411
414
|
}
|
|
412
415
|
}
|
|
413
416
|
const active = getActiveElement();
|
|
414
|
-
if (!(active instanceof
|
|
417
|
+
if (!(active instanceof Element)) {
|
|
415
418
|
return;
|
|
416
419
|
}
|
|
417
420
|
const current = this.#getFocusables();
|
|
@@ -526,15 +529,19 @@ var RovingTabIndex = class _RovingTabIndex {
|
|
|
526
529
|
};
|
|
527
530
|
|
|
528
531
|
// src/index.ts
|
|
529
|
-
var Disclosure = class {
|
|
532
|
+
var Disclosure = class _Disclosure {
|
|
533
|
+
static defaults = {};
|
|
530
534
|
#rootElement;
|
|
535
|
+
#defaults = { collapsible: true };
|
|
536
|
+
#settings;
|
|
531
537
|
#detailsElements;
|
|
532
538
|
#summaryElements;
|
|
533
539
|
#contentElements;
|
|
534
540
|
#bindings = /* @__PURE__ */ new WeakMap();
|
|
541
|
+
#controller = null;
|
|
535
542
|
#cleanupRovingTabIndex = null;
|
|
536
543
|
#isDestroyed = false;
|
|
537
|
-
constructor(root) {
|
|
544
|
+
constructor(root, options = {}) {
|
|
538
545
|
if (!(root instanceof HTMLElement)) {
|
|
539
546
|
throw new TypeError("Invalid root element");
|
|
540
547
|
}
|
|
@@ -543,6 +550,8 @@ var Disclosure = class {
|
|
|
543
550
|
return;
|
|
544
551
|
}
|
|
545
552
|
this.#rootElement = root;
|
|
553
|
+
this.#defaults = this.#mergeOptions(this.#defaults, _Disclosure.defaults);
|
|
554
|
+
this.#settings = this.#mergeOptions(this.#defaults, options);
|
|
546
555
|
const NOT_NESTED = ":not(:scope summary + * *)";
|
|
547
556
|
this.#detailsElements = [
|
|
548
557
|
...this.#rootElement.querySelectorAll(
|
|
@@ -599,6 +608,8 @@ var Disclosure = class {
|
|
|
599
608
|
return;
|
|
600
609
|
}
|
|
601
610
|
this.#isDestroyed = true;
|
|
611
|
+
this.#controller?.abort();
|
|
612
|
+
this.#controller = null;
|
|
602
613
|
this.#cleanupRovingTabIndex?.();
|
|
603
614
|
this.#cleanupRovingTabIndex = null;
|
|
604
615
|
this.#detailsElements.length = 0;
|
|
@@ -623,10 +634,15 @@ var Disclosure = class {
|
|
|
623
634
|
"style",
|
|
624
635
|
"tabindex"
|
|
625
636
|
]);
|
|
626
|
-
this.#
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
summary
|
|
637
|
+
this.#controller = new AbortController();
|
|
638
|
+
const { signal } = this.#controller;
|
|
639
|
+
this.#summaryElements.forEach((summary) => {
|
|
640
|
+
if (!this.#isFocusable(summary)) {
|
|
641
|
+
summary.setAttribute("aria-disabled", "true");
|
|
642
|
+
summary.setAttribute("tabindex", "-1");
|
|
643
|
+
summary.style.setProperty("pointer-events", "none");
|
|
644
|
+
}
|
|
645
|
+
summary.addEventListener("click", this.#onSummaryClick, { signal });
|
|
630
646
|
});
|
|
631
647
|
this.#cleanupRovingTabIndex = createRovingTabIndex(this.#rootElement, {
|
|
632
648
|
direction: "vertical",
|
|
@@ -636,10 +652,27 @@ var Disclosure = class {
|
|
|
636
652
|
});
|
|
637
653
|
this.#rootElement.setAttribute("data-disclosure-initialized", "");
|
|
638
654
|
}
|
|
655
|
+
#onSummaryClick = (event) => {
|
|
656
|
+
event.preventDefault();
|
|
657
|
+
const summary = event.currentTarget;
|
|
658
|
+
if (!(summary instanceof HTMLElement)) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const binding = this.#bindings.get(summary);
|
|
662
|
+
if (!binding) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
const { details } = binding;
|
|
666
|
+
this.#toggle(details, !details.open);
|
|
667
|
+
};
|
|
639
668
|
#toggle(details, isExpand) {
|
|
640
|
-
if (details.open
|
|
641
|
-
|
|
669
|
+
if (details.open === isExpand) {
|
|
670
|
+
return;
|
|
642
671
|
}
|
|
672
|
+
if (!isExpand && !this.#settings.collapsible && this.#detailsElements.filter((details2) => details2.open).length <= 1) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
details.open = isExpand;
|
|
643
676
|
}
|
|
644
677
|
#createBinding(details, summary, content) {
|
|
645
678
|
return { details, summary, content };
|
|
@@ -647,13 +680,16 @@ var Disclosure = class {
|
|
|
647
680
|
#isFocusable(element) {
|
|
648
681
|
return element.tabIndex >= 0;
|
|
649
682
|
}
|
|
683
|
+
#mergeOptions(target, source) {
|
|
684
|
+
return { ...target, ...source };
|
|
685
|
+
}
|
|
650
686
|
};
|
|
651
687
|
/**
|
|
652
688
|
* Disclosure (CSS Animation)
|
|
653
689
|
* WAI-ARIA compliant disclosure pattern implementation in TypeScript.
|
|
654
690
|
* Using the <details> and <summary> element.
|
|
655
691
|
*
|
|
656
|
-
* @version 2.0.
|
|
692
|
+
* @version 2.0.1
|
|
657
693
|
* @author Yusuke Kamiyamane
|
|
658
694
|
* @license MIT
|
|
659
695
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -665,7 +701,7 @@ var Disclosure = class {
|
|
|
665
701
|
(**
|
|
666
702
|
* Attributes Utils
|
|
667
703
|
*
|
|
668
|
-
* @version 1.1.
|
|
704
|
+
* @version 1.1.3
|
|
669
705
|
* @author Yusuke Kamiyamane
|
|
670
706
|
* @license MIT
|
|
671
707
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -678,7 +714,7 @@ power-focusable/dist/index.js:
|
|
|
678
714
|
* High-precision focus management utility with full composed tree support.
|
|
679
715
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
680
716
|
*
|
|
681
|
-
* @version 4.3.
|
|
717
|
+
* @version 4.3.8
|
|
682
718
|
* @author Yusuke Kamiyamane
|
|
683
719
|
* @license MIT
|
|
684
720
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -691,7 +727,7 @@ power-focusable/dist/index.js:
|
|
|
691
727
|
* Lightweight roving tabindex utility with fully focus management.
|
|
692
728
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
693
729
|
*
|
|
694
|
-
* @version 3.1.
|
|
730
|
+
* @version 3.1.6
|
|
695
731
|
* @author Yusuke Kamiyamane
|
|
696
732
|
* @license MIT
|
|
697
733
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.cjs
CHANGED
|
@@ -4,15 +4,19 @@ var attributesUtils = require('@y14e/attributes-utils');
|
|
|
4
4
|
var rovingTabindex = require('@y14e/roving-tabindex');
|
|
5
5
|
|
|
6
6
|
// src/index.ts
|
|
7
|
-
var Disclosure = class {
|
|
7
|
+
var Disclosure = class _Disclosure {
|
|
8
|
+
static defaults = {};
|
|
8
9
|
#rootElement;
|
|
10
|
+
#defaults = { collapsible: true };
|
|
11
|
+
#settings;
|
|
9
12
|
#detailsElements;
|
|
10
13
|
#summaryElements;
|
|
11
14
|
#contentElements;
|
|
12
15
|
#bindings = /* @__PURE__ */ new WeakMap();
|
|
16
|
+
#controller = null;
|
|
13
17
|
#cleanupRovingTabIndex = null;
|
|
14
18
|
#isDestroyed = false;
|
|
15
|
-
constructor(root) {
|
|
19
|
+
constructor(root, options = {}) {
|
|
16
20
|
if (!(root instanceof HTMLElement)) {
|
|
17
21
|
throw new TypeError("Invalid root element");
|
|
18
22
|
}
|
|
@@ -21,6 +25,8 @@ var Disclosure = class {
|
|
|
21
25
|
return;
|
|
22
26
|
}
|
|
23
27
|
this.#rootElement = root;
|
|
28
|
+
this.#defaults = this.#mergeOptions(this.#defaults, _Disclosure.defaults);
|
|
29
|
+
this.#settings = this.#mergeOptions(this.#defaults, options);
|
|
24
30
|
const NOT_NESTED = ":not(:scope summary + * *)";
|
|
25
31
|
this.#detailsElements = [
|
|
26
32
|
...this.#rootElement.querySelectorAll(
|
|
@@ -77,6 +83,8 @@ var Disclosure = class {
|
|
|
77
83
|
return;
|
|
78
84
|
}
|
|
79
85
|
this.#isDestroyed = true;
|
|
86
|
+
this.#controller?.abort();
|
|
87
|
+
this.#controller = null;
|
|
80
88
|
this.#cleanupRovingTabIndex?.();
|
|
81
89
|
this.#cleanupRovingTabIndex = null;
|
|
82
90
|
this.#detailsElements.length = 0;
|
|
@@ -101,10 +109,15 @@ var Disclosure = class {
|
|
|
101
109
|
"style",
|
|
102
110
|
"tabindex"
|
|
103
111
|
]);
|
|
104
|
-
this.#
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
summary
|
|
112
|
+
this.#controller = new AbortController();
|
|
113
|
+
const { signal } = this.#controller;
|
|
114
|
+
this.#summaryElements.forEach((summary) => {
|
|
115
|
+
if (!this.#isFocusable(summary)) {
|
|
116
|
+
summary.setAttribute("aria-disabled", "true");
|
|
117
|
+
summary.setAttribute("tabindex", "-1");
|
|
118
|
+
summary.style.setProperty("pointer-events", "none");
|
|
119
|
+
}
|
|
120
|
+
summary.addEventListener("click", this.#onSummaryClick, { signal });
|
|
108
121
|
});
|
|
109
122
|
this.#cleanupRovingTabIndex = rovingTabindex.createRovingTabIndex(this.#rootElement, {
|
|
110
123
|
direction: "vertical",
|
|
@@ -114,10 +127,27 @@ var Disclosure = class {
|
|
|
114
127
|
});
|
|
115
128
|
this.#rootElement.setAttribute("data-disclosure-initialized", "");
|
|
116
129
|
}
|
|
130
|
+
#onSummaryClick = (event) => {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
const summary = event.currentTarget;
|
|
133
|
+
if (!(summary instanceof HTMLElement)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const binding = this.#bindings.get(summary);
|
|
137
|
+
if (!binding) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const { details } = binding;
|
|
141
|
+
this.#toggle(details, !details.open);
|
|
142
|
+
};
|
|
117
143
|
#toggle(details, isExpand) {
|
|
118
|
-
if (details.open
|
|
119
|
-
|
|
144
|
+
if (details.open === isExpand) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (!isExpand && !this.#settings.collapsible && this.#detailsElements.filter((details2) => details2.open).length <= 1) {
|
|
148
|
+
return;
|
|
120
149
|
}
|
|
150
|
+
details.open = isExpand;
|
|
121
151
|
}
|
|
122
152
|
#createBinding(details, summary, content) {
|
|
123
153
|
return { details, summary, content };
|
|
@@ -125,13 +155,16 @@ var Disclosure = class {
|
|
|
125
155
|
#isFocusable(element) {
|
|
126
156
|
return element.tabIndex >= 0;
|
|
127
157
|
}
|
|
158
|
+
#mergeOptions(target, source) {
|
|
159
|
+
return { ...target, ...source };
|
|
160
|
+
}
|
|
128
161
|
};
|
|
129
162
|
/**
|
|
130
163
|
* Disclosure (CSS Animation)
|
|
131
164
|
* WAI-ARIA compliant disclosure pattern implementation in TypeScript.
|
|
132
165
|
* Using the <details> and <summary> element.
|
|
133
166
|
*
|
|
134
|
-
* @version 2.0.
|
|
167
|
+
* @version 2.0.1
|
|
135
168
|
* @author Yusuke Kamiyamane
|
|
136
169
|
* @license MIT
|
|
137
170
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.d.cts
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
* WAI-ARIA compliant disclosure pattern implementation in TypeScript.
|
|
4
4
|
* Using the <details> and <summary> element.
|
|
5
5
|
*
|
|
6
|
-
* @version 2.0.
|
|
6
|
+
* @version 2.0.1
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
10
10
|
* @see {@link https://github.com/y14e/disclosure-css}
|
|
11
11
|
*/
|
|
12
|
+
interface DisclosureOptions {
|
|
13
|
+
readonly collapsible?: boolean;
|
|
14
|
+
}
|
|
12
15
|
declare class Disclosure {
|
|
13
16
|
#private;
|
|
14
|
-
|
|
17
|
+
static defaults: DisclosureOptions;
|
|
18
|
+
constructor(root: HTMLElement, options?: DisclosureOptions);
|
|
15
19
|
collapse(details: HTMLDetailsElement): void;
|
|
16
20
|
destroy(): void;
|
|
17
21
|
expand(details: HTMLDetailsElement): void;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export { Disclosure as default };
|
|
24
|
+
export { type DisclosureOptions, Disclosure as default };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
* WAI-ARIA compliant disclosure pattern implementation in TypeScript.
|
|
4
4
|
* Using the <details> and <summary> element.
|
|
5
5
|
*
|
|
6
|
-
* @version 2.0.
|
|
6
|
+
* @version 2.0.1
|
|
7
7
|
* @author Yusuke Kamiyamane
|
|
8
8
|
* @license MIT
|
|
9
9
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
10
10
|
* @see {@link https://github.com/y14e/disclosure-css}
|
|
11
11
|
*/
|
|
12
|
+
interface DisclosureOptions {
|
|
13
|
+
readonly collapsible?: boolean;
|
|
14
|
+
}
|
|
12
15
|
declare class Disclosure {
|
|
13
16
|
#private;
|
|
14
|
-
|
|
17
|
+
static defaults: DisclosureOptions;
|
|
18
|
+
constructor(root: HTMLElement, options?: DisclosureOptions);
|
|
15
19
|
collapse(details: HTMLDetailsElement): void;
|
|
16
20
|
destroy(): void;
|
|
17
21
|
expand(details: HTMLDetailsElement): void;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export { Disclosure as default };
|
|
24
|
+
export { type DisclosureOptions, Disclosure as default };
|
package/dist/index.js
CHANGED
|
@@ -2,15 +2,19 @@ import { restoreAttributes, saveAttributes } from '@y14e/attributes-utils';
|
|
|
2
2
|
import { createRovingTabIndex } from '@y14e/roving-tabindex';
|
|
3
3
|
|
|
4
4
|
// src/index.ts
|
|
5
|
-
var Disclosure = class {
|
|
5
|
+
var Disclosure = class _Disclosure {
|
|
6
|
+
static defaults = {};
|
|
6
7
|
#rootElement;
|
|
8
|
+
#defaults = { collapsible: true };
|
|
9
|
+
#settings;
|
|
7
10
|
#detailsElements;
|
|
8
11
|
#summaryElements;
|
|
9
12
|
#contentElements;
|
|
10
13
|
#bindings = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
#controller = null;
|
|
11
15
|
#cleanupRovingTabIndex = null;
|
|
12
16
|
#isDestroyed = false;
|
|
13
|
-
constructor(root) {
|
|
17
|
+
constructor(root, options = {}) {
|
|
14
18
|
if (!(root instanceof HTMLElement)) {
|
|
15
19
|
throw new TypeError("Invalid root element");
|
|
16
20
|
}
|
|
@@ -19,6 +23,8 @@ var Disclosure = class {
|
|
|
19
23
|
return;
|
|
20
24
|
}
|
|
21
25
|
this.#rootElement = root;
|
|
26
|
+
this.#defaults = this.#mergeOptions(this.#defaults, _Disclosure.defaults);
|
|
27
|
+
this.#settings = this.#mergeOptions(this.#defaults, options);
|
|
22
28
|
const NOT_NESTED = ":not(:scope summary + * *)";
|
|
23
29
|
this.#detailsElements = [
|
|
24
30
|
...this.#rootElement.querySelectorAll(
|
|
@@ -75,6 +81,8 @@ var Disclosure = class {
|
|
|
75
81
|
return;
|
|
76
82
|
}
|
|
77
83
|
this.#isDestroyed = true;
|
|
84
|
+
this.#controller?.abort();
|
|
85
|
+
this.#controller = null;
|
|
78
86
|
this.#cleanupRovingTabIndex?.();
|
|
79
87
|
this.#cleanupRovingTabIndex = null;
|
|
80
88
|
this.#detailsElements.length = 0;
|
|
@@ -99,10 +107,15 @@ var Disclosure = class {
|
|
|
99
107
|
"style",
|
|
100
108
|
"tabindex"
|
|
101
109
|
]);
|
|
102
|
-
this.#
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
summary
|
|
110
|
+
this.#controller = new AbortController();
|
|
111
|
+
const { signal } = this.#controller;
|
|
112
|
+
this.#summaryElements.forEach((summary) => {
|
|
113
|
+
if (!this.#isFocusable(summary)) {
|
|
114
|
+
summary.setAttribute("aria-disabled", "true");
|
|
115
|
+
summary.setAttribute("tabindex", "-1");
|
|
116
|
+
summary.style.setProperty("pointer-events", "none");
|
|
117
|
+
}
|
|
118
|
+
summary.addEventListener("click", this.#onSummaryClick, { signal });
|
|
106
119
|
});
|
|
107
120
|
this.#cleanupRovingTabIndex = createRovingTabIndex(this.#rootElement, {
|
|
108
121
|
direction: "vertical",
|
|
@@ -112,10 +125,27 @@ var Disclosure = class {
|
|
|
112
125
|
});
|
|
113
126
|
this.#rootElement.setAttribute("data-disclosure-initialized", "");
|
|
114
127
|
}
|
|
128
|
+
#onSummaryClick = (event) => {
|
|
129
|
+
event.preventDefault();
|
|
130
|
+
const summary = event.currentTarget;
|
|
131
|
+
if (!(summary instanceof HTMLElement)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const binding = this.#bindings.get(summary);
|
|
135
|
+
if (!binding) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const { details } = binding;
|
|
139
|
+
this.#toggle(details, !details.open);
|
|
140
|
+
};
|
|
115
141
|
#toggle(details, isExpand) {
|
|
116
|
-
if (details.open
|
|
117
|
-
|
|
142
|
+
if (details.open === isExpand) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (!isExpand && !this.#settings.collapsible && this.#detailsElements.filter((details2) => details2.open).length <= 1) {
|
|
146
|
+
return;
|
|
118
147
|
}
|
|
148
|
+
details.open = isExpand;
|
|
119
149
|
}
|
|
120
150
|
#createBinding(details, summary, content) {
|
|
121
151
|
return { details, summary, content };
|
|
@@ -123,13 +153,16 @@ var Disclosure = class {
|
|
|
123
153
|
#isFocusable(element) {
|
|
124
154
|
return element.tabIndex >= 0;
|
|
125
155
|
}
|
|
156
|
+
#mergeOptions(target, source) {
|
|
157
|
+
return { ...target, ...source };
|
|
158
|
+
}
|
|
126
159
|
};
|
|
127
160
|
/**
|
|
128
161
|
* Disclosure (CSS Animation)
|
|
129
162
|
* WAI-ARIA compliant disclosure pattern implementation in TypeScript.
|
|
130
163
|
* Using the <details> and <summary> element.
|
|
131
164
|
*
|
|
132
|
-
* @version 2.0.
|
|
165
|
+
* @version 2.0.1
|
|
133
166
|
* @author Yusuke Kamiyamane
|
|
134
167
|
* @license MIT
|
|
135
168
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@y14e/disclosure-css",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "WAI-ARIA compliant disclosure pattern implementation in TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -45,13 +45,14 @@
|
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"bun-types": "latest",
|
|
47
47
|
"tsup": "^8.0.0",
|
|
48
|
-
"typescript": "^5.6.0"
|
|
48
|
+
"typescript": "^5.6.0",
|
|
49
|
+
"utility-types": "^3.11.0"
|
|
49
50
|
},
|
|
50
51
|
"engines": {
|
|
51
52
|
"node": ">=18"
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
|
-
"@y14e/attributes-utils": "^1.1.
|
|
55
|
-
"@y14e/roving-tabindex": "^3.1.
|
|
55
|
+
"@y14e/attributes-utils": "^1.1.3",
|
|
56
|
+
"@y14e/roving-tabindex": "^3.1.6"
|
|
56
57
|
}
|
|
57
58
|
}
|