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