@versini/ui-dialog 9.0.0 → 10.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 +235 -72
- package/dist/index.d.ts +22 -22
- package/dist/index.js +226 -550
- package/package.json +7 -5
package/dist/index.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-dialog
|
|
3
|
-
©
|
|
2
|
+
@versini/ui-dialog v10.0.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
8
8
|
import clsx from "clsx";
|
|
9
|
-
import { createPortal } from "react-dom";
|
|
10
9
|
|
|
11
|
-
;// CONCATENATED MODULE: ./src/common/constants.ts
|
|
12
10
|
const MESSAGEBOX_CLASSNAME = "av-messagebox";
|
|
13
|
-
const
|
|
11
|
+
const DIALOG_CLASSNAME = "av-dialog";
|
|
14
12
|
const TYPE_PANEL = "panel";
|
|
15
13
|
const TYPE_MESSAGEBOX = "messagebox";
|
|
16
14
|
const ANIMATION_SLIDE = "slide";
|
|
@@ -20,543 +18,131 @@ const MEDIUM = "medium";
|
|
|
20
18
|
const LARGE = "large";
|
|
21
19
|
const NONE = "none";
|
|
22
20
|
|
|
23
|
-
;// CONCATENATED MODULE: external "react/jsx-runtime"
|
|
24
21
|
|
|
25
|
-
;// CONCATENATED MODULE: external "react"
|
|
26
22
|
|
|
27
|
-
;// CONCATENATED MODULE: external "clsx"
|
|
28
|
-
|
|
29
|
-
;// CONCATENATED MODULE: external "react-dom"
|
|
30
|
-
|
|
31
|
-
;// CONCATENATED MODULE: ./src/components/Panel/dialogStackManager.ts
|
|
32
|
-
/**
|
|
33
|
-
* Dialog Stack Manager
|
|
34
|
-
*
|
|
35
|
-
* Implements the W3C WAI-ARIA APG pattern for managing nested modal dialogs.
|
|
36
|
-
* This module maintains a stack of open dialogs and coordinates focus event
|
|
37
|
-
* listeners between them.
|
|
38
|
-
*
|
|
39
|
-
* Key features:
|
|
40
|
-
* - Only the topmost dialog has active focus listeners
|
|
41
|
-
* - When a nested dialog opens, parent listeners are suspended
|
|
42
|
-
* - When a dialog closes, parent listeners are restored
|
|
43
|
-
* - Programmatic focus changes are flagged to prevent event interference
|
|
44
|
-
*
|
|
45
|
-
* @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/
|
|
46
|
-
*/ /**
|
|
47
|
-
* Stack of currently open dialogs. The last item is the topmost dialog.
|
|
48
|
-
*/ const openDialogStack = [];
|
|
49
|
-
/**
|
|
50
|
-
* Flag to ignore focus changes during programmatic focus operations.
|
|
51
|
-
* When true, focus event handlers should return early to prevent interference.
|
|
52
|
-
*
|
|
53
|
-
* This is critical for nested dialogs: when a child dialog closes and
|
|
54
|
-
* programmatically returns focus to its trigger element inside the parent,
|
|
55
|
-
* we don't want the parent's focusin handler to interfere.
|
|
56
|
-
*/ let ignoreFocusChanges = false;
|
|
57
|
-
/**
|
|
58
|
-
* Get whether focus changes should be ignored.
|
|
59
|
-
* Focus event handlers should check this and return early if true.
|
|
60
|
-
*/ function shouldIgnoreFocusChanges() {
|
|
61
|
-
return ignoreFocusChanges;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Execute a function while ignoring focus change events.
|
|
65
|
-
* Use this when programmatically moving focus to prevent event handlers
|
|
66
|
-
* from interfering.
|
|
67
|
-
*
|
|
68
|
-
* @param fn - Function to execute (typically contains focus() calls)
|
|
69
|
-
*/ function withIgnoredFocusChanges(fn) {
|
|
70
|
-
ignoreFocusChanges = true;
|
|
71
|
-
try {
|
|
72
|
-
fn();
|
|
73
|
-
} finally{
|
|
74
|
-
// Use setTimeout to ensure the flag stays true through any
|
|
75
|
-
// microtasks that might fire focus events
|
|
76
|
-
setTimeout(()=>{
|
|
77
|
-
ignoreFocusChanges = false;
|
|
78
|
-
}, 0);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
23
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
*/ function registerDialog(entry) {
|
|
87
|
-
// If there's already a dialog open, suspend its listeners
|
|
88
|
-
if (openDialogStack.length > 0) {
|
|
89
|
-
const currentTop = openDialogStack[openDialogStack.length - 1];
|
|
90
|
-
currentTop.removeListeners();
|
|
91
|
-
}
|
|
92
|
-
// Add the new dialog to the stack and activate its listeners
|
|
93
|
-
openDialogStack.push(entry);
|
|
94
|
-
entry.addListeners();
|
|
95
|
-
}
|
|
24
|
+
* Reference-counted scroll lock for document.documentElement.
|
|
25
|
+
* Supports nested dialogs: overflow is only restored when the last lock is released.
|
|
26
|
+
*/ let scrollLockCount = 0;
|
|
27
|
+
let previousOverflow = "";
|
|
96
28
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
// Remove listeners from the dialog being closed
|
|
107
|
-
const [removedEntry] = openDialogStack.splice(index, 1);
|
|
108
|
-
removedEntry.removeListeners();
|
|
109
|
-
// If there's a parent dialog, restore its listeners
|
|
110
|
-
if (openDialogStack.length > 0) {
|
|
111
|
-
const newTop = openDialogStack[openDialogStack.length - 1];
|
|
112
|
-
newTop.addListeners();
|
|
29
|
+
* Acquire a scroll lock. Call the returned function to release.
|
|
30
|
+
* Multiple calls stack; overflow is restored only when all locks are released.
|
|
31
|
+
*/ function acquireScrollLock() {
|
|
32
|
+
scrollLockCount += 1;
|
|
33
|
+
if (scrollLockCount === 1) {
|
|
34
|
+
previousOverflow = document.documentElement.style.overflow;
|
|
35
|
+
document.documentElement.style.overflow = "hidden";
|
|
113
36
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
*/ function getOpenDialogCount() {
|
|
119
|
-
return openDialogStack.length;
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Check if a given dialog is the topmost (current) dialog.
|
|
123
|
-
*
|
|
124
|
-
* @param dialogRef - Reference to the dialog element to check
|
|
125
|
-
*/ function isTopmostDialog(dialogRef) {
|
|
126
|
-
if (openDialogStack.length === 0) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
return openDialogStack[openDialogStack.length - 1].dialogRef === dialogRef;
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* Reset the stack (for testing purposes only).
|
|
133
|
-
* @internal
|
|
134
|
-
*/ function _resetStackForTesting() {
|
|
135
|
-
while(openDialogStack.length > 0){
|
|
136
|
-
openDialogStack.pop();
|
|
137
|
-
}
|
|
138
|
-
ignoreFocusChanges = false;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
;// CONCATENATED MODULE: ./src/components/Panel/PanelPortal.tsx
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Selector for all focusable elements within a container. Based on W3C WAI-ARIA
|
|
150
|
-
* practices for dialog focus management.
|
|
151
|
-
*/ const FOCUSABLE_SELECTOR = [
|
|
152
|
-
'a[href]:not([disabled]):not([tabindex="-1"])',
|
|
153
|
-
'button:not([disabled]):not([tabindex="-1"])',
|
|
154
|
-
'textarea:not([disabled]):not([tabindex="-1"])',
|
|
155
|
-
'input:not([disabled]):not([tabindex="-1"])',
|
|
156
|
-
'select:not([disabled]):not([tabindex="-1"])',
|
|
157
|
-
'[tabindex]:not([tabindex="-1"]):not([disabled])',
|
|
158
|
-
'audio[controls]:not([tabindex="-1"])',
|
|
159
|
-
'video[controls]:not([tabindex="-1"])',
|
|
160
|
-
'details:not([tabindex="-1"])'
|
|
161
|
-
].join(", ");
|
|
162
|
-
/**
|
|
163
|
-
* Portal component for rendering the Panel as a modal dialog using the native
|
|
164
|
-
* HTML <dialog> element with showModal(). This provides:
|
|
165
|
-
* - Native focus trapping (works correctly on iPad with physical keyboard)
|
|
166
|
-
* - Native ESC key handling via cancel event
|
|
167
|
-
* - Native backdrop via ::backdrop pseudo-element
|
|
168
|
-
* - Native inert background (no need for manual scroll lock)
|
|
169
|
-
* - Top layer rendering (no need for createPortal)
|
|
170
|
-
*
|
|
171
|
-
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
|
|
172
|
-
* @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
|
|
173
|
-
*
|
|
174
|
-
*/ function PanelPortal({ open, onClose, children, className, style, title, initialFocus = 0, kind = /* inlined export .TYPE_PANEL */ ("panel") }) {
|
|
175
|
-
const labelId = useId();
|
|
176
|
-
const descriptionId = useId();
|
|
177
|
-
const dialogRef = useRef(null);
|
|
178
|
-
const previouslyFocusedRef = useRef(null);
|
|
179
|
-
/**
|
|
180
|
-
* Get all focusable elements within the dialog. Excludes focus sentinel
|
|
181
|
-
* elements used for circular focus trapping.
|
|
182
|
-
*/ const getFocusableElements = useCallback(()=>{
|
|
183
|
-
/* v8 ignore start - defensive check, dialogRef is always set when open */ if (!dialogRef.current) {
|
|
184
|
-
return [];
|
|
185
|
-
}
|
|
186
|
-
/* v8 ignore stop */ const elements = dialogRef.current.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
187
|
-
return Array.from(elements).filter((el)=>el.offsetParent !== null && // Filter out hidden elements
|
|
188
|
-
!el.hasAttribute("data-focus-sentinel"));
|
|
189
|
-
}, []);
|
|
190
|
-
/**
|
|
191
|
-
* Focus a specific element by index, or the element referenced by a ref.
|
|
192
|
-
*/ /* v8 ignore start - focus element logic with multiple branches */ const focusElement = useCallback((target)=>{
|
|
193
|
-
if (typeof target === "number") {
|
|
194
|
-
if (target === -1) {
|
|
195
|
-
// -1 means don't focus anything.
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
const focusableElements = getFocusableElements();
|
|
199
|
-
if (focusableElements.length > 0) {
|
|
200
|
-
const index = Math.min(target, focusableElements.length - 1);
|
|
201
|
-
focusableElements[index]?.focus();
|
|
202
|
-
}
|
|
203
|
-
} else if (target?.current) {
|
|
204
|
-
target.current.focus();
|
|
205
|
-
}
|
|
206
|
-
}, [
|
|
207
|
-
getFocusableElements
|
|
208
|
-
]);
|
|
209
|
-
/* v8 ignore stop */ /**
|
|
210
|
-
* Handle the native cancel event (fired when ESC is pressed). This replaces
|
|
211
|
-
* the custom keydown handler since the native dialog handles ESC
|
|
212
|
-
* automatically.
|
|
213
|
-
*/ const handleCancel = useCallback((event)=>{
|
|
214
|
-
// Prevent the default close behavior so we can control it via onClose.
|
|
215
|
-
event.preventDefault();
|
|
216
|
-
onClose();
|
|
217
|
-
}, [
|
|
218
|
-
onClose
|
|
219
|
-
]);
|
|
220
|
-
/**
|
|
221
|
-
* Handle Tab key to implement circular focus trapping. Native dialog focus
|
|
222
|
-
* trapping may not be circular in all browsers, so we manually wrap focus from
|
|
223
|
-
* last to first element (and vice versa). Uses document-level event listener
|
|
224
|
-
* for better iPad physical keyboard support.
|
|
225
|
-
*
|
|
226
|
-
* IMPORTANT: On iPad Safari with a physical keyboard, the Tab key does not
|
|
227
|
-
* automatically navigate between focusable elements. We must manually handle
|
|
228
|
-
* ALL Tab key presses, not just wrapping cases.
|
|
229
|
-
*
|
|
230
|
-
*/ const handleKeyDown = useCallback((event)=>{
|
|
231
|
-
if (event.key !== "Tab" || !dialogRef.current) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const focusableElements = getFocusableElements();
|
|
235
|
-
/* v8 ignore start - edge case: dialog with no focusable elements */ if (focusableElements.length === 0) {
|
|
236
|
-
event.preventDefault();
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
/* v8 ignore stop */ const firstElement = focusableElements[0];
|
|
240
|
-
const lastElement = focusableElements[focusableElements.length - 1];
|
|
241
|
-
const activeElement = document.activeElement;
|
|
242
|
-
// Find the current index of the focused element.
|
|
243
|
-
const currentIndex = focusableElements.indexOf(activeElement);
|
|
244
|
-
/**
|
|
245
|
-
* Always prevent default and manually handle focus navigation. This is
|
|
246
|
-
* required for iPad Safari with physical keyboard, where Tab key doesn't
|
|
247
|
-
* automatically navigate between focusable elements.
|
|
248
|
-
*/ event.preventDefault();
|
|
249
|
-
if (event.shiftKey) {
|
|
250
|
-
// Shift+Tab: move to previous element, wrap to last if on first.
|
|
251
|
-
if (activeElement === firstElement || currentIndex <= 0) {
|
|
252
|
-
lastElement?.focus();
|
|
253
|
-
} else {
|
|
254
|
-
focusableElements[currentIndex - 1]?.focus();
|
|
255
|
-
}
|
|
256
|
-
} else {
|
|
257
|
-
// Tab: move to next element, wrap to first if on last.
|
|
258
|
-
if (activeElement === lastElement || currentIndex >= focusableElements.length - 1) {
|
|
259
|
-
firstElement?.focus();
|
|
260
|
-
} else {
|
|
261
|
-
focusableElements[currentIndex + 1]?.focus();
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
}, [
|
|
265
|
-
getFocusableElements
|
|
266
|
-
]);
|
|
267
|
-
/**
|
|
268
|
-
* Handle focus events to ensure focus stays within the dialog. This catches
|
|
269
|
-
* focus that escapes via Tab key on iPad Safari or other means.
|
|
270
|
-
*
|
|
271
|
-
* Uses the dialog stack manager's ignore flag to prevent interference during
|
|
272
|
-
* programmatic focus operations (e.g., when a nested dialog closes and returns
|
|
273
|
-
* focus to its trigger element).
|
|
274
|
-
*
|
|
275
|
-
*/ /* v8 ignore start - focus escape handling for iPad Safari, hard to test in jsdom */ const handleFocusIn = useCallback((event)=>{
|
|
276
|
-
// Ignore focus changes triggered by programmatic focus operations.
|
|
277
|
-
if (shouldIgnoreFocusChanges()) {
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
if (!dialogRef.current || dialogRef.current.contains(event.target)) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
// Focus escaped the dialog, bring it back.
|
|
284
|
-
const focusableElements = getFocusableElements();
|
|
285
|
-
if (focusableElements.length > 0) {
|
|
286
|
-
focusableElements[0]?.focus();
|
|
287
|
-
}
|
|
288
|
-
}, [
|
|
289
|
-
getFocusableElements
|
|
290
|
-
]);
|
|
291
|
-
/* v8 ignore stop */ /**
|
|
292
|
-
* Handle clicks on the backdrop (the area outside the dialog content). Native
|
|
293
|
-
* dialog doesn't provide backdrop click handling, so we use the dialog
|
|
294
|
-
* element's click event and check if the click target is the dialog itself
|
|
295
|
-
* (not a child element).
|
|
296
|
-
*/ /* v8 ignore start - backdrop clicks are disabled by design in current implementation */ const handleDialogClick = useCallback((_event)=>{
|
|
297
|
-
/**
|
|
298
|
-
* If the click is directly on the dialog element (the backdrop area), not on
|
|
299
|
-
* any child element, then close the dialog. Currently disabled -
|
|
300
|
-
* outsidePress is false by design. if (_event.target === dialogRef.current)
|
|
301
|
-
* { onClose(); }
|
|
302
|
-
*/ }, []);
|
|
303
|
-
/* v8 ignore stop */ /**
|
|
304
|
-
* Focus sentinel handler - when a sentinel element receives focus, redirect
|
|
305
|
-
* focus to the appropriate element inside the dialog. This handles iPad
|
|
306
|
-
* Safari's Tab key behavior which can bypass event listeners.
|
|
307
|
-
*/ const handleSentinelFocus = useCallback((position)=>{
|
|
308
|
-
const focusableElements = getFocusableElements();
|
|
309
|
-
/* v8 ignore start - edge case: dialog with no focusable elements */ if (focusableElements.length === 0) {
|
|
310
|
-
return;
|
|
37
|
+
return ()=>{
|
|
38
|
+
scrollLockCount -= 1;
|
|
39
|
+
if (scrollLockCount === 0) {
|
|
40
|
+
document.documentElement.style.overflow = previousOverflow;
|
|
311
41
|
}
|
|
312
|
-
|
|
313
|
-
// Focus came from the end, wrap to last element.
|
|
314
|
-
focusableElements[focusableElements.length - 1]?.focus();
|
|
315
|
-
} else {
|
|
316
|
-
// Focus came from the start, wrap to first element.
|
|
317
|
-
focusableElements[0]?.focus();
|
|
318
|
-
}
|
|
319
|
-
}, [
|
|
320
|
-
getFocusableElements
|
|
321
|
-
]);
|
|
322
|
-
/**
|
|
323
|
-
* Effect to show/hide the dialog and manage focus. Uses the dialog stack
|
|
324
|
-
* manager to coordinate listeners between nested dialogs.
|
|
325
|
-
*/ /* v8 ignore start - complex dialog lifecycle management */ useEffect(()=>{
|
|
326
|
-
const dialog = dialogRef.current;
|
|
327
|
-
/* v8 ignore start - defensive check */ if (!dialog) {
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
/* v8 ignore stop */ // Store the currently focused element to restore later.
|
|
331
|
-
previouslyFocusedRef.current = document.activeElement;
|
|
332
|
-
// Show the dialog as a modal.
|
|
333
|
-
if (!dialog.open) {
|
|
334
|
-
dialog.showModal();
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Add cancel event listener for ESC key (always needed, not managed by
|
|
338
|
-
* stack).
|
|
339
|
-
*/ dialog.addEventListener("cancel", handleCancel);
|
|
340
|
-
/**
|
|
341
|
-
* Define listener management functions for the stack manager. These will be
|
|
342
|
-
* called when this dialog becomes/stops being the topmost dialog.
|
|
343
|
-
*/ const addListeners = ()=>{
|
|
344
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
345
|
-
document.addEventListener("focusin", handleFocusIn);
|
|
346
|
-
};
|
|
347
|
-
const removeListeners = ()=>{
|
|
348
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
349
|
-
document.removeEventListener("focusin", handleFocusIn);
|
|
350
|
-
};
|
|
351
|
-
/**
|
|
352
|
-
* Register this dialog with the stack manager. This will suspend parent
|
|
353
|
-
* dialog listeners if any exist.
|
|
354
|
-
*/ registerDialog({
|
|
355
|
-
dialogRef: dialog,
|
|
356
|
-
addListeners,
|
|
357
|
-
removeListeners
|
|
358
|
-
});
|
|
359
|
-
/**
|
|
360
|
-
* Set initial focus after a small delay to ensure the DOM is ready. This
|
|
361
|
-
* works around React's autoFocus prop not working with native dialog.
|
|
362
|
-
*/ const focusTimer = setTimeout(()=>{
|
|
363
|
-
focusElement(initialFocus);
|
|
364
|
-
}, 0);
|
|
365
|
-
// Capture the previously focused element for restoration in cleanup.
|
|
366
|
-
const previouslyFocused = previouslyFocusedRef.current;
|
|
367
|
-
return ()=>{
|
|
368
|
-
clearTimeout(focusTimer);
|
|
369
|
-
dialog.removeEventListener("cancel", handleCancel);
|
|
370
|
-
/**
|
|
371
|
-
* Unregister from the stack manager. This will restore parent dialog
|
|
372
|
-
* listeners if any exist.
|
|
373
|
-
*/ unregisterDialog(dialog);
|
|
374
|
-
// Close the dialog if it's still open.
|
|
375
|
-
if (dialog.open) {
|
|
376
|
-
dialog.close();
|
|
377
|
-
}
|
|
378
|
-
/**
|
|
379
|
-
* Restore focus to the previously focused element if it's still in the DOM.
|
|
380
|
-
* Use withIgnoredFocusChanges to prevent parent dialog's handleFocusIn from
|
|
381
|
-
* interfering with focus restoration.
|
|
382
|
-
*/ if (previouslyFocused?.isConnected) {
|
|
383
|
-
withIgnoredFocusChanges(()=>{
|
|
384
|
-
if (previouslyFocused.isConnected) {
|
|
385
|
-
previouslyFocused.focus();
|
|
386
|
-
}
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
}, [
|
|
391
|
-
handleCancel,
|
|
392
|
-
handleKeyDown,
|
|
393
|
-
handleFocusIn,
|
|
394
|
-
initialFocus,
|
|
395
|
-
focusElement
|
|
396
|
-
]);
|
|
397
|
-
/* v8 ignore stop */ /* v8 ignore start - early return when panel is closed */ if (!open) {
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
/* v8 ignore stop */ const isMessageBox = kind === TYPE_MESSAGEBOX;
|
|
401
|
-
const dialogClass = clsx(/**
|
|
402
|
-
* Center the dialog on screen with fixed positioning. Native dialog uses
|
|
403
|
-
* position: fixed by default.
|
|
404
|
-
* - Panel on mobile: inset-0 + no margin for full screen.
|
|
405
|
-
* - Panel on desktop: inset-x-0 + top/bottom auto for vertical centering.
|
|
406
|
-
* - MessageBox: Always centered (both mobile and desktop) since it doesn't
|
|
407
|
-
* take full screen.
|
|
408
|
-
*/ "fixed max-h-none max-w-none p-0", {
|
|
409
|
-
// Panel: full screen on mobile, centered on desktop
|
|
410
|
-
"inset-0 m-0 sm:inset-auto sm:inset-x-0 sm:top-1/2 sm:-translate-y-1/2 sm:mx-auto": !isMessageBox,
|
|
411
|
-
// MessageBox: always centered at all breakpoints
|
|
412
|
-
"inset-auto inset-x-0 top-1/2 -translate-y-1/2 mx-auto z-100": isMessageBox
|
|
413
|
-
}, /**
|
|
414
|
-
* Backdrop styling via Tailwind's backdrop: variant for ::backdrop
|
|
415
|
-
* pseudo-element. Full black on mobile, 80% opacity on desktop (matches
|
|
416
|
-
* original overlay).
|
|
417
|
-
*/ "backdrop:bg-black sm:backdrop:bg-black/80", className);
|
|
418
|
-
return /*#__PURE__*/ createPortal(/*#__PURE__*/ jsxs("dialog", {
|
|
419
|
-
ref: dialogRef,
|
|
420
|
-
"aria-labelledby": labelId,
|
|
421
|
-
"aria-describedby": descriptionId,
|
|
422
|
-
className: dialogClass,
|
|
423
|
-
style: style,
|
|
424
|
-
onClick: handleDialogClick,
|
|
425
|
-
children: [
|
|
426
|
-
/*#__PURE__*/ jsx("span", {
|
|
427
|
-
tabIndex: 0,
|
|
428
|
-
onFocus: ()=>handleSentinelFocus("start"),
|
|
429
|
-
"data-focus-sentinel": "start",
|
|
430
|
-
style: {
|
|
431
|
-
position: "absolute",
|
|
432
|
-
width: 1,
|
|
433
|
-
height: 1,
|
|
434
|
-
padding: 0,
|
|
435
|
-
margin: -1,
|
|
436
|
-
overflow: "hidden",
|
|
437
|
-
clip: "rect(0, 0, 0, 0)",
|
|
438
|
-
whiteSpace: "nowrap",
|
|
439
|
-
border: 0
|
|
440
|
-
},
|
|
441
|
-
"aria-hidden": "true"
|
|
442
|
-
}),
|
|
443
|
-
/*#__PURE__*/ jsx("span", {
|
|
444
|
-
id: labelId,
|
|
445
|
-
className: "sr-only",
|
|
446
|
-
children: title
|
|
447
|
-
}),
|
|
448
|
-
children,
|
|
449
|
-
/*#__PURE__*/ jsx("span", {
|
|
450
|
-
tabIndex: 0,
|
|
451
|
-
onFocus: ()=>handleSentinelFocus("end"),
|
|
452
|
-
"data-focus-sentinel": "end",
|
|
453
|
-
style: {
|
|
454
|
-
position: "absolute",
|
|
455
|
-
width: 1,
|
|
456
|
-
height: 1,
|
|
457
|
-
padding: 0,
|
|
458
|
-
margin: -1,
|
|
459
|
-
overflow: "hidden",
|
|
460
|
-
clip: "rect(0, 0, 0, 0)",
|
|
461
|
-
whiteSpace: "nowrap",
|
|
462
|
-
border: 0
|
|
463
|
-
},
|
|
464
|
-
"aria-hidden": "true"
|
|
465
|
-
})
|
|
466
|
-
]
|
|
467
|
-
}), document.body);
|
|
42
|
+
};
|
|
468
43
|
}
|
|
469
44
|
|
|
470
|
-
|
|
45
|
+
|
|
471
46
|
|
|
472
47
|
|
|
473
48
|
const getFooterAndHeaderCommonClasses = ({ blurEffect })=>{
|
|
474
49
|
return clsx("absolute left-0 right-0 z-20 backdrop-brightness-50", {
|
|
475
|
-
"backdrop-blur-sm": blurEffect === /* inlined export .SMALL */
|
|
476
|
-
"backdrop-blur-md": blurEffect === /* inlined export .MEDIUM */
|
|
477
|
-
"backdrop-blur-lg": blurEffect === /* inlined export .LARGE */
|
|
478
|
-
"bg-surface-darker": blurEffect === /* inlined export .NONE */
|
|
50
|
+
"backdrop-blur-sm": blurEffect === (/* inlined export .SMALL */"small"),
|
|
51
|
+
"backdrop-blur-md": blurEffect === (/* inlined export .MEDIUM */"medium"),
|
|
52
|
+
"backdrop-blur-lg": blurEffect === (/* inlined export .LARGE */"large"),
|
|
53
|
+
"bg-surface-darker": blurEffect === (/* inlined export .NONE */"none")
|
|
479
54
|
});
|
|
480
55
|
};
|
|
481
|
-
const
|
|
482
|
-
const effectiveMaxHeight = maxHeight ?? (kind === /* inlined export .TYPE_PANEL */
|
|
56
|
+
const getDialogClassName = ({ className, kind, borderMode, animation, maxWidth = (/* inlined export .MEDIUM */"medium"), maxHeight, blurEffect = (/* inlined export .NONE */"none"), hasFooter })=>{
|
|
57
|
+
const effectiveMaxHeight = maxHeight ?? (kind === (/* inlined export .TYPE_PANEL */"panel") ? (/* inlined export .LARGE */"large") : (/* inlined export .SMALL */"small"));
|
|
483
58
|
return {
|
|
484
|
-
outerWrapper: clsx("
|
|
59
|
+
outerWrapper: clsx("plume plume-lighter flex flex-col bg-surface-dark overflow-hidden", "p-0", {
|
|
485
60
|
"duration-200 ease-out": animation,
|
|
486
61
|
/**
|
|
487
62
|
* Panel styles
|
|
488
|
-
|
|
63
|
+
* border-0 resets the native <dialog> border on mobile;
|
|
64
|
+
* sm:border restores it on desktop.
|
|
65
|
+
*/ [`${DIALOG_CLASSNAME} border-0 sm:rounded-3xl sm:border m-auto! sm:h-fit backdrop:bg-surface-darkest/80`]: kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
489
66
|
/**
|
|
490
|
-
* Widths and max widths for Panel
|
|
491
|
-
*/ ["w-full sm:w-[95%] md:max-w-2xl"]: kind === /* inlined export .TYPE_PANEL */
|
|
492
|
-
["w-full sm:w-[95%] md:max-w-3xl"]: kind === /* inlined export .TYPE_PANEL */
|
|
493
|
-
["w-full sm:w-[95%] md:max-w-4xl"]: kind === /* inlined export .TYPE_PANEL */
|
|
67
|
+
* Widths and max widths for Panel
|
|
68
|
+
*/ ["w-full sm:w-[95%] md:max-w-2xl"]: kind === (/* inlined export .TYPE_PANEL */"panel") && maxWidth === (/* inlined export .SMALL */"small"),
|
|
69
|
+
["w-full sm:w-[95%] md:max-w-3xl"]: kind === (/* inlined export .TYPE_PANEL */"panel") && maxWidth === (/* inlined export .MEDIUM */"medium"),
|
|
70
|
+
["w-full sm:w-[95%] md:max-w-4xl"]: kind === (/* inlined export .TYPE_PANEL */"panel") && maxWidth === (/* inlined export .LARGE */"large"),
|
|
494
71
|
/**
|
|
495
72
|
* Heights and max heights for Panel
|
|
496
|
-
* Mobile: full
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
73
|
+
* Mobile: full viewport height
|
|
74
|
+
* Desktop: h-auto (overridden to fit-content by the injected
|
|
75
|
+
* stylesheet in Dialog.tsx, required for native <dialog> with
|
|
76
|
+
* position:fixed + inset:0 + margin:auto), clamped by max-height.
|
|
77
|
+
*/ "h-dvh sm:h-auto min-h-40 sm:max-h-[40dvh]": kind === (/* inlined export .TYPE_PANEL */"panel") && effectiveMaxHeight === (/* inlined export .SMALL */"small"),
|
|
78
|
+
"h-dvh sm:h-auto min-h-40 sm:max-h-[60dvh]": kind === (/* inlined export .TYPE_PANEL */"panel") && effectiveMaxHeight === (/* inlined export .MEDIUM */"medium"),
|
|
79
|
+
"h-dvh sm:h-auto min-h-40 sm:max-h-[95dvh]": kind === (/* inlined export .TYPE_PANEL */"panel") && effectiveMaxHeight === (/* inlined export .LARGE */"large"),
|
|
500
80
|
/**
|
|
501
81
|
* Panel border colors
|
|
502
|
-
*/ "sm:border-border-dark": borderMode === "dark" && kind === /* inlined export .TYPE_PANEL */
|
|
503
|
-
"sm:border-border-accent": borderMode === "light" && kind === /* inlined export .TYPE_PANEL */
|
|
82
|
+
*/ "sm:border-border-dark": borderMode === "dark" && kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
83
|
+
"sm:border-border-accent": borderMode === "light" && kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
84
|
+
"sm:border-border-medium": borderMode === "medium" && kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
504
85
|
/**
|
|
505
86
|
* Messagebox styles
|
|
506
|
-
*/ [`${MESSAGEBOX_CLASSNAME} rounded-3xl border`]: kind === TYPE_MESSAGEBOX,
|
|
87
|
+
*/ [`${MESSAGEBOX_CLASSNAME} rounded-3xl border m-auto! backdrop:bg-surface-darkest/80`]: kind === TYPE_MESSAGEBOX,
|
|
507
88
|
/**
|
|
508
|
-
* Widths and max widths for Messagebox
|
|
509
|
-
*/ ["w-[95%] sm:w-[50%] md:max-w-2xl"]: kind === TYPE_MESSAGEBOX
|
|
89
|
+
* Widths and max widths for Messagebox
|
|
90
|
+
*/ ["w-[95%] sm:w-[50%] md:max-w-2xl"]: kind === TYPE_MESSAGEBOX,
|
|
510
91
|
/**
|
|
511
92
|
* Heights and max heights for Messagebox
|
|
512
|
-
*/ "h-64": kind === TYPE_MESSAGEBOX && effectiveMaxHeight === /* inlined export .SMALL */
|
|
513
|
-
"h-80": kind === TYPE_MESSAGEBOX && effectiveMaxHeight === /* inlined export .MEDIUM */
|
|
514
|
-
"h-96": kind === TYPE_MESSAGEBOX && effectiveMaxHeight === /* inlined export .LARGE */
|
|
93
|
+
*/ "h-64": kind === TYPE_MESSAGEBOX && effectiveMaxHeight === (/* inlined export .SMALL */"small"),
|
|
94
|
+
"h-80": kind === TYPE_MESSAGEBOX && effectiveMaxHeight === (/* inlined export .MEDIUM */"medium"),
|
|
95
|
+
"h-96": kind === TYPE_MESSAGEBOX && effectiveMaxHeight === (/* inlined export .LARGE */"large"),
|
|
515
96
|
/**
|
|
516
97
|
* Messagebox border colors
|
|
517
98
|
*/ "border-border-dark": borderMode === "dark" && kind === TYPE_MESSAGEBOX,
|
|
518
99
|
"border-border-accent": borderMode === "light" && kind === TYPE_MESSAGEBOX,
|
|
519
|
-
|
|
520
|
-
}),
|
|
521
|
-
innerWrapper: "content flex flex-col rounded-[inherit] relative min-h-
|
|
100
|
+
"border-border-medium": borderMode === "medium" && kind === TYPE_MESSAGEBOX
|
|
101
|
+
}, className),
|
|
102
|
+
innerWrapper: "content flex flex-col rounded-[inherit] relative flex-1 min-h-0",
|
|
522
103
|
scrollableContent: clsx("flex-1 overflow-y-auto overflow-x-hidden", "pt-12", {
|
|
523
|
-
"pb-
|
|
104
|
+
"pb-14": hasFooter
|
|
524
105
|
}),
|
|
525
106
|
footer: clsx(getFooterAndHeaderCommonClasses({
|
|
526
107
|
blurEffect
|
|
527
|
-
}), "
|
|
528
|
-
"
|
|
529
|
-
"
|
|
108
|
+
}), "bottom-0", "sm:min-h-auto h-12", {
|
|
109
|
+
"px-2 pt-4 pb-8 sm:pt-2 sm:pb-2": kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
110
|
+
"p-2": kind === TYPE_MESSAGEBOX,
|
|
111
|
+
"min-h-20": hasFooter && kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
112
|
+
"sm:rounded-b-3xl": kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
530
113
|
"rounded-b-3xl": kind === TYPE_MESSAGEBOX
|
|
531
114
|
}),
|
|
532
115
|
header: clsx("flex flex-row-reverse items-center justify-between h-12", getFooterAndHeaderCommonClasses({
|
|
533
116
|
blurEffect
|
|
534
117
|
}), "top-0", {
|
|
535
|
-
"sm:rounded-t-3xl": kind === /* inlined export .TYPE_PANEL */
|
|
118
|
+
"sm:rounded-t-3xl": kind === (/* inlined export .TYPE_PANEL */"panel"),
|
|
536
119
|
"rounded-t-3xl": kind === TYPE_MESSAGEBOX
|
|
537
120
|
}),
|
|
538
121
|
title: "mb-0 pt-2 pl-4 pr-2 pb-2",
|
|
539
122
|
closeWrapper: "pr-[18px]",
|
|
540
|
-
closeButton: clsx("flex items-center justify-center", "size-3", "p-1", "rounded-full", "border", "border-transparent", "text-[rgba(255,96,92,1)]", "bg-[rgba(255,96,92,1)]", "shadow-[0_0_0_0.5px_red]", "focus:outline", "focus:outline-2", "focus:outline-offset-2", "focus:outline-focus-light", "hover:text-copy-dark", "focus:text-copy-dark", "active:bg-[#ba504a]", // Extended touch target using pseudo-element
|
|
123
|
+
closeButton: clsx("flex items-center justify-center", "size-3", "p-1", "rounded-full", "border", "border-transparent", "cursor-pointer", "text-[rgba(255,96,92,1)]", "bg-[rgba(255,96,92,1)]", "shadow-[0_0_0_0.5px_red]", "focus:outline", "focus:outline-2", "focus:outline-offset-2", "focus:outline-focus-light", "hover:text-copy-dark", "focus:text-copy-dark", "active:bg-[#ba504a]", // Extended touch target using pseudo-element
|
|
541
124
|
"relative", "before:content-['']", "before:absolute", "before:-top-4", "before:-right-4", "before:-bottom-4", "before:-left-4"),
|
|
542
125
|
content: "p-4 rounded-3xl"
|
|
543
126
|
};
|
|
544
127
|
};
|
|
545
128
|
|
|
546
|
-
;// CONCATENATED MODULE: ./src/components/Panel/Panel.tsx
|
|
547
129
|
|
|
548
130
|
|
|
549
131
|
|
|
550
132
|
|
|
551
133
|
|
|
552
|
-
const
|
|
134
|
+
const TABBABLE_SELECTOR = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
135
|
+
const Dialog = ({ open, onOpenChange, title, children, footer, borderMode = "light", kind = (/* inlined export .TYPE_PANEL */"panel"), className, animation = false, animationType = (/* inlined export .ANIMATION_SLIDE */"slide"), maxWidth = (/* inlined export .MEDIUM */"medium"), maxHeight, blurEffect = (/* inlined export .NONE */"none"), initialFocus })=>{
|
|
136
|
+
const dialogRef = useRef(null);
|
|
553
137
|
const originalTitleRef = useRef("");
|
|
554
|
-
|
|
138
|
+
const titleId = useId();
|
|
139
|
+
const descriptionId = useId();
|
|
140
|
+
/* v8 ignore start */ const [animationStyles, setAnimationStyles] = useState(!animation ? {} : animationType === (/* inlined export .ANIMATION_FADE */"fade") ? {
|
|
555
141
|
opacity: 0
|
|
556
142
|
} : {
|
|
557
143
|
transform: "translateY(-100vh)"
|
|
558
144
|
});
|
|
559
|
-
/* v8 ignore stop */ const
|
|
145
|
+
/* v8 ignore stop */ const dialogClassName = getDialogClassName({
|
|
560
146
|
className,
|
|
561
147
|
kind,
|
|
562
148
|
borderMode,
|
|
@@ -566,16 +152,14 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
566
152
|
blurEffect,
|
|
567
153
|
hasFooter: Boolean(footer)
|
|
568
154
|
});
|
|
569
|
-
|
|
570
|
-
* Handle close button click.
|
|
571
|
-
*/ const handleClose = useCallback(()=>{
|
|
155
|
+
const handleClose = useCallback(()=>{
|
|
572
156
|
onOpenChange(false);
|
|
573
157
|
}, [
|
|
574
158
|
onOpenChange
|
|
575
159
|
]);
|
|
576
160
|
/**
|
|
577
|
-
* If the
|
|
578
|
-
* closed, restore the original document.title.
|
|
161
|
+
* If the dialog is opened, set the document title to the dialog's title.
|
|
162
|
+
* If it's closed, restore the original document.title.
|
|
579
163
|
*/ /* v8 ignore start - document title manipulation */ useEffect(()=>{
|
|
580
164
|
if (open) {
|
|
581
165
|
originalTitleRef.current = document.title;
|
|
@@ -597,16 +181,16 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
597
181
|
return;
|
|
598
182
|
}
|
|
599
183
|
if (open) {
|
|
600
|
-
setAnimationStyles(!animation ? {} : animationType === /* inlined export .ANIMATION_FADE */
|
|
184
|
+
setAnimationStyles(!animation ? {} : animationType === (/* inlined export .ANIMATION_FADE */"fade") ? {
|
|
601
185
|
opacity: 0
|
|
602
186
|
} : {
|
|
603
|
-
transform: "translateY(-
|
|
187
|
+
transform: "translateY(-100vh)"
|
|
604
188
|
});
|
|
605
189
|
/**
|
|
606
190
|
* Small delay to ensure the opening state is applied after the component is
|
|
607
191
|
* rendered.
|
|
608
192
|
*/ const timer = setTimeout(()=>{
|
|
609
|
-
setAnimationStyles(!animation ? {} : animationType === /* inlined export .ANIMATION_FADE */
|
|
193
|
+
setAnimationStyles(!animation ? {} : animationType === (/* inlined export .ANIMATION_FADE */"fade") ? {
|
|
610
194
|
opacity: 1
|
|
611
195
|
} : {
|
|
612
196
|
transform: "translateY(0)"
|
|
@@ -619,75 +203,167 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
619
203
|
animation,
|
|
620
204
|
animationType
|
|
621
205
|
]);
|
|
622
|
-
/* v8 ignore stop */
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
206
|
+
/* v8 ignore stop */ /**
|
|
207
|
+
* Show/close the native dialog and manage body scroll locking.
|
|
208
|
+
*
|
|
209
|
+
* Uses a reference-counted scroll lock so nested dialogs work correctly:
|
|
210
|
+
* overflow is only restored when the last open dialog closes.
|
|
211
|
+
*
|
|
212
|
+
* The `open` dependency is required because the Dialog component stays
|
|
213
|
+
* mounted while the `<dialog>` element is conditionally rendered via
|
|
214
|
+
* `{open && <dialog>}`. When `open` is false the ref is null and this
|
|
215
|
+
* effect is a no-op. When `open` becomes true, the `<dialog>` element
|
|
216
|
+
* is added to the DOM, the ref is populated, and this effect re-runs
|
|
217
|
+
* to call `showModal()`.
|
|
218
|
+
*/ // biome-ignore lint/correctness/useExhaustiveDependencies: open triggers re-evaluation when <dialog> mounts/unmounts
|
|
219
|
+
useEffect(()=>{
|
|
220
|
+
const dialog = dialogRef.current;
|
|
221
|
+
/* v8 ignore start - ref is null when open is false */ if (!dialog) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
/* v8 ignore stop */ /* v8 ignore start */ if (typeof dialog.showModal === "function") {
|
|
225
|
+
try {
|
|
226
|
+
dialog.showModal();
|
|
227
|
+
} catch {
|
|
228
|
+
/* showModal can throw if already open */ }
|
|
229
|
+
}
|
|
230
|
+
const releaseScrollLock = acquireScrollLock();
|
|
231
|
+
/* v8 ignore stop */ return ()=>{
|
|
232
|
+
/* v8 ignore start */ releaseScrollLock();
|
|
233
|
+
if (dialog.open) {
|
|
234
|
+
dialog.close();
|
|
235
|
+
}
|
|
236
|
+
/* v8 ignore stop */ };
|
|
237
|
+
}, [
|
|
238
|
+
open
|
|
239
|
+
]);
|
|
240
|
+
/**
|
|
241
|
+
* Handle ESC key via the `cancel` event. The native `<dialog>` fires
|
|
242
|
+
* `cancel` before closing when ESC is pressed. We prevent the default
|
|
243
|
+
* behavior (which would natively close the dialog and cause a race
|
|
244
|
+
* condition with React's conditional rendering) and instead let React
|
|
245
|
+
* control the close by calling onOpenChange(false).
|
|
246
|
+
*/ // biome-ignore lint/correctness/useExhaustiveDependencies: open triggers re-evaluation when <dialog> mounts/unmounts
|
|
247
|
+
useEffect(()=>{
|
|
248
|
+
const dialog = dialogRef.current;
|
|
249
|
+
/* v8 ignore start - ref is null when open is false */ if (!dialog) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
/* v8 ignore stop */ const handleCancel = (e)=>{
|
|
253
|
+
e.preventDefault();
|
|
254
|
+
handleClose();
|
|
255
|
+
};
|
|
256
|
+
dialog.addEventListener("cancel", handleCancel);
|
|
257
|
+
return ()=>{
|
|
258
|
+
dialog.removeEventListener("cancel", handleCancel);
|
|
259
|
+
};
|
|
260
|
+
}, [
|
|
261
|
+
open,
|
|
262
|
+
handleClose
|
|
263
|
+
]);
|
|
264
|
+
/**
|
|
265
|
+
* Manage initial focus when the dialog opens.
|
|
266
|
+
*/ // biome-ignore lint/correctness/useExhaustiveDependencies: open triggers re-evaluation when <dialog> mounts/unmounts
|
|
267
|
+
useEffect(()=>{
|
|
268
|
+
const dialog = dialogRef.current;
|
|
269
|
+
/* v8 ignore start - ref is null when open is false */ if (!dialog) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
/* v8 ignore stop */ const frameId = requestAnimationFrame(()=>{
|
|
273
|
+
if (initialFocus === -1) {
|
|
274
|
+
dialog.focus();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (initialFocus !== undefined && typeof initialFocus !== "number") {
|
|
278
|
+
initialFocus.current?.focus();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const focusIndex = initialFocus ?? 0;
|
|
282
|
+
const tabbable = dialog.querySelectorAll(TABBABLE_SELECTOR);
|
|
283
|
+
const target = tabbable[focusIndex];
|
|
284
|
+
if (target) {
|
|
285
|
+
target.focus();
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
return ()=>cancelAnimationFrame(frameId);
|
|
289
|
+
}, [
|
|
290
|
+
open,
|
|
291
|
+
initialFocus
|
|
292
|
+
]);
|
|
293
|
+
return /*#__PURE__*/ jsx(Fragment, {
|
|
294
|
+
children: open && /*#__PURE__*/ jsx("dialog", {
|
|
295
|
+
ref: dialogRef,
|
|
296
|
+
className: dialogClassName.outerWrapper,
|
|
297
|
+
"aria-labelledby": titleId,
|
|
298
|
+
"aria-describedby": descriptionId,
|
|
299
|
+
tabIndex: -1,
|
|
300
|
+
style: {
|
|
301
|
+
...animationStyles
|
|
302
|
+
},
|
|
303
|
+
children: /*#__PURE__*/ jsxs("div", {
|
|
304
|
+
className: dialogClassName.innerWrapper,
|
|
305
|
+
id: descriptionId,
|
|
306
|
+
children: [
|
|
307
|
+
/*#__PURE__*/ jsxs("div", {
|
|
308
|
+
className: dialogClassName.header,
|
|
309
|
+
children: [
|
|
310
|
+
/*#__PURE__*/ jsx("div", {
|
|
311
|
+
className: dialogClassName.closeWrapper,
|
|
312
|
+
children: /*#__PURE__*/ jsx("button", {
|
|
313
|
+
className: dialogClassName.closeButton,
|
|
314
|
+
type: "button",
|
|
315
|
+
"aria-label": "Close",
|
|
316
|
+
onClick: handleClose,
|
|
317
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
318
|
+
children: /*#__PURE__*/ jsx("svg", {
|
|
319
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
320
|
+
className: "size-3",
|
|
321
|
+
viewBox: "0 0 384 512",
|
|
322
|
+
fill: "currentColor",
|
|
323
|
+
role: "img",
|
|
324
|
+
"aria-hidden": "true",
|
|
325
|
+
focusable: "false",
|
|
326
|
+
children: /*#__PURE__*/ jsx("path", {
|
|
327
|
+
d: "M297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256l105.3-105.4c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3z",
|
|
328
|
+
opacity: "1"
|
|
329
|
+
})
|
|
655
330
|
})
|
|
656
331
|
})
|
|
657
332
|
})
|
|
333
|
+
}),
|
|
334
|
+
/*#__PURE__*/ jsx("h1", {
|
|
335
|
+
className: dialogClassName.title,
|
|
336
|
+
id: titleId,
|
|
337
|
+
children: title
|
|
658
338
|
})
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
339
|
+
]
|
|
340
|
+
}),
|
|
341
|
+
/*#__PURE__*/ jsx("div", {
|
|
342
|
+
className: dialogClassName.scrollableContent,
|
|
343
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
344
|
+
className: dialogClassName.content,
|
|
345
|
+
children: children
|
|
663
346
|
})
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
children: /*#__PURE__*/ jsx("div", {
|
|
669
|
-
className: panelClassName.content,
|
|
670
|
-
children: children
|
|
347
|
+
}),
|
|
348
|
+
footer && /*#__PURE__*/ jsx("div", {
|
|
349
|
+
className: dialogClassName.footer,
|
|
350
|
+
children: footer
|
|
671
351
|
})
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
className: panelClassName.footer,
|
|
675
|
-
children: footer
|
|
676
|
-
})
|
|
677
|
-
]
|
|
352
|
+
]
|
|
353
|
+
})
|
|
678
354
|
})
|
|
679
355
|
});
|
|
680
356
|
};
|
|
681
357
|
|
|
682
|
-
|
|
358
|
+
// @v8-ignore-start
|
|
683
359
|
|
|
684
360
|
|
|
685
361
|
|
|
686
|
-
var
|
|
687
|
-
var
|
|
688
|
-
var
|
|
689
|
-
var
|
|
690
|
-
var
|
|
691
|
-
var
|
|
692
|
-
var
|
|
693
|
-
export {
|
|
362
|
+
var components_ANIMATION_FADE = (/* inlined export .ANIMATION_FADE */"fade");
|
|
363
|
+
var components_ANIMATION_SLIDE = (/* inlined export .ANIMATION_SLIDE */"slide");
|
|
364
|
+
var components_LARGE = (/* inlined export .LARGE */"large");
|
|
365
|
+
var components_MEDIUM = (/* inlined export .MEDIUM */"medium");
|
|
366
|
+
var components_NONE = (/* inlined export .NONE */"none");
|
|
367
|
+
var components_SMALL = (/* inlined export .SMALL */"small");
|
|
368
|
+
var components_TYPE_PANEL = (/* inlined export .TYPE_PANEL */"panel");
|
|
369
|
+
export { DIALOG_CLASSNAME, Dialog, MESSAGEBOX_CLASSNAME, TYPE_MESSAGEBOX, components_ANIMATION_FADE as ANIMATION_FADE, components_ANIMATION_SLIDE as ANIMATION_SLIDE, components_LARGE as LARGE, components_MEDIUM as MEDIUM, components_NONE as NONE, components_SMALL as SMALL, components_TYPE_PANEL as TYPE_PANEL };
|