@magic-spells/dialog-panel 0.2.3 → 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/README.md +163 -114
- package/dist/dialog-panel.cjs.js +281 -230
- package/dist/dialog-panel.cjs.js.map +1 -1
- package/dist/dialog-panel.css +108 -62
- package/dist/dialog-panel.esm.js +281 -226
- package/dist/dialog-panel.esm.js.map +1 -1
- package/dist/dialog-panel.js +273 -413
- package/dist/dialog-panel.js.map +1 -1
- package/dist/dialog-panel.min.css +1 -1
- package/dist/dialog-panel.min.js +1 -1
- package/package.json +11 -20
- package/src/dialog-panel.css +117 -0
- package/src/dialog-panel.js +282 -227
- package/dist/dialog-panel.scss +0 -2
- package/dist/scss/dialog-panel.scss +0 -73
- package/dist/scss/variables.scss +0 -54
- package/src/index.scss +0 -2
- package/src/scss/dialog-panel.scss +0 -73
- package/src/scss/variables.scss +0 -54
package/dist/dialog-panel.esm.js
CHANGED
|
@@ -1,307 +1,362 @@
|
|
|
1
|
-
import '@magic-spells/focus-trap';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
|
-
*
|
|
2
|
+
* DialogPanel - A lightweight web component wrapper for native <dialog> elements
|
|
3
|
+
* with state-driven animations.
|
|
4
|
+
*
|
|
5
5
|
* @extends HTMLElement
|
|
6
|
+
*
|
|
7
|
+
* @property {string} state - Current state: 'hidden' | 'showing' | 'shown' | 'hiding'
|
|
8
|
+
* @property {HTMLDialogElement} dialog - Reference to inner <dialog> element
|
|
9
|
+
* @property {boolean} isOpen - True if state is 'showing' or 'shown'
|
|
10
|
+
* @property {HTMLElement|null} triggerElement - Element that triggered current action
|
|
11
|
+
*
|
|
12
|
+
* @fires beforeShow - Fired before showing starts (cancelable)
|
|
13
|
+
* @fires shown - Fired after show animation completes
|
|
14
|
+
* @fires beforeHide - Fired before hiding starts (cancelable)
|
|
15
|
+
* @fires hidden - Fired after hide animation completes
|
|
6
16
|
*/
|
|
7
17
|
class DialogPanel extends HTMLElement {
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
// Private fields
|
|
19
|
+
#state = 'hidden';
|
|
20
|
+
#triggerElement = null;
|
|
21
|
+
#dialog = null;
|
|
22
|
+
#result = null;
|
|
23
|
+
|
|
24
|
+
// Event handler references for cleanup
|
|
25
|
+
#handlers = {
|
|
26
|
+
click: null,
|
|
27
|
+
dialogClick: null,
|
|
28
|
+
cancel: null,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Animation cleanup references
|
|
32
|
+
#pendingRAF = null;
|
|
33
|
+
#pendingTimeout = null;
|
|
34
|
+
|
|
35
|
+
// Fallback timeout for transitionend (in ms)
|
|
36
|
+
static TRANSITION_FALLBACK_TIMEOUT = 500;
|
|
37
|
+
|
|
38
|
+
connectedCallback() {
|
|
15
39
|
const _ = this;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
40
|
+
|
|
41
|
+
// Find inner dialog element
|
|
42
|
+
_.#dialog = _.querySelector('dialog');
|
|
43
|
+
|
|
44
|
+
if (!_.#dialog) {
|
|
45
|
+
console.warn(
|
|
46
|
+
'DialogPanel: No <dialog> element found inside <dialog-panel>'
|
|
20
47
|
);
|
|
48
|
+
return;
|
|
21
49
|
}
|
|
22
50
|
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
51
|
+
// Auto-create dialog-backdrop if not present
|
|
52
|
+
if (!_.querySelector('dialog-backdrop')) {
|
|
53
|
+
const backdrop = document.createElement('dialog-backdrop');
|
|
54
|
+
_.insertBefore(backdrop, _.firstChild);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Set initial state attribute
|
|
58
|
+
_.#setState('hidden');
|
|
59
|
+
|
|
60
|
+
// Bind event handlers
|
|
61
|
+
_.#bindEvents();
|
|
26
62
|
}
|
|
27
63
|
|
|
28
|
-
|
|
29
|
-
* Saves current scroll position and locks body scrolling
|
|
30
|
-
* @private
|
|
31
|
-
*/
|
|
32
|
-
#lockScroll() {
|
|
64
|
+
disconnectedCallback() {
|
|
33
65
|
const _ = this;
|
|
34
|
-
// Save current scroll position
|
|
35
|
-
_.#scrollPosition = window.pageYOffset;
|
|
36
66
|
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
// Cancel pending animations
|
|
68
|
+
if (_.#pendingRAF) {
|
|
69
|
+
cancelAnimationFrame(_.#pendingRAF);
|
|
70
|
+
_.#pendingRAF = null;
|
|
71
|
+
}
|
|
72
|
+
if (_.#pendingTimeout) {
|
|
73
|
+
clearTimeout(_.#pendingTimeout);
|
|
74
|
+
_.#pendingTimeout = null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Clean up event listeners
|
|
78
|
+
if (_.#handlers.click) {
|
|
79
|
+
_.removeEventListener('click', _.#handlers.click);
|
|
80
|
+
}
|
|
81
|
+
if (_.#dialog) {
|
|
82
|
+
if (_.#handlers.dialogClick) {
|
|
83
|
+
_.#dialog.removeEventListener(
|
|
84
|
+
'click',
|
|
85
|
+
_.#handlers.dialogClick
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (_.#handlers.cancel) {
|
|
89
|
+
_.#dialog.removeEventListener('cancel', _.#handlers.cancel);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
40
92
|
}
|
|
41
93
|
|
|
42
94
|
/**
|
|
43
|
-
*
|
|
95
|
+
* Bind event listeners for close buttons, backdrop, and escape key
|
|
44
96
|
* @private
|
|
45
97
|
*/
|
|
46
|
-
#
|
|
98
|
+
#bindEvents() {
|
|
47
99
|
const _ = this;
|
|
48
|
-
// Remove fixed positioning
|
|
49
|
-
document.body.classList.remove('overflow-hidden');
|
|
50
|
-
document.body.style.removeProperty('top');
|
|
51
100
|
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
constructor() {
|
|
59
|
-
super();
|
|
60
|
-
const _ = this;
|
|
61
|
-
_.id = _.getAttribute('id');
|
|
62
|
-
_.setAttribute('role', 'dialog');
|
|
63
|
-
_.setAttribute('aria-modal', 'true');
|
|
64
|
-
_.setAttribute('aria-hidden', 'true');
|
|
65
|
-
|
|
66
|
-
_.contentPanel = _.querySelector('dialog-content');
|
|
67
|
-
_.focusTrap = document.createElement('focus-trap');
|
|
68
|
-
_.triggerEl = null;
|
|
69
|
-
|
|
70
|
-
// Create a handler for transition end events
|
|
71
|
-
_.#handleTransitionEnd = (e) => {
|
|
72
|
-
if (
|
|
73
|
-
e.propertyName === 'opacity' &&
|
|
74
|
-
_.getAttribute('aria-hidden') === 'true'
|
|
75
|
-
) {
|
|
76
|
-
_.contentPanel.classList.add('hidden');
|
|
77
|
-
|
|
78
|
-
// Dispatch afterHide event - dialog has completed its transition
|
|
79
|
-
_.dispatchEvent(
|
|
80
|
-
new CustomEvent('afterHide', {
|
|
81
|
-
bubbles: true,
|
|
82
|
-
detail: { triggerElement: _.triggerEl },
|
|
83
|
-
})
|
|
84
|
-
);
|
|
101
|
+
// Handle close buttons with data-action-hide-dialog
|
|
102
|
+
_.#handlers.click = (e) => {
|
|
103
|
+
const trigger = e.target.closest('[data-action-hide-dialog]');
|
|
104
|
+
if (trigger) {
|
|
105
|
+
e.stopPropagation();
|
|
106
|
+
_.hide(trigger);
|
|
85
107
|
}
|
|
86
108
|
};
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
109
|
+
_.addEventListener('click', _.#handlers.click);
|
|
110
|
+
|
|
111
|
+
// Handle backdrop click - detect clicks outside dialog bounds
|
|
112
|
+
// This works because clicks on ::backdrop still fire on the dialog element
|
|
113
|
+
_.#handlers.dialogClick = (e) => {
|
|
114
|
+
const rect = _.#dialog.getBoundingClientRect();
|
|
115
|
+
const clickedOutside =
|
|
116
|
+
e.clientX < rect.left ||
|
|
117
|
+
e.clientX > rect.right ||
|
|
118
|
+
e.clientY < rect.top ||
|
|
119
|
+
e.clientY > rect.bottom;
|
|
120
|
+
if (clickedOutside) {
|
|
121
|
+
e.stopPropagation();
|
|
122
|
+
_.hide();
|
|
96
123
|
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
_.contentPanel.parentNode.insertBefore(
|
|
100
|
-
_.focusTrap,
|
|
101
|
-
_.contentPanel
|
|
102
|
-
);
|
|
103
|
-
_.focusTrap.appendChild(_.contentPanel);
|
|
104
|
-
|
|
105
|
-
_.focusTrap.setupTrap();
|
|
124
|
+
};
|
|
125
|
+
_.#dialog.addEventListener('click', _.#handlers.dialogClick);
|
|
106
126
|
|
|
107
|
-
//
|
|
108
|
-
_.
|
|
109
|
-
|
|
110
|
-
|
|
127
|
+
// Handle escape key - intercept native cancel and animate close
|
|
128
|
+
_.#handlers.cancel = (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
e.stopPropagation();
|
|
131
|
+
_.hide();
|
|
132
|
+
};
|
|
133
|
+
_.#dialog.addEventListener('cancel', _.#handlers.cancel);
|
|
111
134
|
}
|
|
112
135
|
|
|
113
136
|
/**
|
|
114
|
-
*
|
|
115
|
-
* @
|
|
137
|
+
* Show the dialog with animation
|
|
138
|
+
* @param {HTMLElement} [triggerEl=null] - The element that triggered the show
|
|
139
|
+
* @returns {boolean} False if show was prevented via beforeShow event
|
|
116
140
|
*/
|
|
117
|
-
|
|
141
|
+
show(triggerEl = null) {
|
|
118
142
|
const _ = this;
|
|
119
143
|
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
const trigger = e.target.closest(`[aria-controls="${_.id}"]`);
|
|
123
|
-
if (!trigger) return;
|
|
144
|
+
// Check if already showing/shown
|
|
145
|
+
if (_.#state === 'showing' || _.#state === 'shown') return true;
|
|
124
146
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
147
|
+
// If currently hiding, ignore (let hide complete first)
|
|
148
|
+
if (_.#state === 'hiding') return false;
|
|
128
149
|
|
|
129
|
-
|
|
130
|
-
|
|
150
|
+
// Store trigger element for focus return later
|
|
151
|
+
_.#triggerElement = triggerEl || null;
|
|
131
152
|
|
|
132
|
-
//
|
|
133
|
-
_
|
|
134
|
-
if (!e.target.closest('[data-action="hide-dialog"]')) return;
|
|
135
|
-
_.hide();
|
|
136
|
-
});
|
|
153
|
+
// Fire beforeShow (cancelable)
|
|
154
|
+
if (!_.#emit('beforeShow', { cancelable: true })) return false;
|
|
137
155
|
|
|
138
|
-
//
|
|
139
|
-
_
|
|
140
|
-
|
|
141
|
-
_.#handleTransitionEnd
|
|
142
|
-
);
|
|
143
|
-
}
|
|
156
|
+
// Set state to 'showing' and open native dialog
|
|
157
|
+
_.#setState('showing');
|
|
158
|
+
_.#dialog.showModal();
|
|
144
159
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
// Double RAF ensures browser has painted the 'showing' state
|
|
161
|
+
// before we transition to 'shown'
|
|
162
|
+
_.#pendingRAF = requestAnimationFrame(() => {
|
|
163
|
+
_.#pendingRAF = requestAnimationFrame(() => {
|
|
164
|
+
_.#pendingRAF = null;
|
|
165
|
+
_.#setState('shown');
|
|
166
|
+
|
|
167
|
+
// Wait for transition to complete before firing 'shown' event
|
|
168
|
+
_.#waitForTransition(() => {
|
|
169
|
+
_.#emit('shown');
|
|
170
|
+
});
|
|
171
|
+
});
|
|
154
172
|
});
|
|
173
|
+
|
|
174
|
+
return true;
|
|
155
175
|
}
|
|
156
176
|
|
|
157
177
|
/**
|
|
158
|
-
*
|
|
159
|
-
* @param {HTMLElement} [triggerEl=null] - The element that triggered the
|
|
160
|
-
* @
|
|
161
|
-
* @fires DialogPanel#show - Fired when the dialog has been shown
|
|
162
|
-
* @returns {boolean} False if the show was prevented by a beforeShow event handler
|
|
178
|
+
* Hide the dialog with animation
|
|
179
|
+
* @param {HTMLElement} [triggerEl=null] - The element that triggered the hide
|
|
180
|
+
* @returns {boolean} False if hide was prevented via beforeHide event
|
|
163
181
|
*/
|
|
164
|
-
|
|
182
|
+
hide(triggerEl = null) {
|
|
165
183
|
const _ = this;
|
|
166
|
-
_.triggerEl = triggerEl || false;
|
|
167
184
|
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
bubbles: true,
|
|
171
|
-
cancelable: true,
|
|
172
|
-
detail: { triggerElement: _.triggerEl },
|
|
173
|
-
});
|
|
185
|
+
// Check if already hiding/hidden
|
|
186
|
+
if (_.#state === 'hiding' || _.#state === 'hidden') return true;
|
|
174
187
|
|
|
175
|
-
|
|
188
|
+
// If currently showing, ignore (let show complete first)
|
|
189
|
+
if (_.#state === 'showing') return false;
|
|
176
190
|
|
|
177
|
-
//
|
|
178
|
-
|
|
191
|
+
// Capture result from trigger element
|
|
192
|
+
_.#result = triggerEl?.dataset?.result ?? null;
|
|
179
193
|
|
|
180
|
-
//
|
|
181
|
-
|
|
194
|
+
// Fire beforeHide (cancelable)
|
|
195
|
+
if (
|
|
196
|
+
!_.#emit('beforeHide', {
|
|
197
|
+
cancelable: true,
|
|
198
|
+
result: _.#result,
|
|
199
|
+
triggerElement: triggerEl,
|
|
200
|
+
})
|
|
201
|
+
) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
182
204
|
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
// Update ARIA states
|
|
186
|
-
_.setAttribute('aria-hidden', 'false');
|
|
187
|
-
if (_.triggerEl) {
|
|
188
|
-
_.triggerEl.setAttribute('aria-expanded', 'true');
|
|
189
|
-
}
|
|
205
|
+
// Set state to 'hiding' - this triggers CSS exit animation
|
|
206
|
+
_.#setState('hiding');
|
|
190
207
|
|
|
191
|
-
|
|
192
|
-
|
|
208
|
+
// Wait for transition to complete
|
|
209
|
+
_.#waitForTransition(() => {
|
|
210
|
+
_.#dialog.close();
|
|
211
|
+
_.#setState('hidden');
|
|
212
|
+
_.#emit('hidden', {
|
|
213
|
+
result: _.#result,
|
|
214
|
+
triggerElement: triggerEl,
|
|
215
|
+
});
|
|
193
216
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
197
|
-
);
|
|
198
|
-
if (firstFocusable) {
|
|
199
|
-
requestAnimationFrame(() => {
|
|
200
|
-
firstFocusable.focus();
|
|
201
|
-
});
|
|
202
|
-
}
|
|
217
|
+
// Return focus to trigger element
|
|
218
|
+
if (_.#triggerElement) _.#triggerElement.focus();
|
|
203
219
|
|
|
204
|
-
//
|
|
205
|
-
_
|
|
206
|
-
|
|
207
|
-
bubbles: true,
|
|
208
|
-
detail: { triggerElement: _.triggerEl },
|
|
209
|
-
})
|
|
210
|
-
);
|
|
220
|
+
// Clean up
|
|
221
|
+
_.#triggerElement = null;
|
|
222
|
+
_.#result = null;
|
|
211
223
|
});
|
|
212
224
|
|
|
213
225
|
return true;
|
|
214
226
|
}
|
|
215
227
|
|
|
216
228
|
/**
|
|
217
|
-
*
|
|
218
|
-
* @
|
|
219
|
-
* @
|
|
220
|
-
* @fires DialogPanel#afterHide - Fired when the dialog has completed its hide transition
|
|
221
|
-
* @returns {boolean} False if the hide was prevented by a beforeHide event handler
|
|
229
|
+
* Wait for CSS transition to complete with fallback timeout
|
|
230
|
+
* @param {Function} callback - Called when transition completes
|
|
231
|
+
* @private
|
|
222
232
|
*/
|
|
223
|
-
|
|
233
|
+
#waitForTransition(callback) {
|
|
224
234
|
const _ = this;
|
|
235
|
+
let called = false;
|
|
236
|
+
|
|
237
|
+
const done = () => {
|
|
238
|
+
if (called) return;
|
|
239
|
+
called = true;
|
|
240
|
+
_.#dialog.removeEventListener('transitionend', onTransitionEnd);
|
|
241
|
+
if (_.#pendingTimeout) {
|
|
242
|
+
clearTimeout(_.#pendingTimeout);
|
|
243
|
+
_.#pendingTimeout = null;
|
|
244
|
+
}
|
|
245
|
+
callback();
|
|
246
|
+
};
|
|
225
247
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
cancelable: true,
|
|
230
|
-
detail: { triggerElement: _.triggerEl },
|
|
231
|
-
});
|
|
248
|
+
const onTransitionEnd = (e) => {
|
|
249
|
+
if (e.target === _.#dialog) done();
|
|
250
|
+
};
|
|
232
251
|
|
|
233
|
-
|
|
252
|
+
_.#dialog.addEventListener('transitionend', onTransitionEnd);
|
|
234
253
|
|
|
235
|
-
|
|
236
|
-
|
|
254
|
+
_.#pendingTimeout = setTimeout(
|
|
255
|
+
done,
|
|
256
|
+
DialogPanel.TRANSITION_FALLBACK_TIMEOUT
|
|
257
|
+
);
|
|
258
|
+
}
|
|
237
259
|
|
|
238
|
-
|
|
239
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Set the component state
|
|
262
|
+
* @param {string} newState - The new state
|
|
263
|
+
* @private
|
|
264
|
+
*/
|
|
265
|
+
#setState(newState) {
|
|
266
|
+
this.#state = newState;
|
|
267
|
+
this.setAttribute('state', newState);
|
|
268
|
+
}
|
|
240
269
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
270
|
+
/**
|
|
271
|
+
* Emit a custom event
|
|
272
|
+
* @param {string} name - Event name
|
|
273
|
+
* @param {Object} options - Event options
|
|
274
|
+
* @returns {boolean} False if event was cancelled
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
#emit(name, options = {}) {
|
|
278
|
+
const _ = this;
|
|
279
|
+
const { cancelable = false, ...detail } = options;
|
|
248
280
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
281
|
+
const event = new CustomEvent(name, {
|
|
282
|
+
bubbles: true,
|
|
283
|
+
composed: true,
|
|
284
|
+
cancelable,
|
|
285
|
+
detail: {
|
|
286
|
+
triggerElement: _.#triggerElement,
|
|
287
|
+
result: _.#result,
|
|
288
|
+
state: _.#state,
|
|
289
|
+
...detail,
|
|
290
|
+
},
|
|
291
|
+
});
|
|
252
292
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
new CustomEvent('hide', {
|
|
256
|
-
bubbles: true,
|
|
257
|
-
detail: { triggerElement: _.triggerEl },
|
|
258
|
-
})
|
|
259
|
-
);
|
|
293
|
+
return _.dispatchEvent(event);
|
|
294
|
+
}
|
|
260
295
|
|
|
261
|
-
|
|
296
|
+
// Read-only properties
|
|
297
|
+
get state() {
|
|
298
|
+
return this.#state;
|
|
299
|
+
}
|
|
300
|
+
get dialog() {
|
|
301
|
+
return this.#dialog;
|
|
302
|
+
}
|
|
303
|
+
get isOpen() {
|
|
304
|
+
return this.#state === 'showing' || this.#state === 'shown';
|
|
305
|
+
}
|
|
306
|
+
get triggerElement() {
|
|
307
|
+
return this.#triggerElement;
|
|
262
308
|
}
|
|
263
309
|
}
|
|
264
310
|
|
|
265
311
|
/**
|
|
266
|
-
*
|
|
312
|
+
* DialogBackdrop - A custom backdrop element that animates with the dialog-panel state.
|
|
313
|
+
* Use this instead of native ::backdrop for consistent cross-browser animations.
|
|
314
|
+
*
|
|
267
315
|
* @extends HTMLElement
|
|
268
316
|
*/
|
|
269
|
-
class
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
this.dialogPanel = this.closest('dialog-panel');
|
|
275
|
-
this.#bindUI();
|
|
276
|
-
}
|
|
317
|
+
class DialogBackdrop extends HTMLElement {
|
|
318
|
+
#panel = null;
|
|
319
|
+
#handlers = {
|
|
320
|
+
click: null,
|
|
321
|
+
};
|
|
277
322
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
323
|
+
connectedCallback() {
|
|
324
|
+
const _ = this;
|
|
325
|
+
|
|
326
|
+
// Find parent dialog-panel
|
|
327
|
+
_.#panel = _.closest('dialog-panel');
|
|
328
|
+
|
|
329
|
+
if (!_.#panel) {
|
|
330
|
+
console.warn(
|
|
331
|
+
'DialogBackdrop: Must be inside a <dialog-panel> element'
|
|
332
|
+
);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Handle click to close
|
|
337
|
+
_.#handlers.click = () => {
|
|
338
|
+
_.#panel.hide();
|
|
339
|
+
};
|
|
340
|
+
_.addEventListener('click', _.#handlers.click);
|
|
282
341
|
}
|
|
283
|
-
}
|
|
284
342
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
constructor() {
|
|
291
|
-
super();
|
|
292
|
-
this.setAttribute('role', 'document'); // Optional: helps with document structure
|
|
343
|
+
disconnectedCallback() {
|
|
344
|
+
const _ = this;
|
|
345
|
+
if (_.#handlers.click) {
|
|
346
|
+
_.removeEventListener('click', _.#handlers.click);
|
|
347
|
+
}
|
|
293
348
|
}
|
|
294
349
|
}
|
|
295
350
|
|
|
351
|
+
// Register the custom elements
|
|
352
|
+
// Note: dialog-backdrop must be defined BEFORE dialog-panel,
|
|
353
|
+
// because dialog-panel's connectedCallback may create dialog-backdrop elements
|
|
354
|
+
if (!customElements.get('dialog-backdrop')) {
|
|
355
|
+
customElements.define('dialog-backdrop', DialogBackdrop);
|
|
356
|
+
}
|
|
296
357
|
if (!customElements.get('dialog-panel')) {
|
|
297
358
|
customElements.define('dialog-panel', DialogPanel);
|
|
298
359
|
}
|
|
299
|
-
if (!customElements.get('dialog-overlay')) {
|
|
300
|
-
customElements.define('dialog-overlay', DialogOverlay);
|
|
301
|
-
}
|
|
302
|
-
if (!customElements.get('dialog-content')) {
|
|
303
|
-
customElements.define('dialog-content', DialogContent);
|
|
304
|
-
}
|
|
305
360
|
|
|
306
|
-
export {
|
|
361
|
+
export { DialogBackdrop, DialogPanel };
|
|
307
362
|
//# sourceMappingURL=dialog-panel.esm.js.map
|