react-modal-port 0.2.6 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ A full rework for correctness, type safety and packaging. See [MIGRATION.md](./MIGRATION.md) for upgrade steps.
6
+
7
+ ### Breaking changes
8
+
9
+ - `launchModal` checks resolvers and props against the modal component's props. Explicit type arguments are no longer supported.
10
+ - `onBackdropClick` moved from `resolvers` to `options.onDismiss`. The resolver form still works at runtime, with a deprecation warning.
11
+ - `updateStack` was removed from the context. `stack` is now read-only.
12
+ - `useModalState()` used inside a modal is scoped to that modal.
13
+ - The UMD build and the `types/` folder were removed. Types now ship in `dist/` as `.d.ts` and `.d.cts`.
14
+
15
+ ### Fixes
16
+
17
+ - A resolver closes its own modal instead of the top of the stack, so async resolvers no longer close modals that were stacked on top of them.
18
+ - A modal resolves only once, even when resolvers are called repeatedly.
19
+ - A resolver that throws or rejects leaves its modal open and can be retried. Previously the rejection went unhandled.
20
+ - Stacked instances of the same component no longer share component state.
21
+ - `useModal()` consumers no longer re-render on every stack or modal-state change.
22
+ - TypeScript users who `require()` the package under `node16`/`nodenext` resolution get correct CommonJS types.
23
+
24
+ ### Features
25
+
26
+ - `launchModal` returns a handle `{ id, close() }`.
27
+ - `useModalStack()` gives a read-only view of the stack.
28
+ - `useModalState` accepts updater functions.
29
+ - `ModalPort` gained `container` (render through a portal) and `onStackChange`. The backdrop receives `modalId` and `stackSize`, and gets `onBackdropClick` only when the modal is dismissible.
30
+ - `ModalProvider` and `ModalPort backdrop` are the new names; `ModalContextProvider` and `render` remain as aliases.
31
+ - The bundles are marked `"use client"` for React Server Components.
32
+
33
+ ### Compatibility
34
+
35
+ - Tested against React 19.0.0, the latest patch of 19.0, 19.1, 19.2 and 19.3, and the React canary. CI runs this matrix.
36
+
37
+ ### Tooling
38
+
39
+ - Build with tsup (ESM + CJS). Tests run on Vitest with type tests, StrictMode and render-count checks, 100% coverage, and a packed-tarball smoke test. Lint, publint, arethetypeswrong and CI were added.
40
+
41
+ ## 0.3.0
42
+
43
+ - Require React 19.
44
+
45
+ ## 0.2.x and earlier
46
+
47
+ - Generic modal props, context improvements and initial releases.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Oliver Greuter-Wehn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/MIGRATION.md ADDED
@@ -0,0 +1,74 @@
1
+ # Migrating from 0.x to 1.0
2
+
3
+ 1.0 fixes several stacking bugs, makes `launchModal` type-safe and modernises the package. Most apps need only a few mechanical changes. Deprecated names keep working throughout 1.x (with a development-only console warning where noted) and will be removed in 2.0.
4
+
5
+ ## Required changes
6
+
7
+ ### `launchModal` types are now checked
8
+
9
+ Resolvers and props are checked against the modal component's props. Code that compiled before may now report real mistakes: misspelled resolver names, missing required props, or wrongly typed props. Fix the call site, or type the modal's props.
10
+
11
+ Explicit type arguments are no longer needed or supported:
12
+
13
+ ```diff
14
+ - launchModal<DecisionModalProps>(DecisionModal, resolvers, props);
15
+ + launchModal(DecisionModal, resolvers, props);
16
+ ```
17
+
18
+ Modals typed loosely (for example `React.FC<ModalProps>` or `FC<Record<string, any>>`) still accept anything, as before.
19
+
20
+ ### `onBackdropClick` moved from resolvers to options
21
+
22
+ ```diff
23
+ - launchModal(Modal, { onClose, onBackdropClick: () => {} }, props);
24
+ + launchModal(Modal, { onClose }, props, { onDismiss: () => {} });
25
+ ```
26
+
27
+ The old form still works at runtime and logs a deprecation warning, but no longer type-checks. The modal component no longer receives `onBackdropClick` as a prop.
28
+
29
+ The backdrop now receives `onBackdropClick` only when the modal is dismissible; otherwise it is `undefined`.
30
+
31
+ ### `updateStack` was removed
32
+
33
+ `useModalContext()` no longer exposes `updateStack`, and `stack` is read-only. To close a modal programmatically, use the handle returned by `launchModal`:
34
+
35
+ ```ts
36
+ const { close } = launchModal(Modal, resolvers, props);
37
+ close();
38
+ ```
39
+
40
+ To inspect the stack, use `useModalStack()`.
41
+
42
+ ### `useModalState` is scoped to its modal
43
+
44
+ Inside a modal, `useModalState()` now reads and writes **that modal's** state, even while another modal is stacked on top. Previously it always targeted the top modal. Outside any modal it still targets the top one.
45
+
46
+ The setter also accepts an updater function. Prefer it over spreading a captured state object:
47
+
48
+ ```diff
49
+ - updateModalState({ ...modalState, name: value });
50
+ + updateModalState((prev) => ({ ...prev, name: value }));
51
+ ```
52
+
53
+ ### Package layout
54
+
55
+ - The UMD build (`dist/index.umd.js`) was removed; React 19 ships no UMD builds either. Use an ES module CDN such as esm.sh.
56
+ - Type declarations moved from `types/` to `dist/`. Deep imports of `react-modal-port/types/...` will break; import types from `react-modal-port`.
57
+ - Node ≥ 18 is required for tooling that resolves the package.
58
+
59
+ ## Behaviour changes you get for free
60
+
61
+ - A resolver closes **its own** modal, even if other modals were stacked on top while it was running. Previously it closed whatever modal was on top.
62
+ - Calling resolvers more than once (double-clicks, two buttons) closes the modal once.
63
+ - A resolver that throws or rejects keeps its modal open and can be called again.
64
+ - Stacking the same component on itself mounts a fresh instance instead of reusing the previous one's component state.
65
+ - Components that only call `useModal()` no longer re-render when modals open, close or update their state.
66
+
67
+ ## Renamed (old names still work)
68
+
69
+ | 0.x | 1.0 |
70
+ | --- | --- |
71
+ | `ModalContextProvider` | `ModalProvider` |
72
+ | `<ModalPort render={...}>` | `<ModalPort backdrop={...}>` |
73
+ | `useModalContext()` | `useModal()`, `useModalStack()`, `useModalState()` |
74
+ | `ModalProps`, `ModalResolver`, `LaunchModalResolvers`, `LaunchModalProps` | Your modal's own prop types |
package/dist/index.cjs CHANGED
@@ -1,2 +1,220 @@
1
- var e=require("react/jsx-runtime"),r=require("react");function n(){return n=Object.assign?Object.assign.bind():function(e){for(var r=1;r<arguments.length;r++){var n=arguments[r];for(var t in n)({}).hasOwnProperty.call(n,t)&&(e[t]=n[t])}return e},n.apply(null,arguments)}var t=r.createContext(null);function o(){var e=r.useContext(t);if(!e)throw new Error("useModalContext must be used within a ModalContextProvider");return e}exports.ModalContextProvider=function(o){var u=o.children,a=r.useState([]),l=a[0],c=a[1],s=r.useCallback(function(e,r,n){void 0===n&&(n={});var t=Object.keys(r).reduce(function(e,n){var t=r[n];return e[n]=function(){try{return Promise.resolve(t.apply(void 0,[].slice.call(arguments))).then(function(e){return c(function(e){return e.slice(0,-1)}),e})}catch(e){return Promise.reject(e)}},e},{});c(function(r){return[].concat(r,[{render:e,resolvers:t,props:n,state:{}}])})},[]),i=r.useCallback(function(e){c(function(r){var t=[].concat(r);return t.length>0&&(t[t.length-1]=n({},t[t.length-1],{state:n({},e)})),t})},[]);return e.jsx(t.Provider,{value:{stack:l,launchModal:s,updateStack:c,updateState:i},children:u})},exports.ModalPort=function(t){var u=t.onModalLaunch,a=t.onModalClose,l=t.render,c=o().stack,s=r.useMemo(function(){return c.length>0?c[c.length-1]:null},[c]),i=r.useRef(null),d=r.useCallback(function(e){e.currentTarget===e.target&&null!=s&&s.resolvers.onBackdropClick&&s.resolvers.onBackdropClick(e)},[s]);r.useEffect(function(){var e=i.current;!e&&s?null==u||u():e&&!s&&(null==a||a()),i.current=s},[s,u,a]);var v=null==s?void 0:s.render;return e.jsx(e.Fragment,{children:v&&e.jsx(l,{onBackdropClick:d,children:e.jsx(v,n({},s.resolvers,s.props))})||null})},exports.useModal=function(){return o().launchModal},exports.useModalContext=o,exports.useModalState=function(){var e=o(),r=e.stack,n=e.updateState;return 0===r.length?[null,n]:[r[r.length-1].state,n]};
2
- //# sourceMappingURL=index.cjs.map
1
+ "use client";
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ ModalContextProvider: () => ModalContextProvider,
25
+ ModalPort: () => ModalPort,
26
+ ModalProvider: () => ModalProvider,
27
+ useModal: () => useModal,
28
+ useModalContext: () => useModalContext,
29
+ useModalStack: () => useModalStack,
30
+ useModalState: () => useModalState
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/context.tsx
35
+ var import_react = require("react");
36
+
37
+ // src/store.ts
38
+ function reducer(stack, action) {
39
+ switch (action.type) {
40
+ case "push":
41
+ return [...stack, action.item];
42
+ case "remove":
43
+ return stack.some((item) => item.id === action.id) ? stack.filter((item) => item.id !== action.id) : stack;
44
+ case "setState": {
45
+ const { id, update } = action;
46
+ if (!stack.some((item) => item.id === id)) return stack;
47
+ return stack.map(
48
+ (item) => item.id === id ? { ...item, state: typeof update === "function" ? update(item.state) : { ...update } } : item
49
+ );
50
+ }
51
+ }
52
+ }
53
+
54
+ // src/context.tsx
55
+ var import_jsx_runtime = require("react/jsx-runtime");
56
+ var ActionsContext = (0, import_react.createContext)(null);
57
+ var StackContext = (0, import_react.createContext)(null);
58
+ var ItemContext = (0, import_react.createContext)(null);
59
+ var nextId = 0;
60
+ var warned = /* @__PURE__ */ new Set();
61
+ var isProduction = () => {
62
+ try {
63
+ return process.env.NODE_ENV === "production";
64
+ } catch {
65
+ return false;
66
+ }
67
+ };
68
+ function warnOnce(key, message) {
69
+ if (isProduction() || warned.has(key)) return;
70
+ warned.add(key);
71
+ console.warn(`[react-modal-port] ${message}`);
72
+ }
73
+ function ModalProvider({ children }) {
74
+ const [stack, dispatch] = (0, import_react.useReducer)(reducer, []);
75
+ const actions = (0, import_react.useMemo)(() => {
76
+ const launchModal = ((render, resolvers, props = {}, options = {}) => {
77
+ const id = ++nextId;
78
+ let settled = false;
79
+ const wrap = (fn) => async (...args) => {
80
+ if (settled) return void 0;
81
+ settled = true;
82
+ try {
83
+ const result = await fn(...args);
84
+ dispatch({ type: "remove", id });
85
+ return result;
86
+ } catch (error) {
87
+ settled = false;
88
+ throw error;
89
+ }
90
+ };
91
+ const { onBackdropClick: legacyDismiss, ...ownResolvers } = resolvers;
92
+ if (legacyDismiss) {
93
+ warnOnce(
94
+ "onBackdropClick",
95
+ "Passing `onBackdropClick` as a resolver is deprecated. Use `launchModal(Modal, resolvers, props, { onDismiss })`."
96
+ );
97
+ }
98
+ const onDismiss = options.onDismiss ?? legacyDismiss;
99
+ for (const key of Object.keys(props)) {
100
+ if (key in ownResolvers) {
101
+ warnOnce(
102
+ `collision:${key}`,
103
+ `Prop "${key}" has the same name as a resolver and is ignored. Resolvers take precedence.`
104
+ );
105
+ }
106
+ }
107
+ dispatch({
108
+ type: "push",
109
+ item: {
110
+ id,
111
+ render,
112
+ props,
113
+ resolvers: Object.fromEntries(Object.entries(ownResolvers).map(([key, fn]) => [key, wrap(fn)])),
114
+ onDismiss: onDismiss ? wrap(onDismiss) : void 0,
115
+ state: {}
116
+ }
117
+ });
118
+ return {
119
+ id,
120
+ close: () => {
121
+ settled = true;
122
+ dispatch({ type: "remove", id });
123
+ }
124
+ };
125
+ });
126
+ return {
127
+ launchModal,
128
+ setState: (id, update) => dispatch({ type: "setState", id, update })
129
+ };
130
+ }, []);
131
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext, { value: actions, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(StackContext, { value: stack, children }) });
132
+ }
133
+ var ModalContextProvider = ModalProvider;
134
+ function useRequired(context, hook) {
135
+ const value = (0, import_react.use)(context);
136
+ if (value === null) {
137
+ throw new Error(`${hook} must be used within a <ModalProvider>`);
138
+ }
139
+ return value;
140
+ }
141
+ function useModalItems(hook = "ModalPort") {
142
+ return useRequired(StackContext, hook);
143
+ }
144
+ function toEntry({ id, render, props, state }) {
145
+ return { id, render, props, state };
146
+ }
147
+ function useModal() {
148
+ return useRequired(ActionsContext, "useModal").launchModal;
149
+ }
150
+ function useModalStack() {
151
+ const items = useModalItems("useModalStack");
152
+ return (0, import_react.useMemo)(() => items.map(toEntry), [items]);
153
+ }
154
+ function useModalState() {
155
+ const { setState } = useRequired(ActionsContext, "useModalState");
156
+ const items = useModalItems("useModalState");
157
+ const ownId = (0, import_react.use)(ItemContext);
158
+ const item = ownId === null ? items.at(-1) : items.find((entry) => entry.id === ownId);
159
+ const id = item?.id;
160
+ const update = (0, import_react.useCallback)(
161
+ (next) => {
162
+ if (id !== void 0) setState(id, next);
163
+ },
164
+ [id, setState]
165
+ );
166
+ return [item ? item.state : null, update];
167
+ }
168
+ function useModalContext() {
169
+ const launchModal = useRequired(ActionsContext, "useModalContext").launchModal;
170
+ const stack = useModalStack();
171
+ const [, updateState] = useModalState();
172
+ return { stack, launchModal, updateState };
173
+ }
174
+
175
+ // src/modal-port.tsx
176
+ var import_react2 = require("react");
177
+ var import_react_dom = require("react-dom");
178
+ var import_jsx_runtime2 = require("react/jsx-runtime");
179
+ var NoBackdrop = ({ children }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react2.Fragment, { children });
180
+ function ModalPort({
181
+ backdrop,
182
+ render,
183
+ container,
184
+ onModalLaunch,
185
+ onModalClose,
186
+ onStackChange
187
+ }) {
188
+ const stack = useModalItems();
189
+ const size = stack.length;
190
+ const previousSize = (0, import_react2.useRef)(0);
191
+ (0, import_react2.useEffect)(() => {
192
+ const previous = previousSize.current;
193
+ if (previous === size) return;
194
+ previousSize.current = size;
195
+ onStackChange?.(size);
196
+ if (previous === 0) onModalLaunch?.();
197
+ else if (size === 0) onModalClose?.();
198
+ }, [size, onModalLaunch, onModalClose, onStackChange]);
199
+ const top = stack.at(-1);
200
+ if (!top) return null;
201
+ const Backdrop = backdrop ?? render ?? NoBackdrop;
202
+ const Modal = top.render;
203
+ const { onDismiss } = top;
204
+ const onBackdropClick = onDismiss ? (event) => {
205
+ if (event.currentTarget === event.target) void onDismiss(event);
206
+ } : void 0;
207
+ const content = /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Backdrop, { onBackdropClick, modalId: top.id, stackSize: size, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ItemContext, { value: top.id, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Modal, { ...top.props, ...top.resolvers }) }, top.id) });
208
+ return container ? (0, import_react_dom.createPortal)(content, container) : content;
209
+ }
210
+ // Annotate the CommonJS export names for ESM import in node:
211
+ 0 && (module.exports = {
212
+ ModalContextProvider,
213
+ ModalPort,
214
+ ModalProvider,
215
+ useModal,
216
+ useModalContext,
217
+ useModalStack,
218
+ useModalState
219
+ });
220
+ //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/context.tsx","../src/modal-port.tsx"],"sourcesContent":["import { \n createContext, \n useContext, \n useState, \n useCallback,\n type SyntheticEvent \n} from 'react';\nimport { \n type ModalStackItem,\n type ModalContextProperties,\n type LaunchModal,\n type LaunchModalResolvers,\n type ModalState,\n type UpdateModalState,\n ModalProps,\n} from './types';\n\nexport const ModalContext = createContext<ModalContextProperties | null>(null);\n\nexport const ModalContextProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => {\n\n const [ stack, updateStack ] = useState<ModalStackItem<any>[]>([]);\n\n const launchModal = useCallback(<P extends Record<string, any> = Record<string, any>>(\n render: React.FunctionComponent<ModalProps<P>>, \n resolvers: LaunchModalResolvers,\n props: any = {}\n ) => {\n // Wrap resolvers to update the stack\n const wrappedResovers = Object.keys(resolvers).reduce((acc, key) => {\n const resolver = resolvers[key];\n acc[key] = async (...args: any[]) => {\n const result = await resolver(...args);\n // update stack with an array containing all the items in the stack except the last one\n updateStack(currStack => currStack.slice(0, -1));\n return result;\n };\n return acc;\n }, {} as LaunchModalResolvers);\n\n updateStack(prevStack => [\n ...prevStack,\n {\n render,\n resolvers: wrappedResovers,\n props,\n state: {},\n },\n ]);\n }, []);\n\n const updateState = useCallback((newState: ModalState) => {\n updateStack(prevStack => {\n const updatedStack = [...prevStack];\n if (updatedStack.length > 0) {\n updatedStack[updatedStack.length - 1] = {\n ...updatedStack[updatedStack.length - 1],\n state: { ...newState }\n };\n }\n return updatedStack;\n });\n }, []);\n\n const contextProperties: ModalContextProperties = {\n stack,\n launchModal,\n updateStack,\n updateState,\n };\n\n return <ModalContext.Provider value={contextProperties}>{children}</ModalContext.Provider>;\n\n};\n\n/**\n * Get properties from the modal context\n * @returns {ModalContextProperties} Object with the modal context properties\n */\nexport function useModalContext(): ModalContextProperties {\n const context = useContext(ModalContext);\n if (!context) {\n throw new Error('useModalContext must be used within a ModalContextProvider');\n }\n return context;\n}\n\n/**\n * Short-hand for `useModalContext().launchModal`\n * @returns {LaunchModal} The launchModal function\n */\nexport function useModal() {\n const { launchModal } = useModalContext();\n return launchModal;\n}\n\n/**\n * Get access to the current modal state and a function to update it\n * @returns {[ ModalState | null, UpdateModalState ]} A tuple with the current modal state and a function to update it\n */\nexport function useModalState(): [ ModalState | null, UpdateModalState ] {\n const { stack, updateState } = useModalContext();\n if (stack.length === 0) {\n return [null, updateState];\n }\n const state = stack[stack.length - 1].state;\n return [ state, updateState ];\n}\n","import { \n ModalProps,\n type ModalPortProps, \n type ModalStackItem \n} from \"./types\";\nimport { useModalContext } from \"./context\";\nimport { \n type SyntheticEvent, \n useCallback, \n useEffect, \n useMemo,\n useRef,\n type ReactElement\n} from \"react\";\n\nconst ModalPort: React.FC<ModalPortProps> = ({\n onModalLaunch,\n onModalClose,\n render: Backdrop\n}) => {\n\n const { stack } = useModalContext();\n const currentModal: ModalStackItem<ModalProps> | null = useMemo(\n () => stack.length > 0 ? stack[stack.length - 1] : null,\n [ stack ]\n );\n \n const previousModalRef = useRef<ModalStackItem<ModalProps> | null>(null);\n \n const onBackdropClick = useCallback((ev: SyntheticEvent) => {\n if (ev.currentTarget !== ev.target) return;\n if (currentModal?.resolvers.onBackdropClick) {\n currentModal.resolvers.onBackdropClick(ev);\n }\n }, [ currentModal ]);\n\n useEffect(() => {\n const previousModal = previousModalRef.current;\n \n // Modal was launched (went from no modal to having a modal)\n if (!previousModal && currentModal) {\n onModalLaunch?.();\n }\n // Modal was closed (went from having a modal to no modal)\n else if (previousModal && !currentModal) {\n onModalClose?.();\n }\n \n // Update the ref for next time\n previousModalRef.current = currentModal;\n }, [ currentModal, onModalLaunch, onModalClose ]);\n\n const ModalContent = currentModal?.render;\n\n return (\n <>\n {ModalContent && (\n <Backdrop onBackdropClick={onBackdropClick}>\n <ModalContent\n {...currentModal.resolvers}\n {...currentModal.props}\n />\n </Backdrop>\n ) || null}\n </>\n );\n};\n\nexport default ModalPort;\nexport { ModalPort };\n"],"names":["ModalContext","createContext","useModalContext","context","useContext","Error","_ref","children","_useState","useState","stack","updateStack","launchModal","useCallback","render","resolvers","props","wrappedResovers","Object","keys","reduce","acc","key","resolver","Promise","resolve","apply","slice","call","arguments","then","result","currStack","e","reject","prevStack","concat","state","updateState","newState","updatedStack","length","_extends","_jsx","Provider","value","onModalLaunch","onModalClose","Backdrop","currentModal","useMemo","previousModalRef","useRef","onBackdropClick","ev","currentTarget","target","useEffect","previousModal","current","ModalContent","_Fragment","jsx","_useModalContext2"],"mappings":"kRAiBaA,EAAeC,EAAaA,cAAgC,MA8DzD,SAAAC,IACd,IAAMC,EAAUC,EAAUA,WAACJ,GAC3B,IAAKG,EACH,MAAU,IAAAE,MAAM,8DAElB,OAAOF,CACT,8BAlEoC,SAAHG,GAA6D,IAAvDC,EAAQD,EAARC,SAErCC,EAA+BC,EAAQA,SAAwB,IAAvDC,EAAKF,EAAA,GAAEG,EAAWH,EAE1B,GAAMI,EAAcC,EAAAA,YAAY,SAC9BC,EACAC,EACAC,QAAAA,IAAAA,IAAAA,EAAa,IAGb,IAAMC,EAAkBC,OAAOC,KAAKJ,GAAWK,OAAO,SAACC,EAAKC,GAC1D,IAAMC,EAAWR,EAAUO,GAO3B,OANAD,EAAIC,GAAI,WAAA,IAA4B,OAAAE,QAAAC,QACbF,EAAQG,WAAAC,EAAAA,GAAAA,MAAAC,KADKC,aACIC,KAAhCC,SAAAA,GAGN,OADApB,EAAY,SAAAqB,GAAa,OAAAA,EAAUL,MAAM,GAAI,EAAE,GACxCI,CAAO,EAChB,CAAC,MAAAE,UAAAT,QAAAU,OAAAD,EAAA,CAAA,EACMZ,CACT,EAAG,CAAA,GAEHV,EAAY,SAAAwB,GAAS,MAAA,GAAAC,OAChBD,EACH,CAAA,CACErB,OAAAA,EACAC,UAAWE,EACXD,MAAAA,EACAqB,MAAO,CAAA,IAEV,EACH,EAAG,IAEGC,EAAczB,cAAY,SAAC0B,GAC/B5B,EAAY,SAAAwB,GACV,IAAMK,EAAYJ,GAAAA,OAAOD,GAOzB,OANIK,EAAaC,OAAS,IACxBD,EAAaA,EAAaC,OAAS,GAAEC,EAChCF,CAAAA,EAAAA,EAAaA,EAAaC,OAAS,GACtCJ,CAAAA,MAAKK,KAAOH,MAGTC,CACT,EACF,EAAG,IASH,OAAOG,EAAAA,IAAC3C,EAAa4C,SAAQ,CAACC,MAPoB,CAChDnC,MAAAA,EACAE,YAAAA,EACAD,YAAAA,EACA2B,YAAAA,GAGoD/B,SAAGA,GAE3D,oBC1D4C,SAA7BD,GACb,IAAAwC,EAAaxC,EAAbwC,cACAC,EAAYzC,EAAZyC,aACQC,EAAQ1C,EAAhBQ,OAGQJ,EAAUR,IAAVQ,MACFuC,EAAkDC,EAAAA,QACtD,WAAA,OAAMxC,EAAM+B,OAAS,EAAI/B,EAAMA,EAAM+B,OAAS,GAAK,IAAI,EACvD,CAAE/B,IAGEyC,EAAmBC,SAA0C,MAE7DC,EAAkBxC,EAAAA,YAAY,SAACyC,GAC/BA,EAAGC,gBAAkBD,EAAGE,cACxBP,GAAAA,EAAclC,UAAUsC,iBAC1BJ,EAAalC,UAAUsC,gBAAgBC,EAE3C,EAAG,CAAEL,IAELQ,EAAAA,UAAU,WACR,IAAMC,EAAgBP,EAAiBQ,SAGlCD,GAAiBT,EACP,MAAbH,GAAAA,IAGOY,IAAkBT,IACzBF,MAAAA,GAAAA,KAIFI,EAAiBQ,QAAUV,CAC7B,EAAG,CAAEA,EAAcH,EAAeC,IAElC,IAAMa,EAAeX,MAAAA,OAAAA,EAAAA,EAAcnC,OAEnC,OACE6B,EAAAA,IAAAkB,EAAAA,SAAA,CAAAtD,SACGqD,GACCjB,EAAAmB,IAACd,EAAQ,CAACK,gBAAiBA,EAAe9C,SACxCoC,EAACmB,IAAAF,EAAYlB,EACPO,CAAAA,EAAAA,EAAalC,UACbkC,EAAajC,WAGlB,MAGX,8BD2BE,OADwBd,IAAhBU,WAEV,6DAOE,IAAAmD,EAA+B7D,IAAvBQ,EAAKqD,EAALrD,MAAO4B,EAAWyB,EAAXzB,YACf,OAAqB,IAAjB5B,EAAM+B,OACD,CAAC,KAAMH,GAGT,CADO5B,EAAMA,EAAM+B,OAAS,GAAGJ,MACtBC,EAClB"}
1
+ {"version":3,"sources":["../src/index.ts","../src/context.tsx","../src/store.ts","../src/modal-port.tsx"],"sourcesContent":["export {\n ModalProvider,\n ModalContextProvider,\n useModal,\n useModalStack,\n useModalState,\n useModalContext,\n} from './context';\nexport { ModalPort } from './modal-port';\n\nexport type {\n FunctionKeys,\n Resolvers,\n RestProps,\n LaunchRest,\n LaunchOptions,\n LaunchModal,\n ModalHandle,\n ModalState,\n UpdateModalState,\n ModalStackEntry,\n ModalPortRenderProps,\n ModalPortProps,\n ModalContextProperties,\n ModalProps,\n ModalResolver,\n LaunchModalResolvers,\n LaunchModalProps,\n} from './types';\n","import {\n createContext,\n use,\n useCallback,\n useMemo,\n useReducer,\n type ComponentType,\n type Context,\n type ReactNode,\n} from 'react';\nimport { reducer, type ModalItem, type StateUpdate } from './store';\nimport type {\n LaunchModal,\n LaunchOptions,\n ModalContextProperties,\n ModalStackEntry,\n ModalState,\n UpdateModalState,\n} from './types';\n\ntype Actions = {\n launchModal: LaunchModal;\n setState: (id: number, update: StateUpdate) => void;\n};\n\nconst ActionsContext = createContext<Actions | null>(null);\nconst StackContext = createContext<readonly ModalItem[] | null>(null);\n/** Id of the modal a component is rendered inside, if any. */\nexport const ItemContext = createContext<number | null>(null);\n\nlet nextId = 0;\nconst warned = new Set<string>();\ndeclare const process: { env: { NODE_ENV?: string } };\n// Bundlers replace the literal `process.env.NODE_ENV` but usually do not define `process`,\n// so read it directly; unbundled ESM (no `process` at all) falls back to development.\nconst isProduction = () => {\n try {\n return process.env.NODE_ENV === 'production';\n } catch {\n return false;\n }\n};\nfunction warnOnce(key: string, message: string) {\n if (isProduction() || warned.has(key)) return;\n warned.add(key);\n console.warn(`[react-modal-port] ${message}`);\n}\n\ntype AnyResolver = (...args: unknown[]) => unknown;\n\nexport function ModalProvider({ children }: { children?: ReactNode }) {\n const [stack, dispatch] = useReducer(reducer, []);\n\n const actions = useMemo<Actions>(() => {\n const launchModal = ((\n render: ComponentType<never>,\n resolvers: Record<string, AnyResolver>,\n props: Record<string, unknown> = {},\n options: LaunchOptions = {},\n ) => {\n // Created outside the reducer so StrictMode's double-invoked reducers stay pure.\n const id = ++nextId;\n let settled = false;\n\n const wrap =\n (fn: AnyResolver) =>\n async (...args: unknown[]) => {\n if (settled) return undefined;\n settled = true;\n try {\n const result = await fn(...args);\n dispatch({ type: 'remove', id });\n return result;\n } catch (error) {\n // Keep the modal open so the user can retry; let the caller handle the error.\n settled = false;\n throw error;\n }\n };\n\n const { onBackdropClick: legacyDismiss, ...ownResolvers } = resolvers;\n if (legacyDismiss) {\n warnOnce(\n 'onBackdropClick',\n 'Passing `onBackdropClick` as a resolver is deprecated. Use `launchModal(Modal, resolvers, props, { onDismiss })`.',\n );\n }\n const onDismiss = options.onDismiss ?? legacyDismiss;\n\n for (const key of Object.keys(props)) {\n if (key in ownResolvers) {\n warnOnce(\n `collision:${key}`,\n `Prop \"${key}\" has the same name as a resolver and is ignored. Resolvers take precedence.`,\n );\n }\n }\n\n dispatch({\n type: 'push',\n item: {\n id,\n render,\n props,\n resolvers: Object.fromEntries(Object.entries(ownResolvers).map(([key, fn]) => [key, wrap(fn)])),\n onDismiss: onDismiss ? wrap(onDismiss as AnyResolver) : undefined,\n state: {},\n },\n });\n\n return {\n id,\n close: () => {\n settled = true;\n dispatch({ type: 'remove', id });\n },\n };\n }) as LaunchModal;\n\n return {\n launchModal,\n setState: (id, update) => dispatch({ type: 'setState', id, update }),\n };\n }, []);\n\n return (\n <ActionsContext value={actions}>\n <StackContext value={stack}>{children}</StackContext>\n </ActionsContext>\n );\n}\n\n/** @deprecated Use `ModalProvider`. */\nexport const ModalContextProvider = ModalProvider;\n\nfunction useRequired<T>(context: Context<T | null>, hook: string): T {\n const value = use(context);\n if (value === null) {\n throw new Error(`${hook} must be used within a <ModalProvider>`);\n }\n return value;\n}\n\n/** @internal Full stack items, including wrapped resolvers. Used by `ModalPort`. */\nexport function useModalItems(hook = 'ModalPort'): readonly ModalItem[] {\n return useRequired(StackContext, hook);\n}\n\nfunction toEntry({ id, render, props, state }: ModalItem): ModalStackEntry {\n return { id, render, props, state };\n}\n\n/** Returns the `launchModal` function. Does not re-render when the stack changes. */\nexport function useModal(): LaunchModal {\n return useRequired(ActionsContext, 'useModal').launchModal;\n}\n\n/** A read-only view of the modal stack, bottom first. */\nexport function useModalStack(): readonly ModalStackEntry[] {\n const items = useModalItems('useModalStack');\n return useMemo(() => items.map(toEntry), [items]);\n}\n\n/**\n * State shared by the modal this hook is called in (or the top modal, when called\n * outside any modal). Survives while other modals are stacked on top and is\n * discarded when the modal closes. `null` when there is no such modal.\n */\nexport function useModalState<S extends ModalState = ModalState>(): [S | null, UpdateModalState<S>] {\n const { setState } = useRequired(ActionsContext, 'useModalState');\n const items = useModalItems('useModalState');\n const ownId = use(ItemContext);\n const item = ownId === null ? items.at(-1) : items.find((entry) => entry.id === ownId);\n const id = item?.id;\n\n const update = useCallback<UpdateModalState<S>>(\n (next) => {\n if (id !== undefined) setState(id, next as StateUpdate);\n },\n [id, setState],\n );\n\n return [item ? (item.state as S) : null, update];\n}\n\n/**\n * @deprecated Use `useModal`, `useModalStack` and `useModalState`. This hook re-renders\n * on every stack change.\n */\nexport function useModalContext(): ModalContextProperties {\n const launchModal = useRequired(ActionsContext, 'useModalContext').launchModal;\n const stack = useModalStack();\n const [, updateState] = useModalState();\n return { stack, launchModal, updateState };\n}\n","import type { ComponentType } from 'react';\nimport type { ModalState } from './types';\n\nexport type ModalItem = {\n readonly id: number;\n readonly render: ComponentType<never>;\n /** Resolvers, already wrapped so that they close this item. */\n readonly resolvers: Readonly<Record<string, (...args: unknown[]) => Promise<unknown>>>;\n readonly props: Readonly<Record<string, unknown>>;\n readonly onDismiss: ((...args: unknown[]) => Promise<unknown>) | undefined;\n readonly state: ModalState;\n};\n\nexport type StateUpdate = ModalState | ((prev: ModalState) => ModalState);\n\nexport type Action =\n | { type: 'push'; item: ModalItem }\n | { type: 'remove'; id: number }\n | { type: 'setState'; id: number; update: StateUpdate };\n\n/** Pure stack reducer. Ids are created by the caller, so this stays StrictMode-safe. */\nexport function reducer(stack: readonly ModalItem[], action: Action): readonly ModalItem[] {\n switch (action.type) {\n case 'push':\n return [...stack, action.item];\n case 'remove':\n return stack.some((item) => item.id === action.id)\n ? stack.filter((item) => item.id !== action.id)\n : stack;\n case 'setState': {\n const { id, update } = action;\n if (!stack.some((item) => item.id === id)) return stack;\n return stack.map((item) =>\n item.id === id\n ? { ...item, state: typeof update === 'function' ? update(item.state) : { ...update } }\n : item,\n );\n }\n }\n}\n","import { Fragment, useEffect, useRef, type ComponentType, type SyntheticEvent } from 'react';\nimport { createPortal } from 'react-dom';\nimport { ItemContext, useModalItems } from './context';\nimport type { ModalPortProps, ModalPortRenderProps } from './types';\n\nconst NoBackdrop = ({ children }: ModalPortRenderProps) => <Fragment>{children}</Fragment>;\n\n/** Renders the top modal of the stack inside `backdrop`. */\nexport function ModalPort({\n backdrop,\n render,\n container,\n onModalLaunch,\n onModalClose,\n onStackChange,\n}: ModalPortProps) {\n const stack = useModalItems();\n const size = stack.length;\n const previousSize = useRef(0);\n\n useEffect(() => {\n const previous = previousSize.current;\n if (previous === size) return;\n previousSize.current = size;\n onStackChange?.(size);\n if (previous === 0) onModalLaunch?.();\n else if (size === 0) onModalClose?.();\n }, [size, onModalLaunch, onModalClose, onStackChange]);\n\n const top = stack.at(-1);\n if (!top) return null;\n\n const Backdrop = backdrop ?? render ?? NoBackdrop;\n // Props are typed per launch; the stack stores them loosely.\n const Modal = top.render as ComponentType<Record<string, unknown>>;\n const { onDismiss } = top;\n const onBackdropClick = onDismiss\n ? (event: SyntheticEvent) => {\n // Ignore clicks that bubble up from the modal content.\n if (event.currentTarget === event.target) void onDismiss(event);\n }\n : undefined;\n\n const content = (\n <Backdrop onBackdropClick={onBackdropClick} modalId={top.id} stackSize={size}>\n <ItemContext key={top.id} value={top.id}>\n <Modal {...top.props} {...top.resolvers} />\n </ItemContext>\n </Backdrop>\n );\n\n return container ? createPortal(content, container) : content;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
@@ -0,0 +1,113 @@
1
+ import * as react from 'react';
2
+ import { ComponentType, ComponentProps, SyntheticEvent, KeyboardEvent, ReactNode } from 'react';
3
+
4
+ type AnyFn = (...args: any[]) => any;
5
+ /** Keys of `P` whose (non-nullable) value is a function, optional props included. */
6
+ type FunctionKeys<P> = {
7
+ [K in keyof P]-?: NonNullable<P[K]> extends AnyFn ? K : never;
8
+ }[keyof P];
9
+ /** The subset of a modal's function props that resolve (and close) it. */
10
+ type Resolvers<P, R extends FunctionKeys<P>> = {
11
+ [K in R]: P[K];
12
+ };
13
+ /** Everything the modal needs that is not passed as a resolver. */
14
+ type RestProps<P, R extends PropertyKey> = Omit<P, R>;
15
+ /** Options that concern the port rather than the modal component itself. */
16
+ type LaunchOptions = {
17
+ /**
18
+ * Called when the modal is dismissed from outside its content (backdrop click,
19
+ * or Escape if your backdrop wires it). Wrapped like a resolver: it closes this
20
+ * modal once it has run, and keeps it open if it throws.
21
+ */
22
+ onDismiss?: (event?: SyntheticEvent | KeyboardEvent | Event) => unknown;
23
+ };
24
+ /**
25
+ * Arguments after `resolvers`: `props` is required when the modal still has required
26
+ * props left, optional when only optional props are left, and must be empty otherwise.
27
+ */
28
+ type LaunchRest<P, R extends PropertyKey> = [keyof RestProps<P, R>] extends [never] ? [props?: Record<string, never>, options?: LaunchOptions] : object extends RestProps<P, R> ? [props?: RestProps<P, R>, options?: LaunchOptions] : [props: RestProps<P, R>, options?: LaunchOptions];
29
+ /** Returned by `launchModal`; lets the launcher close the modal programmatically. */
30
+ type ModalHandle = {
31
+ readonly id: number;
32
+ /** Removes this modal without calling any resolver. No-op if it is already gone. */
33
+ close: () => void;
34
+ };
35
+ /**
36
+ * Launch `render` on top of the modal stack.
37
+ *
38
+ * The modal's props are read from the component `C`. `R` is inferred from the keys of
39
+ * `resolvers`, which must be function props of the modal. The remaining props are type-checked against the modal's props.
40
+ */
41
+ type LaunchModal = <C extends ComponentType<any>, R extends FunctionKeys<ComponentProps<C>> = never>(render: C, resolvers: Resolvers<ComponentProps<C>, R>, ...rest: LaunchRest<ComponentProps<C>, R>) => ModalHandle;
42
+ type ModalState = Record<string, unknown>;
43
+ type UpdateModalState<S extends ModalState = ModalState> = (update: S | ((prev: S) => S)) => void;
44
+ /** A read-only view of one modal on the stack. */
45
+ type ModalStackEntry = {
46
+ readonly id: number;
47
+ readonly render: ComponentType<never>;
48
+ readonly props: Readonly<Record<string, unknown>>;
49
+ readonly state: Readonly<ModalState>;
50
+ };
51
+ type ModalPortRenderProps = {
52
+ children?: ReactNode;
53
+ /** Present only when the current modal can be dismissed (it was launched with `onDismiss`). */
54
+ onBackdropClick?: (event: SyntheticEvent) => void;
55
+ /** Id of the modal currently shown. */
56
+ modalId: number;
57
+ /** Number of modals on the stack, including the one shown. */
58
+ stackSize: number;
59
+ };
60
+ type ModalPortProps = {
61
+ /** Component rendered around the current modal, typically a backdrop. */
62
+ backdrop?: ComponentType<ModalPortRenderProps>;
63
+ /** @deprecated Use `backdrop`. */
64
+ render?: ComponentType<ModalPortRenderProps>;
65
+ /** Render into this element through a portal instead of in place. */
66
+ container?: Element | DocumentFragment | null;
67
+ /** Called when the stack goes from empty to non-empty. */
68
+ onModalLaunch?: () => void;
69
+ /** Called when the stack becomes empty. */
70
+ onModalClose?: () => void;
71
+ /** Called with the new stack size whenever a modal is launched or closed. */
72
+ onStackChange?: (stackSize: number) => void;
73
+ };
74
+ type ModalContextProperties = {
75
+ readonly stack: readonly ModalStackEntry[];
76
+ launchModal: LaunchModal;
77
+ /** Replaces the state of the top modal. */
78
+ updateState: UpdateModalState;
79
+ };
80
+ /** @deprecated Type your modal's props directly. */
81
+ type ModalProps<P = Record<string, any>> = P;
82
+ /** @deprecated Use `Resolvers` or your modal's own prop types. */
83
+ type ModalResolver = (...args: any[]) => unknown;
84
+ /** @deprecated Use `Resolvers` or your modal's own prop types. */
85
+ type LaunchModalResolvers = Record<string, ModalResolver>;
86
+ /** @deprecated Use your modal's own prop types. */
87
+ type LaunchModalProps = Record<string, unknown>;
88
+
89
+ declare function ModalProvider({ children }: {
90
+ children?: ReactNode;
91
+ }): react.JSX.Element;
92
+ /** @deprecated Use `ModalProvider`. */
93
+ declare const ModalContextProvider: typeof ModalProvider;
94
+ /** Returns the `launchModal` function. Does not re-render when the stack changes. */
95
+ declare function useModal(): LaunchModal;
96
+ /** A read-only view of the modal stack, bottom first. */
97
+ declare function useModalStack(): readonly ModalStackEntry[];
98
+ /**
99
+ * State shared by the modal this hook is called in (or the top modal, when called
100
+ * outside any modal). Survives while other modals are stacked on top and is
101
+ * discarded when the modal closes. `null` when there is no such modal.
102
+ */
103
+ declare function useModalState<S extends ModalState = ModalState>(): [S | null, UpdateModalState<S>];
104
+ /**
105
+ * @deprecated Use `useModal`, `useModalStack` and `useModalState`. This hook re-renders
106
+ * on every stack change.
107
+ */
108
+ declare function useModalContext(): ModalContextProperties;
109
+
110
+ /** Renders the top modal of the stack inside `backdrop`. */
111
+ declare function ModalPort({ backdrop, render, container, onModalLaunch, onModalClose, onStackChange, }: ModalPortProps): react.JSX.Element | null;
112
+
113
+ export { type FunctionKeys, type LaunchModal, type LaunchModalProps, type LaunchModalResolvers, type LaunchOptions, type LaunchRest, type ModalContextProperties, ModalContextProvider, type ModalHandle, ModalPort, type ModalPortProps, type ModalPortRenderProps, type ModalProps, ModalProvider, type ModalResolver, type ModalStackEntry, type ModalState, type Resolvers, type RestProps, type UpdateModalState, useModal, useModalContext, useModalStack, useModalState };
@@ -0,0 +1,113 @@
1
+ import * as react from 'react';
2
+ import { ComponentType, ComponentProps, SyntheticEvent, KeyboardEvent, ReactNode } from 'react';
3
+
4
+ type AnyFn = (...args: any[]) => any;
5
+ /** Keys of `P` whose (non-nullable) value is a function, optional props included. */
6
+ type FunctionKeys<P> = {
7
+ [K in keyof P]-?: NonNullable<P[K]> extends AnyFn ? K : never;
8
+ }[keyof P];
9
+ /** The subset of a modal's function props that resolve (and close) it. */
10
+ type Resolvers<P, R extends FunctionKeys<P>> = {
11
+ [K in R]: P[K];
12
+ };
13
+ /** Everything the modal needs that is not passed as a resolver. */
14
+ type RestProps<P, R extends PropertyKey> = Omit<P, R>;
15
+ /** Options that concern the port rather than the modal component itself. */
16
+ type LaunchOptions = {
17
+ /**
18
+ * Called when the modal is dismissed from outside its content (backdrop click,
19
+ * or Escape if your backdrop wires it). Wrapped like a resolver: it closes this
20
+ * modal once it has run, and keeps it open if it throws.
21
+ */
22
+ onDismiss?: (event?: SyntheticEvent | KeyboardEvent | Event) => unknown;
23
+ };
24
+ /**
25
+ * Arguments after `resolvers`: `props` is required when the modal still has required
26
+ * props left, optional when only optional props are left, and must be empty otherwise.
27
+ */
28
+ type LaunchRest<P, R extends PropertyKey> = [keyof RestProps<P, R>] extends [never] ? [props?: Record<string, never>, options?: LaunchOptions] : object extends RestProps<P, R> ? [props?: RestProps<P, R>, options?: LaunchOptions] : [props: RestProps<P, R>, options?: LaunchOptions];
29
+ /** Returned by `launchModal`; lets the launcher close the modal programmatically. */
30
+ type ModalHandle = {
31
+ readonly id: number;
32
+ /** Removes this modal without calling any resolver. No-op if it is already gone. */
33
+ close: () => void;
34
+ };
35
+ /**
36
+ * Launch `render` on top of the modal stack.
37
+ *
38
+ * The modal's props are read from the component `C`. `R` is inferred from the keys of
39
+ * `resolvers`, which must be function props of the modal. The remaining props are type-checked against the modal's props.
40
+ */
41
+ type LaunchModal = <C extends ComponentType<any>, R extends FunctionKeys<ComponentProps<C>> = never>(render: C, resolvers: Resolvers<ComponentProps<C>, R>, ...rest: LaunchRest<ComponentProps<C>, R>) => ModalHandle;
42
+ type ModalState = Record<string, unknown>;
43
+ type UpdateModalState<S extends ModalState = ModalState> = (update: S | ((prev: S) => S)) => void;
44
+ /** A read-only view of one modal on the stack. */
45
+ type ModalStackEntry = {
46
+ readonly id: number;
47
+ readonly render: ComponentType<never>;
48
+ readonly props: Readonly<Record<string, unknown>>;
49
+ readonly state: Readonly<ModalState>;
50
+ };
51
+ type ModalPortRenderProps = {
52
+ children?: ReactNode;
53
+ /** Present only when the current modal can be dismissed (it was launched with `onDismiss`). */
54
+ onBackdropClick?: (event: SyntheticEvent) => void;
55
+ /** Id of the modal currently shown. */
56
+ modalId: number;
57
+ /** Number of modals on the stack, including the one shown. */
58
+ stackSize: number;
59
+ };
60
+ type ModalPortProps = {
61
+ /** Component rendered around the current modal, typically a backdrop. */
62
+ backdrop?: ComponentType<ModalPortRenderProps>;
63
+ /** @deprecated Use `backdrop`. */
64
+ render?: ComponentType<ModalPortRenderProps>;
65
+ /** Render into this element through a portal instead of in place. */
66
+ container?: Element | DocumentFragment | null;
67
+ /** Called when the stack goes from empty to non-empty. */
68
+ onModalLaunch?: () => void;
69
+ /** Called when the stack becomes empty. */
70
+ onModalClose?: () => void;
71
+ /** Called with the new stack size whenever a modal is launched or closed. */
72
+ onStackChange?: (stackSize: number) => void;
73
+ };
74
+ type ModalContextProperties = {
75
+ readonly stack: readonly ModalStackEntry[];
76
+ launchModal: LaunchModal;
77
+ /** Replaces the state of the top modal. */
78
+ updateState: UpdateModalState;
79
+ };
80
+ /** @deprecated Type your modal's props directly. */
81
+ type ModalProps<P = Record<string, any>> = P;
82
+ /** @deprecated Use `Resolvers` or your modal's own prop types. */
83
+ type ModalResolver = (...args: any[]) => unknown;
84
+ /** @deprecated Use `Resolvers` or your modal's own prop types. */
85
+ type LaunchModalResolvers = Record<string, ModalResolver>;
86
+ /** @deprecated Use your modal's own prop types. */
87
+ type LaunchModalProps = Record<string, unknown>;
88
+
89
+ declare function ModalProvider({ children }: {
90
+ children?: ReactNode;
91
+ }): react.JSX.Element;
92
+ /** @deprecated Use `ModalProvider`. */
93
+ declare const ModalContextProvider: typeof ModalProvider;
94
+ /** Returns the `launchModal` function. Does not re-render when the stack changes. */
95
+ declare function useModal(): LaunchModal;
96
+ /** A read-only view of the modal stack, bottom first. */
97
+ declare function useModalStack(): readonly ModalStackEntry[];
98
+ /**
99
+ * State shared by the modal this hook is called in (or the top modal, when called
100
+ * outside any modal). Survives while other modals are stacked on top and is
101
+ * discarded when the modal closes. `null` when there is no such modal.
102
+ */
103
+ declare function useModalState<S extends ModalState = ModalState>(): [S | null, UpdateModalState<S>];
104
+ /**
105
+ * @deprecated Use `useModal`, `useModalStack` and `useModalState`. This hook re-renders
106
+ * on every stack change.
107
+ */
108
+ declare function useModalContext(): ModalContextProperties;
109
+
110
+ /** Renders the top modal of the stack inside `backdrop`. */
111
+ declare function ModalPort({ backdrop, render, container, onModalLaunch, onModalClose, onStackChange, }: ModalPortProps): react.JSX.Element | null;
112
+
113
+ export { type FunctionKeys, type LaunchModal, type LaunchModalProps, type LaunchModalResolvers, type LaunchOptions, type LaunchRest, type ModalContextProperties, ModalContextProvider, type ModalHandle, ModalPort, type ModalPortProps, type ModalPortRenderProps, type ModalProps, ModalProvider, type ModalResolver, type ModalStackEntry, type ModalState, type Resolvers, type RestProps, type UpdateModalState, useModal, useModalContext, useModalStack, useModalState };