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