@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.
- package/dist/dialog-panel.cjs.js +66 -238
- package/dist/dialog-panel.cjs.js.map +1 -1
- package/dist/dialog-panel.esm.js +66 -238
- package/dist/dialog-panel.esm.js.map +1 -1
- package/dist/dialog-panel.js +65 -46
- package/dist/dialog-panel.js.map +1 -1
- package/dist/dialog-panel.min.js +1 -1
- package/dist/dialog-panel.scss +2 -2
- package/dist/scss/dialog-panel.scss +58 -58
- package/dist/scss/variables.scss +23 -23
- package/package.json +2 -2
- package/src/dialog-panel.js +66 -47
- package/src/index.scss +2 -2
- package/src/scss/dialog-panel.scss +58 -58
- package/src/scss/variables.scss +23 -23
package/src/dialog-panel.js
CHANGED
|
@@ -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(
|
|
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 (
|
|
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(
|
|
75
|
-
|
|
76
|
-
|
|
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(
|
|
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(
|
|
198
|
-
|
|
199
|
-
|
|
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(
|
|
245
|
-
|
|
246
|
-
|
|
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.
|
|
285
|
-
customElements.define('dialog-
|
|
286
|
-
|
|
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
|
|
2
|
-
@forward
|
|
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
|
-
|
|
6
|
-
|
|
5
|
+
/* Make it take no space and be invisible in the document flow */
|
|
6
|
+
display: contents;
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
/* When explicitly hidden, remove from layout */
|
|
70
|
+
&.hidden {
|
|
71
|
+
display: none;
|
|
72
|
+
}
|
|
73
73
|
}
|
package/src/scss/variables.scss
CHANGED
|
@@ -28,27 +28,27 @@ $transition-timing: ease-out !default;
|
|
|
28
28
|
|
|
29
29
|
// Define CSS Custom Properties using SCSS values
|
|
30
30
|
:root {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
}
|