@magic-spells/collapsible-content 0.1.4 → 1.0.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/LICENSE +1 -1
- package/README.md +116 -0
- package/dist/collapsible-content.cjs.js +133 -101
- package/dist/collapsible-content.cjs.js.map +1 -1
- package/dist/collapsible-content.css +31 -0
- package/dist/collapsible-content.esm.js +133 -101
- package/dist/collapsible-content.esm.js.map +1 -1
- package/dist/collapsible-content.js +217 -0
- package/dist/collapsible-content.js.map +1 -0
- package/dist/collapsible-content.min.css +1 -1
- package/dist/collapsible-content.min.js +1 -1
- package/package.json +8 -7
- package/src/collapsible-content.css +31 -0
- package/src/collapsible-content.js +134 -102
- package/src/styles.css +0 -22
|
@@ -1,176 +1,208 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Custom element that creates a collapsible/expandable component with proper accessibility
|
|
3
3
|
*/
|
|
4
4
|
class CollapsibleComponent extends HTMLElement {
|
|
5
5
|
#handleClick;
|
|
6
|
+
#abortController;
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
*
|
|
9
|
+
* Initializes the component and sets up references to child elements
|
|
9
10
|
*/
|
|
10
11
|
constructor() {
|
|
11
12
|
super();
|
|
13
|
+
const _ = this;
|
|
12
14
|
|
|
13
15
|
// store references to elements once to avoid re-querying
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
_.button = null;
|
|
17
|
+
_.content = null;
|
|
16
18
|
|
|
17
19
|
// define click handler with proper this binding
|
|
18
|
-
|
|
20
|
+
_.#handleClick = (e) => {
|
|
19
21
|
e.preventDefault();
|
|
20
22
|
// toggle expanded value
|
|
21
|
-
const expanded =
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
const expanded = _.button.getAttribute('aria-expanded') !== 'true';
|
|
24
|
+
_.button.setAttribute('aria-expanded', expanded);
|
|
25
|
+
_.content.collapsed = !expanded;
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
+
* Called when element is added to the DOM
|
|
31
|
+
* Sets up accessibility attributes and event listeners
|
|
30
32
|
*/
|
|
31
33
|
connectedCallback() {
|
|
32
|
-
|
|
33
|
-
this.button = this.querySelector('button');
|
|
34
|
-
this.content = this.querySelector('collapsible-content');
|
|
34
|
+
const _ = this;
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
// initialize element references once
|
|
37
|
+
_.button = _.querySelector('button');
|
|
38
|
+
_.content = _.querySelector('collapsible-content');
|
|
39
|
+
|
|
40
|
+
if (!_.button || !_.content) {
|
|
41
|
+
const error = new Error(
|
|
42
|
+
'CollapsibleComponent requires a <button> and a <collapsible-content>.'
|
|
43
|
+
);
|
|
44
|
+
console.error(error.message);
|
|
45
|
+
_.dispatchEvent(
|
|
46
|
+
new CustomEvent('collapsible-error', {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
detail: { error },
|
|
49
|
+
})
|
|
50
|
+
);
|
|
38
51
|
return;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
// generate ids if not provided
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
_.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
|
|
56
|
+
_.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
|
|
44
57
|
|
|
45
58
|
// set accessibility attributes
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
// set initial state based on
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
59
|
+
_.button.type ||= 'button';
|
|
60
|
+
_.button.setAttribute('aria-controls', _.content.id);
|
|
61
|
+
_.content.setAttribute('aria-labelledby', _.button.id);
|
|
62
|
+
|
|
63
|
+
// set initial state based on open attribute
|
|
64
|
+
const open = _.content.hasAttribute('open');
|
|
65
|
+
_.button.setAttribute('aria-expanded', open);
|
|
66
|
+
_.content.collapsed = !open;
|
|
67
|
+
|
|
68
|
+
// use AbortController to prevent duplicate listeners on reconnection
|
|
69
|
+
_.#abortController = new AbortController();
|
|
70
|
+
_.button.addEventListener('click', _.#handleClick, {
|
|
71
|
+
signal: _.#abortController.signal,
|
|
72
|
+
});
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
76
|
+
* Called when element is removed from the DOM
|
|
77
|
+
* Cleans up event listeners
|
|
63
78
|
*/
|
|
64
79
|
disconnectedCallback() {
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
const _ = this;
|
|
81
|
+
if (_.#abortController) {
|
|
82
|
+
_.#abortController.abort();
|
|
83
|
+
_.#abortController = null;
|
|
67
84
|
}
|
|
68
85
|
}
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
/**
|
|
72
|
-
*
|
|
89
|
+
* Custom element that provides animated collapsible content
|
|
73
90
|
*/
|
|
74
91
|
class CollapsibleContent extends HTMLElement {
|
|
92
|
+
#handleTransitionEnd;
|
|
93
|
+
#abortController;
|
|
94
|
+
#animating = false;
|
|
95
|
+
|
|
75
96
|
/**
|
|
76
|
-
*
|
|
97
|
+
* Initializes the content element and binds event handlers
|
|
77
98
|
*/
|
|
78
99
|
constructor() {
|
|
79
100
|
super();
|
|
101
|
+
const _ = this;
|
|
80
102
|
|
|
81
|
-
//
|
|
82
|
-
|
|
103
|
+
// define event handler using arrow function for proper binding
|
|
104
|
+
_.#handleTransitionEnd = (event) => {
|
|
105
|
+
// exit if isn't height property
|
|
106
|
+
if (event.propertyName !== 'height') return;
|
|
83
107
|
|
|
84
|
-
|
|
85
|
-
|
|
108
|
+
_.#animating = false;
|
|
109
|
+
|
|
110
|
+
// remove the inline height to allow dynamic content changes
|
|
111
|
+
if (!_.collapsed) {
|
|
112
|
+
_.style.height = 'auto';
|
|
113
|
+
}
|
|
114
|
+
};
|
|
86
115
|
}
|
|
87
116
|
|
|
88
117
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
118
|
+
* Called when element is added to the DOM
|
|
119
|
+
* Sets initial height based on open attribute
|
|
91
120
|
*/
|
|
92
121
|
connectedCallback() {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
this.style.height = '0';
|
|
102
|
-
}
|
|
122
|
+
const _ = this;
|
|
123
|
+
_.style.height = _.hasAttribute('open') ? 'auto' : '0';
|
|
124
|
+
|
|
125
|
+
// use AbortController to prevent duplicate listeners on reconnection
|
|
126
|
+
_.#abortController = new AbortController();
|
|
127
|
+
_.addEventListener('transitionend', _.#handleTransitionEnd, {
|
|
128
|
+
signal: _.#abortController.signal,
|
|
129
|
+
});
|
|
103
130
|
}
|
|
104
131
|
|
|
105
132
|
/**
|
|
106
|
-
*
|
|
107
|
-
*
|
|
133
|
+
* Called when element is removed from the DOM
|
|
134
|
+
* Cleans up event listeners
|
|
108
135
|
*/
|
|
109
136
|
disconnectedCallback() {
|
|
110
|
-
|
|
137
|
+
const _ = this;
|
|
138
|
+
if (_.#abortController) {
|
|
139
|
+
_.#abortController.abort();
|
|
140
|
+
_.#abortController = null;
|
|
141
|
+
}
|
|
111
142
|
}
|
|
112
143
|
|
|
113
144
|
/**
|
|
114
|
-
*
|
|
115
|
-
* @param {boolean} value -
|
|
145
|
+
* Handles setting the collapsed state with animation
|
|
146
|
+
* @param {boolean} value - Whether element should be collapsed
|
|
116
147
|
*/
|
|
117
|
-
set
|
|
118
|
-
|
|
148
|
+
set collapsed(value) {
|
|
149
|
+
const _ = this;
|
|
150
|
+
|
|
151
|
+
// prevent rapid clicking during animation
|
|
152
|
+
if (_.#animating) return;
|
|
153
|
+
|
|
154
|
+
// check if transitions are enabled (respects prefers-reduced-motion)
|
|
155
|
+
const hasTransition = getComputedStyle(_).transitionDuration !== '0s';
|
|
156
|
+
|
|
119
157
|
if (value) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
158
|
+
if (hasTransition) {
|
|
159
|
+
_.#animating = true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// animate to closed
|
|
163
|
+
_.style.height = `${_.scrollHeight}px`;
|
|
164
|
+
|
|
165
|
+
// use requestAnimationFrame for reliable frame timing
|
|
166
|
+
requestAnimationFrame(() => {
|
|
167
|
+
requestAnimationFrame(() => {
|
|
168
|
+
_.style.height = '0px';
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
_.removeAttribute('open');
|
|
173
|
+
_.setAttribute('aria-hidden', 'true');
|
|
174
|
+
_.setAttribute('inert', '');
|
|
132
175
|
} else {
|
|
133
|
-
// only animate if not
|
|
134
|
-
if (
|
|
135
|
-
|
|
136
|
-
|
|
176
|
+
// only animate if not already auto
|
|
177
|
+
if (_.style.height !== 'auto') {
|
|
178
|
+
if (hasTransition) {
|
|
179
|
+
_.#animating = true;
|
|
180
|
+
}
|
|
181
|
+
_.style.height = `${_.scrollHeight}px`;
|
|
137
182
|
}
|
|
138
183
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
this.removeAttribute('hidden');
|
|
184
|
+
_.setAttribute('open', '');
|
|
185
|
+
_.removeAttribute('aria-hidden');
|
|
186
|
+
_.removeAttribute('inert');
|
|
143
187
|
}
|
|
144
188
|
}
|
|
145
189
|
|
|
146
190
|
/**
|
|
147
|
-
*
|
|
148
|
-
* @
|
|
149
|
-
*/
|
|
150
|
-
get hidden() {
|
|
151
|
-
return this.hasAttribute('hidden');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* handles the end of css transitions
|
|
156
|
-
* @param {TransitionEvent} event - the transition event
|
|
191
|
+
* Gets the collapsed state
|
|
192
|
+
* @returns {boolean} Whether element is collapsed
|
|
157
193
|
*/
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// exit if isn't height property
|
|
162
|
-
if (event.propertyName != 'height') return;
|
|
163
|
-
|
|
164
|
-
// remove the inline height to allow dynamic content changes
|
|
165
|
-
if (!this.hidden) {
|
|
166
|
-
this.style.height = 'auto';
|
|
167
|
-
}
|
|
194
|
+
get collapsed() {
|
|
195
|
+
return !this.hasAttribute('open');
|
|
168
196
|
}
|
|
169
197
|
}
|
|
170
198
|
|
|
171
|
-
// register custom elements
|
|
172
|
-
customElements.
|
|
173
|
-
customElements.define('collapsible-
|
|
199
|
+
// register custom elements if not already defined
|
|
200
|
+
if (!customElements.get('collapsible-content')) {
|
|
201
|
+
customElements.define('collapsible-content', CollapsibleContent);
|
|
202
|
+
}
|
|
203
|
+
if (!customElements.get('collapsible-component')) {
|
|
204
|
+
customElements.define('collapsible-component', CollapsibleComponent);
|
|
205
|
+
}
|
|
174
206
|
|
|
175
207
|
export { CollapsibleComponent, CollapsibleContent };
|
|
176
208
|
//# sourceMappingURL=collapsible-content.esm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collapsible-content.esm.js","sources":["../src/collapsible-content.js"],"sourcesContent":["import './styles.css';\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\n\t\t// store references to elements once to avoid re-querying\n\t\tthis.button = null;\n\t\tthis.content = null;\n\n\t\t// define click handler with proper this binding\n\t\tthis.#handleClick = (e) => {\n\t\t\te.preventDefault();\n\t\t\t// toggle expanded value\n\t\t\tconst expanded = this.button.getAttribute('aria-expanded') !== 'true';\n\t\t\tthis.button.setAttribute('aria-expanded', expanded);\n\t\t\tthis.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\t// initialize element references once\n\t\tthis.button = this.querySelector('button');\n\t\tthis.content = this.querySelector('collapsible-content');\n\n\t\tif (!this.button || !this.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\tthis.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;\n\t\tthis.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;\n\n\t\t// set accessibility attributes\n\t\tthis.button.setAttribute('aria-controls', this.content.id);\n\t\tthis.content.setAttribute('role', 'region');\n\t\tthis.content.setAttribute('aria-labelledby', this.button.id);\n\n\t\t// set initial state based on aria-expanded attribute\n\t\tconst expanded = this.button.getAttribute('aria-expanded') === 'true';\n\n\t\t// make shure content state matches button state\n\t\tthis.content.hidden = !expanded;\n\n\t\t// add event listener\n\t\tthis.button.addEventListener('click', this.#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\tif (this.button) {\n\t\t\tthis.button.removeEventListener('click', this.#handleClick);\n\t\t}\n\t}\n}\n\n/**\n * custom element that provides animated collapsible content\n */\nclass CollapsibleContent extends HTMLElement {\n\t/**\n\t * initializes the content element and binds event handlers\n\t */\n\tconstructor() {\n\t\tsuper();\n\n\t\t// bind event handler to this instance\n\t\tthis._onTransitionEnd = this._onTransitionEnd.bind(this);\n\n\t\t// add event listener to remove height after transition\n\t\tthis.addEventListener('transitionend', this._onTransitionEnd);\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\t// get aria-expanded state from buton\n\t\tconst component = this.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\tthis.style.height = 'auto';\n\t\t} else {\n\t\t\tthis.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\tthis.removeEventListener('transitionend', this._onTransitionEnd);\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\t// animate to closed\n\t\tif (value) {\n\t\t\t// reset height to animate from\n\t\t\tthis.style.height = `${this.scrollHeight}px`;\n\n\t\t\t// wait one frame and animate to 0\n\t\t\tsetTimeout(() => {\n\t\t\t\tthis.style.height = '0px';\n\t\t\t}, 1);\n\n\t\t\t// set accessibility attributes\n\t\t\tthis.setAttribute('aria-hidden', 'true');\n\t\t\tthis.setAttribute('inert', '');\n\t\t\tthis.setAttribute('hidden', '');\n\t\t} else {\n\t\t\t// only animate if not set to auto\n\t\t\tif (this.style.height !== 'auto') {\n\t\t\t\t// animate to open\n\t\t\t\tthis.style.height = `${this.scrollHeight}px`;\n\t\t\t}\n\n\t\t\t// remove accessibility attributes\n\t\t\tthis.removeAttribute('aria-hidden');\n\t\t\tthis.removeAttribute('inert');\n\t\t\tthis.removeAttribute('hidden');\n\t\t}\n\t}\n\n\t/**\n\t * gets the hidden state from the attribute\n\t * @return {boolean} whether element is hidden\n\t */\n\tget hidden() {\n\t\treturn this.hasAttribute('hidden');\n\t}\n\n\t/**\n\t * handles the end of css transitions\n\t * @param {TransitionEvent} event - the transition event\n\t */\n\t_onTransitionEnd(event) {\n\t\tconsole.log('on transition end');\n\n\t\t// exit if isn't height property\n\t\tif (event.propertyName != 'height') return;\n\n\t\t// remove the inline height to allow dynamic content changes\n\t\tif (!this.hidden) {\n\t\t\tthis.style.height = 'auto';\n\t\t}\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.esm.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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"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
(function (global, factory) {
|
|
2
|
+
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
3
|
+
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
4
|
+
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.CollapsibleContent = {}));
|
|
5
|
+
})(this, (function (exports) { 'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Custom element that creates a collapsible/expandable component with proper accessibility
|
|
9
|
+
*/
|
|
10
|
+
class CollapsibleComponent extends HTMLElement {
|
|
11
|
+
#handleClick;
|
|
12
|
+
#abortController;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initializes the component and sets up references to child elements
|
|
16
|
+
*/
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
const _ = this;
|
|
20
|
+
|
|
21
|
+
// store references to elements once to avoid re-querying
|
|
22
|
+
_.button = null;
|
|
23
|
+
_.content = null;
|
|
24
|
+
|
|
25
|
+
// define click handler with proper this binding
|
|
26
|
+
_.#handleClick = (e) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
// toggle expanded value
|
|
29
|
+
const expanded = _.button.getAttribute('aria-expanded') !== 'true';
|
|
30
|
+
_.button.setAttribute('aria-expanded', expanded);
|
|
31
|
+
_.content.collapsed = !expanded;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Called when element is added to the DOM
|
|
37
|
+
* Sets up accessibility attributes and event listeners
|
|
38
|
+
*/
|
|
39
|
+
connectedCallback() {
|
|
40
|
+
const _ = this;
|
|
41
|
+
|
|
42
|
+
// initialize element references once
|
|
43
|
+
_.button = _.querySelector('button');
|
|
44
|
+
_.content = _.querySelector('collapsible-content');
|
|
45
|
+
|
|
46
|
+
if (!_.button || !_.content) {
|
|
47
|
+
const error = new Error(
|
|
48
|
+
'CollapsibleComponent requires a <button> and a <collapsible-content>.'
|
|
49
|
+
);
|
|
50
|
+
console.error(error.message);
|
|
51
|
+
_.dispatchEvent(
|
|
52
|
+
new CustomEvent('collapsible-error', {
|
|
53
|
+
bubbles: true,
|
|
54
|
+
detail: { error },
|
|
55
|
+
})
|
|
56
|
+
);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// generate ids if not provided
|
|
61
|
+
_.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
|
|
62
|
+
_.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
|
|
63
|
+
|
|
64
|
+
// set accessibility attributes
|
|
65
|
+
_.button.type ||= 'button';
|
|
66
|
+
_.button.setAttribute('aria-controls', _.content.id);
|
|
67
|
+
_.content.setAttribute('aria-labelledby', _.button.id);
|
|
68
|
+
|
|
69
|
+
// set initial state based on open attribute
|
|
70
|
+
const open = _.content.hasAttribute('open');
|
|
71
|
+
_.button.setAttribute('aria-expanded', open);
|
|
72
|
+
_.content.collapsed = !open;
|
|
73
|
+
|
|
74
|
+
// use AbortController to prevent duplicate listeners on reconnection
|
|
75
|
+
_.#abortController = new AbortController();
|
|
76
|
+
_.button.addEventListener('click', _.#handleClick, {
|
|
77
|
+
signal: _.#abortController.signal,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Called when element is removed from the DOM
|
|
83
|
+
* Cleans up event listeners
|
|
84
|
+
*/
|
|
85
|
+
disconnectedCallback() {
|
|
86
|
+
const _ = this;
|
|
87
|
+
if (_.#abortController) {
|
|
88
|
+
_.#abortController.abort();
|
|
89
|
+
_.#abortController = null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Custom element that provides animated collapsible content
|
|
96
|
+
*/
|
|
97
|
+
class CollapsibleContent extends HTMLElement {
|
|
98
|
+
#handleTransitionEnd;
|
|
99
|
+
#abortController;
|
|
100
|
+
#animating = false;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Initializes the content element and binds event handlers
|
|
104
|
+
*/
|
|
105
|
+
constructor() {
|
|
106
|
+
super();
|
|
107
|
+
const _ = this;
|
|
108
|
+
|
|
109
|
+
// define event handler using arrow function for proper binding
|
|
110
|
+
_.#handleTransitionEnd = (event) => {
|
|
111
|
+
// exit if isn't height property
|
|
112
|
+
if (event.propertyName !== 'height') return;
|
|
113
|
+
|
|
114
|
+
_.#animating = false;
|
|
115
|
+
|
|
116
|
+
// remove the inline height to allow dynamic content changes
|
|
117
|
+
if (!_.collapsed) {
|
|
118
|
+
_.style.height = 'auto';
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Called when element is added to the DOM
|
|
125
|
+
* Sets initial height based on open attribute
|
|
126
|
+
*/
|
|
127
|
+
connectedCallback() {
|
|
128
|
+
const _ = this;
|
|
129
|
+
_.style.height = _.hasAttribute('open') ? 'auto' : '0';
|
|
130
|
+
|
|
131
|
+
// use AbortController to prevent duplicate listeners on reconnection
|
|
132
|
+
_.#abortController = new AbortController();
|
|
133
|
+
_.addEventListener('transitionend', _.#handleTransitionEnd, {
|
|
134
|
+
signal: _.#abortController.signal,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Called when element is removed from the DOM
|
|
140
|
+
* Cleans up event listeners
|
|
141
|
+
*/
|
|
142
|
+
disconnectedCallback() {
|
|
143
|
+
const _ = this;
|
|
144
|
+
if (_.#abortController) {
|
|
145
|
+
_.#abortController.abort();
|
|
146
|
+
_.#abortController = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Handles setting the collapsed state with animation
|
|
152
|
+
* @param {boolean} value - Whether element should be collapsed
|
|
153
|
+
*/
|
|
154
|
+
set collapsed(value) {
|
|
155
|
+
const _ = this;
|
|
156
|
+
|
|
157
|
+
// prevent rapid clicking during animation
|
|
158
|
+
if (_.#animating) return;
|
|
159
|
+
|
|
160
|
+
// check if transitions are enabled (respects prefers-reduced-motion)
|
|
161
|
+
const hasTransition = getComputedStyle(_).transitionDuration !== '0s';
|
|
162
|
+
|
|
163
|
+
if (value) {
|
|
164
|
+
if (hasTransition) {
|
|
165
|
+
_.#animating = true;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// animate to closed
|
|
169
|
+
_.style.height = `${_.scrollHeight}px`;
|
|
170
|
+
|
|
171
|
+
// use requestAnimationFrame for reliable frame timing
|
|
172
|
+
requestAnimationFrame(() => {
|
|
173
|
+
requestAnimationFrame(() => {
|
|
174
|
+
_.style.height = '0px';
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
_.removeAttribute('open');
|
|
179
|
+
_.setAttribute('aria-hidden', 'true');
|
|
180
|
+
_.setAttribute('inert', '');
|
|
181
|
+
} else {
|
|
182
|
+
// only animate if not already auto
|
|
183
|
+
if (_.style.height !== 'auto') {
|
|
184
|
+
if (hasTransition) {
|
|
185
|
+
_.#animating = true;
|
|
186
|
+
}
|
|
187
|
+
_.style.height = `${_.scrollHeight}px`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_.setAttribute('open', '');
|
|
191
|
+
_.removeAttribute('aria-hidden');
|
|
192
|
+
_.removeAttribute('inert');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Gets the collapsed state
|
|
198
|
+
* @returns {boolean} Whether element is collapsed
|
|
199
|
+
*/
|
|
200
|
+
get collapsed() {
|
|
201
|
+
return !this.hasAttribute('open');
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// register custom elements if not already defined
|
|
206
|
+
if (!customElements.get('collapsible-content')) {
|
|
207
|
+
customElements.define('collapsible-content', CollapsibleContent);
|
|
208
|
+
}
|
|
209
|
+
if (!customElements.get('collapsible-component')) {
|
|
210
|
+
customElements.define('collapsible-component', CollapsibleComponent);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
exports.CollapsibleComponent = CollapsibleComponent;
|
|
214
|
+
exports.CollapsibleContent = CollapsibleContent;
|
|
215
|
+
|
|
216
|
+
}));
|
|
217
|
+
//# sourceMappingURL=collapsible-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collapsible-content.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 +1 @@
|
|
|
1
|
-
collapsible-component{display:block}collapsible-component button{background:none;border:none;cursor:pointer;text-align:left;width:100%}collapsible-content{display:block;overflow:hidden;
|
|
1
|
+
collapsible-component{display:block}collapsible-component button{background:none;border:none;cursor:pointer;text-align:left;width:100%}collapsible-component button:focus-visible{outline:2px solid currentColor;outline-offset:2px}collapsible-content{--collapsible-duration:0.35s;--collapsible-easing:ease-out;display:block;overflow:hidden;transition:height var(--collapsible-duration) var(--collapsible-easing)}@media (prefers-reduced-motion:reduce){collapsible-content{transition:none}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).CollapsibleContent={})}(this,(function(t){"use strict";class CollapsibleComponent extends HTMLElement{#t;#e;constructor(){super();const t=this;t.button=null,t.content=null,t.#t=e=>{e.preventDefault();const n="true"!==t.button.getAttribute("aria-expanded");t.button.setAttribute("aria-expanded",n),t.content.collapsed=!n}}connectedCallback(){const t=this;if(t.button=t.querySelector("button"),t.content=t.querySelector("collapsible-content"),!t.button||!t.content){const e=new Error("CollapsibleComponent requires a <button> and a <collapsible-content>.");return console.error(e.message),void t.dispatchEvent(new CustomEvent("collapsible-error",{bubbles:!0,detail:{error:e}}))}t.button.id||=`collapsible-button-${crypto.randomUUID().slice(0,8)}`,t.content.id||=`collapsible-content-${crypto.randomUUID().slice(0,8)}`,t.button.type||="button",t.button.setAttribute("aria-controls",t.content.id),t.content.setAttribute("aria-labelledby",t.button.id);const e=t.content.hasAttribute("open");t.button.setAttribute("aria-expanded",e),t.content.collapsed=!e,t.#e=new AbortController,t.button.addEventListener("click",t.#t,{signal:t.#e.signal})}disconnectedCallback(){const t=this;t.#e&&(t.#e.abort(),t.#e=null)}}class CollapsibleContent extends HTMLElement{#n;#e;#o=!1;constructor(){super();const t=this;t.#n=e=>{"height"===e.propertyName&&(t.#o=!1,t.collapsed||(t.style.height="auto"))}}connectedCallback(){const t=this;t.style.height=t.hasAttribute("open")?"auto":"0",t.#e=new AbortController,t.addEventListener("transitionend",t.#n,{signal:t.#e.signal})}disconnectedCallback(){const t=this;t.#e&&(t.#e.abort(),t.#e=null)}set collapsed(t){const e=this;if(e.#o)return;const n="0s"!==getComputedStyle(e).transitionDuration;t?(n&&(e.#o=!0),e.style.height=`${e.scrollHeight}px`,requestAnimationFrame((()=>{requestAnimationFrame((()=>{e.style.height="0px"}))})),e.removeAttribute("open"),e.setAttribute("aria-hidden","true"),e.setAttribute("inert","")):("auto"!==e.style.height&&(n&&(e.#o=!0),e.style.height=`${e.scrollHeight}px`),e.setAttribute("open",""),e.removeAttribute("aria-hidden"),e.removeAttribute("inert"))}get collapsed(){return!this.hasAttribute("open")}}customElements.get("collapsible-content")||customElements.define("collapsible-content",CollapsibleContent),customElements.get("collapsible-component")||customElements.define("collapsible-component",CollapsibleComponent),t.CollapsibleComponent=CollapsibleComponent,t.CollapsibleContent=CollapsibleContent}));
|