@mshafiqyajid/react-modal 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/styled.cjs +149 -2
- package/dist/styled.cjs.map +1 -1
- package/dist/styled.d.cts +30 -2
- package/dist/styled.d.ts +30 -2
- package/dist/styled.js +149 -4
- package/dist/styled.js.map +1 -1
- package/dist/styles.css +101 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,3 +101,11 @@ Set `data-theme="dark"` on any ancestor element — no `prefers-color-scheme` qu
|
|
|
101
101
|
## License
|
|
102
102
|
|
|
103
103
|
MIT
|
|
104
|
+
|
|
105
|
+
## What's new in 0.3.0
|
|
106
|
+
|
|
107
|
+
- **Stacked modals** — depth-aware z-index + behind-scale-down effect. Open a second modal and the first scales/translates back automatically.
|
|
108
|
+
- **Transition variants** — `transition: "fade" | "zoom" | "slide-up" | "slide-down"` (default `"fade"`). Drawers keep their slide-from-edge transition.
|
|
109
|
+
- **`confirm()` programmatic utility** — `import { confirm } from "@mshafiqyajid/react-modal/styled"`. Self-mounts a one-off modal with `Cancel` / `Confirm` buttons (with `danger` variant). Returns `Promise<boolean>`.
|
|
110
|
+
- **Swipe-to-dismiss** — `swipeToDismiss?: boolean` (default `true` for `drawer-bottom`). Touch-drag down past 120 px to dismiss.
|
|
111
|
+
- **`closeOnSubmit?: boolean`** — when the modal contains a `<form>` and it submits successfully, auto-close (skipped if the consumer's onSubmit calls `e.preventDefault()`).
|
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,6 +65,12 @@ 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({
|
|
69
76
|
open,
|
|
@@ -89,8 +96,16 @@ var ModalStyled = react.forwardRef(
|
|
|
89
96
|
onAfterClose,
|
|
90
97
|
preventClose,
|
|
91
98
|
lockBodyScroll = true,
|
|
92
|
-
container
|
|
99
|
+
container,
|
|
100
|
+
transition = "fade",
|
|
101
|
+
swipeToDismiss,
|
|
102
|
+
closeOnSubmit = false
|
|
93
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);
|
|
94
109
|
const titleId = react.useId();
|
|
95
110
|
const descId = react.useId();
|
|
96
111
|
const isActuallyOpen = open ?? isOpen ?? false;
|
|
@@ -159,6 +174,53 @@ var ModalStyled = react.forwardRef(
|
|
|
159
174
|
}
|
|
160
175
|
};
|
|
161
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
|
+
};
|
|
162
224
|
const previouslyFocusedRef = react.useRef(null);
|
|
163
225
|
react.useEffect(() => {
|
|
164
226
|
if (isActuallyOpen) {
|
|
@@ -209,6 +271,19 @@ var ModalStyled = react.forwardRef(
|
|
|
209
271
|
if (!mounted || !rendered) return null;
|
|
210
272
|
const hasHeader = title !== void 0 || showCloseButton;
|
|
211
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
|
+
};
|
|
212
287
|
return reactDom.createPortal(
|
|
213
288
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
214
289
|
"div",
|
|
@@ -219,8 +294,10 @@ var ModalStyled = react.forwardRef(
|
|
|
219
294
|
].filter(Boolean).join(" "),
|
|
220
295
|
"data-variant": variant,
|
|
221
296
|
"data-blur": blur,
|
|
297
|
+
"data-transition": transition,
|
|
298
|
+
"data-stack-position": stackPosition || void 0,
|
|
222
299
|
"data-state": visible ? "open" : "closed",
|
|
223
|
-
style:
|
|
300
|
+
style: overlayStyle,
|
|
224
301
|
onClick: handleOverlayClick,
|
|
225
302
|
onKeyDown,
|
|
226
303
|
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -241,7 +318,14 @@ var ModalStyled = react.forwardRef(
|
|
|
241
318
|
"data-variant": variant,
|
|
242
319
|
"data-padding": padding,
|
|
243
320
|
"data-scrollable": scrollable ? "true" : void 0,
|
|
321
|
+
"data-transition": transition,
|
|
244
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,
|
|
245
329
|
children: [
|
|
246
330
|
hasHeader && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rmod-header", children: [
|
|
247
331
|
title ? /* @__PURE__ */ jsxRuntime.jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsxRuntime.jsx("span", {}),
|
|
@@ -268,7 +352,70 @@ var ModalStyled = react.forwardRef(
|
|
|
268
352
|
);
|
|
269
353
|
}
|
|
270
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 };
|
|
271
416
|
|
|
272
417
|
exports.ModalStyled = ModalStyled;
|
|
418
|
+
exports.confirm = confirm;
|
|
419
|
+
exports.modal = modal;
|
|
273
420
|
//# sourceMappingURL=styled.cjs.map
|
|
274
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":";;;;;;;AAEA,IAAM,mBAAA,GAAsB;AAAA,EAC1B,SAAA;AAAA,EACA,wBAAA;AAAA,EACA,uBAAA;AAAA,EACA,wBAAA;AAAA,EACA,0BAAA;AAAA,EACA,iCAAA;AAAA,EACA;AACF,CAAA,CAAE,KAAK,IAAI,CAAA;AAEX,SAAS,qBAAqB,SAAA,EAAuC;AACnE,EAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,gBAAA,CAA8B,mBAAmB,CAAC,CAAA,CAAE,MAAA;AAAA,IAC9E,CAAC,EAAA,KAAO,CAAC,EAAA,CAAG,OAAA,CAAQ,SAAS,CAAA,IAAK,gBAAA,CAAiB,EAAE,CAAA,CAAE,OAAA,KAAY;AAAA,GACrE;AACF;AAQO,SAAS,YAAA,GAAmC;AACjD,EAAA,MAAM,YAAA,GAAeA,aAA2B,IAAI,CAAA;AACpD,EAAA,MAAM,gBAAA,GAAmBA,aAA2B,IAAI,CAAA;AAExD,EAAA,MAAM,QAAA,GAAWC,iBAAA,CAAY,CAAC,SAAA,KAA2B;AACvD,IAAA,YAAA,CAAa,OAAA,GAAU,SAAA;AACvB,IAAA,gBAAA,CAAiB,UAAU,QAAA,CAAS,aAAA;AAEpC,IAAA,MAAM,SAAA,GAAY,qBAAqB,SAAS,CAAA;AAChD,IAAA,MAAM,KAAA,GAAQ,UAAU,CAAC,CAAA;AACzB,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,KAAA,CAAM,KAAA,EAAM;AAAA,IACd,CAAA,MAAO;AACL,MAAA,SAAA,CAAU,KAAA,EAAM;AAAA,IAClB;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,UAAA,GAAaA,kBAAY,MAAM;AACnC,IAAA,YAAA,CAAa,OAAA,GAAU,IAAA;AACvB,IAAA,MAAM,OAAO,gBAAA,CAAiB,OAAA;AAC9B,IAAA,IAAI,IAAA,IAAQ,OAAO,IAAA,CAAK,KAAA,KAAU,UAAA,EAAY;AAC5C,MAAA,IAAA,CAAK,KAAA,EAAM;AAAA,IACb;AACA,IAAA,gBAAA,CAAiB,OAAA,GAAU,IAAA;AAAA,EAC7B,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgBA,iBAAA,CAAY,CAAC,CAAA,KAAqB;AACtD,IAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,KAAA,IAAS,CAAC,aAAa,OAAA,EAAS;AAE9C,IAAA,MAAM,SAAA,GAAY,oBAAA,CAAqB,YAAA,CAAa,OAAO,CAAA;AAC3D,IAAA,IAAI,SAAA,CAAU,WAAW,CAAA,EAAG;AAC1B,MAAA,CAAA,CAAE,cAAA,EAAe;AACjB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,KAAA,GAAQ,UAAU,CAAC,CAAA;AACzB,IAAA,MAAM,IAAA,GAAO,SAAA,CAAU,SAAA,CAAU,MAAA,GAAS,CAAC,CAAA;AAE3C,IAAA,IAAI,EAAE,QAAA,EAAU;AACd,MAAA,IAAI,QAAA,CAAS,kBAAkB,KAAA,EAAO;AACpC,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,IAAA,CAAK,KAAA,EAAM;AAAA,MACb;AAAA,IACF,CAAA,MAAO;AACL,MAAA,IAAI,QAAA,CAAS,kBAAkB,IAAA,EAAM;AACnC,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,KAAA,CAAM,KAAA,EAAM;AAAA,MACd;AAAA,IACF;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,OAAO,EAAE,QAAA,EAAU,UAAA,EAAY,aAAA,EAAc;AAC/C;ACjBO,IAAM,WAAA,GAAcC,gBAAA;AAAA,EACzB,SAASC,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;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,UAAUC,WAAA,EAAM;AACtB,IAAA,MAAM,SAASA,WAAA,EAAM;AACrB,IAAA,MAAM,cAAA,GAAiB,QAAQ,MAAA,IAAU,KAAA;AACzC,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIC,eAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC9C,IAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAIA,eAAS,KAAK,CAAA;AAC5C,IAAA,MAAM,QAAA,GAAWL,aAA8B,IAAI,CAAA;AACnD,IAAA,MAAM,mBAAA,GAAsBA,aAAO,EAAE,CAAA;AACrC,IAAA,MAAM,YAAA,GAAeA,aAA6C,IAAI,CAAA;AACtE,IAAA,MAAM,aAAA,GAAgBA,aAA6C,IAAI,CAAA;AACvE,IAAA,MAAM,EAAE,QAAA,EAAU,UAAA,EAAY,aAAA,KAAkB,YAAA,EAAa;AAE7D,IAAA,MAAM,cAAA,GAAiBA,aAAO,WAAW,CAAA;AACzC,IAAA,cAAA,CAAe,OAAA,GAAU,WAAA;AACzB,IAAA,MAAM,eAAA,GAAkBA,aAAO,YAAY,CAAA;AAC3C,IAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAE1B,IAAAM,eAAA,CAAU,MAAM;AAAE,MAAA,UAAA,CAAW,IAAI,CAAA;AAAA,IAAG,CAAA,EAAG,EAAE,CAAA;AAEzC,IAAA,MAAM,YAAA,GAAeL,iBAAAA;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,IAAAK,eAAA,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,IAAAA,eAAA,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,MAAM,oBAAA,GAAuBN,aAA2B,IAAI,CAAA;AAC5D,IAAAM,eAAA,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,GAAYL,iBAAAA;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,GAAqBA,iBAAAA;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,GAAcA,iBAAAA;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,OAAOM,qBAAA;AAAA,sBACLC,cAAA;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,YAAA,EAAY,UAAU,MAAA,GAAS,QAAA;AAAA,UAC/B,KAAA,EAAO,YAAA,GAAe,EAAE,mBAAA,EAAqB,cAAa,GAA2B,MAAA;AAAA,UACrF,OAAA,EAAS,kBAAA;AAAA,UACT,SAAA;AAAA,UAEA,QAAA,kBAAAC,eAAA;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,YAAA,EAAY,UAAU,MAAA,GAAS,QAAA;AAAA,cAE9B,QAAA,EAAA;AAAA,gBAAA,SAAA,oBACCA,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,aAAA,EACZ,QAAA,EAAA;AAAA,kBAAA,KAAA,mBACCD,cAAA,CAAC,QAAG,EAAA,EAAI,OAAA,EAAS,WAAU,YAAA,EAAc,QAAA,EAAA,KAAA,EAAM,CAAA,mBAE/CA,cAAA,CAAC,MAAA,EAAA,EAAK,CAAA;AAAA,kBAEP,eAAA,oBACCA,cAAA;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,kBAAAA,cAAA,CAAC,SAAI,aAAA,EAAY,MAAA,EAAO,OAAM,IAAA,EAAK,MAAA,EAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,QACtE,QAAA,kBAAAA,cAAA,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,0BACfA,cAAA,CAAC,GAAA,EAAA,EAAE,IAAI,MAAA,EAAQ,SAAA,EAAU,oBAAoB,QAAA,EAAA,WAAA,EAAY,CAAA;AAAA,gCAE3DA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EAAa,QAAA,EAAS,CAAA;AAAA,gBACpC,MAAA,oBAAUA,cAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAe,QAAA,EAAA,MAAA,EAAO;AAAA;AAAA;AAAA;AAClD;AAAA,OACF;AAAA,MACA;AAAA,KACF;AAAA,EACF;AACF","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\";\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}\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 },\n ref,\n ) {\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 // 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 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-state={visible ? \"open\" : \"closed\"}\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 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-state={visible ? \"open\" : \"closed\"}\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"]}
|
|
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
|
@@ -3,7 +3,8 @@ 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";
|
|
6
|
+
type CloseReason = "esc" | "overlay" | "close-button" | "programmatic" | "swipe" | "submit";
|
|
7
|
+
type ModalTransition = "fade" | "zoom" | "slide-up" | "slide-down";
|
|
7
8
|
interface ModalStyledProps {
|
|
8
9
|
/** Open state. `open` is the canonical name; `isOpen` continues to work. */
|
|
9
10
|
open?: boolean;
|
|
@@ -44,7 +45,34 @@ interface ModalStyledProps {
|
|
|
44
45
|
lockBodyScroll?: boolean;
|
|
45
46
|
/** Override the portal container. Default: document.body. */
|
|
46
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;
|
|
47
54
|
}
|
|
48
55
|
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
49
56
|
|
|
50
|
-
|
|
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
|
@@ -3,7 +3,8 @@ 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";
|
|
6
|
+
type CloseReason = "esc" | "overlay" | "close-button" | "programmatic" | "swipe" | "submit";
|
|
7
|
+
type ModalTransition = "fade" | "zoom" | "slide-up" | "slide-down";
|
|
7
8
|
interface ModalStyledProps {
|
|
8
9
|
/** Open state. `open` is the canonical name; `isOpen` continues to work. */
|
|
9
10
|
open?: boolean;
|
|
@@ -44,7 +45,34 @@ interface ModalStyledProps {
|
|
|
44
45
|
lockBodyScroll?: boolean;
|
|
45
46
|
/** Override the portal container. Default: document.body. */
|
|
46
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;
|
|
47
54
|
}
|
|
48
55
|
declare const ModalStyled: react.ForwardRefExoticComponent<ModalStyledProps & react.RefAttributes<HTMLDivElement>>;
|
|
49
56
|
|
|
50
|
-
|
|
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,8 +1,15 @@
|
|
|
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({
|
|
8
15
|
open,
|
|
@@ -28,8 +35,16 @@ var ModalStyled = forwardRef(
|
|
|
28
35
|
onAfterClose,
|
|
29
36
|
preventClose,
|
|
30
37
|
lockBodyScroll = true,
|
|
31
|
-
container
|
|
38
|
+
container,
|
|
39
|
+
transition = "fade",
|
|
40
|
+
swipeToDismiss,
|
|
41
|
+
closeOnSubmit = false
|
|
32
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);
|
|
33
48
|
const titleId = useId();
|
|
34
49
|
const descId = useId();
|
|
35
50
|
const isActuallyOpen = open ?? isOpen ?? false;
|
|
@@ -98,6 +113,53 @@ var ModalStyled = forwardRef(
|
|
|
98
113
|
}
|
|
99
114
|
};
|
|
100
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]);
|
|
137
|
+
useEffect(() => {
|
|
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
|
+
};
|
|
101
163
|
const previouslyFocusedRef = useRef(null);
|
|
102
164
|
useEffect(() => {
|
|
103
165
|
if (isActuallyOpen) {
|
|
@@ -148,6 +210,19 @@ var ModalStyled = forwardRef(
|
|
|
148
210
|
if (!mounted || !rendered) return null;
|
|
149
211
|
const hasHeader = title !== void 0 || showCloseButton;
|
|
150
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
|
+
};
|
|
151
226
|
return createPortal(
|
|
152
227
|
/* @__PURE__ */ jsx(
|
|
153
228
|
"div",
|
|
@@ -158,8 +233,10 @@ var ModalStyled = forwardRef(
|
|
|
158
233
|
].filter(Boolean).join(" "),
|
|
159
234
|
"data-variant": variant,
|
|
160
235
|
"data-blur": blur,
|
|
236
|
+
"data-transition": transition,
|
|
237
|
+
"data-stack-position": stackPosition || void 0,
|
|
161
238
|
"data-state": visible ? "open" : "closed",
|
|
162
|
-
style:
|
|
239
|
+
style: overlayStyle,
|
|
163
240
|
onClick: handleOverlayClick,
|
|
164
241
|
onKeyDown,
|
|
165
242
|
children: /* @__PURE__ */ jsxs(
|
|
@@ -180,7 +257,14 @@ var ModalStyled = forwardRef(
|
|
|
180
257
|
"data-variant": variant,
|
|
181
258
|
"data-padding": padding,
|
|
182
259
|
"data-scrollable": scrollable ? "true" : void 0,
|
|
260
|
+
"data-transition": transition,
|
|
183
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,
|
|
184
268
|
children: [
|
|
185
269
|
hasHeader && /* @__PURE__ */ jsxs("div", { className: "rmod-header", children: [
|
|
186
270
|
title ? /* @__PURE__ */ jsx("h2", { id: titleId, className: "rmod-title", children: title }) : /* @__PURE__ */ jsx("span", {}),
|
|
@@ -207,7 +291,68 @@ var ModalStyled = forwardRef(
|
|
|
207
291
|
);
|
|
208
292
|
}
|
|
209
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 };
|
|
210
355
|
|
|
211
|
-
export { ModalStyled };
|
|
356
|
+
export { ModalStyled, confirm, modal };
|
|
212
357
|
//# sourceMappingURL=styled.js.map
|
|
213
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 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\";\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}\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 },\n ref,\n ) {\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 // 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 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-state={visible ? \"open\" : \"closed\"}\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 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-state={visible ? \"open\" : \"closed\"}\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/styled/ModalStyled.tsx","../src/styled/confirm.tsx"],"names":["ModalStyled","useState","jsx","jsxs"],"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 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
|
@@ -259,3 +259,104 @@
|
|
|
259
259
|
.rmod-panel[data-padding="none"] .rmod-description { padding: 0; }
|
|
260
260
|
.rmod-panel[data-padding="sm"] .rmod-description { padding: 0 0.75rem; }
|
|
261
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