@rcarls/rc-disclosure 0.4.2 → 0.6.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/CHANGELOG.md +15 -0
- package/README.md +18 -1
- package/dist/custom-elements.json +78 -24
- package/dist/rc-disclosure.js +36 -43
- package/dist/rc-disclosure.js.map +1 -1
- package/dist/types/packages/rc-disclosure/src/rc-disclosure.d.ts +17 -16
- package/package.json +1 -1
- package/dist/types/packages/rc-disclosure/src/rc-disclosure.test.d.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @rcarls/rc-disclosure
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 57370e4: Add `defaultOpen` and `default-open` for uncontrolled disclosure state. Host writes to `open`
|
|
8
|
+
now establish controlled mode, user toggles report the requested state without overriding the
|
|
9
|
+
host value, and assigning `undefined` releases control.
|
|
10
|
+
|
|
11
|
+
## 0.5.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 689340c: Remove pre-1.0 deprecated APIs: the boolean `keyNavigation` option, `rc-disclosure.fragment`,
|
|
16
|
+
`rc-textarea.listNumbers`, `rc-textarea.label`, and `decorationsFromHtml`.
|
|
17
|
+
|
|
3
18
|
## 0.4.2
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,3 +1,20 @@
|
|
|
1
1
|
# @rcarls/rc-disclosure
|
|
2
2
|
|
|
3
|
-
Native details
|
|
3
|
+
Native `<details>`/`<summary>` disclosure web component with light-DOM styling hooks and
|
|
4
|
+
controlled or uncontrolled open state.
|
|
5
|
+
|
|
6
|
+
Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-disclosure](https://richardcarls.github.io/rc-webcomponents/components/rc-disclosure).
|
|
7
|
+
|
|
8
|
+
```html
|
|
9
|
+
<rc-disclosure default-open>
|
|
10
|
+
<details>
|
|
11
|
+
<summary>Shipping details</summary>
|
|
12
|
+
<p>Orders usually ship within two business days.</p>
|
|
13
|
+
</details>
|
|
14
|
+
</rc-disclosure>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use `default-open` for uncontrolled initial state. Writing `open` enables controlled mode;
|
|
18
|
+
user interaction emits `rc-disclosure-toggle` with `{ open: boolean }`, while the rendered
|
|
19
|
+
state continues to follow the host value. Assign `undefined` to the JavaScript `open` property
|
|
20
|
+
to release control.
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"declarations": [
|
|
47
47
|
{
|
|
48
48
|
"kind": "class",
|
|
49
|
-
"description": "Disclosure wrapper for a native <details>/<summary> pair with controlled
|
|
49
|
+
"description": "Disclosure wrapper for a native <details>/<summary> pair with controlled and uncontrolled\nopen state,\nfollowing the WAI-ARIA Disclosure pattern.\n\nThe browser keeps ownership of open/close behavior, keyboard support, and\naccessibility. This element mirrors state, emits a consistent custom event,\nand automatically opens when the URL hash matches any id within its subtree.",
|
|
50
50
|
"name": "RCDisclosure",
|
|
51
51
|
"members": [
|
|
52
52
|
{
|
|
@@ -75,34 +75,84 @@
|
|
|
75
75
|
},
|
|
76
76
|
{
|
|
77
77
|
"kind": "field",
|
|
78
|
-
"name": "
|
|
78
|
+
"name": "_controlledOpen",
|
|
79
|
+
"type": {
|
|
80
|
+
"text": "boolean | undefined"
|
|
81
|
+
},
|
|
82
|
+
"privacy": "private"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"kind": "field",
|
|
86
|
+
"name": "_defaultOpen",
|
|
79
87
|
"type": {
|
|
80
88
|
"text": "boolean"
|
|
81
89
|
},
|
|
82
|
-
"
|
|
90
|
+
"privacy": "private",
|
|
91
|
+
"default": "false"
|
|
83
92
|
},
|
|
84
93
|
{
|
|
85
94
|
"kind": "field",
|
|
86
|
-
"name": "
|
|
95
|
+
"name": "_defaultOpenInitialized",
|
|
87
96
|
"type": {
|
|
88
97
|
"text": "boolean"
|
|
89
98
|
},
|
|
90
|
-
"
|
|
91
|
-
"
|
|
99
|
+
"privacy": "private",
|
|
100
|
+
"default": "false"
|
|
92
101
|
},
|
|
93
102
|
{
|
|
94
|
-
"kind": "
|
|
95
|
-
"name": "
|
|
103
|
+
"kind": "field",
|
|
104
|
+
"name": "_uncontrolledOpen",
|
|
105
|
+
"type": {
|
|
106
|
+
"text": "boolean | undefined"
|
|
107
|
+
},
|
|
108
|
+
"privacy": "private"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"kind": "field",
|
|
112
|
+
"name": "_openInitialized",
|
|
113
|
+
"type": {
|
|
114
|
+
"text": "boolean"
|
|
115
|
+
},
|
|
96
116
|
"privacy": "private",
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
117
|
+
"default": "false"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"kind": "field",
|
|
121
|
+
"name": "_reflectingOpen",
|
|
122
|
+
"type": {
|
|
123
|
+
"text": "boolean"
|
|
124
|
+
},
|
|
125
|
+
"privacy": "private",
|
|
126
|
+
"default": "false"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"kind": "field",
|
|
130
|
+
"name": "_suppressNextToggle",
|
|
131
|
+
"type": {
|
|
132
|
+
"text": "boolean"
|
|
133
|
+
},
|
|
134
|
+
"privacy": "private",
|
|
135
|
+
"default": "false"
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"kind": "field",
|
|
139
|
+
"name": "open",
|
|
140
|
+
"type": {
|
|
141
|
+
"text": "boolean"
|
|
142
|
+
},
|
|
143
|
+
"description": "Current open state. Host writes are silent; assign `undefined` to release control."
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"kind": "field",
|
|
147
|
+
"name": "defaultOpen",
|
|
148
|
+
"type": {
|
|
149
|
+
"text": "boolean"
|
|
150
|
+
},
|
|
151
|
+
"description": "Initial open state for uncontrolled usage."
|
|
102
152
|
},
|
|
103
153
|
{
|
|
104
154
|
"kind": "method",
|
|
105
|
-
"name": "
|
|
155
|
+
"name": "_setupDetails",
|
|
106
156
|
"privacy": "private",
|
|
107
157
|
"return": {
|
|
108
158
|
"type": {
|
|
@@ -112,7 +162,7 @@
|
|
|
112
162
|
},
|
|
113
163
|
{
|
|
114
164
|
"kind": "method",
|
|
115
|
-
"name": "
|
|
165
|
+
"name": "_teardownDetails",
|
|
116
166
|
"privacy": "private",
|
|
117
167
|
"return": {
|
|
118
168
|
"type": {
|
|
@@ -122,7 +172,7 @@
|
|
|
122
172
|
},
|
|
123
173
|
{
|
|
124
174
|
"kind": "method",
|
|
125
|
-
"name": "
|
|
175
|
+
"name": "_applyOpen",
|
|
126
176
|
"privacy": "private",
|
|
127
177
|
"return": {
|
|
128
178
|
"type": {
|
|
@@ -131,16 +181,14 @@
|
|
|
131
181
|
},
|
|
132
182
|
"parameters": [
|
|
133
183
|
{
|
|
134
|
-
"name": "
|
|
184
|
+
"name": "open",
|
|
135
185
|
"type": {
|
|
136
186
|
"text": "boolean"
|
|
137
187
|
}
|
|
138
188
|
},
|
|
139
189
|
{
|
|
140
|
-
"name": "
|
|
141
|
-
"
|
|
142
|
-
"text": "boolean"
|
|
143
|
-
}
|
|
190
|
+
"name": "suppressToggle",
|
|
191
|
+
"default": "true"
|
|
144
192
|
}
|
|
145
193
|
]
|
|
146
194
|
},
|
|
@@ -184,6 +232,12 @@
|
|
|
184
232
|
}
|
|
185
233
|
],
|
|
186
234
|
"events": [
|
|
235
|
+
{
|
|
236
|
+
"name": "TOGGLE_EVENT",
|
|
237
|
+
"type": {
|
|
238
|
+
"text": "CustomEvent"
|
|
239
|
+
}
|
|
240
|
+
},
|
|
187
241
|
{
|
|
188
242
|
"description": "Fires when the child `<details>` toggles.",
|
|
189
243
|
"name": "rc-disclosure-toggle"
|
|
@@ -192,11 +246,11 @@
|
|
|
192
246
|
"attributes": [
|
|
193
247
|
{
|
|
194
248
|
"name": "open",
|
|
195
|
-
"description": "
|
|
249
|
+
"description": "Controlled open state mirrored to the child `<details>`."
|
|
196
250
|
},
|
|
197
251
|
{
|
|
198
|
-
"name": "
|
|
199
|
-
"description": "
|
|
252
|
+
"name": "default-open",
|
|
253
|
+
"description": "Initial open state for uncontrolled usage."
|
|
200
254
|
}
|
|
201
255
|
],
|
|
202
256
|
"superclass": {
|
package/dist/rc-disclosure.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
const n = "rc-disclosure-toggle", r = ":scope > details", i = ":scope > summary";
|
|
2
|
-
class
|
|
2
|
+
class l extends HTMLElement {
|
|
3
3
|
constructor() {
|
|
4
|
-
super(...arguments), this._observer = new MutationObserver(() => this._setupDetails()), this._$details = null, this._scrollFrame = 0, this._onToggle = () => {
|
|
4
|
+
super(...arguments), this._observer = new MutationObserver(() => this._setupDetails()), this._$details = null, this._scrollFrame = 0, this._defaultOpen = !1, this._defaultOpenInitialized = !1, this._openInitialized = !1, this._reflectingOpen = !1, this._suppressNextToggle = !1, this._onToggle = () => {
|
|
5
|
+
if (this._suppressNextToggle) {
|
|
6
|
+
this._suppressNextToggle = !1;
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
5
9
|
const e = this._$details?.open ?? !1;
|
|
6
|
-
this._reflectOpen(e), this.dispatchEvent(
|
|
10
|
+
this._controlledOpen === void 0 ? (this._uncontrolledOpen = e, this._reflectOpen(e)) : (this._applyOpen(this._controlledOpen, !0), this._reflectOpen(this._controlledOpen)), this.dispatchEvent(
|
|
7
11
|
new CustomEvent(n, {
|
|
8
12
|
bubbles: !0,
|
|
9
13
|
composed: !0,
|
|
@@ -15,27 +19,21 @@ class a extends HTMLElement {
|
|
|
15
19
|
};
|
|
16
20
|
}
|
|
17
21
|
static get observedAttributes() {
|
|
18
|
-
return ["open", "
|
|
22
|
+
return ["open", "default-open"];
|
|
19
23
|
}
|
|
20
|
-
/** Current open state
|
|
24
|
+
/** Current open state. Host writes are silent; assign `undefined` to release control. */
|
|
21
25
|
get open() {
|
|
22
|
-
return this.
|
|
26
|
+
return this._controlledOpen ?? this._uncontrolledOpen ?? this._defaultOpen;
|
|
23
27
|
}
|
|
24
28
|
set open(e) {
|
|
25
|
-
this.
|
|
29
|
+
this._controlledOpen = e, this._openInitialized = !0, this._applyOpen(this.open), this._reflectOpen(this.open);
|
|
26
30
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @deprecated No longer required. `rc-disclosure` now automatically opens and
|
|
31
|
-
* scrolls when the URL hash matches any id within its subtree. Will be removed
|
|
32
|
-
* in v1.0 release.
|
|
33
|
-
*/
|
|
34
|
-
get fragment() {
|
|
35
|
-
return this.hasAttribute("fragment");
|
|
31
|
+
/** Initial open state for uncontrolled usage. */
|
|
32
|
+
get defaultOpen() {
|
|
33
|
+
return this._defaultOpen;
|
|
36
34
|
}
|
|
37
|
-
set
|
|
38
|
-
this.
|
|
35
|
+
set defaultOpen(e) {
|
|
36
|
+
this._defaultOpen = e, this._defaultOpenInitialized = !0, !this._openInitialized && this._controlledOpen === void 0 && this._uncontrolledOpen === void 0 && (this._applyOpen(e), this._reflectOpen(e));
|
|
39
37
|
}
|
|
40
38
|
connectedCallback() {
|
|
41
39
|
this._observer.observe(this, { childList: !0 }), window.addEventListener("hashchange", this._onHashChange), this._setupDetails();
|
|
@@ -43,41 +41,30 @@ class a extends HTMLElement {
|
|
|
43
41
|
disconnectedCallback() {
|
|
44
42
|
this._observer.disconnect(), window.removeEventListener("hashchange", this._onHashChange), cancelAnimationFrame(this._scrollFrame), this._teardownDetails();
|
|
45
43
|
}
|
|
46
|
-
attributeChangedCallback(e,
|
|
47
|
-
if (
|
|
48
|
-
if (e === "open") {
|
|
49
|
-
this.
|
|
44
|
+
attributeChangedCallback(e, s, t) {
|
|
45
|
+
if (s !== t) {
|
|
46
|
+
if (e === "default-open") {
|
|
47
|
+
this.defaultOpen = t !== null;
|
|
50
48
|
return;
|
|
51
49
|
}
|
|
52
|
-
|
|
50
|
+
this._reflectingOpen || (this.open = t !== null);
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
53
|
_setupDetails() {
|
|
56
54
|
const e = this.querySelector(r);
|
|
57
55
|
if (e === this._$details || (this._teardownDetails(), this._$details = e, !e))
|
|
58
56
|
return;
|
|
59
|
-
e.id || (e.id = crypto.randomUUID()), e.querySelector(i)?.setAttribute("aria-controls", e.id), e.addEventListener("toggle", this._onToggle),
|
|
57
|
+
e.id || (e.id = crypto.randomUUID()), e.querySelector(i)?.setAttribute("aria-controls", e.id), e.addEventListener("toggle", this._onToggle), this._controlledOpen === void 0 && this._uncontrolledOpen === void 0 && (this._uncontrolledOpen = this._defaultOpenInitialized ? this._defaultOpen : e.open), this._applyOpen(this.open, !0), this._reflectOpen(this.open), this._openForCurrentHash();
|
|
60
58
|
}
|
|
61
59
|
_teardownDetails() {
|
|
62
60
|
this._$details?.querySelector(i)?.removeAttribute("aria-controls"), this._$details?.removeEventListener("toggle", this._onToggle), this._$details = null;
|
|
63
61
|
}
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
return;
|
|
68
|
-
const t = this.hasAttribute("open");
|
|
69
|
-
e.open !== t && (e.open = t);
|
|
70
|
-
}
|
|
71
|
-
_setOpen(e, t) {
|
|
72
|
-
const s = this._$details;
|
|
73
|
-
s && s.open !== e && (s.open = e), t && this._reflectOpen(e);
|
|
62
|
+
_applyOpen(e, s = !0) {
|
|
63
|
+
const t = this._$details;
|
|
64
|
+
t && t.open !== e && (this._suppressNextToggle = s, t.open = e);
|
|
74
65
|
}
|
|
75
66
|
_reflectOpen(e) {
|
|
76
|
-
|
|
77
|
-
this.hasAttribute("open") || this.setAttribute("open", "");
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this.hasAttribute("open") && this.removeAttribute("open");
|
|
67
|
+
this.hasAttribute("open") !== e && (this._reflectingOpen = !0, this.toggleAttribute("open", e), this._reflectingOpen = !1);
|
|
81
68
|
}
|
|
82
69
|
_openForCurrentHash() {
|
|
83
70
|
if (!location.hash)
|
|
@@ -85,13 +72,19 @@ class a extends HTMLElement {
|
|
|
85
72
|
const e = decodeURIComponent(location.hash.slice(1));
|
|
86
73
|
if (!e || !this.querySelector(`#${CSS.escape(e)}`))
|
|
87
74
|
return;
|
|
88
|
-
const
|
|
89
|
-
this.
|
|
75
|
+
const t = this.open;
|
|
76
|
+
this._controlledOpen === void 0 ? (this._uncontrolledOpen = !0, this._applyOpen(!0), this._reflectOpen(!0)) : this.dispatchEvent(
|
|
77
|
+
new CustomEvent(n, {
|
|
78
|
+
bubbles: !0,
|
|
79
|
+
composed: !0,
|
|
80
|
+
detail: { open: !0 }
|
|
81
|
+
})
|
|
82
|
+
), this.open && (t || (cancelAnimationFrame(this._scrollFrame), this._scrollFrame = requestAnimationFrame(() => {
|
|
90
83
|
document.getElementById(e)?.scrollIntoView();
|
|
91
|
-
}));
|
|
84
|
+
})));
|
|
92
85
|
}
|
|
93
86
|
}
|
|
94
87
|
export {
|
|
95
|
-
|
|
88
|
+
l as RCDisclosure
|
|
96
89
|
};
|
|
97
90
|
//# sourceMappingURL=rc-disclosure.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rc-disclosure.js","sources":["../src/rc-disclosure.ts"],"sourcesContent":["export interface RCDisclosureToggleEvent {\n /** Whether the disclosure is now open. */\n open: boolean;\n}\n\nconst TOGGLE_EVENT = 'rc-disclosure-toggle';\nconst DETAILS_SELECTOR = ':scope > details';\nconst SUMMARY_SELECTOR = ':scope > summary';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-disclosure': RCDisclosure;\n }\n}\n\n/**\n * Disclosure wrapper for a native <details>/<summary> pair with controlled open state,\n * following the WAI-ARIA Disclosure pattern.\n *\n * The browser keeps ownership of open/close behavior, keyboard support, and\n * accessibility. This element mirrors state, emits a consistent custom event,\n * and automatically opens when the URL hash matches any id within its subtree.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-disclosure rc-disclosure docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/ WAI-ARIA Disclosure pattern}\n *\n * @fires rc-disclosure-toggle - Fires when the child `<details>` toggles.\n *\n * @attr open - Current open state mirrored to the child `<details>`.\n * @attr fragment - Deprecated legacy opt-in for URL-fragment auto-open; removed before v1.0.\n */\nexport class RCDisclosure extends HTMLElement {\n private _observer = new MutationObserver(() => this._setupDetails());\n\n private _$details: HTMLDetailsElement | null = null;\n\n private _scrollFrame = 0;\n\n static get observedAttributes(): string[] {\n return ['open', 'fragment'];\n }\n\n /** Current open state mirrored to the child `<details>`. */\n get open(): boolean {\n return this._$details?.open ?? this.hasAttribute('open');\n }\n\n set open(value: boolean) {\n this._setOpen(value, true);\n }\n\n /**\n * Legacy manual opt-in for URL-fragment auto-open.\n *\n * @deprecated No longer required. `rc-disclosure` now automatically opens and\n * scrolls when the URL hash matches any id within its subtree. Will be removed\n * in v1.0 release.\n */\n get fragment(): boolean {\n return this.hasAttribute('fragment');\n }\n\n set fragment(value: boolean) {\n this.toggleAttribute('fragment', value);\n }\n\n connectedCallback(): void {\n this._observer.observe(this, { childList: true });\n window.addEventListener('hashchange', this._onHashChange);\n this._setupDetails();\n }\n\n disconnectedCallback(): void {\n this._observer.disconnect();\n window.removeEventListener('hashchange', this._onHashChange);\n cancelAnimationFrame(this._scrollFrame);\n this._teardownDetails();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) {\n return;\n }\n\n if (name === 'open') {\n this._syncDetailsOpen();\n\n return;\n }\n\n if (name === 'fragment') {\n this._openForCurrentHash();\n }\n }\n\n private _setupDetails(): void {\n const $details = this.querySelector<HTMLDetailsElement>(DETAILS_SELECTOR);\n if ($details === this._$details) {\n return;\n }\n\n this._teardownDetails();\n this._$details = $details;\n\n if (!$details) {\n if (import.meta.env.DEV) {\n console.warn(\n '[rc-disclosure] No direct child <details> found. Place native ' +\n '<details><summary>...</summary>...</details> inside <rc-disclosure>.',\n this,\n );\n }\n\n return;\n }\n\n if (!$details.id) {\n $details.id = crypto.randomUUID();\n }\n\n const $summary = $details.querySelector<HTMLElement>(SUMMARY_SELECTOR);\n $summary?.setAttribute('aria-controls', $details.id);\n\n $details.addEventListener('toggle', this._onToggle);\n\n // If the host hasn't been given an explicit initial `open` of its own,\n // adopt whatever the wrapped <details> markup already says instead of\n // silently forcing it closed on connect — plain `<details open>`\n // should still behave the way it always has when nothing overrides it.\n if (!this.hasAttribute('open') && $details.open) {\n this._reflectOpen(true);\n }\n\n this._syncDetailsOpen();\n this._reflectOpen($details.open);\n this._openForCurrentHash();\n }\n\n private _teardownDetails(): void {\n const $summary = this._$details?.querySelector<HTMLElement>(SUMMARY_SELECTOR);\n $summary?.removeAttribute('aria-controls');\n\n this._$details?.removeEventListener('toggle', this._onToggle);\n this._$details = null;\n }\n\n private _syncDetailsOpen(): void {\n const $details = this._$details;\n if (!$details) {\n return;\n }\n\n const nextOpen = this.hasAttribute('open');\n if ($details.open !== nextOpen) {\n $details.open = nextOpen;\n }\n }\n\n private _setOpen(value: boolean, reflect: boolean): void {\n const $details = this._$details;\n if ($details && $details.open !== value) {\n $details.open = value;\n }\n\n // _reflectOpen is called here for programmatic opens because the <details>\n // toggle event is async (queued task per HTML spec). _onToggle also calls\n // _reflectOpen for user-initiated clicks, but that arrives after a tick.\n if (reflect) {\n this._reflectOpen(value);\n }\n }\n\n private _reflectOpen(value: boolean): void {\n if (value) {\n if (!this.hasAttribute('open')) {\n this.setAttribute('open', '');\n }\n\n return;\n }\n\n if (this.hasAttribute('open')) {\n this.removeAttribute('open');\n }\n }\n\n private _onToggle = (): void => {\n const open = this._$details?.open ?? false;\n\n this._reflectOpen(open);\n this.dispatchEvent(\n new CustomEvent<RCDisclosureToggleEvent>(TOGGLE_EVENT, {\n bubbles: true,\n composed: true,\n detail: { open },\n }),\n );\n };\n\n private _onHashChange = (): void => {\n this._openForCurrentHash();\n };\n\n private _openForCurrentHash(): void {\n if (!location.hash) {\n return;\n }\n\n const targetId = decodeURIComponent(location.hash.slice(1));\n if (!targetId) {\n return;\n }\n\n const $target = this.querySelector(`#${CSS.escape(targetId)}`);\n if (!$target) {\n return;\n }\n\n const wasOpen = this.open;\n\n this.open = true;\n\n if (wasOpen) {\n return;\n }\n\n cancelAnimationFrame(this._scrollFrame);\n this._scrollFrame = requestAnimationFrame(() => {\n document.getElementById(targetId)?.scrollIntoView();\n });\n }\n}\n\nexport default RCDisclosure;\n"],"names":["TOGGLE_EVENT","DETAILS_SELECTOR","SUMMARY_SELECTOR","RCDisclosure","open","value","name","oldValue","newValue","$details","nextOpen","reflect","targetId","wasOpen"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rc-disclosure.js","sources":["../src/rc-disclosure.ts"],"sourcesContent":["export interface RCDisclosureToggleEvent {\n /** Whether the disclosure is now open. */\n open: boolean;\n}\n\nconst TOGGLE_EVENT = 'rc-disclosure-toggle';\nconst DETAILS_SELECTOR = ':scope > details';\nconst SUMMARY_SELECTOR = ':scope > summary';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-disclosure': RCDisclosure;\n }\n}\n\n/**\n * Disclosure wrapper for a native <details>/<summary> pair with controlled and uncontrolled\n * open state,\n * following the WAI-ARIA Disclosure pattern.\n *\n * The browser keeps ownership of open/close behavior, keyboard support, and\n * accessibility. This element mirrors state, emits a consistent custom event,\n * and automatically opens when the URL hash matches any id within its subtree.\n *\n * @see {@link https://richardcarls.github.io/rc-webcomponents/components/rc-disclosure rc-disclosure docs}\n * @see {@link https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/ WAI-ARIA Disclosure pattern}\n *\n * @fires rc-disclosure-toggle - Fires when the child `<details>` toggles.\n *\n * @attr open - Controlled open state mirrored to the child `<details>`.\n * @attr default-open - Initial open state for uncontrolled usage.\n */\nexport class RCDisclosure extends HTMLElement {\n private _observer = new MutationObserver(() => this._setupDetails());\n\n private _$details: HTMLDetailsElement | null = null;\n\n private _scrollFrame = 0;\n private _controlledOpen: boolean | undefined;\n private _defaultOpen = false;\n private _defaultOpenInitialized = false;\n private _uncontrolledOpen: boolean | undefined;\n private _openInitialized = false;\n private _reflectingOpen = false;\n private _suppressNextToggle = false;\n\n static get observedAttributes(): string[] {\n return ['open', 'default-open'];\n }\n\n /** Current open state. Host writes are silent; assign `undefined` to release control. */\n get open(): boolean {\n return this._controlledOpen ?? this._uncontrolledOpen ?? this._defaultOpen;\n }\n\n set open(value: boolean | undefined) {\n this._controlledOpen = value;\n this._openInitialized = true;\n this._applyOpen(this.open);\n this._reflectOpen(this.open);\n }\n\n /** Initial open state for uncontrolled usage. */\n get defaultOpen(): boolean {\n return this._defaultOpen;\n }\n\n set defaultOpen(value: boolean) {\n this._defaultOpen = value;\n this._defaultOpenInitialized = true;\n\n if (\n !this._openInitialized &&\n this._controlledOpen === undefined &&\n this._uncontrolledOpen === undefined\n ) {\n this._applyOpen(value);\n this._reflectOpen(value);\n }\n }\n\n connectedCallback(): void {\n this._observer.observe(this, { childList: true });\n window.addEventListener('hashchange', this._onHashChange);\n this._setupDetails();\n }\n\n disconnectedCallback(): void {\n this._observer.disconnect();\n window.removeEventListener('hashchange', this._onHashChange);\n cancelAnimationFrame(this._scrollFrame);\n this._teardownDetails();\n }\n\n attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void {\n if (oldValue === newValue) {\n return;\n }\n\n if (name === 'default-open') {\n this.defaultOpen = newValue !== null;\n\n return;\n }\n\n if (this._reflectingOpen) {\n return;\n }\n\n this.open = newValue === null ? false : true;\n }\n\n private _setupDetails(): void {\n const $details = this.querySelector<HTMLDetailsElement>(DETAILS_SELECTOR);\n\n if ($details === this._$details) {\n return;\n }\n\n this._teardownDetails();\n this._$details = $details;\n\n if (!$details) {\n if (import.meta.env.DEV) {\n console.warn(\n '[rc-disclosure] No direct child <details> found. Place native ' +\n '<details><summary>...</summary>...</details> inside <rc-disclosure>.',\n this,\n );\n }\n\n return;\n }\n\n if (!$details.id) {\n $details.id = crypto.randomUUID();\n }\n\n const $summary = $details.querySelector<HTMLElement>(SUMMARY_SELECTOR);\n\n $summary?.setAttribute('aria-controls', $details.id);\n\n $details.addEventListener('toggle', this._onToggle);\n\n if (this._controlledOpen === undefined && this._uncontrolledOpen === undefined) {\n this._uncontrolledOpen = this._defaultOpenInitialized ? this._defaultOpen : $details.open;\n }\n\n this._applyOpen(this.open, true);\n this._reflectOpen(this.open);\n this._openForCurrentHash();\n }\n\n private _teardownDetails(): void {\n const $summary = this._$details?.querySelector<HTMLElement>(SUMMARY_SELECTOR);\n\n $summary?.removeAttribute('aria-controls');\n\n this._$details?.removeEventListener('toggle', this._onToggle);\n this._$details = null;\n }\n\n private _applyOpen(open: boolean, suppressToggle = true): void {\n const $details = this._$details;\n\n if (!$details) {\n return;\n }\n\n if ($details.open !== open) {\n this._suppressNextToggle = suppressToggle;\n $details.open = open;\n }\n }\n\n private _reflectOpen(value: boolean): void {\n if (this.hasAttribute('open') === value) {\n return;\n }\n\n this._reflectingOpen = true;\n this.toggleAttribute('open', value);\n this._reflectingOpen = false;\n }\n\n private _onToggle = (): void => {\n if (this._suppressNextToggle) {\n this._suppressNextToggle = false;\n\n return;\n }\n\n const requestedOpen = this._$details?.open ?? false;\n\n if (this._controlledOpen === undefined) {\n this._uncontrolledOpen = requestedOpen;\n this._reflectOpen(requestedOpen);\n } else {\n this._applyOpen(this._controlledOpen, true);\n this._reflectOpen(this._controlledOpen);\n }\n\n this.dispatchEvent(\n new CustomEvent<RCDisclosureToggleEvent>(TOGGLE_EVENT, {\n bubbles: true,\n composed: true,\n detail: { open: requestedOpen },\n }),\n );\n };\n\n private _onHashChange = (): void => {\n this._openForCurrentHash();\n };\n\n private _openForCurrentHash(): void {\n if (!location.hash) {\n return;\n }\n\n const targetId = decodeURIComponent(location.hash.slice(1));\n\n if (!targetId) {\n return;\n }\n\n const $target = this.querySelector(`#${CSS.escape(targetId)}`);\n\n if (!$target) {\n return;\n }\n\n const wasOpen = this.open;\n\n if (this._controlledOpen === undefined) {\n this._uncontrolledOpen = true;\n this._applyOpen(true);\n this._reflectOpen(true);\n } else {\n this.dispatchEvent(\n new CustomEvent<RCDisclosureToggleEvent>(TOGGLE_EVENT, {\n bubbles: true,\n composed: true,\n detail: { open: true },\n }),\n );\n }\n\n if (!this.open) {\n return;\n }\n\n if (wasOpen) {\n return;\n }\n\n cancelAnimationFrame(this._scrollFrame);\n\n this._scrollFrame = requestAnimationFrame(() => {\n document.getElementById(targetId)?.scrollIntoView();\n });\n }\n}\n\nexport default RCDisclosure;\n"],"names":["TOGGLE_EVENT","DETAILS_SELECTOR","SUMMARY_SELECTOR","RCDisclosure","requestedOpen","value","name","oldValue","newValue","$details","open","suppressToggle","targetId","wasOpen"],"mappings":"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"}
|
|
@@ -8,7 +8,8 @@ declare global {
|
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Disclosure wrapper for a native <details>/<summary> pair with controlled
|
|
11
|
+
* Disclosure wrapper for a native <details>/<summary> pair with controlled and uncontrolled
|
|
12
|
+
* open state,
|
|
12
13
|
* following the WAI-ARIA Disclosure pattern.
|
|
13
14
|
*
|
|
14
15
|
* The browser keeps ownership of open/close behavior, keyboard support, and
|
|
@@ -20,33 +21,33 @@ declare global {
|
|
|
20
21
|
*
|
|
21
22
|
* @fires rc-disclosure-toggle - Fires when the child `<details>` toggles.
|
|
22
23
|
*
|
|
23
|
-
* @attr open -
|
|
24
|
-
* @attr
|
|
24
|
+
* @attr open - Controlled open state mirrored to the child `<details>`.
|
|
25
|
+
* @attr default-open - Initial open state for uncontrolled usage.
|
|
25
26
|
*/
|
|
26
27
|
export declare class RCDisclosure extends HTMLElement {
|
|
27
28
|
private _observer;
|
|
28
29
|
private _$details;
|
|
29
30
|
private _scrollFrame;
|
|
31
|
+
private _controlledOpen;
|
|
32
|
+
private _defaultOpen;
|
|
33
|
+
private _defaultOpenInitialized;
|
|
34
|
+
private _uncontrolledOpen;
|
|
35
|
+
private _openInitialized;
|
|
36
|
+
private _reflectingOpen;
|
|
37
|
+
private _suppressNextToggle;
|
|
30
38
|
static get observedAttributes(): string[];
|
|
31
|
-
/** Current open state
|
|
39
|
+
/** Current open state. Host writes are silent; assign `undefined` to release control. */
|
|
32
40
|
get open(): boolean;
|
|
33
|
-
set open(value: boolean);
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* @deprecated No longer required. `rc-disclosure` now automatically opens and
|
|
38
|
-
* scrolls when the URL hash matches any id within its subtree. Will be removed
|
|
39
|
-
* in v1.0 release.
|
|
40
|
-
*/
|
|
41
|
-
get fragment(): boolean;
|
|
42
|
-
set fragment(value: boolean);
|
|
41
|
+
set open(value: boolean | undefined);
|
|
42
|
+
/** Initial open state for uncontrolled usage. */
|
|
43
|
+
get defaultOpen(): boolean;
|
|
44
|
+
set defaultOpen(value: boolean);
|
|
43
45
|
connectedCallback(): void;
|
|
44
46
|
disconnectedCallback(): void;
|
|
45
47
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
46
48
|
private _setupDetails;
|
|
47
49
|
private _teardownDetails;
|
|
48
|
-
private
|
|
49
|
-
private _setOpen;
|
|
50
|
+
private _applyOpen;
|
|
50
51
|
private _reflectOpen;
|
|
51
52
|
private _onToggle;
|
|
52
53
|
private _onHashChange;
|
package/package.json
CHANGED
|
File without changes
|