@lerx/promise-modal 0.12.2 → 0.12.3
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 +5 -3
- package/dist/app/ModalManager.d.ts +10 -1
- package/dist/app/index.d.ts +2 -0
- package/dist/bootstrap/BootstrapProvider/useBootstrap.d.ts +2 -28
- package/dist/claude-hashes.json +3 -3
- package/dist/core/handle/alert.d.ts +5 -3
- package/dist/core/handle/confirm.d.ts +6 -4
- package/dist/core/handle/dispatchModal.d.ts +29 -0
- package/dist/core/handle/index.d.ts +1 -1
- package/dist/core/handle/prompt.d.ts +11 -12
- package/dist/core/handle/static.d.ts +7 -3
- package/dist/core/handle/type.d.ts +7 -3
- package/dist/core/node/ModalNode/PromptNode.d.ts +1 -1
- package/dist/helpers/hideModal.d.ts +2 -0
- package/dist/hooks/useModal.d.ts +2 -3
- package/dist/index.cjs +188 -149
- package/dist/index.mjs +188 -149
- package/dist/providers/ModalManagerContext/ModalManagerContext.d.ts +2 -2
- package/dist/providers/ModalManagerContext/useModalManagerContext.d.ts +1 -1
- package/dist/types/base.d.ts +5 -0
- package/docs/claude/skills/promise-modal-skill/knowledge/api-reference.md +3 -3
- package/docs/en/SPECIFICATION.md +3 -3
- package/docs/ko/SPECIFICATION.md +3 -3
- package/package.json +17 -6
- package/dist/helpers/subscribeAbortSignal.d.ts +0 -2
package/dist/index.mjs
CHANGED
|
@@ -2,13 +2,13 @@ import { jsx, jsxs } from 'react/jsx-runtime';
|
|
|
2
2
|
import { createContext, useContext, useMemo, forwardRef, memo, useRef, useState, useLayoutEffect, useCallback, Fragment, useEffect, useImperativeHandle } from 'react';
|
|
3
3
|
import { convertMsFromDuration } from '@winglet/common-utils/convert';
|
|
4
4
|
import { useMemorize, useSnapshot, useReference, useOnMountLayout, useHandle, useVersion, useOnMount, useOnUnmount } from '@winglet/react-utils/hook';
|
|
5
|
+
import { map } from '@winglet/common-utils/array';
|
|
5
6
|
import { polynomialHash } from '@winglet/common-utils/hash';
|
|
6
7
|
import { getRandomString, counterFactory } from '@winglet/common-utils/lib';
|
|
7
8
|
import { styleManagerFactory, destroyScope } from '@winglet/style-utils/style-manager';
|
|
8
9
|
import { compressCss, cxLite } from '@winglet/style-utils/util';
|
|
9
10
|
import { isPlainObject, isString, isFunction } from '@winglet/common-utils/filter';
|
|
10
11
|
import { createPortal } from 'react-dom';
|
|
11
|
-
import { map } from '@winglet/common-utils/array';
|
|
12
12
|
import { withErrorBoundary } from '@winglet/react-utils/hoc';
|
|
13
13
|
import { renderComponent } from '@winglet/react-utils/render';
|
|
14
14
|
|
|
@@ -28,11 +28,38 @@ class ModalManager {
|
|
|
28
28
|
return ModalManager.__anchor__ !== null;
|
|
29
29
|
}
|
|
30
30
|
static get prerender() {
|
|
31
|
-
return ModalManager.__prerenderList__;
|
|
31
|
+
return map(ModalManager.__prerenderList__, (entry) => entry.modal);
|
|
32
32
|
}
|
|
33
33
|
static set openHandler(handler) {
|
|
34
34
|
ModalManager.__openHandler__ = handler;
|
|
35
|
+
const entries = ModalManager.__prerenderList__;
|
|
35
36
|
ModalManager.__prerenderList__ = [];
|
|
37
|
+
for (const entry of entries)
|
|
38
|
+
try {
|
|
39
|
+
if (entry.dispatch)
|
|
40
|
+
entry.dispatch();
|
|
41
|
+
else
|
|
42
|
+
handler(entry.modal);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
static bindPrerender(modal, dispatch) {
|
|
48
|
+
const list = ModalManager.__prerenderList__;
|
|
49
|
+
for (let index = 0; index < list.length; index++)
|
|
50
|
+
if (list[index].modal === modal) {
|
|
51
|
+
list[index].dispatch = dispatch;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
static cancelPrerender(modal) {
|
|
56
|
+
const list = ModalManager.__prerenderList__;
|
|
57
|
+
for (let index = 0; index < list.length; index++)
|
|
58
|
+
if (list[index].modal === modal) {
|
|
59
|
+
list.splice(index, 1);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
36
63
|
}
|
|
37
64
|
static set refreshHandler(handler) {
|
|
38
65
|
ModalManager.__refreshHandler__ = handler;
|
|
@@ -53,11 +80,10 @@ class ModalManager {
|
|
|
53
80
|
static reset() {
|
|
54
81
|
ModalManager.__anchor__ = null;
|
|
55
82
|
ModalManager.__prerenderList__ = [];
|
|
56
|
-
ModalManager.__openHandler__ =
|
|
57
|
-
ModalManager.__prerenderList__.push(modal);
|
|
58
|
-
});
|
|
83
|
+
ModalManager.__openHandler__ = ModalManager.__defaultOpenHandler__;
|
|
59
84
|
ModalManager.__refreshHandler__ = undefined;
|
|
60
85
|
destroyScope(ModalManager.__scope__);
|
|
86
|
+
ModalManager.__styleManager__ = styleManagerFactory(ModalManager.__scope__);
|
|
61
87
|
}
|
|
62
88
|
static open(modal) {
|
|
63
89
|
return ModalManager.__openHandler__(modal);
|
|
@@ -69,14 +95,17 @@ ModalManager.__hash__ = polynomialHash(ModalManager.__scope__);
|
|
|
69
95
|
ModalManager.__styleManager__ = styleManagerFactory(ModalManager.__scope__);
|
|
70
96
|
ModalManager.__styleSheetDefinition__ = new Map();
|
|
71
97
|
ModalManager.__prerenderList__ = [];
|
|
72
|
-
ModalManager.
|
|
73
|
-
ModalManager.__prerenderList__.push(modal);
|
|
74
|
-
|
|
98
|
+
ModalManager.__defaultOpenHandler__ = (modal) => {
|
|
99
|
+
ModalManager.__prerenderList__.push({ modal });
|
|
100
|
+
return undefined;
|
|
101
|
+
};
|
|
102
|
+
ModalManager.__openHandler__ = ModalManager.__defaultOpenHandler__;
|
|
75
103
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
104
|
+
const DEFAULT_Z_INDEX = 1000;
|
|
105
|
+
const DEFAULT_ANIMATION_DURATION = '300ms';
|
|
106
|
+
const DEFAULT_BACKDROP_COLOR = 'rgba(0, 0, 0, 0.5)';
|
|
107
|
+
|
|
108
|
+
const hideModal = (modalNode, refresh = true) => {
|
|
80
109
|
modalNode.onHide();
|
|
81
110
|
if (refresh)
|
|
82
111
|
ModalManager.refresh();
|
|
@@ -85,85 +114,108 @@ const closeModal = (modalNode, refresh = true) => {
|
|
|
85
114
|
return setTimeout(() => modalNode.onDestroy(), modalNode.duration);
|
|
86
115
|
};
|
|
87
116
|
|
|
88
|
-
const
|
|
89
|
-
if (
|
|
90
|
-
return
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return () => signal.removeEventListener('abort', handleAbort);
|
|
117
|
+
const closeModal = (modalNode, refresh = true) => {
|
|
118
|
+
if (modalNode.visible === false)
|
|
119
|
+
return;
|
|
120
|
+
modalNode.onClose();
|
|
121
|
+
return hideModal(modalNode, refresh);
|
|
94
122
|
};
|
|
95
123
|
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
type: 'alert',
|
|
100
|
-
});
|
|
101
|
-
const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
|
|
124
|
+
const dispatchModal = (modal, { signal, mapResult, cancelResult }) => {
|
|
125
|
+
let modalNode;
|
|
126
|
+
let cancel = () => { };
|
|
102
127
|
const promiseHandler = new Promise((resolve, reject) => {
|
|
128
|
+
let cleanupAbort = null;
|
|
129
|
+
let settled = false;
|
|
130
|
+
const settle = (result) => {
|
|
131
|
+
if (settled)
|
|
132
|
+
return;
|
|
133
|
+
settled = true;
|
|
134
|
+
cleanupAbort?.();
|
|
135
|
+
cleanupAbort = null;
|
|
136
|
+
resolve(mapResult(result));
|
|
137
|
+
};
|
|
138
|
+
modal.handleResolve = settle;
|
|
139
|
+
cancel = () => {
|
|
140
|
+
if (ModalManager.cancelPrerender(modal))
|
|
141
|
+
settle(cancelResult());
|
|
142
|
+
};
|
|
143
|
+
const wire = (node) => {
|
|
144
|
+
modalNode = node;
|
|
145
|
+
if (signal === undefined)
|
|
146
|
+
return;
|
|
147
|
+
cleanupAbort?.();
|
|
148
|
+
if (signal.aborted) {
|
|
149
|
+
closeModal(node);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const handleAbort = () => closeModal(node);
|
|
153
|
+
signal.addEventListener('abort', handleAbort, { once: true });
|
|
154
|
+
cleanupAbort = () => signal.removeEventListener('abort', handleAbort);
|
|
155
|
+
};
|
|
103
156
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
157
|
+
if (signal?.aborted) {
|
|
158
|
+
settle(cancelResult());
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const node = ModalManager.open(modal);
|
|
162
|
+
if (node)
|
|
163
|
+
wire(node);
|
|
164
|
+
else {
|
|
165
|
+
if (signal) {
|
|
166
|
+
const handleAbort = () => {
|
|
167
|
+
ModalManager.cancelPrerender(modal);
|
|
168
|
+
settle(cancelResult());
|
|
169
|
+
};
|
|
170
|
+
signal.addEventListener('abort', handleAbort, { once: true });
|
|
171
|
+
cleanupAbort = () => signal.removeEventListener('abort', handleAbort);
|
|
172
|
+
}
|
|
173
|
+
ModalManager.bindPrerender(modal, () => {
|
|
174
|
+
try {
|
|
175
|
+
const flushed = ModalManager.open(modal);
|
|
176
|
+
if (flushed)
|
|
177
|
+
wire(flushed);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
settled = true;
|
|
181
|
+
cleanupAbort?.();
|
|
182
|
+
cleanupAbort = null;
|
|
183
|
+
reject(error);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
110
187
|
}
|
|
111
188
|
catch (error) {
|
|
112
|
-
|
|
113
|
-
unsubscribe?.();
|
|
189
|
+
cleanupAbort?.();
|
|
114
190
|
reject(error);
|
|
115
191
|
}
|
|
116
192
|
});
|
|
117
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
get modalNode() {
|
|
195
|
+
return modalNode;
|
|
196
|
+
},
|
|
197
|
+
promiseHandler,
|
|
198
|
+
cancel,
|
|
199
|
+
};
|
|
118
200
|
};
|
|
119
201
|
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
|
|
126
|
-
const promiseHandler = new Promise((resolve, reject) => {
|
|
127
|
-
try {
|
|
128
|
-
modalNode.handleResolve = (result) => {
|
|
129
|
-
unsubscribe?.();
|
|
130
|
-
resolve(result ?? false);
|
|
131
|
-
};
|
|
132
|
-
if (args.signal?.aborted)
|
|
133
|
-
closeModal(modalNode);
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
closeModal(modalNode);
|
|
137
|
-
unsubscribe?.();
|
|
138
|
-
reject(error);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
return { modalNode, promiseHandler };
|
|
142
|
-
};
|
|
202
|
+
const alertHandler = (args) => dispatchModal({ ...args, type: 'alert' }, {
|
|
203
|
+
signal: args.signal,
|
|
204
|
+
mapResult: () => undefined,
|
|
205
|
+
cancelResult: () => null,
|
|
206
|
+
});
|
|
143
207
|
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
};
|
|
156
|
-
if (args.signal?.aborted)
|
|
157
|
-
closeModal(modalNode);
|
|
158
|
-
}
|
|
159
|
-
catch (error) {
|
|
160
|
-
closeModal(modalNode);
|
|
161
|
-
unsubscribe?.();
|
|
162
|
-
reject(error);
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
return { modalNode, promiseHandler };
|
|
166
|
-
};
|
|
208
|
+
const confirmHandler = (args) => dispatchModal({ ...args, type: 'confirm' }, {
|
|
209
|
+
signal: args.signal,
|
|
210
|
+
mapResult: (result) => result ?? false,
|
|
211
|
+
cancelResult: () => null,
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
const promptHandler = (args) => dispatchModal({ ...args, type: 'prompt' }, {
|
|
215
|
+
signal: args.signal,
|
|
216
|
+
mapResult: (result) => result ?? null,
|
|
217
|
+
cancelResult: () => args.returnOnCancel ? (args.defaultValue ?? null) : null,
|
|
218
|
+
});
|
|
167
219
|
|
|
168
220
|
const alert = (args) => alertHandler(args).promiseHandler;
|
|
169
221
|
const confirm = (args) => confirmHandler(args).promiseHandler;
|
|
@@ -342,10 +394,6 @@ const nodeFactory = (modal) => {
|
|
|
342
394
|
throw new Error(`Unknown modal: ${modal.type}`, { modal });
|
|
343
395
|
};
|
|
344
396
|
|
|
345
|
-
const DEFAULT_Z_INDEX = 1000;
|
|
346
|
-
const DEFAULT_ANIMATION_DURATION = '300ms';
|
|
347
|
-
const DEFAULT_BACKDROP_COLOR = 'rgba(0, 0, 0, 0.5)';
|
|
348
|
-
|
|
349
397
|
const FallbackTitle = ({ children }) => {
|
|
350
398
|
return jsx("h2", { style: { margin: 'unset' }, children: children });
|
|
351
399
|
};
|
|
@@ -478,24 +526,12 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
|
|
|
478
526
|
const initiator = useRef(pathname);
|
|
479
527
|
const modalIdSequence = useRef(0);
|
|
480
528
|
const options = useConfigurationOptions();
|
|
481
|
-
const
|
|
529
|
+
const optionsRef = useReference(options);
|
|
482
530
|
useOnMountLayout(() => {
|
|
483
|
-
const { manualDestroy, closeOnBackdropClick } = options;
|
|
484
|
-
for (const data of ModalManager.prerender) {
|
|
485
|
-
const modal = nodeFactory({
|
|
486
|
-
duration,
|
|
487
|
-
manualDestroy,
|
|
488
|
-
closeOnBackdropClick,
|
|
489
|
-
...data,
|
|
490
|
-
id: modalIdSequence.current++,
|
|
491
|
-
initiator: initiator.current,
|
|
492
|
-
});
|
|
493
|
-
modalDictionary.current.set(modal.id, modal);
|
|
494
|
-
setModalIds((ids) => [...ids, modal.id]);
|
|
495
|
-
}
|
|
496
531
|
ModalManager.openHandler = (data) => {
|
|
532
|
+
const { duration, manualDestroy, closeOnBackdropClick } = optionsRef.current;
|
|
497
533
|
const modalNode = nodeFactory({
|
|
498
|
-
duration,
|
|
534
|
+
duration: convertMsFromDuration(duration),
|
|
499
535
|
manualDestroy,
|
|
500
536
|
closeOnBackdropClick,
|
|
501
537
|
...data,
|
|
@@ -530,43 +566,33 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
|
|
|
530
566
|
}
|
|
531
567
|
initiator.current = pathname;
|
|
532
568
|
}, [pathname]);
|
|
533
|
-
const getModalNodeRef = useRef((modalId) => modalDictionary.current.get(modalId));
|
|
569
|
+
const getModalNodeRef = useRef((modalId) => modalId !== undefined ? modalDictionary.current.get(modalId) : undefined);
|
|
534
570
|
const onDestroyRef = useRef((modalId) => {
|
|
535
|
-
const modal =
|
|
571
|
+
const modal = getModalNodeRef.current(modalId);
|
|
536
572
|
if (!modal)
|
|
537
573
|
return;
|
|
538
574
|
modal.onDestroy();
|
|
539
575
|
ModalManager.refresh();
|
|
540
576
|
});
|
|
541
|
-
const hideModalRef = useRef((modalId) => {
|
|
542
|
-
const modal = modalDictionary.current.get(modalId);
|
|
543
|
-
if (!modal)
|
|
544
|
-
return;
|
|
545
|
-
modal.onHide();
|
|
546
|
-
ModalManager.refresh();
|
|
547
|
-
if (modal.manualDestroy === false)
|
|
548
|
-
setTimeout(() => modal.onDestroy(), modal.duration);
|
|
549
|
-
});
|
|
550
577
|
const onChangeRef = useRef((modalId, value) => {
|
|
551
|
-
const modal =
|
|
578
|
+
const modal = getModalNodeRef.current(modalId);
|
|
552
579
|
if (!modal)
|
|
553
580
|
return;
|
|
554
581
|
if (modal.type === 'prompt')
|
|
555
582
|
modal.onChange(value);
|
|
556
583
|
});
|
|
557
584
|
const onConfirmRef = useRef((modalId) => {
|
|
558
|
-
const modal =
|
|
585
|
+
const modal = getModalNodeRef.current(modalId);
|
|
559
586
|
if (!modal)
|
|
560
587
|
return;
|
|
561
588
|
modal.onConfirm();
|
|
562
|
-
|
|
589
|
+
hideModal(modal);
|
|
563
590
|
});
|
|
564
591
|
const onCloseRef = useRef((modalId) => {
|
|
565
|
-
const modal =
|
|
592
|
+
const modal = getModalNodeRef.current(modalId);
|
|
566
593
|
if (!modal)
|
|
567
594
|
return;
|
|
568
|
-
modal
|
|
569
|
-
hideModalRef.current(modalId);
|
|
595
|
+
closeModal(modal);
|
|
570
596
|
});
|
|
571
597
|
const getModalRef = useRef((modalId) => ({
|
|
572
598
|
modal: getModalNodeRef.current(modalId),
|
|
@@ -603,21 +629,26 @@ const useUserDefinedContext = () => {
|
|
|
603
629
|
const usePathname = () => {
|
|
604
630
|
const [pathname, setPathname] = useState(window.location.pathname);
|
|
605
631
|
useLayoutEffect(() => {
|
|
632
|
+
const handleChange = () => setPathname(window.location.pathname);
|
|
633
|
+
const navigation = window.navigation;
|
|
634
|
+
if (navigation) {
|
|
635
|
+
navigation.addEventListener('currententrychange', handleChange);
|
|
636
|
+
window.addEventListener('popstate', handleChange);
|
|
637
|
+
return () => {
|
|
638
|
+
navigation.removeEventListener('currententrychange', handleChange);
|
|
639
|
+
window.removeEventListener('popstate', handleChange);
|
|
640
|
+
};
|
|
641
|
+
}
|
|
606
642
|
let requestId;
|
|
607
643
|
const checkPathname = () => {
|
|
608
|
-
if (
|
|
609
|
-
cancelAnimationFrame(requestId);
|
|
610
|
-
if (pathname !== window.location.pathname) {
|
|
644
|
+
if (pathname !== window.location.pathname)
|
|
611
645
|
setPathname(window.location.pathname);
|
|
612
|
-
|
|
613
|
-
else {
|
|
646
|
+
else
|
|
614
647
|
requestId = requestAnimationFrame(checkPathname);
|
|
615
|
-
}
|
|
616
648
|
};
|
|
617
649
|
requestId = requestAnimationFrame(checkPathname);
|
|
618
650
|
return () => {
|
|
619
|
-
|
|
620
|
-
cancelAnimationFrame(requestId);
|
|
651
|
+
cancelAnimationFrame(requestId);
|
|
621
652
|
};
|
|
622
653
|
}, [pathname]);
|
|
623
654
|
return { pathname };
|
|
@@ -723,7 +754,7 @@ const PromptInner = memo(({ modal, handlers }) => {
|
|
|
723
754
|
const handleConfirm = useCallback(() => {
|
|
724
755
|
requestAnimationFrame(onConfirm);
|
|
725
756
|
}, [onConfirm]);
|
|
726
|
-
const disabled = useMemo(() => (
|
|
757
|
+
const disabled = useMemo(() => (checkDisabled ? !!checkDisabled(value) : false), [checkDisabled, value]);
|
|
727
758
|
const { TitleComponent, SubtitleComponent, ContentComponent, FooterComponent, } = useConfigurationContext();
|
|
728
759
|
return (jsxs(Fragment, { children: [title &&
|
|
729
760
|
(isString(title) ? (jsx(TitleComponent, { context: userDefinedContext, children: title })) : (title)), subtitle &&
|
|
@@ -796,6 +827,7 @@ const style$1 = `
|
|
|
796
827
|
z-index: var(--z-index);
|
|
797
828
|
pointer-events: none;
|
|
798
829
|
overflow: hidden;
|
|
830
|
+
}
|
|
799
831
|
`;
|
|
800
832
|
ModalManager.defineStyleSheet('presenter', style$1);
|
|
801
833
|
|
|
@@ -854,8 +886,10 @@ const AnchorInner = () => {
|
|
|
854
886
|
}, [update]);
|
|
855
887
|
const options = useConfigurationOptions();
|
|
856
888
|
const dimmed = useActiveModalCount(validateDimmable, version);
|
|
857
|
-
|
|
858
|
-
|
|
889
|
+
useEffect(() => {
|
|
890
|
+
if (!dimmed)
|
|
891
|
+
reset();
|
|
892
|
+
}, [dimmed]);
|
|
859
893
|
const anchorStyle = useMemo(() => ({
|
|
860
894
|
'--z-index': options.zIndex,
|
|
861
895
|
'--transition-duration': options.duration,
|
|
@@ -896,12 +930,12 @@ const BootstrapProvider = memo(forwardRef(({ usePathname: useExternalPathname, F
|
|
|
896
930
|
initialize: handleInitialize,
|
|
897
931
|
}), [handleInitialize]);
|
|
898
932
|
useOnMount(() => {
|
|
899
|
-
if (handleRef
|
|
900
|
-
|
|
901
|
-
handleInitialize();
|
|
933
|
+
if (handleRef === null)
|
|
934
|
+
handleInitialize();
|
|
902
935
|
return () => {
|
|
903
|
-
if (anchorRef.current)
|
|
904
|
-
|
|
936
|
+
if (anchorRef.current === null)
|
|
937
|
+
return;
|
|
938
|
+
anchorRef.current.remove();
|
|
905
939
|
handleReset();
|
|
906
940
|
};
|
|
907
941
|
});
|
|
@@ -922,13 +956,15 @@ const BootstrapProvider = memo(forwardRef(({ usePathname: useExternalPathname, F
|
|
|
922
956
|
|
|
923
957
|
const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, BackgroundComponent, TitleComponent, SubtitleComponent, ContentComponent, FooterComponent, options, context, mode = 'auto', } = {}) => {
|
|
924
958
|
const usePathname$1 = useMemo(() => useExternalPathname || usePathname, [useExternalPathname]);
|
|
925
|
-
const { anchorRef, handleInitialize } = useInitialize();
|
|
959
|
+
const { anchorRef, handleInitialize, handleReset } = useInitialize();
|
|
926
960
|
useOnMount(() => {
|
|
927
961
|
if (mode === 'auto')
|
|
928
962
|
handleInitialize();
|
|
929
963
|
return () => {
|
|
930
|
-
if (anchorRef.current)
|
|
931
|
-
|
|
964
|
+
if (anchorRef.current === null)
|
|
965
|
+
return;
|
|
966
|
+
anchorRef.current.remove();
|
|
967
|
+
handleReset();
|
|
932
968
|
};
|
|
933
969
|
});
|
|
934
970
|
const initialize = useCallback((element) => {
|
|
@@ -952,28 +988,33 @@ const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, B
|
|
|
952
988
|
};
|
|
953
989
|
|
|
954
990
|
const useModal = (configuration) => {
|
|
955
|
-
const
|
|
991
|
+
const modalResultsRef = useRef([]);
|
|
956
992
|
const baseArgsRef = useRef(configuration);
|
|
957
993
|
const alertRef = useRef((args) => {
|
|
958
|
-
const
|
|
959
|
-
|
|
960
|
-
return promiseHandler;
|
|
994
|
+
const result = alertHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
|
|
995
|
+
modalResultsRef.current.push(result);
|
|
996
|
+
return result.promiseHandler;
|
|
961
997
|
});
|
|
962
998
|
const confirmRef = useRef((args) => {
|
|
963
|
-
const
|
|
964
|
-
|
|
965
|
-
return promiseHandler;
|
|
999
|
+
const result = confirmHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
|
|
1000
|
+
modalResultsRef.current.push(result);
|
|
1001
|
+
return result.promiseHandler;
|
|
966
1002
|
});
|
|
967
1003
|
const promptRef = useRef((args) => {
|
|
968
|
-
const
|
|
969
|
-
|
|
970
|
-
return promiseHandler;
|
|
1004
|
+
const result = promptHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
|
|
1005
|
+
modalResultsRef.current.push(result);
|
|
1006
|
+
return result.promiseHandler;
|
|
971
1007
|
});
|
|
972
1008
|
useOnUnmount(() => {
|
|
973
|
-
for (const
|
|
974
|
-
|
|
1009
|
+
for (const result of modalResultsRef.current) {
|
|
1010
|
+
const modalNode = result.modalNode;
|
|
1011
|
+
if (modalNode)
|
|
1012
|
+
closeModal(modalNode, false);
|
|
1013
|
+
else
|
|
1014
|
+
result.cancel();
|
|
1015
|
+
}
|
|
975
1016
|
ModalManager.refresh();
|
|
976
|
-
|
|
1017
|
+
modalResultsRef.current = [];
|
|
977
1018
|
});
|
|
978
1019
|
return {
|
|
979
1020
|
alert: alertRef.current,
|
|
@@ -1026,8 +1067,6 @@ const useModalAnimation = (visible, handler) => {
|
|
|
1026
1067
|
|
|
1027
1068
|
const useModalDuration = (modalId) => {
|
|
1028
1069
|
const globalDuration = useConfigurationDuration();
|
|
1029
|
-
if (modalId === undefined)
|
|
1030
|
-
return globalDuration;
|
|
1031
1070
|
const { modal } = useModalManager(modalId);
|
|
1032
1071
|
if (modal === undefined)
|
|
1033
1072
|
return globalDuration;
|
|
@@ -3,7 +3,7 @@ import type { ModalNode } from '../../core/index.js';
|
|
|
3
3
|
import type { ModalActions, ModalHandlersWithId } from '../../types/index.js';
|
|
4
4
|
export interface ModalManagerContextProps extends ModalHandlersWithId {
|
|
5
5
|
modalIds: ModalNode['id'][];
|
|
6
|
-
getModal: Fn<[id: ModalNode['id']], ModalActions>;
|
|
7
|
-
getModalNode: Fn<[id: ModalNode['id']], ModalNode | undefined>;
|
|
6
|
+
getModal: Fn<[id: ModalNode['id'] | undefined], ModalActions>;
|
|
7
|
+
getModalNode: Fn<[id: ModalNode['id'] | undefined], ModalNode | undefined>;
|
|
8
8
|
}
|
|
9
9
|
export declare const ModalManagerContext: import("react").Context<ModalManagerContextProps>;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { ManagedModal } from '../../types/index.js';
|
|
2
2
|
export declare const useModalManagerContext: () => import("./ModalManagerContext").ModalManagerContextProps;
|
|
3
|
-
export declare const useModalManager: (id
|
|
3
|
+
export declare const useModalManager: (id?: ManagedModal["id"]) => import("../../types").ModalActions;
|
package/dist/types/base.d.ts
CHANGED
|
@@ -11,6 +11,11 @@ export interface BaseModal<T, B> {
|
|
|
11
11
|
duration?: number;
|
|
12
12
|
manualDestroy?: boolean;
|
|
13
13
|
closeOnBackdropClick?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* @internal Promise settlement channel injected by the handle layer
|
|
16
|
+
* (dispatchModal). Travels with the modal data so prerendered modals keep
|
|
17
|
+
* their wiring; not part of the public props surface.
|
|
18
|
+
*/
|
|
14
19
|
handleResolve?: Fn<[result: T | null]>;
|
|
15
20
|
ForegroundComponent?: ForegroundComponent;
|
|
16
21
|
BackgroundComponent?: BackgroundComponent;
|
|
@@ -118,10 +118,10 @@ const userInfo = await prompt<{ name: string; age: number }>({
|
|
|
118
118
|
|------|------|------|
|
|
119
119
|
| `Input` | `(props: PromptInputProps<T>) => ReactNode` | Input component (required) |
|
|
120
120
|
| `defaultValue` | `T` | Default input value |
|
|
121
|
-
| `disabled` | `(value: T) => boolean` | Condition to disable confirm button |
|
|
122
|
-
| `returnOnCancel` | `boolean` |
|
|
121
|
+
| `disabled` | `(value: T | undefined) => boolean` | Condition to disable confirm button |
|
|
122
|
+
| `returnOnCancel` | `boolean` | If true, cancel resolves with the current input value instead of null |
|
|
123
123
|
|
|
124
|
-
**Return Value**: `Promise<T>`
|
|
124
|
+
**Return Value**: `Promise<T | null>` — resolves null on cancel
|
|
125
125
|
|
|
126
126
|
---
|
|
127
127
|
|
package/docs/en/SPECIFICATION.md
CHANGED
|
@@ -311,7 +311,7 @@ Opens a prompt modal to collect user input.
|
|
|
311
311
|
#### Signature
|
|
312
312
|
|
|
313
313
|
```typescript
|
|
314
|
-
function prompt<T, B = any>(options: PromptProps<T, B>): Promise<T>;
|
|
314
|
+
function prompt<T, B = any>(options: PromptProps<T, B>): Promise<T | null>;
|
|
315
315
|
```
|
|
316
316
|
|
|
317
317
|
#### Parameters
|
|
@@ -322,8 +322,8 @@ All options from `alert`, plus:
|
|
|
322
322
|
|--------|------|----------|-------------|
|
|
323
323
|
| `Input` | `(props: PromptInputProps<T>) => ReactNode` | Yes | Input component |
|
|
324
324
|
| `defaultValue` | `T` | No | Default value |
|
|
325
|
-
| `disabled` | `(value: T) => boolean` | No | Disable confirm |
|
|
326
|
-
| `returnOnCancel` | `boolean` | No |
|
|
325
|
+
| `disabled` | `(value: T | undefined) => boolean` | No | Disable confirm |
|
|
326
|
+
| `returnOnCancel` | `boolean` | No | Resolve current value on cancel (instead of null) |
|
|
327
327
|
|
|
328
328
|
#### PromptInputProps
|
|
329
329
|
|
package/docs/ko/SPECIFICATION.md
CHANGED
|
@@ -311,7 +311,7 @@ if (shouldDelete) {
|
|
|
311
311
|
#### 시그니처
|
|
312
312
|
|
|
313
313
|
```typescript
|
|
314
|
-
function prompt<T, B = any>(options: PromptProps<T, B>): Promise<T>;
|
|
314
|
+
function prompt<T, B = any>(options: PromptProps<T, B>): Promise<T | null>;
|
|
315
315
|
```
|
|
316
316
|
|
|
317
317
|
#### 매개변수
|
|
@@ -322,8 +322,8 @@ function prompt<T, B = any>(options: PromptProps<T, B>): Promise<T>;
|
|
|
322
322
|
|------|------|------|------|
|
|
323
323
|
| `Input` | `(props: PromptInputProps<T>) => ReactNode` | 예 | 입력 컴포넌트 |
|
|
324
324
|
| `defaultValue` | `T` | 아니오 | 기본값 |
|
|
325
|
-
| `disabled` | `(value: T) => boolean` | 아니오 | 확인 버튼 비활성화 |
|
|
326
|
-
| `returnOnCancel` | `boolean` | 아니오 | 취소 시
|
|
325
|
+
| `disabled` | `(value: T | undefined) => boolean` | 아니오 | 확인 버튼 비활성화 |
|
|
326
|
+
| `returnOnCancel` | `boolean` | 아니오 | 취소 시 null 대신 현재 입력값 반환 |
|
|
327
327
|
|
|
328
328
|
#### PromptInputProps
|
|
329
329
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lerx/promise-modal",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "Universal React modal utility that can be used outside React components with promise-based results for alert, confirm, and prompt modals",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -29,7 +29,11 @@
|
|
|
29
29
|
"name": "Vincent K. Kelvin",
|
|
30
30
|
"email": "lunox273@gmail.com"
|
|
31
31
|
},
|
|
32
|
-
"sideEffects":
|
|
32
|
+
"sideEffects": [
|
|
33
|
+
"./dist/index.mjs",
|
|
34
|
+
"./dist/index.cjs",
|
|
35
|
+
"**/style.ts"
|
|
36
|
+
],
|
|
33
37
|
"type": "module",
|
|
34
38
|
"exports": {
|
|
35
39
|
".": {
|
|
@@ -61,23 +65,30 @@
|
|
|
61
65
|
"size-limit": "size-limit",
|
|
62
66
|
"start": "yarn build && yarn storybook",
|
|
63
67
|
"storybook": "storybook dev -p 6006",
|
|
64
|
-
"test": "vitest",
|
|
68
|
+
"test": "yarn run -T vitest",
|
|
69
|
+
"test:prod": "yarn run -T vitest run --config vitest.prod.config.ts",
|
|
65
70
|
"version:major": "yarn version major",
|
|
66
71
|
"version:minor": "yarn version minor",
|
|
67
72
|
"version:patch": "yarn version patch"
|
|
68
73
|
},
|
|
69
74
|
"dependencies": {
|
|
70
|
-
"@winglet/common-utils": "^0.12.
|
|
75
|
+
"@winglet/common-utils": "^0.12.3",
|
|
71
76
|
"@winglet/react-utils": "^0.12.2",
|
|
72
77
|
"@winglet/style-utils": "^0.12.2"
|
|
73
78
|
},
|
|
74
79
|
"devDependencies": {
|
|
75
|
-
"@slats/claude-assets-sync": "^0.3.
|
|
80
|
+
"@slats/claude-assets-sync": "^0.3.4",
|
|
81
|
+
"@testing-library/dom": "^10.4.0",
|
|
82
|
+
"@testing-library/react": "^16.1.0",
|
|
76
83
|
"@types/react": "^19.0.0",
|
|
77
84
|
"@types/react-dom": "^19.0.0",
|
|
78
85
|
"antd": "^5.22.5",
|
|
79
86
|
"react": "^19.0.0",
|
|
80
|
-
"react-dom": "^19.0.0"
|
|
87
|
+
"react-dom": "^19.0.0",
|
|
88
|
+
"rollup": "^4.59.0",
|
|
89
|
+
"rollup-plugin-peer-deps-external": "^2.2.4",
|
|
90
|
+
"tsc-alias": "^1.8.16",
|
|
91
|
+
"typescript": "^5.7.2"
|
|
81
92
|
},
|
|
82
93
|
"peerDependencies": {
|
|
83
94
|
"react": ">=18 <20",
|