@lerx/promise-modal 0.12.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -4,13 +4,13 @@ const jsxRuntime = require('react/jsx-runtime');
4
4
  const react = require('react');
5
5
  const convert = require('@winglet/common-utils/convert');
6
6
  const hook = require('@winglet/react-utils/hook');
7
+ const array = require('@winglet/common-utils/array');
7
8
  const hash = require('@winglet/common-utils/hash');
8
9
  const lib = require('@winglet/common-utils/lib');
9
10
  const styleManager = require('@winglet/style-utils/style-manager');
10
11
  const util = require('@winglet/style-utils/util');
11
12
  const filter = require('@winglet/common-utils/filter');
12
13
  const reactDom = require('react-dom');
13
- const array = require('@winglet/common-utils/array');
14
14
  const hoc = require('@winglet/react-utils/hoc');
15
15
  const render = require('@winglet/react-utils/render');
16
16
 
@@ -30,11 +30,38 @@ class ModalManager {
30
30
  return ModalManager.__anchor__ !== null;
31
31
  }
32
32
  static get prerender() {
33
- return ModalManager.__prerenderList__;
33
+ return array.map(ModalManager.__prerenderList__, (entry) => entry.modal);
34
34
  }
35
35
  static set openHandler(handler) {
36
36
  ModalManager.__openHandler__ = handler;
37
+ const entries = ModalManager.__prerenderList__;
37
38
  ModalManager.__prerenderList__ = [];
39
+ for (const entry of entries)
40
+ try {
41
+ if (entry.dispatch)
42
+ entry.dispatch();
43
+ else
44
+ handler(entry.modal);
45
+ }
46
+ catch {
47
+ }
48
+ }
49
+ static bindPrerender(modal, dispatch) {
50
+ const list = ModalManager.__prerenderList__;
51
+ for (let index = 0; index < list.length; index++)
52
+ if (list[index].modal === modal) {
53
+ list[index].dispatch = dispatch;
54
+ return;
55
+ }
56
+ }
57
+ static cancelPrerender(modal) {
58
+ const list = ModalManager.__prerenderList__;
59
+ for (let index = 0; index < list.length; index++)
60
+ if (list[index].modal === modal) {
61
+ list.splice(index, 1);
62
+ return true;
63
+ }
64
+ return false;
38
65
  }
39
66
  static set refreshHandler(handler) {
40
67
  ModalManager.__refreshHandler__ = handler;
@@ -55,11 +82,10 @@ class ModalManager {
55
82
  static reset() {
56
83
  ModalManager.__anchor__ = null;
57
84
  ModalManager.__prerenderList__ = [];
58
- ModalManager.__openHandler__ = ((modal) => {
59
- ModalManager.__prerenderList__.push(modal);
60
- });
85
+ ModalManager.__openHandler__ = ModalManager.__defaultOpenHandler__;
61
86
  ModalManager.__refreshHandler__ = undefined;
62
87
  styleManager.destroyScope(ModalManager.__scope__);
88
+ ModalManager.__styleManager__ = styleManager.styleManagerFactory(ModalManager.__scope__);
63
89
  }
64
90
  static open(modal) {
65
91
  return ModalManager.__openHandler__(modal);
@@ -71,14 +97,17 @@ ModalManager.__hash__ = hash.polynomialHash(ModalManager.__scope__);
71
97
  ModalManager.__styleManager__ = styleManager.styleManagerFactory(ModalManager.__scope__);
72
98
  ModalManager.__styleSheetDefinition__ = new Map();
73
99
  ModalManager.__prerenderList__ = [];
74
- ModalManager.__openHandler__ = ((modal) => {
75
- ModalManager.__prerenderList__.push(modal);
76
- });
100
+ ModalManager.__defaultOpenHandler__ = (modal) => {
101
+ ModalManager.__prerenderList__.push({ modal });
102
+ return undefined;
103
+ };
104
+ ModalManager.__openHandler__ = ModalManager.__defaultOpenHandler__;
77
105
 
78
- const closeModal = (modalNode, refresh = true) => {
79
- if (modalNode.visible === false)
80
- return;
81
- modalNode.onClose();
106
+ const DEFAULT_Z_INDEX = 1000;
107
+ const DEFAULT_ANIMATION_DURATION = '300ms';
108
+ const DEFAULT_BACKDROP_COLOR = 'rgba(0, 0, 0, 0.5)';
109
+
110
+ const hideModal = (modalNode, refresh = true) => {
82
111
  modalNode.onHide();
83
112
  if (refresh)
84
113
  ModalManager.refresh();
@@ -87,85 +116,108 @@ const closeModal = (modalNode, refresh = true) => {
87
116
  return setTimeout(() => modalNode.onDestroy(), modalNode.duration);
88
117
  };
89
118
 
90
- const subscribeAbortSignal = (modalNode, signal) => {
91
- if (signal === undefined)
92
- return null;
93
- const handleAbort = () => closeModal(modalNode);
94
- signal.addEventListener('abort', handleAbort, { once: true });
95
- return () => signal.removeEventListener('abort', handleAbort);
119
+ const closeModal = (modalNode, refresh = true) => {
120
+ if (modalNode.visible === false)
121
+ return;
122
+ modalNode.onClose();
123
+ return hideModal(modalNode, refresh);
96
124
  };
97
125
 
98
- const alertHandler = (args) => {
99
- const modalNode = ModalManager.open({
100
- ...args,
101
- type: 'alert',
102
- });
103
- const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
126
+ const dispatchModal = (modal, { signal, mapResult, cancelResult }) => {
127
+ let modalNode;
128
+ let cancel = () => { };
104
129
  const promiseHandler = new Promise((resolve, reject) => {
130
+ let cleanupAbort = null;
131
+ let settled = false;
132
+ const settle = (result) => {
133
+ if (settled)
134
+ return;
135
+ settled = true;
136
+ cleanupAbort?.();
137
+ cleanupAbort = null;
138
+ resolve(mapResult(result));
139
+ };
140
+ modal.handleResolve = settle;
141
+ cancel = () => {
142
+ if (ModalManager.cancelPrerender(modal))
143
+ settle(cancelResult());
144
+ };
145
+ const wire = (node) => {
146
+ modalNode = node;
147
+ if (signal === undefined)
148
+ return;
149
+ cleanupAbort?.();
150
+ if (signal.aborted) {
151
+ closeModal(node);
152
+ return;
153
+ }
154
+ const handleAbort = () => closeModal(node);
155
+ signal.addEventListener('abort', handleAbort, { once: true });
156
+ cleanupAbort = () => signal.removeEventListener('abort', handleAbort);
157
+ };
105
158
  try {
106
- modalNode.handleResolve = () => {
107
- unsubscribe?.();
108
- resolve();
109
- };
110
- if (args.signal?.aborted)
111
- closeModal(modalNode);
159
+ if (signal?.aborted) {
160
+ settle(cancelResult());
161
+ return;
162
+ }
163
+ const node = ModalManager.open(modal);
164
+ if (node)
165
+ wire(node);
166
+ else {
167
+ if (signal) {
168
+ const handleAbort = () => {
169
+ ModalManager.cancelPrerender(modal);
170
+ settle(cancelResult());
171
+ };
172
+ signal.addEventListener('abort', handleAbort, { once: true });
173
+ cleanupAbort = () => signal.removeEventListener('abort', handleAbort);
174
+ }
175
+ ModalManager.bindPrerender(modal, () => {
176
+ try {
177
+ const flushed = ModalManager.open(modal);
178
+ if (flushed)
179
+ wire(flushed);
180
+ }
181
+ catch (error) {
182
+ settled = true;
183
+ cleanupAbort?.();
184
+ cleanupAbort = null;
185
+ reject(error);
186
+ }
187
+ });
188
+ }
112
189
  }
113
190
  catch (error) {
114
- closeModal(modalNode);
115
- unsubscribe?.();
191
+ cleanupAbort?.();
116
192
  reject(error);
117
193
  }
118
194
  });
119
- return { modalNode, promiseHandler };
195
+ return {
196
+ get modalNode() {
197
+ return modalNode;
198
+ },
199
+ promiseHandler,
200
+ cancel,
201
+ };
120
202
  };
121
203
 
122
- const confirmHandler = (args) => {
123
- const modalNode = ModalManager.open({
124
- ...args,
125
- type: 'confirm',
126
- });
127
- const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
128
- const promiseHandler = new Promise((resolve, reject) => {
129
- try {
130
- modalNode.handleResolve = (result) => {
131
- unsubscribe?.();
132
- resolve(result ?? false);
133
- };
134
- if (args.signal?.aborted)
135
- closeModal(modalNode);
136
- }
137
- catch (error) {
138
- closeModal(modalNode);
139
- unsubscribe?.();
140
- reject(error);
141
- }
142
- });
143
- return { modalNode, promiseHandler };
144
- };
204
+ const alertHandler = (args) => dispatchModal({ ...args, type: 'alert' }, {
205
+ signal: args.signal,
206
+ mapResult: () => undefined,
207
+ cancelResult: () => null,
208
+ });
145
209
 
146
- const promptHandler = (args) => {
147
- const modalNode = ModalManager.open({
148
- ...args,
149
- type: 'prompt',
150
- });
151
- const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
152
- const promiseHandler = new Promise((resolve, reject) => {
153
- try {
154
- modalNode.handleResolve = (result) => {
155
- unsubscribe?.();
156
- resolve(result);
157
- };
158
- if (args.signal?.aborted)
159
- closeModal(modalNode);
160
- }
161
- catch (error) {
162
- closeModal(modalNode);
163
- unsubscribe?.();
164
- reject(error);
165
- }
166
- });
167
- return { modalNode, promiseHandler };
168
- };
210
+ const confirmHandler = (args) => dispatchModal({ ...args, type: 'confirm' }, {
211
+ signal: args.signal,
212
+ mapResult: (result) => result ?? false,
213
+ cancelResult: () => null,
214
+ });
215
+
216
+ const promptHandler = (args) => dispatchModal({ ...args, type: 'prompt' }, {
217
+ signal: args.signal,
218
+ mapResult: (result) => result ?? null,
219
+ cancelResult: () => args.returnOnCancel ? (args.defaultValue ?? null) : null,
220
+ });
169
221
 
170
222
  const alert = (args) => alertHandler(args).promiseHandler;
171
223
  const confirm = (args) => confirmHandler(args).promiseHandler;
@@ -344,10 +396,6 @@ const nodeFactory = (modal) => {
344
396
  throw new Error(`Unknown modal: ${modal.type}`, { modal });
345
397
  };
346
398
 
347
- const DEFAULT_Z_INDEX = 1000;
348
- const DEFAULT_ANIMATION_DURATION = '300ms';
349
- const DEFAULT_BACKDROP_COLOR = 'rgba(0, 0, 0, 0.5)';
350
-
351
399
  const FallbackTitle = ({ children }) => {
352
400
  return jsxRuntime.jsx("h2", { style: { margin: 'unset' }, children: children });
353
401
  };
@@ -480,24 +528,12 @@ const ModalManagerContextProvider = react.memo(({ usePathname, children, }) => {
480
528
  const initiator = react.useRef(pathname);
481
529
  const modalIdSequence = react.useRef(0);
482
530
  const options = useConfigurationOptions();
483
- const duration = react.useMemo(() => convert.convertMsFromDuration(options.duration), [options]);
531
+ const optionsRef = hook.useReference(options);
484
532
  hook.useOnMountLayout(() => {
485
- const { manualDestroy, closeOnBackdropClick } = options;
486
- for (const data of ModalManager.prerender) {
487
- const modal = nodeFactory({
488
- duration,
489
- manualDestroy,
490
- closeOnBackdropClick,
491
- ...data,
492
- id: modalIdSequence.current++,
493
- initiator: initiator.current,
494
- });
495
- modalDictionary.current.set(modal.id, modal);
496
- setModalIds((ids) => [...ids, modal.id]);
497
- }
498
533
  ModalManager.openHandler = (data) => {
534
+ const { duration, manualDestroy, closeOnBackdropClick } = optionsRef.current;
499
535
  const modalNode = nodeFactory({
500
- duration,
536
+ duration: convert.convertMsFromDuration(duration),
501
537
  manualDestroy,
502
538
  closeOnBackdropClick,
503
539
  ...data,
@@ -532,43 +568,33 @@ const ModalManagerContextProvider = react.memo(({ usePathname, children, }) => {
532
568
  }
533
569
  initiator.current = pathname;
534
570
  }, [pathname]);
535
- const getModalNodeRef = react.useRef((modalId) => modalDictionary.current.get(modalId));
571
+ const getModalNodeRef = react.useRef((modalId) => modalId !== undefined ? modalDictionary.current.get(modalId) : undefined);
536
572
  const onDestroyRef = react.useRef((modalId) => {
537
- const modal = modalDictionary.current.get(modalId);
573
+ const modal = getModalNodeRef.current(modalId);
538
574
  if (!modal)
539
575
  return;
540
576
  modal.onDestroy();
541
577
  ModalManager.refresh();
542
578
  });
543
- const hideModalRef = react.useRef((modalId) => {
544
- const modal = modalDictionary.current.get(modalId);
545
- if (!modal)
546
- return;
547
- modal.onHide();
548
- ModalManager.refresh();
549
- if (modal.manualDestroy === false)
550
- setTimeout(() => modal.onDestroy(), modal.duration);
551
- });
552
579
  const onChangeRef = react.useRef((modalId, value) => {
553
- const modal = modalDictionary.current.get(modalId);
580
+ const modal = getModalNodeRef.current(modalId);
554
581
  if (!modal)
555
582
  return;
556
583
  if (modal.type === 'prompt')
557
584
  modal.onChange(value);
558
585
  });
559
586
  const onConfirmRef = react.useRef((modalId) => {
560
- const modal = modalDictionary.current.get(modalId);
587
+ const modal = getModalNodeRef.current(modalId);
561
588
  if (!modal)
562
589
  return;
563
590
  modal.onConfirm();
564
- hideModalRef.current(modalId);
591
+ hideModal(modal);
565
592
  });
566
593
  const onCloseRef = react.useRef((modalId) => {
567
- const modal = modalDictionary.current.get(modalId);
594
+ const modal = getModalNodeRef.current(modalId);
568
595
  if (!modal)
569
596
  return;
570
- modal.onClose();
571
- hideModalRef.current(modalId);
597
+ closeModal(modal);
572
598
  });
573
599
  const getModalRef = react.useRef((modalId) => ({
574
600
  modal: getModalNodeRef.current(modalId),
@@ -605,21 +631,26 @@ const useUserDefinedContext = () => {
605
631
  const usePathname = () => {
606
632
  const [pathname, setPathname] = react.useState(window.location.pathname);
607
633
  react.useLayoutEffect(() => {
634
+ const handleChange = () => setPathname(window.location.pathname);
635
+ const navigation = window.navigation;
636
+ if (navigation) {
637
+ navigation.addEventListener('currententrychange', handleChange);
638
+ window.addEventListener('popstate', handleChange);
639
+ return () => {
640
+ navigation.removeEventListener('currententrychange', handleChange);
641
+ window.removeEventListener('popstate', handleChange);
642
+ };
643
+ }
608
644
  let requestId;
609
645
  const checkPathname = () => {
610
- if (requestId)
611
- cancelAnimationFrame(requestId);
612
- if (pathname !== window.location.pathname) {
646
+ if (pathname !== window.location.pathname)
613
647
  setPathname(window.location.pathname);
614
- }
615
- else {
648
+ else
616
649
  requestId = requestAnimationFrame(checkPathname);
617
- }
618
650
  };
619
651
  requestId = requestAnimationFrame(checkPathname);
620
652
  return () => {
621
- if (requestId)
622
- cancelAnimationFrame(requestId);
653
+ cancelAnimationFrame(requestId);
623
654
  };
624
655
  }, [pathname]);
625
656
  return { pathname };
@@ -725,7 +756,7 @@ const PromptInner = react.memo(({ modal, handlers }) => {
725
756
  const handleConfirm = react.useCallback(() => {
726
757
  requestAnimationFrame(onConfirm);
727
758
  }, [onConfirm]);
728
- const disabled = react.useMemo(() => (value ? !!checkDisabled?.(value) : false), [checkDisabled, value]);
759
+ const disabled = react.useMemo(() => (checkDisabled ? !!checkDisabled(value) : false), [checkDisabled, value]);
729
760
  const { TitleComponent, SubtitleComponent, ContentComponent, FooterComponent, } = useConfigurationContext();
730
761
  return (jsxRuntime.jsxs(react.Fragment, { children: [title &&
731
762
  (filter.isString(title) ? (jsxRuntime.jsx(TitleComponent, { context: userDefinedContext, children: title })) : (title)), subtitle &&
@@ -798,6 +829,7 @@ const style$1 = `
798
829
  z-index: var(--z-index);
799
830
  pointer-events: none;
800
831
  overflow: hidden;
832
+ }
801
833
  `;
802
834
  ModalManager.defineStyleSheet('presenter', style$1);
803
835
 
@@ -856,8 +888,10 @@ const AnchorInner = () => {
856
888
  }, [update]);
857
889
  const options = useConfigurationOptions();
858
890
  const dimmed = useActiveModalCount(validateDimmable, version);
859
- if (!dimmed)
860
- reset();
891
+ react.useEffect(() => {
892
+ if (!dimmed)
893
+ reset();
894
+ }, [dimmed]);
861
895
  const anchorStyle = react.useMemo(() => ({
862
896
  '--z-index': options.zIndex,
863
897
  '--transition-duration': options.duration,
@@ -898,12 +932,12 @@ const BootstrapProvider = react.memo(react.forwardRef(({ usePathname: useExterna
898
932
  initialize: handleInitialize,
899
933
  }), [handleInitialize]);
900
934
  hook.useOnMount(() => {
901
- if (handleRef !== null)
902
- return;
903
- handleInitialize();
935
+ if (handleRef === null)
936
+ handleInitialize();
904
937
  return () => {
905
- if (anchorRef.current)
906
- anchorRef.current.remove();
938
+ if (anchorRef.current === null)
939
+ return;
940
+ anchorRef.current.remove();
907
941
  handleReset();
908
942
  };
909
943
  });
@@ -924,13 +958,15 @@ const BootstrapProvider = react.memo(react.forwardRef(({ usePathname: useExterna
924
958
 
925
959
  const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, BackgroundComponent, TitleComponent, SubtitleComponent, ContentComponent, FooterComponent, options, context, mode = 'auto', } = {}) => {
926
960
  const usePathname$1 = react.useMemo(() => useExternalPathname || usePathname, [useExternalPathname]);
927
- const { anchorRef, handleInitialize } = useInitialize();
961
+ const { anchorRef, handleInitialize, handleReset } = useInitialize();
928
962
  hook.useOnMount(() => {
929
963
  if (mode === 'auto')
930
964
  handleInitialize();
931
965
  return () => {
932
- if (anchorRef.current)
933
- anchorRef.current.remove();
966
+ if (anchorRef.current === null)
967
+ return;
968
+ anchorRef.current.remove();
969
+ handleReset();
934
970
  };
935
971
  });
936
972
  const initialize = react.useCallback((element) => {
@@ -954,28 +990,33 @@ const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, B
954
990
  };
955
991
 
956
992
  const useModal = (configuration) => {
957
- const modalNodesRef = react.useRef([]);
993
+ const modalResultsRef = react.useRef([]);
958
994
  const baseArgsRef = react.useRef(configuration);
959
995
  const alertRef = react.useRef((args) => {
960
- const { modalNode, promiseHandler } = alertHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
961
- modalNodesRef.current.push(modalNode);
962
- return promiseHandler;
996
+ const result = alertHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
997
+ modalResultsRef.current.push(result);
998
+ return result.promiseHandler;
963
999
  });
964
1000
  const confirmRef = react.useRef((args) => {
965
- const { modalNode, promiseHandler } = confirmHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
966
- modalNodesRef.current.push(modalNode);
967
- return promiseHandler;
1001
+ const result = confirmHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
1002
+ modalResultsRef.current.push(result);
1003
+ return result.promiseHandler;
968
1004
  });
969
1005
  const promptRef = react.useRef((args) => {
970
- const { modalNode, promiseHandler } = promptHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
971
- modalNodesRef.current.push(modalNode);
972
- return promiseHandler;
1006
+ const result = promptHandler(baseArgsRef.current ? { ...baseArgsRef.current, ...args } : args);
1007
+ modalResultsRef.current.push(result);
1008
+ return result.promiseHandler;
973
1009
  });
974
1010
  hook.useOnUnmount(() => {
975
- for (const node of modalNodesRef.current)
976
- closeModal(node, false);
1011
+ for (const result of modalResultsRef.current) {
1012
+ const modalNode = result.modalNode;
1013
+ if (modalNode)
1014
+ closeModal(modalNode, false);
1015
+ else
1016
+ result.cancel();
1017
+ }
977
1018
  ModalManager.refresh();
978
- modalNodesRef.current = [];
1019
+ modalResultsRef.current = [];
979
1020
  });
980
1021
  return {
981
1022
  alert: alertRef.current,
@@ -1028,8 +1069,6 @@ const useModalAnimation = (visible, handler) => {
1028
1069
 
1029
1070
  const useModalDuration = (modalId) => {
1030
1071
  const globalDuration = useConfigurationDuration();
1031
- if (modalId === undefined)
1032
- return globalDuration;
1033
1072
  const { modal } = useModalManager(modalId);
1034
1073
  if (modal === undefined)
1035
1074
  return globalDuration;