@lerx/promise-modal 0.8.4 → 0.8.5

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.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  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
- import { useConstant, useSnapshot, useReference, useOnMountLayout, useHandle, useVersion, useOnMount } from '@winglet/react-utils/hook';
4
+ import { useConstant, useSnapshot, useReference, useOnMountLayout, useHandle, useVersion, useOnMount, useOnUnmount } from '@winglet/react-utils/hook';
5
5
  import { polynomialHash } from '@winglet/common-utils/hash';
6
6
  import { getRandomString, counterFactory } from '@winglet/common-utils/lib';
7
7
  import { styleManagerFactory, destroyScope } from '@winglet/style-utils/style-manager';
@@ -16,6 +16,8 @@ class ModalManager {
16
16
  static #anchor = null;
17
17
  static #scope = `promise-modal-${getRandomString(36)}`;
18
18
  static #hash = polynomialHash(ModalManager.#scope);
19
+ static #styleManager = styleManagerFactory(ModalManager.#scope);
20
+ static #styleSheetDefinition = new Map();
19
21
  static anchor(options) {
20
22
  if (ModalManager.#anchor !== null)
21
23
  return ModalManager.#anchor;
@@ -34,13 +36,20 @@ class ModalManager {
34
36
  static get prerender() {
35
37
  return ModalManager.#prerenderList;
36
38
  }
37
- static #openHandler = (modal) => ModalManager.#prerenderList.push(modal);
39
+ static #openHandler = ((modal) => {
40
+ ModalManager.#prerenderList.push(modal);
41
+ });
38
42
  static set openHandler(handler) {
39
43
  ModalManager.#openHandler = handler;
40
44
  ModalManager.#prerenderList = [];
41
45
  }
42
- static #styleManager = styleManagerFactory(ModalManager.#scope);
43
- static #styleSheetDefinition = new Map();
46
+ static #refreshHandler;
47
+ static set refreshHandler(handler) {
48
+ ModalManager.#refreshHandler = handler;
49
+ }
50
+ static refresh() {
51
+ ModalManager.#refreshHandler?.();
52
+ }
44
53
  static defineStyleSheet(styleId, css) {
45
54
  ModalManager.#styleSheetDefinition.set(styleId, compressCss(css));
46
55
  }
@@ -54,95 +63,113 @@ class ModalManager {
54
63
  static reset() {
55
64
  ModalManager.#anchor = null;
56
65
  ModalManager.#prerenderList = [];
57
- ModalManager.#openHandler = (modal) => ModalManager.#prerenderList.push(modal);
66
+ ModalManager.#openHandler = ((modal) => {
67
+ ModalManager.#prerenderList.push(modal);
68
+ });
69
+ ModalManager.#refreshHandler = undefined;
58
70
  destroyScope(ModalManager.#scope);
59
71
  }
60
72
  static open(modal) {
61
- ModalManager.#openHandler(modal);
73
+ return ModalManager.#openHandler(modal);
62
74
  }
63
75
  }
64
76
 
65
- const alert = ({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }) => {
66
- return new Promise((resolve, reject) => {
77
+ const closeModal = (modalNode, refresh = true) => {
78
+ if (modalNode.visible === false)
79
+ return;
80
+ modalNode.onClose();
81
+ modalNode.onHide();
82
+ if (refresh)
83
+ ModalManager.refresh();
84
+ if (modalNode.manualDestroy || modalNode.alive === false)
85
+ return;
86
+ return setTimeout(() => modalNode.onDestroy(), modalNode.duration);
87
+ };
88
+
89
+ const subscribeAbortSignal = (modalNode, signal) => {
90
+ if (signal === undefined)
91
+ return null;
92
+ const handleAbort = () => closeModal(modalNode);
93
+ signal.addEventListener('abort', handleAbort, { once: true });
94
+ return () => signal.removeEventListener('abort', handleAbort);
95
+ };
96
+
97
+ const alertHandler = (args) => {
98
+ const modalNode = ModalManager.open({
99
+ ...args,
100
+ type: 'alert',
101
+ });
102
+ const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
103
+ const promiseHandler = new Promise((resolve, reject) => {
67
104
  try {
68
- ModalManager.open({
69
- type: 'alert',
70
- group,
71
- subtype,
72
- resolve: () => resolve(),
73
- title,
74
- subtitle,
75
- content,
76
- background,
77
- footer,
78
- dimmed,
79
- manualDestroy,
80
- closeOnBackdropClick,
81
- ForegroundComponent,
82
- BackgroundComponent,
83
- });
105
+ modalNode.handleResolve = () => {
106
+ unsubscribe?.();
107
+ resolve();
108
+ };
109
+ if (args.signal?.aborted)
110
+ closeModal(modalNode);
84
111
  }
85
112
  catch (error) {
113
+ closeModal(modalNode);
114
+ unsubscribe?.();
86
115
  reject(error);
87
116
  }
88
117
  });
118
+ return { modalNode, promiseHandler };
89
119
  };
90
120
 
91
- const confirm = ({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }) => {
92
- return new Promise((resolve, reject) => {
121
+ const confirmHandler = (args) => {
122
+ const modalNode = ModalManager.open({
123
+ ...args,
124
+ type: 'confirm',
125
+ });
126
+ const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
127
+ const promiseHandler = new Promise((resolve, reject) => {
93
128
  try {
94
- ModalManager.open({
95
- type: 'confirm',
96
- group,
97
- subtype,
98
- resolve: (result) => resolve(result ?? false),
99
- title,
100
- subtitle,
101
- content,
102
- background,
103
- footer,
104
- dimmed,
105
- manualDestroy,
106
- closeOnBackdropClick,
107
- ForegroundComponent,
108
- BackgroundComponent,
109
- });
129
+ modalNode.handleResolve = (result) => {
130
+ unsubscribe?.();
131
+ resolve(result ?? false);
132
+ };
133
+ if (args.signal?.aborted)
134
+ closeModal(modalNode);
110
135
  }
111
136
  catch (error) {
137
+ closeModal(modalNode);
138
+ unsubscribe?.();
112
139
  reject(error);
113
140
  }
114
141
  });
142
+ return { modalNode, promiseHandler };
115
143
  };
116
144
 
117
- const prompt = ({ group, title, subtitle, content, defaultValue, Input, disabled, returnOnCancel, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }) => {
118
- return new Promise((resolve, reject) => {
145
+ const promptHandler = (args) => {
146
+ const modalNode = ModalManager.open({
147
+ ...args,
148
+ type: 'prompt',
149
+ });
150
+ const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
151
+ const promiseHandler = new Promise((resolve, reject) => {
119
152
  try {
120
- ModalManager.open({
121
- type: 'prompt',
122
- group,
123
- resolve: (result) => resolve(result),
124
- title,
125
- subtitle,
126
- content,
127
- Input,
128
- defaultValue,
129
- disabled,
130
- returnOnCancel,
131
- background,
132
- footer,
133
- dimmed,
134
- manualDestroy,
135
- closeOnBackdropClick,
136
- ForegroundComponent,
137
- BackgroundComponent,
138
- });
153
+ modalNode.handleResolve = (result) => {
154
+ unsubscribe?.();
155
+ resolve(result);
156
+ };
157
+ if (args.signal?.aborted)
158
+ closeModal(modalNode);
139
159
  }
140
160
  catch (error) {
161
+ closeModal(modalNode);
162
+ unsubscribe?.();
141
163
  reject(error);
142
164
  }
143
165
  });
166
+ return { modalNode, promiseHandler };
144
167
  };
145
168
 
169
+ const alert = (args) => alertHandler(args).promiseHandler;
170
+ const confirm = (args) => confirmHandler(args).promiseHandler;
171
+ const prompt = (args) => promptHandler(args).promiseHandler;
172
+
146
173
  class AbstractNode {
147
174
  id;
148
175
  group;
@@ -150,9 +177,10 @@ class AbstractNode {
150
177
  title;
151
178
  subtitle;
152
179
  background;
180
+ dimmed;
181
+ duration;
153
182
  manualDestroy;
154
183
  closeOnBackdropClick;
155
- dimmed;
156
184
  ForegroundComponent;
157
185
  BackgroundComponent;
158
186
  #alive;
@@ -163,9 +191,12 @@ class AbstractNode {
163
191
  get visible() {
164
192
  return this.#visible;
165
193
  }
166
- #resolve;
194
+ #handleResolve;
195
+ set handleResolve(handleResolve) {
196
+ this.#handleResolve = handleResolve;
197
+ }
167
198
  #listeners = new Set();
168
- constructor({ id, initiator, group, title, subtitle, background, dimmed = true, manualDestroy = false, closeOnBackdropClick = true, resolve, ForegroundComponent, BackgroundComponent, }) {
199
+ constructor({ id, initiator, group, title, subtitle, background, dimmed = true, duration = 0, manualDestroy = false, closeOnBackdropClick = true, handleResolve, ForegroundComponent, BackgroundComponent, }) {
169
200
  this.id = id;
170
201
  this.group = group;
171
202
  this.initiator = initiator;
@@ -173,13 +204,17 @@ class AbstractNode {
173
204
  this.subtitle = subtitle;
174
205
  this.background = background;
175
206
  this.dimmed = dimmed;
207
+ this.duration = duration;
176
208
  this.manualDestroy = manualDestroy;
177
209
  this.closeOnBackdropClick = closeOnBackdropClick;
178
210
  this.ForegroundComponent = ForegroundComponent;
179
211
  this.BackgroundComponent = BackgroundComponent;
180
212
  this.#alive = true;
181
213
  this.#visible = true;
182
- this.#resolve = resolve;
214
+ this.#handleResolve = handleResolve;
215
+ }
216
+ onResolve(result) {
217
+ this.#handleResolve?.(result);
183
218
  }
184
219
  subscribe(listener) {
185
220
  this.#listeners.add(listener);
@@ -191,9 +226,6 @@ class AbstractNode {
191
226
  for (const listener of this.#listeners)
192
227
  listener();
193
228
  }
194
- resolve(result) {
195
- this.#resolve(result);
196
- }
197
229
  onDestroy() {
198
230
  const needPublish = this.#alive === true;
199
231
  this.#alive = false;
@@ -219,7 +251,7 @@ class AlertNode extends AbstractNode {
219
251
  subtype;
220
252
  content;
221
253
  footer;
222
- constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, manualDestroy, closeOnBackdropClick, resolve, ForegroundComponent, BackgroundComponent, }) {
254
+ constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, duration, manualDestroy, closeOnBackdropClick, handleResolve, ForegroundComponent, BackgroundComponent, }) {
223
255
  super({
224
256
  id,
225
257
  group,
@@ -228,9 +260,10 @@ class AlertNode extends AbstractNode {
228
260
  subtitle,
229
261
  background,
230
262
  dimmed,
263
+ duration,
231
264
  manualDestroy,
232
265
  closeOnBackdropClick,
233
- resolve,
266
+ handleResolve,
234
267
  ForegroundComponent,
235
268
  BackgroundComponent,
236
269
  });
@@ -240,10 +273,10 @@ class AlertNode extends AbstractNode {
240
273
  this.footer = footer;
241
274
  }
242
275
  onClose() {
243
- this.resolve(null);
276
+ this.onResolve(null);
244
277
  }
245
278
  onConfirm() {
246
- this.resolve(null);
279
+ this.onResolve(null);
247
280
  }
248
281
  }
249
282
 
@@ -252,7 +285,7 @@ class ConfirmNode extends AbstractNode {
252
285
  subtype;
253
286
  content;
254
287
  footer;
255
- constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, manualDestroy, closeOnBackdropClick, resolve, ForegroundComponent, BackgroundComponent, }) {
288
+ constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, duration, manualDestroy, closeOnBackdropClick, handleResolve, ForegroundComponent, BackgroundComponent, }) {
256
289
  super({
257
290
  id,
258
291
  group,
@@ -261,9 +294,10 @@ class ConfirmNode extends AbstractNode {
261
294
  subtitle,
262
295
  background,
263
296
  dimmed,
297
+ duration,
264
298
  manualDestroy,
265
299
  closeOnBackdropClick,
266
- resolve,
300
+ handleResolve,
267
301
  ForegroundComponent,
268
302
  BackgroundComponent,
269
303
  });
@@ -273,10 +307,10 @@ class ConfirmNode extends AbstractNode {
273
307
  this.footer = footer;
274
308
  }
275
309
  onClose() {
276
- this.resolve(false);
310
+ this.onResolve(false);
277
311
  }
278
312
  onConfirm() {
279
- this.resolve(true);
313
+ this.onResolve(true);
280
314
  }
281
315
  }
282
316
 
@@ -289,7 +323,7 @@ class PromptNode extends AbstractNode {
289
323
  returnOnCancel;
290
324
  footer;
291
325
  #value;
292
- constructor({ id, group, initiator, type, title, subtitle, content, defaultValue, Input, disabled, returnOnCancel, footer, background, dimmed, manualDestroy, closeOnBackdropClick, resolve, ForegroundComponent, BackgroundComponent, }) {
326
+ constructor({ id, group, initiator, type, title, subtitle, content, defaultValue, Input, disabled, returnOnCancel, footer, background, dimmed, duration, manualDestroy, closeOnBackdropClick, handleResolve, ForegroundComponent, BackgroundComponent, }) {
293
327
  super({
294
328
  id,
295
329
  group,
@@ -298,9 +332,10 @@ class PromptNode extends AbstractNode {
298
332
  subtitle,
299
333
  background,
300
334
  dimmed,
335
+ duration,
301
336
  manualDestroy,
302
337
  closeOnBackdropClick,
303
- resolve,
338
+ handleResolve,
304
339
  ForegroundComponent,
305
340
  BackgroundComponent,
306
341
  });
@@ -317,13 +352,13 @@ class PromptNode extends AbstractNode {
317
352
  this.#value = value;
318
353
  }
319
354
  onConfirm() {
320
- this.resolve(this.#value ?? null);
355
+ this.onResolve(this.#value ?? null);
321
356
  }
322
357
  onClose() {
323
358
  if (this.returnOnCancel)
324
- this.resolve(this.#value ?? null);
359
+ this.onResolve(this.#value ?? null);
325
360
  else
326
- this.resolve(null);
361
+ this.onResolve(null);
327
362
  }
328
363
  }
329
364
 
@@ -362,8 +397,8 @@ const FallbackFooter = ({ confirmLabel, hideConfirm = false, cancelLabel, hideCa
362
397
  const ModalManagerContext = createContext({});
363
398
 
364
399
  const useModalManagerContext = () => useContext(ModalManagerContext);
365
- const useModal = (id) => {
366
- const { getModal } = useModalManagerContext();
400
+ const useModalManager = (id) => {
401
+ const { getModal } = useContext(ModalManagerContext);
367
402
  return useMemo(() => getModal(id), [id, getModal]);
368
403
  };
369
404
 
@@ -480,32 +515,26 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
480
515
  const { manualDestroy, closeOnBackdropClick } = options;
481
516
  for (const data of ModalManager.prerender) {
482
517
  const modal = nodeFactory({
518
+ duration,
519
+ manualDestroy,
520
+ closeOnBackdropClick,
483
521
  ...data,
484
522
  id: modalIdSequence.current++,
485
523
  initiator: initiator.current,
486
- manualDestroy: data.manualDestroy !== undefined
487
- ? data.manualDestroy
488
- : manualDestroy,
489
- closeOnBackdropClick: data.closeOnBackdropClick !== undefined
490
- ? data.closeOnBackdropClick
491
- : closeOnBackdropClick,
492
524
  });
493
525
  modalDictionary.current.set(modal.id, modal);
494
526
  setModalIds((ids) => [...ids, modal.id]);
495
527
  }
496
528
  ModalManager.openHandler = (data) => {
497
- const modal = nodeFactory({
529
+ const modalNode = nodeFactory({
530
+ duration,
531
+ manualDestroy,
532
+ closeOnBackdropClick,
498
533
  ...data,
499
534
  id: modalIdSequence.current++,
500
535
  initiator: initiator.current,
501
- manualDestroy: data.manualDestroy !== undefined
502
- ? data.manualDestroy
503
- : manualDestroy,
504
- closeOnBackdropClick: data.closeOnBackdropClick !== undefined
505
- ? data.closeOnBackdropClick
506
- : closeOnBackdropClick,
507
536
  });
508
- modalDictionary.current.set(modal.id, modal);
537
+ modalDictionary.current.set(modalNode.id, modalNode);
509
538
  setModalIds((ids) => {
510
539
  const aliveIds = [];
511
540
  for (let i = 0, l = ids.length; i < l; i++) {
@@ -516,8 +545,9 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
516
545
  else
517
546
  aliveIds.push(id);
518
547
  }
519
- return [...aliveIds, modal.id];
548
+ return [...aliveIds, modalNode.id];
520
549
  });
550
+ return modalNode;
521
551
  };
522
552
  });
523
553
  useLayoutEffect(() => {
@@ -532,78 +562,62 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
532
562
  }
533
563
  initiator.current = pathname;
534
564
  }, [pathname]);
535
- const getModalNode = useCallback((modalId) => {
536
- return modalDictionary.current.get(modalId);
537
- }, []);
538
- const onDestroy = useCallback((modalId) => {
565
+ const getModalNodeRef = useRef((modalId) => modalDictionary.current.get(modalId));
566
+ const onDestroyRef = useRef((modalId) => {
539
567
  const modal = modalDictionary.current.get(modalId);
540
568
  if (!modal)
541
569
  return;
542
570
  modal.onDestroy();
543
- updaterRef.current?.();
544
- }, []);
545
- const updaterRef = useRef(undefined);
546
- const hideModal = useCallback((modalId) => {
571
+ ModalManager.refresh();
572
+ });
573
+ const hideModalRef = useRef((modalId) => {
547
574
  const modal = modalDictionary.current.get(modalId);
548
575
  if (!modal)
549
576
  return;
550
577
  modal.onHide();
551
- updaterRef.current?.();
552
- if (!modal.manualDestroy)
553
- setTimeout(() => {
554
- modal.onDestroy();
555
- }, duration);
556
- }, [duration]);
557
- const onChange = useCallback((modalId, value) => {
578
+ ModalManager.refresh();
579
+ if (modal.manualDestroy === false)
580
+ setTimeout(() => modal.onDestroy(), modal.duration);
581
+ });
582
+ const onChangeRef = useRef((modalId, value) => {
558
583
  const modal = modalDictionary.current.get(modalId);
559
584
  if (!modal)
560
585
  return;
561
586
  if (modal.type === 'prompt')
562
587
  modal.onChange(value);
563
- }, []);
564
- const onConfirm = useCallback((modalId) => {
588
+ });
589
+ const onConfirmRef = useRef((modalId) => {
565
590
  const modal = modalDictionary.current.get(modalId);
566
591
  if (!modal)
567
592
  return;
568
593
  modal.onConfirm();
569
- hideModal(modalId);
570
- }, [hideModal]);
571
- const onClose = useCallback((modalId) => {
594
+ hideModalRef.current(modalId);
595
+ });
596
+ const onCloseRef = useRef((modalId) => {
572
597
  const modal = modalDictionary.current.get(modalId);
573
598
  if (!modal)
574
599
  return;
575
600
  modal.onClose();
576
- hideModal(modalId);
577
- }, [hideModal]);
578
- const getModal = useCallback((modalId) => ({
579
- modal: getModalNode(modalId),
580
- onConfirm: () => onConfirm(modalId),
581
- onClose: () => onClose(modalId),
582
- onChange: (value) => onChange(modalId, value),
583
- onDestroy: () => onDestroy(modalId),
584
- }), [getModalNode, onConfirm, onClose, onChange, onDestroy]);
601
+ hideModalRef.current(modalId);
602
+ });
603
+ const getModalRef = useRef((modalId) => ({
604
+ modal: getModalNodeRef.current(modalId),
605
+ onConfirm: () => onConfirmRef.current(modalId),
606
+ onClose: () => onCloseRef.current(modalId),
607
+ onChange: (value) => onChangeRef.current(modalId, value),
608
+ onDestroy: () => onDestroyRef.current(modalId),
609
+ }));
585
610
  const value = useMemo(() => {
586
611
  return {
587
612
  modalIds,
588
- getModalNode,
589
- onChange,
590
- onConfirm,
591
- onClose,
592
- onDestroy,
593
- getModal,
594
- setUpdater: (updater) => {
595
- updaterRef.current = updater;
596
- },
613
+ getModalNode: getModalNodeRef.current,
614
+ onChange: onChangeRef.current,
615
+ onConfirm: onConfirmRef.current,
616
+ onClose: onCloseRef.current,
617
+ onDestroy: onDestroyRef.current,
618
+ getModal: getModalRef.current,
597
619
  };
598
- }, [
599
- modalIds,
600
- getModal,
601
- getModalNode,
602
- onChange,
603
- onConfirm,
604
- onClose,
605
- onDestroy,
606
- ]);
620
+ }, [modalIds]);
607
621
  return (jsx(ModalManagerContext.Provider, { value: value, children: children }));
608
622
  });
609
623
 
@@ -672,7 +686,7 @@ ModalManager.defineStyleSheet('background', style$3);
672
686
  const BackgroundFrame = ({ modalId, onChangeOrder, }) => {
673
687
  const { BackgroundComponent } = useConfigurationContext();
674
688
  const { context: userDefinedContext } = useUserDefinedContext();
675
- const { modal, onClose, onChange, onConfirm, onDestroy } = useModal(modalId);
689
+ const { modal, onClose, onChange, onConfirm, onDestroy } = useModalManager(modalId);
676
690
  const handleClose = useCallback((event) => {
677
691
  if (modal && modal.closeOnBackdropClick && modal.visible)
678
692
  onClose();
@@ -788,7 +802,7 @@ ModalManager.defineStyleSheet('foreground', style$2);
788
802
  const ForegroundFrame = ({ modalId, onChangeOrder, }) => {
789
803
  const { ForegroundComponent } = useConfigurationContext();
790
804
  const { context: userDefinedContext } = useUserDefinedContext();
791
- const { modal, onChange, onConfirm, onClose, onDestroy } = useModal(modalId);
805
+ const { modal, onChange, onConfirm, onClose, onDestroy } = useModalManager(modalId);
792
806
  const Foreground = useMemo(() => modal?.ForegroundComponent || ForegroundComponent, [ForegroundComponent, modal]);
793
807
  if (!modal)
794
808
  return null;
@@ -820,7 +834,7 @@ ModalManager.defineStyleSheet('presenter', style$1);
820
834
  const Presenter = memo(({ modalId, getValue, increment }) => {
821
835
  const ref = useRef(null);
822
836
  const options = useConfigurationOptions();
823
- const { modal } = useModal(modalId);
837
+ const { modal } = useModalManager(modalId);
824
838
  useSubscribeModal(modal);
825
839
  useOnMountLayout(() => {
826
840
  if (ref.current === null)
@@ -866,10 +880,10 @@ ModalManager.defineStyleSheet('backdrop', backdropStyle);
866
880
  const { getValue, increment, reset } = counterFactory(0);
867
881
  const AnchorInner = () => {
868
882
  const [version, update] = useVersion();
869
- const { modalIds, setUpdater } = useModalManagerContext();
883
+ const { modalIds } = useModalManagerContext();
870
884
  useEffect(() => {
871
- setUpdater(update);
872
- }, [setUpdater, update]);
885
+ ModalManager.refreshHandler = update;
886
+ }, [update]);
873
887
  const options = useConfigurationOptions();
874
888
  const dimmed = useActiveModalCount(validateDimmable, version);
875
889
  if (!dimmed)
@@ -969,8 +983,38 @@ const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, B
969
983
  return { portal, initialize };
970
984
  };
971
985
 
986
+ const useModal = () => {
987
+ const modalNodesRef = useRef([]);
988
+ const alertRef = useRef((args) => {
989
+ const { modalNode, promiseHandler } = alertHandler(args);
990
+ modalNodesRef.current.push(modalNode);
991
+ return promiseHandler;
992
+ });
993
+ const confirmRef = useRef((args) => {
994
+ const { modalNode, promiseHandler } = confirmHandler(args);
995
+ modalNodesRef.current.push(modalNode);
996
+ return promiseHandler;
997
+ });
998
+ const promptRef = useRef((args) => {
999
+ const { modalNode, promiseHandler } = promptHandler(args);
1000
+ modalNodesRef.current.push(modalNode);
1001
+ return promiseHandler;
1002
+ });
1003
+ useOnUnmount(() => {
1004
+ for (const node of modalNodesRef.current)
1005
+ closeModal(node, false);
1006
+ ModalManager.refresh();
1007
+ modalNodesRef.current = [];
1008
+ });
1009
+ return {
1010
+ alert: alertRef.current,
1011
+ confirm: confirmRef.current,
1012
+ prompt: promptRef.current,
1013
+ };
1014
+ };
1015
+
972
1016
  const useDestroyAfter = (modalId, duration) => {
973
- const { modal, onDestroy } = useModal(modalId);
1017
+ const { modal, onDestroy } = useModalManager(modalId);
974
1018
  const tick = useSubscribeModal(modal);
975
1019
  const reference = useRef({
976
1020
  modal,
@@ -1011,4 +1055,4 @@ const useModalAnimation = (visible, handler) => {
1011
1055
  }, [visible]);
1012
1056
  };
1013
1057
 
1014
- export { BootstrapProvider as ModalProvider, alert, confirm, prompt, useActiveModalCount, useDestroyAfter, useBootstrap as useInitializeModal, useModalAnimation, useConfigurationBackdrop as useModalBackdrop, useConfigurationDuration as useModalDuration, useConfigurationOptions as useModalOptions, useSubscribeModal };
1058
+ export { BootstrapProvider as ModalProvider, alert, confirm, prompt, useActiveModalCount, useDestroyAfter, useBootstrap as useInitializeModal, useModal, useModalAnimation, useConfigurationBackdrop as useModalBackdrop, useConfigurationDuration as useModalDuration, useConfigurationOptions as useModalOptions, useSubscribeModal };
@@ -5,6 +5,5 @@ export interface ModalManagerContextProps extends ModalHandlersWithId {
5
5
  modalIds: ModalNode['id'][];
6
6
  getModal: Fn<[id: ModalNode['id']], ModalActions>;
7
7
  getModalNode: Fn<[id: ModalNode['id']], ModalNode | undefined>;
8
- setUpdater: Fn<[updater: Fn]>;
9
8
  }
10
9
  export declare const ModalManagerContext: import("react").Context<ModalManagerContextProps>;
@@ -1,2 +1,2 @@
1
1
  export { ModalManagerContextProvider } from './ModalManagerContextProvider';
2
- export { useModalManagerContext, useModal } from './useModalManagerContext';
2
+ export { useModalManagerContext, useModalManager, } from './useModalManagerContext';
@@ -1,3 +1,3 @@
1
1
  import type { ManagedModal } from '../../types';
2
2
  export declare const useModalManagerContext: () => import("./ModalManagerContext").ModalManagerContextProps;
3
- export declare const useModal: (id: ManagedModal["id"]) => import("../../types").ModalActions;
3
+ export declare const useModalManager: (id: ManagedModal["id"]) => import("../../types").ModalActions;
@@ -1,3 +1,3 @@
1
- export { useModalManagerContext, useModal } from './ModalManagerContext';
1
+ export { useModalManagerContext, useModalManager } from './ModalManagerContext';
2
2
  export { useConfigurationContext, useConfigurationOptions, useConfigurationDuration, useConfigurationBackdrop, } from './ConfigurationContext';
3
3
  export { useUserDefinedContext } from './UserDefinedContext';
@@ -8,9 +8,10 @@ export interface BaseModal<T, B> {
8
8
  subtitle?: ReactNode;
9
9
  background?: ModalBackground<B>;
10
10
  dimmed?: boolean;
11
+ duration?: number;
11
12
  manualDestroy?: boolean;
12
13
  closeOnBackdropClick?: boolean;
13
- resolve: Fn<[result: T | null]>;
14
+ handleResolve?: Fn<[result: T | null]>;
14
15
  ForegroundComponent?: ForegroundComponent;
15
16
  BackgroundComponent?: BackgroundComponent;
16
17
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lerx/promise-modal",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
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",