@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.mjs CHANGED
@@ -1,21 +1,23 @@
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';
8
8
  import { compressCss, cxLite } from '@winglet/style-utils/util';
9
+ import { isPlainObject, isString, isFunction } from '@winglet/common-utils/filter';
9
10
  import { createPortal } from 'react-dom';
10
11
  import { map } from '@winglet/common-utils/array';
11
12
  import { withErrorBoundary } from '@winglet/react-utils/hoc';
12
- import { isString, isFunction } from '@winglet/common-utils/filter';
13
13
  import { renderComponent } from '@winglet/react-utils/render';
14
14
 
15
15
  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
 
@@ -427,20 +462,28 @@ const ConfigurationContextProvider = memo(({ ForegroundComponent, BackgroundComp
427
462
  FooterComponent: memo(FooterComponent || FallbackFooter),
428
463
  });
429
464
  const options = useSnapshot(inputOptions);
430
- const value = useMemo(() => ({
431
- ForegroundComponent: constant.ForegroundComponent,
432
- BackgroundComponent: constant.BackgroundComponent,
433
- TitleComponent: constant.TitleComponent,
434
- SubtitleComponent: constant.SubtitleComponent,
435
- ContentComponent: constant.ContentComponent,
436
- FooterComponent: constant.FooterComponent,
437
- options: {
438
- ...DEFAULT_OPTIONS,
439
- ...options,
440
- },
441
- }), [constant, options]);
465
+ const value = useMemo(() => {
466
+ const { backdrop: defaultBackdrop, ...defaultOptions } = DEFAULT_OPTIONS;
467
+ const backdrop = getBackdropStyle(options?.backdrop, defaultBackdrop);
468
+ return {
469
+ ForegroundComponent: constant.ForegroundComponent,
470
+ BackgroundComponent: constant.BackgroundComponent,
471
+ TitleComponent: constant.TitleComponent,
472
+ SubtitleComponent: constant.SubtitleComponent,
473
+ ContentComponent: constant.ContentComponent,
474
+ FooterComponent: constant.FooterComponent,
475
+ options: { ...defaultOptions, ...options, backdrop },
476
+ };
477
+ }, [constant, options]);
442
478
  return (jsx(ConfigurationContext.Provider, { value: value, children: children }));
443
479
  });
480
+ const getBackdropStyle = (backdrop, defaultBackdrop) => {
481
+ if (isPlainObject(backdrop))
482
+ return backdrop;
483
+ if (isString(backdrop))
484
+ return { backgroundColor: backdrop };
485
+ return defaultBackdrop;
486
+ };
444
487
 
445
488
  const useConfigurationContext = () => useContext(ConfigurationContext);
446
489
  const useConfigurationOptions = () => {
@@ -472,32 +515,26 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
472
515
  const { manualDestroy, closeOnBackdropClick } = options;
473
516
  for (const data of ModalManager.prerender) {
474
517
  const modal = nodeFactory({
518
+ duration,
519
+ manualDestroy,
520
+ closeOnBackdropClick,
475
521
  ...data,
476
522
  id: modalIdSequence.current++,
477
523
  initiator: initiator.current,
478
- manualDestroy: data.manualDestroy !== undefined
479
- ? data.manualDestroy
480
- : manualDestroy,
481
- closeOnBackdropClick: data.closeOnBackdropClick !== undefined
482
- ? data.closeOnBackdropClick
483
- : closeOnBackdropClick,
484
524
  });
485
525
  modalDictionary.current.set(modal.id, modal);
486
526
  setModalIds((ids) => [...ids, modal.id]);
487
527
  }
488
528
  ModalManager.openHandler = (data) => {
489
- const modal = nodeFactory({
529
+ const modalNode = nodeFactory({
530
+ duration,
531
+ manualDestroy,
532
+ closeOnBackdropClick,
490
533
  ...data,
491
534
  id: modalIdSequence.current++,
492
535
  initiator: initiator.current,
493
- manualDestroy: data.manualDestroy !== undefined
494
- ? data.manualDestroy
495
- : manualDestroy,
496
- closeOnBackdropClick: data.closeOnBackdropClick !== undefined
497
- ? data.closeOnBackdropClick
498
- : closeOnBackdropClick,
499
536
  });
500
- modalDictionary.current.set(modal.id, modal);
537
+ modalDictionary.current.set(modalNode.id, modalNode);
501
538
  setModalIds((ids) => {
502
539
  const aliveIds = [];
503
540
  for (let i = 0, l = ids.length; i < l; i++) {
@@ -508,8 +545,9 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
508
545
  else
509
546
  aliveIds.push(id);
510
547
  }
511
- return [...aliveIds, modal.id];
548
+ return [...aliveIds, modalNode.id];
512
549
  });
550
+ return modalNode;
513
551
  };
514
552
  });
515
553
  useLayoutEffect(() => {
@@ -524,78 +562,62 @@ const ModalManagerContextProvider = memo(({ usePathname, children, }) => {
524
562
  }
525
563
  initiator.current = pathname;
526
564
  }, [pathname]);
527
- const getModalNode = useCallback((modalId) => {
528
- return modalDictionary.current.get(modalId);
529
- }, []);
530
- const onDestroy = useCallback((modalId) => {
565
+ const getModalNodeRef = useRef((modalId) => modalDictionary.current.get(modalId));
566
+ const onDestroyRef = useRef((modalId) => {
531
567
  const modal = modalDictionary.current.get(modalId);
532
568
  if (!modal)
533
569
  return;
534
570
  modal.onDestroy();
535
- updaterRef.current?.();
536
- }, []);
537
- const updaterRef = useRef(undefined);
538
- const hideModal = useCallback((modalId) => {
571
+ ModalManager.refresh();
572
+ });
573
+ const hideModalRef = useRef((modalId) => {
539
574
  const modal = modalDictionary.current.get(modalId);
540
575
  if (!modal)
541
576
  return;
542
577
  modal.onHide();
543
- updaterRef.current?.();
544
- if (!modal.manualDestroy)
545
- setTimeout(() => {
546
- modal.onDestroy();
547
- }, duration);
548
- }, [duration]);
549
- 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) => {
550
583
  const modal = modalDictionary.current.get(modalId);
551
584
  if (!modal)
552
585
  return;
553
586
  if (modal.type === 'prompt')
554
587
  modal.onChange(value);
555
- }, []);
556
- const onConfirm = useCallback((modalId) => {
588
+ });
589
+ const onConfirmRef = useRef((modalId) => {
557
590
  const modal = modalDictionary.current.get(modalId);
558
591
  if (!modal)
559
592
  return;
560
593
  modal.onConfirm();
561
- hideModal(modalId);
562
- }, [hideModal]);
563
- const onClose = useCallback((modalId) => {
594
+ hideModalRef.current(modalId);
595
+ });
596
+ const onCloseRef = useRef((modalId) => {
564
597
  const modal = modalDictionary.current.get(modalId);
565
598
  if (!modal)
566
599
  return;
567
600
  modal.onClose();
568
- hideModal(modalId);
569
- }, [hideModal]);
570
- const getModal = useCallback((modalId) => ({
571
- modal: getModalNode(modalId),
572
- onConfirm: () => onConfirm(modalId),
573
- onClose: () => onClose(modalId),
574
- onChange: (value) => onChange(modalId, value),
575
- onDestroy: () => onDestroy(modalId),
576
- }), [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
+ }));
577
610
  const value = useMemo(() => {
578
611
  return {
579
612
  modalIds,
580
- getModalNode,
581
- onChange,
582
- onConfirm,
583
- onClose,
584
- onDestroy,
585
- getModal,
586
- setUpdater: (updater) => {
587
- updaterRef.current = updater;
588
- },
613
+ getModalNode: getModalNodeRef.current,
614
+ onChange: onChangeRef.current,
615
+ onConfirm: onConfirmRef.current,
616
+ onClose: onCloseRef.current,
617
+ onDestroy: onDestroyRef.current,
618
+ getModal: getModalRef.current,
589
619
  };
590
- }, [
591
- modalIds,
592
- getModal,
593
- getModalNode,
594
- onChange,
595
- onConfirm,
596
- onClose,
597
- onDestroy,
598
- ]);
620
+ }, [modalIds]);
599
621
  return (jsx(ModalManagerContext.Provider, { value: value, children: children }));
600
622
  });
601
623
 
@@ -664,7 +686,7 @@ ModalManager.defineStyleSheet('background', style$3);
664
686
  const BackgroundFrame = ({ modalId, onChangeOrder, }) => {
665
687
  const { BackgroundComponent } = useConfigurationContext();
666
688
  const { context: userDefinedContext } = useUserDefinedContext();
667
- const { modal, onClose, onChange, onConfirm, onDestroy } = useModal(modalId);
689
+ const { modal, onClose, onChange, onConfirm, onDestroy } = useModalManager(modalId);
668
690
  const handleClose = useCallback((event) => {
669
691
  if (modal && modal.closeOnBackdropClick && modal.visible)
670
692
  onClose();
@@ -780,7 +802,7 @@ ModalManager.defineStyleSheet('foreground', style$2);
780
802
  const ForegroundFrame = ({ modalId, onChangeOrder, }) => {
781
803
  const { ForegroundComponent } = useConfigurationContext();
782
804
  const { context: userDefinedContext } = useUserDefinedContext();
783
- const { modal, onChange, onConfirm, onClose, onDestroy } = useModal(modalId);
805
+ const { modal, onChange, onConfirm, onClose, onDestroy } = useModalManager(modalId);
784
806
  const Foreground = useMemo(() => modal?.ForegroundComponent || ForegroundComponent, [ForegroundComponent, modal]);
785
807
  if (!modal)
786
808
  return null;
@@ -812,7 +834,7 @@ ModalManager.defineStyleSheet('presenter', style$1);
812
834
  const Presenter = memo(({ modalId, getValue, increment }) => {
813
835
  const ref = useRef(null);
814
836
  const options = useConfigurationOptions();
815
- const { modal } = useModal(modalId);
837
+ const { modal } = useModalManager(modalId);
816
838
  useSubscribeModal(modal);
817
839
  useOnMountLayout(() => {
818
840
  if (ref.current === null)
@@ -840,27 +862,41 @@ const style = `
840
862
  inset: 0;
841
863
  pointer-events: none;
842
864
  z-index: var(--z-index);
843
- transition: background-color ease-in-out;
865
+ }`;
866
+ const backdrop = ModalManager.getHashedClassNames('backdrop');
867
+ const backdropStyle = `
868
+ .${backdrop} {
869
+ position: fixed;
870
+ inset: 0;
871
+ opacity: 0;
872
+ transition-property: opacity;
873
+ transition-duration: var(--transition-duration);
874
+ transition-timing-function: ease-in-out;
875
+ pointer-events: none;
844
876
  }`;
845
877
  ModalManager.defineStyleSheet('anchor', style);
878
+ ModalManager.defineStyleSheet('backdrop', backdropStyle);
846
879
 
847
880
  const { getValue, increment, reset } = counterFactory(0);
848
881
  const AnchorInner = () => {
849
882
  const [version, update] = useVersion();
850
- const { modalIds, setUpdater } = useModalManagerContext();
883
+ const { modalIds } = useModalManagerContext();
851
884
  useEffect(() => {
852
- setUpdater(update);
853
- }, [setUpdater, update]);
885
+ ModalManager.refreshHandler = update;
886
+ }, [update]);
854
887
  const options = useConfigurationOptions();
855
888
  const dimmed = useActiveModalCount(validateDimmable, version);
856
889
  if (!dimmed)
857
890
  reset();
858
- const backdropStyle = useMemo(() => ({
859
- ...(dimmed ? options.backdrop : {}),
891
+ const anchorStyle = useMemo(() => ({
860
892
  '--z-index': options.zIndex,
861
- transitionDuration: options.duration,
893
+ '--transition-duration': options.duration,
894
+ }), [options]);
895
+ const backdropStyle = useMemo(() => ({
896
+ ...options.backdrop,
897
+ opacity: dimmed ? 1 : 0,
862
898
  }), [dimmed, options]);
863
- return (jsx("div", { className: anchor, style: backdropStyle, children: map(modalIds, (id) => (jsx(Presenter, { modalId: id, getValue: getValue, increment: increment, reset: reset }, id))) }));
899
+ return (jsxs("div", { className: anchor, style: anchorStyle, children: [jsx("div", { className: backdrop, style: backdropStyle }), map(modalIds, (id) => (jsx(Presenter, { modalId: id, getValue: getValue, increment: increment, reset: reset }, id)))] }));
864
900
  };
865
901
  const validateDimmable = (modal) => modal?.visible && modal.dimmed;
866
902
  const Anchor = memo(withErrorBoundary(AnchorInner));
@@ -947,8 +983,38 @@ const useBootstrap = ({ usePathname: useExternalPathname, ForegroundComponent, B
947
983
  return { portal, initialize };
948
984
  };
949
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
+
950
1016
  const useDestroyAfter = (modalId, duration) => {
951
- const { modal, onDestroy } = useModal(modalId);
1017
+ const { modal, onDestroy } = useModalManager(modalId);
952
1018
  const tick = useSubscribeModal(modal);
953
1019
  const reference = useRef({
954
1020
  modal,
@@ -989,4 +1055,4 @@ const useModalAnimation = (visible, handler) => {
989
1055
  }, [visible]);
990
1056
  };
991
1057
 
992
- 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 };