@mshafiqyajid/react-modal 0.1.0 → 0.3.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/dist/styled.cjs +233 -21
- package/dist/styled.cjs.map +1 -1
- package/dist/styled.d.cts +50 -3
- package/dist/styled.d.ts +50 -3
- package/dist/styled.js +233 -23
- package/dist/styled.js.map +1 -1
- package/dist/styles.css +115 -0
- package/package.json +1 -1
package/dist/styled.cjs
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var reactDom = require('react-dom');
|
|
5
5
|
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var client = require('react-dom/client');
|
|
6
7
|
|
|
7
8
|
// src/styled/ModalStyled.tsx
|
|
8
9
|
var FOCUSABLE_SELECTORS = [
|
|
@@ -64,11 +65,19 @@ function useFocusTrap() {
|
|
|
64
65
|
}, []);
|
|
65
66
|
return { activate, deactivate, handleKeyDown };
|
|
66
67
|
}
|
|
68
|
+
var _openModals = [];
|
|
69
|
+
var _modalIdCounter = 0;
|
|
70
|
+
var _stackListeners = /* @__PURE__ */ new Set();
|
|
71
|
+
function _notifyStack() {
|
|
72
|
+
_stackListeners.forEach((fn) => fn());
|
|
73
|
+
}
|
|
67
74
|
var ModalStyled = react.forwardRef(
|
|
68
75
|
function ModalStyled2({
|
|
76
|
+
open,
|
|
69
77
|
isOpen,
|
|
70
78
|
onClose,
|
|
71
79
|
title,
|
|
80
|
+
description,
|
|
72
81
|
children,
|
|
73
82
|
footer,
|
|
74
83
|
size = "md",
|
|
@@ -80,83 +89,201 @@ var ModalStyled = react.forwardRef(
|
|
|
80
89
|
overlayColor,
|
|
81
90
|
padding = "md",
|
|
82
91
|
scrollable = true,
|
|
83
|
-
className
|
|
92
|
+
className,
|
|
93
|
+
initialFocusRef,
|
|
94
|
+
finalFocusRef,
|
|
95
|
+
onAfterOpen,
|
|
96
|
+
onAfterClose,
|
|
97
|
+
preventClose,
|
|
98
|
+
lockBodyScroll = true,
|
|
99
|
+
container,
|
|
100
|
+
transition = "fade",
|
|
101
|
+
swipeToDismiss,
|
|
102
|
+
closeOnSubmit = false
|
|
84
103
|
}, ref) {
|
|
104
|
+
const swipeEnabled = swipeToDismiss ?? variant === "drawer-bottom";
|
|
105
|
+
const myStackIdRef = react.useRef(-1);
|
|
106
|
+
const [stackDepth, setStackDepth] = react.useState(0);
|
|
107
|
+
const [stackPosition, setStackPosition] = react.useState(0);
|
|
108
|
+
const [swipeY, setSwipeY] = react.useState(0);
|
|
85
109
|
const titleId = react.useId();
|
|
110
|
+
const descId = react.useId();
|
|
111
|
+
const isActuallyOpen = open ?? isOpen ?? false;
|
|
86
112
|
const [mounted, setMounted] = react.useState(false);
|
|
87
113
|
const [rendered, setRendered] = react.useState(false);
|
|
88
114
|
const [visible, setVisible] = react.useState(false);
|
|
89
115
|
const panelRef = react.useRef(null);
|
|
90
116
|
const originalOverflowRef = react.useRef("");
|
|
91
117
|
const exitTimerRef = react.useRef(null);
|
|
118
|
+
const enterTimerRef = react.useRef(null);
|
|
92
119
|
const { activate, deactivate, handleKeyDown } = useFocusTrap();
|
|
120
|
+
const onAfterOpenRef = react.useRef(onAfterOpen);
|
|
121
|
+
onAfterOpenRef.current = onAfterOpen;
|
|
122
|
+
const onAfterCloseRef = react.useRef(onAfterClose);
|
|
123
|
+
onAfterCloseRef.current = onAfterClose;
|
|
93
124
|
react.useEffect(() => {
|
|
94
125
|
setMounted(true);
|
|
95
126
|
}, []);
|
|
127
|
+
const requestClose = react.useCallback(
|
|
128
|
+
(reason) => {
|
|
129
|
+
if (preventClose && !preventClose(reason)) return;
|
|
130
|
+
onClose();
|
|
131
|
+
},
|
|
132
|
+
[preventClose, onClose]
|
|
133
|
+
);
|
|
96
134
|
react.useEffect(() => {
|
|
97
135
|
if (exitTimerRef.current) {
|
|
98
136
|
clearTimeout(exitTimerRef.current);
|
|
99
137
|
exitTimerRef.current = null;
|
|
100
138
|
}
|
|
101
|
-
if (
|
|
139
|
+
if (enterTimerRef.current) {
|
|
140
|
+
clearTimeout(enterTimerRef.current);
|
|
141
|
+
enterTimerRef.current = null;
|
|
142
|
+
}
|
|
143
|
+
if (isActuallyOpen) {
|
|
102
144
|
setRendered(true);
|
|
103
145
|
requestAnimationFrame(() => {
|
|
104
146
|
requestAnimationFrame(() => {
|
|
105
147
|
setVisible(true);
|
|
106
148
|
});
|
|
107
149
|
});
|
|
108
|
-
|
|
109
|
-
|
|
150
|
+
if (lockBodyScroll) {
|
|
151
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
152
|
+
document.body.style.overflow = "hidden";
|
|
153
|
+
}
|
|
154
|
+
enterTimerRef.current = setTimeout(() => {
|
|
155
|
+
onAfterOpenRef.current?.();
|
|
156
|
+
}, 320);
|
|
110
157
|
} else {
|
|
111
158
|
setVisible(false);
|
|
112
159
|
exitTimerRef.current = setTimeout(() => {
|
|
113
160
|
setRendered(false);
|
|
161
|
+
onAfterCloseRef.current?.();
|
|
114
162
|
}, 300);
|
|
115
|
-
|
|
163
|
+
if (lockBodyScroll) {
|
|
164
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
165
|
+
}
|
|
116
166
|
}
|
|
117
|
-
}, [
|
|
167
|
+
}, [isActuallyOpen, lockBodyScroll]);
|
|
118
168
|
react.useEffect(() => {
|
|
119
169
|
return () => {
|
|
120
170
|
if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
|
|
121
|
-
|
|
171
|
+
if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
|
|
172
|
+
if (lockBodyScroll) {
|
|
173
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
174
|
+
}
|
|
122
175
|
};
|
|
123
|
-
}, []);
|
|
176
|
+
}, [lockBodyScroll]);
|
|
177
|
+
react.useEffect(() => {
|
|
178
|
+
if (!isActuallyOpen) return;
|
|
179
|
+
const id = ++_modalIdCounter;
|
|
180
|
+
myStackIdRef.current = id;
|
|
181
|
+
_openModals.push({ id });
|
|
182
|
+
_notifyStack();
|
|
183
|
+
const sync = () => {
|
|
184
|
+
const idx = _openModals.findIndex((m) => m.id === id);
|
|
185
|
+
const total = _openModals.length;
|
|
186
|
+
setStackDepth(total);
|
|
187
|
+
setStackPosition(idx === -1 ? 0 : total - 1 - idx);
|
|
188
|
+
};
|
|
189
|
+
sync();
|
|
190
|
+
_stackListeners.add(sync);
|
|
191
|
+
return () => {
|
|
192
|
+
_stackListeners.delete(sync);
|
|
193
|
+
const idx = _openModals.findIndex((m) => m.id === id);
|
|
194
|
+
if (idx !== -1) _openModals.splice(idx, 1);
|
|
195
|
+
_notifyStack();
|
|
196
|
+
};
|
|
197
|
+
}, [isActuallyOpen]);
|
|
198
|
+
react.useEffect(() => {
|
|
199
|
+
if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;
|
|
200
|
+
const panel = panelRef.current;
|
|
201
|
+
const onSubmit = (e) => {
|
|
202
|
+
if (e.defaultPrevented) return;
|
|
203
|
+
setTimeout(() => requestClose("submit"), 0);
|
|
204
|
+
};
|
|
205
|
+
panel.addEventListener("submit", onSubmit);
|
|
206
|
+
return () => panel.removeEventListener("submit", onSubmit);
|
|
207
|
+
}, [closeOnSubmit, isActuallyOpen, requestClose]);
|
|
208
|
+
const swipeStartYRef = react.useRef(null);
|
|
209
|
+
const handleSwipePointerDown = (e) => {
|
|
210
|
+
if (!swipeEnabled || e.pointerType !== "touch") return;
|
|
211
|
+
swipeStartYRef.current = e.clientY;
|
|
212
|
+
};
|
|
213
|
+
const handleSwipePointerMove = (e) => {
|
|
214
|
+
if (swipeStartYRef.current === null) return;
|
|
215
|
+
const dy = Math.max(0, e.clientY - swipeStartYRef.current);
|
|
216
|
+
setSwipeY(dy);
|
|
217
|
+
};
|
|
218
|
+
const handleSwipePointerUp = () => {
|
|
219
|
+
if (swipeStartYRef.current === null) return;
|
|
220
|
+
if (swipeY > 120) requestClose("swipe");
|
|
221
|
+
setSwipeY(0);
|
|
222
|
+
swipeStartYRef.current = null;
|
|
223
|
+
};
|
|
224
|
+
const previouslyFocusedRef = react.useRef(null);
|
|
124
225
|
react.useEffect(() => {
|
|
125
|
-
if (
|
|
126
|
-
|
|
127
|
-
|
|
226
|
+
if (isActuallyOpen) {
|
|
227
|
+
previouslyFocusedRef.current = document.activeElement;
|
|
228
|
+
if (panelRef.current) {
|
|
229
|
+
activate(panelRef.current);
|
|
230
|
+
requestAnimationFrame(() => {
|
|
231
|
+
if (initialFocusRef?.current) {
|
|
232
|
+
initialFocusRef.current.focus();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
128
237
|
deactivate();
|
|
238
|
+
const target = finalFocusRef?.current ?? previouslyFocusedRef.current;
|
|
239
|
+
if (target && typeof target.focus === "function") {
|
|
240
|
+
target.focus();
|
|
241
|
+
}
|
|
129
242
|
}
|
|
130
|
-
}, [
|
|
243
|
+
}, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);
|
|
131
244
|
const onKeyDown = react.useCallback(
|
|
132
245
|
(e) => {
|
|
133
246
|
if (closeOnEsc && e.key === "Escape") {
|
|
134
|
-
|
|
247
|
+
requestClose("esc");
|
|
135
248
|
return;
|
|
136
249
|
}
|
|
137
250
|
handleKeyDown(e.nativeEvent);
|
|
138
251
|
},
|
|
139
|
-
[closeOnEsc,
|
|
252
|
+
[closeOnEsc, requestClose, handleKeyDown]
|
|
140
253
|
);
|
|
141
254
|
const handleOverlayClick = react.useCallback(
|
|
142
255
|
(e) => {
|
|
143
256
|
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
144
|
-
|
|
257
|
+
requestClose("overlay");
|
|
145
258
|
}
|
|
146
259
|
},
|
|
147
|
-
[closeOnOverlayClick,
|
|
260
|
+
[closeOnOverlayClick, requestClose]
|
|
148
261
|
);
|
|
149
262
|
const setPanelRef = react.useCallback(
|
|
150
263
|
(el) => {
|
|
151
264
|
panelRef.current = el;
|
|
152
265
|
if (typeof ref === "function") ref(el);
|
|
153
266
|
else if (ref) ref.current = el;
|
|
154
|
-
if (el &&
|
|
267
|
+
if (el && isActuallyOpen) activate(el);
|
|
155
268
|
},
|
|
156
|
-
[ref,
|
|
269
|
+
[ref, isActuallyOpen, activate]
|
|
157
270
|
);
|
|
158
271
|
if (!mounted || !rendered) return null;
|
|
159
272
|
const hasHeader = title !== void 0 || showCloseButton;
|
|
273
|
+
const portalTarget = container ?? document.body;
|
|
274
|
+
const overlayStyle = {
|
|
275
|
+
...overlayColor ? { "--rmod-overlay-bg": overlayColor } : {},
|
|
276
|
+
// Higher stack depth → lower modals scale down + fade slightly behind the topmost.
|
|
277
|
+
zIndex: 1e3 + stackDepth * 10
|
|
278
|
+
};
|
|
279
|
+
const panelStyle = {
|
|
280
|
+
// Stack-position effects: only applied when this isn't the topmost modal.
|
|
281
|
+
...stackPosition > 0 ? {
|
|
282
|
+
transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,
|
|
283
|
+
opacity: Math.max(0.6, 1 - stackPosition * 0.18)
|
|
284
|
+
} : {},
|
|
285
|
+
...swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: "none" } : {}
|
|
286
|
+
};
|
|
160
287
|
return reactDom.createPortal(
|
|
161
288
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
162
289
|
"div",
|
|
@@ -167,7 +294,10 @@ var ModalStyled = react.forwardRef(
|
|
|
167
294
|
].filter(Boolean).join(" "),
|
|
168
295
|
"data-variant": variant,
|
|
169
296
|
"data-blur": blur,
|
|
170
|
-
|
|
297
|
+
"data-transition": transition,
|
|
298
|
+
"data-stack-position": stackPosition || void 0,
|
|
299
|
+
"data-state": visible ? "open" : "closed",
|
|
300
|
+
style: overlayStyle,
|
|
171
301
|
onClick: handleOverlayClick,
|
|
172
302
|
onKeyDown,
|
|
173
303
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -177,6 +307,7 @@ var ModalStyled = react.forwardRef(
|
|
|
177
307
|
role: "dialog",
|
|
178
308
|
"aria-modal": "true",
|
|
179
309
|
"aria-labelledby": title ? titleId : void 0,
|
|
310
|
+
"aria-describedby": description ? descId : void 0,
|
|
180
311
|
tabIndex: -1,
|
|
181
312
|
className: [
|
|
182
313
|
"rmod-panel",
|
|
@@ -187,11 +318,29 @@ var ModalStyled = react.forwardRef(
|
|
|
187
318
|
"data-variant": variant,
|
|
188
319
|
"data-padding": padding,
|
|
189
320
|
"data-scrollable": scrollable ? "true" : void 0,
|
|
321
|
+
"data-transition": transition,
|
|
322
|
+
"data-state": visible ? "open" : "closed",
|
|
323
|
+
"data-swiping": swipeY > 0 ? "true" : void 0,
|
|
324
|
+
style: panelStyle,
|
|
325
|
+
onPointerDown: swipeEnabled ? handleSwipePointerDown : void 0,
|
|
326
|
+
onPointerMove: swipeEnabled ? handleSwipePointerMove : void 0,
|
|
327
|
+
onPointerUp: swipeEnabled ? handleSwipePointerUp : void 0,
|
|
328
|
+
onPointerCancel: swipeEnabled ? handleSwipePointerUp : void 0,
|
|
190
329
|
children: [
|
|
191
330
|
hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rmod-header", children: [
|
|
192
331
|
title ? /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
|
|
193
|
-
showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
332
|
+
showCloseButton && /* @__PURE__ */ jsxRuntime.jsx(
|
|
333
|
+
"button",
|
|
334
|
+
{
|
|
335
|
+
type: "button",
|
|
336
|
+
className: "rmod-close",
|
|
337
|
+
"aria-label": "Close",
|
|
338
|
+
onClick: () => requestClose("close-button"),
|
|
339
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }) })
|
|
340
|
+
}
|
|
341
|
+
)
|
|
194
342
|
] }),
|
|
343
|
+
description !== void 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { id: descId, className: "rmod-description", children: description }),
|
|
195
344
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-body", children }),
|
|
196
345
|
footer && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rmod-footer", children: footer })
|
|
197
346
|
]
|
|
@@ -199,11 +348,74 @@ var ModalStyled = react.forwardRef(
|
|
|
199
348
|
)
|
|
200
349
|
}
|
|
201
350
|
),
|
|
202
|
-
|
|
351
|
+
portalTarget
|
|
203
352
|
);
|
|
204
353
|
}
|
|
205
354
|
);
|
|
355
|
+
function confirm(opts = {}) {
|
|
356
|
+
return new Promise((resolve) => {
|
|
357
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
358
|
+
resolve(false);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
const host = document.createElement("div");
|
|
362
|
+
document.body.appendChild(host);
|
|
363
|
+
const root = client.createRoot(host);
|
|
364
|
+
const cleanup = () => {
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
try {
|
|
367
|
+
root.unmount();
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
if (host.parentNode) host.parentNode.removeChild(host);
|
|
371
|
+
}, 320);
|
|
372
|
+
};
|
|
373
|
+
function ConfirmShell() {
|
|
374
|
+
const [open, setOpen] = react.useState(true);
|
|
375
|
+
const finish = (value) => {
|
|
376
|
+
setOpen(false);
|
|
377
|
+
cleanup();
|
|
378
|
+
resolve(value);
|
|
379
|
+
};
|
|
380
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
381
|
+
ModalStyled,
|
|
382
|
+
{
|
|
383
|
+
open,
|
|
384
|
+
onClose: () => finish(false),
|
|
385
|
+
title: opts.title ?? "Are you sure?",
|
|
386
|
+
size: "sm",
|
|
387
|
+
footer: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
|
|
388
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
389
|
+
"button",
|
|
390
|
+
{
|
|
391
|
+
type: "button",
|
|
392
|
+
className: "rmod-btn rmod-btn--cancel",
|
|
393
|
+
onClick: () => finish(false),
|
|
394
|
+
children: opts.cancelLabel ?? "Cancel"
|
|
395
|
+
}
|
|
396
|
+
),
|
|
397
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
398
|
+
"button",
|
|
399
|
+
{
|
|
400
|
+
type: "button",
|
|
401
|
+
className: `rmod-btn rmod-btn--confirm${opts.danger ? " rmod-btn--danger" : ""}`,
|
|
402
|
+
onClick: () => finish(true),
|
|
403
|
+
autoFocus: true,
|
|
404
|
+
children: opts.confirmLabel ?? "Confirm"
|
|
405
|
+
}
|
|
406
|
+
)
|
|
407
|
+
] }),
|
|
408
|
+
children: opts.body ?? null
|
|
409
|
+
}
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
root.render(/* @__PURE__ */ jsxRuntime.jsx(ConfirmShell, {}));
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
var modal = { confirm };
|
|
206
416
|
|
|
207
417
|
exports.ModalStyled = ModalStyled;
|
|
418
|
+
exports.confirm = confirm;
|
|
419
|
+
exports.modal = modal;
|
|
208
420
|
//# sourceMappingURL=styled.cjs.map
|
|
209
421
|
//# sourceMappingURL=styled.cjs.map
|
package/dist/styled.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/useFocusTrap.ts","../src/styled/ModalStyled.tsx"],"names":["useRef","useCallback","forwardRef","ModalStyled","useId","useState","useEffect","createPortal","jsx","jsxs"],"mappings":"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","file":"styled.cjs","sourcesContent":["import { useCallback, useRef } from \"react\";\n\nconst FOCUSABLE_SELECTORS = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"details > summary\",\n].join(\", \");\n\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\n (el) => !el.closest(\"[inert]\") && getComputedStyle(el).display !== \"none\",\n );\n}\n\nexport interface UseFocusTrapResult {\n activate: (container: HTMLElement) => void;\n deactivate: () => void;\n handleKeyDown: (e: KeyboardEvent) => void;\n}\n\nexport function useFocusTrap(): UseFocusTrapResult {\n const containerRef = useRef<HTMLElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n\n const activate = useCallback((container: HTMLElement) => {\n containerRef.current = container;\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n\n const focusable = getFocusableElements(container);\n const first = focusable[0];\n if (first) {\n first.focus();\n } else {\n container.focus();\n }\n }, []);\n\n const deactivate = useCallback(() => {\n containerRef.current = null;\n const prev = previousFocusRef.current;\n if (prev && typeof prev.focus === \"function\") {\n prev.focus();\n }\n previousFocusRef.current = null;\n }, []);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key !== \"Tab\" || !containerRef.current) return;\n\n const focusable = getFocusableElements(containerRef.current);\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }, []);\n\n return { activate, deactivate, handleKeyDown };\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\n\nexport interface ModalStyledProps {\n isOpen: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n isOpen,\n onClose,\n title,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n },\n ref,\n ) {\n const titleId = useId();\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n useEffect(() => { setMounted(true); }, []);\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n\n if (isOpen) {\n setRendered(true);\n // Double RAF ensures the element is in the DOM before we add the visible class\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n } else {\n // Remove visible class first (triggers CSS exit transition)\n setVisible(false);\n // Then unmount after transition finishes\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n }, 300);\n document.body.style.overflow = originalOverflowRef.current;\n }\n }, [isOpen]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n document.body.style.overflow = originalOverflowRef.current;\n };\n }, []);\n\n useEffect(() => {\n if (isOpen && panelRef.current) {\n activate(panelRef.current);\n } else if (!isOpen) {\n deactivate();\n }\n }, [isOpen, activate, deactivate]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n onClose();\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, onClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n },\n [closeOnOverlayClick, onClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isOpen) activate(el);\n },\n [ref, isOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n style={overlayColor ? { \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties : undefined}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button type=\"button\" className=\"rmod-close\" aria-label=\"Close\" onClick={onClose}>\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n document.body,\n );\n },\n);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/useFocusTrap.ts","../src/styled/ModalStyled.tsx","../src/styled/confirm.tsx"],"names":["useRef","useCallback","forwardRef","ModalStyled","useState","useId","useEffect","createPortal","jsx","jsxs","createRoot"],"mappings":"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","file":"styled.cjs","sourcesContent":["import { useCallback, useRef } from \"react\";\n\nconst FOCUSABLE_SELECTORS = [\n \"a[href]\",\n \"button:not([disabled])\",\n \"input:not([disabled])\",\n \"select:not([disabled])\",\n \"textarea:not([disabled])\",\n \"[tabindex]:not([tabindex='-1'])\",\n \"details > summary\",\n].join(\", \");\n\nfunction getFocusableElements(container: HTMLElement): HTMLElement[] {\n return Array.from(container.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTORS)).filter(\n (el) => !el.closest(\"[inert]\") && getComputedStyle(el).display !== \"none\",\n );\n}\n\nexport interface UseFocusTrapResult {\n activate: (container: HTMLElement) => void;\n deactivate: () => void;\n handleKeyDown: (e: KeyboardEvent) => void;\n}\n\nexport function useFocusTrap(): UseFocusTrapResult {\n const containerRef = useRef<HTMLElement | null>(null);\n const previousFocusRef = useRef<HTMLElement | null>(null);\n\n const activate = useCallback((container: HTMLElement) => {\n containerRef.current = container;\n previousFocusRef.current = document.activeElement as HTMLElement | null;\n\n const focusable = getFocusableElements(container);\n const first = focusable[0];\n if (first) {\n first.focus();\n } else {\n container.focus();\n }\n }, []);\n\n const deactivate = useCallback(() => {\n containerRef.current = null;\n const prev = previousFocusRef.current;\n if (prev && typeof prev.focus === \"function\") {\n prev.focus();\n }\n previousFocusRef.current = null;\n }, []);\n\n const handleKeyDown = useCallback((e: KeyboardEvent) => {\n if (e.key !== \"Tab\" || !containerRef.current) return;\n\n const focusable = getFocusableElements(containerRef.current);\n if (focusable.length === 0) {\n e.preventDefault();\n return;\n }\n\n const first = focusable[0]!;\n const last = focusable[focusable.length - 1]!;\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n }, []);\n\n return { activate, deactivate, handleKeyDown };\n}\n","import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\nexport type CloseReason = \"esc\" | \"overlay\" | \"close-button\" | \"programmatic\" | \"swipe\" | \"submit\";\nexport type ModalTransition = \"fade\" | \"zoom\" | \"slide-up\" | \"slide-down\";\n\n// ---- Stacking registry (depth-aware z-index + scale-behind effect) -------\nconst _openModals: { id: number }[] = [];\nlet _modalIdCounter = 0;\nconst _stackListeners = new Set<() => void>();\nfunction _notifyStack() { _stackListeners.forEach((fn) => fn()); }\n\nexport interface ModalStyledProps {\n /** Open state. `open` is the canonical name; `isOpen` continues to work. */\n open?: boolean;\n isOpen?: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n /** Optional accessible description. Renders below the title and links via aria-describedby. */\n description?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n /** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */\n initialFocusRef?: RefObject<HTMLElement | null>;\n /** Element to focus when the modal closes. Defaults to whatever was focused before opening. */\n finalFocusRef?: RefObject<HTMLElement | null>;\n /** Fires after the open transition completes. */\n onAfterOpen?: () => void;\n /** Fires after the close transition completes (and unmount). */\n onAfterClose?: () => void;\n /** Return false to veto a close attempt. Receives the trigger reason. */\n preventClose?: (reason: CloseReason) => boolean;\n /** Disable body scroll lock while open. Default: true (locked). */\n lockBodyScroll?: boolean;\n /** Override the portal container. Default: document.body. */\n container?: HTMLElement | null;\n /** Transition variant for the panel (only `dialog`; drawers always slide). Default: \"fade\" */\n transition?: ModalTransition;\n /** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */\n swipeToDismiss?: boolean;\n /** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */\n closeOnSubmit?: boolean;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n open,\n isOpen,\n onClose,\n title,\n description,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n initialFocusRef,\n finalFocusRef,\n onAfterOpen,\n onAfterClose,\n preventClose,\n lockBodyScroll = true,\n container,\n transition = \"fade\",\n swipeToDismiss,\n closeOnSubmit = false,\n },\n ref,\n ) {\n const swipeEnabled = swipeToDismiss ?? variant === \"drawer-bottom\";\n const myStackIdRef = useRef<number>(-1);\n const [stackDepth, setStackDepth] = useState(0);\n const [stackPosition, setStackPosition] = useState(0); // 0 = top\n const [swipeY, setSwipeY] = useState(0);\n const titleId = useId();\n const descId = useId();\n const isActuallyOpen = open ?? isOpen ?? false;\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n const onAfterOpenRef = useRef(onAfterOpen);\n onAfterOpenRef.current = onAfterOpen;\n const onAfterCloseRef = useRef(onAfterClose);\n onAfterCloseRef.current = onAfterClose;\n\n useEffect(() => { setMounted(true); }, []);\n\n const requestClose = useCallback(\n (reason: CloseReason) => {\n if (preventClose && !preventClose(reason)) return;\n onClose();\n },\n [preventClose, onClose],\n );\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n if (enterTimerRef.current) {\n clearTimeout(enterTimerRef.current);\n enterTimerRef.current = null;\n }\n\n if (isActuallyOpen) {\n setRendered(true);\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n if (lockBodyScroll) {\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n // Fire onAfterOpen once the enter transition has had time to settle.\n enterTimerRef.current = setTimeout(() => {\n onAfterOpenRef.current?.();\n }, 320);\n } else {\n setVisible(false);\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n onAfterCloseRef.current?.();\n }, 300);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n }\n }, [isActuallyOpen, lockBodyScroll]);\n\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n if (enterTimerRef.current) clearTimeout(enterTimerRef.current);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n };\n }, [lockBodyScroll]);\n\n // ---- Stacking: register / deregister this modal ---------------------\n useEffect(() => {\n if (!isActuallyOpen) return;\n const id = ++_modalIdCounter;\n myStackIdRef.current = id;\n _openModals.push({ id });\n _notifyStack();\n const sync = () => {\n const idx = _openModals.findIndex((m) => m.id === id);\n const total = _openModals.length;\n setStackDepth(total);\n setStackPosition(idx === -1 ? 0 : total - 1 - idx);\n };\n sync();\n _stackListeners.add(sync);\n return () => {\n _stackListeners.delete(sync);\n const idx = _openModals.findIndex((m) => m.id === id);\n if (idx !== -1) _openModals.splice(idx, 1);\n _notifyStack();\n };\n }, [isActuallyOpen]);\n\n // ---- closeOnSubmit: auto-close on a contained <form> submit ---------\n useEffect(() => {\n if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;\n const panel = panelRef.current;\n const onSubmit = (e: Event) => {\n // Defer the close so the consumer's onSubmit handler runs first\n // and can call e.preventDefault() if it wants to abort the close.\n if (e.defaultPrevented) return;\n setTimeout(() => requestClose(\"submit\"), 0);\n };\n panel.addEventListener(\"submit\", onSubmit);\n return () => panel.removeEventListener(\"submit\", onSubmit);\n }, [closeOnSubmit, isActuallyOpen, requestClose]);\n\n // ---- Swipe-to-dismiss (touch only) -----------------------------------\n const swipeStartYRef = useRef<number | null>(null);\n const handleSwipePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!swipeEnabled || e.pointerType !== \"touch\") return;\n // Only initiate from the panel header / the panel itself, not buttons.\n swipeStartYRef.current = e.clientY;\n };\n const handleSwipePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (swipeStartYRef.current === null) return;\n const dy = Math.max(0, e.clientY - swipeStartYRef.current);\n setSwipeY(dy);\n };\n const handleSwipePointerUp = () => {\n if (swipeStartYRef.current === null) return;\n if (swipeY > 120) requestClose(\"swipe\");\n setSwipeY(0);\n swipeStartYRef.current = null;\n };\n\n // Focus management\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n useEffect(() => {\n if (isActuallyOpen) {\n previouslyFocusedRef.current = document.activeElement as HTMLElement | null;\n if (panelRef.current) {\n activate(panelRef.current);\n // Honor initialFocusRef after the panel is mounted+visible\n requestAnimationFrame(() => {\n if (initialFocusRef?.current) {\n initialFocusRef.current.focus();\n }\n });\n }\n } else {\n deactivate();\n // Honor finalFocusRef on close, otherwise restore previous focus\n const target = finalFocusRef?.current ?? previouslyFocusedRef.current;\n if (target && typeof target.focus === \"function\") {\n target.focus();\n }\n }\n }, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n requestClose(\"esc\");\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, requestClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n requestClose(\"overlay\");\n }\n },\n [closeOnOverlayClick, requestClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isActuallyOpen) activate(el);\n },\n [ref, isActuallyOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n const portalTarget = container ?? document.body;\n\n const overlayStyle: React.CSSProperties = {\n ...(overlayColor ? ({ \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties) : {}),\n // Higher stack depth → lower modals scale down + fade slightly behind the topmost.\n zIndex: 1000 + stackDepth * 10,\n };\n\n const panelStyle: React.CSSProperties = {\n // Stack-position effects: only applied when this isn't the topmost modal.\n ...(stackPosition > 0\n ? {\n transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,\n opacity: Math.max(0.6, 1 - stackPosition * 0.18),\n }\n : {}),\n ...(swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: \"none\" } : {}),\n };\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n data-transition={transition}\n data-stack-position={stackPosition || undefined}\n data-state={visible ? \"open\" : \"closed\"}\n style={overlayStyle}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={description ? descId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n data-transition={transition}\n data-state={visible ? \"open\" : \"closed\"}\n data-swiping={swipeY > 0 ? \"true\" : undefined}\n style={panelStyle}\n onPointerDown={swipeEnabled ? handleSwipePointerDown : undefined}\n onPointerMove={swipeEnabled ? handleSwipePointerMove : undefined}\n onPointerUp={swipeEnabled ? handleSwipePointerUp : undefined}\n onPointerCancel={swipeEnabled ? handleSwipePointerUp : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button\n type=\"button\"\n className=\"rmod-close\"\n aria-label=\"Close\"\n onClick={() => requestClose(\"close-button\")}\n >\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n {description !== undefined && (\n <p id={descId} className=\"rmod-description\">{description}</p>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n portalTarget,\n );\n },\n);\n","import { useState, type ReactNode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { ModalStyled } from \"./ModalStyled\";\n\nexport interface ConfirmOptions {\n title?: ReactNode;\n body?: ReactNode;\n confirmLabel?: ReactNode;\n cancelLabel?: ReactNode;\n /** Style the confirm button as the danger CTA (red). */\n danger?: boolean;\n}\n\n/**\n * Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled\n * or dismissed via overlay/Esc/close button.\n *\n * Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`\n * and unmounts on close — no provider required.\n */\nexport function confirm(opts: ConfirmOptions = {}): Promise<boolean> {\n return new Promise((resolve) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n resolve(false);\n return;\n }\n const host = document.createElement(\"div\");\n document.body.appendChild(host);\n const root = createRoot(host);\n\n const cleanup = () => {\n // Defer unmount so the close transition can run.\n setTimeout(() => {\n try { root.unmount(); } catch { /* ignore */ }\n if (host.parentNode) host.parentNode.removeChild(host);\n }, 320);\n };\n\n function ConfirmShell() {\n const [open, setOpen] = useState(true);\n const finish = (value: boolean) => {\n setOpen(false);\n cleanup();\n resolve(value);\n };\n return (\n <ModalStyled\n open={open}\n onClose={() => finish(false)}\n title={opts.title ?? \"Are you sure?\"}\n size=\"sm\"\n footer={\n <div style={{ display: \"flex\", gap: 8, justifyContent: \"flex-end\" }}>\n <button\n type=\"button\"\n className=\"rmod-btn rmod-btn--cancel\"\n onClick={() => finish(false)}\n >\n {opts.cancelLabel ?? \"Cancel\"}\n </button>\n <button\n type=\"button\"\n className={`rmod-btn rmod-btn--confirm${opts.danger ? \" rmod-btn--danger\" : \"\"}`}\n onClick={() => finish(true)}\n autoFocus\n >\n {opts.confirmLabel ?? \"Confirm\"}\n </button>\n </div>\n }\n >\n {opts.body ?? null}\n </ModalStyled>\n );\n }\n\n root.render(<ConfirmShell />);\n });\n}\n\n/** Module-style export so consumers can do `modal.confirm(...)`. */\nexport const modal = { confirm };\n"]}
|
package/dist/styled.d.cts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, RefObject } from 'react';
|
|
3
3
|
|
|
4
4
|
type ModalSize = "sm" | "md" | "lg" | "full";
|
|
5
5
|
type ModalVariant = "dialog" | "drawer-left" | "drawer-right" | "drawer-bottom";
|
|
6
|
+
type CloseReason = "esc" | "overlay" | "close-button" | "programmatic" | "swipe" | "submit";
|
|
7
|
+
type ModalTransition = "fade" | "zoom" | "slide-up" | "slide-down";
|
|
6
8
|
interface ModalStyledProps {
|
|
7
|
-
isOpen
|
|
9
|
+
/** Open state. `open` is the canonical name; `isOpen` continues to work. */
|
|
10
|
+
open?: boolean;
|
|
11
|
+
isOpen?: boolean;
|
|
8
12
|
onClose: () => void;
|
|
9
13
|
/** Modal heading — omit to hide the header entirely */
|
|
10
14
|
title?: ReactNode;
|
|
15
|
+
/** Optional accessible description. Renders below the title and links via aria-describedby. */
|
|
16
|
+
description?: ReactNode;
|
|
11
17
|
children: ReactNode;
|
|
12
18
|
/** Footer content — omit to hide footer */
|
|
13
19
|
footer?: ReactNode;
|
|
@@ -25,7 +31,48 @@ interface ModalStyledProps {
|
|
|
25
31
|
/** Max height of scrollable body. Default: auto */
|
|
26
32
|
scrollable?: boolean;
|
|
27
33
|
className?: string;
|
|
34
|
+
/** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */
|
|
35
|
+
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
36
|
+
/** Element to focus when the modal closes. Defaults to whatever was focused before opening. */
|
|
37
|
+
finalFocusRef?: RefObject<HTMLElement | null>;
|
|
38
|
+
/** Fires after the open transition completes. */
|
|
39
|
+
onAfterOpen?: () => void;
|
|
40
|
+
/** Fires after the close transition completes (and unmount). */
|
|
41
|
+
onAfterClose?: () => void;
|
|
42
|
+
/** Return false to veto a close attempt. Receives the trigger reason. */
|
|
43
|
+
preventClose?: (reason: CloseReason) => boolean;
|
|
44
|
+
/** Disable body scroll lock while open. Default: true (locked). */
|
|
45
|
+
lockBodyScroll?: boolean;
|
|
46
|
+
/** Override the portal container. Default: document.body. */
|
|
47
|
+
container?: HTMLElement | null;
|
|
48
|
+
/** Transition variant for the panel (only `dialog`; drawers always slide). Default: "fade" */
|
|
49
|
+
transition?: ModalTransition;
|
|
50
|
+
/** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */
|
|
51
|
+
swipeToDismiss?: boolean;
|
|
52
|
+
/** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */
|
|
53
|
+
closeOnSubmit?: boolean;
|
|
28
54
|
}
|
|
29
55
|
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
30
56
|
|
|
31
|
-
|
|
57
|
+
interface ConfirmOptions {
|
|
58
|
+
title?: ReactNode;
|
|
59
|
+
body?: ReactNode;
|
|
60
|
+
confirmLabel?: ReactNode;
|
|
61
|
+
cancelLabel?: ReactNode;
|
|
62
|
+
/** Style the confirm button as the danger CTA (red). */
|
|
63
|
+
danger?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled
|
|
67
|
+
* or dismissed via overlay/Esc/close button.
|
|
68
|
+
*
|
|
69
|
+
* Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`
|
|
70
|
+
* and unmounts on close — no provider required.
|
|
71
|
+
*/
|
|
72
|
+
declare function confirm(opts?: ConfirmOptions): Promise<boolean>;
|
|
73
|
+
/** Module-style export so consumers can do `modal.confirm(...)`. */
|
|
74
|
+
declare const modal: {
|
|
75
|
+
confirm: typeof confirm;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export { type ConfirmOptions, type ModalSize, ModalStyled, type ModalStyledProps, type ModalTransition, type ModalVariant, confirm, modal };
|
package/dist/styled.d.ts
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, RefObject } from 'react';
|
|
3
3
|
|
|
4
4
|
type ModalSize = "sm" | "md" | "lg" | "full";
|
|
5
5
|
type ModalVariant = "dialog" | "drawer-left" | "drawer-right" | "drawer-bottom";
|
|
6
|
+
type CloseReason = "esc" | "overlay" | "close-button" | "programmatic" | "swipe" | "submit";
|
|
7
|
+
type ModalTransition = "fade" | "zoom" | "slide-up" | "slide-down";
|
|
6
8
|
interface ModalStyledProps {
|
|
7
|
-
isOpen
|
|
9
|
+
/** Open state. `open` is the canonical name; `isOpen` continues to work. */
|
|
10
|
+
open?: boolean;
|
|
11
|
+
isOpen?: boolean;
|
|
8
12
|
onClose: () => void;
|
|
9
13
|
/** Modal heading — omit to hide the header entirely */
|
|
10
14
|
title?: ReactNode;
|
|
15
|
+
/** Optional accessible description. Renders below the title and links via aria-describedby. */
|
|
16
|
+
description?: ReactNode;
|
|
11
17
|
children: ReactNode;
|
|
12
18
|
/** Footer content — omit to hide footer */
|
|
13
19
|
footer?: ReactNode;
|
|
@@ -25,7 +31,48 @@ interface ModalStyledProps {
|
|
|
25
31
|
/** Max height of scrollable body. Default: auto */
|
|
26
32
|
scrollable?: boolean;
|
|
27
33
|
className?: string;
|
|
34
|
+
/** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */
|
|
35
|
+
initialFocusRef?: RefObject<HTMLElement | null>;
|
|
36
|
+
/** Element to focus when the modal closes. Defaults to whatever was focused before opening. */
|
|
37
|
+
finalFocusRef?: RefObject<HTMLElement | null>;
|
|
38
|
+
/** Fires after the open transition completes. */
|
|
39
|
+
onAfterOpen?: () => void;
|
|
40
|
+
/** Fires after the close transition completes (and unmount). */
|
|
41
|
+
onAfterClose?: () => void;
|
|
42
|
+
/** Return false to veto a close attempt. Receives the trigger reason. */
|
|
43
|
+
preventClose?: (reason: CloseReason) => boolean;
|
|
44
|
+
/** Disable body scroll lock while open. Default: true (locked). */
|
|
45
|
+
lockBodyScroll?: boolean;
|
|
46
|
+
/** Override the portal container. Default: document.body. */
|
|
47
|
+
container?: HTMLElement | null;
|
|
48
|
+
/** Transition variant for the panel (only `dialog`; drawers always slide). Default: "fade" */
|
|
49
|
+
transition?: ModalTransition;
|
|
50
|
+
/** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */
|
|
51
|
+
swipeToDismiss?: boolean;
|
|
52
|
+
/** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */
|
|
53
|
+
closeOnSubmit?: boolean;
|
|
28
54
|
}
|
|
29
55
|
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
30
56
|
|
|
31
|
-
|
|
57
|
+
interface ConfirmOptions {
|
|
58
|
+
title?: ReactNode;
|
|
59
|
+
body?: ReactNode;
|
|
60
|
+
confirmLabel?: ReactNode;
|
|
61
|
+
cancelLabel?: ReactNode;
|
|
62
|
+
/** Style the confirm button as the danger CTA (red). */
|
|
63
|
+
danger?: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled
|
|
67
|
+
* or dismissed via overlay/Esc/close button.
|
|
68
|
+
*
|
|
69
|
+
* Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`
|
|
70
|
+
* and unmounts on close — no provider required.
|
|
71
|
+
*/
|
|
72
|
+
declare function confirm(opts?: ConfirmOptions): Promise<boolean>;
|
|
73
|
+
/** Module-style export so consumers can do `modal.confirm(...)`. */
|
|
74
|
+
declare const modal: {
|
|
75
|
+
confirm: typeof confirm;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export { type ConfirmOptions, type ModalSize, ModalStyled, type ModalStyledProps, type ModalTransition, type ModalVariant, confirm, modal };
|
package/dist/styled.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { useFocusTrap } from './chunk-BFH4N5GR.js';
|
|
2
|
-
import { forwardRef,
|
|
2
|
+
import { forwardRef, useRef, useState, useId, useEffect, useCallback } from 'react';
|
|
3
3
|
import { createPortal } from 'react-dom';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
import { createRoot } from 'react-dom/client';
|
|
5
6
|
|
|
7
|
+
var _openModals = [];
|
|
8
|
+
var _modalIdCounter = 0;
|
|
9
|
+
var _stackListeners = /* @__PURE__ */ new Set();
|
|
10
|
+
function _notifyStack() {
|
|
11
|
+
_stackListeners.forEach((fn) => fn());
|
|
12
|
+
}
|
|
6
13
|
var ModalStyled = forwardRef(
|
|
7
14
|
function ModalStyled2({
|
|
15
|
+
open,
|
|
8
16
|
isOpen,
|
|
9
17
|
onClose,
|
|
10
18
|
title,
|
|
19
|
+
description,
|
|
11
20
|
children,
|
|
12
21
|
footer,
|
|
13
22
|
size = "md",
|
|
@@ -19,83 +28,201 @@ var ModalStyled = forwardRef(
|
|
|
19
28
|
overlayColor,
|
|
20
29
|
padding = "md",
|
|
21
30
|
scrollable = true,
|
|
22
|
-
className
|
|
31
|
+
className,
|
|
32
|
+
initialFocusRef,
|
|
33
|
+
finalFocusRef,
|
|
34
|
+
onAfterOpen,
|
|
35
|
+
onAfterClose,
|
|
36
|
+
preventClose,
|
|
37
|
+
lockBodyScroll = true,
|
|
38
|
+
container,
|
|
39
|
+
transition = "fade",
|
|
40
|
+
swipeToDismiss,
|
|
41
|
+
closeOnSubmit = false
|
|
23
42
|
}, ref) {
|
|
43
|
+
const swipeEnabled = swipeToDismiss ?? variant === "drawer-bottom";
|
|
44
|
+
const myStackIdRef = useRef(-1);
|
|
45
|
+
const [stackDepth, setStackDepth] = useState(0);
|
|
46
|
+
const [stackPosition, setStackPosition] = useState(0);
|
|
47
|
+
const [swipeY, setSwipeY] = useState(0);
|
|
24
48
|
const titleId = useId();
|
|
49
|
+
const descId = useId();
|
|
50
|
+
const isActuallyOpen = open ?? isOpen ?? false;
|
|
25
51
|
const [mounted, setMounted] = useState(false);
|
|
26
52
|
const [rendered, setRendered] = useState(false);
|
|
27
53
|
const [visible, setVisible] = useState(false);
|
|
28
54
|
const panelRef = useRef(null);
|
|
29
55
|
const originalOverflowRef = useRef("");
|
|
30
56
|
const exitTimerRef = useRef(null);
|
|
57
|
+
const enterTimerRef = useRef(null);
|
|
31
58
|
const { activate, deactivate, handleKeyDown } = useFocusTrap();
|
|
59
|
+
const onAfterOpenRef = useRef(onAfterOpen);
|
|
60
|
+
onAfterOpenRef.current = onAfterOpen;
|
|
61
|
+
const onAfterCloseRef = useRef(onAfterClose);
|
|
62
|
+
onAfterCloseRef.current = onAfterClose;
|
|
32
63
|
useEffect(() => {
|
|
33
64
|
setMounted(true);
|
|
34
65
|
}, []);
|
|
66
|
+
const requestClose = useCallback(
|
|
67
|
+
(reason) => {
|
|
68
|
+
if (preventClose && !preventClose(reason)) return;
|
|
69
|
+
onClose();
|
|
70
|
+
},
|
|
71
|
+
[preventClose, onClose]
|
|
72
|
+
);
|
|
35
73
|
useEffect(() => {
|
|
36
74
|
if (exitTimerRef.current) {
|
|
37
75
|
clearTimeout(exitTimerRef.current);
|
|
38
76
|
exitTimerRef.current = null;
|
|
39
77
|
}
|
|
40
|
-
if (
|
|
78
|
+
if (enterTimerRef.current) {
|
|
79
|
+
clearTimeout(enterTimerRef.current);
|
|
80
|
+
enterTimerRef.current = null;
|
|
81
|
+
}
|
|
82
|
+
if (isActuallyOpen) {
|
|
41
83
|
setRendered(true);
|
|
42
84
|
requestAnimationFrame(() => {
|
|
43
85
|
requestAnimationFrame(() => {
|
|
44
86
|
setVisible(true);
|
|
45
87
|
});
|
|
46
88
|
});
|
|
47
|
-
|
|
48
|
-
|
|
89
|
+
if (lockBodyScroll) {
|
|
90
|
+
originalOverflowRef.current = document.body.style.overflow;
|
|
91
|
+
document.body.style.overflow = "hidden";
|
|
92
|
+
}
|
|
93
|
+
enterTimerRef.current = setTimeout(() => {
|
|
94
|
+
onAfterOpenRef.current?.();
|
|
95
|
+
}, 320);
|
|
49
96
|
} else {
|
|
50
97
|
setVisible(false);
|
|
51
98
|
exitTimerRef.current = setTimeout(() => {
|
|
52
99
|
setRendered(false);
|
|
100
|
+
onAfterCloseRef.current?.();
|
|
53
101
|
}, 300);
|
|
54
|
-
|
|
102
|
+
if (lockBodyScroll) {
|
|
103
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
104
|
+
}
|
|
55
105
|
}
|
|
56
|
-
}, [
|
|
106
|
+
}, [isActuallyOpen, lockBodyScroll]);
|
|
57
107
|
useEffect(() => {
|
|
58
108
|
return () => {
|
|
59
109
|
if (exitTimerRef.current) clearTimeout(exitTimerRef.current);
|
|
60
|
-
|
|
110
|
+
if (enterTimerRef.current) clearTimeout(enterTimerRef.current);
|
|
111
|
+
if (lockBodyScroll) {
|
|
112
|
+
document.body.style.overflow = originalOverflowRef.current;
|
|
113
|
+
}
|
|
61
114
|
};
|
|
62
|
-
}, []);
|
|
115
|
+
}, [lockBodyScroll]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
if (!isActuallyOpen) return;
|
|
118
|
+
const id = ++_modalIdCounter;
|
|
119
|
+
myStackIdRef.current = id;
|
|
120
|
+
_openModals.push({ id });
|
|
121
|
+
_notifyStack();
|
|
122
|
+
const sync = () => {
|
|
123
|
+
const idx = _openModals.findIndex((m) => m.id === id);
|
|
124
|
+
const total = _openModals.length;
|
|
125
|
+
setStackDepth(total);
|
|
126
|
+
setStackPosition(idx === -1 ? 0 : total - 1 - idx);
|
|
127
|
+
};
|
|
128
|
+
sync();
|
|
129
|
+
_stackListeners.add(sync);
|
|
130
|
+
return () => {
|
|
131
|
+
_stackListeners.delete(sync);
|
|
132
|
+
const idx = _openModals.findIndex((m) => m.id === id);
|
|
133
|
+
if (idx !== -1) _openModals.splice(idx, 1);
|
|
134
|
+
_notifyStack();
|
|
135
|
+
};
|
|
136
|
+
}, [isActuallyOpen]);
|
|
63
137
|
useEffect(() => {
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
138
|
+
if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;
|
|
139
|
+
const panel = panelRef.current;
|
|
140
|
+
const onSubmit = (e) => {
|
|
141
|
+
if (e.defaultPrevented) return;
|
|
142
|
+
setTimeout(() => requestClose("submit"), 0);
|
|
143
|
+
};
|
|
144
|
+
panel.addEventListener("submit", onSubmit);
|
|
145
|
+
return () => panel.removeEventListener("submit", onSubmit);
|
|
146
|
+
}, [closeOnSubmit, isActuallyOpen, requestClose]);
|
|
147
|
+
const swipeStartYRef = useRef(null);
|
|
148
|
+
const handleSwipePointerDown = (e) => {
|
|
149
|
+
if (!swipeEnabled || e.pointerType !== "touch") return;
|
|
150
|
+
swipeStartYRef.current = e.clientY;
|
|
151
|
+
};
|
|
152
|
+
const handleSwipePointerMove = (e) => {
|
|
153
|
+
if (swipeStartYRef.current === null) return;
|
|
154
|
+
const dy = Math.max(0, e.clientY - swipeStartYRef.current);
|
|
155
|
+
setSwipeY(dy);
|
|
156
|
+
};
|
|
157
|
+
const handleSwipePointerUp = () => {
|
|
158
|
+
if (swipeStartYRef.current === null) return;
|
|
159
|
+
if (swipeY > 120) requestClose("swipe");
|
|
160
|
+
setSwipeY(0);
|
|
161
|
+
swipeStartYRef.current = null;
|
|
162
|
+
};
|
|
163
|
+
const previouslyFocusedRef = useRef(null);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
if (isActuallyOpen) {
|
|
166
|
+
previouslyFocusedRef.current = document.activeElement;
|
|
167
|
+
if (panelRef.current) {
|
|
168
|
+
activate(panelRef.current);
|
|
169
|
+
requestAnimationFrame(() => {
|
|
170
|
+
if (initialFocusRef?.current) {
|
|
171
|
+
initialFocusRef.current.focus();
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
67
176
|
deactivate();
|
|
177
|
+
const target = finalFocusRef?.current ?? previouslyFocusedRef.current;
|
|
178
|
+
if (target && typeof target.focus === "function") {
|
|
179
|
+
target.focus();
|
|
180
|
+
}
|
|
68
181
|
}
|
|
69
|
-
}, [
|
|
182
|
+
}, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);
|
|
70
183
|
const onKeyDown = useCallback(
|
|
71
184
|
(e) => {
|
|
72
185
|
if (closeOnEsc && e.key === "Escape") {
|
|
73
|
-
|
|
186
|
+
requestClose("esc");
|
|
74
187
|
return;
|
|
75
188
|
}
|
|
76
189
|
handleKeyDown(e.nativeEvent);
|
|
77
190
|
},
|
|
78
|
-
[closeOnEsc,
|
|
191
|
+
[closeOnEsc, requestClose, handleKeyDown]
|
|
79
192
|
);
|
|
80
193
|
const handleOverlayClick = useCallback(
|
|
81
194
|
(e) => {
|
|
82
195
|
if (closeOnOverlayClick && e.target === e.currentTarget) {
|
|
83
|
-
|
|
196
|
+
requestClose("overlay");
|
|
84
197
|
}
|
|
85
198
|
},
|
|
86
|
-
[closeOnOverlayClick,
|
|
199
|
+
[closeOnOverlayClick, requestClose]
|
|
87
200
|
);
|
|
88
201
|
const setPanelRef = useCallback(
|
|
89
202
|
(el) => {
|
|
90
203
|
panelRef.current = el;
|
|
91
204
|
if (typeof ref === "function") ref(el);
|
|
92
205
|
else if (ref) ref.current = el;
|
|
93
|
-
if (el &&
|
|
206
|
+
if (el && isActuallyOpen) activate(el);
|
|
94
207
|
},
|
|
95
|
-
[ref,
|
|
208
|
+
[ref, isActuallyOpen, activate]
|
|
96
209
|
);
|
|
97
210
|
if (!mounted || !rendered) return null;
|
|
98
211
|
const hasHeader = title !== void 0 || showCloseButton;
|
|
212
|
+
const portalTarget = container ?? document.body;
|
|
213
|
+
const overlayStyle = {
|
|
214
|
+
...overlayColor ? { "--rmod-overlay-bg": overlayColor } : {},
|
|
215
|
+
// Higher stack depth → lower modals scale down + fade slightly behind the topmost.
|
|
216
|
+
zIndex: 1e3 + stackDepth * 10
|
|
217
|
+
};
|
|
218
|
+
const panelStyle = {
|
|
219
|
+
// Stack-position effects: only applied when this isn't the topmost modal.
|
|
220
|
+
...stackPosition > 0 ? {
|
|
221
|
+
transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,
|
|
222
|
+
opacity: Math.max(0.6, 1 - stackPosition * 0.18)
|
|
223
|
+
} : {},
|
|
224
|
+
...swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: "none" } : {}
|
|
225
|
+
};
|
|
99
226
|
return createPortal(
|
|
100
227
|
/* @__PURE__ */ jsx(
|
|
101
228
|
"div",
|
|
@@ -106,7 +233,10 @@ var ModalStyled = forwardRef(
|
|
|
106
233
|
].filter(Boolean).join(" "),
|
|
107
234
|
"data-variant": variant,
|
|
108
235
|
"data-blur": blur,
|
|
109
|
-
|
|
236
|
+
"data-transition": transition,
|
|
237
|
+
"data-stack-position": stackPosition || void 0,
|
|
238
|
+
"data-state": visible ? "open" : "closed",
|
|
239
|
+
style: overlayStyle,
|
|
110
240
|
onClick: handleOverlayClick,
|
|
111
241
|
onKeyDown,
|
|
112
242
|
children: /* @__PURE__ */ jsxs(
|
|
@@ -116,6 +246,7 @@ var ModalStyled = forwardRef(
|
|
|
116
246
|
role: "dialog",
|
|
117
247
|
"aria-modal": "true",
|
|
118
248
|
"aria-labelledby": title ? titleId : void 0,
|
|
249
|
+
"aria-describedby": description ? descId : void 0,
|
|
119
250
|
tabIndex: -1,
|
|
120
251
|
className: [
|
|
121
252
|
"rmod-panel",
|
|
@@ -126,11 +257,29 @@ var ModalStyled = forwardRef(
|
|
|
126
257
|
"data-variant": variant,
|
|
127
258
|
"data-padding": padding,
|
|
128
259
|
"data-scrollable": scrollable ? "true" : void 0,
|
|
260
|
+
"data-transition": transition,
|
|
261
|
+
"data-state": visible ? "open" : "closed",
|
|
262
|
+
"data-swiping": swipeY > 0 ? "true" : void 0,
|
|
263
|
+
style: panelStyle,
|
|
264
|
+
onPointerDown: swipeEnabled ? handleSwipePointerDown : void 0,
|
|
265
|
+
onPointerMove: swipeEnabled ? handleSwipePointerMove : void 0,
|
|
266
|
+
onPointerUp: swipeEnabled ? handleSwipePointerUp : void 0,
|
|
267
|
+
onPointerCancel: swipeEnabled ? handleSwipePointerUp : void 0,
|
|
129
268
|
children: [
|
|
130
269
|
hasHeader && /* @__PURE__ */ jsxs("div", { className: "rmod-header", children: [
|
|
131
270
|
title ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsx("span", {}),
|
|
132
|
-
showCloseButton && /* @__PURE__ */ jsx(
|
|
271
|
+
showCloseButton && /* @__PURE__ */ jsx(
|
|
272
|
+
"button",
|
|
273
|
+
{
|
|
274
|
+
type: "button",
|
|
275
|
+
className: "rmod-close",
|
|
276
|
+
"aria-label": "Close",
|
|
277
|
+
onClick: () => requestClose("close-button"),
|
|
278
|
+
children: /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M12 4L4 12M4 4l8 8", stroke: "currentColor", strokeWidth: "1.75", strokeLinecap: "round" }) })
|
|
279
|
+
}
|
|
280
|
+
)
|
|
133
281
|
] }),
|
|
282
|
+
description !== void 0 && /* @__PURE__ */ jsx("p", { id: descId, className: "rmod-description", children: description }),
|
|
134
283
|
/* @__PURE__ */ jsx("div", { className: "rmod-body", children }),
|
|
135
284
|
footer && /* @__PURE__ */ jsx("div", { className: "rmod-footer", children: footer })
|
|
136
285
|
]
|
|
@@ -138,11 +287,72 @@ var ModalStyled = forwardRef(
|
|
|
138
287
|
)
|
|
139
288
|
}
|
|
140
289
|
),
|
|
141
|
-
|
|
290
|
+
portalTarget
|
|
142
291
|
);
|
|
143
292
|
}
|
|
144
293
|
);
|
|
294
|
+
function confirm(opts = {}) {
|
|
295
|
+
return new Promise((resolve) => {
|
|
296
|
+
if (typeof window === "undefined" || typeof document === "undefined") {
|
|
297
|
+
resolve(false);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const host = document.createElement("div");
|
|
301
|
+
document.body.appendChild(host);
|
|
302
|
+
const root = createRoot(host);
|
|
303
|
+
const cleanup = () => {
|
|
304
|
+
setTimeout(() => {
|
|
305
|
+
try {
|
|
306
|
+
root.unmount();
|
|
307
|
+
} catch {
|
|
308
|
+
}
|
|
309
|
+
if (host.parentNode) host.parentNode.removeChild(host);
|
|
310
|
+
}, 320);
|
|
311
|
+
};
|
|
312
|
+
function ConfirmShell() {
|
|
313
|
+
const [open, setOpen] = useState(true);
|
|
314
|
+
const finish = (value) => {
|
|
315
|
+
setOpen(false);
|
|
316
|
+
cleanup();
|
|
317
|
+
resolve(value);
|
|
318
|
+
};
|
|
319
|
+
return /* @__PURE__ */ jsx(
|
|
320
|
+
ModalStyled,
|
|
321
|
+
{
|
|
322
|
+
open,
|
|
323
|
+
onClose: () => finish(false),
|
|
324
|
+
title: opts.title ?? "Are you sure?",
|
|
325
|
+
size: "sm",
|
|
326
|
+
footer: /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, justifyContent: "flex-end" }, children: [
|
|
327
|
+
/* @__PURE__ */ jsx(
|
|
328
|
+
"button",
|
|
329
|
+
{
|
|
330
|
+
type: "button",
|
|
331
|
+
className: "rmod-btn rmod-btn--cancel",
|
|
332
|
+
onClick: () => finish(false),
|
|
333
|
+
children: opts.cancelLabel ?? "Cancel"
|
|
334
|
+
}
|
|
335
|
+
),
|
|
336
|
+
/* @__PURE__ */ jsx(
|
|
337
|
+
"button",
|
|
338
|
+
{
|
|
339
|
+
type: "button",
|
|
340
|
+
className: `rmod-btn rmod-btn--confirm${opts.danger ? " rmod-btn--danger" : ""}`,
|
|
341
|
+
onClick: () => finish(true),
|
|
342
|
+
autoFocus: true,
|
|
343
|
+
children: opts.confirmLabel ?? "Confirm"
|
|
344
|
+
}
|
|
345
|
+
)
|
|
346
|
+
] }),
|
|
347
|
+
children: opts.body ?? null
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
root.render(/* @__PURE__ */ jsx(ConfirmShell, {}));
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
var modal = { confirm };
|
|
145
355
|
|
|
146
|
-
export { ModalStyled };
|
|
356
|
+
export { ModalStyled, confirm, modal };
|
|
147
357
|
//# sourceMappingURL=styled.js.map
|
|
148
358
|
//# sourceMappingURL=styled.js.map
|
package/dist/styled.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/styled/ModalStyled.tsx"],"names":["ModalStyled"],"mappings":"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","file":"styled.js","sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\n\nexport interface ModalStyledProps {\n isOpen: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n isOpen,\n onClose,\n title,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n },\n ref,\n ) {\n const titleId = useId();\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n useEffect(() => { setMounted(true); }, []);\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n\n if (isOpen) {\n setRendered(true);\n // Double RAF ensures the element is in the DOM before we add the visible class\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n } else {\n // Remove visible class first (triggers CSS exit transition)\n setVisible(false);\n // Then unmount after transition finishes\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n }, 300);\n document.body.style.overflow = originalOverflowRef.current;\n }\n }, [isOpen]);\n\n // Cleanup on unmount\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n document.body.style.overflow = originalOverflowRef.current;\n };\n }, []);\n\n useEffect(() => {\n if (isOpen && panelRef.current) {\n activate(panelRef.current);\n } else if (!isOpen) {\n deactivate();\n }\n }, [isOpen, activate, deactivate]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n onClose();\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, onClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n onClose();\n }\n },\n [closeOnOverlayClick, onClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isOpen) activate(el);\n },\n [ref, isOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n style={overlayColor ? { \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties : undefined}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button type=\"button\" className=\"rmod-close\" aria-label=\"Close\" onClick={onClose}>\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n document.body,\n );\n },\n);\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/styled/ModalStyled.tsx","../src/styled/confirm.tsx"],"names":["ModalStyled","useState","jsx","jsxs"],"mappings":";;;;;;AAmBA,IAAM,cAAgC,EAAC;AACvC,IAAI,eAAA,GAAkB,CAAA;AACtB,IAAM,eAAA,uBAAsB,GAAA,EAAgB;AAC5C,SAAS,YAAA,GAAe;AAAE,EAAA,eAAA,CAAgB,OAAA,CAAQ,CAAC,EAAA,KAAO,EAAA,EAAI,CAAA;AAAG;AAkD1D,IAAM,WAAA,GAAc,UAAA;AAAA,EACzB,SAASA,YAAAA,CACP;AAAA,IACE,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAA;AAAA,IACA,QAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA,GAAO,IAAA;AAAA,IACP,OAAA,GAAU,QAAA;AAAA,IACV,mBAAA,GAAsB,IAAA;AAAA,IACtB,UAAA,GAAa,IAAA;AAAA,IACb,eAAA,GAAkB,IAAA;AAAA,IAClB,IAAA,GAAO,IAAA;AAAA,IACP,YAAA;AAAA,IACA,OAAA,GAAU,IAAA;AAAA,IACV,UAAA,GAAa,IAAA;AAAA,IACb,SAAA;AAAA,IACA,eAAA;AAAA,IACA,aAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA,GAAiB,IAAA;AAAA,IACjB,SAAA;AAAA,IACA,UAAA,GAAa,MAAA;AAAA,IACb,cAAA;AAAA,IACA,aAAA,GAAgB;AAAA,KAElB,GAAA,EACA;AACA,IAAA,MAAM,YAAA,GAAe,kBAAkB,OAAA,KAAY,eAAA;AACnD,IAAA,MAAM,YAAA,GAAe,OAAe,EAAE,CAAA;AACtC,IAAA,MAAM,CAAC,UAAA,EAAY,aAAa,CAAA,GAAI,SAAS,CAAC,CAAA;AAC9C,IAAA,MAAM,CAAC,aAAA,EAAe,gBAAgB,CAAA,GAAI,SAAS,CAAC,CAAA;AACpD,IAAA,MAAM,CAAC,MAAA,EAAQ,SAAS,CAAA,GAAI,SAAS,CAAC,CAAA;AACtC,IAAA,MAAM,UAAU,KAAA,EAAM;AACtB,IAAA,MAAM,SAAS,KAAA,EAAM;AACrB,IAAA,MAAM,cAAA,GAAiB,QAAQ,MAAA,IAAU,KAAA;AACzC,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,SAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,QAAA,GAAW,OAA8B,IAAI,CAAA;AACnD,IAAA,MAAM,mBAAA,GAAsB,OAAO,EAAE,CAAA;AACrC,IAAA,MAAM,YAAA,GAAe,OAA6C,IAAI,CAAA;AACtE,IAAA,MAAM,aAAA,GAAgB,OAA6C,IAAI,CAAA;AACvE,IAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAY,aAAA,KAAkB,YAAA,EAAa;AAE7D,IAAA,MAAM,cAAA,GAAiB,OAAO,WAAW,CAAA;AACzC,IAAA,cAAA,CAAe,OAAA,GAAU,WAAA;AACzB,IAAA,MAAM,eAAA,GAAkB,OAAO,YAAY,CAAA;AAC3C,IAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAE1B,IAAA,SAAA,CAAU,MAAM;AAAE,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA,IAAG,CAAA,EAAG,EAAE,CAAA;AAEzC,IAAA,MAAM,YAAA,GAAe,WAAA;AAAA,MACnB,CAAC,MAAA,KAAwB;AACvB,QAAA,IAAI,YAAA,IAAgB,CAAC,YAAA,CAAa,MAAM,CAAA,EAAG;AAC3C,QAAA,OAAA,EAAQ;AAAA,MACV,CAAA;AAAA,MACA,CAAC,cAAc,OAAO;AAAA,KACxB;AAEA,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,aAAa,OAAA,EAAS;AACxB,QAAA,YAAA,CAAa,aAAa,OAAO,CAAA;AACjC,QAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AAAA,MACzB;AACA,MAAA,IAAI,cAAc,OAAA,EAAS;AACzB,QAAA,YAAA,CAAa,cAAc,OAAO,CAAA;AAClC,QAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AAAA,MAC1B;AAEA,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,WAAA,CAAY,IAAI,CAAA;AAChB,QAAA,qBAAA,CAAsB,MAAM;AAC1B,UAAA,qBAAA,CAAsB,MAAM;AAC1B,YAAA,UAAA,CAAW,IAAI,CAAA;AAAA,UACjB,CAAC,CAAA;AAAA,QACH,CAAC,CAAA;AACD,QAAA,IAAI,cAAA,EAAgB;AAClB,UAAA,mBAAA,CAAoB,OAAA,GAAU,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,QAAA;AAClD,UAAA,QAAA,CAAS,IAAA,CAAK,MAAM,QAAA,GAAW,QAAA;AAAA,QACjC;AAEA,QAAA,aAAA,CAAc,OAAA,GAAU,WAAW,MAAM;AACvC,UAAA,cAAA,CAAe,OAAA,IAAU;AAAA,QAC3B,GAAG,GAAG,CAAA;AAAA,MACR,CAAA,MAAO;AACL,QAAA,UAAA,CAAW,KAAK,CAAA;AAChB,QAAA,YAAA,CAAa,OAAA,GAAU,WAAW,MAAM;AACtC,UAAA,WAAA,CAAY,KAAK,CAAA;AACjB,UAAA,eAAA,CAAgB,OAAA,IAAU;AAAA,QAC5B,GAAG,GAAG,CAAA;AACN,QAAA,IAAI,cAAA,EAAgB;AAClB,UAAA,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,QAAA,GAAW,mBAAA,CAAoB,OAAA;AAAA,QACrD;AAAA,MACF;AAAA,IACF,CAAA,EAAG,CAAC,cAAA,EAAgB,cAAc,CAAC,CAAA;AAEnC,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,OAAO,MAAM;AACX,QAAA,IAAI,YAAA,CAAa,OAAA,EAAS,YAAA,CAAa,YAAA,CAAa,OAAO,CAAA;AAC3D,QAAA,IAAI,aAAA,CAAc,OAAA,EAAS,YAAA,CAAa,aAAA,CAAc,OAAO,CAAA;AAC7D,QAAA,IAAI,cAAA,EAAgB;AAClB,UAAA,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,QAAA,GAAW,mBAAA,CAAoB,OAAA;AAAA,QACrD;AAAA,MACF,CAAA;AAAA,IACF,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAGnB,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,CAAC,cAAA,EAAgB;AACrB,MAAA,MAAM,KAAK,EAAE,eAAA;AACb,MAAA,YAAA,CAAa,OAAA,GAAU,EAAA;AACvB,MAAA,WAAA,CAAY,IAAA,CAAK,EAAE,EAAA,EAAI,CAAA;AACvB,MAAA,YAAA,EAAa;AACb,MAAA,MAAM,OAAO,MAAM;AACjB,QAAA,MAAM,MAAM,WAAA,CAAY,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AACpD,QAAA,MAAM,QAAQ,WAAA,CAAY,MAAA;AAC1B,QAAA,aAAA,CAAc,KAAK,CAAA;AACnB,QAAA,gBAAA,CAAiB,GAAA,KAAQ,EAAA,GAAK,CAAA,GAAI,KAAA,GAAQ,IAAI,GAAG,CAAA;AAAA,MACnD,CAAA;AACA,MAAA,IAAA,EAAK;AACL,MAAA,eAAA,CAAgB,IAAI,IAAI,CAAA;AACxB,MAAA,OAAO,MAAM;AACX,QAAA,eAAA,CAAgB,OAAO,IAAI,CAAA;AAC3B,QAAA,MAAM,MAAM,WAAA,CAAY,SAAA,CAAU,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,EAAE,CAAA;AACpD,QAAA,IAAI,GAAA,KAAQ,EAAA,EAAI,WAAA,CAAY,MAAA,CAAO,KAAK,CAAC,CAAA;AACzC,QAAA,YAAA,EAAa;AAAA,MACf,CAAA;AAAA,IACF,CAAA,EAAG,CAAC,cAAc,CAAC,CAAA;AAGnB,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,CAAC,aAAA,IAAiB,CAAC,cAAA,IAAkB,CAAC,SAAS,OAAA,EAAS;AAC5D,MAAA,MAAM,QAAQ,QAAA,CAAS,OAAA;AACvB,MAAA,MAAM,QAAA,GAAW,CAAC,CAAA,KAAa;AAG7B,QAAA,IAAI,EAAE,gBAAA,EAAkB;AACxB,QAAA,UAAA,CAAW,MAAM,YAAA,CAAa,QAAQ,CAAA,EAAG,CAAC,CAAA;AAAA,MAC5C,CAAA;AACA,MAAA,KAAA,CAAM,gBAAA,CAAiB,UAAU,QAAQ,CAAA;AACzC,MAAA,OAAO,MAAM,KAAA,CAAM,mBAAA,CAAoB,QAAA,EAAU,QAAQ,CAAA;AAAA,IAC3D,CAAA,EAAG,CAAC,aAAA,EAAe,cAAA,EAAgB,YAAY,CAAC,CAAA;AAGhD,IAAA,MAAM,cAAA,GAAiB,OAAsB,IAAI,CAAA;AACjD,IAAA,MAAM,sBAAA,GAAyB,CAAC,CAAA,KAA0C;AACxE,MAAA,IAAI,CAAC,YAAA,IAAgB,CAAA,CAAE,WAAA,KAAgB,OAAA,EAAS;AAEhD,MAAA,cAAA,CAAe,UAAU,CAAA,CAAE,OAAA;AAAA,IAC7B,CAAA;AACA,IAAA,MAAM,sBAAA,GAAyB,CAAC,CAAA,KAA0C;AACxE,MAAA,IAAI,cAAA,CAAe,YAAY,IAAA,EAAM;AACrC,MAAA,MAAM,KAAK,IAAA,CAAK,GAAA,CAAI,GAAG,CAAA,CAAE,OAAA,GAAU,eAAe,OAAO,CAAA;AACzD,MAAA,SAAA,CAAU,EAAE,CAAA;AAAA,IACd,CAAA;AACA,IAAA,MAAM,uBAAuB,MAAM;AACjC,MAAA,IAAI,cAAA,CAAe,YAAY,IAAA,EAAM;AACrC,MAAA,IAAI,MAAA,GAAS,GAAA,EAAK,YAAA,CAAa,OAAO,CAAA;AACtC,MAAA,SAAA,CAAU,CAAC,CAAA;AACX,MAAA,cAAA,CAAe,OAAA,GAAU,IAAA;AAAA,IAC3B,CAAA;AAGA,IAAA,MAAM,oBAAA,GAAuB,OAA2B,IAAI,CAAA;AAC5D,IAAA,SAAA,CAAU,MAAM;AACd,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,oBAAA,CAAqB,UAAU,QAAA,CAAS,aAAA;AACxC,QAAA,IAAI,SAAS,OAAA,EAAS;AACpB,UAAA,QAAA,CAAS,SAAS,OAAO,CAAA;AAEzB,UAAA,qBAAA,CAAsB,MAAM;AAC1B,YAAA,IAAI,iBAAiB,OAAA,EAAS;AAC5B,cAAA,eAAA,CAAgB,QAAQ,KAAA,EAAM;AAAA,YAChC;AAAA,UACF,CAAC,CAAA;AAAA,QACH;AAAA,MACF,CAAA,MAAO;AACL,QAAA,UAAA,EAAW;AAEX,QAAA,MAAM,MAAA,GAAS,aAAA,EAAe,OAAA,IAAW,oBAAA,CAAqB,OAAA;AAC9D,QAAA,IAAI,MAAA,IAAU,OAAO,MAAA,CAAO,KAAA,KAAU,UAAA,EAAY;AAChD,UAAA,MAAA,CAAO,KAAA,EAAM;AAAA,QACf;AAAA,MACF;AAAA,IACF,GAAG,CAAC,cAAA,EAAgB,UAAU,UAAA,EAAY,eAAA,EAAiB,aAAa,CAAC,CAAA;AAEzE,IAAA,MAAM,SAAA,GAAY,WAAA;AAAA,MAChB,CAAC,CAAA,KAA2B;AAC1B,QAAA,IAAI,UAAA,IAAc,CAAA,CAAE,GAAA,KAAQ,QAAA,EAAU;AACpC,UAAA,YAAA,CAAa,KAAK,CAAA;AAClB,UAAA;AAAA,QACF;AACA,QAAA,aAAA,CAAc,EAAE,WAAW,CAAA;AAAA,MAC7B,CAAA;AAAA,MACA,CAAC,UAAA,EAAY,YAAA,EAAc,aAAa;AAAA,KAC1C;AAEA,IAAA,MAAM,kBAAA,GAAqB,WAAA;AAAA,MACzB,CAAC,CAAA,KAAwC;AACvC,QAAA,IAAI,mBAAA,IAAuB,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,aAAA,EAAe;AACvD,UAAA,YAAA,CAAa,SAAS,CAAA;AAAA,QACxB;AAAA,MACF,CAAA;AAAA,MACA,CAAC,qBAAqB,YAAY;AAAA,KACpC;AAEA,IAAA,MAAM,WAAA,GAAc,WAAA;AAAA,MAClB,CAAC,EAAA,KAA8B;AAC7B,QAAA,QAAA,CAAS,OAAA,GAAU,EAAA;AACnB,QAAA,IAAI,OAAO,GAAA,KAAQ,UAAA,EAAY,GAAA,CAAI,EAAE,CAAA;AAAA,aAAA,IAC5B,GAAA,EAAM,GAAA,CAAsD,OAAA,GAAU,EAAA;AAC/E,QAAA,IAAI,EAAA,IAAM,cAAA,EAAgB,QAAA,CAAS,EAAE,CAAA;AAAA,MACvC,CAAA;AAAA,MACA,CAAC,GAAA,EAAK,cAAA,EAAgB,QAAQ;AAAA,KAChC;AAEA,IAAA,IAAI,CAAC,OAAA,IAAW,CAAC,QAAA,EAAU,OAAO,IAAA;AAElC,IAAA,MAAM,SAAA,GAAY,UAAU,MAAA,IAAa,eAAA;AACzC,IAAA,MAAM,YAAA,GAAe,aAAa,QAAA,CAAS,IAAA;AAE3C,IAAA,MAAM,YAAA,GAAoC;AAAA,MACxC,GAAI,YAAA,GAAgB,EAAE,mBAAA,EAAqB,YAAA,KAAyC,EAAC;AAAA;AAAA,MAErF,MAAA,EAAQ,MAAO,UAAA,GAAa;AAAA,KAC9B;AAEA,IAAA,MAAM,UAAA,GAAkC;AAAA;AAAA,MAEtC,GAAI,gBAAgB,CAAA,GAChB;AAAA,QACE,SAAA,EAAW,cAAc,CAAC,aAAA,GAAgB,CAAC,CAAA,UAAA,EAAa,CAAA,GAAI,gBAAgB,IAAI,CAAA,CAAA,CAAA;AAAA,QAChF,SAAS,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,CAAA,GAAI,gBAAgB,IAAI;AAAA,UAEjD,EAAC;AAAA,MACL,GAAI,MAAA,GAAS,CAAA,GAAI,EAAE,SAAA,EAAW,CAAA,WAAA,EAAc,MAAM,CAAA,GAAA,CAAA,EAAO,UAAA,EAAY,MAAA,EAAO,GAAI;AAAC,KACnF;AAEA,IAAA,OAAO,YAAA;AAAA,sBACL,GAAA;AAAA,QAAC,KAAA;AAAA,QAAA;AAAA,UACC,SAAA,EAAW;AAAA,YACT,cAAA;AAAA,YACA,UAAU,uBAAA,GAA0B;AAAA,WACtC,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,UAC1B,cAAA,EAAc,OAAA;AAAA,UACd,WAAA,EAAW,IAAA;AAAA,UACX,iBAAA,EAAiB,UAAA;AAAA,UACjB,uBAAqB,aAAA,IAAiB,MAAA;AAAA,UACtC,YAAA,EAAY,UAAU,MAAA,GAAS,QAAA;AAAA,UAC/B,KAAA,EAAO,YAAA;AAAA,UACP,OAAA,EAAS,kBAAA;AAAA,UACT,SAAA;AAAA,UAEA,QAAA,kBAAA,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,GAAA,EAAK,WAAA;AAAA,cACL,IAAA,EAAK,QAAA;AAAA,cACL,YAAA,EAAW,MAAA;AAAA,cACX,iBAAA,EAAiB,QAAQ,OAAA,GAAU,MAAA;AAAA,cACnC,kBAAA,EAAkB,cAAc,MAAA,GAAS,MAAA;AAAA,cACzC,QAAA,EAAU,EAAA;AAAA,cACV,SAAA,EAAW;AAAA,gBACT,YAAA;AAAA,gBACA,SAAA;AAAA,gBACA,UAAU,qBAAA,GAAwB;AAAA,eACpC,CAAE,MAAA,CAAO,OAAO,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,cAC1B,WAAA,EAAW,IAAA;AAAA,cACX,cAAA,EAAc,OAAA;AAAA,cACd,cAAA,EAAc,OAAA;AAAA,cACd,iBAAA,EAAiB,aAAa,MAAA,GAAS,MAAA;AAAA,cACvC,iBAAA,EAAiB,UAAA;AAAA,cACjB,YAAA,EAAY,UAAU,MAAA,GAAS,QAAA;AAAA,cAC/B,cAAA,EAAc,MAAA,GAAS,CAAA,GAAI,MAAA,GAAS,MAAA;AAAA,cACpC,KAAA,EAAO,UAAA;AAAA,cACP,aAAA,EAAe,eAAe,sBAAA,GAAyB,MAAA;AAAA,cACvD,aAAA,EAAe,eAAe,sBAAA,GAAyB,MAAA;AAAA,cACvD,WAAA,EAAa,eAAe,oBAAA,GAAuB,MAAA;AAAA,cACnD,eAAA,EAAiB,eAAe,oBAAA,GAAuB,MAAA;AAAA,cAEtD,QAAA,EAAA;AAAA,gBAAA,SAAA,oBACC,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EACZ,QAAA,EAAA;AAAA,kBAAA,KAAA,mBACC,GAAA,CAAC,QAAG,EAAA,EAAI,OAAA,EAAS,WAAU,YAAA,EAAc,QAAA,EAAA,KAAA,EAAM,CAAA,mBAE/C,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA;AAAA,kBAEP,eAAA,oBACC,GAAA;AAAA,oBAAC,QAAA;AAAA,oBAAA;AAAA,sBACC,IAAA,EAAK,QAAA;AAAA,sBACL,SAAA,EAAU,YAAA;AAAA,sBACV,YAAA,EAAW,OAAA;AAAA,sBACX,OAAA,EAAS,MAAM,YAAA,CAAa,cAAc,CAAA;AAAA,sBAE1C,QAAA,kBAAA,GAAA,CAAC,SAAI,aAAA,EAAY,MAAA,EAAO,OAAM,IAAA,EAAK,MAAA,EAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,QACtE,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,GAAE,oBAAA,EAAqB,MAAA,EAAO,gBAAe,WAAA,EAAY,MAAA,EAAO,aAAA,EAAc,OAAA,EAAQ,CAAA,EAC9F;AAAA;AAAA;AACF,iBAAA,EAEJ,CAAA;AAAA,gBAED,WAAA,KAAgB,0BACf,GAAA,CAAC,GAAA,EAAA,EAAE,IAAI,MAAA,EAAQ,SAAA,EAAU,oBAAoB,QAAA,EAAA,WAAA,EAAY,CAAA;AAAA,gCAE3D,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EAAa,QAAA,EAAS,CAAA;AAAA,gBACpC,MAAA,oBAAU,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAe,QAAA,EAAA,MAAA,EAAO;AAAA;AAAA;AAAA;AAClD;AAAA,OACF;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF;AC/WO,SAAS,OAAA,CAAQ,IAAA,GAAuB,EAAC,EAAqB;AACnE,EAAA,OAAO,IAAI,OAAA,CAAQ,CAAC,OAAA,KAAY;AAC9B,IAAA,IAAI,OAAO,MAAA,KAAW,WAAA,IAAe,OAAO,aAAa,WAAA,EAAa;AACpE,MAAA,OAAA,CAAQ,KAAK,CAAA;AACb,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACzC,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,IAAI,CAAA;AAC9B,IAAA,MAAM,IAAA,GAAO,WAAW,IAAI,CAAA;AAE5B,IAAA,MAAM,UAAU,MAAM;AAEpB,MAAA,UAAA,CAAW,MAAM;AACf,QAAA,IAAI;AAAE,UAAA,IAAA,CAAK,OAAA,EAAQ;AAAA,QAAG,CAAA,CAAA,MAAQ;AAAA,QAAe;AAC7C,QAAA,IAAI,IAAA,CAAK,UAAA,EAAY,IAAA,CAAK,UAAA,CAAW,YAAY,IAAI,CAAA;AAAA,MACvD,GAAG,GAAG,CAAA;AAAA,IACR,CAAA;AAEA,IAAA,SAAS,YAAA,GAAe;AACtB,MAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIC,SAAS,IAAI,CAAA;AACrC,MAAA,MAAM,MAAA,GAAS,CAAC,KAAA,KAAmB;AACjC,QAAA,OAAA,CAAQ,KAAK,CAAA;AACb,QAAA,OAAA,EAAQ;AACR,QAAA,OAAA,CAAQ,KAAK,CAAA;AAAA,MACf,CAAA;AACA,MAAA,uBACEC,GAAAA;AAAA,QAAC,WAAA;AAAA,QAAA;AAAA,UACC,IAAA;AAAA,UACA,OAAA,EAAS,MAAM,MAAA,CAAO,KAAK,CAAA;AAAA,UAC3B,KAAA,EAAO,KAAK,KAAA,IAAS,eAAA;AAAA,UACrB,IAAA,EAAK,IAAA;AAAA,UACL,MAAA,kBACEC,IAAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAO,EAAE,OAAA,EAAS,MAAA,EAAQ,GAAA,EAAK,CAAA,EAAG,cAAA,EAAgB,UAAA,EAAW,EAChE,QAAA,EAAA;AAAA,4BAAAD,GAAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,SAAA,EAAU,2BAAA;AAAA,gBACV,OAAA,EAAS,MAAM,MAAA,CAAO,KAAK,CAAA;AAAA,gBAE1B,eAAK,WAAA,IAAe;AAAA;AAAA,aACvB;AAAA,4BACAA,GAAAA;AAAA,cAAC,QAAA;AAAA,cAAA;AAAA,gBACC,IAAA,EAAK,QAAA;AAAA,gBACL,SAAA,EAAW,CAAA,0BAAA,EAA6B,IAAA,CAAK,MAAA,GAAS,sBAAsB,EAAE,CAAA,CAAA;AAAA,gBAC9E,OAAA,EAAS,MAAM,MAAA,CAAO,IAAI,CAAA;AAAA,gBAC1B,SAAA,EAAS,IAAA;AAAA,gBAER,eAAK,YAAA,IAAgB;AAAA;AAAA;AACxB,WAAA,EACF,CAAA;AAAA,UAGD,eAAK,IAAA,IAAQ;AAAA;AAAA,OAChB;AAAA,IAEJ;AAEA,IAAA,IAAA,CAAK,MAAA,iBAAOA,GAAAA,CAAC,YAAA,EAAA,EAAa,CAAE,CAAA;AAAA,EAC9B,CAAC,CAAA;AACH;AAGO,IAAM,KAAA,GAAQ,EAAE,OAAA","file":"styled.js","sourcesContent":["import {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useFocusTrap } from \"../useFocusTrap\";\n\nexport type ModalSize = \"sm\" | \"md\" | \"lg\" | \"full\";\nexport type ModalVariant = \"dialog\" | \"drawer-left\" | \"drawer-right\" | \"drawer-bottom\";\nexport type CloseReason = \"esc\" | \"overlay\" | \"close-button\" | \"programmatic\" | \"swipe\" | \"submit\";\nexport type ModalTransition = \"fade\" | \"zoom\" | \"slide-up\" | \"slide-down\";\n\n// ---- Stacking registry (depth-aware z-index + scale-behind effect) -------\nconst _openModals: { id: number }[] = [];\nlet _modalIdCounter = 0;\nconst _stackListeners = new Set<() => void>();\nfunction _notifyStack() { _stackListeners.forEach((fn) => fn()); }\n\nexport interface ModalStyledProps {\n /** Open state. `open` is the canonical name; `isOpen` continues to work. */\n open?: boolean;\n isOpen?: boolean;\n onClose: () => void;\n /** Modal heading — omit to hide the header entirely */\n title?: ReactNode;\n /** Optional accessible description. Renders below the title and links via aria-describedby. */\n description?: ReactNode;\n children: ReactNode;\n /** Footer content — omit to hide footer */\n footer?: ReactNode;\n size?: ModalSize;\n variant?: ModalVariant;\n closeOnOverlayClick?: boolean;\n closeOnEsc?: boolean;\n showCloseButton?: boolean;\n /** Backdrop blur intensity. Default: \"md\" */\n blur?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Custom overlay color e.g. \"rgba(0,0,0,0.6)\" */\n overlayColor?: string;\n /** Padding inside the body. Default: \"md\" */\n padding?: \"none\" | \"sm\" | \"md\" | \"lg\";\n /** Max height of scrollable body. Default: auto */\n scrollable?: boolean;\n className?: string;\n /** Element to focus when the modal opens. Defaults to the first focusable child inside the panel. */\n initialFocusRef?: RefObject<HTMLElement | null>;\n /** Element to focus when the modal closes. Defaults to whatever was focused before opening. */\n finalFocusRef?: RefObject<HTMLElement | null>;\n /** Fires after the open transition completes. */\n onAfterOpen?: () => void;\n /** Fires after the close transition completes (and unmount). */\n onAfterClose?: () => void;\n /** Return false to veto a close attempt. Receives the trigger reason. */\n preventClose?: (reason: CloseReason) => boolean;\n /** Disable body scroll lock while open. Default: true (locked). */\n lockBodyScroll?: boolean;\n /** Override the portal container. Default: document.body. */\n container?: HTMLElement | null;\n /** Transition variant for the panel (only `dialog`; drawers always slide). Default: \"fade\" */\n transition?: ModalTransition;\n /** Allow swipe-down-to-dismiss on touch devices. Default: false (true for `drawer-bottom`). */\n swipeToDismiss?: boolean;\n /** When the modal contains a single `<form>` and it submits successfully, auto-close. Default: false. */\n closeOnSubmit?: boolean;\n}\n\nexport const ModalStyled = forwardRef<HTMLDivElement, ModalStyledProps>(\n function ModalStyled(\n {\n open,\n isOpen,\n onClose,\n title,\n description,\n children,\n footer,\n size = \"md\",\n variant = \"dialog\",\n closeOnOverlayClick = true,\n closeOnEsc = true,\n showCloseButton = true,\n blur = \"md\",\n overlayColor,\n padding = \"md\",\n scrollable = true,\n className,\n initialFocusRef,\n finalFocusRef,\n onAfterOpen,\n onAfterClose,\n preventClose,\n lockBodyScroll = true,\n container,\n transition = \"fade\",\n swipeToDismiss,\n closeOnSubmit = false,\n },\n ref,\n ) {\n const swipeEnabled = swipeToDismiss ?? variant === \"drawer-bottom\";\n const myStackIdRef = useRef<number>(-1);\n const [stackDepth, setStackDepth] = useState(0);\n const [stackPosition, setStackPosition] = useState(0); // 0 = top\n const [swipeY, setSwipeY] = useState(0);\n const titleId = useId();\n const descId = useId();\n const isActuallyOpen = open ?? isOpen ?? false;\n const [mounted, setMounted] = useState(false);\n const [rendered, setRendered] = useState(false);\n const [visible, setVisible] = useState(false);\n const panelRef = useRef<HTMLDivElement | null>(null);\n const originalOverflowRef = useRef(\"\");\n const exitTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const enterTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const { activate, deactivate, handleKeyDown } = useFocusTrap();\n\n const onAfterOpenRef = useRef(onAfterOpen);\n onAfterOpenRef.current = onAfterOpen;\n const onAfterCloseRef = useRef(onAfterClose);\n onAfterCloseRef.current = onAfterClose;\n\n useEffect(() => { setMounted(true); }, []);\n\n const requestClose = useCallback(\n (reason: CloseReason) => {\n if (preventClose && !preventClose(reason)) return;\n onClose();\n },\n [preventClose, onClose],\n );\n\n useEffect(() => {\n if (exitTimerRef.current) {\n clearTimeout(exitTimerRef.current);\n exitTimerRef.current = null;\n }\n if (enterTimerRef.current) {\n clearTimeout(enterTimerRef.current);\n enterTimerRef.current = null;\n }\n\n if (isActuallyOpen) {\n setRendered(true);\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setVisible(true);\n });\n });\n if (lockBodyScroll) {\n originalOverflowRef.current = document.body.style.overflow;\n document.body.style.overflow = \"hidden\";\n }\n // Fire onAfterOpen once the enter transition has had time to settle.\n enterTimerRef.current = setTimeout(() => {\n onAfterOpenRef.current?.();\n }, 320);\n } else {\n setVisible(false);\n exitTimerRef.current = setTimeout(() => {\n setRendered(false);\n onAfterCloseRef.current?.();\n }, 300);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n }\n }, [isActuallyOpen, lockBodyScroll]);\n\n useEffect(() => {\n return () => {\n if (exitTimerRef.current) clearTimeout(exitTimerRef.current);\n if (enterTimerRef.current) clearTimeout(enterTimerRef.current);\n if (lockBodyScroll) {\n document.body.style.overflow = originalOverflowRef.current;\n }\n };\n }, [lockBodyScroll]);\n\n // ---- Stacking: register / deregister this modal ---------------------\n useEffect(() => {\n if (!isActuallyOpen) return;\n const id = ++_modalIdCounter;\n myStackIdRef.current = id;\n _openModals.push({ id });\n _notifyStack();\n const sync = () => {\n const idx = _openModals.findIndex((m) => m.id === id);\n const total = _openModals.length;\n setStackDepth(total);\n setStackPosition(idx === -1 ? 0 : total - 1 - idx);\n };\n sync();\n _stackListeners.add(sync);\n return () => {\n _stackListeners.delete(sync);\n const idx = _openModals.findIndex((m) => m.id === id);\n if (idx !== -1) _openModals.splice(idx, 1);\n _notifyStack();\n };\n }, [isActuallyOpen]);\n\n // ---- closeOnSubmit: auto-close on a contained <form> submit ---------\n useEffect(() => {\n if (!closeOnSubmit || !isActuallyOpen || !panelRef.current) return;\n const panel = panelRef.current;\n const onSubmit = (e: Event) => {\n // Defer the close so the consumer's onSubmit handler runs first\n // and can call e.preventDefault() if it wants to abort the close.\n if (e.defaultPrevented) return;\n setTimeout(() => requestClose(\"submit\"), 0);\n };\n panel.addEventListener(\"submit\", onSubmit);\n return () => panel.removeEventListener(\"submit\", onSubmit);\n }, [closeOnSubmit, isActuallyOpen, requestClose]);\n\n // ---- Swipe-to-dismiss (touch only) -----------------------------------\n const swipeStartYRef = useRef<number | null>(null);\n const handleSwipePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {\n if (!swipeEnabled || e.pointerType !== \"touch\") return;\n // Only initiate from the panel header / the panel itself, not buttons.\n swipeStartYRef.current = e.clientY;\n };\n const handleSwipePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {\n if (swipeStartYRef.current === null) return;\n const dy = Math.max(0, e.clientY - swipeStartYRef.current);\n setSwipeY(dy);\n };\n const handleSwipePointerUp = () => {\n if (swipeStartYRef.current === null) return;\n if (swipeY > 120) requestClose(\"swipe\");\n setSwipeY(0);\n swipeStartYRef.current = null;\n };\n\n // Focus management\n const previouslyFocusedRef = useRef<HTMLElement | null>(null);\n useEffect(() => {\n if (isActuallyOpen) {\n previouslyFocusedRef.current = document.activeElement as HTMLElement | null;\n if (panelRef.current) {\n activate(panelRef.current);\n // Honor initialFocusRef after the panel is mounted+visible\n requestAnimationFrame(() => {\n if (initialFocusRef?.current) {\n initialFocusRef.current.focus();\n }\n });\n }\n } else {\n deactivate();\n // Honor finalFocusRef on close, otherwise restore previous focus\n const target = finalFocusRef?.current ?? previouslyFocusedRef.current;\n if (target && typeof target.focus === \"function\") {\n target.focus();\n }\n }\n }, [isActuallyOpen, activate, deactivate, initialFocusRef, finalFocusRef]);\n\n const onKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n if (closeOnEsc && e.key === \"Escape\") {\n requestClose(\"esc\");\n return;\n }\n handleKeyDown(e.nativeEvent);\n },\n [closeOnEsc, requestClose, handleKeyDown],\n );\n\n const handleOverlayClick = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (closeOnOverlayClick && e.target === e.currentTarget) {\n requestClose(\"overlay\");\n }\n },\n [closeOnOverlayClick, requestClose],\n );\n\n const setPanelRef = useCallback(\n (el: HTMLDivElement | null) => {\n panelRef.current = el;\n if (typeof ref === \"function\") ref(el);\n else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = el;\n if (el && isActuallyOpen) activate(el);\n },\n [ref, isActuallyOpen, activate],\n );\n\n if (!mounted || !rendered) return null;\n\n const hasHeader = title !== undefined || showCloseButton;\n const portalTarget = container ?? document.body;\n\n const overlayStyle: React.CSSProperties = {\n ...(overlayColor ? ({ \"--rmod-overlay-bg\": overlayColor } as React.CSSProperties) : {}),\n // Higher stack depth → lower modals scale down + fade slightly behind the topmost.\n zIndex: 1000 + stackDepth * 10,\n };\n\n const panelStyle: React.CSSProperties = {\n // Stack-position effects: only applied when this isn't the topmost modal.\n ...(stackPosition > 0\n ? {\n transform: `translateY(${-stackPosition * 8}px) scale(${1 - stackPosition * 0.03})`,\n opacity: Math.max(0.6, 1 - stackPosition * 0.18),\n }\n : {}),\n ...(swipeY > 0 ? { transform: `translateY(${swipeY}px)`, transition: \"none\" } : {}),\n };\n\n return createPortal(\n <div\n className={[\n \"rmod-overlay\",\n visible ? \"rmod-overlay--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-variant={variant}\n data-blur={blur}\n data-transition={transition}\n data-stack-position={stackPosition || undefined}\n data-state={visible ? \"open\" : \"closed\"}\n style={overlayStyle}\n onClick={handleOverlayClick}\n onKeyDown={onKeyDown}\n >\n <div\n ref={setPanelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n aria-describedby={description ? descId : undefined}\n tabIndex={-1}\n className={[\n \"rmod-panel\",\n className,\n visible ? \"rmod-panel--visible\" : \"\",\n ].filter(Boolean).join(\" \")}\n data-size={size}\n data-variant={variant}\n data-padding={padding}\n data-scrollable={scrollable ? \"true\" : undefined}\n data-transition={transition}\n data-state={visible ? \"open\" : \"closed\"}\n data-swiping={swipeY > 0 ? \"true\" : undefined}\n style={panelStyle}\n onPointerDown={swipeEnabled ? handleSwipePointerDown : undefined}\n onPointerMove={swipeEnabled ? handleSwipePointerMove : undefined}\n onPointerUp={swipeEnabled ? handleSwipePointerUp : undefined}\n onPointerCancel={swipeEnabled ? handleSwipePointerUp : undefined}\n >\n {hasHeader && (\n <div className=\"rmod-header\">\n {title ? (\n <h2 id={titleId} className=\"rmod-title\">{title}</h2>\n ) : (\n <span />\n )}\n {showCloseButton && (\n <button\n type=\"button\"\n className=\"rmod-close\"\n aria-label=\"Close\"\n onClick={() => requestClose(\"close-button\")}\n >\n <svg aria-hidden=\"true\" width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" fill=\"none\">\n <path d=\"M12 4L4 12M4 4l8 8\" stroke=\"currentColor\" strokeWidth=\"1.75\" strokeLinecap=\"round\" />\n </svg>\n </button>\n )}\n </div>\n )}\n {description !== undefined && (\n <p id={descId} className=\"rmod-description\">{description}</p>\n )}\n <div className=\"rmod-body\">{children}</div>\n {footer && <div className=\"rmod-footer\">{footer}</div>}\n </div>\n </div>,\n portalTarget,\n );\n },\n);\n","import { useState, type ReactNode } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { ModalStyled } from \"./ModalStyled\";\n\nexport interface ConfirmOptions {\n title?: ReactNode;\n body?: ReactNode;\n confirmLabel?: ReactNode;\n cancelLabel?: ReactNode;\n /** Style the confirm button as the danger CTA (red). */\n danger?: boolean;\n}\n\n/**\n * Programmatic confirm dialog. Resolves `true` if confirmed, `false` if cancelled\n * or dismissed via overlay/Esc/close button.\n *\n * Self-mounts a `<ModalStyled>` into a temporary div appended to `document.body`\n * and unmounts on close — no provider required.\n */\nexport function confirm(opts: ConfirmOptions = {}): Promise<boolean> {\n return new Promise((resolve) => {\n if (typeof window === \"undefined\" || typeof document === \"undefined\") {\n resolve(false);\n return;\n }\n const host = document.createElement(\"div\");\n document.body.appendChild(host);\n const root = createRoot(host);\n\n const cleanup = () => {\n // Defer unmount so the close transition can run.\n setTimeout(() => {\n try { root.unmount(); } catch { /* ignore */ }\n if (host.parentNode) host.parentNode.removeChild(host);\n }, 320);\n };\n\n function ConfirmShell() {\n const [open, setOpen] = useState(true);\n const finish = (value: boolean) => {\n setOpen(false);\n cleanup();\n resolve(value);\n };\n return (\n <ModalStyled\n open={open}\n onClose={() => finish(false)}\n title={opts.title ?? \"Are you sure?\"}\n size=\"sm\"\n footer={\n <div style={{ display: \"flex\", gap: 8, justifyContent: \"flex-end\" }}>\n <button\n type=\"button\"\n className=\"rmod-btn rmod-btn--cancel\"\n onClick={() => finish(false)}\n >\n {opts.cancelLabel ?? \"Cancel\"}\n </button>\n <button\n type=\"button\"\n className={`rmod-btn rmod-btn--confirm${opts.danger ? \" rmod-btn--danger\" : \"\"}`}\n onClick={() => finish(true)}\n autoFocus\n >\n {opts.confirmLabel ?? \"Confirm\"}\n </button>\n </div>\n }\n >\n {opts.body ?? null}\n </ModalStyled>\n );\n }\n\n root.render(<ConfirmShell />);\n });\n}\n\n/** Module-style export so consumers can do `modal.confirm(...)`. */\nexport const modal = { confirm };\n"]}
|
package/dist/styles.css
CHANGED
|
@@ -245,3 +245,118 @@
|
|
|
245
245
|
@media (prefers-reduced-motion: reduce) {
|
|
246
246
|
.rmod-overlay, .rmod-panel { transition-duration: 0ms !important; }
|
|
247
247
|
}
|
|
248
|
+
|
|
249
|
+
/* ============================================================================
|
|
250
|
+
* 0.2.0 additions: description below title
|
|
251
|
+
* ========================================================================== */
|
|
252
|
+
.rmod-description {
|
|
253
|
+
margin: 0 0 0.6rem;
|
|
254
|
+
padding: 0 1rem;
|
|
255
|
+
color: var(--rmod-fg-muted);
|
|
256
|
+
font-size: 0.875rem;
|
|
257
|
+
line-height: 1.55;
|
|
258
|
+
}
|
|
259
|
+
.rmod-panel[data-padding="none"] .rmod-description { padding: 0; }
|
|
260
|
+
.rmod-panel[data-padding="sm"] .rmod-description { padding: 0 0.75rem; }
|
|
261
|
+
.rmod-panel[data-padding="lg"] .rmod-description { padding: 0 1.5rem; }
|
|
262
|
+
|
|
263
|
+
/* ============================================================================
|
|
264
|
+
* 1.x additions — transition variants · stacked behind · swipe · confirm()
|
|
265
|
+
* ============================================================================ */
|
|
266
|
+
|
|
267
|
+
/* Transition variants on the dialog panel.
|
|
268
|
+
* Drawers (drawer-left/right/bottom) keep their existing slide-from-edge
|
|
269
|
+
* transition; these only apply to data-variant="dialog". */
|
|
270
|
+
.rmod-panel[data-variant="dialog"][data-transition="zoom"] {
|
|
271
|
+
transform: scale(0.94);
|
|
272
|
+
opacity: 0;
|
|
273
|
+
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
|
|
274
|
+
}
|
|
275
|
+
.rmod-panel[data-variant="dialog"][data-transition="zoom"].rmod-panel--visible {
|
|
276
|
+
transform: scale(1);
|
|
277
|
+
opacity: 1;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.rmod-panel[data-variant="dialog"][data-transition="slide-up"] {
|
|
281
|
+
transform: translateY(20px);
|
|
282
|
+
opacity: 0;
|
|
283
|
+
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
|
|
284
|
+
}
|
|
285
|
+
.rmod-panel[data-variant="dialog"][data-transition="slide-up"].rmod-panel--visible {
|
|
286
|
+
transform: translateY(0);
|
|
287
|
+
opacity: 1;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.rmod-panel[data-variant="dialog"][data-transition="slide-down"] {
|
|
291
|
+
transform: translateY(-20px);
|
|
292
|
+
opacity: 0;
|
|
293
|
+
transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1), opacity 200ms ease;
|
|
294
|
+
}
|
|
295
|
+
.rmod-panel[data-variant="dialog"][data-transition="slide-down"].rmod-panel--visible {
|
|
296
|
+
transform: translateY(0);
|
|
297
|
+
opacity: 1;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Stacked-behind effect — applied via inline style from JS but we add a
|
|
301
|
+
* smooth transition here so depth changes animate. */
|
|
302
|
+
.rmod-overlay[data-stack-position] .rmod-panel:not([data-swiping="true"]) {
|
|
303
|
+
transition:
|
|
304
|
+
transform 240ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
305
|
+
opacity 220ms ease;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* Swipe-to-dismiss visual */
|
|
309
|
+
.rmod-panel[data-swiping="true"] {
|
|
310
|
+
transition: none;
|
|
311
|
+
cursor: grabbing;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/* Confirm() utility's footer buttons — minimal, inherit consumer themes
|
|
315
|
+
* via CSS variables. */
|
|
316
|
+
.rmod-btn {
|
|
317
|
+
appearance: none;
|
|
318
|
+
font: inherit;
|
|
319
|
+
padding: 0.5rem 1rem;
|
|
320
|
+
border-radius: 6px;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
font-weight: 500;
|
|
323
|
+
font-size: 0.875rem;
|
|
324
|
+
transition: background 120ms ease, border-color 120ms ease;
|
|
325
|
+
}
|
|
326
|
+
.rmod-btn--cancel {
|
|
327
|
+
background: transparent;
|
|
328
|
+
border: 1px solid var(--rmod-border, rgba(0, 0, 0, 0.12));
|
|
329
|
+
color: var(--rmod-fg, #18181b);
|
|
330
|
+
}
|
|
331
|
+
.rmod-btn--cancel:hover {
|
|
332
|
+
background: var(--rmod-surface-hover, rgba(0, 0, 0, 0.04));
|
|
333
|
+
}
|
|
334
|
+
.rmod-btn--confirm {
|
|
335
|
+
background: var(--rmod-confirm-bg, #6366f1);
|
|
336
|
+
border: 1px solid var(--rmod-confirm-bg, #6366f1);
|
|
337
|
+
color: white;
|
|
338
|
+
}
|
|
339
|
+
.rmod-btn--confirm:hover {
|
|
340
|
+
background: var(--rmod-confirm-bg-hover, #5558e6);
|
|
341
|
+
}
|
|
342
|
+
.rmod-btn--danger {
|
|
343
|
+
background: var(--rmod-danger-bg, #dc2626);
|
|
344
|
+
border-color: var(--rmod-danger-bg, #dc2626);
|
|
345
|
+
}
|
|
346
|
+
.rmod-btn--danger:hover {
|
|
347
|
+
background: var(--rmod-danger-bg-hover, #c01f1f);
|
|
348
|
+
}
|
|
349
|
+
[data-theme="dark"] .rmod-btn--cancel {
|
|
350
|
+
border-color: var(--rmod-border, rgba(255, 255, 255, 0.16));
|
|
351
|
+
color: var(--rmod-fg, #f4f4f5);
|
|
352
|
+
}
|
|
353
|
+
[data-theme="dark"] .rmod-btn--cancel:hover {
|
|
354
|
+
background: var(--rmod-surface-hover, rgba(255, 255, 255, 0.06));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
@media (prefers-reduced-motion: reduce) {
|
|
358
|
+
.rmod-panel[data-variant="dialog"][data-transition] {
|
|
359
|
+
transition: opacity 100ms linear;
|
|
360
|
+
transform: none !important;
|
|
361
|
+
}
|
|
362
|
+
}
|
package/package.json
CHANGED