@magic-spells/collapsible-content 1.0.0 → 1.1.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 +72 -21
- package/dist/collapsible-content.cjs.js +128 -36
- package/dist/collapsible-content.cjs.js.map +1 -1
- package/dist/collapsible-content.css +16 -16
- package/dist/collapsible-content.esm.js +128 -36
- package/dist/collapsible-content.esm.js.map +1 -1
- package/dist/collapsible-content.js +128 -36
- package/dist/collapsible-content.js.map +1 -1
- package/dist/collapsible-content.min.css +1 -1
- package/dist/collapsible-content.min.js +1 -1
- package/package.json +2 -2
- package/src/collapsible-content.css +16 -16
- package/src/collapsible-content.js +128 -36
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ A lightweight, accessible Web Component for creating collapsible content section
|
|
|
10
10
|
- Lightweight
|
|
11
11
|
- Accessible (ARIA attributes, keyboard support, focus management)
|
|
12
12
|
- Smooth animations with `prefers-reduced-motion` support
|
|
13
|
-
-
|
|
13
|
+
- Smooth mid-animation reversal on rapid clicks
|
|
14
14
|
- Safe to import multiple times (no double-registration errors)
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
@@ -33,13 +33,13 @@ Or include directly in your HTML:
|
|
|
33
33
|
|
|
34
34
|
```html
|
|
35
35
|
<collapsible-component>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
<button type="button">Product Information</button>
|
|
37
|
+
<collapsible-content>
|
|
38
|
+
<div class="content-wrapper">
|
|
39
|
+
<h3>Details</h3>
|
|
40
|
+
<p>This product is made with 100% organic materials.</p>
|
|
41
|
+
</div>
|
|
42
|
+
</collapsible-content>
|
|
43
43
|
</collapsible-component>
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -49,28 +49,79 @@ Add the `open` attribute to start with content visible:
|
|
|
49
49
|
|
|
50
50
|
```html
|
|
51
51
|
<collapsible-component>
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
<button type="button">Already Open</button>
|
|
53
|
+
<collapsible-content open>
|
|
54
|
+
<p>This content is visible by default.</p>
|
|
55
|
+
</collapsible-content>
|
|
56
56
|
</collapsible-component>
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
## Animation Speed
|
|
60
|
+
|
|
61
|
+
Animation duration scales dynamically with content height using a px/sec speed model. Short panels animate quickly, tall panels take proportionally longer — no fixed duration that feels too slow or too fast.
|
|
62
|
+
|
|
63
|
+
The default speed is `900` px/sec. Duration is clamped between 250ms and 1s so animations always feel responsive. Tune it with the `speed`, `min-duration`, and `max-duration` attributes:
|
|
64
|
+
|
|
65
|
+
```html
|
|
66
|
+
<!-- Default: 900px/sec -->
|
|
67
|
+
<collapsible-content>...</collapsible-content>
|
|
68
|
+
|
|
69
|
+
<!-- Faster -->
|
|
70
|
+
<collapsible-content speed="1200">...</collapsible-content>
|
|
71
|
+
|
|
72
|
+
<!-- Slower -->
|
|
73
|
+
<collapsible-content speed="80">...</collapsible-content>
|
|
74
|
+
|
|
75
|
+
<!-- Snappier minimum (150ms instead of 250ms) -->
|
|
76
|
+
<collapsible-content min-duration="0.15">...</collapsible-content>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
| Attribute | Default | Description |
|
|
80
|
+
| -------------- | ------- | ------------------------------------------ |
|
|
81
|
+
| `speed` | `900` | Animation speed in pixels per second |
|
|
82
|
+
| `min-duration` | `0.25` | Minimum animation duration in seconds |
|
|
83
|
+
| `max-duration` | `1` | Maximum animation duration in seconds |
|
|
84
|
+
|
|
85
|
+
## Accordion Groups
|
|
86
|
+
|
|
87
|
+
Link collapsible components together with the `group` attribute so that opening one closes the others:
|
|
88
|
+
|
|
89
|
+
```html
|
|
90
|
+
<collapsible-component group="faq">
|
|
91
|
+
<button type="button">Question One</button>
|
|
92
|
+
<collapsible-content>
|
|
93
|
+
<p>Answer one.</p>
|
|
94
|
+
</collapsible-content>
|
|
95
|
+
</collapsible-component>
|
|
96
|
+
|
|
97
|
+
<collapsible-component group="faq">
|
|
98
|
+
<button type="button">Question Two</button>
|
|
99
|
+
<collapsible-content>
|
|
100
|
+
<p>Answer two.</p>
|
|
101
|
+
</collapsible-content>
|
|
102
|
+
</collapsible-component>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Items without a `group` attribute continue to work independently. All items in a group can be closed simultaneously — clicking the open item simply closes it.
|
|
106
|
+
|
|
107
|
+
| Attribute | Element | Default | Description |
|
|
108
|
+
| --------- | -------------------------- | ------- | -------------------------------------------------- |
|
|
109
|
+
| `group` | `<collapsible-component>` | — | Group name; items with the same name form an accordion |
|
|
110
|
+
|
|
59
111
|
## Customization
|
|
60
112
|
|
|
61
|
-
Customize the animation with CSS custom properties:
|
|
113
|
+
Customize the animation easing with CSS custom properties:
|
|
62
114
|
|
|
63
115
|
```css
|
|
64
116
|
collapsible-content {
|
|
65
|
-
|
|
66
|
-
--collapsible-easing: ease-in-out;
|
|
117
|
+
--collapsible-easing: ease-in-out;
|
|
67
118
|
}
|
|
68
119
|
```
|
|
69
120
|
|
|
70
|
-
| Property
|
|
71
|
-
|
|
72
|
-
| `--collapsible-duration` | `
|
|
73
|
-
| `--collapsible-easing`
|
|
121
|
+
| Property | Default | Description |
|
|
122
|
+
| ------------------------ | ---------- | ---------------------------------------------------- |
|
|
123
|
+
| `--collapsible-duration` | dynamic | Calculated from content height and `speed` attribute |
|
|
124
|
+
| `--collapsible-easing` | `ease` | Animation timing function |
|
|
74
125
|
|
|
75
126
|
## Events
|
|
76
127
|
|
|
@@ -80,7 +131,7 @@ Fired when the component is missing required children:
|
|
|
80
131
|
|
|
81
132
|
```javascript
|
|
82
133
|
document.addEventListener('collapsible-error', (e) => {
|
|
83
|
-
|
|
134
|
+
console.error('Collapsible setup failed:', e.detail.error);
|
|
84
135
|
});
|
|
85
136
|
```
|
|
86
137
|
|
|
@@ -90,7 +141,7 @@ Access the `collapsed` property on the `<collapsible-content>` element:
|
|
|
90
141
|
|
|
91
142
|
```javascript
|
|
92
143
|
const content = document.querySelector('collapsible-content');
|
|
93
|
-
content.collapsed = true;
|
|
144
|
+
content.collapsed = true; // collapse
|
|
94
145
|
content.collapsed = false; // expand
|
|
95
146
|
console.log(content.collapsed); // get current state
|
|
96
147
|
```
|
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const DEFAULT_SPEED = 900; // px per second
|
|
4
|
+
const MIN_DURATION = 0.25; // seconds
|
|
5
|
+
const MAX_DURATION = 1.0; // seconds
|
|
6
|
+
|
|
3
7
|
/**
|
|
4
8
|
* Custom element that creates a collapsible/expandable component with proper accessibility
|
|
5
9
|
*/
|
|
6
10
|
class CollapsibleComponent extends HTMLElement {
|
|
11
|
+
static #groups = new Map();
|
|
12
|
+
|
|
13
|
+
static #addToGroup(name, instance) {
|
|
14
|
+
if (!CollapsibleComponent.#groups.has(name)) {
|
|
15
|
+
CollapsibleComponent.#groups.set(name, new Set());
|
|
16
|
+
}
|
|
17
|
+
CollapsibleComponent.#groups.get(name).add(instance);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static #removeFromGroup(name, instance) {
|
|
21
|
+
const group = CollapsibleComponent.#groups.get(name);
|
|
22
|
+
if (group) {
|
|
23
|
+
group.delete(instance);
|
|
24
|
+
if (group.size === 0) CollapsibleComponent.#groups.delete(name);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static get observedAttributes() {
|
|
29
|
+
return ['group'];
|
|
30
|
+
}
|
|
31
|
+
|
|
7
32
|
#handleClick;
|
|
8
33
|
#abortController;
|
|
9
34
|
|
|
@@ -18,13 +43,12 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
18
43
|
_.button = null;
|
|
19
44
|
_.content = null;
|
|
20
45
|
|
|
21
|
-
// define click handler
|
|
22
|
-
_.#handleClick = (
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_
|
|
27
|
-
_.content.collapsed = !expanded;
|
|
46
|
+
// define click handler — content is source of truth
|
|
47
|
+
_.#handleClick = () => {
|
|
48
|
+
const wasCollapsed = _.content.collapsed;
|
|
49
|
+
_.content.collapsed = !wasCollapsed;
|
|
50
|
+
_.button.setAttribute('aria-expanded', !_.content.collapsed);
|
|
51
|
+
if (wasCollapsed) _.#closeSiblings();
|
|
28
52
|
};
|
|
29
53
|
}
|
|
30
54
|
|
|
@@ -58,14 +82,26 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
58
82
|
_.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
|
|
59
83
|
|
|
60
84
|
// set accessibility attributes
|
|
61
|
-
_.button.type
|
|
85
|
+
if (!_.button.hasAttribute('type')) {
|
|
86
|
+
_.button.type = 'button';
|
|
87
|
+
}
|
|
62
88
|
_.button.setAttribute('aria-controls', _.content.id);
|
|
63
89
|
_.content.setAttribute('aria-labelledby', _.button.id);
|
|
90
|
+
if (!_.content.hasAttribute('role')) {
|
|
91
|
+
_.content.setAttribute('role', 'region');
|
|
92
|
+
}
|
|
64
93
|
|
|
65
|
-
// set initial state
|
|
94
|
+
// set initial state without triggering an opening/closing animation
|
|
66
95
|
const open = _.content.hasAttribute('open');
|
|
67
96
|
_.button.setAttribute('aria-expanded', open);
|
|
68
|
-
_.content.
|
|
97
|
+
_.content.style.height = open ? 'auto' : '0px';
|
|
98
|
+
if (open) {
|
|
99
|
+
_.content.removeAttribute('aria-hidden');
|
|
100
|
+
_.content.removeAttribute('inert');
|
|
101
|
+
} else {
|
|
102
|
+
_.content.setAttribute('aria-hidden', 'true');
|
|
103
|
+
_.content.setAttribute('inert', '');
|
|
104
|
+
}
|
|
69
105
|
|
|
70
106
|
// use AbortController to prevent duplicate listeners on reconnection
|
|
71
107
|
_.#abortController = new AbortController();
|
|
@@ -74,17 +110,40 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
74
110
|
});
|
|
75
111
|
}
|
|
76
112
|
|
|
113
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
114
|
+
if (name !== 'group') return;
|
|
115
|
+
if (oldValue) CollapsibleComponent.#removeFromGroup(oldValue, this);
|
|
116
|
+
if (newValue) CollapsibleComponent.#addToGroup(newValue, this);
|
|
117
|
+
}
|
|
118
|
+
|
|
77
119
|
/**
|
|
78
120
|
* Called when element is removed from the DOM
|
|
79
121
|
* Cleans up event listeners
|
|
80
122
|
*/
|
|
81
123
|
disconnectedCallback() {
|
|
82
124
|
const _ = this;
|
|
125
|
+
const group = _.getAttribute('group');
|
|
126
|
+
if (group) CollapsibleComponent.#removeFromGroup(group, _);
|
|
83
127
|
if (_.#abortController) {
|
|
84
128
|
_.#abortController.abort();
|
|
85
129
|
_.#abortController = null;
|
|
86
130
|
}
|
|
87
131
|
}
|
|
132
|
+
|
|
133
|
+
#closeSiblings() {
|
|
134
|
+
const _ = this;
|
|
135
|
+
const groupName = _.getAttribute('group');
|
|
136
|
+
if (!groupName) return;
|
|
137
|
+
const group = CollapsibleComponent.#groups.get(groupName);
|
|
138
|
+
if (!group) return;
|
|
139
|
+
for (const sibling of group) {
|
|
140
|
+
if (sibling === _) continue;
|
|
141
|
+
if (!sibling.content.collapsed) {
|
|
142
|
+
sibling.content.collapsed = true;
|
|
143
|
+
sibling.button.setAttribute('aria-expanded', 'false');
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
88
147
|
}
|
|
89
148
|
|
|
90
149
|
/**
|
|
@@ -104,10 +163,11 @@ class CollapsibleContent extends HTMLElement {
|
|
|
104
163
|
|
|
105
164
|
// define event handler using arrow function for proper binding
|
|
106
165
|
_.#handleTransitionEnd = (event) => {
|
|
107
|
-
|
|
166
|
+
if (event.target !== _) return;
|
|
108
167
|
if (event.propertyName !== 'height') return;
|
|
109
168
|
|
|
110
169
|
_.#animating = false;
|
|
170
|
+
_.style.removeProperty('--collapsible-duration');
|
|
111
171
|
|
|
112
172
|
// remove the inline height to allow dynamic content changes
|
|
113
173
|
if (!_.collapsed) {
|
|
@@ -137,55 +197,87 @@ class CollapsibleContent extends HTMLElement {
|
|
|
137
197
|
*/
|
|
138
198
|
disconnectedCallback() {
|
|
139
199
|
const _ = this;
|
|
200
|
+
_.#animating = false;
|
|
140
201
|
if (_.#abortController) {
|
|
141
202
|
_.#abortController.abort();
|
|
142
203
|
_.#abortController = null;
|
|
143
204
|
}
|
|
144
205
|
}
|
|
145
206
|
|
|
207
|
+
get #speed() {
|
|
208
|
+
const attr = this.getAttribute('speed');
|
|
209
|
+
if (attr === null) return DEFAULT_SPEED;
|
|
210
|
+
const value = Number(attr);
|
|
211
|
+
return value > 0 ? value : DEFAULT_SPEED;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
get #minDuration() {
|
|
215
|
+
const attr = this.getAttribute('min-duration');
|
|
216
|
+
if (attr === null) return MIN_DURATION;
|
|
217
|
+
const value = Number(attr);
|
|
218
|
+
return value > 0 ? value : MIN_DURATION;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
get #maxDuration() {
|
|
222
|
+
const attr = this.getAttribute('max-duration');
|
|
223
|
+
if (attr === null) return MAX_DURATION;
|
|
224
|
+
const value = Number(attr);
|
|
225
|
+
return value > 0 ? value : MAX_DURATION;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#setDynamicDuration(currentHeight, targetHeight) {
|
|
229
|
+
const _ = this;
|
|
230
|
+
const delta = Math.abs(targetHeight - currentHeight);
|
|
231
|
+
const duration = Math.min(_.#maxDuration, Math.max(_.#minDuration, delta / _.#speed));
|
|
232
|
+
_.style.setProperty('--collapsible-duration', `${duration.toFixed(3)}s`);
|
|
233
|
+
}
|
|
234
|
+
|
|
146
235
|
/**
|
|
147
236
|
* Handles setting the collapsed state with animation
|
|
148
237
|
* @param {boolean} value - Whether element should be collapsed
|
|
149
238
|
*/
|
|
150
239
|
set collapsed(value) {
|
|
151
240
|
const _ = this;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (_.#animating) return;
|
|
241
|
+
const collapsed = Boolean(value);
|
|
242
|
+
if (_.collapsed === collapsed) return;
|
|
155
243
|
|
|
156
244
|
// check if transitions are enabled (respects prefers-reduced-motion)
|
|
157
245
|
const hasTransition = getComputedStyle(_).transitionDuration !== '0s';
|
|
158
246
|
|
|
159
|
-
if (
|
|
160
|
-
if (hasTransition) {
|
|
161
|
-
_.#animating = true;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// animate to closed
|
|
165
|
-
_.style.height = `${_.scrollHeight}px`;
|
|
166
|
-
|
|
167
|
-
// use requestAnimationFrame for reliable frame timing
|
|
168
|
-
requestAnimationFrame(() => {
|
|
169
|
-
requestAnimationFrame(() => {
|
|
170
|
-
_.style.height = '0px';
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
|
-
|
|
247
|
+
if (collapsed) {
|
|
174
248
|
_.removeAttribute('open');
|
|
175
249
|
_.setAttribute('aria-hidden', 'true');
|
|
176
250
|
_.setAttribute('inert', '');
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
_.#animating = true;
|
|
182
|
-
}
|
|
183
|
-
_.style.height = `${_.scrollHeight}px`;
|
|
251
|
+
|
|
252
|
+
if (!hasTransition) {
|
|
253
|
+
_.style.height = '0px';
|
|
254
|
+
return;
|
|
184
255
|
}
|
|
185
256
|
|
|
257
|
+
// capture current height in px (converts auto or mid-animation value)
|
|
258
|
+
const currentHeight = _.getBoundingClientRect().height;
|
|
259
|
+
_.style.height = `${currentHeight}px`;
|
|
260
|
+
_.#setDynamicDuration(currentHeight, 0);
|
|
261
|
+
_.#animating = true;
|
|
262
|
+
_.offsetHeight; // force reflow — browser commits the px start value
|
|
263
|
+
_.style.height = '0px';
|
|
264
|
+
} else {
|
|
186
265
|
_.setAttribute('open', '');
|
|
187
266
|
_.removeAttribute('aria-hidden');
|
|
188
267
|
_.removeAttribute('inert');
|
|
268
|
+
|
|
269
|
+
if (!hasTransition) {
|
|
270
|
+
_.style.height = 'auto';
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// capture current height in px (0px or mid-animation value)
|
|
275
|
+
const currentHeight = _.getBoundingClientRect().height;
|
|
276
|
+
_.style.height = `${currentHeight}px`;
|
|
277
|
+
_.#setDynamicDuration(currentHeight, _.scrollHeight);
|
|
278
|
+
_.#animating = true;
|
|
279
|
+
_.offsetHeight; // force reflow — browser commits the px start value
|
|
280
|
+
_.style.height = `${_.scrollHeight}px`;
|
|
189
281
|
}
|
|
190
282
|
}
|
|
191
283
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collapsible-content.cjs.js","sources":["../src/collapsible-content.js"],"sourcesContent":["import './collapsible-content.css';\n\n/**\n * Custom element that creates a collapsible/expandable component with proper accessibility\n */\nclass CollapsibleComponent extends HTMLElement {\n\t#handleClick;\n\t#abortController;\n\n\t/**\n\t * Initializes the component and sets up references to child elements\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\n\t\t// store references to elements once to avoid re-querying\n\t\t_.button = null;\n\t\t_.content = null;\n\n\t\t// define click handler with proper this binding\n\t\t_.#handleClick = (e) => {\n\t\t\te.preventDefault();\n\t\t\t// toggle expanded value\n\t\t\tconst expanded = _.button.getAttribute('aria-expanded') !== 'true';\n\t\t\t_.button.setAttribute('aria-expanded', expanded);\n\t\t\t_.content.collapsed = !expanded;\n\t\t};\n\t}\n\n\t/**\n\t * Called when element is added to the DOM\n\t * Sets up accessibility attributes and event listeners\n\t */\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// initialize element references once\n\t\t_.button = _.querySelector('button');\n\t\t_.content = _.querySelector('collapsible-content');\n\n\t\tif (!_.button || !_.content) {\n\t\t\tconst error = new Error(\n\t\t\t\t'CollapsibleComponent requires a <button> and a <collapsible-content>.'\n\t\t\t);\n\t\t\tconsole.error(error.message);\n\t\t\t_.dispatchEvent(\n\t\t\t\tnew CustomEvent('collapsible-error', {\n\t\t\t\t\tbubbles: true,\n\t\t\t\t\tdetail: { error },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// generate ids if not provided\n\t\t_.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;\n\t\t_.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;\n\n\t\t// set accessibility attributes\n\t\t_.button.type ||= 'button';\n\t\t_.button.setAttribute('aria-controls', _.content.id);\n\t\t_.content.setAttribute('aria-labelledby', _.button.id);\n\n\t\t// set initial state based on open attribute\n\t\tconst open = _.content.hasAttribute('open');\n\t\t_.button.setAttribute('aria-expanded', open);\n\t\t_.content.collapsed = !open;\n\n\t\t// use AbortController to prevent duplicate listeners on reconnection\n\t\t_.#abortController = new AbortController();\n\t\t_.button.addEventListener('click', _.#handleClick, {\n\t\t\tsignal: _.#abortController.signal,\n\t\t});\n\t}\n\n\t/**\n\t * Called when element is removed from the DOM\n\t * Cleans up event listeners\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tif (_.#abortController) {\n\t\t\t_.#abortController.abort();\n\t\t\t_.#abortController = null;\n\t\t}\n\t}\n}\n\n/**\n * Custom element that provides animated collapsible content\n */\nclass CollapsibleContent extends HTMLElement {\n\t#handleTransitionEnd;\n\t#abortController;\n\t#animating = false;\n\n\t/**\n\t * Initializes the content element and binds event handlers\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\n\t\t// define event handler using arrow function for proper binding\n\t\t_.#handleTransitionEnd = (event) => {\n\t\t\t// exit if isn't height property\n\t\t\tif (event.propertyName !== 'height') return;\n\n\t\t\t_.#animating = false;\n\n\t\t\t// remove the inline height to allow dynamic content changes\n\t\t\tif (!_.collapsed) {\n\t\t\t\t_.style.height = 'auto';\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Called when element is added to the DOM\n\t * Sets initial height based on open attribute\n\t */\n\tconnectedCallback() {\n\t\tconst _ = this;\n\t\t_.style.height = _.hasAttribute('open') ? 'auto' : '0';\n\n\t\t// use AbortController to prevent duplicate listeners on reconnection\n\t\t_.#abortController = new AbortController();\n\t\t_.addEventListener('transitionend', _.#handleTransitionEnd, {\n\t\t\tsignal: _.#abortController.signal,\n\t\t});\n\t}\n\n\t/**\n\t * Called when element is removed from the DOM\n\t * Cleans up event listeners\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tif (_.#abortController) {\n\t\t\t_.#abortController.abort();\n\t\t\t_.#abortController = null;\n\t\t}\n\t}\n\n\t/**\n\t * Handles setting the collapsed state with animation\n\t * @param {boolean} value - Whether element should be collapsed\n\t */\n\tset collapsed(value) {\n\t\tconst _ = this;\n\n\t\t// prevent rapid clicking during animation\n\t\tif (_.#animating) return;\n\n\t\t// check if transitions are enabled (respects prefers-reduced-motion)\n\t\tconst hasTransition = getComputedStyle(_).transitionDuration !== '0s';\n\n\t\tif (value) {\n\t\t\tif (hasTransition) {\n\t\t\t\t_.#animating = true;\n\t\t\t}\n\n\t\t\t// animate to closed\n\t\t\t_.style.height = `${_.scrollHeight}px`;\n\n\t\t\t// use requestAnimationFrame for reliable frame timing\n\t\t\trequestAnimationFrame(() => {\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\t_.style.height = '0px';\n\t\t\t\t});\n\t\t\t});\n\n\t\t\t_.removeAttribute('open');\n\t\t\t_.setAttribute('aria-hidden', 'true');\n\t\t\t_.setAttribute('inert', '');\n\t\t} else {\n\t\t\t// only animate if not already auto\n\t\t\tif (_.style.height !== 'auto') {\n\t\t\t\tif (hasTransition) {\n\t\t\t\t\t_.#animating = true;\n\t\t\t\t}\n\t\t\t\t_.style.height = `${_.scrollHeight}px`;\n\t\t\t}\n\n\t\t\t_.setAttribute('open', '');\n\t\t\t_.removeAttribute('aria-hidden');\n\t\t\t_.removeAttribute('inert');\n\t\t}\n\t}\n\n\t/**\n\t * Gets the collapsed state\n\t * @returns {boolean} Whether element is collapsed\n\t */\n\tget collapsed() {\n\t\treturn !this.hasAttribute('open');\n\t}\n}\n\n// register custom elements if not already defined\nif (!customElements.get('collapsible-content')) {\n\tcustomElements.define('collapsible-content', CollapsibleContent);\n}\nif (!customElements.get('collapsible-component')) {\n\tcustomElements.define('collapsible-component', CollapsibleComponent);\n}\n\nexport { CollapsibleContent, CollapsibleComponent };\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"collapsible-content.cjs.js","sources":["../src/collapsible-content.js"],"sourcesContent":["import './collapsible-content.css';\n\nconst DEFAULT_SPEED = 900; // px per second\nconst MIN_DURATION = 0.25; // seconds\nconst MAX_DURATION = 1.0; // seconds\n\n/**\n * Custom element that creates a collapsible/expandable component with proper accessibility\n */\nclass CollapsibleComponent extends HTMLElement {\n\tstatic #groups = new Map();\n\n\tstatic #addToGroup(name, instance) {\n\t\tif (!CollapsibleComponent.#groups.has(name)) {\n\t\t\tCollapsibleComponent.#groups.set(name, new Set());\n\t\t}\n\t\tCollapsibleComponent.#groups.get(name).add(instance);\n\t}\n\n\tstatic #removeFromGroup(name, instance) {\n\t\tconst group = CollapsibleComponent.#groups.get(name);\n\t\tif (group) {\n\t\t\tgroup.delete(instance);\n\t\t\tif (group.size === 0) CollapsibleComponent.#groups.delete(name);\n\t\t}\n\t}\n\n\tstatic get observedAttributes() {\n\t\treturn ['group'];\n\t}\n\n\t#handleClick;\n\t#abortController;\n\n\t/**\n\t * Initializes the component and sets up references to child elements\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\n\t\t// store references to elements once to avoid re-querying\n\t\t_.button = null;\n\t\t_.content = null;\n\n\t\t// define click handler — content is source of truth\n\t\t_.#handleClick = () => {\n\t\t\tconst wasCollapsed = _.content.collapsed;\n\t\t\t_.content.collapsed = !wasCollapsed;\n\t\t\t_.button.setAttribute('aria-expanded', !_.content.collapsed);\n\t\t\tif (wasCollapsed) _.#closeSiblings();\n\t\t};\n\t}\n\n\t/**\n\t * Called when element is added to the DOM\n\t * Sets up accessibility attributes and event listeners\n\t */\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// initialize element references once\n\t\t_.button = _.querySelector('button');\n\t\t_.content = _.querySelector('collapsible-content');\n\n\t\tif (!_.button || !_.content) {\n\t\t\tconst error = new Error(\n\t\t\t\t'CollapsibleComponent requires a <button> and a <collapsible-content>.'\n\t\t\t);\n\t\t\tconsole.error(error.message);\n\t\t\t_.dispatchEvent(\n\t\t\t\tnew CustomEvent('collapsible-error', {\n\t\t\t\t\tbubbles: true,\n\t\t\t\t\tdetail: { error },\n\t\t\t\t})\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// generate ids if not provided\n\t\t_.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;\n\t\t_.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;\n\n\t\t// set accessibility attributes\n\t\tif (!_.button.hasAttribute('type')) {\n\t\t\t_.button.type = 'button';\n\t\t}\n\t\t_.button.setAttribute('aria-controls', _.content.id);\n\t\t_.content.setAttribute('aria-labelledby', _.button.id);\n\t\tif (!_.content.hasAttribute('role')) {\n\t\t\t_.content.setAttribute('role', 'region');\n\t\t}\n\n\t\t// set initial state without triggering an opening/closing animation\n\t\tconst open = _.content.hasAttribute('open');\n\t\t_.button.setAttribute('aria-expanded', open);\n\t\t_.content.style.height = open ? 'auto' : '0px';\n\t\tif (open) {\n\t\t\t_.content.removeAttribute('aria-hidden');\n\t\t\t_.content.removeAttribute('inert');\n\t\t} else {\n\t\t\t_.content.setAttribute('aria-hidden', 'true');\n\t\t\t_.content.setAttribute('inert', '');\n\t\t}\n\n\t\t// use AbortController to prevent duplicate listeners on reconnection\n\t\t_.#abortController = new AbortController();\n\t\t_.button.addEventListener('click', _.#handleClick, {\n\t\t\tsignal: _.#abortController.signal,\n\t\t});\n\t}\n\n\tattributeChangedCallback(name, oldValue, newValue) {\n\t\tif (name !== 'group') return;\n\t\tif (oldValue) CollapsibleComponent.#removeFromGroup(oldValue, this);\n\t\tif (newValue) CollapsibleComponent.#addToGroup(newValue, this);\n\t}\n\n\t/**\n\t * Called when element is removed from the DOM\n\t * Cleans up event listeners\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tconst group = _.getAttribute('group');\n\t\tif (group) CollapsibleComponent.#removeFromGroup(group, _);\n\t\tif (_.#abortController) {\n\t\t\t_.#abortController.abort();\n\t\t\t_.#abortController = null;\n\t\t}\n\t}\n\n\t#closeSiblings() {\n\t\tconst _ = this;\n\t\tconst groupName = _.getAttribute('group');\n\t\tif (!groupName) return;\n\t\tconst group = CollapsibleComponent.#groups.get(groupName);\n\t\tif (!group) return;\n\t\tfor (const sibling of group) {\n\t\t\tif (sibling === _) continue;\n\t\t\tif (!sibling.content.collapsed) {\n\t\t\t\tsibling.content.collapsed = true;\n\t\t\t\tsibling.button.setAttribute('aria-expanded', 'false');\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * Custom element that provides animated collapsible content\n */\nclass CollapsibleContent extends HTMLElement {\n\t#handleTransitionEnd;\n\t#abortController;\n\t#animating = false;\n\n\t/**\n\t * Initializes the content element and binds event handlers\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\n\t\t// define event handler using arrow function for proper binding\n\t\t_.#handleTransitionEnd = (event) => {\n\t\t\tif (event.target !== _) return;\n\t\t\tif (event.propertyName !== 'height') return;\n\n\t\t\t_.#animating = false;\n\t\t\t_.style.removeProperty('--collapsible-duration');\n\n\t\t\t// remove the inline height to allow dynamic content changes\n\t\t\tif (!_.collapsed) {\n\t\t\t\t_.style.height = 'auto';\n\t\t\t}\n\t\t};\n\t}\n\n\t/**\n\t * Called when element is added to the DOM\n\t * Sets initial height based on open attribute\n\t */\n\tconnectedCallback() {\n\t\tconst _ = this;\n\t\t_.style.height = _.hasAttribute('open') ? 'auto' : '0';\n\n\t\t// use AbortController to prevent duplicate listeners on reconnection\n\t\t_.#abortController = new AbortController();\n\t\t_.addEventListener('transitionend', _.#handleTransitionEnd, {\n\t\t\tsignal: _.#abortController.signal,\n\t\t});\n\t}\n\n\t/**\n\t * Called when element is removed from the DOM\n\t * Cleans up event listeners\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\t_.#animating = false;\n\t\tif (_.#abortController) {\n\t\t\t_.#abortController.abort();\n\t\t\t_.#abortController = null;\n\t\t}\n\t}\n\n\tget #speed() {\n\t\tconst attr = this.getAttribute('speed');\n\t\tif (attr === null) return DEFAULT_SPEED;\n\t\tconst value = Number(attr);\n\t\treturn value > 0 ? value : DEFAULT_SPEED;\n\t}\n\n\tget #minDuration() {\n\t\tconst attr = this.getAttribute('min-duration');\n\t\tif (attr === null) return MIN_DURATION;\n\t\tconst value = Number(attr);\n\t\treturn value > 0 ? value : MIN_DURATION;\n\t}\n\n\tget #maxDuration() {\n\t\tconst attr = this.getAttribute('max-duration');\n\t\tif (attr === null) return MAX_DURATION;\n\t\tconst value = Number(attr);\n\t\treturn value > 0 ? value : MAX_DURATION;\n\t}\n\n\t#setDynamicDuration(currentHeight, targetHeight) {\n\t\tconst _ = this;\n\t\tconst delta = Math.abs(targetHeight - currentHeight);\n\t\tconst duration = Math.min(_.#maxDuration, Math.max(_.#minDuration, delta / _.#speed));\n\t\t_.style.setProperty('--collapsible-duration', `${duration.toFixed(3)}s`);\n\t}\n\n\t/**\n\t * Handles setting the collapsed state with animation\n\t * @param {boolean} value - Whether element should be collapsed\n\t */\n\tset collapsed(value) {\n\t\tconst _ = this;\n\t\tconst collapsed = Boolean(value);\n\t\tif (_.collapsed === collapsed) return;\n\n\t\t// check if transitions are enabled (respects prefers-reduced-motion)\n\t\tconst hasTransition = getComputedStyle(_).transitionDuration !== '0s';\n\n\t\tif (collapsed) {\n\t\t\t_.removeAttribute('open');\n\t\t\t_.setAttribute('aria-hidden', 'true');\n\t\t\t_.setAttribute('inert', '');\n\n\t\t\tif (!hasTransition) {\n\t\t\t\t_.style.height = '0px';\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// capture current height in px (converts auto or mid-animation value)\n\t\t\tconst currentHeight = _.getBoundingClientRect().height;\n\t\t\t_.style.height = `${currentHeight}px`;\n\t\t\t_.#setDynamicDuration(currentHeight, 0);\n\t\t\t_.#animating = true;\n\t\t\t_.offsetHeight; // force reflow — browser commits the px start value\n\t\t\t_.style.height = '0px';\n\t\t} else {\n\t\t\t_.setAttribute('open', '');\n\t\t\t_.removeAttribute('aria-hidden');\n\t\t\t_.removeAttribute('inert');\n\n\t\t\tif (!hasTransition) {\n\t\t\t\t_.style.height = 'auto';\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// capture current height in px (0px or mid-animation value)\n\t\t\tconst currentHeight = _.getBoundingClientRect().height;\n\t\t\t_.style.height = `${currentHeight}px`;\n\t\t\t_.#setDynamicDuration(currentHeight, _.scrollHeight);\n\t\t\t_.#animating = true;\n\t\t\t_.offsetHeight; // force reflow — browser commits the px start value\n\t\t\t_.style.height = `${_.scrollHeight}px`;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the collapsed state\n\t * @returns {boolean} Whether element is collapsed\n\t */\n\tget collapsed() {\n\t\treturn !this.hasAttribute('open');\n\t}\n}\n\n// register custom elements if not already defined\nif (!customElements.get('collapsible-content')) {\n\tcustomElements.define('collapsible-content', CollapsibleContent);\n}\nif (!customElements.get('collapsible-component')) {\n\tcustomElements.define('collapsible-component', CollapsibleComponent);\n}\n\nexport { CollapsibleContent, CollapsibleComponent };\n"],"names":[],"mappings":"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"}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
collapsible-component {
|
|
2
|
-
|
|
2
|
+
display: block;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
5
|
collapsible-component button {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
width: 100%;
|
|
7
|
+
text-align: left;
|
|
8
|
+
background: none;
|
|
9
|
+
border: none;
|
|
10
|
+
cursor: pointer;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
collapsible-component button:focus-visible {
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
outline: 2px solid currentColor;
|
|
15
|
+
outline-offset: 2px;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
collapsible-content {
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
--collapsible-duration: 0.35s;
|
|
20
|
+
--collapsible-easing: ease;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
display: block;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
transition: height var(--collapsible-duration) var(--collapsible-easing);
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
@media (prefers-reduced-motion: reduce) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
collapsible-content {
|
|
29
|
+
transition: none;
|
|
30
|
+
}
|
|
31
31
|
}
|