@magic-spells/collapsible-content 0.1.4 → 0.2.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.
@@ -1,170 +1,175 @@
1
1
  /**
2
- * custom element that creates a collapsible/expandable component with proper accessibility
2
+ * Custom element that creates a collapsible/expandable component with proper accessibility
3
3
  */
4
4
  class CollapsibleComponent extends HTMLElement {
5
5
  #handleClick;
6
6
 
7
7
  /**
8
- * initializes the component and sets up references to child elements
8
+ * Initializes the component and sets up references to child elements
9
9
  */
10
10
  constructor() {
11
11
  super();
12
+ const _ = this;
12
13
 
13
14
  // store references to elements once to avoid re-querying
14
- this.button = null;
15
- this.content = null;
15
+ _.button = null;
16
+ _.content = null;
16
17
 
17
18
  // define click handler with proper this binding
18
- this.#handleClick = (e) => {
19
+ _.#handleClick = (e) => {
19
20
  e.preventDefault();
20
21
  // toggle expanded value
21
- const expanded = this.button.getAttribute('aria-expanded') !== 'true';
22
- this.button.setAttribute('aria-expanded', expanded);
23
- this.content.hidden = !expanded;
22
+ const expanded = _.button.getAttribute('aria-expanded') !== 'true';
23
+ _.button.setAttribute('aria-expanded', expanded);
24
+ _.content.hidden = !expanded;
24
25
  };
25
26
  }
26
27
 
27
28
  /**
28
- * called when element is added to the dom
29
- * sets up accessibility attributes and event listeners
29
+ * Called when element is added to the DOM
30
+ * Sets up accessibility attributes and event listeners
30
31
  */
31
32
  connectedCallback() {
33
+ const _ = this;
34
+
32
35
  // initialize element references once
33
- this.button = this.querySelector('button');
34
- this.content = this.querySelector('collapsible-content');
36
+ _.button = _.querySelector('button');
37
+ _.content = _.querySelector('collapsible-content');
35
38
 
36
- if (!this.button || !this.content) {
39
+ if (!_.button || !_.content) {
37
40
  console.error('CollapsibleComponent requires a <button> and a <collapsible-content>.');
38
41
  return;
39
42
  }
40
43
 
41
44
  // generate ids if not provided
42
- this.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
43
- this.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
45
+ _.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
46
+ _.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
44
47
 
45
48
  // set accessibility attributes
46
- this.button.setAttribute('aria-controls', this.content.id);
47
- this.content.setAttribute('role', 'region');
48
- this.content.setAttribute('aria-labelledby', this.button.id);
49
+ _.button.setAttribute('aria-controls', _.content.id);
50
+ _.content.setAttribute('role', 'region');
51
+ _.content.setAttribute('aria-labelledby', _.button.id);
49
52
 
50
53
  // set initial state based on aria-expanded attribute
51
- const expanded = this.button.getAttribute('aria-expanded') === 'true';
54
+ const expanded = _.button.getAttribute('aria-expanded') === 'true';
52
55
 
53
- // make shure content state matches button state
54
- this.content.hidden = !expanded;
56
+ // make sure content state matches button state
57
+ _.content.hidden = !expanded;
55
58
 
56
59
  // add event listener
57
- this.button.addEventListener('click', this.#handleClick);
60
+ _.button.addEventListener('click', _.#handleClick);
58
61
  }
59
62
 
60
63
  /**
61
- * called when element is removed from the dom
62
- * cleans up event listeners
64
+ * Called when element is removed from the DOM
65
+ * Cleans up event listeners
63
66
  */
64
67
  disconnectedCallback() {
65
- if (this.button) {
66
- this.button.removeEventListener('click', this.#handleClick);
68
+ const _ = this;
69
+ if (_.button) {
70
+ _.button.removeEventListener('click', _.#handleClick);
67
71
  }
68
72
  }
69
73
  }
70
74
 
71
75
  /**
72
- * custom element that provides animated collapsible content
76
+ * Custom element that provides animated collapsible content
73
77
  */
74
78
  class CollapsibleContent extends HTMLElement {
79
+ #handleTransitionEnd;
80
+
75
81
  /**
76
- * initializes the content element and binds event handlers
82
+ * Initializes the content element and binds event handlers
77
83
  */
78
84
  constructor() {
79
85
  super();
86
+ const _ = this;
80
87
 
81
- // bind event handler to this instance
82
- this._onTransitionEnd = this._onTransitionEnd.bind(this);
88
+ // define event handler using arrow function for proper binding
89
+ _.#handleTransitionEnd = (event) => {
90
+ // exit if isn't height property
91
+ if (event.propertyName != 'height') return;
92
+
93
+ // remove the inline height to allow dynamic content changes
94
+ if (!_.hidden) {
95
+ _.style.height = 'auto';
96
+ }
97
+ };
83
98
 
84
99
  // add event listener to remove height after transition
85
- this.addEventListener('transitionend', this._onTransitionEnd);
100
+ _.addEventListener('transitionend', _.#handleTransitionEnd);
86
101
  }
87
102
 
88
103
  /**
89
- * called when element is added to the dom
90
- * sets initial height based on hidden state
104
+ * Called when element is added to the DOM
105
+ * Sets initial height based on hidden state
91
106
  */
92
107
  connectedCallback() {
93
- // get aria-expanded state from buton
94
- const component = this.closest('collapsible-component');
108
+ const _ = this;
109
+
110
+ // get aria-expanded state from button
111
+ const component = _.closest('collapsible-component');
95
112
  const button = component.querySelector('button');
96
113
  const expanded = button.getAttribute('aria-expanded') === 'true';
97
114
 
98
115
  if (expanded) {
99
- this.style.height = 'auto';
116
+ _.style.height = 'auto';
100
117
  } else {
101
- this.style.height = '0';
118
+ _.style.height = '0';
102
119
  }
103
120
  }
104
121
 
105
122
  /**
106
- * called when element is removed from the dom
107
- * cleans up event listeners
123
+ * Called when element is removed from the DOM
124
+ * Cleans up event listeners
108
125
  */
109
126
  disconnectedCallback() {
110
- this.removeEventListener('transitionend', this._onTransitionEnd);
127
+ const _ = this;
128
+ _.removeEventListener('transitionend', _.#handleTransitionEnd);
111
129
  }
112
130
 
113
131
  /**
114
- * handles setting the hidden attribute with animation
115
- * @param {boolean} value - whether element should be hidden
132
+ * Handles setting the hidden attribute with animation
133
+ * @param {boolean} value - Whether element should be hidden
116
134
  */
117
135
  set hidden(value) {
136
+ const _ = this;
137
+
118
138
  // animate to closed
119
139
  if (value) {
120
140
  // reset height to animate from
121
- this.style.height = `${this.scrollHeight}px`;
141
+ _.style.height = `${_.scrollHeight}px`;
122
142
 
123
143
  // wait one frame and animate to 0
124
144
  setTimeout(() => {
125
- this.style.height = '0px';
145
+ _.style.height = '0px';
126
146
  }, 1);
127
147
 
128
148
  // set accessibility attributes
129
- this.setAttribute('aria-hidden', 'true');
130
- this.setAttribute('inert', '');
131
- this.setAttribute('hidden', '');
149
+ _.setAttribute('aria-hidden', 'true');
150
+ _.setAttribute('inert', '');
151
+ _.setAttribute('hidden', '');
132
152
  } else {
133
153
  // only animate if not set to auto
134
- if (this.style.height !== 'auto') {
154
+ if (_.style.height !== 'auto') {
135
155
  // animate to open
136
- this.style.height = `${this.scrollHeight}px`;
156
+ _.style.height = `${_.scrollHeight}px`;
137
157
  }
138
158
 
139
159
  // remove accessibility attributes
140
- this.removeAttribute('aria-hidden');
141
- this.removeAttribute('inert');
142
- this.removeAttribute('hidden');
160
+ _.removeAttribute('aria-hidden');
161
+ _.removeAttribute('inert');
162
+ _.removeAttribute('hidden');
143
163
  }
144
164
  }
145
165
 
146
166
  /**
147
- * gets the hidden state from the attribute
148
- * @return {boolean} whether element is hidden
167
+ * Gets the hidden state from the attribute
168
+ * @returns {boolean} Whether element is hidden
149
169
  */
150
170
  get hidden() {
151
- return this.hasAttribute('hidden');
152
- }
153
-
154
- /**
155
- * handles the end of css transitions
156
- * @param {TransitionEvent} event - the transition event
157
- */
158
- _onTransitionEnd(event) {
159
- console.log('on transition end');
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
- }
171
+ const _ = this;
172
+ return _.hasAttribute('hidden');
168
173
  }
169
174
  }
170
175
 
@@ -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 './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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"}
@@ -0,0 +1,190 @@
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
+
13
+ /**
14
+ * Initializes the component and sets up references to child elements
15
+ */
16
+ constructor() {
17
+ super();
18
+ const _ = this;
19
+
20
+ // store references to elements once to avoid re-querying
21
+ _.button = null;
22
+ _.content = null;
23
+
24
+ // define click handler with proper this binding
25
+ _.#handleClick = (e) => {
26
+ e.preventDefault();
27
+ // toggle expanded value
28
+ const expanded = _.button.getAttribute('aria-expanded') !== 'true';
29
+ _.button.setAttribute('aria-expanded', expanded);
30
+ _.content.hidden = !expanded;
31
+ };
32
+ }
33
+
34
+ /**
35
+ * Called when element is added to the DOM
36
+ * Sets up accessibility attributes and event listeners
37
+ */
38
+ connectedCallback() {
39
+ const _ = this;
40
+
41
+ // initialize element references once
42
+ _.button = _.querySelector('button');
43
+ _.content = _.querySelector('collapsible-content');
44
+
45
+ if (!_.button || !_.content) {
46
+ console.error('CollapsibleComponent requires a <button> and a <collapsible-content>.');
47
+ return;
48
+ }
49
+
50
+ // generate ids if not provided
51
+ _.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
52
+ _.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
53
+
54
+ // set accessibility attributes
55
+ _.button.setAttribute('aria-controls', _.content.id);
56
+ _.content.setAttribute('role', 'region');
57
+ _.content.setAttribute('aria-labelledby', _.button.id);
58
+
59
+ // set initial state based on aria-expanded attribute
60
+ const expanded = _.button.getAttribute('aria-expanded') === 'true';
61
+
62
+ // make sure content state matches button state
63
+ _.content.hidden = !expanded;
64
+
65
+ // add event listener
66
+ _.button.addEventListener('click', _.#handleClick);
67
+ }
68
+
69
+ /**
70
+ * Called when element is removed from the DOM
71
+ * Cleans up event listeners
72
+ */
73
+ disconnectedCallback() {
74
+ const _ = this;
75
+ if (_.button) {
76
+ _.button.removeEventListener('click', _.#handleClick);
77
+ }
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Custom element that provides animated collapsible content
83
+ */
84
+ class CollapsibleContent extends HTMLElement {
85
+ #handleTransitionEnd;
86
+
87
+ /**
88
+ * Initializes the content element and binds event handlers
89
+ */
90
+ constructor() {
91
+ super();
92
+ const _ = this;
93
+
94
+ // define event handler using arrow function for proper binding
95
+ _.#handleTransitionEnd = (event) => {
96
+ // exit if isn't height property
97
+ if (event.propertyName != 'height') return;
98
+
99
+ // remove the inline height to allow dynamic content changes
100
+ if (!_.hidden) {
101
+ _.style.height = 'auto';
102
+ }
103
+ };
104
+
105
+ // add event listener to remove height after transition
106
+ _.addEventListener('transitionend', _.#handleTransitionEnd);
107
+ }
108
+
109
+ /**
110
+ * Called when element is added to the DOM
111
+ * Sets initial height based on hidden state
112
+ */
113
+ connectedCallback() {
114
+ const _ = this;
115
+
116
+ // get aria-expanded state from button
117
+ const component = _.closest('collapsible-component');
118
+ const button = component.querySelector('button');
119
+ const expanded = button.getAttribute('aria-expanded') === 'true';
120
+
121
+ if (expanded) {
122
+ _.style.height = 'auto';
123
+ } else {
124
+ _.style.height = '0';
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Called when element is removed from the DOM
130
+ * Cleans up event listeners
131
+ */
132
+ disconnectedCallback() {
133
+ const _ = this;
134
+ _.removeEventListener('transitionend', _.#handleTransitionEnd);
135
+ }
136
+
137
+ /**
138
+ * Handles setting the hidden attribute with animation
139
+ * @param {boolean} value - Whether element should be hidden
140
+ */
141
+ set hidden(value) {
142
+ const _ = this;
143
+
144
+ // animate to closed
145
+ if (value) {
146
+ // reset height to animate from
147
+ _.style.height = `${_.scrollHeight}px`;
148
+
149
+ // wait one frame and animate to 0
150
+ setTimeout(() => {
151
+ _.style.height = '0px';
152
+ }, 1);
153
+
154
+ // set accessibility attributes
155
+ _.setAttribute('aria-hidden', 'true');
156
+ _.setAttribute('inert', '');
157
+ _.setAttribute('hidden', '');
158
+ } else {
159
+ // only animate if not set to auto
160
+ if (_.style.height !== 'auto') {
161
+ // animate to open
162
+ _.style.height = `${_.scrollHeight}px`;
163
+ }
164
+
165
+ // remove accessibility attributes
166
+ _.removeAttribute('aria-hidden');
167
+ _.removeAttribute('inert');
168
+ _.removeAttribute('hidden');
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Gets the hidden state from the attribute
174
+ * @returns {boolean} Whether element is hidden
175
+ */
176
+ get hidden() {
177
+ const _ = this;
178
+ return _.hasAttribute('hidden');
179
+ }
180
+ }
181
+
182
+ // register custom elements
183
+ customElements.define('collapsible-content', CollapsibleContent);
184
+ customElements.define('collapsible-component', CollapsibleComponent);
185
+
186
+ exports.CollapsibleComponent = CollapsibleComponent;
187
+ exports.CollapsibleContent = CollapsibleContent;
188
+
189
+ }));
190
+ //# sourceMappingURL=collapsible-content.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collapsible-content.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 +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;padding:0;transition:height .35s ease-out}collapsible-component collapsible-content[hidden]{display:block}
1
+ :root{--cc-component-display:block;--cc-button-width:100%;--cc-button-text-align:left;--cc-button-background:none;--cc-button-border:none;--cc-button-cursor:pointer;--cc-content-padding:0px;--cc-content-display:block;--cc-content-overflow:hidden;--cc-transition-duration:0.35s;--cc-transition-timing:ease-out}collapsible-component{display:var(--cc-component-display,block)}collapsible-component button{background:var(--cc-button-background,none);border:var(--cc-button-border,none);cursor:var(--cc-button-cursor,pointer);text-align:var(--cc-button-text-align,left);width:var(--cc-button-width,100%)}collapsible-component collapsible-content[hidden],collapsible-content{display:var(--cc-content-display,block)}collapsible-content{overflow:var(--cc-content-overflow,hidden);padding:var(--cc-content-padding,0);transition:height var(--cc-transition-duration,.35s) var(--cc-transition-timing,ease-out)}
@@ -1 +1 @@
1
- var CollapsibleContent=function(t){"use strict";class CollapsibleComponent extends HTMLElement{#t;constructor(){super(),this.button=null,this.content=null,this.#t=t=>{t.preventDefault();const e="true"!==this.button.getAttribute("aria-expanded");this.button.setAttribute("aria-expanded",e),this.content.hidden=!e}}connectedCallback(){if(this.button=this.querySelector("button"),this.content=this.querySelector("collapsible-content"),!this.button||!this.content)return void console.error("CollapsibleComponent requires a <button> and a <collapsible-content>.");this.button.id||=`collapsible-button-${crypto.randomUUID().slice(0,8)}`,this.content.id||=`collapsible-content-${crypto.randomUUID().slice(0,8)}`,this.button.setAttribute("aria-controls",this.content.id),this.content.setAttribute("role","region"),this.content.setAttribute("aria-labelledby",this.button.id);const t="true"===this.button.getAttribute("aria-expanded");this.content.hidden=!t,this.button.addEventListener("click",this.#t)}disconnectedCallback(){this.button&&this.button.removeEventListener("click",this.#t)}}class CollapsibleContent extends HTMLElement{constructor(){super(),this._onTransitionEnd=this._onTransitionEnd.bind(this),this.addEventListener("transitionend",this._onTransitionEnd)}connectedCallback(){const t="true"===this.closest("collapsible-component").querySelector("button").getAttribute("aria-expanded");this.style.height=t?"auto":"0"}disconnectedCallback(){this.removeEventListener("transitionend",this._onTransitionEnd)}set hidden(t){t?(this.style.height=`${this.scrollHeight}px`,setTimeout((()=>{this.style.height="0px"}),1),this.setAttribute("aria-hidden","true"),this.setAttribute("inert",""),this.setAttribute("hidden","")):("auto"!==this.style.height&&(this.style.height=`${this.scrollHeight}px`),this.removeAttribute("aria-hidden"),this.removeAttribute("inert"),this.removeAttribute("hidden"))}get hidden(){return this.hasAttribute("hidden")}_onTransitionEnd(t){console.log("on transition end"),"height"==t.propertyName&&(this.hidden||(this.style.height="auto"))}}return customElements.define("collapsible-content",CollapsibleContent),customElements.define("collapsible-component",CollapsibleComponent),t.CollapsibleComponent=CollapsibleComponent,t.CollapsibleContent=CollapsibleContent,t}({});
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;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.hidden=!n}}connectedCallback(){const t=this;if(t.button=t.querySelector("button"),t.content=t.querySelector("collapsible-content"),!t.button||!t.content)return void console.error("CollapsibleComponent requires a <button> and a <collapsible-content>.");t.button.id||=`collapsible-button-${crypto.randomUUID().slice(0,8)}`,t.content.id||=`collapsible-content-${crypto.randomUUID().slice(0,8)}`,t.button.setAttribute("aria-controls",t.content.id),t.content.setAttribute("role","region"),t.content.setAttribute("aria-labelledby",t.button.id);const e="true"===t.button.getAttribute("aria-expanded");t.content.hidden=!e,t.button.addEventListener("click",t.#t)}disconnectedCallback(){const t=this;t.button&&t.button.removeEventListener("click",t.#t)}}class CollapsibleContent extends HTMLElement{#e;constructor(){super();const t=this;t.#e=e=>{"height"==e.propertyName&&(t.hidden||(t.style.height="auto"))},t.addEventListener("transitionend",t.#e)}connectedCallback(){const t=this,e="true"===t.closest("collapsible-component").querySelector("button").getAttribute("aria-expanded");t.style.height=e?"auto":"0"}disconnectedCallback(){this.removeEventListener("transitionend",this.#e)}set hidden(t){const e=this;t?(e.style.height=`${e.scrollHeight}px`,setTimeout((()=>{e.style.height="0px"}),1),e.setAttribute("aria-hidden","true"),e.setAttribute("inert",""),e.setAttribute("hidden","")):("auto"!==e.style.height&&(e.style.height=`${e.scrollHeight}px`),e.removeAttribute("aria-hidden"),e.removeAttribute("inert"),e.removeAttribute("hidden"))}get hidden(){return this.hasAttribute("hidden")}}customElements.define("collapsible-content",CollapsibleContent),customElements.define("collapsible-component",CollapsibleComponent),t.CollapsibleComponent=CollapsibleComponent,t.CollapsibleContent=CollapsibleContent}));
@@ -0,0 +1,2 @@
1
+ @forward "scss/variables";
2
+ @forward "scss/collapsible-content";
@@ -0,0 +1,25 @@
1
+ // Import variables using the modern @use rule
2
+ @use 'variables' as vars;
3
+
4
+ collapsible-component {
5
+ display: var(--cc-component-display, block);
6
+
7
+ button {
8
+ width: var(--cc-button-width, 100%);
9
+ text-align: var(--cc-button-text-align, left);
10
+ background: var(--cc-button-background, none);
11
+ border: var(--cc-button-border, none);
12
+ cursor: var(--cc-button-cursor, pointer);
13
+ }
14
+
15
+ collapsible-content[hidden] {
16
+ display: var(--cc-content-display, block);
17
+ }
18
+ }
19
+
20
+ collapsible-content {
21
+ padding: var(--cc-content-padding, 0px);
22
+ display: var(--cc-content-display, block);
23
+ overflow: var(--cc-content-overflow, hidden);
24
+ transition: height var(--cc-transition-duration, 0.35s) var(--cc-transition-timing, ease-out);
25
+ }
@@ -0,0 +1,40 @@
1
+ // Collapsible Content variables
2
+ // SCSS variables for internal component use and customization
3
+ // these variables can be customized by the user
4
+
5
+ // layout
6
+ $component-display: block !default;
7
+ $button-width: 100% !default;
8
+ $button-text-align: left !default;
9
+
10
+ // appearance
11
+ $button-background: none !default;
12
+ $button-border: none !default;
13
+ $button-cursor: pointer !default;
14
+
15
+ // animation
16
+ $content-padding: 0px !default;
17
+ $content-display: block !default;
18
+ $content-overflow: hidden !default;
19
+ $transition-duration: 0.35s !default;
20
+ $transition-timing: ease-out !default;
21
+
22
+ // Define CSS Custom Properties using SCSS values
23
+ :root {
24
+ // layout
25
+ --cc-component-display: #{$component-display};
26
+ --cc-button-width: #{$button-width};
27
+ --cc-button-text-align: #{$button-text-align};
28
+
29
+ // appearance
30
+ --cc-button-background: #{$button-background};
31
+ --cc-button-border: #{$button-border};
32
+ --cc-button-cursor: #{$button-cursor};
33
+
34
+ // animation
35
+ --cc-content-padding: #{$content-padding};
36
+ --cc-content-display: #{$content-display};
37
+ --cc-content-overflow: #{$content-overflow};
38
+ --cc-transition-duration: #{$transition-duration};
39
+ --cc-transition-timing: #{$transition-timing};
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-spells/collapsible-content",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Collapsible content web component",
5
5
  "author": "Cory Schulz",
6
6
  "license": "MIT",
@@ -9,13 +9,17 @@
9
9
  "module": "dist/collapsible-content.esm.js",
10
10
  "unpkg": "dist/collapsible-content.min.js",
11
11
  "style": "dist/collapsible-content.min.css",
12
+ "sass": "dist/collapsible-content.scss",
12
13
  "exports": {
13
14
  ".": {
14
15
  "import": "./dist/collapsible-content.esm.js",
15
16
  "require": "./dist/collapsible-content.cjs.js",
16
17
  "default": "./dist/collapsible-content.esm.js"
17
18
  },
18
- "./style.css": "./dist/collapsible-content.min.css"
19
+ "./css": "./dist/collapsible-content.css",
20
+ "./css/min": "./dist/collapsible-content.min.css",
21
+ "./scss": "./dist/collapsible-content.scss",
22
+ "./scss/*": "./dist/scss/*"
19
23
  },
20
24
  "sideEffects": true,
21
25
  "repository": {
@@ -61,11 +65,12 @@
61
65
  "@rollup/plugin-terser": "^0.4.4",
62
66
  "eslint": "^8.0.0",
63
67
  "globals": "^13.24.0",
68
+ "postcss-lightningcss": "^1.0.1",
64
69
  "prettier": "^3.3.3",
65
70
  "rollup": "^3.0.0",
66
71
  "rollup-plugin-copy": "^3.5.0",
67
72
  "rollup-plugin-postcss": "^4.0.2",
68
73
  "rollup-plugin-serve": "^1.1.1",
69
- "postcss-lightningcss": "^1.0.1"
74
+ "sass": "^1.86.3"
70
75
  }
71
76
  }