@e1himself/react-promise-modal 2.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Prezly
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/README.md ADDED
@@ -0,0 +1,217 @@
1
+ # React Promise Modal
2
+
3
+ [![npm version](https://badgen.net/npm/v/@e1himself/react-promise-modal)](https://www.npmjs.com/package/@e1himself/react-promise-modal)
4
+
5
+ `usePromiseModal()` is a React hook that allows you to define a modal
6
+ by providing a custom rendering function.
7
+
8
+ After defining your modal you can invoke it as a normal function,
9
+ and await for the returned promise to get the modal resolution result.
10
+
11
+ ## Usage
12
+
13
+ 1. Define your modal with `usePromiseModal()`
14
+ 2. Invoke it from your event handler using `invoke()`
15
+ 3. Wait for the modal to resolve with `await`
16
+
17
+ ```js
18
+ // 1) Define your modal
19
+ const confirmation = usePromiseModal((props) => <MyModal {...props} />);
20
+
21
+ // 2) Call it in your event handler
22
+ async function handleClick() {
23
+ // 3) Wait for the modal to resolve
24
+ if (await confirmation.invoke()) {
25
+ // TODO: Perform the operation.
26
+ }
27
+ }
28
+ ```
29
+
30
+ **Demo: https://codesandbox.io/p/sandbox/zen-jennings-4pmm3k**
31
+
32
+ ## API
33
+
34
+ **The `usePromiseModal()` hook returns the following values**:
35
+
36
+ ```tsx
37
+ const { invoke, modal, isDisplayed } = usePromiseModal(/* ... */);
38
+ ```
39
+
40
+ - `invoke` — imperatively invoke the modal, optionally passing additional call-time arguments.
41
+ Returns a promise you can _await_ to get the modal resolution value, when it's available.
42
+ Or _undefined_ if the modal has been dismissed or cancelled.
43
+
44
+ - `modal` — the rendered modal markup (`ReactElement | null`). You should always render this
45
+ value into your component subtree.
46
+
47
+ - `isDisplayed` — a boolean flag indicating if there is currently a pending modal
48
+ for this definition.
49
+
50
+ **The modal render function receives these properties**:
51
+
52
+ ```tsx
53
+ usePromiseModal(({ show, onDismiss, onSubmit }) => (
54
+ <MyModal show={show} onDismiss={onDismiss} onSubmit={onSubmit} />
55
+ ));
56
+ ```
57
+
58
+
59
+ - `show` — boolean to tell if the window is visible or not.
60
+ Used for in/out transitions.
61
+ Primarily intended to be used as *react-bootstrap* Modal `show` property.
62
+
63
+ - `onDismiss` — should be invoked when the modal is dismissed.
64
+ Always resolves the promise to `undefined`.
65
+
66
+ - `onSubmit` — should be invoked when the modal is submitted/confirmed.
67
+ Resolves to the value provided as an argument to it.
68
+ The resolve value cannot be `undefined`, because it is already reserved for dismissal.
69
+
70
+
71
+ ## Examples
72
+
73
+ ### Confirmation
74
+
75
+ You can easily implement a confirmation modal using `usePromiseModal()`:
76
+
77
+ ```jsx
78
+ import { usePromiseModal } from '@e1himself/react-promise-modal';
79
+
80
+ function MyApp() {
81
+ const confirmation = usePromiseModal(({ show, onSubmit, onDismiss }) => {
82
+ // Use any modal implementation you want
83
+ <MyConfirmationModal title="⚠️ Are you sure?" show={show} onConfirm={() => onSubmit(true)} onDismiss={onDismiss} />
84
+ });
85
+
86
+ async function handleDeleteAccount() {
87
+ if (await confirmation.invoke()) {
88
+ console.log('Confirmed');
89
+ } else {
90
+ console.log('Cancelled');
91
+ }
92
+ }
93
+
94
+ return (
95
+ <div>
96
+ <button onClick={handleDeleteAccount}>Delete account</button>
97
+ {confirmation.modal}
98
+ </div>
99
+ )
100
+ }
101
+ ```
102
+
103
+ ### Alert
104
+
105
+ Alert is basically the same as confirmation, except there is no difference whether
106
+ it is submitted or dismissed -- the modal has single action anyway.
107
+ So we only need `onDismiss`:
108
+
109
+ ```jsx
110
+ import { usePromiseModal } from '@e1himself/react-promise-modal';
111
+
112
+ function MyApp() {
113
+ const alert = usePromiseModal(({ show, onDismiss }) => {
114
+ // Use any modal implementation you want
115
+ <MyAlertModal title="✔ Account deleted!" show={show} onDismiss={onDismiss} />
116
+ });
117
+
118
+ async function handleDeleteAccount() {
119
+ await api.deleteAccount();
120
+ await alert.invoke();
121
+ }
122
+
123
+ return (
124
+ <div>
125
+ <button onClick={handleDeleteAccount}>Delete account</button>
126
+ {alert.modal}
127
+ </div>
128
+ )
129
+ }
130
+
131
+ ```
132
+
133
+ ### Prompt User Input
134
+
135
+ For data prompts all you need is to resolve the promise by submitting the value to `onSubmit`:
136
+ either a scalar, or more complex shapes wrapped into an object:
137
+
138
+ ```tsx
139
+ import { usePromiseModal } from '@e1himself/react-promise-modal';
140
+
141
+ function MyApp() {
142
+ const prompt = usePromiseModal<string, { title: string }>(
143
+ (props) => <MyFilenamePromptModal {...props} />,
144
+ );
145
+
146
+ async function handleCreateFile() {
147
+ const filename = await prompt.invoke({ title: 'Please enter filename:' });
148
+ if (!filename) {
149
+ console.error('Filename is required');
150
+ return;
151
+ }
152
+ await api.createFile(filename);
153
+ }
154
+
155
+ return (
156
+ <div>
157
+ <button onClick={handleCreateFile}>Create new file</button>
158
+ {prompt.modal}
159
+ </div>
160
+ )
161
+ }
162
+
163
+ interface Props {
164
+ title: string;
165
+ show: boolean;
166
+ onSubmit: (filename: string) => void;
167
+ onDismiss: () => void;
168
+ }
169
+
170
+ function MyFilenamePromptModal({ title, show, onSubmit, onDismiss }: Props) {
171
+ const [filename, setFilename] = useState("Untitled.txt");
172
+
173
+ return (
174
+ // Use any modal implementation you want
175
+ <Modal show={show} onHide={onDismiss}>
176
+ <form onSubmit={() => onSubmit(filename)}>
177
+ <p>{title}</p>
178
+ <input autoFocus value={filename} onChange={(event) => setFilename(event.target.value)} />
179
+
180
+ <button variant="secondary" onClick={onDismiss}>Cancel</button>
181
+ <button variant="primary" type="submit">Confirm</button>
182
+ </form>
183
+ </Modal>
184
+ );
185
+ }
186
+ ```
187
+
188
+ ## Additional Invoke-Time Arguments
189
+
190
+ In addition to the three standard properties your render callback will always receive when rendered,
191
+ you can also pass extra call-time properties. Declare them with the second generic type parameter of `usePromiseModal()`,
192
+ and then pass to the `invoke()` method:
193
+
194
+ ```tsx
195
+ import { usePromiseModal } from "@e1himself/react-promise-modal";
196
+
197
+ const failureFeedback = usePromiseModal<undefined, { status: Status, failures: OperationFailure[] }>(
198
+ ({ status, failures, show, onSubmit, onDismiss }) => (
199
+ <FailureModal status={status} failures={failures} show={show} onSubmit={onSubmit} onDismiss={onDismiss} />
200
+ ),
201
+ );
202
+
203
+ // Invocation of the modal now requires these additional properties:
204
+ async function handleFlakyOperation() {
205
+ const { status, failures } = await api.flakyOperation();
206
+ if (status !== 'success') {
207
+ await failureFeedback.invoke({ status, failures }); // Note: here we pass additional parameters call-time
208
+ }
209
+ }
210
+ ```
211
+
212
+ ------------------
213
+
214
+ # Credits
215
+
216
+ Brought to you with :metal: by [Ivan Voskoboinyk](https://voskoboinyk.com/?utm_source=github&utm_campaign=react-promise-modal)
217
+ while working in [Prezly](https://www.prezly.com/?utm_source=github&utm_campaign=react-promise-modal).
@@ -0,0 +1,22 @@
1
+ import * as React from "react";
2
+ export type Milliseconds = number;
3
+ type Props = {
4
+ isOpen: boolean;
5
+ onClosed?: () => void;
6
+ transitionDuration: Milliseconds;
7
+ render: (props: RenderProps) => React.ReactElement | null;
8
+ };
9
+ type RenderProps = {
10
+ isOpen: boolean;
11
+ stage: `${Stage.CLOSED | Stage.OPEN | Stage.OPENING | Stage.CLOSING}`;
12
+ onClose: () => void;
13
+ };
14
+ export declare enum Stage {
15
+ UNMOUNTED = "unmounted",
16
+ OPEN = "open",
17
+ OPENING = "opening",
18
+ CLOSING = "closing",
19
+ CLOSED = "closed"
20
+ }
21
+ export declare function ModalTransitions({ isOpen: shouldOpen, onClosed, transitionDuration, render }: Props): React.ReactElement<any, string | React.JSXElementConstructor<any>> | null;
22
+ export {};
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+
3
+ var _react = require("react");
4
+ var React = _react;
5
+ var _lib = require("./lib");
6
+ var delay = _lib.delay;
7
+ var noop = _lib.noop;
8
+ var _useIsMounted = require("./useIsMounted");
9
+ var useIsMounted = _useIsMounted.useIsMounted;
10
+ var _useLatest = require("./useLatest");
11
+ var useLatest = _useLatest.useLatest;
12
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
13
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
14
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
15
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
16
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
17
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
18
+ var Stage = /*#__PURE__*/exports.Stage = function (Stage) {
19
+ Stage["UNMOUNTED"] = "unmounted";
20
+ Stage["OPEN"] = "open";
21
+ Stage["OPENING"] = "opening";
22
+ Stage["CLOSING"] = "closing";
23
+ Stage["CLOSED"] = "closed";
24
+ return Stage;
25
+ }({});
26
+ function ModalTransitions(_ref) {
27
+ var shouldOpen = _ref.isOpen,
28
+ onClosed = _ref.onClosed,
29
+ transitionDuration = _ref.transitionDuration,
30
+ render = _ref.render;
31
+ var isMounted = useIsMounted();
32
+ var _React$useState = React.useState(Stage.UNMOUNTED),
33
+ _React$useState2 = _slicedToArray(_React$useState, 2),
34
+ stage = _React$useState2[0],
35
+ setStage = _React$useState2[1];
36
+ var refs = useLatest({
37
+ stage: stage,
38
+ onClosed: onClosed,
39
+ transitionDuration: transitionDuration
40
+ });
41
+ var onOpen = React.useCallback(function () {
42
+ if (!isMounted()) {
43
+ return noop;
44
+ }
45
+ if (refs.current.stage === Stage.OPENING || refs.current.stage === Stage.OPEN) {
46
+ return noop; // Nothing to do
47
+ }
48
+ var cancel = false;
49
+
50
+ // First, render it closed
51
+ setStage(Stage.CLOSED);
52
+
53
+ // Then, immediately start "opening" sequence
54
+ delay(0).then(function () {
55
+ return setStage(Stage.OPENING);
56
+ }).then(function () {
57
+ return delay(refs.current.transitionDuration);
58
+ }) // Wait another `transitionDelay`
59
+ .then(function () {
60
+ if (cancel || !isMounted()) {
61
+ return;
62
+ }
63
+ // Mark it open.
64
+ setStage(Stage.OPEN);
65
+ });
66
+ return function () {
67
+ cancel = true;
68
+ };
69
+ }, []);
70
+ var onClose = React.useCallback(function () {
71
+ if (!isMounted()) {
72
+ return noop;
73
+ }
74
+ if (refs.current.stage === Stage.CLOSING || refs.current.stage === Stage.CLOSED) {
75
+ return noop; // Nothing to do
76
+ }
77
+ var cancel = false;
78
+ setStage(Stage.CLOSING);
79
+ delay(refs.current.transitionDuration).then(function () {
80
+ var _refs$current$onClose, _refs$current;
81
+ if (cancel || !isMounted()) {
82
+ return;
83
+ }
84
+ setStage(Stage.CLOSED);
85
+ (_refs$current$onClose = (_refs$current = refs.current).onClosed) === null || _refs$current$onClose === void 0 || _refs$current$onClose.call(_refs$current);
86
+ }).then(function () {
87
+ return delay(0);
88
+ }).then(function () {
89
+ return setStage(Stage.UNMOUNTED);
90
+ });
91
+ return function () {
92
+ cancel = true;
93
+ };
94
+ }, []);
95
+ React.useEffect(function () {
96
+ if (shouldOpen) {
97
+ return onOpen();
98
+ } else {
99
+ return onClose();
100
+ }
101
+ }, [open]);
102
+ if (stage === Stage.UNMOUNTED) {
103
+ // Do not render anything in "closed" stage
104
+ return null;
105
+ }
106
+ return render({
107
+ stage: stage,
108
+ isOpen: stage === Stage.OPEN || stage === Stage.OPENING,
109
+ onClose: onClose
110
+ });
111
+ }
112
+ exports.ModalTransitions = ModalTransitions;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * This kinda follows the Jquery.Deferred type idea.
3
+ * @see https://api.jquery.com/jQuery.Deferred/
4
+ *
5
+ * Deferred is mostly a promise, but you can resolve or reject it from outside.
6
+ * This 'Deferred' here is a much more limited version of the JQuery Deferred.
7
+ */
8
+ export type Deferred<T> = {
9
+ promise: Promise<T>;
10
+ resolve: (value: T) => void;
11
+ reject: (reason: unknown) => void;
12
+ isSettled(): boolean;
13
+ };
14
+ export declare function createDeferred<T>(): Deferred<T>;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+
3
+ var _lib = require("./lib");
4
+ var noop = _lib.noop;
5
+ /**
6
+ * This kinda follows the Jquery.Deferred type idea.
7
+ * @see https://api.jquery.com/jQuery.Deferred/
8
+ *
9
+ * Deferred is mostly a promise, but you can resolve or reject it from outside.
10
+ * This 'Deferred' here is a much more limited version of the JQuery Deferred.
11
+ */
12
+
13
+ function createDeferred() {
14
+ var isSettled = false;
15
+ var resolvePromise = noop;
16
+ var rejectPromise = noop;
17
+ var promise = new Promise(function (resolve, reject) {
18
+ resolvePromise = resolve;
19
+ rejectPromise = reject;
20
+ });
21
+ return {
22
+ promise: promise,
23
+ resolve: function resolve(value) {
24
+ isSettled = true;
25
+ resolvePromise(value);
26
+ },
27
+ reject: function reject(reason) {
28
+ isSettled = true;
29
+ rejectPromise(reason);
30
+ },
31
+ isSettled: function (_isSettled) {
32
+ function isSettled() {
33
+ return _isSettled.apply(this, arguments);
34
+ }
35
+ isSettled.toString = function () {
36
+ return _isSettled.toString();
37
+ };
38
+ return isSettled;
39
+ }(function () {
40
+ return isSettled;
41
+ })
42
+ };
43
+ }
44
+ exports.createDeferred = createDeferred;
@@ -0,0 +1 @@
1
+ export { usePromiseModal } from "./usePromiseModal";
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ "use strict";
2
+
3
+ var _usePromiseModal = require("./usePromiseModal");
4
+ exports.usePromiseModal = _usePromiseModal.usePromiseModal;
package/dist/lib.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type * as React from "react";
2
+ export declare function noop(): void;
3
+ export declare function generateId(): string;
4
+ export declare function delay(ms: number): Promise<void>;
5
+ export declare function isEvent(value: unknown): value is Event;
6
+ export declare function isSyntheticEvent(value: unknown | Partial<React.SyntheticEvent>): value is React.SyntheticEvent;
package/dist/lib.js ADDED
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ function noop() {
5
+ // Nothing
6
+ }
7
+ exports.noop = noop;
8
+ function generateId() {
9
+ return "".concat(new Date().getTime(), "-").concat(Math.random());
10
+ }
11
+ exports.generateId = generateId;
12
+ function delay(ms) {
13
+ return new Promise(function (resolve) {
14
+ setTimeout(resolve, ms);
15
+ });
16
+ }
17
+ exports.delay = delay;
18
+ function isEvent(value) {
19
+ return value instanceof Event;
20
+ }
21
+ exports.isEvent = isEvent;
22
+ function isSyntheticEvent(value) {
23
+ return _typeof(value) === "object" && value !== null && "nativeEvent" in value && "persist" in value && "stopPropagation" in value && "preventDefault" in value;
24
+ }
25
+ exports.isSyntheticEvent = isSyntheticEvent;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @see https://streamich.github.io/react-use/?path=/story/lifecycle-usemountedstate--docs
3
+ *
4
+ * Lifecycle hook providing ability to check component's mount status.
5
+ * Returns a function that will return true if component mounted and false otherwise.
6
+ */
7
+ export declare function useIsMounted(): () => boolean;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+
3
+ var _react = require("react");
4
+ var React = _react;
5
+ /**
6
+ * @see https://streamich.github.io/react-use/?path=/story/lifecycle-usemountedstate--docs
7
+ *
8
+ * Lifecycle hook providing ability to check component's mount status.
9
+ * Returns a function that will return true if component mounted and false otherwise.
10
+ */
11
+ function useIsMounted() {
12
+ var isMounted = React.useRef(false);
13
+ React.useEffect(function () {
14
+ isMounted.current = true;
15
+ return function () {
16
+ isMounted.current = false;
17
+ };
18
+ }, []);
19
+ return React.useCallback(function () {
20
+ return isMounted.current;
21
+ }, []);
22
+ }
23
+ exports.useIsMounted = useIsMounted;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @see https://streamich.github.io/react-use/?path=/story/state-uselatest--docs
3
+ *
4
+ * React state hook that returns the latest state as described in the React hooks FAQ.
5
+ * @see https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function
6
+ *
7
+ * This is mostly useful to get access to the latest value of some props
8
+ * or state inside an asynchronous callback, instead of that value
9
+ * at the time the callback was created from.
10
+ */
11
+ export declare function useLatest<T>(value: T): {
12
+ readonly current: T;
13
+ };
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+
3
+ var _react = require("react");
4
+ var React = _react;
5
+ /**
6
+ * @see https://streamich.github.io/react-use/?path=/story/state-uselatest--docs
7
+ *
8
+ * React state hook that returns the latest state as described in the React hooks FAQ.
9
+ * @see https://reactjs.org/docs/hooks-faq.html#why-am-i-seeing-stale-props-or-state-inside-my-function
10
+ *
11
+ * This is mostly useful to get access to the latest value of some props
12
+ * or state inside an asynchronous callback, instead of that value
13
+ * at the time the callback was created from.
14
+ */
15
+ function useLatest(value) {
16
+ var ref = React.useRef(value);
17
+ ref.current = value;
18
+ return ref;
19
+ }
20
+ exports.useLatest = useLatest;
@@ -0,0 +1,25 @@
1
+ import * as React from "react";
2
+ import { type Milliseconds } from "./ModalTransitions";
3
+ interface Options {
4
+ concurrencyMode?: ConcurrencyMode;
5
+ transitionDuration?: Milliseconds;
6
+ }
7
+ type ConcurrencyMode = "stack" | "replace" | "ignore";
8
+ type RenderFunction<P> = (props: P) => React.ReactElement | null | undefined;
9
+ interface RenderProps<T> {
10
+ show: boolean;
11
+ stage: "closed" | "opening" | "open" | "closing";
12
+ onDismiss: () => void;
13
+ onSubmit: (value: Exclude<T, undefined>) => void;
14
+ }
15
+ export declare function usePromiseModal<T>(render: RenderFunction<RenderProps<T>>, options?: Options): {
16
+ modal: React.ReactElement;
17
+ invoke: () => Promise<T | undefined>;
18
+ isDisplayed: boolean;
19
+ };
20
+ export declare function usePromiseModal<T, Args>(render: RenderFunction<RenderProps<T> & Omit<Args, keyof RenderProps<T>>>, options?: Options): {
21
+ modal: React.ReactElement;
22
+ invoke: (args: Args) => Promise<T | undefined>;
23
+ isDisplayed: boolean;
24
+ };
25
+ export {};
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+
3
+ function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
4
+ var _react = require("react");
5
+ var React = _react;
6
+ var _deferred = require("./deferred");
7
+ var createDeferred = _deferred.createDeferred;
8
+ var _lib = require("./lib");
9
+ var generateId = _lib.generateId;
10
+ var isEvent = _lib.isEvent;
11
+ var isSyntheticEvent = _lib.isSyntheticEvent;
12
+ var _ModalTransitions = require("./ModalTransitions");
13
+ var ModalTransitions = _ModalTransitions.ModalTransitions;
14
+ var _useIsMounted = require("./useIsMounted");
15
+ var useIsMounted = _useIsMounted.useIsMounted;
16
+ var _useLatest = require("./useLatest");
17
+ var useLatest = _useLatest.useLatest;
18
+ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
19
+ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
20
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
21
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
22
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
23
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
24
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
25
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
26
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
27
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
28
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
29
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
30
+ function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
31
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
32
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
33
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
34
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
35
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
36
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
37
+ var DEFAULT_CONCURRENCY_MODE = "replace";
38
+ var DEFAULT_TRANSITION_DURATION = 300;
39
+ function usePromiseModal(render) {
40
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
41
+ _ref$concurrencyMode = _ref.concurrencyMode,
42
+ concurrencyMode = _ref$concurrencyMode === void 0 ? DEFAULT_CONCURRENCY_MODE : _ref$concurrencyMode,
43
+ _ref$transitionDurati = _ref.transitionDuration,
44
+ transitionDuration = _ref$transitionDurati === void 0 ? DEFAULT_TRANSITION_DURATION : _ref$transitionDurati;
45
+ var isMounted = useIsMounted();
46
+ var _React$useState = React.useState([]),
47
+ _React$useState2 = _slicedToArray(_React$useState, 2),
48
+ invoked = _React$useState2[0],
49
+ setInvoked = _React$useState2[1];
50
+ var refs = useLatest({
51
+ invoked: invoked
52
+ });
53
+ var isDisplayed = invoked.length > 0;
54
+ var modal = /*#__PURE__*/React.createElement(React.Fragment, null, invoked.map(function (invocation, index) {
55
+ var id = invocation.id,
56
+ args = invocation.args,
57
+ deferred = invocation.deferred;
58
+ return /*#__PURE__*/React.createElement(ModalTransitions, {
59
+ key: "react-promise-modal-".concat(id)
60
+ /* only show the latest invoked modal */,
61
+ isOpen: index === invoked.length - 1,
62
+ onClosed: function onClosed() {
63
+ if (isMounted()) {
64
+ // Remove the invocation when the modal is closed
65
+ setInvoked(function (prev) {
66
+ return prev.filter(function (x) {
67
+ return x.id !== invocation.id;
68
+ });
69
+ });
70
+ }
71
+ },
72
+ transitionDuration: transitionDuration,
73
+ render: function (_render2) {
74
+ function render(_x) {
75
+ return _render2.apply(this, arguments);
76
+ }
77
+ render.toString = function () {
78
+ return _render2.toString();
79
+ };
80
+ return render;
81
+ }(function (_ref2) {
82
+ var _render;
83
+ var isOpen = _ref2.isOpen,
84
+ stage = _ref2.stage,
85
+ onClose = _ref2.onClose;
86
+ return (_render = render(_objectSpread(_objectSpread({}, args), {}, {
87
+ stage: stage,
88
+ show: isOpen,
89
+ onDismiss: function onDismiss() {
90
+ deferred.resolve(undefined);
91
+ onClose();
92
+ },
93
+ onSubmit: function onSubmit(value) {
94
+ deferred.resolve(value);
95
+ onClose();
96
+ }
97
+ }))) !== null && _render !== void 0 ? _render : null;
98
+ })
99
+ });
100
+ }));
101
+ var invoke = React.useCallback(/*#__PURE__*/function () {
102
+ var _ref3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(incomingArgs) {
103
+ var id, deferred, args, invocation, _t;
104
+ return _regenerator().w(function (_context) {
105
+ while (1) switch (_context.n) {
106
+ case 0:
107
+ id = generateId();
108
+ deferred = createDeferred();
109
+ args =
110
+ // Note: Do not spread the incoming args object, if it's a DOM Event or a React SyntheticEvent,
111
+ // which is a super common scenario. Modal `invoke()` functions are passed to onClick handlers all the time.
112
+ // Also, an event should never be used as a props object to spread anyway.
113
+ // @see https://github.com/prezly/prezly/pull/12020#discussion_r1101325932
114
+ isEvent(incomingArgs) || isSyntheticEvent(incomingArgs) ? {} : incomingArgs;
115
+ invocation = {
116
+ id: id,
117
+ args: args,
118
+ deferred: deferred
119
+ };
120
+ _t = concurrencyMode;
121
+ _context.n = _t === "ignore" ? 1 : _t === "stack" ? 2 : _t === "replace" ? 3 : 3;
122
+ break;
123
+ case 1:
124
+ setInvoked(function (prev) {
125
+ if (prev.length > 0) {
126
+ // auto-dismiss the current call
127
+ deferred.resolve(undefined);
128
+ return prev;
129
+ }
130
+ return [invocation];
131
+ });
132
+ return _context.a(3, 4);
133
+ case 2:
134
+ setInvoked(function (prev) {
135
+ return [].concat(_toConsumableArray(prev), [invocation]);
136
+ });
137
+ return _context.a(3, 4);
138
+ case 3:
139
+ setInvoked(function (prev) {
140
+ // auto-dismiss all previous calls
141
+ prev.forEach(function (_ref4) {
142
+ var deferred = _ref4.deferred;
143
+ return deferred.resolve(undefined);
144
+ });
145
+ return [invocation];
146
+ });
147
+ return _context.a(3, 4);
148
+ case 4:
149
+ return _context.a(2, deferred.promise);
150
+ }
151
+ }, _callee);
152
+ }));
153
+ return function (_x2) {
154
+ return _ref3.apply(this, arguments);
155
+ };
156
+ }(), [concurrencyMode]);
157
+ React.useEffect(function () {
158
+ // Auto-dismiss all open modals on unmount
159
+ return function () {
160
+ refs.current.invoked.forEach(function (_ref5) {
161
+ var deferred = _ref5.deferred;
162
+ if (!deferred.isSettled()) {
163
+ deferred.resolve(undefined);
164
+ }
165
+ });
166
+ };
167
+ }, []);
168
+ return {
169
+ modal: modal,
170
+ invoke: invoke,
171
+ isDisplayed: isDisplayed
172
+ };
173
+ }
174
+ exports.usePromiseModal = usePromiseModal;
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@e1himself/react-promise-modal",
3
+ "version": "2.0.2",
4
+ "description": "The proper (and easy) way of doing modals in React. With Promises.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "README.md",
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "clean": "rimraf package-lock.json pnpm-lock.yaml yarn.lock node_modules/ dist/",
13
+ "build": "npm run build:js && npm run build:types",
14
+ "build:js": "babel src/* -x .ts,.tsx -d dist/",
15
+ "build:types": "tsc --project .",
16
+ "test": "npm run test:ts && npm run test:types && npm run test:build",
17
+ "test:types": "tsc --project ./tsconfig.test.json",
18
+ "test:ts": "tsc --noEmit --lib es2018,dom --jsx react --skipLibCheck ./test/typescript.test.tsx",
19
+ "test:build": "node test/commonjs.test.js",
20
+ "publish": "np"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/e1himself/react-promise-modal.git"
25
+ },
26
+ "keywords": [
27
+ "react",
28
+ "modal",
29
+ "promise",
30
+ "async",
31
+ "await",
32
+ "confirmation"
33
+ ],
34
+ "author": "Ivan Voskboinyk <ivan@voskoboinyk.com>",
35
+ "license": "MIT",
36
+ "bugs": {
37
+ "url": "https://github.com/e1himself/react-promise-modal/issues"
38
+ },
39
+ "homepage": "https://github.com/e1himself/react-promise-modal#readme",
40
+ "dependencies": {
41
+ "@types/react": "^18.0.0",
42
+ "@types/react-dom": "^18.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@babel/cli": "^7.26.4",
46
+ "@babel/core": "^7.26.0",
47
+ "@babel/preset-env": "^7.26.0",
48
+ "@babel/preset-react": "^7.26.3",
49
+ "@babel/preset-typescript": "^7.26.0",
50
+ "babel-plugin-transform-es2015-modules-simple-commonjs": "^0.3.0",
51
+ "np": "^12.0.1",
52
+ "prettier": "^3.4.2",
53
+ "react": "^18.0.0",
54
+ "react-dom": "^18.0.0",
55
+ "rimraf": "^6.0.1",
56
+ "typescript": "^5.7.2"
57
+ },
58
+ "peerDependencies": {
59
+ "react": ">=18",
60
+ "react-dom": ">=18"
61
+ }
62
+ }