@rcarls/rc-accordion 0.1.0 → 0.3.0
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 +5 -1
- package/dist/custom-elements.json +115 -4
- package/dist/rc-accordion-define.js.map +1 -1
- package/dist/rc-accordion.js +94 -26
- package/dist/rc-accordion.js.map +1 -1
- package/dist/types/packages/rc-accordion/src/rc-accordion.d.ts +22 -7
- package/package.json +7 -11
- package/dist/demo.css +0 -85
- package/dist/demo.js +0 -40
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
# @rcarls/rc-accordion
|
|
2
2
|
|
|
3
|
-
Accordion coordinator for
|
|
3
|
+
Accordion coordinator for child native `<details>` panels with single- or multiple-open behavior, following the [WAI-ARIA Accordion pattern](https://www.w3.org/WAI/ARIA/apg/patterns/accordion/).
|
|
4
|
+
|
|
5
|
+
Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-accordion](https://richardcarls.github.io/rc-webcomponents/components/rc-accordion).
|
|
6
|
+
|
|
7
|
+
Also supports [`rc-disclosure`](./rc-disclosure) for granular control and fragment-target.
|
|
@@ -38,12 +38,32 @@
|
|
|
38
38
|
"declarations": [
|
|
39
39
|
{
|
|
40
40
|
"kind": "class",
|
|
41
|
-
"description": "
|
|
41
|
+
"description": "Accordion coordinator for child native <details> panels with single- or multiple-open\nbehavior, following the WAI-ARIA Accordion pattern.\n\nSupports native details and rc-disclosure wrappers.",
|
|
42
42
|
"name": "RCAccordion",
|
|
43
43
|
"members": [
|
|
44
|
+
{
|
|
45
|
+
"kind": "field",
|
|
46
|
+
"name": "_$managedGroupNames",
|
|
47
|
+
"privacy": "private",
|
|
48
|
+
"default": "new Map<HTMLDetailsElement, string>()"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"kind": "field",
|
|
52
|
+
"name": "_childrenObserver",
|
|
53
|
+
"privacy": "private",
|
|
54
|
+
"default": "new MutationObserver((records) => this._onChildrenChanged(records))"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"kind": "field",
|
|
58
|
+
"name": "multiple",
|
|
59
|
+
"type": {
|
|
60
|
+
"text": "boolean"
|
|
61
|
+
},
|
|
62
|
+
"description": "Allows more than one panel open at a time."
|
|
63
|
+
},
|
|
44
64
|
{
|
|
45
65
|
"kind": "method",
|
|
46
|
-
"name": "
|
|
66
|
+
"name": "_$details",
|
|
47
67
|
"privacy": "private",
|
|
48
68
|
"return": {
|
|
49
69
|
"type": {
|
|
@@ -53,7 +73,7 @@
|
|
|
53
73
|
},
|
|
54
74
|
{
|
|
55
75
|
"kind": "method",
|
|
56
|
-
"name": "
|
|
76
|
+
"name": "_$summaries",
|
|
57
77
|
"privacy": "private",
|
|
58
78
|
"return": {
|
|
59
79
|
"type": {
|
|
@@ -78,8 +98,99 @@
|
|
|
78
98
|
},
|
|
79
99
|
{
|
|
80
100
|
"kind": "field",
|
|
81
|
-
"name": "
|
|
101
|
+
"name": "_onDetailsToggle",
|
|
82
102
|
"privacy": "private"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"kind": "method",
|
|
106
|
+
"name": "_setDetailsOpen",
|
|
107
|
+
"privacy": "private",
|
|
108
|
+
"return": {
|
|
109
|
+
"type": {
|
|
110
|
+
"text": "void"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"parameters": [
|
|
114
|
+
{
|
|
115
|
+
"name": "$target",
|
|
116
|
+
"type": {
|
|
117
|
+
"text": "HTMLDetailsElement"
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "open",
|
|
122
|
+
"type": {
|
|
123
|
+
"text": "boolean"
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"kind": "method",
|
|
130
|
+
"name": "_onChildrenChanged",
|
|
131
|
+
"privacy": "private",
|
|
132
|
+
"return": {
|
|
133
|
+
"type": {
|
|
134
|
+
"text": "void"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"parameters": [
|
|
138
|
+
{
|
|
139
|
+
"name": "records",
|
|
140
|
+
"type": {
|
|
141
|
+
"text": "MutationRecord[]"
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"kind": "method",
|
|
148
|
+
"name": "_findAddedOpenDetails",
|
|
149
|
+
"privacy": "private",
|
|
150
|
+
"return": {
|
|
151
|
+
"type": {
|
|
152
|
+
"text": "HTMLDetailsElement | undefined"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"parameters": [
|
|
156
|
+
{
|
|
157
|
+
"name": "records",
|
|
158
|
+
"type": {
|
|
159
|
+
"text": "MutationRecord[]"
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"kind": "method",
|
|
166
|
+
"name": "_enforceSingleOpen",
|
|
167
|
+
"privacy": "private",
|
|
168
|
+
"return": {
|
|
169
|
+
"type": {
|
|
170
|
+
"text": "void"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"parameters": [
|
|
174
|
+
{
|
|
175
|
+
"name": "$target",
|
|
176
|
+
"optional": true,
|
|
177
|
+
"type": {
|
|
178
|
+
"text": "HTMLDetailsElement"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
"attributes": [
|
|
185
|
+
{
|
|
186
|
+
"name": "multiple",
|
|
187
|
+
"type": {
|
|
188
|
+
"text": "boolean"
|
|
189
|
+
},
|
|
190
|
+
"description": "Allows more than one panel open at a time."
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"name": "name"
|
|
83
194
|
}
|
|
84
195
|
],
|
|
85
196
|
"superclass": {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rc-accordion-define.js","sources":["../src/define.ts"],"sourcesContent":["import '@rcarls/rc-disclosure/define';\n\nimport { RCAccordion } from './index.js';\n\
|
|
1
|
+
{"version":3,"file":"rc-accordion-define.js","sources":["../src/define.ts"],"sourcesContent":["import '@rcarls/rc-disclosure/define';\n\nimport { RCAccordion } from './index.js';\n\nif (!customElements.get('rc-accordion')) {\n customElements.define('rc-accordion', RCAccordion);\n}\n"],"names":["RCAccordion"],"mappings":";;AAIK,eAAe,IAAI,cAAc,KACpC,eAAe,OAAO,gBAAgBA,CAAW;"}
|
package/dist/rc-accordion.js
CHANGED
|
@@ -1,50 +1,118 @@
|
|
|
1
|
-
|
|
1
|
+
const a = ["ArrowDown", "ArrowUp", "Home", "End"];
|
|
2
|
+
function u(l) {
|
|
3
|
+
return a.includes(l);
|
|
4
|
+
}
|
|
5
|
+
function c(l) {
|
|
6
|
+
return l?.localName === "rc-disclosure";
|
|
7
|
+
}
|
|
8
|
+
class m extends HTMLElement {
|
|
2
9
|
constructor() {
|
|
3
|
-
super(...arguments), this._onKeydown = (e) => {
|
|
4
|
-
if (e.altKey || e.ctrlKey || e.metaKey || e.key
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
10
|
+
super(...arguments), this._$managedGroupNames = /* @__PURE__ */ new Map(), this._childrenObserver = new MutationObserver((e) => this._onChildrenChanged(e)), this._onKeydown = (e) => {
|
|
11
|
+
if (e.altKey || e.ctrlKey || e.metaKey || !u(e.key))
|
|
12
|
+
return;
|
|
13
|
+
const t = this._$summaries();
|
|
14
|
+
if (t.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
const i = document.activeElement, n = i ? t.indexOf(i) : -1;
|
|
17
|
+
if (n === -1)
|
|
18
|
+
return;
|
|
9
19
|
if (e.preventDefault(), e.key === "Home") {
|
|
10
|
-
t
|
|
20
|
+
const o = t.at(0);
|
|
21
|
+
if (!o)
|
|
22
|
+
return;
|
|
23
|
+
o.focus();
|
|
11
24
|
return;
|
|
12
25
|
}
|
|
13
26
|
if (e.key === "End") {
|
|
14
|
-
t
|
|
27
|
+
const o = t.at(-1);
|
|
28
|
+
if (!o)
|
|
29
|
+
return;
|
|
30
|
+
o.focus();
|
|
15
31
|
return;
|
|
16
32
|
}
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
}, this.
|
|
20
|
-
if (!e.detail.open) return;
|
|
33
|
+
const s = e.key === "ArrowDown" ? (n + 1) % t.length : (n - 1 + t.length) % t.length, r = t.at(s);
|
|
34
|
+
r && r.focus();
|
|
35
|
+
}, this._onDetailsToggle = (e) => {
|
|
21
36
|
const t = e.target;
|
|
22
|
-
|
|
23
|
-
s !== t && (s.open = !1);
|
|
37
|
+
t instanceof HTMLDetailsElement && (this.multiple || t.open && this._enforceSingleOpen(t));
|
|
24
38
|
};
|
|
25
39
|
}
|
|
40
|
+
static get observedAttributes() {
|
|
41
|
+
return ["multiple", "name"];
|
|
42
|
+
}
|
|
43
|
+
/** Allows more than one panel open at a time. */
|
|
44
|
+
get multiple() {
|
|
45
|
+
return this.hasAttribute("multiple");
|
|
46
|
+
}
|
|
47
|
+
set multiple(e) {
|
|
48
|
+
this.toggleAttribute("multiple", e);
|
|
49
|
+
}
|
|
26
50
|
connectedCallback() {
|
|
27
|
-
this.addEventListener("
|
|
51
|
+
this._childrenObserver.observe(this, { childList: !0, subtree: !0 }), this.addEventListener("toggle", this._onDetailsToggle, !0), this.addEventListener("keydown", this._onKeydown), this._syncNativeGroupNames(), this._enforceSingleOpen();
|
|
28
52
|
}
|
|
29
53
|
disconnectedCallback() {
|
|
30
|
-
this.removeEventListener("
|
|
54
|
+
this._childrenObserver.disconnect(), this.removeEventListener("toggle", this._onDetailsToggle, !0), this.removeEventListener("keydown", this._onKeydown);
|
|
55
|
+
}
|
|
56
|
+
attributeChangedCallback(e, t, i) {
|
|
57
|
+
t !== i && ((e === "name" || e === "multiple") && this._syncNativeGroupNames(), e === "multiple" && !this.multiple && this._enforceSingleOpen());
|
|
31
58
|
}
|
|
32
|
-
|
|
59
|
+
_$details() {
|
|
33
60
|
return Array.from(
|
|
34
|
-
this.querySelectorAll(
|
|
61
|
+
this.querySelectorAll(
|
|
62
|
+
":scope > details, :scope > rc-disclosure > details"
|
|
63
|
+
)
|
|
35
64
|
);
|
|
36
65
|
}
|
|
37
|
-
|
|
38
|
-
return this.
|
|
66
|
+
_$summaries() {
|
|
67
|
+
return this._$details().map((e) => e.querySelector(":scope > summary")).filter((e) => e !== null);
|
|
39
68
|
}
|
|
40
69
|
_syncNativeGroupNames() {
|
|
41
|
-
const e = this.getAttribute("name");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
70
|
+
const e = this.getAttribute("name"), t = this._$details(), i = new Set(t);
|
|
71
|
+
for (const [n, s] of this._$managedGroupNames)
|
|
72
|
+
(!i.has(n) || this.multiple || !e || s !== e || n.getAttribute("name") !== s) && (n.getAttribute("name") === s && n.removeAttribute("name"), this._$managedGroupNames.delete(n));
|
|
73
|
+
if (!(this.multiple || !e))
|
|
74
|
+
for (const n of t)
|
|
75
|
+
n.hasAttribute("name") && !this._$managedGroupNames.has(n) || (n.setAttribute("name", e), this._$managedGroupNames.set(n, e));
|
|
76
|
+
}
|
|
77
|
+
_setDetailsOpen(e, t) {
|
|
78
|
+
const i = e.parentElement;
|
|
79
|
+
if (c(i)) {
|
|
80
|
+
i.open = t;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
e.open = t;
|
|
84
|
+
}
|
|
85
|
+
_onChildrenChanged(e) {
|
|
86
|
+
this._syncNativeGroupNames(), !this.multiple && this._enforceSingleOpen(this._findAddedOpenDetails(e));
|
|
87
|
+
}
|
|
88
|
+
_findAddedOpenDetails(e) {
|
|
89
|
+
const i = this._$details().filter(
|
|
90
|
+
(n) => e.some(
|
|
91
|
+
(s) => Array.from(s.addedNodes).some(
|
|
92
|
+
(r) => r === n || r instanceof Element && r.contains(n)
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
for (let n = i.length - 1; n >= 0; n -= 1) {
|
|
97
|
+
const s = i.at(n);
|
|
98
|
+
if (s && s.open)
|
|
99
|
+
return s;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
_enforceSingleOpen(e) {
|
|
103
|
+
const t = this._$details();
|
|
104
|
+
if (e && !t.includes(e))
|
|
105
|
+
return;
|
|
106
|
+
const i = t.filter((s) => s.open);
|
|
107
|
+
if (i.length <= 1)
|
|
108
|
+
return;
|
|
109
|
+
const n = e ?? i.at(0);
|
|
110
|
+
if (n)
|
|
111
|
+
for (const s of i)
|
|
112
|
+
s !== n && this._setDetailsOpen(s, !1);
|
|
45
113
|
}
|
|
46
114
|
}
|
|
47
115
|
export {
|
|
48
|
-
|
|
116
|
+
m as RCAccordion
|
|
49
117
|
};
|
|
50
118
|
//# sourceMappingURL=rc-accordion.js.map
|
package/dist/rc-accordion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rc-accordion.js","sources":["../src/rc-accordion.ts"],"sourcesContent":["import type { RCDisclosureToggleEvent } from '@rcarls/rc-disclosure';\nimport type { RCDisclosure } from '@rcarls/rc-disclosure';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-accordion': RCAccordion;\n }\n}\n\n/**\n * Coordinates direct child disclosures so only one stays open at a time.\n *\n * Child disclosures remain light-DOM behavioral wrappers around native\n * `<details>` elements. The accordion only adds group behavior and optional\n * arrow-key navigation between summaries.\n */\nexport class RCAccordion extends HTMLElement {\n connectedCallback(): void {\n this.addEventListener('rc-disclosure-toggle', this._onDisclosureToggle as EventListener);\n this.addEventListener('keydown', this._onKeydown);\n this._syncNativeGroupNames();\n }\n\n disconnectedCallback(): void {\n this.removeEventListener('rc-disclosure-toggle', this._onDisclosureToggle as EventListener);\n this.removeEventListener('keydown', this._onKeydown);\n }\n\n private _details(): HTMLDetailsElement[] {\n return Array.from(\n this.querySelectorAll<HTMLDetailsElement>(':scope > rc-disclosure > details'),\n );\n }\n\n private _summaries(): HTMLElement[] {\n return this._details()\n .map((details) => details.querySelector<HTMLElement>(':scope > summary'))\n .filter((summary): summary is HTMLElement => summary !== null);\n }\n\n private _syncNativeGroupNames(): void {\n const name = this.getAttribute('name');\n if (!name) return;\n\n for (const details of this._details()) {\n if (!details.hasAttribute('name')) details.setAttribute('name', name);\n }\n }\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (e.altKey || e.ctrlKey || e.metaKey) return;\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp' && e.key !== 'Home' && e.key !== 'End') return;\n\n const summaries = this._summaries();\n if (summaries.length === 0) return;\n\n const active = document.activeElement as HTMLElement | null;\n const idx = active ? summaries.indexOf(active) : -1;\n if (idx === -1) return;\n\n e.preventDefault();\n\n if (e.key === 'Home') {\n summaries[0].focus();\n return;\n }\n\n if (e.key === 'End') {\n summaries[summaries.length - 1].focus();\n return;\n }\n\n const next = e.key === 'ArrowDown'\n ? (idx + 1) % summaries.length\n : (idx - 1 + summaries.length) % summaries.length;\n\n summaries[next].focus();\n };\n\n private _onDisclosureToggle = (e: CustomEvent<RCDisclosureToggleEvent>): void => {\n if (!e.detail.open) return;\n\n const opened = e.target;\n for (const disclosure of this.querySelectorAll<RCDisclosure>(':scope > rc-disclosure')) {\n if (disclosure !== opened) disclosure.open = false;\n }\n };\n}\n\nexport default RCAccordion;\n"],"names":["RCAccordion","summaries","active","idx","next","opened","disclosure","details","summary","name"],"mappings":"AAgBO,MAAMA,UAAoB,YAAY;AAAA,EAAtC,cAAA;AAAA,UAAA,GAAA,SAAA,GAiCL,KAAQ,aAAa,CAAC,MAA2B;AAE/C,UADI,EAAE,UAAU,EAAE,WAAW,EAAE,WAC3B,EAAE,QAAQ,eAAe,EAAE,QAAQ,aAAa,EAAE,QAAQ,UAAU,EAAE,QAAQ,MAAO;AAEzF,YAAMC,IAAY,KAAK,WAAA;AACvB,UAAIA,EAAU,WAAW,EAAG;AAE5B,YAAMC,IAAS,SAAS,eAClBC,IAAMD,IAASD,EAAU,QAAQC,CAAM,IAAI;AACjD,UAAIC,MAAQ,GAAI;AAIhB,UAFA,EAAE,eAAA,GAEE,EAAE,QAAQ,QAAQ;AACpB,QAAAF,EAAU,CAAC,EAAE,MAAA;AACb;AAAA,MACF;AAEA,UAAI,EAAE,QAAQ,OAAO;AACnB,QAAAA,EAAUA,EAAU,SAAS,CAAC,EAAE,MAAA;AAChC;AAAA,MACF;AAEA,YAAMG,IAAO,EAAE,QAAQ,eAClBD,IAAM,KAAKF,EAAU,UACrBE,IAAM,IAAIF,EAAU,UAAUA,EAAU;AAE7C,MAAAA,EAAUG,CAAI,EAAE,MAAA;AAAA,IAClB,GAEA,KAAQ,sBAAsB,CAAC,MAAkD;AAC/E,UAAI,CAAC,EAAE,OAAO,KAAM;AAEpB,YAAMC,IAAS,EAAE;AACjB,iBAAWC,KAAc,KAAK,iBAA+B,wBAAwB;AACnF,QAAIA,MAAeD,MAAQC,EAAW,OAAO;AAAA,IAEjD;AAAA,EAAA;AAAA,EArEA,oBAA0B;AACxB,SAAK,iBAAiB,wBAAwB,KAAK,mBAAoC,GACvF,KAAK,iBAAiB,WAAW,KAAK,UAAU,GAChD,KAAK,sBAAA;AAAA,EACP;AAAA,EAEA,uBAA6B;AAC3B,SAAK,oBAAoB,wBAAwB,KAAK,mBAAoC,GAC1F,KAAK,oBAAoB,WAAW,KAAK,UAAU;AAAA,EACrD;AAAA,EAEQ,WAAiC;AACvC,WAAO,MAAM;AAAA,MACX,KAAK,iBAAqC,kCAAkC;AAAA,IAAA;AAAA,EAEhF;AAAA,EAEQ,aAA4B;AAClC,WAAO,KAAK,SAAA,EACT,IAAI,CAACC,MAAYA,EAAQ,cAA2B,kBAAkB,CAAC,EACvE,OAAO,CAACC,MAAoCA,MAAY,IAAI;AAAA,EACjE;AAAA,EAEQ,wBAA8B;AACpC,UAAMC,IAAO,KAAK,aAAa,MAAM;AACrC,QAAKA;AAEL,iBAAWF,KAAW,KAAK;AACzB,QAAKA,EAAQ,aAAa,MAAM,KAAGA,EAAQ,aAAa,QAAQE,CAAI;AAAA,EAExE;AAwCF;"}
|
|
1
|
+
{"version":3,"file":"rc-accordion.js","sources":["../src/rc-accordion.ts"],"sourcesContent":["const NAVIGATION_KEYS = ['ArrowDown', 'ArrowUp', 'Home', 'End'] as const;\n\ntype NavigationKey = (typeof NAVIGATION_KEYS)[number];\n\ntype RCDisclosureElement = HTMLElement & {\n open: boolean;\n};\n\nfunction isNavigationKey(key: string): key is NavigationKey {\n return NAVIGATION_KEYS.includes(key as NavigationKey);\n}\n\nfunction isRCDisclosureElement($el: Element | null): $el is RCDisclosureElement {\n return $el?.localName === 'rc-disclosure';\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-accordion': RCAccordion;\n }\n}\n\n/**\n * Accordion coordinator for child native <details> panels with single- or multiple-open\n * behavior, following the WAI-ARIA Accordion pattern.\n *\n * Supports native details and rc-disclosure wrappers.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-accordion rc-accordion docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/accordion/ WAI-ARIA Accordion pattern}\n *\n * @attr {boolean} multiple - Allows more than one panel open at a time.\n */\nexport class RCAccordion extends HTMLElement {\n private _$managedGroupNames = new Map<HTMLDetailsElement, string>();\n\n private _childrenObserver = new MutationObserver((records) => this._onChildrenChanged(records));\n\n static get observedAttributes(): string[] {\n return ['multiple', 'name'];\n }\n\n /** Allows more than one panel open at a time. */\n get multiple(): boolean {\n return this.hasAttribute('multiple');\n }\n\n set multiple(value: boolean) {\n this.toggleAttribute('multiple', value);\n }\n\n connectedCallback(): void {\n this._childrenObserver.observe(this, { childList: true, subtree: true });\n\n this.addEventListener('toggle', this._onDetailsToggle, true);\n this.addEventListener('keydown', this._onKeydown);\n\n this._syncNativeGroupNames();\n this._enforceSingleOpen();\n }\n\n disconnectedCallback(): void {\n this._childrenObserver.disconnect();\n\n this.removeEventListener('toggle', this._onDetailsToggle, true);\n this.removeEventListener('keydown', this._onKeydown);\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) {\n return;\n }\n\n if (name === 'name' || name === 'multiple') {\n this._syncNativeGroupNames();\n }\n\n if (name === 'multiple' && !this.multiple) {\n this._enforceSingleOpen();\n }\n }\n\n private _$details(): HTMLDetailsElement[] {\n return Array.from(\n this.querySelectorAll<HTMLDetailsElement>(\n ':scope > details, :scope > rc-disclosure > details',\n ),\n );\n }\n\n private _$summaries(): HTMLElement[] {\n return this._$details()\n .map(($details) => $details.querySelector<HTMLElement>(':scope > summary'))\n .filter(($summary): $summary is HTMLElement => $summary !== null);\n }\n\n private _syncNativeGroupNames(): void {\n const groupName = this.getAttribute('name');\n const $details = this._$details();\n const $currentDetails = new Set($details);\n\n for (const [$el, managedName] of this._$managedGroupNames) {\n const shouldReleaseName =\n !$currentDetails.has($el) ||\n this.multiple ||\n !groupName ||\n managedName !== groupName ||\n $el.getAttribute('name') !== managedName;\n\n if (!shouldReleaseName) {\n continue;\n }\n\n if ($el.getAttribute('name') === managedName) {\n $el.removeAttribute('name');\n }\n\n this._$managedGroupNames.delete($el);\n }\n\n if (this.multiple || !groupName) {\n return;\n }\n\n for (const $el of $details) {\n if ($el.hasAttribute('name') && !this._$managedGroupNames.has($el)) {\n continue;\n }\n\n $el.setAttribute('name', groupName);\n this._$managedGroupNames.set($el, groupName);\n }\n }\n\n private _onKeydown = (e: KeyboardEvent): void => {\n if (e.altKey || e.ctrlKey || e.metaKey) {\n return;\n }\n\n if (!isNavigationKey(e.key)) {\n return;\n }\n\n const $summaries = this._$summaries();\n\n if ($summaries.length === 0) {\n return;\n }\n\n const $active = document.activeElement as HTMLElement | null;\n const index = $active ? $summaries.indexOf($active) : -1;\n\n if (index === -1) {\n return;\n }\n\n e.preventDefault();\n\n if (e.key === 'Home') {\n const $firstSummary = $summaries.at(0);\n\n if (!$firstSummary) {\n return;\n }\n\n $firstSummary.focus();\n\n return;\n }\n\n if (e.key === 'End') {\n const $lastSummary = $summaries.at(-1);\n\n if (!$lastSummary) {\n return;\n }\n\n $lastSummary.focus();\n\n return;\n }\n\n const next =\n e.key === 'ArrowDown'\n ? (index + 1) % $summaries.length\n : (index - 1 + $summaries.length) % $summaries.length;\n\n const $nextSummary = $summaries.at(next);\n\n if (!$nextSummary) {\n return;\n }\n\n $nextSummary.focus();\n };\n\n private _onDetailsToggle = (e: Event): void => {\n const $opened = e.target;\n\n if (!($opened instanceof HTMLDetailsElement)) {\n return;\n }\n\n if (this.multiple) {\n return;\n }\n\n if (!$opened.open) {\n return;\n }\n\n this._enforceSingleOpen($opened);\n };\n\n private _setDetailsOpen($target: HTMLDetailsElement, open: boolean): void {\n const $parent = $target.parentElement;\n\n if (isRCDisclosureElement($parent)) {\n $parent.open = open;\n\n return;\n }\n\n $target.open = open;\n }\n\n private _onChildrenChanged(records: MutationRecord[]): void {\n this._syncNativeGroupNames();\n\n if (this.multiple) {\n return;\n }\n\n this._enforceSingleOpen(this._findAddedOpenDetails(records));\n }\n\n private _findAddedOpenDetails(records: MutationRecord[]): HTMLDetailsElement | undefined {\n const $details = this._$details();\n\n const $added = $details.filter(($el) =>\n records.some((record) =>\n Array.from(record.addedNodes).some(\n ($node) => $node === $el || ($node instanceof Element && $node.contains($el)),\n ),\n ),\n );\n\n for (let i = $added.length - 1; i >= 0; i -= 1) {\n const $el = $added.at(i);\n\n if (!$el) {\n continue;\n }\n\n if ($el.open) {\n return $el;\n }\n }\n\n return undefined;\n }\n\n private _enforceSingleOpen($target?: HTMLDetailsElement): void {\n const $details = this._$details();\n\n if ($target && !$details.includes($target)) {\n return;\n }\n\n const $openDetails = $details.filter(($el) => $el.open);\n\n if ($openDetails.length <= 1) {\n return;\n }\n\n const $keepOpen = $target ?? $openDetails.at(0);\n\n if (!$keepOpen) {\n return;\n }\n\n for (const $el of $openDetails) {\n if ($el !== $keepOpen) {\n this._setDetailsOpen($el, false);\n }\n }\n }\n}\n\nexport default RCAccordion;\n"],"names":["NAVIGATION_KEYS","isNavigationKey","key","isRCDisclosureElement","$el","RCAccordion","records","$summaries","$active","index","$firstSummary","$lastSummary","next","$nextSummary","$opened","value","name","oldValue","newValue","$details","$summary","groupName","$currentDetails","managedName","$target","open","$parent","$added","record","$node","i","$openDetails","$keepOpen"],"mappings":"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"}
|
|
@@ -4,19 +4,34 @@ declare global {
|
|
|
4
4
|
}
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Accordion coordinator for child native <details> panels with single- or multiple-open
|
|
8
|
+
* behavior, following the WAI-ARIA Accordion pattern.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Supports native details and rc-disclosure wrappers.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-accordion rc-accordion docs}
|
|
13
|
+
* @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/accordion/ WAI-ARIA Accordion pattern}
|
|
14
|
+
*
|
|
15
|
+
* @attr {boolean} multiple - Allows more than one panel open at a time.
|
|
12
16
|
*/
|
|
13
17
|
export declare class RCAccordion extends HTMLElement {
|
|
18
|
+
private _$managedGroupNames;
|
|
19
|
+
private _childrenObserver;
|
|
20
|
+
static get observedAttributes(): string[];
|
|
21
|
+
/** Allows more than one panel open at a time. */
|
|
22
|
+
get multiple(): boolean;
|
|
23
|
+
set multiple(value: boolean);
|
|
14
24
|
connectedCallback(): void;
|
|
15
25
|
disconnectedCallback(): void;
|
|
16
|
-
|
|
17
|
-
private
|
|
26
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
27
|
+
private _$details;
|
|
28
|
+
private _$summaries;
|
|
18
29
|
private _syncNativeGroupNames;
|
|
19
30
|
private _onKeydown;
|
|
20
|
-
private
|
|
31
|
+
private _onDetailsToggle;
|
|
32
|
+
private _setDetailsOpen;
|
|
33
|
+
private _onChildrenChanged;
|
|
34
|
+
private _findAddedOpenDetails;
|
|
35
|
+
private _enforceSingleOpen;
|
|
21
36
|
}
|
|
22
37
|
export default RCAccordion;
|
package/package.json
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
7
|
-
"description": "
|
|
6
|
+
"version": "0.3.0",
|
|
7
|
+
"description": "Accordion coordinator for child native <details> panels with single- or multiple-open behavior.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/richardcarls/rc-webcomponents.git",
|
|
11
11
|
"directory": "packages/rc-accordion"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://github.
|
|
13
|
+
"homepage": "https://richardcarls.github.io/rc-webcomponents/components/rc-accordion",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"files": [
|
|
@@ -37,21 +37,17 @@
|
|
|
37
37
|
],
|
|
38
38
|
"customElements": "dist/custom-elements.json",
|
|
39
39
|
"scripts": {
|
|
40
|
-
"dev": "vite",
|
|
41
40
|
"build": "tsc && vite build && cem analyze",
|
|
42
41
|
"cem:analyze": "cem analyze",
|
|
43
|
-
"
|
|
44
|
-
"test:browser": "vitest",
|
|
45
|
-
"test:browser:
|
|
46
|
-
"test:browser:firefox": "vitest --project=firefox",
|
|
47
|
-
"yalc:publish": "yalc publish --push"
|
|
42
|
+
"test:browser": "vitest --run",
|
|
43
|
+
"test:browser:chrome": "vitest --run --project=chromium",
|
|
44
|
+
"test:browser:firefox": "vitest --run --project=firefox"
|
|
48
45
|
},
|
|
49
46
|
"dependencies": {
|
|
50
|
-
"@rcarls/rc-disclosure": "workspace
|
|
47
|
+
"@rcarls/rc-disclosure": "workspace:*"
|
|
51
48
|
},
|
|
52
49
|
"devDependencies": {
|
|
53
50
|
"@custom-elements-manifest/analyzer": "0.11.0",
|
|
54
|
-
"@guanghechen/rollup-plugin-copy": "^6.0.9",
|
|
55
51
|
"@vitest/browser-playwright": "4.1.5",
|
|
56
52
|
"lit": "^3.0.0",
|
|
57
53
|
"playwright": "^1.56.0",
|
package/dist/demo.css
DELETED
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
*, *::before, *::after {
|
|
2
|
-
box-sizing: border-box;
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
:root {
|
|
6
|
-
color-scheme: light dark;
|
|
7
|
-
--rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
@supports (color: AccentColor) {
|
|
11
|
-
:root {
|
|
12
|
-
--rc-accent: AccentColor;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
[data-theme="light"] { color-scheme: light; }
|
|
17
|
-
[data-theme="dark"] { color-scheme: dark; }
|
|
18
|
-
|
|
19
|
-
[data-theme="solarized-light"] {
|
|
20
|
-
color-scheme: light;
|
|
21
|
-
--rc-surface: #fdf6e3;
|
|
22
|
-
--rc-text: #657b83;
|
|
23
|
-
--rc-border: 1px solid #93a1a1;
|
|
24
|
-
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
[data-theme="solarized-dark"] {
|
|
28
|
-
color-scheme: dark;
|
|
29
|
-
--rc-surface: #002b36;
|
|
30
|
-
--rc-text: #839496;
|
|
31
|
-
--rc-border: 1px solid #586e75;
|
|
32
|
-
--rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
body {
|
|
36
|
-
font-family: system-ui, sans-serif;
|
|
37
|
-
margin: 0;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.demo-page {
|
|
41
|
-
max-width: 60rem;
|
|
42
|
-
margin: 0 auto;
|
|
43
|
-
padding: 2rem 1.5rem;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.demo-controls {
|
|
47
|
-
display: flex;
|
|
48
|
-
align-items: center;
|
|
49
|
-
gap: 1rem;
|
|
50
|
-
margin-bottom: 2rem;
|
|
51
|
-
padding-bottom: 1rem;
|
|
52
|
-
border-bottom: 1px solid ButtonBorder;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
.demo-controls .demo-title {
|
|
56
|
-
flex: 1;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.demo-controls h1 {
|
|
60
|
-
margin: 0 0 0.15rem;
|
|
61
|
-
font-size: 1.5rem;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.demo-controls a {
|
|
65
|
-
font-size: 0.8rem;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
.demo-section {
|
|
69
|
-
margin-bottom: 2rem;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.demo-section h2 {
|
|
73
|
-
margin: 0 0 0.5rem;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
.theme-picker {
|
|
77
|
-
padding: 0.35em 0.75em;
|
|
78
|
-
font-family: inherit;
|
|
79
|
-
font-size: 0.8rem;
|
|
80
|
-
cursor: pointer;
|
|
81
|
-
border: 1px solid ButtonBorder;
|
|
82
|
-
border-radius: 4px;
|
|
83
|
-
background: Canvas;
|
|
84
|
-
color: CanvasText;
|
|
85
|
-
}
|
package/dist/demo.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
|
|
2
|
-
const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
|
|
3
|
-
|
|
4
|
-
const stored = localStorage.getItem('rc-demo-theme') ?? '';
|
|
5
|
-
applyTheme(stored);
|
|
6
|
-
|
|
7
|
-
function applyTheme(theme) {
|
|
8
|
-
if (theme) {
|
|
9
|
-
document.documentElement.dataset.theme = theme;
|
|
10
|
-
} else {
|
|
11
|
-
delete document.documentElement.dataset.theme;
|
|
12
|
-
}
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function currentIndex() {
|
|
16
|
-
const current = document.documentElement.dataset.theme ?? '';
|
|
17
|
-
const idx = THEMES.indexOf(current);
|
|
18
|
-
return idx === -1 ? 0 : idx;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function updateButtons() {
|
|
22
|
-
const label = LABELS[currentIndex()];
|
|
23
|
-
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
24
|
-
btn.textContent = label;
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
window.cycleTheme = function () {
|
|
29
|
-
const next = THEMES[(currentIndex() + 1) % THEMES.length];
|
|
30
|
-
applyTheme(next);
|
|
31
|
-
localStorage.setItem('rc-demo-theme', next);
|
|
32
|
-
updateButtons();
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
document.addEventListener('DOMContentLoaded', () => {
|
|
36
|
-
updateButtons();
|
|
37
|
-
document.querySelectorAll('.theme-picker').forEach((btn) => {
|
|
38
|
-
btn.addEventListener('click', window.cycleTheme);
|
|
39
|
-
});
|
|
40
|
-
});
|