@magic-spells/dialog-panel 0.3.0 → 1.0.1
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 +287 -219
- package/dist/dialog-panel.cjs.js.map +1 -1
- package/dist/dialog-panel.css +105 -59
- package/dist/dialog-panel.esm.js +287 -215
- package/dist/dialog-panel.esm.js.map +1 -1
- package/dist/dialog-panel.js +276 -405
- 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 +8 -17
- package/src/dialog-panel.css +117 -0
- package/src/dialog-panel.js +288 -216
- package/dist/dialog-panel.scss +0 -2
- package/dist/scss/dialog-panel.scss +0 -78
- package/dist/scss/variables.scss +0 -40
- package/src/index.scss +0 -2
- package/src/scss/dialog-panel.scss +0 -78
- package/src/scss/variables.scss +0 -40
package/dist/dialog-panel.esm.js
CHANGED
|
@@ -1,290 +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
|
-
|
|
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 = 700;
|
|
37
|
+
|
|
38
|
+
connectedCallback() {
|
|
14
39
|
const _ = this;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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>'
|
|
19
47
|
);
|
|
48
|
+
return;
|
|
20
49
|
}
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Initializes the dialog panel, sets up focus trap and overlay
|
|
24
|
-
*/
|
|
25
|
-
constructor() {
|
|
26
|
-
super();
|
|
27
|
-
const _ = this;
|
|
28
|
-
_.id = _.getAttribute('id');
|
|
29
|
-
_.setAttribute('role', 'dialog');
|
|
30
|
-
_.setAttribute('aria-modal', 'true');
|
|
31
|
-
_.setAttribute('aria-hidden', 'true');
|
|
32
|
-
|
|
33
|
-
_.contentPanel = _.querySelector('dialog-content');
|
|
34
|
-
_.triggerEl = null;
|
|
35
|
-
|
|
36
|
-
// Create a handler for transition end events
|
|
37
|
-
_.#handleTransitionEnd = (e) => {
|
|
38
|
-
if (
|
|
39
|
-
e.propertyName === 'opacity' &&
|
|
40
|
-
_.getAttribute('aria-hidden') === 'true'
|
|
41
|
-
) {
|
|
42
|
-
_.contentPanel.classList.add('hidden');
|
|
43
|
-
|
|
44
|
-
// Dispatch afterHide event - dialog has completed its transition
|
|
45
|
-
_.dispatchEvent(
|
|
46
|
-
new CustomEvent('afterHide', {
|
|
47
|
-
bubbles: true,
|
|
48
|
-
detail: { triggerElement: _.triggerEl },
|
|
49
|
-
})
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
50
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
}
|
|
59
56
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
// Set initial state attribute
|
|
58
|
+
_.#setState('hidden');
|
|
59
|
+
|
|
60
|
+
// Bind event handlers
|
|
61
|
+
_.#bindEvents();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
disconnectedCallback() {
|
|
65
|
+
const _ = this;
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
// Cancel pending animations
|
|
68
|
+
if (_.#pendingRAF) {
|
|
69
|
+
cancelAnimationFrame(_.#pendingRAF);
|
|
70
|
+
_.#pendingRAF = null;
|
|
71
|
+
}
|
|
72
|
+
if (_.#pendingTimeout) {
|
|
73
|
+
clearTimeout(_.#pendingTimeout);
|
|
74
|
+
_.#pendingTimeout = null;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
//
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
+
);
|
|
75
87
|
}
|
|
76
|
-
if (
|
|
77
|
-
_.
|
|
88
|
+
if (_.#handlers.cancel) {
|
|
89
|
+
_.#dialog.removeEventListener('cancel', _.#handlers.cancel);
|
|
78
90
|
}
|
|
79
91
|
}
|
|
80
|
-
|
|
81
|
-
// Add modal overlay
|
|
82
|
-
_.prepend(document.createElement('dialog-overlay'));
|
|
83
|
-
_.#bindUI();
|
|
84
|
-
_.#bindKeyboard();
|
|
85
92
|
}
|
|
86
93
|
|
|
87
94
|
/**
|
|
88
|
-
*
|
|
95
|
+
* Bind event listeners for close buttons, backdrop, and escape key
|
|
89
96
|
* @private
|
|
90
97
|
*/
|
|
91
|
-
#
|
|
98
|
+
#bindEvents() {
|
|
92
99
|
const _ = this;
|
|
93
100
|
|
|
94
|
-
// Handle
|
|
95
|
-
|
|
96
|
-
const trigger = e.target.closest(
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
e.preventDefault();
|
|
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);
|
|
101
107
|
}
|
|
108
|
+
};
|
|
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();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
_.#dialog.addEventListener('click', _.#handlers.dialogClick);
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
_.addEventListener('click', (e) => {
|
|
108
|
-
if (!e.target.closest('[data-action-hide-dialog]')) return;
|
|
127
|
+
// Handle escape key - intercept native cancel and animate close
|
|
128
|
+
_.#handlers.cancel = (e) => {
|
|
129
|
+
e.preventDefault();
|
|
130
|
+
e.stopPropagation();
|
|
109
131
|
_.hide();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Add transition end listener
|
|
113
|
-
_.contentPanel.addEventListener(
|
|
114
|
-
'transitionend',
|
|
115
|
-
_.#handleTransitionEnd
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Binds keyboard events for accessibility
|
|
121
|
-
* @private
|
|
122
|
-
*/
|
|
123
|
-
#bindKeyboard() {
|
|
124
|
-
this.addEventListener('keydown', (e) => {
|
|
125
|
-
if (e.key === 'Escape') {
|
|
126
|
-
this.hide();
|
|
127
|
-
}
|
|
128
|
-
});
|
|
132
|
+
};
|
|
133
|
+
_.#dialog.addEventListener('cancel', _.#handlers.cancel);
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
/**
|
|
132
|
-
*
|
|
133
|
-
* @param {HTMLElement} [triggerEl=null] - The element that triggered the
|
|
134
|
-
* @
|
|
135
|
-
* @fires DialogPanel#show - Fired when the dialog has been shown
|
|
136
|
-
* @returns {boolean} False if the show was prevented by a beforeShow event handler
|
|
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
|
|
137
140
|
*/
|
|
138
141
|
show(triggerEl = null) {
|
|
139
142
|
const _ = this;
|
|
140
|
-
_.triggerEl = triggerEl || false;
|
|
141
143
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
bubbles: true,
|
|
145
|
-
cancelable: true,
|
|
146
|
-
detail: { triggerElement: _.triggerEl },
|
|
147
|
-
});
|
|
144
|
+
// Check if already showing/shown
|
|
145
|
+
if (_.#state === 'showing' || _.#state === 'shown') return true;
|
|
148
146
|
|
|
149
|
-
|
|
147
|
+
// If currently hiding, ignore (let hide complete first)
|
|
148
|
+
if (_.#state === 'hiding') return false;
|
|
150
149
|
|
|
151
|
-
//
|
|
152
|
-
|
|
150
|
+
// Store trigger element for focus return later
|
|
151
|
+
_.#triggerElement = triggerEl || null;
|
|
153
152
|
|
|
154
|
-
//
|
|
155
|
-
_
|
|
153
|
+
// Fire beforeShow (cancelable)
|
|
154
|
+
if (!_.#emit('beforeShow', { cancelable: true })) return false;
|
|
156
155
|
|
|
157
|
-
//
|
|
158
|
-
_
|
|
156
|
+
// Set state to 'showing' and open native dialog
|
|
157
|
+
_.#setState('showing');
|
|
158
|
+
_.#dialog.showModal();
|
|
159
159
|
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
_
|
|
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');
|
|
164
166
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Focus management
|
|
171
|
-
const firstFocusable = _.querySelector(
|
|
172
|
-
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
|
|
173
|
-
);
|
|
174
|
-
if (firstFocusable) {
|
|
175
|
-
requestAnimationFrame(() => {
|
|
176
|
-
firstFocusable.focus();
|
|
167
|
+
// Wait for transition to complete before firing 'shown' event
|
|
168
|
+
_.#waitForTransition(() => {
|
|
169
|
+
_.#emit('shown');
|
|
177
170
|
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Dispatch show event - dialog is now visible
|
|
181
|
-
_.dispatchEvent(
|
|
182
|
-
new CustomEvent('show', {
|
|
183
|
-
bubbles: true,
|
|
184
|
-
detail: { triggerElement: _.triggerEl },
|
|
185
|
-
})
|
|
186
|
-
);
|
|
171
|
+
});
|
|
187
172
|
});
|
|
173
|
+
|
|
174
|
+
return true;
|
|
188
175
|
}
|
|
189
176
|
|
|
190
177
|
/**
|
|
191
|
-
*
|
|
192
|
-
* @
|
|
193
|
-
* @
|
|
194
|
-
* @fires DialogPanel#afterHide - Fired when the dialog has completed its hide transition
|
|
195
|
-
* @returns {boolean} False if the hide was prevented by a beforeHide 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
|
|
196
181
|
*/
|
|
197
|
-
hide() {
|
|
182
|
+
hide(triggerEl = null) {
|
|
198
183
|
const _ = this;
|
|
199
184
|
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
bubbles: true,
|
|
203
|
-
cancelable: true,
|
|
204
|
-
detail: { triggerElement: _.triggerEl },
|
|
205
|
-
});
|
|
185
|
+
// Check if already hiding/hidden
|
|
186
|
+
if (_.#state === 'hiding' || _.#state === 'hidden') return true;
|
|
206
187
|
|
|
207
|
-
|
|
188
|
+
// If currently showing, ignore (let show complete first)
|
|
189
|
+
if (_.#state === 'showing') return false;
|
|
208
190
|
|
|
209
|
-
//
|
|
210
|
-
|
|
191
|
+
// Capture result from trigger element
|
|
192
|
+
_.#result = triggerEl?.dataset?.result ?? null;
|
|
211
193
|
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
194
|
+
// Fire beforeHide (cancelable)
|
|
195
|
+
if (
|
|
196
|
+
!_.#emit('beforeHide', {
|
|
197
|
+
cancelable: true,
|
|
198
|
+
result: _.#result,
|
|
199
|
+
triggerElement: triggerEl,
|
|
200
|
+
})
|
|
201
|
+
) {
|
|
202
|
+
return false;
|
|
217
203
|
}
|
|
218
204
|
|
|
219
|
-
//
|
|
220
|
-
|
|
221
|
-
// remove focus from modal panel first
|
|
222
|
-
_.triggerEl.focus();
|
|
223
|
-
// mark trigger as no longer expanded
|
|
224
|
-
_.triggerEl.setAttribute('aria-expanded', 'false');
|
|
225
|
-
} else {
|
|
226
|
-
// Move focus to body to ensure it's not trapped
|
|
227
|
-
document.body.focus();
|
|
228
|
-
}
|
|
205
|
+
// Set state to 'hiding' - this triggers CSS exit animation
|
|
206
|
+
_.#setState('hiding');
|
|
229
207
|
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
208
|
+
// Wait for transition to complete
|
|
209
|
+
_.#waitForTransition(() => {
|
|
210
|
+
_.#dialog.close();
|
|
211
|
+
_.#setState('hidden');
|
|
212
|
+
_.#emit('hidden', {
|
|
213
|
+
result: _.#result,
|
|
214
|
+
triggerElement: triggerEl,
|
|
215
|
+
});
|
|
234
216
|
|
|
235
|
-
|
|
217
|
+
// Return focus to trigger element
|
|
218
|
+
if (_.#triggerElement) _.#triggerElement.focus();
|
|
236
219
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
detail: { triggerElement: _.triggerEl },
|
|
242
|
-
})
|
|
243
|
-
);
|
|
220
|
+
// Clean up
|
|
221
|
+
_.#triggerElement = null;
|
|
222
|
+
_.#result = null;
|
|
223
|
+
});
|
|
244
224
|
|
|
245
225
|
return true;
|
|
246
226
|
}
|
|
247
|
-
}
|
|
248
227
|
|
|
249
|
-
/**
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
228
|
+
/**
|
|
229
|
+
* Wait for CSS transition to complete with fallback timeout
|
|
230
|
+
* @param {Function} callback - Called when transition completes
|
|
231
|
+
* @private
|
|
232
|
+
*/
|
|
233
|
+
#waitForTransition(callback) {
|
|
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
|
+
};
|
|
247
|
+
|
|
248
|
+
const onTransitionEnd = (e) => {
|
|
249
|
+
if (e.target === _.#dialog) done();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
_.#dialog.addEventListener('transitionend', onTransitionEnd);
|
|
253
|
+
|
|
254
|
+
_.#pendingTimeout = setTimeout(
|
|
255
|
+
done,
|
|
256
|
+
DialogPanel.TRANSITION_FALLBACK_TIMEOUT
|
|
257
|
+
);
|
|
259
258
|
}
|
|
260
259
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
+
}
|
|
269
|
+
|
|
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;
|
|
280
|
+
|
|
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
|
+
},
|
|
264
291
|
});
|
|
292
|
+
|
|
293
|
+
return _.dispatchEvent(event);
|
|
294
|
+
}
|
|
295
|
+
|
|
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;
|
|
265
308
|
}
|
|
266
309
|
}
|
|
267
310
|
|
|
268
311
|
/**
|
|
269
|
-
*
|
|
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
|
+
*
|
|
270
315
|
* @extends HTMLElement
|
|
271
316
|
*/
|
|
272
|
-
class
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
317
|
+
class DialogBackdrop extends HTMLElement {
|
|
318
|
+
#panel = null;
|
|
319
|
+
#handlers = {
|
|
320
|
+
click: null,
|
|
321
|
+
};
|
|
322
|
+
|
|
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);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
disconnectedCallback() {
|
|
344
|
+
const _ = this;
|
|
345
|
+
if (_.#handlers.click) {
|
|
346
|
+
_.removeEventListener('click', _.#handlers.click);
|
|
347
|
+
}
|
|
276
348
|
}
|
|
277
349
|
}
|
|
278
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
|
+
}
|
|
279
357
|
if (!customElements.get('dialog-panel')) {
|
|
280
358
|
customElements.define('dialog-panel', DialogPanel);
|
|
281
359
|
}
|
|
282
|
-
if (!customElements.get('dialog-overlay')) {
|
|
283
|
-
customElements.define('dialog-overlay', DialogOverlay);
|
|
284
|
-
}
|
|
285
|
-
if (!customElements.get('dialog-content')) {
|
|
286
|
-
customElements.define('dialog-content', DialogContent);
|
|
287
|
-
}
|
|
288
360
|
|
|
289
|
-
export {
|
|
361
|
+
export { DialogBackdrop, DialogPanel };
|
|
290
362
|
//# sourceMappingURL=dialog-panel.esm.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dialog-panel.esm.js","sources":["../src/dialog-panel.js"],"sourcesContent":["import './index.scss';\nimport '@magic-spells/focus-trap';\n\n/**\n * Custom element that creates an accessible modal dialog panel with focus management\n * @extends HTMLElement\n */\nclass DialogPanel extends HTMLElement {\n\t#handleTransitionEnd;\n\n\t/**\n\t * Clean up event listeners when component is removed from DOM\n\t */\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tif (_.contentPanel) {\n\t\t\t_.contentPanel.removeEventListener(\n\t\t\t\t'transitionend',\n\t\t\t\t_.#handleTransitionEnd\n\t\t\t);\n\t\t}\n\t}\n\t/**\n\t * Initializes the dialog panel, sets up focus trap and overlay\n\t */\n\tconstructor() {\n\t\tsuper();\n\t\tconst _ = this;\n\t\t_.id = _.getAttribute('id');\n\t\t_.setAttribute('role', 'dialog');\n\t\t_.setAttribute('aria-modal', 'true');\n\t\t_.setAttribute('aria-hidden', 'true');\n\n\t\t_.contentPanel = _.querySelector('dialog-content');\n\t\t_.triggerEl = null;\n\n\t\t// Create a handler for transition end events\n\t\t_.#handleTransitionEnd = (e) => {\n\t\t\tif (\n\t\t\t\te.propertyName === 'opacity' &&\n\t\t\t\t_.getAttribute('aria-hidden') === 'true'\n\t\t\t) {\n\t\t\t\t_.contentPanel.classList.add('hidden');\n\n\t\t\t\t// Dispatch afterHide event - dialog has completed its transition\n\t\t\t\t_.dispatchEvent(\n\t\t\t\t\tnew CustomEvent('afterHide', {\n\t\t\t\t\t\tbubbles: true,\n\t\t\t\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t}\n\t\t};\n\n\t\t// Set up focus-trap inside dialog-content\n\t\t// Check if focus-trap already exists\n\t\t_.focusTrap = _.contentPanel.querySelector('focus-trap');\n\t\tif (!_.focusTrap) {\n\t\t\t_.focusTrap = document.createElement('focus-trap');\n\n\t\t\t// Move all existing dialog-content children into focus-trap\n\t\t\tconst existingContent = Array.from(_.contentPanel.childNodes);\n\t\t\texistingContent.forEach((child) =>\n\t\t\t\t_.focusTrap.appendChild(child)\n\t\t\t);\n\n\t\t\t// Insert focus-trap inside dialog-content\n\t\t\t_.contentPanel.appendChild(_.focusTrap);\n\t\t}\n\n\t\t// Ensure we have labelledby and describedby references\n\t\tif (!_.getAttribute('aria-labelledby')) {\n\t\t\tconst heading = _.querySelector('h1, h2, h3');\n\t\t\tif (heading && !heading.id) {\n\t\t\t\theading.id = `${_.id}-title`;\n\t\t\t}\n\t\t\tif (heading?.id) {\n\t\t\t\t_.setAttribute('aria-labelledby', heading.id);\n\t\t\t}\n\t\t}\n\n\t\t// Add modal overlay\n\t\t_.prepend(document.createElement('dialog-overlay'));\n\t\t_.#bindUI();\n\t\t_.#bindKeyboard();\n\t}\n\n\t/**\n\t * Binds click events for showing and hiding the dialog\n\t * @private\n\t */\n\t#bindUI() {\n\t\tconst _ = this;\n\n\t\t// Handle trigger buttons\n\t\tdocument.addEventListener('click', (e) => {\n\t\t\tconst trigger = e.target.closest(`[aria-controls=\"${_.id}\"]`);\n\t\t\tif (!trigger) return;\n\n\t\t\tif (trigger.getAttribute('data-prevent-default') === 'true') {\n\t\t\t\te.preventDefault();\n\t\t\t}\n\n\t\t\t_.show(trigger);\n\t\t});\n\n\t\t// Handle close buttons\n\t\t_.addEventListener('click', (e) => {\n\t\t\tif (!e.target.closest('[data-action-hide-dialog]')) return;\n\t\t\t_.hide();\n\t\t});\n\n\t\t// Add transition end listener\n\t\t_.contentPanel.addEventListener(\n\t\t\t'transitionend',\n\t\t\t_.#handleTransitionEnd\n\t\t);\n\t}\n\n\t/**\n\t * Binds keyboard events for accessibility\n\t * @private\n\t */\n\t#bindKeyboard() {\n\t\tthis.addEventListener('keydown', (e) => {\n\t\t\tif (e.key === 'Escape') {\n\t\t\t\tthis.hide();\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Shows the dialog and traps focus within it\n\t * @param {HTMLElement} [triggerEl=null] - The element that triggered the dialog\n\t * @fires DialogPanel#beforeShow - Fired before the dialog starts to show\n\t * @fires DialogPanel#show - Fired when the dialog has been shown\n\t * @returns {boolean} False if the show was prevented by a beforeShow event handler\n\t */\n\tshow(triggerEl = null) {\n\t\tconst _ = this;\n\t\t_.triggerEl = triggerEl || false;\n\n\t\t// Dispatch beforeShow event - allows preventing the dialog from opening\n\t\tconst beforeShowEvent = new CustomEvent('beforeShow', {\n\t\t\tbubbles: true,\n\t\t\tcancelable: true,\n\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t});\n\n\t\tconst showAllowed = _.dispatchEvent(beforeShowEvent);\n\n\t\t// If event was canceled (preventDefault was called), don't show the dialog\n\t\tif (!showAllowed) return false;\n\n\t\t// Add open attribute for CSS :has() selector\n\t\t_.setAttribute('open', '');\n\n\t\t// Remove the hidden class first to ensure content is rendered\n\t\t_.contentPanel.classList.remove('hidden');\n\n\t\t// Give the browser a moment to process before starting animation\n\t\trequestAnimationFrame(() => {\n\t\t\t// Update ARIA states\n\t\t\t_.setAttribute('aria-hidden', 'false');\n\n\t\t\t// set trigger element to expanded: true\n\t\t\tif (_.triggerEl) {\n\t\t\t\t_.triggerEl.setAttribute('aria-expanded', 'true');\n\t\t\t}\n\n\t\t\t// Focus management\n\t\t\tconst firstFocusable = _.querySelector(\n\t\t\t\t'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n\t\t\t);\n\t\t\tif (firstFocusable) {\n\t\t\t\trequestAnimationFrame(() => {\n\t\t\t\t\tfirstFocusable.focus();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Dispatch show event - dialog is now visible\n\t\t\t_.dispatchEvent(\n\t\t\t\tnew CustomEvent('show', {\n\t\t\t\t\tbubbles: true,\n\t\t\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t\t\t})\n\t\t\t);\n\t\t});\n\t}\n\n\t/**\n\t * Hides the dialog and restores focus\n\t * @fires DialogPanel#beforeHide - Fired before the dialog starts to hide\n\t * @fires DialogPanel#hide - Fired when the dialog has started hiding (transition begins)\n\t * @fires DialogPanel#afterHide - Fired when the dialog has completed its hide transition\n\t * @returns {boolean} False if the hide was prevented by a beforeHide event handler\n\t */\n\thide() {\n\t\tconst _ = this;\n\n\t\t// Dispatch beforeHide event - allows preventing the dialog from closing\n\t\tconst beforeHideEvent = new CustomEvent('beforeHide', {\n\t\t\tbubbles: true,\n\t\t\tcancelable: true,\n\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t});\n\n\t\tconst hideAllowed = _.dispatchEvent(beforeHideEvent);\n\n\t\t// If event was canceled (preventDefault was called), don't hide the dialog\n\t\tif (!hideAllowed) return false;\n\n\t\t// ensure focus is moved out of the dialog\n\t\tconst activeElement = document.activeElement;\n\t\tif (activeElement && _.contains(activeElement)) {\n\t\t\t// Blur the currently focused element if it's inside the dialog\n\t\t\tactiveElement.blur();\n\t\t}\n\n\t\t// Update ARIA states and restore focus\n\t\tif (_.triggerEl) {\n\t\t\t// remove focus from modal panel first\n\t\t\t_.triggerEl.focus();\n\t\t\t// mark trigger as no longer expanded\n\t\t\t_.triggerEl.setAttribute('aria-expanded', 'false');\n\t\t} else {\n\t\t\t// Move focus to body to ensure it's not trapped\n\t\t\tdocument.body.focus();\n\t\t}\n\n\t\t// Set aria-hidden to start transition\n\t\t// The transitionend event handler will add display:none when complete\n\t\t_.setAttribute('aria-hidden', 'true');\n\t\t// Remove open attribute for CSS :has() selector\n\n\t\t_.removeAttribute('open');\n\n\t\t// Dispatch hide event - dialog is now starting to hide\n\t\t_.dispatchEvent(\n\t\t\tnew CustomEvent('hide', {\n\t\t\t\tbubbles: true,\n\t\t\t\tdetail: { triggerElement: _.triggerEl },\n\t\t\t})\n\t\t);\n\n\t\treturn true;\n\t}\n}\n\n/**\n * Custom element that creates a clickable overlay for the dialog\n * @extends HTMLElement\n */\nclass DialogOverlay extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('tabindex', '-1');\n\t\tthis.dialogPanel = this.closest('dialog-panel');\n\t\tthis.#bindUI();\n\t}\n\n\t#bindUI() {\n\t\tthis.addEventListener('click', () => {\n\t\t\tthis.dialogPanel.hide();\n\t\t});\n\t}\n}\n\n/**\n * Custom element that wraps the content of the dialog\n * @extends HTMLElement\n */\nclass DialogContent extends HTMLElement {\n\tconstructor() {\n\t\tsuper();\n\t\tthis.setAttribute('role', 'document');\n\t}\n}\n\nif (!customElements.get('dialog-panel')) {\n\tcustomElements.define('dialog-panel', DialogPanel);\n}\nif (!customElements.get('dialog-overlay')) {\n\tcustomElements.define('dialog-overlay', DialogOverlay);\n}\nif (!customElements.get('dialog-content')) {\n\tcustomElements.define('dialog-content', DialogContent);\n}\n\nexport { DialogPanel, DialogOverlay, DialogContent };\nexport default DialogPanel;\n"],"names":[],"mappings":";;AAGA;AACA;AACA;AACA;AACA,MAAM,WAAW,SAAS,WAAW,CAAC;AACtC,CAAC,oBAAoB,CAAC;AACtB;AACA;AACA;AACA;AACA,CAAC,oBAAoB,GAAG;AACxB,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;AACjB,EAAE,IAAI,CAAC,CAAC,YAAY,EAAE;AACtB,GAAG,CAAC,CAAC,YAAY,CAAC,mBAAmB;AACrC,IAAI,eAAe;AACnB,IAAI,CAAC,CAAC,oBAAoB;AAC1B,IAAI,CAAC;AACL,GAAG;AACH,EAAE;AACF;AACA;AACA;AACA,CAAC,WAAW,GAAG;AACf,EAAE,KAAK,EAAE,CAAC;AACV,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;AACjB,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;AAC9B,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AACnC,EAAE,CAAC,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;AACvC,EAAE,CAAC,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AACxC;AACA,EAAE,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC;AACrD,EAAE,CAAC,CAAC,SAAS,GAAG,IAAI,CAAC;AACrB;AACA;AACA,EAAE,CAAC,CAAC,oBAAoB,GAAG,CAAC,CAAC,KAAK;AAClC,GAAG;AACH,IAAI,CAAC,CAAC,YAAY,KAAK,SAAS;AAChC,IAAI,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,MAAM;AAC5C,KAAK;AACL,IAAI,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AAC3C;AACA;AACA,IAAI,CAAC,CAAC,aAAa;AACnB,KAAK,IAAI,WAAW,CAAC,WAAW,EAAE;AAClC,MAAM,OAAO,EAAE,IAAI;AACnB,MAAM,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,SAAS,EAAE;AAC7C,MAAM,CAAC;AACP,KAAK,CAAC;AACN,IAAI;AACJ,GAAG,CAAC;AACJ;AACA;AACA;AACA,EAAE,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,YAAY,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;AAC3D,EAAE,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE;AACpB,GAAG,CAAC,CAAC,SAAS,GAAG,QAAQ,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;AACtD;AACA;AACA,GAAG,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;AACjE,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC,KAAK;AACjC,IAAI,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,KAAK,CAAC;AAClC,IAAI,CAAC;AACL;AACA;AACA,GAAG,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC3C,GAAG;AACH;AACA;AACA,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,iBAAiB,CAAC,EAAE;AAC1C,GAAG,MAAM,OAAO,GAAG,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;AACjD,GAAG,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE;AAC/B,IAAI,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;AACjC,IAAI;AACJ,GAAG,IAAI,OAAO,EAAE,EAAE,EAAE;AACpB,IAAI,CAAC,CAAC,YAAY,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;AAClD,IAAI;AACJ,GAAG;AACH;AACA;AACA,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,gBAAgB,CAAC,CAAC,CAAC;AACtD,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;AACd,EAAE,CAAC,CAAC,aAAa,EAAE,CAAC;AACpB,EAAE;AACF;AACA;AACA;AACA;AACA;AACA,CAAC,OAAO,GAAG;AACX,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;AACjB;AACA;AACA,EAAE,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK;AAC5C,GAAG,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AACjE,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO;AACxB;AACA,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC,sBAAsB,CAAC,KAAK,MAAM,EAAE;AAChE,IAAI,CAAC,CAAC,cAAc,EAAE,CAAC;AACvB,IAAI;AACJ;AACA,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AACnB,GAAG,CAAC,CAAC;AACL;AACA;AACA,EAAE,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,KAAK;AACrC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,2BAA2B,CAAC,EAAE,OAAO;AAC9D,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACZ,GAAG,CAAC,CAAC;AACL;AACA;AACA,EAAE,CAAC,CAAC,YAAY,CAAC,gBAAgB;AACjC,GAAG,eAAe;AAClB,GAAG,CAAC,CAAC,oBAAoB;AACzB,GAAG,CAAC;AACJ,EAAE;AACF;AACA;AACA;AACA;AACA;AACA,CAAC,aAAa,GAAG;AACjB,EAAE,IAAI,CAAC,gBAAgB,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK;AAC1C,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,QAAQ,EAAE;AAC3B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;AAChB,IAAI;AACJ,GAAG,CAAC,CAAC;AACL,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,EAAE;AACxB,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;AACjB,EAAE,CAAC,CAAC,SAAS,GAAG,SAAS,IAAI,KAAK,CAAC;AACnC;AACA;AACA,EAAE,MAAM,eAAe,GAAG,IAAI,WAAW,CAAC,YAAY,EAAE;AACxD,GAAG,OAAO,EAAE,IAAI;AAChB,GAAG,UAAU,EAAE,IAAI;AACnB,GAAG,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,SAAS,EAAE;AAC1C,GAAG,CAAC,CAAC;AACL;AACA,EAAE,MAAM,WAAW,GAAG,CAAC,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;AACvD;AACA;AACA,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,KAAK,CAAC;AACjC;AACA;AACA,EAAE,CAAC,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC7B;AACA;AACA,EAAE,CAAC,CAAC,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC5C;AACA;AACA,EAAE,qBAAqB,CAAC,MAAM;AAC9B;AACA,GAAG,CAAC,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;AAC1C;AACA;AACA,GAAG,IAAI,CAAC,CAAC,SAAS,EAAE;AACpB,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;AACtD,IAAI;AACJ;AACA;AACA,GAAG,MAAM,cAAc,GAAG,CAAC,CAAC,aAAa;AACzC,IAAI,0EAA0E;AAC9E,IAAI,CAAC;AACL,GAAG,IAAI,cAAc,EAAE;AACvB,IAAI,qBAAqB,CAAC,MAAM;AAChC,KAAK,cAAc,CAAC,KAAK,EAAE,CAAC;AAC5B,KAAK,CAAC,CAAC;AACP,IAAI;AACJ;AACA;AACA,GAAG,CAAC,CAAC,aAAa;AAClB,IAAI,IAAI,WAAW,CAAC,MAAM,EAAE;AAC5B,KAAK,OAAO,EAAE,IAAI;AAClB,KAAK,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,SAAS,EAAE;AAC5C,KAAK,CAAC;AACN,IAAI,CAAC;AACL,GAAG,CAAC,CAAC;AACL,EAAE;AACF;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,IAAI,GAAG;AACR,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;AACjB;AACA;AACA,EAAE,MAAM,eAAe,GAAG,IAAI,WAAW,CAAC,YAAY,EAAE;AACxD,GAAG,OAAO,EAAE,IAAI;AAChB,GAAG,UAAU,EAAE,IAAI;AACnB,GAAG,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,SAAS,EAAE;AAC1C,GAAG,CAAC,CAAC;AACL;AACA,EAAE,MAAM,WAAW,GAAG,CAAC,CAAC,aAAa,CAAC,eAAe,CAAC,CAAC;AACvD;AACA;AACA,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,KAAK,CAAC;AACjC;AACA;AACA,EAAE,MAAM,aAAa,GAAG,QAAQ,CAAC,aAAa,CAAC;AAC/C,EAAE,IAAI,aAAa,IAAI,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE;AAClD;AACA,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC;AACxB,GAAG;AACH;AACA;AACA,EAAE,IAAI,CAAC,CAAC,SAAS,EAAE;AACnB;AACA,GAAG,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;AACvB;AACA,GAAG,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AACtD,GAAG,MAAM;AACT;AACA,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;AACzB,GAAG;AACH;AACA;AACA;AACA,EAAE,CAAC,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;AACxC;AACA;AACA,EAAE,CAAC,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;AAC5B;AACA;AACA,EAAE,CAAC,CAAC,aAAa;AACjB,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE;AAC3B,IAAI,OAAO,EAAE,IAAI;AACjB,IAAI,MAAM,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC,SAAS,EAAE;AAC3C,IAAI,CAAC;AACL,GAAG,CAAC;AACJ;AACA,EAAE,OAAO,IAAI,CAAC;AACd,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA,MAAM,aAAa,SAAS,WAAW,CAAC;AACxC,CAAC,WAAW,GAAG;AACf,EAAE,KAAK,EAAE,CAAC;AACV,EAAE,IAAI,CAAC,YAAY,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACtC,EAAE,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;AAClD,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;AACjB,EAAE;AACF;AACA,CAAC,OAAO,GAAG;AACX,EAAE,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM;AACvC,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;AAC3B,GAAG,CAAC,CAAC;AACL,EAAE;AACF,CAAC;AACD;AACA;AACA;AACA;AACA;AACA,MAAM,aAAa,SAAS,WAAW,CAAC;AACxC,CAAC,WAAW,GAAG;AACf,EAAE,KAAK,EAAE,CAAC;AACV,EAAE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;AACxC,EAAE;AACF,CAAC;AACD;AACA,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE;AACzC,CAAC,cAAc,CAAC,MAAM,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC;AACpD,CAAC;AACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE;AAC3C,CAAC,cAAc,CAAC,MAAM,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;AACxD,CAAC;AACD,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,gBAAgB,CAAC,EAAE;AAC3C,CAAC,cAAc,CAAC,MAAM,CAAC,gBAAgB,EAAE,aAAa,CAAC,CAAC;AACxD;;;;"}
|
|
1
|
+
{"version":3,"file":"dialog-panel.esm.js","sources":["../src/dialog-panel.js"],"sourcesContent":["import './dialog-panel.css';\n\n/**\n * DialogPanel - A lightweight web component wrapper for native <dialog> elements\n * with state-driven animations.\n *\n * @extends HTMLElement\n *\n * @property {string} state - Current state: 'hidden' | 'showing' | 'shown' | 'hiding'\n * @property {HTMLDialogElement} dialog - Reference to inner <dialog> element\n * @property {boolean} isOpen - True if state is 'showing' or 'shown'\n * @property {HTMLElement|null} triggerElement - Element that triggered current action\n *\n * @fires beforeShow - Fired before showing starts (cancelable)\n * @fires shown - Fired after show animation completes\n * @fires beforeHide - Fired before hiding starts (cancelable)\n * @fires hidden - Fired after hide animation completes\n */\nclass DialogPanel extends HTMLElement {\n\t// Private fields\n\t#state = 'hidden';\n\t#triggerElement = null;\n\t#dialog = null;\n\t#result = null;\n\n\t// Event handler references for cleanup\n\t#handlers = {\n\t\tclick: null,\n\t\tdialogClick: null,\n\t\tcancel: null,\n\t};\n\n\t// Animation cleanup references\n\t#pendingRAF = null;\n\t#pendingTimeout = null;\n\n\t// Fallback timeout for transitionend (in ms)\n\tstatic TRANSITION_FALLBACK_TIMEOUT = 700;\n\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// Find inner dialog element\n\t\t_.#dialog = _.querySelector('dialog');\n\n\t\tif (!_.#dialog) {\n\t\t\tconsole.warn(\n\t\t\t\t'DialogPanel: No <dialog> element found inside <dialog-panel>'\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Auto-create dialog-backdrop if not present\n\t\tif (!_.querySelector('dialog-backdrop')) {\n\t\t\tconst backdrop = document.createElement('dialog-backdrop');\n\t\t\t_.insertBefore(backdrop, _.firstChild);\n\t\t}\n\n\t\t// Set initial state attribute\n\t\t_.#setState('hidden');\n\n\t\t// Bind event handlers\n\t\t_.#bindEvents();\n\t}\n\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// Cancel pending animations\n\t\tif (_.#pendingRAF) {\n\t\t\tcancelAnimationFrame(_.#pendingRAF);\n\t\t\t_.#pendingRAF = null;\n\t\t}\n\t\tif (_.#pendingTimeout) {\n\t\t\tclearTimeout(_.#pendingTimeout);\n\t\t\t_.#pendingTimeout = null;\n\t\t}\n\n\t\t// Clean up event listeners\n\t\tif (_.#handlers.click) {\n\t\t\t_.removeEventListener('click', _.#handlers.click);\n\t\t}\n\t\tif (_.#dialog) {\n\t\t\tif (_.#handlers.dialogClick) {\n\t\t\t\t_.#dialog.removeEventListener(\n\t\t\t\t\t'click',\n\t\t\t\t\t_.#handlers.dialogClick\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (_.#handlers.cancel) {\n\t\t\t\t_.#dialog.removeEventListener('cancel', _.#handlers.cancel);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Bind event listeners for close buttons, backdrop, and escape key\n\t * @private\n\t */\n\t#bindEvents() {\n\t\tconst _ = this;\n\n\t\t// Handle close buttons with data-action-hide-dialog\n\t\t_.#handlers.click = (e) => {\n\t\t\tconst trigger = e.target.closest('[data-action-hide-dialog]');\n\t\t\tif (trigger) {\n\t\t\t\te.stopPropagation();\n\t\t\t\t_.hide(trigger);\n\t\t\t}\n\t\t};\n\t\t_.addEventListener('click', _.#handlers.click);\n\n\t\t// Handle backdrop click - detect clicks outside dialog bounds\n\t\t// This works because clicks on ::backdrop still fire on the dialog element\n\t\t_.#handlers.dialogClick = (e) => {\n\t\t\tconst rect = _.#dialog.getBoundingClientRect();\n\t\t\tconst clickedOutside =\n\t\t\t\te.clientX < rect.left ||\n\t\t\t\te.clientX > rect.right ||\n\t\t\t\te.clientY < rect.top ||\n\t\t\t\te.clientY > rect.bottom;\n\t\t\tif (clickedOutside) {\n\t\t\t\te.stopPropagation();\n\t\t\t\t_.hide();\n\t\t\t}\n\t\t};\n\t\t_.#dialog.addEventListener('click', _.#handlers.dialogClick);\n\n\t\t// Handle escape key - intercept native cancel and animate close\n\t\t_.#handlers.cancel = (e) => {\n\t\t\te.preventDefault();\n\t\t\te.stopPropagation();\n\t\t\t_.hide();\n\t\t};\n\t\t_.#dialog.addEventListener('cancel', _.#handlers.cancel);\n\t}\n\n\t/**\n\t * Show the dialog with animation\n\t * @param {HTMLElement} [triggerEl=null] - The element that triggered the show\n\t * @returns {boolean} False if show was prevented via beforeShow event\n\t */\n\tshow(triggerEl = null) {\n\t\tconst _ = this;\n\n\t\t// Check if already showing/shown\n\t\tif (_.#state === 'showing' || _.#state === 'shown') return true;\n\n\t\t// If currently hiding, ignore (let hide complete first)\n\t\tif (_.#state === 'hiding') return false;\n\n\t\t// Store trigger element for focus return later\n\t\t_.#triggerElement = triggerEl || null;\n\n\t\t// Fire beforeShow (cancelable)\n\t\tif (!_.#emit('beforeShow', { cancelable: true })) return false;\n\n\t\t// Set state to 'showing' and open native dialog\n\t\t_.#setState('showing');\n\t\t_.#dialog.showModal();\n\n\t\t// Double RAF ensures browser has painted the 'showing' state\n\t\t// before we transition to 'shown'\n\t\t_.#pendingRAF = requestAnimationFrame(() => {\n\t\t\t_.#pendingRAF = requestAnimationFrame(() => {\n\t\t\t\t_.#pendingRAF = null;\n\t\t\t\t_.#setState('shown');\n\n\t\t\t\t// Wait for transition to complete before firing 'shown' event\n\t\t\t\t_.#waitForTransition(() => {\n\t\t\t\t\t_.#emit('shown');\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Hide the dialog with animation\n\t * @param {HTMLElement} [triggerEl=null] - The element that triggered the hide\n\t * @returns {boolean} False if hide was prevented via beforeHide event\n\t */\n\thide(triggerEl = null) {\n\t\tconst _ = this;\n\n\t\t// Check if already hiding/hidden\n\t\tif (_.#state === 'hiding' || _.#state === 'hidden') return true;\n\n\t\t// If currently showing, ignore (let show complete first)\n\t\tif (_.#state === 'showing') return false;\n\n\t\t// Capture result from trigger element\n\t\t_.#result = triggerEl?.dataset?.result ?? null;\n\n\t\t// Fire beforeHide (cancelable)\n\t\tif (\n\t\t\t!_.#emit('beforeHide', {\n\t\t\t\tcancelable: true,\n\t\t\t\tresult: _.#result,\n\t\t\t\ttriggerElement: triggerEl,\n\t\t\t})\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Set state to 'hiding' - this triggers CSS exit animation\n\t\t_.#setState('hiding');\n\n\t\t// Wait for transition to complete\n\t\t_.#waitForTransition(() => {\n\t\t\t_.#dialog.close();\n\t\t\t_.#setState('hidden');\n\t\t\t_.#emit('hidden', {\n\t\t\t\tresult: _.#result,\n\t\t\t\ttriggerElement: triggerEl,\n\t\t\t});\n\n\t\t\t// Return focus to trigger element\n\t\t\tif (_.#triggerElement) _.#triggerElement.focus();\n\n\t\t\t// Clean up\n\t\t\t_.#triggerElement = null;\n\t\t\t_.#result = null;\n\t\t});\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Wait for CSS transition to complete with fallback timeout\n\t * @param {Function} callback - Called when transition completes\n\t * @private\n\t */\n\t#waitForTransition(callback) {\n\t\tconst _ = this;\n\t\tlet called = false;\n\n\t\tconst done = () => {\n\t\t\tif (called) return;\n\t\t\tcalled = true;\n\t\t\t_.#dialog.removeEventListener('transitionend', onTransitionEnd);\n\t\t\tif (_.#pendingTimeout) {\n\t\t\t\tclearTimeout(_.#pendingTimeout);\n\t\t\t\t_.#pendingTimeout = null;\n\t\t\t}\n\t\t\tcallback();\n\t\t};\n\n\t\tconst onTransitionEnd = (e) => {\n\t\t\tif (e.target === _.#dialog) done();\n\t\t};\n\n\t\t_.#dialog.addEventListener('transitionend', onTransitionEnd);\n\n\t\t_.#pendingTimeout = setTimeout(\n\t\t\tdone,\n\t\t\tDialogPanel.TRANSITION_FALLBACK_TIMEOUT\n\t\t);\n\t}\n\n\t/**\n\t * Set the component state\n\t * @param {string} newState - The new state\n\t * @private\n\t */\n\t#setState(newState) {\n\t\tthis.#state = newState;\n\t\tthis.setAttribute('state', newState);\n\t}\n\n\t/**\n\t * Emit a custom event\n\t * @param {string} name - Event name\n\t * @param {Object} options - Event options\n\t * @returns {boolean} False if event was cancelled\n\t * @private\n\t */\n\t#emit(name, options = {}) {\n\t\tconst _ = this;\n\t\tconst { cancelable = false, ...detail } = options;\n\n\t\tconst event = new CustomEvent(name, {\n\t\t\tbubbles: true,\n\t\t\tcomposed: true,\n\t\t\tcancelable,\n\t\t\tdetail: {\n\t\t\t\ttriggerElement: _.#triggerElement,\n\t\t\t\tresult: _.#result,\n\t\t\t\tstate: _.#state,\n\t\t\t\t...detail,\n\t\t\t},\n\t\t});\n\n\t\treturn _.dispatchEvent(event);\n\t}\n\n\t// Read-only properties\n\tget state() {\n\t\treturn this.#state;\n\t}\n\tget dialog() {\n\t\treturn this.#dialog;\n\t}\n\tget isOpen() {\n\t\treturn this.#state === 'showing' || this.#state === 'shown';\n\t}\n\tget triggerElement() {\n\t\treturn this.#triggerElement;\n\t}\n}\n\n/**\n * DialogBackdrop - A custom backdrop element that animates with the dialog-panel state.\n * Use this instead of native ::backdrop for consistent cross-browser animations.\n *\n * @extends HTMLElement\n */\nclass DialogBackdrop extends HTMLElement {\n\t#panel = null;\n\t#handlers = {\n\t\tclick: null,\n\t};\n\n\tconnectedCallback() {\n\t\tconst _ = this;\n\n\t\t// Find parent dialog-panel\n\t\t_.#panel = _.closest('dialog-panel');\n\n\t\tif (!_.#panel) {\n\t\t\tconsole.warn(\n\t\t\t\t'DialogBackdrop: Must be inside a <dialog-panel> element'\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle click to close\n\t\t_.#handlers.click = () => {\n\t\t\t_.#panel.hide();\n\t\t};\n\t\t_.addEventListener('click', _.#handlers.click);\n\t}\n\n\tdisconnectedCallback() {\n\t\tconst _ = this;\n\t\tif (_.#handlers.click) {\n\t\t\t_.removeEventListener('click', _.#handlers.click);\n\t\t}\n\t}\n}\n\n// Register the custom elements\n// Note: dialog-backdrop must be defined BEFORE dialog-panel,\n// because dialog-panel's connectedCallback may create dialog-backdrop elements\nif (!customElements.get('dialog-backdrop')) {\n\tcustomElements.define('dialog-backdrop', DialogBackdrop);\n}\nif (!customElements.get('dialog-panel')) {\n\tcustomElements.define('dialog-panel', DialogPanel);\n}\n\nexport { DialogPanel, DialogBackdrop };\n"],"names":[],"mappings":"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"}
|