@y14e/accordion 1.3.3 → 1.4.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 +4 -4
- package/dist/index.cjs +118 -54
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +118 -54
- package/package.json +4 -3
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';
|
|
13
|
+
import Accordion from '@y14e/accordion@1.4.1';
|
|
14
14
|
|
|
15
15
|
// CDNs
|
|
16
|
-
import Accordion from 'https://esm.sh/@y14e/accordion'
|
|
16
|
+
import Accordion from 'https://esm.sh/@y14e/accordion@1.4.1';
|
|
17
17
|
// or
|
|
18
|
-
import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion/+esm';
|
|
18
|
+
import Accordion from 'https://cdn.jsdelivr.net/npm/@y14e/accordion@1.4.1/+esm';
|
|
19
19
|
// or
|
|
20
|
-
import Accordion from 'https://unpkg.com/@y14e/accordion
|
|
20
|
+
import Accordion from 'https://esm.unpkg.com/@y14e/accordion@1.4.1';
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
## Usage
|
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,62 @@ function saveAttributes(elements, attributes) {
|
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// node_modules/@y14e/button/dist/index.js
|
|
54
|
+
var Button = class {
|
|
55
|
+
#element;
|
|
56
|
+
#controller = null;
|
|
57
|
+
#isDestroyed = false;
|
|
58
|
+
constructor(element) {
|
|
59
|
+
if (!(element instanceof HTMLElement)) {
|
|
60
|
+
throw new TypeError("Invalid element");
|
|
61
|
+
}
|
|
62
|
+
if (element.hasAttribute("data-button-initialized")) {
|
|
63
|
+
console.warn("Already initialized");
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.#element = element;
|
|
67
|
+
this.#initialize();
|
|
68
|
+
}
|
|
69
|
+
destroy() {
|
|
70
|
+
if (this.#isDestroyed) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
this.#isDestroyed = true;
|
|
74
|
+
this.#controller?.abort();
|
|
75
|
+
this.#controller = null;
|
|
76
|
+
this.#element.removeAttribute("data-button-initialized");
|
|
77
|
+
}
|
|
78
|
+
#initialize() {
|
|
79
|
+
this.#controller = new AbortController();
|
|
80
|
+
this.#element.addEventListener("keydown", this.#onKeyDown, {
|
|
81
|
+
signal: this.#controller.signal
|
|
82
|
+
});
|
|
83
|
+
this.#element.setAttribute("data-button-initialized", "");
|
|
84
|
+
}
|
|
85
|
+
#onKeyDown = (event) => {
|
|
86
|
+
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
87
|
+
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!["Enter", " "].includes(key)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const active = getActiveElement();
|
|
94
|
+
if (!(active instanceof HTMLElement)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
active.click();
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
function getActiveElement() {
|
|
102
|
+
let current = document.activeElement;
|
|
103
|
+
while (current?.shadowRoot?.activeElement) {
|
|
104
|
+
current = current.shadowRoot.activeElement;
|
|
105
|
+
}
|
|
106
|
+
return current;
|
|
107
|
+
}
|
|
108
|
+
|
|
53
109
|
// node_modules/@y14e/roving-tabindex/dist/index.js
|
|
54
110
|
var defaultParser2 = (value) => value.split(/\s+/);
|
|
55
111
|
var defaultSerializer2 = (tokens) => tokens.join(" ");
|
|
@@ -109,6 +165,7 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
109
165
|
composed = false,
|
|
110
166
|
filter,
|
|
111
167
|
include,
|
|
168
|
+
skipNegativeTabIndexCheck = false,
|
|
112
169
|
skipVisibilityCheck = false
|
|
113
170
|
} = options;
|
|
114
171
|
if (typeof composed !== "boolean") {
|
|
@@ -127,11 +184,22 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
127
184
|
);
|
|
128
185
|
include = void 0;
|
|
129
186
|
}
|
|
187
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
188
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
189
|
+
skipNegativeTabIndexCheck = false;
|
|
190
|
+
}
|
|
191
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
192
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
193
|
+
skipVisibilityCheck = false;
|
|
194
|
+
}
|
|
130
195
|
const elements = [];
|
|
131
196
|
if (composed || include) {
|
|
132
197
|
let traverse2 = function(node) {
|
|
133
198
|
if (node instanceof Element) {
|
|
134
|
-
if (isFocusable(node, {
|
|
199
|
+
if (isFocusable(node, {
|
|
200
|
+
skipNegativeTabIndexCheck,
|
|
201
|
+
skipVisibilityCheck
|
|
202
|
+
}) || include?.(node)) {
|
|
135
203
|
elements[elements.length] = node;
|
|
136
204
|
}
|
|
137
205
|
}
|
|
@@ -152,7 +220,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
152
220
|
if (!(candidate instanceof Element)) {
|
|
153
221
|
continue;
|
|
154
222
|
}
|
|
155
|
-
if (isFocusable(candidate, {
|
|
223
|
+
if (isFocusable(candidate, {
|
|
224
|
+
skipNegativeTabIndexCheck,
|
|
225
|
+
skipVisibilityCheck
|
|
226
|
+
})) {
|
|
156
227
|
elements[elements.length] = candidate;
|
|
157
228
|
}
|
|
158
229
|
}
|
|
@@ -165,7 +236,11 @@ function isFocusable(element, options = {}) {
|
|
|
165
236
|
console.warn("Invalid element");
|
|
166
237
|
return false;
|
|
167
238
|
}
|
|
168
|
-
let { skipVisibilityCheck = false } = options;
|
|
239
|
+
let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
|
|
240
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
241
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
242
|
+
skipNegativeTabIndexCheck = false;
|
|
243
|
+
}
|
|
169
244
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
170
245
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
171
246
|
skipVisibilityCheck = false;
|
|
@@ -173,10 +248,12 @@ function isFocusable(element, options = {}) {
|
|
|
173
248
|
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
174
249
|
return false;
|
|
175
250
|
}
|
|
176
|
-
if (getTabIndex(element) < 0) {
|
|
251
|
+
if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
|
|
177
252
|
return false;
|
|
178
253
|
}
|
|
179
|
-
if (!element.matches(
|
|
254
|
+
if (!element.matches(
|
|
255
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
|
|
256
|
+
)) {
|
|
180
257
|
return false;
|
|
181
258
|
}
|
|
182
259
|
if (isDisabledDeep(element)) {
|
|
@@ -351,13 +428,14 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
351
428
|
console.warn("Invalid wrap option. Fallback: false.");
|
|
352
429
|
wrap = false;
|
|
353
430
|
}
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
431
|
+
const settings = { navigationOnly, typeahead, wrap };
|
|
432
|
+
if (direction !== void 0) {
|
|
433
|
+
Object.assign(settings, { direction });
|
|
434
|
+
}
|
|
435
|
+
if (selector !== void 0) {
|
|
436
|
+
Object.assign(settings, { selector });
|
|
437
|
+
}
|
|
438
|
+
const roving = new RovingTabIndex(container, settings);
|
|
361
439
|
return () => roving.destroy();
|
|
362
440
|
}
|
|
363
441
|
var RovingTabIndex = class {
|
|
@@ -399,8 +477,8 @@ var RovingTabIndex = class {
|
|
|
399
477
|
if (!event.composedPath().includes(this.#container)) {
|
|
400
478
|
return;
|
|
401
479
|
}
|
|
402
|
-
const { key, altKey, ctrlKey, metaKey } = event;
|
|
403
|
-
if (altKey || ctrlKey || metaKey) {
|
|
480
|
+
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
481
|
+
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
404
482
|
return;
|
|
405
483
|
}
|
|
406
484
|
const { direction, typeahead, wrap } = this.#options;
|
|
@@ -416,7 +494,7 @@ var RovingTabIndex = class {
|
|
|
416
494
|
return;
|
|
417
495
|
}
|
|
418
496
|
}
|
|
419
|
-
const active =
|
|
497
|
+
const active = getActiveElement2();
|
|
420
498
|
if (!(active instanceof HTMLElement)) {
|
|
421
499
|
return;
|
|
422
500
|
}
|
|
@@ -463,14 +541,7 @@ var RovingTabIndex = class {
|
|
|
463
541
|
focusElement(focusable);
|
|
464
542
|
};
|
|
465
543
|
#update(active) {
|
|
466
|
-
const current =
|
|
467
|
-
...this.#getFocusables(),
|
|
468
|
-
...getFocusables(this.#container, {
|
|
469
|
-
composed: true,
|
|
470
|
-
filter: this.#selectorFilter,
|
|
471
|
-
skipVisibilityCheck: true
|
|
472
|
-
})
|
|
473
|
-
]);
|
|
544
|
+
const current = new Set(this.#getFocusables());
|
|
474
545
|
for (const focusable of this.#focusables) {
|
|
475
546
|
if (current.has(focusable)) {
|
|
476
547
|
continue;
|
|
@@ -534,15 +605,14 @@ var RovingTabIndex = class {
|
|
|
534
605
|
return getFocusables(this.#container, {
|
|
535
606
|
composed: true,
|
|
536
607
|
filter: this.#selectorFilter,
|
|
537
|
-
|
|
538
|
-
skipVisibilityCheck: true
|
|
608
|
+
skipNegativeTabIndexCheck: !this.#options.navigationOnly
|
|
539
609
|
});
|
|
540
610
|
}
|
|
541
611
|
};
|
|
542
612
|
function focusElement(element) {
|
|
543
613
|
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
544
614
|
}
|
|
545
|
-
function
|
|
615
|
+
function getActiveElement2() {
|
|
546
616
|
let current = document.activeElement;
|
|
547
617
|
while (current?.shadowRoot?.activeElement) {
|
|
548
618
|
current = current.shadowRoot.activeElement;
|
|
@@ -568,6 +638,7 @@ var Accordion = class _Accordion {
|
|
|
568
638
|
#eventController = null;
|
|
569
639
|
#animationController = null;
|
|
570
640
|
#cleanupRovingTabIndex = null;
|
|
641
|
+
#buttons = [];
|
|
571
642
|
#isDestroyed = false;
|
|
572
643
|
constructor(root, options = {}) {
|
|
573
644
|
if (!(root instanceof HTMLElement)) {
|
|
@@ -641,6 +712,10 @@ var Accordion = class _Accordion {
|
|
|
641
712
|
this.#eventController = null;
|
|
642
713
|
this.#cleanupRovingTabIndex?.();
|
|
643
714
|
this.#cleanupRovingTabIndex = null;
|
|
715
|
+
this.#buttons.forEach((button) => {
|
|
716
|
+
button.destroy();
|
|
717
|
+
});
|
|
718
|
+
this.#buttons.length = 0;
|
|
644
719
|
!force && await this.#waitAnimationsFinish();
|
|
645
720
|
this.#contentElements.forEach((content) => {
|
|
646
721
|
force && this.#bindings.get(content)?.animation?.finish();
|
|
@@ -683,12 +758,12 @@ var Accordion = class _Accordion {
|
|
|
683
758
|
trigger2.style.setProperty("pointer-events", "none");
|
|
684
759
|
}
|
|
685
760
|
trigger2.addEventListener("click", this.#onTriggerClick, { signal });
|
|
686
|
-
trigger2.addEventListener("keydown", this.#onTriggerKeyDown, { signal });
|
|
687
761
|
addTokenToAttribute(content2, "aria-labelledby", trigger2.id);
|
|
688
762
|
content2.setAttribute("role", "region");
|
|
689
763
|
content2.addEventListener("beforematch", this.#onContentBeforeMatch, {
|
|
690
764
|
signal
|
|
691
765
|
});
|
|
766
|
+
this.#buttons.push(new Button(trigger2));
|
|
692
767
|
});
|
|
693
768
|
const { trigger, content } = this.#settings.selector;
|
|
694
769
|
this.#cleanupRovingTabIndex = createRovingTabIndex(this.#rootElement, {
|
|
@@ -707,21 +782,6 @@ var Accordion = class _Accordion {
|
|
|
707
782
|
}
|
|
708
783
|
this.#toggle(trigger, trigger.ariaExpanded === "false");
|
|
709
784
|
};
|
|
710
|
-
#onTriggerKeyDown = (event) => {
|
|
711
|
-
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
712
|
-
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
if (!["Enter", " "].includes(key)) {
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
const active = getActiveElement2();
|
|
719
|
-
if (!(active instanceof HTMLElement)) {
|
|
720
|
-
return;
|
|
721
|
-
}
|
|
722
|
-
event.preventDefault();
|
|
723
|
-
active.click();
|
|
724
|
-
};
|
|
725
785
|
#onContentBeforeMatch = (event) => {
|
|
726
786
|
const content = event.currentTarget;
|
|
727
787
|
if (!(content instanceof HTMLElement)) {
|
|
@@ -818,13 +878,6 @@ var Accordion = class _Accordion {
|
|
|
818
878
|
function createBinding(trigger, content) {
|
|
819
879
|
return { trigger, content, animation: null };
|
|
820
880
|
}
|
|
821
|
-
function getActiveElement2() {
|
|
822
|
-
let current = document.activeElement;
|
|
823
|
-
while (current?.shadowRoot?.activeElement) {
|
|
824
|
-
current = current.shadowRoot.activeElement;
|
|
825
|
-
}
|
|
826
|
-
return current;
|
|
827
|
-
}
|
|
828
881
|
function isFocusable2(element) {
|
|
829
882
|
return !element.hasAttribute("disabled") && element.tabIndex >= 0;
|
|
830
883
|
}
|
|
@@ -841,7 +894,7 @@ function waitAnimationFinish(animation) {
|
|
|
841
894
|
* Accordion
|
|
842
895
|
* WAI-ARIA compliant accordion pattern implementation in TypeScript.
|
|
843
896
|
*
|
|
844
|
-
* @version 1.
|
|
897
|
+
* @version 1.4.1
|
|
845
898
|
* @author Yusuke Kamiyamane
|
|
846
899
|
* @license MIT
|
|
847
900
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -853,20 +906,31 @@ function waitAnimationFinish(animation) {
|
|
|
853
906
|
(**
|
|
854
907
|
* Attributes Utils
|
|
855
908
|
*
|
|
856
|
-
* @version 1.0
|
|
909
|
+
* @version 1.1.0
|
|
857
910
|
* @author Yusuke Kamiyamane
|
|
858
911
|
* @license MIT
|
|
859
912
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
860
913
|
* @see {@link https://github.com/y14e/attributes-utils}
|
|
861
914
|
*)
|
|
862
915
|
|
|
916
|
+
@y14e/button/dist/index.js:
|
|
917
|
+
(**
|
|
918
|
+
* Button
|
|
919
|
+
*
|
|
920
|
+
* @version 1.0.0
|
|
921
|
+
* @author Yusuke Kamiyamane
|
|
922
|
+
* @license MIT
|
|
923
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
924
|
+
* @see {@link https://github.com/y14e/button}
|
|
925
|
+
*)
|
|
926
|
+
|
|
863
927
|
@y14e/roving-tabindex/dist/index.js:
|
|
864
928
|
(**
|
|
865
929
|
* Roving Tabindex
|
|
866
930
|
* Lightweight roving tabindex utility with fully focus management.
|
|
867
931
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
868
932
|
*
|
|
869
|
-
* @version
|
|
933
|
+
* @version 2.0.3
|
|
870
934
|
* @author Yusuke Kamiyamane
|
|
871
935
|
* @license MIT
|
|
872
936
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -878,7 +942,7 @@ function waitAnimationFinish(animation) {
|
|
|
878
942
|
(**
|
|
879
943
|
* Attributes Utils
|
|
880
944
|
*
|
|
881
|
-
* @version 1.0
|
|
945
|
+
* @version 1.1.0
|
|
882
946
|
* @author Yusuke Kamiyamane
|
|
883
947
|
* @license MIT
|
|
884
948
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -891,7 +955,7 @@ function waitAnimationFinish(animation) {
|
|
|
891
955
|
* High-precision focus management utility with full composed tree support.
|
|
892
956
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
893
957
|
*
|
|
894
|
-
* @version 4.
|
|
958
|
+
* @version 4.3.1
|
|
895
959
|
* @author Yusuke Kamiyamane
|
|
896
960
|
* @license MIT
|
|
897
961
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -48,6 +48,62 @@ function saveAttributes(elements, attributes) {
|
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
// node_modules/@y14e/button/dist/index.js
|
|
52
|
+
var Button = class {
|
|
53
|
+
#element;
|
|
54
|
+
#controller = null;
|
|
55
|
+
#isDestroyed = false;
|
|
56
|
+
constructor(element) {
|
|
57
|
+
if (!(element instanceof HTMLElement)) {
|
|
58
|
+
throw new TypeError("Invalid element");
|
|
59
|
+
}
|
|
60
|
+
if (element.hasAttribute("data-button-initialized")) {
|
|
61
|
+
console.warn("Already initialized");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.#element = element;
|
|
65
|
+
this.#initialize();
|
|
66
|
+
}
|
|
67
|
+
destroy() {
|
|
68
|
+
if (this.#isDestroyed) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
this.#isDestroyed = true;
|
|
72
|
+
this.#controller?.abort();
|
|
73
|
+
this.#controller = null;
|
|
74
|
+
this.#element.removeAttribute("data-button-initialized");
|
|
75
|
+
}
|
|
76
|
+
#initialize() {
|
|
77
|
+
this.#controller = new AbortController();
|
|
78
|
+
this.#element.addEventListener("keydown", this.#onKeyDown, {
|
|
79
|
+
signal: this.#controller.signal
|
|
80
|
+
});
|
|
81
|
+
this.#element.setAttribute("data-button-initialized", "");
|
|
82
|
+
}
|
|
83
|
+
#onKeyDown = (event) => {
|
|
84
|
+
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
85
|
+
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!["Enter", " "].includes(key)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const active = getActiveElement();
|
|
92
|
+
if (!(active instanceof HTMLElement)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
active.click();
|
|
97
|
+
};
|
|
98
|
+
};
|
|
99
|
+
function getActiveElement() {
|
|
100
|
+
let current = document.activeElement;
|
|
101
|
+
while (current?.shadowRoot?.activeElement) {
|
|
102
|
+
current = current.shadowRoot.activeElement;
|
|
103
|
+
}
|
|
104
|
+
return current;
|
|
105
|
+
}
|
|
106
|
+
|
|
51
107
|
// node_modules/@y14e/roving-tabindex/dist/index.js
|
|
52
108
|
var defaultParser2 = (value) => value.split(/\s+/);
|
|
53
109
|
var defaultSerializer2 = (tokens) => tokens.join(" ");
|
|
@@ -107,6 +163,7 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
107
163
|
composed = false,
|
|
108
164
|
filter,
|
|
109
165
|
include,
|
|
166
|
+
skipNegativeTabIndexCheck = false,
|
|
110
167
|
skipVisibilityCheck = false
|
|
111
168
|
} = options;
|
|
112
169
|
if (typeof composed !== "boolean") {
|
|
@@ -125,11 +182,22 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
125
182
|
);
|
|
126
183
|
include = void 0;
|
|
127
184
|
}
|
|
185
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
186
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
187
|
+
skipNegativeTabIndexCheck = false;
|
|
188
|
+
}
|
|
189
|
+
if (typeof skipVisibilityCheck !== "boolean") {
|
|
190
|
+
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
191
|
+
skipVisibilityCheck = false;
|
|
192
|
+
}
|
|
128
193
|
const elements = [];
|
|
129
194
|
if (composed || include) {
|
|
130
195
|
let traverse2 = function(node) {
|
|
131
196
|
if (node instanceof Element) {
|
|
132
|
-
if (isFocusable(node, {
|
|
197
|
+
if (isFocusable(node, {
|
|
198
|
+
skipNegativeTabIndexCheck,
|
|
199
|
+
skipVisibilityCheck
|
|
200
|
+
}) || include?.(node)) {
|
|
133
201
|
elements[elements.length] = node;
|
|
134
202
|
}
|
|
135
203
|
}
|
|
@@ -150,7 +218,10 @@ function getFocusables(container = document.body, options = {}) {
|
|
|
150
218
|
if (!(candidate instanceof Element)) {
|
|
151
219
|
continue;
|
|
152
220
|
}
|
|
153
|
-
if (isFocusable(candidate, {
|
|
221
|
+
if (isFocusable(candidate, {
|
|
222
|
+
skipNegativeTabIndexCheck,
|
|
223
|
+
skipVisibilityCheck
|
|
224
|
+
})) {
|
|
154
225
|
elements[elements.length] = candidate;
|
|
155
226
|
}
|
|
156
227
|
}
|
|
@@ -163,7 +234,11 @@ function isFocusable(element, options = {}) {
|
|
|
163
234
|
console.warn("Invalid element");
|
|
164
235
|
return false;
|
|
165
236
|
}
|
|
166
|
-
let { skipVisibilityCheck = false } = options;
|
|
237
|
+
let { skipNegativeTabIndexCheck = false, skipVisibilityCheck = false } = options;
|
|
238
|
+
if (typeof skipNegativeTabIndexCheck !== "boolean") {
|
|
239
|
+
console.warn("Invalid skipNegativeTabIndexCheck option. Fallback: false.");
|
|
240
|
+
skipNegativeTabIndexCheck = false;
|
|
241
|
+
}
|
|
167
242
|
if (typeof skipVisibilityCheck !== "boolean") {
|
|
168
243
|
console.warn("Invalid skipVisibilityCheck option. Fallback: false.");
|
|
169
244
|
skipVisibilityCheck = false;
|
|
@@ -171,10 +246,12 @@ function isFocusable(element, options = {}) {
|
|
|
171
246
|
if (element.hasAttribute("hidden") || isInert(element)) {
|
|
172
247
|
return false;
|
|
173
248
|
}
|
|
174
|
-
if (getTabIndex(element) < 0) {
|
|
249
|
+
if (!skipNegativeTabIndexCheck && getTabIndex(element) < 0) {
|
|
175
250
|
return false;
|
|
176
251
|
}
|
|
177
|
-
if (!element.matches(
|
|
252
|
+
if (!element.matches(
|
|
253
|
+
skipNegativeTabIndexCheck ? FOCUSABLE_SELECTOR.replace(/(,\s*)?\[tabindex="-1"\]/g, "") : FOCUSABLE_SELECTOR
|
|
254
|
+
)) {
|
|
178
255
|
return false;
|
|
179
256
|
}
|
|
180
257
|
if (isDisabledDeep(element)) {
|
|
@@ -349,13 +426,14 @@ function createRovingTabIndex(container, options = {}) {
|
|
|
349
426
|
console.warn("Invalid wrap option. Fallback: false.");
|
|
350
427
|
wrap = false;
|
|
351
428
|
}
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
429
|
+
const settings = { navigationOnly, typeahead, wrap };
|
|
430
|
+
if (direction !== void 0) {
|
|
431
|
+
Object.assign(settings, { direction });
|
|
432
|
+
}
|
|
433
|
+
if (selector !== void 0) {
|
|
434
|
+
Object.assign(settings, { selector });
|
|
435
|
+
}
|
|
436
|
+
const roving = new RovingTabIndex(container, settings);
|
|
359
437
|
return () => roving.destroy();
|
|
360
438
|
}
|
|
361
439
|
var RovingTabIndex = class {
|
|
@@ -397,8 +475,8 @@ var RovingTabIndex = class {
|
|
|
397
475
|
if (!event.composedPath().includes(this.#container)) {
|
|
398
476
|
return;
|
|
399
477
|
}
|
|
400
|
-
const { key, altKey, ctrlKey, metaKey } = event;
|
|
401
|
-
if (altKey || ctrlKey || metaKey) {
|
|
478
|
+
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
479
|
+
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
402
480
|
return;
|
|
403
481
|
}
|
|
404
482
|
const { direction, typeahead, wrap } = this.#options;
|
|
@@ -414,7 +492,7 @@ var RovingTabIndex = class {
|
|
|
414
492
|
return;
|
|
415
493
|
}
|
|
416
494
|
}
|
|
417
|
-
const active =
|
|
495
|
+
const active = getActiveElement2();
|
|
418
496
|
if (!(active instanceof HTMLElement)) {
|
|
419
497
|
return;
|
|
420
498
|
}
|
|
@@ -461,14 +539,7 @@ var RovingTabIndex = class {
|
|
|
461
539
|
focusElement(focusable);
|
|
462
540
|
};
|
|
463
541
|
#update(active) {
|
|
464
|
-
const current =
|
|
465
|
-
...this.#getFocusables(),
|
|
466
|
-
...getFocusables(this.#container, {
|
|
467
|
-
composed: true,
|
|
468
|
-
filter: this.#selectorFilter,
|
|
469
|
-
skipVisibilityCheck: true
|
|
470
|
-
})
|
|
471
|
-
]);
|
|
542
|
+
const current = new Set(this.#getFocusables());
|
|
472
543
|
for (const focusable of this.#focusables) {
|
|
473
544
|
if (current.has(focusable)) {
|
|
474
545
|
continue;
|
|
@@ -532,15 +603,14 @@ var RovingTabIndex = class {
|
|
|
532
603
|
return getFocusables(this.#container, {
|
|
533
604
|
composed: true,
|
|
534
605
|
filter: this.#selectorFilter,
|
|
535
|
-
|
|
536
|
-
skipVisibilityCheck: true
|
|
606
|
+
skipNegativeTabIndexCheck: !this.#options.navigationOnly
|
|
537
607
|
});
|
|
538
608
|
}
|
|
539
609
|
};
|
|
540
610
|
function focusElement(element) {
|
|
541
611
|
"focus" in element && typeof element.focus === "function" && element.focus();
|
|
542
612
|
}
|
|
543
|
-
function
|
|
613
|
+
function getActiveElement2() {
|
|
544
614
|
let current = document.activeElement;
|
|
545
615
|
while (current?.shadowRoot?.activeElement) {
|
|
546
616
|
current = current.shadowRoot.activeElement;
|
|
@@ -566,6 +636,7 @@ var Accordion = class _Accordion {
|
|
|
566
636
|
#eventController = null;
|
|
567
637
|
#animationController = null;
|
|
568
638
|
#cleanupRovingTabIndex = null;
|
|
639
|
+
#buttons = [];
|
|
569
640
|
#isDestroyed = false;
|
|
570
641
|
constructor(root, options = {}) {
|
|
571
642
|
if (!(root instanceof HTMLElement)) {
|
|
@@ -639,6 +710,10 @@ var Accordion = class _Accordion {
|
|
|
639
710
|
this.#eventController = null;
|
|
640
711
|
this.#cleanupRovingTabIndex?.();
|
|
641
712
|
this.#cleanupRovingTabIndex = null;
|
|
713
|
+
this.#buttons.forEach((button) => {
|
|
714
|
+
button.destroy();
|
|
715
|
+
});
|
|
716
|
+
this.#buttons.length = 0;
|
|
642
717
|
!force && await this.#waitAnimationsFinish();
|
|
643
718
|
this.#contentElements.forEach((content) => {
|
|
644
719
|
force && this.#bindings.get(content)?.animation?.finish();
|
|
@@ -681,12 +756,12 @@ var Accordion = class _Accordion {
|
|
|
681
756
|
trigger2.style.setProperty("pointer-events", "none");
|
|
682
757
|
}
|
|
683
758
|
trigger2.addEventListener("click", this.#onTriggerClick, { signal });
|
|
684
|
-
trigger2.addEventListener("keydown", this.#onTriggerKeyDown, { signal });
|
|
685
759
|
addTokenToAttribute(content2, "aria-labelledby", trigger2.id);
|
|
686
760
|
content2.setAttribute("role", "region");
|
|
687
761
|
content2.addEventListener("beforematch", this.#onContentBeforeMatch, {
|
|
688
762
|
signal
|
|
689
763
|
});
|
|
764
|
+
this.#buttons.push(new Button(trigger2));
|
|
690
765
|
});
|
|
691
766
|
const { trigger, content } = this.#settings.selector;
|
|
692
767
|
this.#cleanupRovingTabIndex = createRovingTabIndex(this.#rootElement, {
|
|
@@ -705,21 +780,6 @@ var Accordion = class _Accordion {
|
|
|
705
780
|
}
|
|
706
781
|
this.#toggle(trigger, trigger.ariaExpanded === "false");
|
|
707
782
|
};
|
|
708
|
-
#onTriggerKeyDown = (event) => {
|
|
709
|
-
const { key, altKey, ctrlKey, metaKey, shiftKey } = event;
|
|
710
|
-
if (altKey || ctrlKey || metaKey || shiftKey) {
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
if (!["Enter", " "].includes(key)) {
|
|
714
|
-
return;
|
|
715
|
-
}
|
|
716
|
-
const active = getActiveElement2();
|
|
717
|
-
if (!(active instanceof HTMLElement)) {
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
event.preventDefault();
|
|
721
|
-
active.click();
|
|
722
|
-
};
|
|
723
783
|
#onContentBeforeMatch = (event) => {
|
|
724
784
|
const content = event.currentTarget;
|
|
725
785
|
if (!(content instanceof HTMLElement)) {
|
|
@@ -816,13 +876,6 @@ var Accordion = class _Accordion {
|
|
|
816
876
|
function createBinding(trigger, content) {
|
|
817
877
|
return { trigger, content, animation: null };
|
|
818
878
|
}
|
|
819
|
-
function getActiveElement2() {
|
|
820
|
-
let current = document.activeElement;
|
|
821
|
-
while (current?.shadowRoot?.activeElement) {
|
|
822
|
-
current = current.shadowRoot.activeElement;
|
|
823
|
-
}
|
|
824
|
-
return current;
|
|
825
|
-
}
|
|
826
879
|
function isFocusable2(element) {
|
|
827
880
|
return !element.hasAttribute("disabled") && element.tabIndex >= 0;
|
|
828
881
|
}
|
|
@@ -839,7 +892,7 @@ function waitAnimationFinish(animation) {
|
|
|
839
892
|
* Accordion
|
|
840
893
|
* WAI-ARIA compliant accordion pattern implementation in TypeScript.
|
|
841
894
|
*
|
|
842
|
-
* @version 1.
|
|
895
|
+
* @version 1.4.1
|
|
843
896
|
* @author Yusuke Kamiyamane
|
|
844
897
|
* @license MIT
|
|
845
898
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -851,20 +904,31 @@ function waitAnimationFinish(animation) {
|
|
|
851
904
|
(**
|
|
852
905
|
* Attributes Utils
|
|
853
906
|
*
|
|
854
|
-
* @version 1.0
|
|
907
|
+
* @version 1.1.0
|
|
855
908
|
* @author Yusuke Kamiyamane
|
|
856
909
|
* @license MIT
|
|
857
910
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
858
911
|
* @see {@link https://github.com/y14e/attributes-utils}
|
|
859
912
|
*)
|
|
860
913
|
|
|
914
|
+
@y14e/button/dist/index.js:
|
|
915
|
+
(**
|
|
916
|
+
* Button
|
|
917
|
+
*
|
|
918
|
+
* @version 1.0.0
|
|
919
|
+
* @author Yusuke Kamiyamane
|
|
920
|
+
* @license MIT
|
|
921
|
+
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
922
|
+
* @see {@link https://github.com/y14e/button}
|
|
923
|
+
*)
|
|
924
|
+
|
|
861
925
|
@y14e/roving-tabindex/dist/index.js:
|
|
862
926
|
(**
|
|
863
927
|
* Roving Tabindex
|
|
864
928
|
* Lightweight roving tabindex utility with fully focus management.
|
|
865
929
|
* Designed for accessible menus, tabs, toolbars, and composite widgets.
|
|
866
930
|
*
|
|
867
|
-
* @version
|
|
931
|
+
* @version 2.0.3
|
|
868
932
|
* @author Yusuke Kamiyamane
|
|
869
933
|
* @license MIT
|
|
870
934
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -876,7 +940,7 @@ function waitAnimationFinish(animation) {
|
|
|
876
940
|
(**
|
|
877
941
|
* Attributes Utils
|
|
878
942
|
*
|
|
879
|
-
* @version 1.0
|
|
943
|
+
* @version 1.1.0
|
|
880
944
|
* @author Yusuke Kamiyamane
|
|
881
945
|
* @license MIT
|
|
882
946
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
|
@@ -889,7 +953,7 @@ function waitAnimationFinish(animation) {
|
|
|
889
953
|
* High-precision focus management utility with full composed tree support.
|
|
890
954
|
* Handles complex focus rules including tabindex ordering, radio groups, inert.
|
|
891
955
|
*
|
|
892
|
-
* @version 4.
|
|
956
|
+
* @version 4.3.1
|
|
893
957
|
* @author Yusuke Kamiyamane
|
|
894
958
|
* @license MIT
|
|
895
959
|
* @copyright Copyright (c) Yusuke Kamiyamane
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@y14e/accordion",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "WAI-ARIA compliant accordion pattern implementation in TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -43,8 +43,9 @@
|
|
|
43
43
|
},
|
|
44
44
|
"homepage": "https://github.com/y14e/accordion#readme",
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@y14e/attributes-utils": "^1.0
|
|
47
|
-
"@y14e/
|
|
46
|
+
"@y14e/attributes-utils": "^1.1.0",
|
|
47
|
+
"@y14e/button": "^1.0.0",
|
|
48
|
+
"@y14e/roving-tabindex": "^2.0.3",
|
|
48
49
|
"bun-types": "latest",
|
|
49
50
|
"tsup": "^8.0.0",
|
|
50
51
|
"typescript": "^5.6.0",
|