@magic-spells/collapsible-content 0.2.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 +110 -62
- package/dist/collapsible-content.cjs.js +183 -64
- package/dist/collapsible-content.cjs.js.map +1 -1
- package/dist/collapsible-content.css +24 -27
- package/dist/collapsible-content.esm.js +183 -64
- package/dist/collapsible-content.esm.js.map +1 -1
- package/dist/collapsible-content.js +183 -64
- 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 +7 -11
- package/src/collapsible-content.css +31 -0
- package/src/collapsible-content.js +184 -65
- package/dist/collapsible-content.scss +0 -2
- package/dist/scss/collapsible-content.scss +0 -25
- package/dist/scss/variables.scss +0 -40
- package/src/index.scss +0 -2
- package/src/scss/collapsible-content.scss +0 -25
- package/src/scss/variables.scss +0 -40
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Collapsible Content Web Component
|
|
2
2
|
|
|
3
|
-
A lightweight,
|
|
3
|
+
A lightweight, accessible Web Component for creating collapsible content sections. Perfect for FAQs, accordions, or any content that needs to be toggled.
|
|
4
4
|
|
|
5
5
|
[**Live Demo**](https://magic-spells.github.io/collapsible-content/demo/)
|
|
6
6
|
|
|
@@ -8,10 +8,10 @@ A lightweight, customizable Web Component for creating accessible collapsible co
|
|
|
8
8
|
|
|
9
9
|
- No dependencies
|
|
10
10
|
- Lightweight
|
|
11
|
-
-
|
|
12
|
-
- Smooth
|
|
13
|
-
-
|
|
14
|
-
-
|
|
11
|
+
- Accessible (ARIA attributes, keyboard support, focus management)
|
|
12
|
+
- Smooth animations with `prefers-reduced-motion` support
|
|
13
|
+
- Smooth mid-animation reversal on rapid clicks
|
|
14
|
+
- Safe to import multiple times (no double-registration errors)
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
17
17
|
|
|
@@ -20,7 +20,6 @@ npm install @magic-spells/collapsible-content
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```javascript
|
|
23
|
-
// Add to your JavaScript file
|
|
24
23
|
import '@magic-spells/collapsible-content';
|
|
25
24
|
```
|
|
26
25
|
|
|
@@ -34,86 +33,135 @@ Or include directly in your HTML:
|
|
|
34
33
|
|
|
35
34
|
```html
|
|
36
35
|
<collapsible-component>
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
</p>
|
|
45
|
-
</div>
|
|
46
|
-
</collapsible-content>
|
|
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>
|
|
47
43
|
</collapsible-component>
|
|
48
44
|
```
|
|
49
45
|
|
|
50
|
-
|
|
46
|
+
### Start Expanded
|
|
51
47
|
|
|
52
|
-
|
|
53
|
-
- Clicking the button toggles the visibility of the content
|
|
54
|
-
- The component handles all the ARIA attributes for accessibility
|
|
55
|
-
- Smooth animations are provided for opening and closing
|
|
48
|
+
Add the `open` attribute to start with content visible:
|
|
56
49
|
|
|
57
|
-
|
|
50
|
+
```html
|
|
51
|
+
<collapsible-component>
|
|
52
|
+
<button type="button">Already Open</button>
|
|
53
|
+
<collapsible-content open>
|
|
54
|
+
<p>This content is visible by default.</p>
|
|
55
|
+
</collapsible-content>
|
|
56
|
+
</collapsible-component>
|
|
57
|
+
```
|
|
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
|
|
58
86
|
|
|
59
|
-
|
|
87
|
+
Link collapsible components together with the `group` attribute so that opening one closes the others:
|
|
60
88
|
|
|
61
|
-
|
|
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
|
+
|
|
111
|
+
## Customization
|
|
112
|
+
|
|
113
|
+
Customize the animation easing with CSS custom properties:
|
|
62
114
|
|
|
63
115
|
```css
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
--cc-component-display: block;
|
|
67
|
-
--cc-button-width: 100%;
|
|
68
|
-
--cc-button-text-align: left;
|
|
69
|
-
|
|
70
|
-
/* Appearance */
|
|
71
|
-
--cc-button-background: none;
|
|
72
|
-
--cc-button-border: none;
|
|
73
|
-
--cc-button-cursor: pointer;
|
|
74
|
-
|
|
75
|
-
/* Animation */
|
|
76
|
-
--cc-content-padding: 10px;
|
|
77
|
-
--cc-transition-duration: 0.5s;
|
|
78
|
-
--cc-transition-timing: ease-in-out;
|
|
116
|
+
collapsible-content {
|
|
117
|
+
--collapsible-easing: ease-in-out;
|
|
79
118
|
}
|
|
80
119
|
```
|
|
81
120
|
|
|
82
|
-
|
|
121
|
+
| Property | Default | Description |
|
|
122
|
+
| ------------------------ | ---------- | ---------------------------------------------------- |
|
|
123
|
+
| `--collapsible-duration` | dynamic | Calculated from content height and `speed` attribute |
|
|
124
|
+
| `--collapsible-easing` | `ease` | Animation timing function |
|
|
125
|
+
|
|
126
|
+
## Events
|
|
127
|
+
|
|
128
|
+
### collapsible-error
|
|
83
129
|
|
|
84
|
-
|
|
130
|
+
Fired when the component is missing required children:
|
|
85
131
|
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
|
|
132
|
+
```javascript
|
|
133
|
+
document.addEventListener('collapsible-error', (e) => {
|
|
134
|
+
console.error('Collapsible setup failed:', e.detail.error);
|
|
135
|
+
});
|
|
136
|
+
```
|
|
89
137
|
|
|
90
|
-
|
|
91
|
-
@use '@magic-spells/collapsible-content/scss' with (
|
|
92
|
-
$transition-duration: 0.5s,
|
|
93
|
-
$transition-timing: ease-in-out,
|
|
94
|
-
$content-padding: 10px
|
|
95
|
-
);
|
|
138
|
+
## Programmatic Control
|
|
96
139
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
);
|
|
101
|
-
|
|
140
|
+
Access the `collapsed` property on the `<collapsible-content>` element:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
const content = document.querySelector('collapsible-content');
|
|
144
|
+
content.collapsed = true; // collapse
|
|
145
|
+
content.collapsed = false; // expand
|
|
146
|
+
console.log(content.collapsed); // get current state
|
|
102
147
|
```
|
|
103
148
|
|
|
104
149
|
## Accessibility
|
|
105
150
|
|
|
106
|
-
This component follows WCAG guidelines
|
|
151
|
+
This component follows WCAG guidelines:
|
|
107
152
|
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
153
|
+
- `aria-expanded` on button indicates current state
|
|
154
|
+
- `aria-controls` links button to content
|
|
155
|
+
- `role="region"` and `aria-labelledby` on content
|
|
156
|
+
- `aria-hidden` and `inert` when collapsed
|
|
157
|
+
- Keyboard accessible (Space/Enter to toggle)
|
|
158
|
+
- Focus-visible styling for keyboard users
|
|
159
|
+
- Respects `prefers-reduced-motion`
|
|
112
160
|
|
|
113
161
|
## Browser Support
|
|
114
162
|
|
|
115
|
-
|
|
163
|
+
Modern browsers with Web Components support (Chrome, Firefox, Safari, Edge).
|
|
116
164
|
|
|
117
165
|
## License
|
|
118
166
|
|
|
119
|
-
MIT
|
|
167
|
+
MIT
|
|
@@ -1,10 +1,36 @@
|
|
|
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;
|
|
33
|
+
#abortController;
|
|
8
34
|
|
|
9
35
|
/**
|
|
10
36
|
* Initializes the component and sets up references to child elements
|
|
@@ -17,13 +43,12 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
17
43
|
_.button = null;
|
|
18
44
|
_.content = null;
|
|
19
45
|
|
|
20
|
-
// define click handler
|
|
21
|
-
_.#handleClick = (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
_
|
|
26
|
-
_.content.hidden = !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();
|
|
27
52
|
};
|
|
28
53
|
}
|
|
29
54
|
|
|
@@ -39,7 +64,16 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
39
64
|
_.content = _.querySelector('collapsible-content');
|
|
40
65
|
|
|
41
66
|
if (!_.button || !_.content) {
|
|
42
|
-
|
|
67
|
+
const error = new Error(
|
|
68
|
+
'CollapsibleComponent requires a <button> and a <collapsible-content>.'
|
|
69
|
+
);
|
|
70
|
+
console.error(error.message);
|
|
71
|
+
_.dispatchEvent(
|
|
72
|
+
new CustomEvent('collapsible-error', {
|
|
73
|
+
bubbles: true,
|
|
74
|
+
detail: { error },
|
|
75
|
+
})
|
|
76
|
+
);
|
|
43
77
|
return;
|
|
44
78
|
}
|
|
45
79
|
|
|
@@ -48,18 +82,38 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
48
82
|
_.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
|
|
49
83
|
|
|
50
84
|
// set accessibility attributes
|
|
85
|
+
if (!_.button.hasAttribute('type')) {
|
|
86
|
+
_.button.type = 'button';
|
|
87
|
+
}
|
|
51
88
|
_.button.setAttribute('aria-controls', _.content.id);
|
|
52
|
-
_.content.setAttribute('role', 'region');
|
|
53
89
|
_.content.setAttribute('aria-labelledby', _.button.id);
|
|
90
|
+
if (!_.content.hasAttribute('role')) {
|
|
91
|
+
_.content.setAttribute('role', 'region');
|
|
92
|
+
}
|
|
54
93
|
|
|
55
|
-
// set initial state
|
|
56
|
-
const
|
|
94
|
+
// set initial state without triggering an opening/closing animation
|
|
95
|
+
const open = _.content.hasAttribute('open');
|
|
96
|
+
_.button.setAttribute('aria-expanded', open);
|
|
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
|
+
}
|
|
57
105
|
|
|
58
|
-
//
|
|
59
|
-
_
|
|
106
|
+
// use AbortController to prevent duplicate listeners on reconnection
|
|
107
|
+
_.#abortController = new AbortController();
|
|
108
|
+
_.button.addEventListener('click', _.#handleClick, {
|
|
109
|
+
signal: _.#abortController.signal,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
60
112
|
|
|
61
|
-
|
|
62
|
-
|
|
113
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
114
|
+
if (name !== 'group') return;
|
|
115
|
+
if (oldValue) CollapsibleComponent.#removeFromGroup(oldValue, this);
|
|
116
|
+
if (newValue) CollapsibleComponent.#addToGroup(newValue, this);
|
|
63
117
|
}
|
|
64
118
|
|
|
65
119
|
/**
|
|
@@ -68,8 +122,26 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
68
122
|
*/
|
|
69
123
|
disconnectedCallback() {
|
|
70
124
|
const _ = this;
|
|
71
|
-
|
|
72
|
-
|
|
125
|
+
const group = _.getAttribute('group');
|
|
126
|
+
if (group) CollapsibleComponent.#removeFromGroup(group, _);
|
|
127
|
+
if (_.#abortController) {
|
|
128
|
+
_.#abortController.abort();
|
|
129
|
+
_.#abortController = null;
|
|
130
|
+
}
|
|
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
|
+
}
|
|
73
145
|
}
|
|
74
146
|
}
|
|
75
147
|
}
|
|
@@ -79,6 +151,8 @@ class CollapsibleComponent extends HTMLElement {
|
|
|
79
151
|
*/
|
|
80
152
|
class CollapsibleContent extends HTMLElement {
|
|
81
153
|
#handleTransitionEnd;
|
|
154
|
+
#abortController;
|
|
155
|
+
#animating = false;
|
|
82
156
|
|
|
83
157
|
/**
|
|
84
158
|
* Initializes the content element and binds event handlers
|
|
@@ -89,36 +163,32 @@ class CollapsibleContent extends HTMLElement {
|
|
|
89
163
|
|
|
90
164
|
// define event handler using arrow function for proper binding
|
|
91
165
|
_.#handleTransitionEnd = (event) => {
|
|
92
|
-
|
|
93
|
-
if (event.propertyName
|
|
166
|
+
if (event.target !== _) return;
|
|
167
|
+
if (event.propertyName !== 'height') return;
|
|
168
|
+
|
|
169
|
+
_.#animating = false;
|
|
170
|
+
_.style.removeProperty('--collapsible-duration');
|
|
94
171
|
|
|
95
172
|
// remove the inline height to allow dynamic content changes
|
|
96
|
-
if (!_.
|
|
173
|
+
if (!_.collapsed) {
|
|
97
174
|
_.style.height = 'auto';
|
|
98
175
|
}
|
|
99
176
|
};
|
|
100
|
-
|
|
101
|
-
// add event listener to remove height after transition
|
|
102
|
-
_.addEventListener('transitionend', _.#handleTransitionEnd);
|
|
103
177
|
}
|
|
104
178
|
|
|
105
179
|
/**
|
|
106
180
|
* Called when element is added to the DOM
|
|
107
|
-
* Sets initial height based on
|
|
181
|
+
* Sets initial height based on open attribute
|
|
108
182
|
*/
|
|
109
183
|
connectedCallback() {
|
|
110
184
|
const _ = this;
|
|
185
|
+
_.style.height = _.hasAttribute('open') ? 'auto' : '0';
|
|
111
186
|
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (expanded) {
|
|
118
|
-
_.style.height = 'auto';
|
|
119
|
-
} else {
|
|
120
|
-
_.style.height = '0';
|
|
121
|
-
}
|
|
187
|
+
// use AbortController to prevent duplicate listeners on reconnection
|
|
188
|
+
_.#abortController = new AbortController();
|
|
189
|
+
_.addEventListener('transitionend', _.#handleTransitionEnd, {
|
|
190
|
+
signal: _.#abortController.signal,
|
|
191
|
+
});
|
|
122
192
|
}
|
|
123
193
|
|
|
124
194
|
/**
|
|
@@ -127,57 +197,106 @@ class CollapsibleContent extends HTMLElement {
|
|
|
127
197
|
*/
|
|
128
198
|
disconnectedCallback() {
|
|
129
199
|
const _ = this;
|
|
130
|
-
_
|
|
200
|
+
_.#animating = false;
|
|
201
|
+
if (_.#abortController) {
|
|
202
|
+
_.#abortController.abort();
|
|
203
|
+
_.#abortController = null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
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`);
|
|
131
233
|
}
|
|
132
234
|
|
|
133
235
|
/**
|
|
134
|
-
* Handles setting the
|
|
135
|
-
* @param {boolean} value - Whether element should be
|
|
236
|
+
* Handles setting the collapsed state with animation
|
|
237
|
+
* @param {boolean} value - Whether element should be collapsed
|
|
136
238
|
*/
|
|
137
|
-
set
|
|
239
|
+
set collapsed(value) {
|
|
138
240
|
const _ = this;
|
|
241
|
+
const collapsed = Boolean(value);
|
|
242
|
+
if (_.collapsed === collapsed) return;
|
|
139
243
|
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
// reset height to animate from
|
|
143
|
-
_.style.height = `${_.scrollHeight}px`;
|
|
144
|
-
|
|
145
|
-
// wait one frame and animate to 0
|
|
146
|
-
setTimeout(() => {
|
|
147
|
-
_.style.height = '0px';
|
|
148
|
-
}, 1);
|
|
244
|
+
// check if transitions are enabled (respects prefers-reduced-motion)
|
|
245
|
+
const hasTransition = getComputedStyle(_).transitionDuration !== '0s';
|
|
149
246
|
|
|
150
|
-
|
|
247
|
+
if (collapsed) {
|
|
248
|
+
_.removeAttribute('open');
|
|
151
249
|
_.setAttribute('aria-hidden', 'true');
|
|
152
250
|
_.setAttribute('inert', '');
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// animate to open
|
|
158
|
-
_.style.height = `${_.scrollHeight}px`;
|
|
251
|
+
|
|
252
|
+
if (!hasTransition) {
|
|
253
|
+
_.style.height = '0px';
|
|
254
|
+
return;
|
|
159
255
|
}
|
|
160
256
|
|
|
161
|
-
//
|
|
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 {
|
|
265
|
+
_.setAttribute('open', '');
|
|
162
266
|
_.removeAttribute('aria-hidden');
|
|
163
267
|
_.removeAttribute('inert');
|
|
164
|
-
|
|
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`;
|
|
165
281
|
}
|
|
166
282
|
}
|
|
167
283
|
|
|
168
284
|
/**
|
|
169
|
-
* Gets the
|
|
170
|
-
* @returns {boolean} Whether element is
|
|
285
|
+
* Gets the collapsed state
|
|
286
|
+
* @returns {boolean} Whether element is collapsed
|
|
171
287
|
*/
|
|
172
|
-
get
|
|
173
|
-
|
|
174
|
-
return _.hasAttribute('hidden');
|
|
288
|
+
get collapsed() {
|
|
289
|
+
return !this.hasAttribute('open');
|
|
175
290
|
}
|
|
176
291
|
}
|
|
177
292
|
|
|
178
|
-
// register custom elements
|
|
179
|
-
customElements.
|
|
180
|
-
customElements.define('collapsible-
|
|
293
|
+
// register custom elements if not already defined
|
|
294
|
+
if (!customElements.get('collapsible-content')) {
|
|
295
|
+
customElements.define('collapsible-content', CollapsibleContent);
|
|
296
|
+
}
|
|
297
|
+
if (!customElements.get('collapsible-component')) {
|
|
298
|
+
customElements.define('collapsible-component', CollapsibleComponent);
|
|
299
|
+
}
|
|
181
300
|
|
|
182
301
|
exports.CollapsibleComponent = CollapsibleComponent;
|
|
183
302
|
exports.CollapsibleContent = CollapsibleContent;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collapsible-content.cjs.js","sources":["../src/collapsible-content.js"],"sourcesContent":["import './index.scss';\n\n/**\n * Custom element that creates a collapsible/expandable component with proper accessibility\n */\nclass CollapsibleComponent extends HTMLElement {\n\t#handleClick;\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.hidden = !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\tconsole.error('CollapsibleComponent requires a <button> and a <collapsible-content>.');\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.setAttribute('aria-controls', _.content.id);\n\t\t_.content.setAttribute('role', 'region');\n\t\t_.content.setAttribute('aria-labelledby', _.button.id);\n\n\t\t// set initial state based on aria-expanded attribute\n\t\tconst expanded = _.button.getAttribute('aria-expanded') === 'true';\n\n\t\t// make sure content state matches button state\n\t\t_.content.hidden = !expanded;\n\n\t\t// add event listener\n\t\t_.button.addEventListener('click', _.#handleClick);\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 (_.button) {\n\t\t\t_.button.removeEventListener('click', _.#handleClick);\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\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// remove the inline height to allow dynamic content changes\n\t\t\tif (!_.hidden) {\n\t\t\t\t_.style.height = 'auto';\n\t\t\t}\n\t\t};\n\n\t\t// add event listener to remove height after transition\n\t\t_.addEventListener('transitionend', _.#handleTransitionEnd);\n\t}\n\n\t/**\n\t * Called when element is added to the DOM\n\t * Sets initial height based on hidden state\n\t */\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// get aria-expanded state from button\n\t\tconst component = _.closest('collapsible-component');\n\t\tconst button = component.querySelector('button');\n\t\tconst expanded = button.getAttribute('aria-expanded') === 'true';\n\n\t\tif (expanded) {\n\t\t\t_.style.height = 'auto';\n\t\t} else {\n\t\t\t_.style.height = '0';\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_.removeEventListener('transitionend', _.#handleTransitionEnd);\n\t}\n\n\t/**\n\t * Handles setting the hidden attribute with animation\n\t * @param {boolean} value - Whether element should be hidden\n\t */\n\tset hidden(value) {\n\t\tconst _ = this;\n\n\t\t// animate to closed\n\t\tif (value) {\n\t\t\t// reset height to animate from\n\t\t\t_.style.height = `${_.scrollHeight}px`;\n\n\t\t\t// wait one frame and animate to 0\n\t\t\tsetTimeout(() => {\n\t\t\t\t_.style.height = '0px';\n\t\t\t}, 1);\n\n\t\t\t// set accessibility attributes\n\t\t\t_.setAttribute('aria-hidden', 'true');\n\t\t\t_.setAttribute('inert', '');\n\t\t\t_.setAttribute('hidden', '');\n\t\t} else {\n\t\t\t// only animate if not set to auto\n\t\t\tif (_.style.height !== 'auto') {\n\t\t\t\t// animate to open\n\t\t\t\t_.style.height = `${_.scrollHeight}px`;\n\t\t\t}\n\n\t\t\t// remove accessibility attributes\n\t\t\t_.removeAttribute('aria-hidden');\n\t\t\t_.removeAttribute('inert');\n\t\t\t_.removeAttribute('hidden');\n\t\t}\n\t}\n\n\t/**\n\t * Gets the hidden state from the attribute\n\t * @returns {boolean} Whether element is hidden\n\t */\n\tget hidden() {\n\t\tconst _ = this;\n\t\treturn _.hasAttribute('hidden');\n\t}\n}\n\n// register custom elements\ncustomElements.define('collapsible-content', CollapsibleContent);\ncustomElements.define('collapsible-component', CollapsibleComponent);\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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"}
|