@lerx/promise-modal 0.8.3 → 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.cjs CHANGED
@@ -8,16 +8,18 @@ const hash = require('@winglet/common-utils/hash');
8
8
  const lib = require('@winglet/common-utils/lib');
9
9
  const styleManager = require('@winglet/style-utils/style-manager');
10
10
  const util = require('@winglet/style-utils/util');
11
+ const filter = require('@winglet/common-utils/filter');
11
12
  const reactDom = require('react-dom');
12
13
  const array = require('@winglet/common-utils/array');
13
14
  const hoc = require('@winglet/react-utils/hoc');
14
- const filter = require('@winglet/common-utils/filter');
15
15
  const render = require('@winglet/react-utils/render');
16
16
 
17
17
  class ModalManager {
18
18
  static #anchor = null;
19
19
  static #scope = `promise-modal-${lib.getRandomString(36)}`;
20
20
  static #hash = hash.polynomialHash(ModalManager.#scope);
21
+ static #styleManager = styleManager.styleManagerFactory(ModalManager.#scope);
22
+ static #styleSheetDefinition = new Map();
21
23
  static anchor(options) {
22
24
  if (ModalManager.#anchor !== null)
23
25
  return ModalManager.#anchor;
@@ -36,13 +38,20 @@ class ModalManager {
36
38
  static get prerender() {
37
39
  return ModalManager.#prerenderList;
38
40
  }
39
- static #openHandler = (modal) => ModalManager.#prerenderList.push(modal);
41
+ static #openHandler = ((modal) => {
42
+ ModalManager.#prerenderList.push(modal);
43
+ });
40
44
  static set openHandler(handler) {
41
45
  ModalManager.#openHandler = handler;
42
46
  ModalManager.#prerenderList = [];
43
47
  }
44
- static #styleManager = styleManager.styleManagerFactory(ModalManager.#scope);
45
- static #styleSheetDefinition = new Map();
48
+ static #refreshHandler;
49
+ static set refreshHandler(handler) {
50
+ ModalManager.#refreshHandler = handler;
51
+ }
52
+ static refresh() {
53
+ ModalManager.#refreshHandler?.();
54
+ }
46
55
  static defineStyleSheet(styleId, css) {
47
56
  ModalManager.#styleSheetDefinition.set(styleId, util.compressCss(css));
48
57
  }
@@ -56,95 +65,113 @@ class ModalManager {
56
65
  static reset() {
57
66
  ModalManager.#anchor = null;
58
67
  ModalManager.#prerenderList = [];
59
- ModalManager.#openHandler = (modal) => ModalManager.#prerenderList.push(modal);
68
+ ModalManager.#openHandler = ((modal) => {
69
+ ModalManager.#prerenderList.push(modal);
70
+ });
71
+ ModalManager.#refreshHandler = undefined;
60
72
  styleManager.destroyScope(ModalManager.#scope);
61
73
  }
62
74
  static open(modal) {
63
- ModalManager.#openHandler(modal);
75
+ return ModalManager.#openHandler(modal);
64
76
  }
65
77
  }
66
78
 
67
- const alert = ({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }) => {
68
- return new Promise((resolve, reject) => {
79
+ const closeModal = (modalNode, refresh = true) => {
80
+ if (modalNode.visible === false)
81
+ return;
82
+ modalNode.onClose();
83
+ modalNode.onHide();
84
+ if (refresh)
85
+ ModalManager.refresh();
86
+ if (modalNode.manualDestroy || modalNode.alive === false)
87
+ return;
88
+ return setTimeout(() => modalNode.onDestroy(), modalNode.duration);
89
+ };
90
+
91
+ const subscribeAbortSignal = (modalNode, signal) => {
92
+ if (signal === undefined)
93
+ return null;
94
+ const handleAbort = () => closeModal(modalNode);
95
+ signal.addEventListener('abort', handleAbort, { once: true });
96
+ return () => signal.removeEventListener('abort', handleAbort);
97
+ };
98
+
99
+ const alertHandler = (args) => {
100
+ const modalNode = ModalManager.open({
101
+ ...args,
102
+ type: 'alert',
103
+ });
104
+ const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
105
+ const promiseHandler = new Promise((resolve, reject) => {
69
106
  try {
70
- ModalManager.open({
71
- type: 'alert',
72
- group,
73
- subtype,
74
- resolve: () => resolve(),
75
- title,
76
- subtitle,
77
- content,
78
- background,
79
- footer,
80
- dimmed,
81
- manualDestroy,
82
- closeOnBackdropClick,
83
- ForegroundComponent,
84
- BackgroundComponent,
85
- });
107
+ modalNode.handleResolve = () => {
108
+ unsubscribe?.();
109
+ resolve();
110
+ };
111
+ if (args.signal?.aborted)
112
+ closeModal(modalNode);
86
113
  }
87
114
  catch (error) {
115
+ closeModal(modalNode);
116
+ unsubscribe?.();
88
117
  reject(error);
89
118
  }
90
119
  });
120
+ return { modalNode, promiseHandler };
91
121
  };
92
122
 
93
- const confirm = ({ group, subtype, title, subtitle, content, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }) => {
94
- return new Promise((resolve, reject) => {
123
+ const confirmHandler = (args) => {
124
+ const modalNode = ModalManager.open({
125
+ ...args,
126
+ type: 'confirm',
127
+ });
128
+ const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
129
+ const promiseHandler = new Promise((resolve, reject) => {
95
130
  try {
96
- ModalManager.open({
97
- type: 'confirm',
98
- group,
99
- subtype,
100
- resolve: (result) => resolve(result ?? false),
101
- title,
102
- subtitle,
103
- content,
104
- background,
105
- footer,
106
- dimmed,
107
- manualDestroy,
108
- closeOnBackdropClick,
109
- ForegroundComponent,
110
- BackgroundComponent,
111
- });
131
+ modalNode.handleResolve = (result) => {
132
+ unsubscribe?.();
133
+ resolve(result ?? false);
134
+ };
135
+ if (args.signal?.aborted)
136
+ closeModal(modalNode);
112
137
  }
113
138
  catch (error) {
139
+ closeModal(modalNode);
140
+ unsubscribe?.();
114
141
  reject(error);
115
142
  }
116
143
  });
144
+ return { modalNode, promiseHandler };
117
145
  };
118
146
 
119
- const prompt = ({ group, title, subtitle, content, defaultValue, Input, disabled, returnOnCancel, background, footer, dimmed, manualDestroy, closeOnBackdropClick, ForegroundComponent, BackgroundComponent, }) => {
120
- return new Promise((resolve, reject) => {
147
+ const promptHandler = (args) => {
148
+ const modalNode = ModalManager.open({
149
+ ...args,
150
+ type: 'prompt',
151
+ });
152
+ const unsubscribe = subscribeAbortSignal(modalNode, args.signal);
153
+ const promiseHandler = new Promise((resolve, reject) => {
121
154
  try {
122
- ModalManager.open({
123
- type: 'prompt',
124
- group,
125
- resolve: (result) => resolve(result),
126
- title,
127
- subtitle,
128
- content,
129
- Input,
130
- defaultValue,
131
- disabled,
132
- returnOnCancel,
133
- background,
134
- footer,
135
- dimmed,
136
- manualDestroy,
137
- closeOnBackdropClick,
138
- ForegroundComponent,
139
- BackgroundComponent,
140
- });
155
+ modalNode.handleResolve = (result) => {
156
+ unsubscribe?.();
157
+ resolve(result);
158
+ };
159
+ if (args.signal?.aborted)
160
+ closeModal(modalNode);
141
161
  }
142
162
  catch (error) {
163
+ closeModal(modalNode);
164
+ unsubscribe?.();
143
165
  reject(error);
144
166
  }
145
167
  });
168
+ return { modalNode, promiseHandler };
146
169
  };
147
170
 
171
+ const alert = (args) => alertHandler(args).promiseHandler;
172
+ const confirm = (args) => confirmHandler(args).promiseHandler;
173
+ const prompt = (args) => promptHandler(args).promiseHandler;
174
+
148
175
  class AbstractNode {
149
176
  id;
150
177
  group;
@@ -152,9 +179,10 @@ class AbstractNode {
152
179
  title;
153
180
  subtitle;
154
181
  background;
182
+ dimmed;
183
+ duration;
155
184
  manualDestroy;
156
185
  closeOnBackdropClick;
157
- dimmed;
158
186
  ForegroundComponent;
159
187
  BackgroundComponent;
160
188
  #alive;
@@ -165,9 +193,12 @@ class AbstractNode {
165
193
  get visible() {
166
194
  return this.#visible;
167
195
  }
168
- #resolve;
196
+ #handleResolve;
197
+ set handleResolve(handleResolve) {
198
+ this.#handleResolve = handleResolve;
199
+ }
169
200
  #listeners = new Set();
170
- constructor({ id, initiator, group, title, subtitle, background, dimmed = true, manualDestroy = false, closeOnBackdropClick = true, resolve, ForegroundComponent, BackgroundComponent, }) {
201
+ constructor({ id, initiator, group, title, subtitle, background, dimmed = true, duration = 0, manualDestroy = false, closeOnBackdropClick = true, handleResolve, ForegroundComponent, BackgroundComponent, }) {
171
202
  this.id = id;
172
203
  this.group = group;
173
204
  this.initiator = initiator;
@@ -175,13 +206,17 @@ class AbstractNode {
175
206
  this.subtitle = subtitle;
176
207
  this.background = background;
177
208
  this.dimmed = dimmed;
209
+ this.duration = duration;
178
210
  this.manualDestroy = manualDestroy;
179
211
  this.closeOnBackdropClick = closeOnBackdropClick;
180
212
  this.ForegroundComponent = ForegroundComponent;
181
213
  this.BackgroundComponent = BackgroundComponent;
182
214
  this.#alive = true;
183
215
  this.#visible = true;
184
- this.#resolve = resolve;
216
+ this.#handleResolve = handleResolve;
217
+ }
218
+ onResolve(result) {
219
+ this.#handleResolve?.(result);
185
220
  }
186
221
  subscribe(listener) {
187
222
  this.#listeners.add(listener);
@@ -193,9 +228,6 @@ class AbstractNode {
193
228
  for (const listener of this.#listeners)
194
229
  listener();
195
230
  }
196
- resolve(result) {
197
- this.#resolve(result);
198
- }
199
231
  onDestroy() {
200
232
  const needPublish = this.#alive === true;
201
233
  this.#alive = false;
@@ -221,7 +253,7 @@ class AlertNode extends AbstractNode {
221
253
  subtype;
222
254
  content;
223
255
  footer;
224
- constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, manualDestroy, closeOnBackdropClick, resolve, ForegroundComponent, BackgroundComponent, }) {
256
+ constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, duration, manualDestroy, closeOnBackdropClick, handleResolve, ForegroundComponent, BackgroundComponent, }) {
225
257
  super({
226
258
  id,
227
259
  group,
@@ -230,9 +262,10 @@ class AlertNode extends AbstractNode {
230
262
  subtitle,
231
263
  background,
232
264
  dimmed,
265
+ duration,
233
266
  manualDestroy,
234
267
  closeOnBackdropClick,
235
- resolve,
268
+ handleResolve,
236
269
  ForegroundComponent,
237
270
  BackgroundComponent,
238
271
  });
@@ -242,10 +275,10 @@ class AlertNode extends AbstractNode {
242
275
  this.footer = footer;
243
276
  }
244
277
  onClose() {
245
- this.resolve(null);
278
+ this.onResolve(null);
246
279
  }
247
280
  onConfirm() {
248
- this.resolve(null);
281
+ this.onResolve(null);
249
282
  }
250
283
  }
251
284
 
@@ -254,7 +287,7 @@ class ConfirmNode extends AbstractNode {
254
287
  subtype;
255
288
  content;
256
289
  footer;
257
- constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, manualDestroy, closeOnBackdropClick, resolve, ForegroundComponent, BackgroundComponent, }) {
290
+ constructor({ id, group, initiator, type, subtype, title, subtitle, content, footer, background, dimmed, duration, manualDestroy, closeOnBackdropClick, handleResolve, ForegroundComponent, BackgroundComponent, }) {
258
291
  super({
259
292
  id,
260
293
  group,
@@ -263,9 +296,10 @@ class ConfirmNode extends AbstractNode {
263
296
  subtitle,
264
297
  background,
265
298
  dimmed,
299
+ duration,
266
300
  manualDestroy,
267
301
  closeOnBackdropClick,
268
- resolve,
302
+ handleResolve,
269
303
  ForegroundComponent,
270
304
  BackgroundComponent,
271
305
  });
@@ -275,10 +309,10 @@ class ConfirmNode extends AbstractNode {
275
309
  this.footer = footer;
276
310
  }
277
311
  onClose() {
278
- this.resolve(false);
312
+ this.onResolve(false);
279
313
  }
280
314
  onConfirm() {
281
- this.resolve(true);
315
+ this.onResolve(true);
282
316
  }
283
317
  }
284
318
 
@@ -291,7 +325,7 @@ class PromptNode extends AbstractNode {
291
325
  returnOnCancel;
292
326
  footer;
293
327
  #value;
294
- constructor({ id, group, initiator, type, title, subtitle, content, defaultValue, Input, disabled, returnOnCancel, footer, background, dimmed, manualDestroy, closeOnBackdropClick, resolve, ForegroundComponent, BackgroundComponent, }) {
328
+ constructor({ id, group, initiator, type, title, subtitle, content, defaultValue, Input, disabled, returnOnCancel, footer, background, dimmed, duration, manualDestroy, closeOnBackdropClick, handleResolve, ForegroundComponent, BackgroundComponent, }) {
295
329
  super({
296
330
  id,
297
331
  group,
@@ -300,9 +334,10 @@ class PromptNode extends AbstractNode {
300
334
  subtitle,
301
335
  background,
302
336
  dimmed,
337
+ duration,
303
338
  manualDestroy,
304
339
  closeOnBackdropClick,
305
- resolve,
340
+ handleResolve,
306
341
  ForegroundComponent,
307
342
  BackgroundComponent,
308
343
  });
@@ -319,13 +354,13 @@ class PromptNode extends AbstractNode {
319
354
  this.#value = value;
320
355
  }
321
356
  onConfirm() {
322
- this.resolve(this.#value ?? null);
357
+ this.onResolve(this.#value ?? null);
323
358
  }
324
359
  onClose() {
325
360
  if (this.returnOnCancel)
326
- this.resolve(this.#value ?? null);
361
+ this.onResolve(this.#value ?? null);
327
362
  else
328
- this.resolve(null);
363
+ this.onResolve(null);
329
364
  }
330
365
  }
331
366
 
@@ -364,8 +399,8 @@ const FallbackFooter = ({ confirmLabel, hideConfirm = false, cancelLabel, hideCa
364
399
  const ModalManagerContext = react.createContext({});
365
400
 
366
401
  const useModalManagerContext = () => react.useContext(ModalManagerContext);
367
- const useModal = (id) => {
368
- const { getModal } = useModalManagerContext();
402
+ const useModalManager = (id) => {
403
+ const { getModal } = react.useContext(ModalManagerContext);
369
404
  return react.useMemo(() => getModal(id), [id, getModal]);
370
405
  };
371
406
 
@@ -429,20 +464,28 @@ const ConfigurationContextProvider = react.memo(({ ForegroundComponent, Backgrou
429
464
  FooterComponent: react.memo(FooterComponent || FallbackFooter),
430
465
  });
431
466
  const options = hook.useSnapshot(inputOptions);
432
- const value = react.useMemo(() => ({
433
- ForegroundComponent: constant.ForegroundComponent,
434
- BackgroundComponent: constant.BackgroundComponent,
435
- TitleComponent: constant.TitleComponent,
436
- SubtitleComponent: constant.SubtitleComponent,
437
- ContentComponent: constant.ContentComponent,
438
- FooterComponent: constant.FooterComponent,
439
- options: {
440
- ...DEFAULT_OPTIONS,
441
- ...options,
442
- },
443
- }), [constant, options]);
467
+ const value = react.useMemo(() => {
468
+ const { backdrop: defaultBackdrop, ...defaultOptions } = DEFAULT_OPTIONS;
469
+ const backdrop = getBackdropStyle(options?.backdrop, defaultBackdrop);
470
+ return {
471
+ ForegroundComponent: constant.ForegroundComponent,
472
+ BackgroundComponent: constant.BackgroundComponent,
473
+ TitleComponent: constant.TitleComponent,
474
+ SubtitleComponent: constant.SubtitleComponent,
475
+ ContentComponent: constant.ContentComponent,
476
+ FooterComponent: constant.FooterComponent,
477
+ options: { ...defaultOptions, ...options, backdrop },
478
+ };
479
+ }, [constant, options]);
444
480
  return (jsxRuntime.jsx(ConfigurationContext.Provider, { value: value, children: children }));
445
481
  });
482
+ const getBackdropStyle = (backdrop, defaultBackdrop) => {
483
+ if (filter.isPlainObject(backdrop))
484
+ return backdrop;
485
+ if (filter.isString(backdrop))
486
+ return { backgroundColor: backdrop };
487
+ return defaultBackdrop;
488
+ };
446
489
 
447
490
  const useConfigurationContext = () => react.useContext(ConfigurationContext);
448
491
  const useConfigurationOptions = () => {
@@ -474,32 +517,26 @@ const ModalManagerContextProvider = react.memo(({ usePathname, children, }) => {
474
517
  const { manualDestroy, closeOnBackdropClick } = options;
475
518
  for (const data of ModalManager.prerender) {
476
519
  const modal = nodeFactory({
520
+ duration,
521
+ manualDestroy,
522
+ closeOnBackdropClick,
477
523
  ...data,
478
524
  id: modalIdSequence.current++,
479
525
  initiator: initiator.current,
480
- manualDestroy: data.manualDestroy !== undefined
481
- ? data.manualDestroy
482
- : manualDestroy,
483
- closeOnBackdropClick: data.closeOnBackdropClick !== undefined
484
- ? data.closeOnBackdropClick
485
- : closeOnBackdropClick,
486
526
  });
487
527
  modalDictionary.current.set(modal.id, modal);
488
528
  setModalIds((ids) => [...ids, modal.id]);
489
529
  }
490
530
  ModalManager.openHandler = (data) => {
491
- const modal = nodeFactory({
531
+ const modalNode = nodeFactory({
532
+ duration,
533
+ manualDestroy,
534
+ closeOnBackdropClick,
492
535
  ...data,
493
536
  id: modalIdSequence.current++,
494
537
  initiator: initiator.current,
495
- manualDestroy: data.manualDestroy !== undefined
496
- ? data.manualDestroy
497
- : manualDestroy,
498
- closeOnBackdropClick: data.closeOnBackdropClick !== undefined
499
- ? data.closeOnBackdropClick
500
- : closeOnBackdropClick,
501
538
  });
502
- modalDictionary.current.set(modal.id, modal);
539
+ modalDictionary.current.set(modalNode.id, modalNode);
503
540
  setModalIds((ids) => {
504
541
  const aliveIds = [];
505
542
  for (let i = 0, l = ids.length; i < l; i++) {
@@ -510,8 +547,9 @@ const ModalManagerContextProvider = react.memo(({ usePathname, children, }) => {
510
547
  else
511
548
  aliveIds.push(id);
512
549
  }
513
- return [...aliveIds, modal.id];
550
+ return [...aliveIds, modalNode.id];
514
551
  });
552
+ return modalNode;
515
553
  };
516
554
  });
517
555
  react.useLayoutEffect(() => {
@@ -526,78 +564,62 @@ const ModalManagerContextProvider = react.memo(({ usePathname, children, }) => {
526
564
  }
527
565
  initiator.current = pathname;
528
566
  }, [pathname]);
529
- const getModalNode = react.useCallback((modalId) => {
530
- return modalDictionary.current.get(modalId);
531
- }, []);
532
- const onDestroy = react.useCallback((modalId) => {
567
+ const getModalNodeRef = react.useRef((modalId) => modalDictionary.current.get(modalId));
568
+ const onDestroyRef = react.useRef((modalId) => {
533
569
  const modal = modalDictionary.current.get(modalId);
534
570
  if (!modal)
535
571
  return;
536
572
  modal.onDestroy();
537
- updaterRef.current?.();
538
- }, []);
539
- const updaterRef = react.useRef(undefined);
540
- const hideModal = react.useCallback((modalId) => {
573
+ ModalManager.refresh();
574
+ });
575
+ const hideModalRef = react.useRef((modalId) => {
541
576
  const modal = modalDictionary.current.get(modalId);
542
577
  if (!modal)
543
578
  return;
544
579
  modal.onHide();
545
- updaterRef.current?.();
546
- if (!modal.manualDestroy)
547
- setTimeout(() => {
548
- modal.onDestroy();
549
- }, duration);
550
- }, [duration]);
551
- const onChange = react.useCallback((modalId, value) => {
580
+ ModalManager.refresh();
581
+ if (modal.manualDestroy === false)
582
+ setTimeout(() => modal.onDestroy(), modal.duration);
583
+ });
584
+ const onChangeRef = react.useRef((modalId, value) => {
552
585
  const modal = modalDictionary.current.get(modalId);
553
586
  if (!modal)
554
587
  return;
555
588
  if (modal.type === 'prompt')
556
589
  modal.onChange(value);
557
- }, []);
558
- const onConfirm = react.useCallback((modalId) => {
590
+ });
591
+ const onConfirmRef = react.useRef((modalId) => {
559
592
  const modal = modalDictionary.current.get(modalId);
560
593
  if (!modal)
561
594
  return;
562
595
  modal.onConfirm();
563
- hideModal(modalId);
564
- }, [hideModal]);
565
- const onClose = react.useCallback((modalId) => {
596
+ hideModalRef.current(modalId);
597
+ });
598
+ const onCloseRef = react.useRef((modalId) => {
566
599
  const modal = modalDictionary.current.get(modalId);
567
600
  if (!modal)
568
601
  return;
569
602
  modal.onClose();
570
- hideModal(modalId);
571
- }, [hideModal]);
572
- const getModal = react.useCallback((modalId) => ({
573
- modal: getModalNode(modalId),
574
- onConfirm: () => onConfirm(modalId),
575
- onClose: () => onClose(modalId),
576
- onChange: (value) => onChange(modalId, value),
577
- onDestroy: () => onDestroy(modalId),
578
- }), [getModalNode, onConfirm, onClose, onChange, onDestroy]);
603
+ hideModalRef.current(modalId);
604
+ });
605
+ const getModalRef = react.useRef((modalId) => ({
606
+ modal: getModalNodeRef.current(modalId),
607
+ onConfirm: () => onConfirmRef.current(modalId),
608
+ onClose: () => onCloseRef.current(modalId),
609
+ onChange: (value) => onChangeRef.current(modalId, value),
610
+ onDestroy: () => onDestroyRef.current(modalId),
611
+ }));
579
612
  const value = react.useMemo(() => {
580
613
  return {
581
614
  modalIds,
582
- getModalNode,
583
- onChange,
584
- onConfirm,
585
- onClose,
586
- onDestroy,
587
- getModal,
588
- setUpdater: (updater) => {
589
- updaterRef.current = updater;
590
- },
615
+ getModalNode: getModalNodeRef.current,
616
+ onChange: onChangeRef.current,
617
+ onConfirm: onConfirmRef.current,
618
+ onClose: onCloseRef.current,
619
+ onDestroy: onDestroyRef.current,
620
+ getModal: getModalRef.current,
591
621
  };
592
- }, [
593
- modalIds,
594
- getModal,
595
- getModalNode,
596
- onChange,
597
- onConfirm,
598
- onClose,
599
- onDestroy,
600
- ]);
622
+ }, [modalIds]);
601
623
  return (jsxRuntime.jsx(ModalManagerContext.Provider, { value: value, children: children }));
602
624
  });
603
625
 
@@ -666,7 +688,7 @@ ModalManager.defineStyleSheet('background', style$3);
666
688
  const BackgroundFrame = ({ modalId, onChangeOrder, }) => {
667
689
  const { BackgroundComponent } = useConfigurationContext();
668
690
  const { context: userDefinedContext } = useUserDefinedContext();
669
- const { modal, onClose, onChange, onConfirm, onDestroy } = useModal(modalId);
691
+ const { modal, onClose, onChange, onConfirm, onDestroy } = useModalManager(modalId);
670
692
  const handleClose = react.useCallback((event) => {
671
693
  if (modal && modal.closeOnBackdropClick && modal.visible)
672
694
  onClose();
@@ -782,7 +804,7 @@ ModalManager.defineStyleSheet('foreground', style$2);
782
804
  const ForegroundFrame = ({ modalId, onChangeOrder, }) => {
783
805
  const { ForegroundComponent } = useConfigurationContext();
784
806
  const { context: userDefinedContext } = useUserDefinedContext();
785
- const { modal, onChange, onConfirm, onClose, onDestroy } = useModal(modalId);
807
+ const { modal, onChange, onConfirm, onClose, onDestroy } = useModalManager(modalId);
786
808
  const Foreground = react.useMemo(() => modal?.ForegroundComponent || ForegroundComponent, [ForegroundComponent, modal]);
787
809
  if (!modal)
788
810
  return null;
@@ -814,7 +836,7 @@ ModalManager.defineStyleSheet('presenter', style$1);
814
836
  const Presenter = react.memo(({ modalId, getValue, increment }) => {
815
837
  const ref = react.useRef(null);
816
838
  const options = useConfigurationOptions();
817
- const { modal } = useModal(modalId);
839
+ const { modal } = useModalManager(modalId);
818
840
  useSubscribeModal(modal);
819
841
  hook.useOnMountLayout(() => {
820
842
  if (ref.current === null)
@@ -842,27 +864,41 @@ const style = `
842
864
  inset: 0;
843
865
  pointer-events: none;
844
866
  z-index: var(--z-index);
845
- transition: background-color ease-in-out;
867
+ }`;
868
+ const backdrop = ModalManager.getHashedClassNames('backdrop');
869
+ const backdropStyle = `
870
+ .${backdrop} {
871
+ position: fixed;
872
+ inset: 0;
873
+ opacity: 0;
874
+ transition-property: opacity;
875
+ transition-duration: var(--transition-duration);
876
+ transition-timing-function: ease-in-out;
877
+ pointer-events: none;
846
878
  }`;
847
879
  ModalManager.defineStyleSheet('anchor', style);
880
+ ModalManager.defineStyleSheet('backdrop', backdropStyle);
848
881
 
849
882
  const { getValue, increment, reset } = lib.counterFactory(0);
850
883
  const AnchorInner = () => {
851
884
  const [version, update] = hook.useVersion();
852
- const { modalIds, setUpdater } = useModalManagerContext();
885
+ const { modalIds } = useModalManagerContext();
853
886
  react.useEffect(() => {
854
- setUpdater(update);
855
- }, [setUpdater, update]);
887
+ ModalManager.refreshHandler = update;
888
+ }, [update]);
856
889
  const options = useConfigurationOptions();
857
890
  const dimmed = useActiveModalCount(validateDimmable, version);
858
891
  if (!dimmed)
859
892
  reset();
860
- const backdropStyle = react.useMemo(() => ({
861
- ...(dimmed ? options.backdrop : {}),
893
+ const anchorStyle = react.useMemo(() => ({
862
894
  '--z-index': options.zIndex,
863
- transitionDuration: options.duration,
895
+ '--transition-duration': options.duration,
896
+ }), [options]);
897
+ const backdropStyle = react.useMemo(() => ({
898
+ ...options.backdrop,
899
+ opacity: dimmed ? 1 : 0,
864
900
  }), [dimmed, options]);
865
- return (jsxRuntime.jsx("div", { className: anchor, style: backdropStyle, children: array.map(modalIds, (id) => (jsxRuntime.jsx(Presenter, { modalId: id, getValue: getValue, increment: increment, reset: reset }, id))) }));
901
+ return (jsxRuntime.jsxs("div", { className: anchor, style: anchorStyle, children: [jsxRuntime.jsx("div", { className: backdrop, style: backdropStyle }), array.map(modalIds, (id) => (jsxRuntime.jsx(Presenter, { modalId: id, getValue: getValue, increment: increment, reset: reset }, id)))] }));
866
902
  };
867
903
  const validateDimmable = (modal) => modal?.visible && modal.dimmed;
868
904
  const Anchor = react.memo(hoc.withErrorBoundary(AnchorInner));
@@ -949,8 +985,38 @@ const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, B
949
985
  return { portal, initialize };
950
986
  };
951
987
 
988
+ const useModal = () => {
989
+ const modalNodesRef = react.useRef([]);
990
+ const alertRef = react.useRef((args) => {
991
+ const { modalNode, promiseHandler } = alertHandler(args);
992
+ modalNodesRef.current.push(modalNode);
993
+ return promiseHandler;
994
+ });
995
+ const confirmRef = react.useRef((args) => {
996
+ const { modalNode, promiseHandler } = confirmHandler(args);
997
+ modalNodesRef.current.push(modalNode);
998
+ return promiseHandler;
999
+ });
1000
+ const promptRef = react.useRef((args) => {
1001
+ const { modalNode, promiseHandler } = promptHandler(args);
1002
+ modalNodesRef.current.push(modalNode);
1003
+ return promiseHandler;
1004
+ });
1005
+ hook.useOnUnmount(() => {
1006
+ for (const node of modalNodesRef.current)
1007
+ closeModal(node, false);
1008
+ ModalManager.refresh();
1009
+ modalNodesRef.current = [];
1010
+ });
1011
+ return {
1012
+ alert: alertRef.current,
1013
+ confirm: confirmRef.current,
1014
+ prompt: promptRef.current,
1015
+ };
1016
+ };
1017
+
952
1018
  const useDestroyAfter = (modalId, duration) => {
953
- const { modal, onDestroy } = useModal(modalId);
1019
+ const { modal, onDestroy } = useModalManager(modalId);
954
1020
  const tick = useSubscribeModal(modal);
955
1021
  const reference = react.useRef({
956
1022
  modal,
@@ -998,6 +1064,7 @@ exports.prompt = prompt;
998
1064
  exports.useActiveModalCount = useActiveModalCount;
999
1065
  exports.useDestroyAfter = useDestroyAfter;
1000
1066
  exports.useInitializeModal = useBootstrap;
1067
+ exports.useModal = useModal;
1001
1068
  exports.useModalAnimation = useModalAnimation;
1002
1069
  exports.useModalBackdrop = useConfigurationBackdrop;
1003
1070
  exports.useModalDuration = useConfigurationDuration;