@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,172 +1,177 @@
1
- import './styles.css';
1
+ import './index.scss';
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
8
 
9
9
  /**
10
- * initializes the component and sets up references to child elements
10
+ * Initializes the component and sets up references to child elements
11
11
  */
12
12
  constructor() {
13
13
  super();
14
+ const _ = this;
14
15
 
15
16
  // store references to elements once to avoid re-querying
16
- this.button = null;
17
- this.content = null;
17
+ _.button = null;
18
+ _.content = null;
18
19
 
19
20
  // define click handler with proper this binding
20
- this.#handleClick = (e) => {
21
+ _.#handleClick = (e) => {
21
22
  e.preventDefault();
22
23
  // toggle expanded value
23
- const expanded = this.button.getAttribute('aria-expanded') !== 'true';
24
- this.button.setAttribute('aria-expanded', expanded);
25
- this.content.hidden = !expanded;
24
+ const expanded = _.button.getAttribute('aria-expanded') !== 'true';
25
+ _.button.setAttribute('aria-expanded', expanded);
26
+ _.content.hidden = !expanded;
26
27
  };
27
28
  }
28
29
 
29
30
  /**
30
- * called when element is added to the dom
31
- * sets up accessibility attributes and event listeners
31
+ * Called when element is added to the DOM
32
+ * Sets up accessibility attributes and event listeners
32
33
  */
33
34
  connectedCallback() {
35
+ const _ = this;
36
+
34
37
  // initialize element references once
35
- this.button = this.querySelector('button');
36
- this.content = this.querySelector('collapsible-content');
38
+ _.button = _.querySelector('button');
39
+ _.content = _.querySelector('collapsible-content');
37
40
 
38
- if (!this.button || !this.content) {
41
+ if (!_.button || !_.content) {
39
42
  console.error('CollapsibleComponent requires a <button> and a <collapsible-content>.');
40
43
  return;
41
44
  }
42
45
 
43
46
  // 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)}`;
47
+ _.button.id ||= `collapsible-button-${crypto.randomUUID().slice(0, 8)}`;
48
+ _.content.id ||= `collapsible-content-${crypto.randomUUID().slice(0, 8)}`;
46
49
 
47
50
  // 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
+ _.button.setAttribute('aria-controls', _.content.id);
52
+ _.content.setAttribute('role', 'region');
53
+ _.content.setAttribute('aria-labelledby', _.button.id);
51
54
 
52
55
  // set initial state based on aria-expanded attribute
53
- const expanded = this.button.getAttribute('aria-expanded') === 'true';
56
+ const expanded = _.button.getAttribute('aria-expanded') === 'true';
54
57
 
55
- // make shure content state matches button state
56
- this.content.hidden = !expanded;
58
+ // make sure content state matches button state
59
+ _.content.hidden = !expanded;
57
60
 
58
61
  // add event listener
59
- this.button.addEventListener('click', this.#handleClick);
62
+ _.button.addEventListener('click', _.#handleClick);
60
63
  }
61
64
 
62
65
  /**
63
- * called when element is removed from the dom
64
- * cleans up event listeners
66
+ * Called when element is removed from the DOM
67
+ * Cleans up event listeners
65
68
  */
66
69
  disconnectedCallback() {
67
- if (this.button) {
68
- this.button.removeEventListener('click', this.#handleClick);
70
+ const _ = this;
71
+ if (_.button) {
72
+ _.button.removeEventListener('click', _.#handleClick);
69
73
  }
70
74
  }
71
75
  }
72
76
 
73
77
  /**
74
- * custom element that provides animated collapsible content
78
+ * Custom element that provides animated collapsible content
75
79
  */
76
80
  class CollapsibleContent extends HTMLElement {
81
+ #handleTransitionEnd;
82
+
77
83
  /**
78
- * initializes the content element and binds event handlers
84
+ * Initializes the content element and binds event handlers
79
85
  */
80
86
  constructor() {
81
87
  super();
88
+ const _ = this;
82
89
 
83
- // bind event handler to this instance
84
- this._onTransitionEnd = this._onTransitionEnd.bind(this);
90
+ // define event handler using arrow function for proper binding
91
+ _.#handleTransitionEnd = (event) => {
92
+ // exit if isn't height property
93
+ if (event.propertyName != 'height') return;
94
+
95
+ // remove the inline height to allow dynamic content changes
96
+ if (!_.hidden) {
97
+ _.style.height = 'auto';
98
+ }
99
+ };
85
100
 
86
101
  // add event listener to remove height after transition
87
- this.addEventListener('transitionend', this._onTransitionEnd);
102
+ _.addEventListener('transitionend', _.#handleTransitionEnd);
88
103
  }
89
104
 
90
105
  /**
91
- * called when element is added to the dom
92
- * sets initial height based on hidden state
106
+ * Called when element is added to the DOM
107
+ * Sets initial height based on hidden state
93
108
  */
94
109
  connectedCallback() {
95
- // get aria-expanded state from buton
96
- const component = this.closest('collapsible-component');
110
+ const _ = this;
111
+
112
+ // get aria-expanded state from button
113
+ const component = _.closest('collapsible-component');
97
114
  const button = component.querySelector('button');
98
115
  const expanded = button.getAttribute('aria-expanded') === 'true';
99
116
 
100
117
  if (expanded) {
101
- this.style.height = 'auto';
118
+ _.style.height = 'auto';
102
119
  } else {
103
- this.style.height = '0';
120
+ _.style.height = '0';
104
121
  }
105
122
  }
106
123
 
107
124
  /**
108
- * called when element is removed from the dom
109
- * cleans up event listeners
125
+ * Called when element is removed from the DOM
126
+ * Cleans up event listeners
110
127
  */
111
128
  disconnectedCallback() {
112
- this.removeEventListener('transitionend', this._onTransitionEnd);
129
+ const _ = this;
130
+ _.removeEventListener('transitionend', _.#handleTransitionEnd);
113
131
  }
114
132
 
115
133
  /**
116
- * handles setting the hidden attribute with animation
117
- * @param {boolean} value - whether element should be hidden
134
+ * Handles setting the hidden attribute with animation
135
+ * @param {boolean} value - Whether element should be hidden
118
136
  */
119
137
  set hidden(value) {
138
+ const _ = this;
139
+
120
140
  // animate to closed
121
141
  if (value) {
122
142
  // reset height to animate from
123
- this.style.height = `${this.scrollHeight}px`;
143
+ _.style.height = `${_.scrollHeight}px`;
124
144
 
125
145
  // wait one frame and animate to 0
126
146
  setTimeout(() => {
127
- this.style.height = '0px';
147
+ _.style.height = '0px';
128
148
  }, 1);
129
149
 
130
150
  // set accessibility attributes
131
- this.setAttribute('aria-hidden', 'true');
132
- this.setAttribute('inert', '');
133
- this.setAttribute('hidden', '');
151
+ _.setAttribute('aria-hidden', 'true');
152
+ _.setAttribute('inert', '');
153
+ _.setAttribute('hidden', '');
134
154
  } else {
135
155
  // only animate if not set to auto
136
- if (this.style.height !== 'auto') {
156
+ if (_.style.height !== 'auto') {
137
157
  // animate to open
138
- this.style.height = `${this.scrollHeight}px`;
158
+ _.style.height = `${_.scrollHeight}px`;
139
159
  }
140
160
 
141
161
  // remove accessibility attributes
142
- this.removeAttribute('aria-hidden');
143
- this.removeAttribute('inert');
144
- this.removeAttribute('hidden');
162
+ _.removeAttribute('aria-hidden');
163
+ _.removeAttribute('inert');
164
+ _.removeAttribute('hidden');
145
165
  }
146
166
  }
147
167
 
148
168
  /**
149
- * gets the hidden state from the attribute
150
- * @return {boolean} whether element is hidden
169
+ * Gets the hidden state from the attribute
170
+ * @returns {boolean} Whether element is hidden
151
171
  */
152
172
  get hidden() {
153
- return this.hasAttribute('hidden');
154
- }
155
-
156
- /**
157
- * handles the end of css transitions
158
- * @param {TransitionEvent} event - the transition event
159
- */
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
- }
173
+ const _ = this;
174
+ return _.hasAttribute('hidden');
170
175
  }
171
176
  }
172
177
 
package/src/index.scss ADDED
@@ -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/src/styles.css DELETED
@@ -1,22 +0,0 @@
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-content {
14
- padding: 0px;
15
- display: block;
16
- overflow: hidden;
17
- transition: height 0.35s ease-out;
18
- }
19
-
20
- collapsible-component collapsible-content[hidden] {
21
- display: block;
22
- }