@versini/ui-panel 8.0.4 → 8.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +221 -494
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-panel v8.0.
|
|
2
|
+
@versini/ui-panel v8.0.6
|
|
3
3
|
© 2025 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
try {
|
|
6
6
|
if (!window.__VERSINI_UI_PANEL__) {
|
|
7
7
|
window.__VERSINI_UI_PANEL__ = {
|
|
8
|
-
version: "8.0.
|
|
9
|
-
buildTime: "12/18/2025
|
|
8
|
+
version: "8.0.6",
|
|
9
|
+
buildTime: "12/18/2025 07:21 PM EST",
|
|
10
10
|
homepage: "https://www.npmjs.com/package/@versini/ui-panel",
|
|
11
11
|
license: "MIT",
|
|
12
12
|
};
|
|
@@ -15,9 +15,10 @@ try {
|
|
|
15
15
|
// nothing to declare officer
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
-
import {
|
|
18
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
19
|
+
import { FloatingFocusManager, FloatingOverlay, FloatingPortal, useClick, useDismiss, useFloating, useInteractions, useMergeRefs, useRole } from "@floating-ui/react";
|
|
20
20
|
import clsx from "clsx";
|
|
21
|
+
import { cloneElement, createContext, forwardRef, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
21
22
|
|
|
22
23
|
;// CONCATENATED MODULE: ./src/common/constants.ts
|
|
23
24
|
const MESSAGEBOX_CLASSNAME = "av-messagebox";
|
|
@@ -33,447 +34,177 @@ const NONE = "none";
|
|
|
33
34
|
|
|
34
35
|
;// CONCATENATED MODULE: external "react/jsx-runtime"
|
|
35
36
|
|
|
36
|
-
;// CONCATENATED MODULE: external "react"
|
|
37
|
+
;// CONCATENATED MODULE: external "@floating-ui/react"
|
|
37
38
|
|
|
38
39
|
;// CONCATENATED MODULE: external "clsx"
|
|
39
40
|
|
|
40
|
-
;// CONCATENATED MODULE:
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
* @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/examples/dialog/
|
|
55
|
-
*/ /**
|
|
56
|
-
* Stack of currently open dialogs. The last item is the topmost dialog.
|
|
57
|
-
*/ const openDialogStack = [];
|
|
58
|
-
/**
|
|
59
|
-
* Flag to ignore focus changes during programmatic focus operations.
|
|
60
|
-
* When true, focus event handlers should return early to prevent interference.
|
|
61
|
-
*
|
|
62
|
-
* This is critical for nested dialogs: when a child dialog closes and
|
|
63
|
-
* programmatically returns focus to its trigger element inside the parent,
|
|
64
|
-
* we don't want the parent's focusin handler to interfere.
|
|
65
|
-
*/ let ignoreFocusChanges = false;
|
|
66
|
-
/**
|
|
67
|
-
* Get whether focus changes should be ignored.
|
|
68
|
-
* Focus event handlers should check this and return early if true.
|
|
69
|
-
*/ function shouldIgnoreFocusChanges() {
|
|
70
|
-
return ignoreFocusChanges;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Execute a function while ignoring focus change events.
|
|
74
|
-
* Use this when programmatically moving focus to prevent event handlers
|
|
75
|
-
* from interfering.
|
|
76
|
-
*
|
|
77
|
-
* @param fn - Function to execute (typically contains focus() calls)
|
|
78
|
-
*/ function withIgnoredFocusChanges(fn) {
|
|
79
|
-
ignoreFocusChanges = true;
|
|
80
|
-
try {
|
|
81
|
-
fn();
|
|
82
|
-
} finally{
|
|
83
|
-
// Use setTimeout to ensure the flag stays true through any
|
|
84
|
-
// microtasks that might fire focus events
|
|
85
|
-
setTimeout(()=>{
|
|
86
|
-
ignoreFocusChanges = false;
|
|
87
|
-
}, 0);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Register a dialog in the stack when it opens.
|
|
92
|
-
* If there's a parent dialog, its listeners are suspended.
|
|
93
|
-
*
|
|
94
|
-
* @param entry - The dialog entry to register
|
|
95
|
-
*/ function registerDialog(entry) {
|
|
96
|
-
// If there's already a dialog open, suspend its listeners
|
|
97
|
-
if (openDialogStack.length > 0) {
|
|
98
|
-
const currentTop = openDialogStack[openDialogStack.length - 1];
|
|
99
|
-
currentTop.removeListeners();
|
|
100
|
-
}
|
|
101
|
-
// Add the new dialog to the stack and activate its listeners
|
|
102
|
-
openDialogStack.push(entry);
|
|
103
|
-
entry.addListeners();
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Unregister a dialog from the stack when it closes.
|
|
107
|
-
* If there's a parent dialog, its listeners are restored.
|
|
108
|
-
*
|
|
109
|
-
* @param dialogRef - Reference to the dialog element being closed
|
|
110
|
-
*/ function unregisterDialog(dialogRef) {
|
|
111
|
-
const index = openDialogStack.findIndex((entry)=>entry.dialogRef === dialogRef);
|
|
112
|
-
/* c8 ignore next 3 - defensive check */ if (index === -1) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
// Remove listeners from the dialog being closed
|
|
116
|
-
const [removedEntry] = openDialogStack.splice(index, 1);
|
|
117
|
-
removedEntry.removeListeners();
|
|
118
|
-
// If there's a parent dialog, restore its listeners
|
|
119
|
-
if (openDialogStack.length > 0) {
|
|
120
|
-
const newTop = openDialogStack[openDialogStack.length - 1];
|
|
121
|
-
newTop.addListeners();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* Get the current number of open dialogs.
|
|
126
|
-
* Useful for debugging and testing.
|
|
127
|
-
*/ function getOpenDialogCount() {
|
|
128
|
-
return openDialogStack.length;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Check if a given dialog is the topmost (current) dialog.
|
|
132
|
-
*
|
|
133
|
-
* @param dialogRef - Reference to the dialog element to check
|
|
134
|
-
*/ function isTopmostDialog(dialogRef) {
|
|
135
|
-
if (openDialogStack.length === 0) {
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
return openDialogStack[openDialogStack.length - 1].dialogRef === dialogRef;
|
|
139
|
-
}
|
|
140
|
-
/**
|
|
141
|
-
* Reset the stack (for testing purposes only).
|
|
142
|
-
* @internal
|
|
143
|
-
*/ function _resetStackForTesting() {
|
|
144
|
-
while(openDialogStack.length > 0){
|
|
145
|
-
openDialogStack.pop();
|
|
41
|
+
;// CONCATENATED MODULE: external "react"
|
|
42
|
+
|
|
43
|
+
;// CONCATENATED MODULE: ../ui-modal/dist/index.js
|
|
44
|
+
/*!
|
|
45
|
+
@versini/ui-modal v3.2.0
|
|
46
|
+
© 2025 gizmette.com
|
|
47
|
+
*/ try {
|
|
48
|
+
if (!window.__VERSINI_UI_MODAL__) {
|
|
49
|
+
window.__VERSINI_UI_MODAL__ = {
|
|
50
|
+
version: "3.2.0",
|
|
51
|
+
buildTime: "12/18/2025 07:21 PM EST",
|
|
52
|
+
homepage: "https://github.com/aversini/ui-components",
|
|
53
|
+
license: "MIT"
|
|
54
|
+
};
|
|
146
55
|
}
|
|
147
|
-
|
|
56
|
+
} catch (error) {
|
|
57
|
+
// nothing to declare officer
|
|
148
58
|
}
|
|
149
59
|
|
|
150
|
-
;// CONCATENATED MODULE: ./src/components/Panel/PanelPortal.tsx
|
|
151
|
-
|
|
152
|
-
|
|
153
60
|
|
|
154
61
|
|
|
155
62
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
* @see https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/
|
|
181
|
-
*
|
|
182
|
-
*/ function PanelPortal({ open, onClose, children, className, style, title, initialFocus = 0, kind = /* inlined export .TYPE_PANEL */ ("panel") }) {
|
|
183
|
-
const labelId = useId();
|
|
184
|
-
const descriptionId = useId();
|
|
185
|
-
const dialogRef = useRef(null);
|
|
186
|
-
const previouslyFocusedRef = useRef(null);
|
|
187
|
-
/**
|
|
188
|
-
* Get all focusable elements within the dialog. Excludes focus sentinel
|
|
189
|
-
* elements used for circular focus trapping.
|
|
190
|
-
*/ const getFocusableElements = useCallback(()=>{
|
|
191
|
-
/* c8 ignore next 3 - defensive check, dialogRef is always set when open */ if (!dialogRef.current) {
|
|
192
|
-
return [];
|
|
193
|
-
}
|
|
194
|
-
const elements = dialogRef.current.querySelectorAll(FOCUSABLE_SELECTOR);
|
|
195
|
-
return Array.from(elements).filter((el)=>el.offsetParent !== null && // Filter out hidden elements
|
|
196
|
-
!el.hasAttribute("data-focus-sentinel"));
|
|
197
|
-
}, []);
|
|
198
|
-
/**
|
|
199
|
-
* Focus a specific element by index, or the element referenced by a ref.
|
|
200
|
-
*/ const focusElement = useCallback((target)=>{
|
|
201
|
-
if (typeof target === "number") {
|
|
202
|
-
if (target === -1) {
|
|
203
|
-
// -1 means don't focus anything.
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const focusableElements = getFocusableElements();
|
|
207
|
-
if (focusableElements.length > 0) {
|
|
208
|
-
const index = Math.min(target, focusableElements.length - 1);
|
|
209
|
-
focusableElements[index]?.focus();
|
|
210
|
-
}
|
|
211
|
-
} else if (target?.current) {
|
|
212
|
-
target.current.focus();
|
|
213
|
-
}
|
|
214
|
-
}, [
|
|
215
|
-
getFocusableElements
|
|
63
|
+
const ModalContext = /*#__PURE__*/ createContext(null);
|
|
64
|
+
function useModal({ initialOpen = false, open: controlledOpen, onOpenChange: setControlledOpen, initialFocus } = {}) {
|
|
65
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(initialOpen);
|
|
66
|
+
const [labelId, setLabelId] = useState();
|
|
67
|
+
const [descriptionId, setDescriptionId] = useState();
|
|
68
|
+
/* v8 ignore next 2 */ const open = controlledOpen ?? uncontrolledOpen;
|
|
69
|
+
const setOpen = setControlledOpen ?? setUncontrolledOpen;
|
|
70
|
+
const data = useFloating({
|
|
71
|
+
open,
|
|
72
|
+
onOpenChange: setOpen
|
|
73
|
+
});
|
|
74
|
+
const context = data.context;
|
|
75
|
+
const click = useClick(context, {
|
|
76
|
+
enabled: controlledOpen == null
|
|
77
|
+
});
|
|
78
|
+
const dismiss = useDismiss(context, {
|
|
79
|
+
outsidePress: false,
|
|
80
|
+
outsidePressEvent: "mousedown"
|
|
81
|
+
});
|
|
82
|
+
const role = useRole(context);
|
|
83
|
+
const interactions = useInteractions([
|
|
84
|
+
click,
|
|
85
|
+
dismiss,
|
|
86
|
+
role
|
|
216
87
|
]);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
88
|
+
return useMemo(()=>({
|
|
89
|
+
open,
|
|
90
|
+
setOpen,
|
|
91
|
+
...interactions,
|
|
92
|
+
...data,
|
|
93
|
+
labelId,
|
|
94
|
+
descriptionId,
|
|
95
|
+
setLabelId,
|
|
96
|
+
setDescriptionId,
|
|
97
|
+
initialFocus
|
|
98
|
+
}), [
|
|
99
|
+
open,
|
|
100
|
+
setOpen,
|
|
101
|
+
interactions,
|
|
102
|
+
data,
|
|
103
|
+
labelId,
|
|
104
|
+
descriptionId,
|
|
105
|
+
initialFocus
|
|
227
106
|
]);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const lastElement = focusableElements[focusableElements.length - 1];
|
|
249
|
-
const activeElement = document.activeElement;
|
|
250
|
-
// Find the current index of the focused element.
|
|
251
|
-
const currentIndex = focusableElements.indexOf(activeElement);
|
|
252
|
-
/**
|
|
253
|
-
* Always prevent default and manually handle focus navigation. This is
|
|
254
|
-
* required for iPad Safari with physical keyboard, where Tab key doesn't
|
|
255
|
-
* automatically navigate between focusable elements.
|
|
256
|
-
*/ event.preventDefault();
|
|
257
|
-
if (event.shiftKey) {
|
|
258
|
-
// Shift+Tab: move to previous element, wrap to last if on first.
|
|
259
|
-
if (activeElement === firstElement || currentIndex <= 0) {
|
|
260
|
-
lastElement?.focus();
|
|
261
|
-
} else {
|
|
262
|
-
focusableElements[currentIndex - 1]?.focus();
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
// Tab: move to next element, wrap to first if on last.
|
|
266
|
-
if (activeElement === lastElement || currentIndex >= focusableElements.length - 1) {
|
|
267
|
-
firstElement?.focus();
|
|
268
|
-
} else {
|
|
269
|
-
focusableElements[currentIndex + 1]?.focus();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}, [
|
|
273
|
-
getFocusableElements
|
|
107
|
+
}
|
|
108
|
+
const useModalContext = ()=>{
|
|
109
|
+
const context = useContext(ModalContext);
|
|
110
|
+
/* v8 ignore next 3 */ if (context == null) {
|
|
111
|
+
throw new Error("Modal components must be wrapped in <Modal />");
|
|
112
|
+
}
|
|
113
|
+
return context;
|
|
114
|
+
};
|
|
115
|
+
function Modal({ children, ...options }) {
|
|
116
|
+
const dialog = useModal(options);
|
|
117
|
+
return /*#__PURE__*/ jsx(ModalContext.Provider, {
|
|
118
|
+
value: dialog,
|
|
119
|
+
children: children
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const Modal_ModalContent = /*#__PURE__*/ forwardRef(function ModalContent(props, propRef) {
|
|
123
|
+
const { context: floatingContext, ...context } = useModalContext();
|
|
124
|
+
const ref = useMergeRefs([
|
|
125
|
+
context.refs.setFloating,
|
|
126
|
+
propRef
|
|
274
127
|
]);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
128
|
+
/* v8 ignore next 3 */ if (!floatingContext.open) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const { overlayBackground, ...rest } = props;
|
|
132
|
+
const overlayClass = clsx("grid place-items-center", {
|
|
133
|
+
[`${overlayBackground}`]: overlayBackground,
|
|
134
|
+
"bg-black sm:bg-black/[.8]": !overlayBackground
|
|
135
|
+
});
|
|
136
|
+
return /*#__PURE__*/ jsx(FloatingPortal, {
|
|
137
|
+
children: /*#__PURE__*/ jsx(FloatingOverlay, {
|
|
138
|
+
className: overlayClass,
|
|
139
|
+
lockScroll: true,
|
|
140
|
+
children: /*#__PURE__*/ jsx(FloatingFocusManager, {
|
|
141
|
+
context: floatingContext,
|
|
142
|
+
initialFocus: context.initialFocus,
|
|
143
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
144
|
+
ref: ref,
|
|
145
|
+
"aria-labelledby": context.labelId,
|
|
146
|
+
"aria-describedby": context.descriptionId,
|
|
147
|
+
...context.getFloatingProps(rest),
|
|
148
|
+
children: rest.children
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
const Modal_ModalHeading = /*#__PURE__*/ forwardRef(function ModalHeading({ children, ...props }, ref) {
|
|
155
|
+
const { setLabelId } = useModalContext();
|
|
156
|
+
const id = useId();
|
|
157
|
+
// Only sets `aria-labelledby` on the Modal root element
|
|
158
|
+
// if this component is mounted inside it.
|
|
159
|
+
useLayoutEffect(()=>{
|
|
160
|
+
setLabelId(id);
|
|
161
|
+
return ()=>setLabelId(undefined);
|
|
296
162
|
}, [
|
|
297
|
-
|
|
163
|
+
id,
|
|
164
|
+
setLabelId
|
|
298
165
|
]);
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
* Safari's Tab key behavior which can bypass event listeners.
|
|
315
|
-
*/ const handleSentinelFocus = useCallback((position)=>{
|
|
316
|
-
const focusableElements = getFocusableElements();
|
|
317
|
-
/* c8 ignore next 3 - edge case: dialog with no focusable elements */ if (focusableElements.length === 0) {
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (position === "start") {
|
|
321
|
-
// Focus came from the end, wrap to last element.
|
|
322
|
-
focusableElements[focusableElements.length - 1]?.focus();
|
|
323
|
-
} else {
|
|
324
|
-
// Focus came from the start, wrap to first element.
|
|
325
|
-
focusableElements[0]?.focus();
|
|
326
|
-
}
|
|
166
|
+
return /*#__PURE__*/ jsx("h1", {
|
|
167
|
+
...props,
|
|
168
|
+
ref: ref,
|
|
169
|
+
id: id,
|
|
170
|
+
children: children
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const Modal_ModalDescription = /*#__PURE__*/ forwardRef(function ModalDescription({ children, ...props }, ref) {
|
|
174
|
+
const { setDescriptionId } = useModalContext();
|
|
175
|
+
const id = useId();
|
|
176
|
+
// Only sets `aria-describedby` on the Modal root element
|
|
177
|
+
// if this component is mounted inside it.
|
|
178
|
+
useLayoutEffect(()=>{
|
|
179
|
+
setDescriptionId(id);
|
|
180
|
+
return ()=>setDescriptionId(undefined);
|
|
327
181
|
}, [
|
|
328
|
-
|
|
182
|
+
id,
|
|
183
|
+
setDescriptionId
|
|
329
184
|
]);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
dialog.showModal();
|
|
343
|
-
}
|
|
344
|
-
/**
|
|
345
|
-
* Add cancel event listener for ESC key (always needed, not managed by
|
|
346
|
-
* stack).
|
|
347
|
-
*/ dialog.addEventListener("cancel", handleCancel);
|
|
348
|
-
/**
|
|
349
|
-
* Define listener management functions for the stack manager. These will be
|
|
350
|
-
* called when this dialog becomes/stops being the topmost dialog.
|
|
351
|
-
*/ const addListeners = ()=>{
|
|
352
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
353
|
-
document.addEventListener("focusin", handleFocusIn);
|
|
354
|
-
};
|
|
355
|
-
const removeListeners = ()=>{
|
|
356
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
357
|
-
document.removeEventListener("focusin", handleFocusIn);
|
|
358
|
-
};
|
|
359
|
-
/**
|
|
360
|
-
* Register this dialog with the stack manager. This will suspend parent
|
|
361
|
-
* dialog listeners if any exist.
|
|
362
|
-
*/ registerDialog({
|
|
363
|
-
dialogRef: dialog,
|
|
364
|
-
addListeners,
|
|
365
|
-
removeListeners
|
|
366
|
-
});
|
|
367
|
-
/**
|
|
368
|
-
* Set initial focus after a small delay to ensure the DOM is ready. This
|
|
369
|
-
* works around React's autoFocus prop not working with native dialog.
|
|
370
|
-
*/ const focusTimer = setTimeout(()=>{
|
|
371
|
-
focusElement(initialFocus);
|
|
372
|
-
}, 0);
|
|
373
|
-
// Capture the previously focused element for restoration in cleanup.
|
|
374
|
-
const previouslyFocused = previouslyFocusedRef.current;
|
|
375
|
-
return ()=>{
|
|
376
|
-
clearTimeout(focusTimer);
|
|
377
|
-
dialog.removeEventListener("cancel", handleCancel);
|
|
378
|
-
/**
|
|
379
|
-
* Unregister from the stack manager. This will restore parent dialog
|
|
380
|
-
* listeners if any exist.
|
|
381
|
-
*/ unregisterDialog(dialog);
|
|
382
|
-
// Close the dialog if it's still open.
|
|
383
|
-
if (dialog.open) {
|
|
384
|
-
dialog.close();
|
|
385
|
-
}
|
|
386
|
-
/**
|
|
387
|
-
* Restore focus to the previously focused element if it's still in the DOM.
|
|
388
|
-
* Use withIgnoredFocusChanges to prevent parent dialog's handleFocusIn from
|
|
389
|
-
* interfering with focus restoration.
|
|
390
|
-
*/ if (previouslyFocused?.isConnected) {
|
|
391
|
-
withIgnoredFocusChanges(()=>{
|
|
392
|
-
if (previouslyFocused.isConnected) {
|
|
393
|
-
previouslyFocused.focus();
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
}, [
|
|
399
|
-
handleCancel,
|
|
400
|
-
handleKeyDown,
|
|
401
|
-
handleFocusIn,
|
|
402
|
-
initialFocus,
|
|
403
|
-
focusElement
|
|
185
|
+
return /*#__PURE__*/ jsx("div", {
|
|
186
|
+
...props,
|
|
187
|
+
ref: ref,
|
|
188
|
+
id: id,
|
|
189
|
+
children: children
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
const Modal_ModalClose = /*#__PURE__*/ forwardRef(function ModalClose(props, ref) {
|
|
193
|
+
const { setOpen } = useModalContext();
|
|
194
|
+
const { trigger, className, ...rest } = props;
|
|
195
|
+
const handleClose = useCallback(()=>setOpen(false), [
|
|
196
|
+
setOpen
|
|
404
197
|
]);
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
* - Panel on mobile: inset-0 + no margin for full screen.
|
|
413
|
-
* - Panel on desktop: inset-x-0 + top/bottom auto for vertical centering.
|
|
414
|
-
* - MessageBox: Always centered (both mobile and desktop) since it doesn't
|
|
415
|
-
* take full screen.
|
|
416
|
-
*/ "fixed max-h-none max-w-none p-0", {
|
|
417
|
-
// Panel: full screen on mobile, centered on desktop
|
|
418
|
-
"inset-0 m-0 sm:inset-auto sm:inset-x-0 sm:top-1/2 sm:-translate-y-1/2 sm:mx-auto": !isMessageBox,
|
|
419
|
-
// MessageBox: always centered at all breakpoints
|
|
420
|
-
"inset-auto inset-x-0 top-1/2 -translate-y-1/2 mx-auto z-100": isMessageBox
|
|
421
|
-
}, /**
|
|
422
|
-
* Backdrop styling via Tailwind's backdrop: variant for ::backdrop
|
|
423
|
-
* pseudo-element. Full black on mobile, 80% opacity on desktop (matches
|
|
424
|
-
* original overlay).
|
|
425
|
-
*/ "backdrop:bg-black sm:backdrop:bg-black/80", className);
|
|
426
|
-
return /*#__PURE__*/ jsxs("dialog", {
|
|
427
|
-
ref: dialogRef,
|
|
428
|
-
"aria-labelledby": labelId,
|
|
429
|
-
"aria-describedby": descriptionId,
|
|
430
|
-
className: dialogClass,
|
|
431
|
-
style: style,
|
|
432
|
-
onClick: handleDialogClick,
|
|
433
|
-
children: [
|
|
434
|
-
/*#__PURE__*/ jsx("span", {
|
|
435
|
-
tabIndex: 0,
|
|
436
|
-
onFocus: ()=>handleSentinelFocus("start"),
|
|
437
|
-
"data-focus-sentinel": "start",
|
|
438
|
-
style: {
|
|
439
|
-
position: "absolute",
|
|
440
|
-
width: 1,
|
|
441
|
-
height: 1,
|
|
442
|
-
padding: 0,
|
|
443
|
-
margin: -1,
|
|
444
|
-
overflow: "hidden",
|
|
445
|
-
clip: "rect(0, 0, 0, 0)",
|
|
446
|
-
whiteSpace: "nowrap",
|
|
447
|
-
border: 0
|
|
448
|
-
},
|
|
449
|
-
"aria-hidden": "true"
|
|
450
|
-
}),
|
|
451
|
-
/*#__PURE__*/ jsx("span", {
|
|
452
|
-
id: labelId,
|
|
453
|
-
className: "sr-only",
|
|
454
|
-
children: title
|
|
455
|
-
}),
|
|
456
|
-
children,
|
|
457
|
-
/*#__PURE__*/ jsx("span", {
|
|
458
|
-
tabIndex: 0,
|
|
459
|
-
onFocus: ()=>handleSentinelFocus("end"),
|
|
460
|
-
"data-focus-sentinel": "end",
|
|
461
|
-
style: {
|
|
462
|
-
position: "absolute",
|
|
463
|
-
width: 1,
|
|
464
|
-
height: 1,
|
|
465
|
-
padding: 0,
|
|
466
|
-
margin: -1,
|
|
467
|
-
overflow: "hidden",
|
|
468
|
-
clip: "rect(0, 0, 0, 0)",
|
|
469
|
-
whiteSpace: "nowrap",
|
|
470
|
-
border: 0
|
|
471
|
-
},
|
|
472
|
-
"aria-hidden": "true"
|
|
473
|
-
})
|
|
474
|
-
]
|
|
198
|
+
return /*#__PURE__*/ jsx("div", {
|
|
199
|
+
className: className,
|
|
200
|
+
children: /*#__PURE__*/ cloneElement(trigger, {
|
|
201
|
+
ref,
|
|
202
|
+
onClick: handleClose,
|
|
203
|
+
...rest
|
|
204
|
+
})
|
|
475
205
|
});
|
|
476
|
-
}
|
|
206
|
+
});
|
|
207
|
+
/* v8 ignore next 1 */
|
|
477
208
|
|
|
478
209
|
;// CONCATENATED MODULE: ./src/components/Panel/utilities.ts
|
|
479
210
|
|
|
@@ -501,10 +232,10 @@ const getPanelClassName = ({ className, kind, borderMode, animation, maxWidth =
|
|
|
501
232
|
["w-full sm:w-[95%] md:max-w-4xl"]: kind === /* inlined export .TYPE_PANEL */ ("panel") && !className && maxWidth === /* inlined export .LARGE */ ("large"),
|
|
502
233
|
/**
|
|
503
234
|
* Heights and max heights for Panel
|
|
504
|
-
* Mobile: full height
|
|
505
|
-
*/ "
|
|
506
|
-
"
|
|
507
|
-
"
|
|
235
|
+
* Mobile: full height, Desktop: clamp between min and max to allow flexible sizing
|
|
236
|
+
*/ "min-h-40 max-h-full sm:max-h-[40vh]": kind === /* inlined export .TYPE_PANEL */ ("panel") && effectiveMaxHeight === /* inlined export .SMALL */ ("small"),
|
|
237
|
+
"min-h-40 max-h-full sm:max-h-[60vh]": kind === /* inlined export .TYPE_PANEL */ ("panel") && effectiveMaxHeight === /* inlined export .MEDIUM */ ("medium"),
|
|
238
|
+
"min-h-40 max-h-full sm:max-h-[95vh]": kind === /* inlined export .TYPE_PANEL */ ("panel") && effectiveMaxHeight === /* inlined export .LARGE */ ("large"),
|
|
508
239
|
/**
|
|
509
240
|
* Panel border colors
|
|
510
241
|
*/ "sm:border-border-dark": borderMode === "dark" && kind === /* inlined export .TYPE_PANEL */ ("panel"),
|
|
@@ -526,7 +257,7 @@ const getPanelClassName = ({ className, kind, borderMode, animation, maxWidth =
|
|
|
526
257
|
"border-border-accent": borderMode === "light" && kind === TYPE_MESSAGEBOX,
|
|
527
258
|
[`${className}`]: !!className
|
|
528
259
|
}),
|
|
529
|
-
innerWrapper: "content flex flex-col rounded-[inherit] relative min-h-full
|
|
260
|
+
innerWrapper: "content flex flex-col rounded-[inherit] relative min-h-full",
|
|
530
261
|
scrollableContent: clsx("flex-1 overflow-y-auto overflow-x-hidden", "pt-12", {
|
|
531
262
|
"pb-12": hasFooter
|
|
532
263
|
}),
|
|
@@ -575,15 +306,9 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
575
306
|
hasFooter: Boolean(footer)
|
|
576
307
|
});
|
|
577
308
|
/**
|
|
578
|
-
*
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}, [
|
|
582
|
-
onOpenChange
|
|
583
|
-
]);
|
|
584
|
-
/**
|
|
585
|
-
* If the panel is opened, set the document title to the panel's title. If it's
|
|
586
|
-
* closed, restore the original document.title.
|
|
309
|
+
* If the panel is opened, set the document
|
|
310
|
+
* title to the panel's title. If it's closed,
|
|
311
|
+
* restore the original document.title.
|
|
587
312
|
*/ useEffect(()=>{
|
|
588
313
|
if (open) {
|
|
589
314
|
originalTitleRef.current = document.title;
|
|
@@ -600,7 +325,7 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
600
325
|
]);
|
|
601
326
|
/**
|
|
602
327
|
* Effect to handle the opening and closing animations.
|
|
603
|
-
*/ /* v8 ignore next
|
|
328
|
+
*/ /* v8 ignore next 30 */ useEffect(()=>{
|
|
604
329
|
if (!animation) {
|
|
605
330
|
return;
|
|
606
331
|
}
|
|
@@ -610,10 +335,9 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
610
335
|
} : {
|
|
611
336
|
transform: "translateY(-666vh)"
|
|
612
337
|
});
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
*/ const timer = setTimeout(()=>{
|
|
338
|
+
// Small delay to ensure the opening state is applied after
|
|
339
|
+
// the component is rendered.
|
|
340
|
+
const timer = setTimeout(()=>{
|
|
617
341
|
setAnimationStyles(!animation ? {} : animationType === /* inlined export .ANIMATION_FADE */ ("fade") ? {
|
|
618
342
|
opacity: 1
|
|
619
343
|
} : {
|
|
@@ -627,62 +351,65 @@ const Panel = ({ open, onOpenChange, title, children, footer, borderMode = "ligh
|
|
|
627
351
|
animation,
|
|
628
352
|
animationType
|
|
629
353
|
]);
|
|
630
|
-
return /*#__PURE__*/ jsx(
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
className: panelClassName.header,
|
|
354
|
+
return /*#__PURE__*/ jsx(Fragment, {
|
|
355
|
+
children: open && /*#__PURE__*/ jsx(Modal, {
|
|
356
|
+
open: open,
|
|
357
|
+
onOpenChange: onOpenChange,
|
|
358
|
+
initialFocus: initialFocus,
|
|
359
|
+
children: /*#__PURE__*/ jsx(Modal_ModalContent, {
|
|
360
|
+
className: panelClassName.outerWrapper,
|
|
361
|
+
style: {
|
|
362
|
+
...animationStyles
|
|
363
|
+
},
|
|
364
|
+
children: /*#__PURE__*/ jsxs(Modal_ModalDescription, {
|
|
365
|
+
className: panelClassName.innerWrapper,
|
|
643
366
|
children: [
|
|
644
|
-
/*#__PURE__*/
|
|
645
|
-
className: panelClassName.
|
|
646
|
-
children:
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
367
|
+
/*#__PURE__*/ jsxs("div", {
|
|
368
|
+
className: panelClassName.header,
|
|
369
|
+
children: [
|
|
370
|
+
/*#__PURE__*/ jsx(Modal_ModalClose, {
|
|
371
|
+
className: panelClassName.closeWrapper,
|
|
372
|
+
trigger: /*#__PURE__*/ jsx("button", {
|
|
373
|
+
className: panelClassName.closeButton,
|
|
374
|
+
type: "button",
|
|
375
|
+
"aria-label": "Close",
|
|
376
|
+
children: /*#__PURE__*/ jsx("span", {
|
|
377
|
+
children: /*#__PURE__*/ jsx("svg", {
|
|
378
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
379
|
+
className: "size-3",
|
|
380
|
+
viewBox: "0 0 384 512",
|
|
381
|
+
fill: "currentColor",
|
|
382
|
+
role: "img",
|
|
383
|
+
"aria-hidden": "true",
|
|
384
|
+
focusable: "false",
|
|
385
|
+
children: /*#__PURE__*/ jsx("path", {
|
|
386
|
+
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",
|
|
387
|
+
opacity: "1"
|
|
388
|
+
})
|
|
389
|
+
})
|
|
663
390
|
})
|
|
664
391
|
})
|
|
392
|
+
}),
|
|
393
|
+
/*#__PURE__*/ jsx(Modal_ModalHeading, {
|
|
394
|
+
className: panelClassName.title,
|
|
395
|
+
children: title
|
|
665
396
|
})
|
|
397
|
+
]
|
|
398
|
+
}),
|
|
399
|
+
/*#__PURE__*/ jsx("div", {
|
|
400
|
+
className: panelClassName.scrollableContent,
|
|
401
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
402
|
+
className: panelClassName.content,
|
|
403
|
+
children: children
|
|
666
404
|
})
|
|
667
405
|
}),
|
|
668
|
-
/*#__PURE__*/ jsx("
|
|
669
|
-
className: panelClassName.
|
|
670
|
-
children:
|
|
406
|
+
footer && /*#__PURE__*/ jsx("div", {
|
|
407
|
+
className: panelClassName.footer,
|
|
408
|
+
children: footer
|
|
671
409
|
})
|
|
672
410
|
]
|
|
673
|
-
}),
|
|
674
|
-
/*#__PURE__*/ jsx("div", {
|
|
675
|
-
className: panelClassName.scrollableContent,
|
|
676
|
-
children: /*#__PURE__*/ jsx("div", {
|
|
677
|
-
className: panelClassName.content,
|
|
678
|
-
children: children
|
|
679
|
-
})
|
|
680
|
-
}),
|
|
681
|
-
footer && /*#__PURE__*/ jsx("div", {
|
|
682
|
-
className: panelClassName.footer,
|
|
683
|
-
children: footer
|
|
684
411
|
})
|
|
685
|
-
|
|
412
|
+
})
|
|
686
413
|
})
|
|
687
414
|
});
|
|
688
415
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versini/ui-panel",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Arno Versini",
|
|
6
6
|
"publishConfig": {
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"test": "vitest run"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@testing-library/jest-dom": "6.9.1"
|
|
45
|
+
"@testing-library/jest-dom": "6.9.1",
|
|
46
|
+
"@versini/ui-modal": "3.2.0"
|
|
46
47
|
},
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"@tailwindcss/typography": "0.5.19",
|
|
@@ -52,5 +53,5 @@
|
|
|
52
53
|
"sideEffects": [
|
|
53
54
|
"**/*.css"
|
|
54
55
|
],
|
|
55
|
-
"gitHead": "
|
|
56
|
+
"gitHead": "3582aaec11fa1d50f3d6ef280d4f06adaf4746e4"
|
|
56
57
|
}
|