@magic-spells/dialog-panel 0.2.0 → 0.2.3

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.
@@ -8,21 +8,24 @@ import '@magic-spells/focus-trap';
8
8
  class DialogPanel extends HTMLElement {
9
9
  #handleTransitionEnd;
10
10
  #scrollPosition = 0;
11
-
11
+
12
12
  /**
13
13
  * Clean up event listeners when component is removed from DOM
14
14
  */
15
15
  disconnectedCallback() {
16
16
  const _ = this;
17
17
  if (_.contentPanel) {
18
- _.contentPanel.removeEventListener('transitionend', _.#handleTransitionEnd);
18
+ _.contentPanel.removeEventListener(
19
+ 'transitionend',
20
+ _.#handleTransitionEnd
21
+ );
19
22
  }
20
-
23
+
21
24
  // Ensure body scroll is restored if component is removed while open
22
25
  document.body.classList.remove('overflow-hidden');
23
26
  this.#restoreScroll();
24
27
  }
25
-
28
+
26
29
  /**
27
30
  * Saves current scroll position and locks body scrolling
28
31
  * @private
@@ -31,12 +34,12 @@ class DialogPanel extends HTMLElement {
31
34
  const _ = this;
32
35
  // Save current scroll position
33
36
  _.#scrollPosition = window.pageYOffset;
34
-
37
+
35
38
  // Apply fixed position to body
36
39
  document.body.classList.add('overflow-hidden');
37
40
  document.body.style.top = `-${_.#scrollPosition}px`;
38
41
  }
39
-
42
+
40
43
  /**
41
44
  * Restores scroll position when dialog is closed
42
45
  * @private
@@ -46,7 +49,7 @@ class DialogPanel extends HTMLElement {
46
49
  // Remove fixed positioning
47
50
  document.body.classList.remove('overflow-hidden');
48
51
  document.body.style.removeProperty('top');
49
-
52
+
50
53
  // Restore scroll position
51
54
  window.scrollTo(0, _.#scrollPosition);
52
55
  }
@@ -64,17 +67,22 @@ class DialogPanel extends HTMLElement {
64
67
  _.contentPanel = _.querySelector('dialog-content');
65
68
  _.focusTrap = document.createElement('focus-trap');
66
69
  _.triggerEl = null;
67
-
70
+
68
71
  // Create a handler for transition end events
69
72
  _.#handleTransitionEnd = (e) => {
70
- if (e.propertyName === 'opacity' && _.getAttribute('aria-hidden') === 'true') {
73
+ if (
74
+ e.propertyName === 'opacity' &&
75
+ _.getAttribute('aria-hidden') === 'true'
76
+ ) {
71
77
  _.contentPanel.classList.add('hidden');
72
-
78
+
73
79
  // Dispatch afterHide event - dialog has completed its transition
74
- _.dispatchEvent(new CustomEvent('afterHide', {
75
- bubbles: true,
76
- detail: { triggerElement: _.triggerEl }
77
- }));
80
+ _.dispatchEvent(
81
+ new CustomEvent('afterHide', {
82
+ bubbles: true,
83
+ detail: { triggerElement: _.triggerEl },
84
+ })
85
+ );
78
86
  }
79
87
  };
80
88
 
@@ -109,12 +117,10 @@ class DialogPanel extends HTMLElement {
109
117
  */
110
118
  #bindUI() {
111
119
  const _ = this;
112
-
120
+
113
121
  // Handle trigger buttons
114
122
  document.addEventListener('click', (e) => {
115
- const trigger = e.target.closest(
116
- `[aria-controls="${_.id}"]`
117
- );
123
+ const trigger = e.target.closest(`[aria-controls="${_.id}"]`);
118
124
  if (!trigger) return;
119
125
 
120
126
  if (trigger.getAttribute('data-prevent-default') === 'true') {
@@ -129,9 +135,12 @@ class DialogPanel extends HTMLElement {
129
135
  if (!e.target.closest('[data-action="hide-dialog"]')) return;
130
136
  _.hide();
131
137
  });
132
-
138
+
133
139
  // Add transition end listener
134
- _.contentPanel.addEventListener('transitionend', _.#handleTransitionEnd);
140
+ _.contentPanel.addEventListener(
141
+ 'transitionend',
142
+ _.#handleTransitionEnd
143
+ );
135
144
  }
136
145
 
137
146
  /**
@@ -161,17 +170,17 @@ class DialogPanel extends HTMLElement {
161
170
  const beforeShowEvent = new CustomEvent('beforeShow', {
162
171
  bubbles: true,
163
172
  cancelable: true,
164
- detail: { triggerElement: _.triggerEl }
173
+ detail: { triggerElement: _.triggerEl },
165
174
  });
166
-
175
+
167
176
  const showAllowed = _.dispatchEvent(beforeShowEvent);
168
-
177
+
169
178
  // If event was canceled (preventDefault was called), don't show the dialog
170
179
  if (!showAllowed) return false;
171
180
 
172
181
  // Remove the hidden class first to ensure content is rendered
173
182
  _.contentPanel.classList.remove('hidden');
174
-
183
+
175
184
  // Give the browser a moment to process before starting animation
176
185
  requestAnimationFrame(() => {
177
186
  // Update ARIA states
@@ -179,10 +188,10 @@ class DialogPanel extends HTMLElement {
179
188
  if (_.triggerEl) {
180
189
  _.triggerEl.setAttribute('aria-expanded', 'true');
181
190
  }
182
-
191
+
183
192
  // Lock body scrolling and save scroll position
184
193
  _.#lockScroll();
185
-
194
+
186
195
  // Focus management
187
196
  const firstFocusable = _.querySelector(
188
197
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
@@ -192,14 +201,16 @@ class DialogPanel extends HTMLElement {
192
201
  firstFocusable.focus();
193
202
  });
194
203
  }
195
-
204
+
196
205
  // Dispatch show event - dialog is now visible
197
- _.dispatchEvent(new CustomEvent('show', {
198
- bubbles: true,
199
- detail: { triggerElement: _.triggerEl }
200
- }));
206
+ _.dispatchEvent(
207
+ new CustomEvent('show', {
208
+ bubbles: true,
209
+ detail: { triggerElement: _.triggerEl },
210
+ })
211
+ );
201
212
  });
202
-
213
+
203
214
  return true;
204
215
  }
205
216
 
@@ -212,19 +223,19 @@ class DialogPanel extends HTMLElement {
212
223
  */
213
224
  hide() {
214
225
  const _ = this;
215
-
226
+
216
227
  // Dispatch beforeHide event - allows preventing the dialog from closing
217
228
  const beforeHideEvent = new CustomEvent('beforeHide', {
218
229
  bubbles: true,
219
230
  cancelable: true,
220
- detail: { triggerElement: _.triggerEl }
231
+ detail: { triggerElement: _.triggerEl },
221
232
  });
222
-
233
+
223
234
  const hideAllowed = _.dispatchEvent(beforeHideEvent);
224
-
235
+
225
236
  // If event was canceled (preventDefault was called), don't hide the dialog
226
237
  if (!hideAllowed) return false;
227
-
238
+
228
239
  // Restore body scroll and scroll position
229
240
  _.#restoreScroll();
230
241
 
@@ -239,13 +250,15 @@ class DialogPanel extends HTMLElement {
239
250
  // Set aria-hidden to start transition
240
251
  // The transitionend event handler will add display:none when complete
241
252
  _.setAttribute('aria-hidden', 'true');
242
-
253
+
243
254
  // Dispatch hide event - dialog is now starting to hide
244
- _.dispatchEvent(new CustomEvent('hide', {
245
- bubbles: true,
246
- detail: { triggerElement: _.triggerEl }
247
- }));
248
-
255
+ _.dispatchEvent(
256
+ new CustomEvent('hide', {
257
+ bubbles: true,
258
+ detail: { triggerElement: _.triggerEl },
259
+ })
260
+ );
261
+
249
262
  return true;
250
263
  }
251
264
  }
@@ -281,9 +294,15 @@ class DialogContent extends HTMLElement {
281
294
  }
282
295
  }
283
296
 
284
- customElements.define('dialog-panel', DialogPanel);
285
- customElements.define('dialog-overlay', DialogOverlay);
286
- customElements.define('dialog-content', DialogContent);
297
+ if (!customElements.get('dialog-panel')) {
298
+ customElements.define('dialog-panel', DialogPanel);
299
+ }
300
+ if (!customElements.get('dialog-overlay')) {
301
+ customElements.define('dialog-overlay', DialogOverlay);
302
+ }
303
+ if (!customElements.get('dialog-content')) {
304
+ customElements.define('dialog-content', DialogContent);
305
+ }
287
306
 
288
307
  export { DialogPanel, DialogOverlay, DialogContent };
289
- export default DialogPanel;
308
+ export default DialogPanel;
package/src/index.scss CHANGED
@@ -1,2 +1,2 @@
1
- @forward "scss/variables";
2
- @forward "scss/dialog-panel";
1
+ @forward 'scss/variables';
2
+ @forward 'scss/dialog-panel';
@@ -2,72 +2,72 @@
2
2
  @use 'variables' as vars;
3
3
 
4
4
  dialog-panel {
5
- /* Make it take no space and be invisible in the document flow */
6
- display: contents;
5
+ /* Make it take no space and be invisible in the document flow */
6
+ display: contents;
7
7
 
8
- &[aria-hidden='false'] {
9
- dialog-overlay,
10
- dialog-content {
11
- pointer-events: auto;
12
- opacity: 1;
13
- transform: scale(1);
14
- filter: blur(0px);
15
- }
16
- }
8
+ &[aria-hidden='false'] {
9
+ dialog-overlay,
10
+ dialog-content {
11
+ pointer-events: auto;
12
+ opacity: 1;
13
+ transform: scale(1);
14
+ filter: blur(0px);
15
+ }
16
+ }
17
17
  }
18
18
 
19
19
  /* Overlay background */
20
20
  dialog-overlay {
21
- position: fixed;
22
- top: 0;
23
- left: 0;
24
- width: 100vw;
25
- height: 100vh;
26
- opacity: 0;
27
- pointer-events: none;
28
- z-index: var(--dp-overlay-z-index, 1000);
29
- transition: var(--dp-overlay-transition, all 300ms ease-out);
30
- background-color: var(
31
- --dp-overlay-background,
32
- rgba(20, 23, 26, 0.4)
33
- );
34
- backdrop-filter: var(
35
- --dp-overlay-backdrop-filter,
36
- blur(2px) saturate(120%)
37
- );
21
+ position: fixed;
22
+ top: 0;
23
+ left: 0;
24
+ width: 100vw;
25
+ height: 100vh;
26
+ opacity: 0;
27
+ pointer-events: none;
28
+ z-index: var(--dp-overlay-z-index, 1000);
29
+ transition: var(--dp-overlay-transition, all 300ms ease-out);
30
+ background-color: var(
31
+ --dp-overlay-background,
32
+ rgba(20, 23, 26, 0.4)
33
+ );
34
+ backdrop-filter: var(
35
+ --dp-overlay-backdrop-filter,
36
+ blur(2px) saturate(120%)
37
+ );
38
38
  }
39
39
 
40
40
  dialog-content {
41
- position: fixed;
42
- top: 50%;
43
- left: 50%;
44
- transform: translate(-50%, -50%) scale(0.95);
45
- max-width: 90vw;
46
- max-height: 85vh;
47
- display: var(--dp-content-display, block);
48
- opacity: 0;
49
- background: var(--dp-content-background, white);
50
- pointer-events: none;
51
- z-index: var(--dp-content-z-index, 1001);
52
- box-shadow: var(
53
- --dp-content-shadow,
54
- 0 10px 25px rgba(0, 0, 0, 0.15)
55
- );
56
- border-radius: var(--dp-content-border-radius, 8px);
57
- overflow: auto;
58
- transition:
59
- opacity var(--dp-transition-duration, 300ms)
60
- var(--dp-transition-timing, ease-out),
61
- transform var(--dp-transition-duration, 300ms)
62
- var(--dp-transition-timing, ease-out);
41
+ position: fixed;
42
+ top: 50%;
43
+ left: 50%;
44
+ transform: translate(-50%, -50%) scale(0.95);
45
+ max-width: 90vw;
46
+ max-height: 85vh;
47
+ display: var(--dp-content-display, block);
48
+ opacity: 0;
49
+ background: var(--dp-content-background, white);
50
+ pointer-events: none;
51
+ z-index: var(--dp-content-z-index, 1001);
52
+ box-shadow: var(
53
+ --dp-content-shadow,
54
+ 0 10px 25px rgba(0, 0, 0, 0.15)
55
+ );
56
+ border-radius: var(--dp-content-border-radius, 8px);
57
+ overflow: auto;
58
+ transition:
59
+ opacity var(--dp-transition-duration, 300ms)
60
+ var(--dp-transition-timing, ease-out),
61
+ transform var(--dp-transition-duration, 300ms)
62
+ var(--dp-transition-timing, ease-out);
63
63
 
64
- /* When shown, reset transform to center */
65
- dialog-panel[aria-hidden='false'] & {
66
- transform: translate(-50%, -50%) scale(1);
67
- }
64
+ /* When shown, reset transform to center */
65
+ dialog-panel[aria-hidden='false'] & {
66
+ transform: translate(-50%, -50%) scale(1);
67
+ }
68
68
 
69
- /* When explicitly hidden, remove from layout */
70
- &.hidden {
71
- display: none;
72
- }
69
+ /* When explicitly hidden, remove from layout */
70
+ &.hidden {
71
+ display: none;
72
+ }
73
73
  }
@@ -28,27 +28,27 @@ $transition-timing: ease-out !default;
28
28
 
29
29
  // Define CSS Custom Properties using SCSS values
30
30
  :root {
31
- // Layout
32
- --dp-panel-top: #{$panel-top};
33
- --dp-panel-left: #{$panel-left};
34
- --dp-panel-width: #{$panel-width};
35
- --dp-panel-height: #{$panel-height};
36
- --dp-panel-z-index: #{$panel-z-index};
37
-
38
- // Overlay
39
- --dp-overlay-z-index: #{$overlay-z-index};
40
- --dp-overlay-background: #{$overlay-background};
41
- --dp-overlay-backdrop-filter: #{$overlay-backdrop-filter};
42
- --dp-overlay-transition: #{$overlay-transition};
43
-
44
- // Content
45
- --dp-content-display: #{$content-display};
46
- --dp-content-background: #{$content-background};
47
- --dp-content-z-index: #{$content-z-index};
48
- --dp-content-shadow: #{$content-shadow};
49
- --dp-content-border-radius: #{$content-border-radius};
50
-
51
- // Animation
52
- --dp-transition-duration: #{$transition-duration};
53
- --dp-transition-timing: #{$transition-timing};
31
+ // Layout
32
+ --dp-panel-top: #{$panel-top};
33
+ --dp-panel-left: #{$panel-left};
34
+ --dp-panel-width: #{$panel-width};
35
+ --dp-panel-height: #{$panel-height};
36
+ --dp-panel-z-index: #{$panel-z-index};
37
+
38
+ // Overlay
39
+ --dp-overlay-z-index: #{$overlay-z-index};
40
+ --dp-overlay-background: #{$overlay-background};
41
+ --dp-overlay-backdrop-filter: #{$overlay-backdrop-filter};
42
+ --dp-overlay-transition: #{$overlay-transition};
43
+
44
+ // Content
45
+ --dp-content-display: #{$content-display};
46
+ --dp-content-background: #{$content-background};
47
+ --dp-content-z-index: #{$content-z-index};
48
+ --dp-content-shadow: #{$content-shadow};
49
+ --dp-content-border-radius: #{$content-border-radius};
50
+
51
+ // Animation
52
+ --dp-transition-duration: #{$transition-duration};
53
+ --dp-transition-timing: #{$transition-timing};
54
54
  }