@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2024 Magic Spells (Cory Schulz)
3
+ Copyright (c) 2025 Magic Spells (Cory Schulz)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Collapsible Content Web Component
2
+
3
+ A lightweight, accessible Web Component for creating collapsible content sections. Perfect for FAQs, accordions, or any content that needs to be toggled.
4
+
5
+ [**Live Demo**](https://magic-spells.github.io/collapsible-content/demo/)
6
+
7
+ ## Features
8
+
9
+ - No dependencies
10
+ - Lightweight
11
+ - Accessible (ARIA attributes, keyboard support, focus management)
12
+ - Smooth animations with `prefers-reduced-motion` support
13
+ - Prevents rapid click issues during animation
14
+ - Safe to import multiple times (no double-registration errors)
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @magic-spells/collapsible-content
20
+ ```
21
+
22
+ ```javascript
23
+ import '@magic-spells/collapsible-content';
24
+ ```
25
+
26
+ Or include directly in your HTML:
27
+
28
+ ```html
29
+ <script src="https://unpkg.com/@magic-spells/collapsible-content"></script>
30
+ ```
31
+
32
+ ## Usage
33
+
34
+ ```html
35
+ <collapsible-component>
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
+ </collapsible-component>
44
+ ```
45
+
46
+ ### Start Expanded
47
+
48
+ Add the `open` attribute to start with content visible:
49
+
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
+ ## Customization
60
+
61
+ Customize the animation with CSS custom properties:
62
+
63
+ ```css
64
+ collapsible-content {
65
+ --collapsible-duration: 0.5s;
66
+ --collapsible-easing: ease-in-out;
67
+ }
68
+ ```
69
+
70
+ | Property | Default | Description |
71
+ |----------|---------|-------------|
72
+ | `--collapsible-duration` | `0.35s` | Animation duration |
73
+ | `--collapsible-easing` | `ease-out` | Animation timing function |
74
+
75
+ ## Events
76
+
77
+ ### collapsible-error
78
+
79
+ Fired when the component is missing required children:
80
+
81
+ ```javascript
82
+ document.addEventListener('collapsible-error', (e) => {
83
+ console.error('Collapsible setup failed:', e.detail.error);
84
+ });
85
+ ```
86
+
87
+ ## Programmatic Control
88
+
89
+ Access the `collapsed` property on the `<collapsible-content>` element:
90
+
91
+ ```javascript
92
+ const content = document.querySelector('collapsible-content');
93
+ content.collapsed = true; // collapse
94
+ content.collapsed = false; // expand
95
+ console.log(content.collapsed); // get current state
96
+ ```
97
+
98
+ ## Accessibility
99
+
100
+ This component follows WCAG guidelines:
101
+
102
+ - `aria-expanded` on button indicates current state
103
+ - `aria-controls` links button to content
104
+ - `role="region"` and `aria-labelledby` on content
105
+ - `aria-hidden` and `inert` when collapsed
106
+ - Keyboard accessible (Space/Enter to toggle)
107
+ - Focus-visible styling for keyboard users
108
+ - Respects `prefers-reduced-motion`
109
+
110
+ ## Browser Support
111
+
112
+ Modern browsers with Web Components support (Chrome, Firefox, Safari, Edge).
113
+
114
+ ## License
115
+
116
+ MIT
@@ -1,178 +1,210 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * custom element that creates a collapsible/expandable component with proper accessibility
4
+ * Custom element that creates a collapsible/expandable component with proper accessibility
5
5
  */
6
6
  class CollapsibleComponent extends HTMLElement {
7
7
  #handleClick;
8
+ #abortController;
8
9
 
9
10
  /**
10
- * initializes the component and sets up references to child elements
11
+ * Initializes the component and sets up references to child elements
11
12
  */
12
13
  constructor() {
13
14
  super();
15
+ const _ = this;
14
16
 
15
17
  // store references to elements once to avoid re-querying
16
- this.button = null;
17
- this.content = null;
18
+ _.button = null;
19
+ _.content = null;
18
20
 
19
21
  // define click handler with proper this binding
20
- this.#handleClick = (e) => {
22
+ _.#handleClick = (e) => {
21
23
  e.preventDefault();
22
24
  // toggle expanded value
23
- const expanded = this.button.getAttribute('aria-expanded') !== 'true';
24
- this.button.setAttribute('aria-expanded', expanded);
25
- this.content.hidden = !expanded;
25
+ const expanded = _.button.getAttribute('aria-expanded') !== 'true';
26
+ _.button.setAttribute('aria-expanded', expanded);
27
+ _.content.collapsed = !expanded;
26
28
  };
27
29
  }
28
30
 
29
31
  /**
30
- * called when element is added to the dom
31
- * sets up accessibility attributes and event listeners
32
+ * Called when element is added to the DOM
33
+ * Sets up accessibility attributes and event listeners
32
34
  */
33
35
  connectedCallback() {
34
- // initialize element references once
35
- this.button = this.querySelector('button');
36
- this.content = this.querySelector('collapsible-content');
36
+ const _ = this;
37
37
 
38
- if (!this.button || !this.content) {
39
- console.error('CollapsibleComponent requires a <button> and a <collapsible-content>.');
38
+ // initialize element references once
39
+ _.button = _.querySelector('button');
40
+ _.content = _.querySelector('collapsible-content');
41
+
42
+ if (!_.button || !_.content) {
43
+ const error = new Error(
44
+ 'CollapsibleComponent requires a <button> and a <collapsible-content>.'
45
+ );
46
+ console.error(error.message);
47
+ _.dispatchEvent(
48
+ new CustomEvent('collapsible-error', {
49
+ bubbles: true,
50
+ detail: { error },
51
+ })
52
+ );
40
53
  return;
41
54
  }
42
55
 
43
56
  // generate ids if not provided
44
- this.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
45
- this.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
57
+ _.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
58
+ _.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
46
59
 
47
60
  // set accessibility attributes
48
- this.button.setAttribute('aria-controls', this.content.id);
49
- this.content.setAttribute('role', 'region');
50
- this.content.setAttribute('aria-labelledby', this.button.id);
51
-
52
- // set initial state based on aria-expanded attribute
53
- const expanded = this.button.getAttribute('aria-expanded') === 'true';
54
-
55
- // make shure content state matches button state
56
- this.content.hidden = !expanded;
57
-
58
- // add event listener
59
- this.button.addEventListener('click', this.#handleClick);
61
+ _.button.type ||= 'button';
62
+ _.button.setAttribute('aria-controls', _.content.id);
63
+ _.content.setAttribute('aria-labelledby', _.button.id);
64
+
65
+ // set initial state based on open attribute
66
+ const open = _.content.hasAttribute('open');
67
+ _.button.setAttribute('aria-expanded', open);
68
+ _.content.collapsed = !open;
69
+
70
+ // use AbortController to prevent duplicate listeners on reconnection
71
+ _.#abortController = new AbortController();
72
+ _.button.addEventListener('click', _.#handleClick, {
73
+ signal: _.#abortController.signal,
74
+ });
60
75
  }
61
76
 
62
77
  /**
63
- * called when element is removed from the dom
64
- * cleans up event listeners
78
+ * Called when element is removed from the DOM
79
+ * Cleans up event listeners
65
80
  */
66
81
  disconnectedCallback() {
67
- if (this.button) {
68
- this.button.removeEventListener('click', this.#handleClick);
82
+ const _ = this;
83
+ if (_.#abortController) {
84
+ _.#abortController.abort();
85
+ _.#abortController = null;
69
86
  }
70
87
  }
71
88
  }
72
89
 
73
90
  /**
74
- * custom element that provides animated collapsible content
91
+ * Custom element that provides animated collapsible content
75
92
  */
76
93
  class CollapsibleContent extends HTMLElement {
94
+ #handleTransitionEnd;
95
+ #abortController;
96
+ #animating = false;
97
+
77
98
  /**
78
- * initializes the content element and binds event handlers
99
+ * Initializes the content element and binds event handlers
79
100
  */
80
101
  constructor() {
81
102
  super();
103
+ const _ = this;
82
104
 
83
- // bind event handler to this instance
84
- this._onTransitionEnd = this._onTransitionEnd.bind(this);
105
+ // define event handler using arrow function for proper binding
106
+ _.#handleTransitionEnd = (event) => {
107
+ // exit if isn't height property
108
+ if (event.propertyName !== 'height') return;
85
109
 
86
- // add event listener to remove height after transition
87
- this.addEventListener('transitionend', this._onTransitionEnd);
110
+ _.#animating = false;
111
+
112
+ // remove the inline height to allow dynamic content changes
113
+ if (!_.collapsed) {
114
+ _.style.height = 'auto';
115
+ }
116
+ };
88
117
  }
89
118
 
90
119
  /**
91
- * called when element is added to the dom
92
- * sets initial height based on hidden state
120
+ * Called when element is added to the DOM
121
+ * Sets initial height based on open attribute
93
122
  */
94
123
  connectedCallback() {
95
- // get aria-expanded state from buton
96
- const component = this.closest('collapsible-component');
97
- const button = component.querySelector('button');
98
- const expanded = button.getAttribute('aria-expanded') === 'true';
99
-
100
- if (expanded) {
101
- this.style.height = 'auto';
102
- } else {
103
- this.style.height = '0';
104
- }
124
+ const _ = this;
125
+ _.style.height = _.hasAttribute('open') ? 'auto' : '0';
126
+
127
+ // use AbortController to prevent duplicate listeners on reconnection
128
+ _.#abortController = new AbortController();
129
+ _.addEventListener('transitionend', _.#handleTransitionEnd, {
130
+ signal: _.#abortController.signal,
131
+ });
105
132
  }
106
133
 
107
134
  /**
108
- * called when element is removed from the dom
109
- * cleans up event listeners
135
+ * Called when element is removed from the DOM
136
+ * Cleans up event listeners
110
137
  */
111
138
  disconnectedCallback() {
112
- this.removeEventListener('transitionend', this._onTransitionEnd);
139
+ const _ = this;
140
+ if (_.#abortController) {
141
+ _.#abortController.abort();
142
+ _.#abortController = null;
143
+ }
113
144
  }
114
145
 
115
146
  /**
116
- * handles setting the hidden attribute with animation
117
- * @param {boolean} value - whether element should be hidden
147
+ * Handles setting the collapsed state with animation
148
+ * @param {boolean} value - Whether element should be collapsed
118
149
  */
119
- set hidden(value) {
120
- // animate to closed
150
+ set collapsed(value) {
151
+ const _ = this;
152
+
153
+ // prevent rapid clicking during animation
154
+ if (_.#animating) return;
155
+
156
+ // check if transitions are enabled (respects prefers-reduced-motion)
157
+ const hasTransition = getComputedStyle(_).transitionDuration !== '0s';
158
+
121
159
  if (value) {
122
- // reset height to animate from
123
- this.style.height = `${this.scrollHeight}px`;
124
-
125
- // wait one frame and animate to 0
126
- setTimeout(() => {
127
- this.style.height = '0px';
128
- }, 1);
129
-
130
- // set accessibility attributes
131
- this.setAttribute('aria-hidden', 'true');
132
- this.setAttribute('inert', '');
133
- this.setAttribute('hidden', '');
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
+
174
+ _.removeAttribute('open');
175
+ _.setAttribute('aria-hidden', 'true');
176
+ _.setAttribute('inert', '');
134
177
  } else {
135
- // only animate if not set to auto
136
- if (this.style.height !== 'auto') {
137
- // animate to open
138
- this.style.height = `${this.scrollHeight}px`;
178
+ // only animate if not already auto
179
+ if (_.style.height !== 'auto') {
180
+ if (hasTransition) {
181
+ _.#animating = true;
182
+ }
183
+ _.style.height = `${_.scrollHeight}px`;
139
184
  }
140
185
 
141
- // remove accessibility attributes
142
- this.removeAttribute('aria-hidden');
143
- this.removeAttribute('inert');
144
- this.removeAttribute('hidden');
186
+ _.setAttribute('open', '');
187
+ _.removeAttribute('aria-hidden');
188
+ _.removeAttribute('inert');
145
189
  }
146
190
  }
147
191
 
148
192
  /**
149
- * gets the hidden state from the attribute
150
- * @return {boolean} whether element is hidden
151
- */
152
- get hidden() {
153
- return this.hasAttribute('hidden');
154
- }
155
-
156
- /**
157
- * handles the end of css transitions
158
- * @param {TransitionEvent} event - the transition event
193
+ * Gets the collapsed state
194
+ * @returns {boolean} Whether element is collapsed
159
195
  */
160
- _onTransitionEnd(event) {
161
- console.log('on transition end');
162
-
163
- // exit if isn't height property
164
- if (event.propertyName != 'height') return;
165
-
166
- // remove the inline height to allow dynamic content changes
167
- if (!this.hidden) {
168
- this.style.height = 'auto';
169
- }
196
+ get collapsed() {
197
+ return !this.hasAttribute('open');
170
198
  }
171
199
  }
172
200
 
173
- // register custom elements
174
- customElements.define('collapsible-content', CollapsibleContent);
175
- customElements.define('collapsible-component', CollapsibleComponent);
201
+ // register custom elements if not already defined
202
+ if (!customElements.get('collapsible-content')) {
203
+ customElements.define('collapsible-content', CollapsibleContent);
204
+ }
205
+ if (!customElements.get('collapsible-component')) {
206
+ customElements.define('collapsible-component', CollapsibleComponent);
207
+ }
176
208
 
177
209
  exports.CollapsibleComponent = CollapsibleComponent;
178
210
  exports.CollapsibleContent = CollapsibleContent;
@@ -1 +1 @@
1
- {"version":3,"file":"collapsible-content.cjs.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.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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"}
@@ -0,0 +1,31 @@
1
+ collapsible-component {
2
+ display: block;
3
+ }
4
+
5
+ collapsible-component button {
6
+ width: 100%;
7
+ text-align: left;
8
+ background: none;
9
+ border: none;
10
+ cursor: pointer;
11
+ }
12
+
13
+ collapsible-component button:focus-visible {
14
+ outline: 2px solid currentColor;
15
+ outline-offset: 2px;
16
+ }
17
+
18
+ collapsible-content {
19
+ --collapsible-duration: 0.35s;
20
+ --collapsible-easing: ease-out;
21
+
22
+ display: block;
23
+ overflow: hidden;
24
+ transition: height var(--collapsible-duration) var(--collapsible-easing);
25
+ }
26
+
27
+ @media (prefers-reduced-motion: reduce) {
28
+ collapsible-content {
29
+ transition: none;
30
+ }
31
+ }