@monstermann/signals-modal 0.4.2 → 0.4.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.
Files changed (124) hide show
  1. package/README.md +2011 -0
  2. package/dist/anchor/getAnchorElement.d.mts +30 -0
  3. package/dist/anchor/getAnchorElement.mjs +30 -0
  4. package/dist/anchor/getAnchorMeasurement.d.mts +39 -1
  5. package/dist/anchor/getAnchorMeasurement.mjs +37 -0
  6. package/dist/anchor/setAnchorElement.d.mts +26 -0
  7. package/dist/anchor/setAnchorElement.mjs +26 -0
  8. package/dist/anchor/withAnchorElement.d.mts +31 -0
  9. package/dist/anchor/withAnchorElement.mjs +31 -1
  10. package/dist/anchor/withAnchorMeasurement.d.mts +44 -1
  11. package/dist/anchor/withAnchorMeasurement.mjs +45 -3
  12. package/dist/anchor/withMouseAnchor.d.mts +35 -1
  13. package/dist/anchor/withMouseAnchor.mjs +36 -3
  14. package/dist/createModal.d.mts +29 -0
  15. package/dist/createModal.mjs +29 -0
  16. package/dist/floating/getFloatingElement.d.mts +30 -0
  17. package/dist/floating/getFloatingElement.mjs +30 -0
  18. package/dist/floating/getFloatingMeasurement.d.mts +39 -1
  19. package/dist/floating/getFloatingMeasurement.mjs +37 -0
  20. package/dist/floating/setFloatingElement.d.mts +26 -0
  21. package/dist/floating/setFloatingElement.mjs +26 -0
  22. package/dist/floating/withFloatingElement.d.mts +31 -0
  23. package/dist/floating/withFloatingElement.mjs +31 -1
  24. package/dist/floating/withFloatingMeasurement.d.mts +42 -1
  25. package/dist/floating/withFloatingMeasurement.mjs +43 -3
  26. package/dist/groups/getDialogs.d.mts +29 -0
  27. package/dist/groups/getDialogs.mjs +29 -0
  28. package/dist/groups/getGroupsForModal.d.mts +29 -0
  29. package/dist/groups/getGroupsForModal.mjs +29 -0
  30. package/dist/groups/getModalsForGroup.d.mts +29 -0
  31. package/dist/groups/getModalsForGroup.mjs +29 -0
  32. package/dist/groups/getPopovers.d.mts +29 -0
  33. package/dist/groups/getPopovers.mjs +29 -0
  34. package/dist/groups/getTooltips.d.mts +29 -0
  35. package/dist/groups/getTooltips.mjs +29 -0
  36. package/dist/groups/isDialog.d.mts +29 -0
  37. package/dist/groups/isDialog.mjs +29 -0
  38. package/dist/groups/isModalInGroup.d.mts +29 -0
  39. package/dist/groups/isModalInGroup.mjs +29 -0
  40. package/dist/groups/isPopover.d.mts +29 -0
  41. package/dist/groups/isPopover.mjs +29 -0
  42. package/dist/groups/isTooltip.d.mts +29 -0
  43. package/dist/groups/isTooltip.mjs +29 -0
  44. package/dist/groups/modalGroups.mjs +42 -0
  45. package/dist/groups/withModalGroups.d.mts +30 -0
  46. package/dist/groups/withModalGroups.mjs +30 -1
  47. package/dist/position/getModalPlacement.d.mts +50 -0
  48. package/dist/position/getModalPlacement.mjs +49 -0
  49. package/dist/position/getModalPosition.d.mts +59 -0
  50. package/dist/position/getModalPosition.mjs +58 -0
  51. package/dist/position/withBoundary.d.mts +32 -1
  52. package/dist/position/withBoundary.mjs +33 -3
  53. package/dist/position/withPlacement.d.mts +78 -1
  54. package/dist/position/withPlacement.mjs +78 -1
  55. package/dist/position/withPosition.d.mts +67 -1
  56. package/dist/position/withPosition.mjs +68 -2
  57. package/dist/scroll/withCloseOnScroll.d.mts +37 -0
  58. package/dist/scroll/withCloseOnScroll.mjs +37 -1
  59. package/dist/status/closeAllModals.d.mts +33 -0
  60. package/dist/status/closeAllModals.mjs +33 -0
  61. package/dist/status/closeLastModal.d.mts +18 -0
  62. package/dist/status/closeLastModal.mjs +18 -0
  63. package/dist/status/closeModal.d.mts +28 -0
  64. package/dist/status/closeModal.mjs +28 -0
  65. package/dist/status/getClosedModals.d.mts +32 -0
  66. package/dist/status/getClosedModals.mjs +32 -0
  67. package/dist/status/getClosingModals.d.mts +36 -0
  68. package/dist/status/getClosingModals.mjs +36 -0
  69. package/dist/status/getModalStatus.d.mts +31 -0
  70. package/dist/status/getModalStatus.mjs +30 -0
  71. package/dist/status/getOpenModals.d.mts +36 -0
  72. package/dist/status/getOpenModals.mjs +36 -0
  73. package/dist/status/getOpenedModals.d.mts +32 -0
  74. package/dist/status/getOpenedModals.mjs +32 -0
  75. package/dist/status/getOpeningModals.d.mts +36 -0
  76. package/dist/status/getOpeningModals.mjs +36 -0
  77. package/dist/status/getVisibleModals.d.mts +36 -0
  78. package/dist/status/getVisibleModals.mjs +36 -0
  79. package/dist/status/internals.mjs +15 -6
  80. package/dist/status/isAnyModalClosed.d.mts +32 -0
  81. package/dist/status/isAnyModalClosed.mjs +32 -0
  82. package/dist/status/isAnyModalClosing.d.mts +35 -0
  83. package/dist/status/isAnyModalClosing.mjs +35 -0
  84. package/dist/status/isAnyModalOpen.d.mts +32 -0
  85. package/dist/status/isAnyModalOpen.mjs +32 -0
  86. package/dist/status/isAnyModalOpened.d.mts +32 -0
  87. package/dist/status/isAnyModalOpened.mjs +32 -0
  88. package/dist/status/isAnyModalOpening.d.mts +35 -0
  89. package/dist/status/isAnyModalOpening.mjs +35 -0
  90. package/dist/status/isAnyModalVisible.d.mts +32 -0
  91. package/dist/status/isAnyModalVisible.mjs +32 -0
  92. package/dist/status/isModalClosed.d.mts +28 -0
  93. package/dist/status/isModalClosed.mjs +28 -0
  94. package/dist/status/isModalClosing.d.mts +32 -0
  95. package/dist/status/isModalClosing.mjs +32 -0
  96. package/dist/status/isModalOpen.d.mts +28 -0
  97. package/dist/status/isModalOpen.mjs +28 -0
  98. package/dist/status/isModalOpened.d.mts +30 -0
  99. package/dist/status/isModalOpened.mjs +30 -0
  100. package/dist/status/isModalOpening.d.mts +30 -0
  101. package/dist/status/isModalOpening.mjs +30 -0
  102. package/dist/status/isModalVisible.d.mts +30 -0
  103. package/dist/status/isModalVisible.mjs +30 -0
  104. package/dist/status/onModalClosed.d.mts +31 -0
  105. package/dist/status/onModalClosed.mjs +31 -1
  106. package/dist/status/onModalClosing.d.mts +33 -2
  107. package/dist/status/onModalClosing.mjs +31 -1
  108. package/dist/status/onModalOpened.d.mts +31 -0
  109. package/dist/status/onModalOpened.mjs +31 -1
  110. package/dist/status/onModalOpening.d.mts +33 -2
  111. package/dist/status/onModalOpening.mjs +31 -1
  112. package/dist/status/openModal.d.mts +26 -0
  113. package/dist/status/openModal.mjs +26 -0
  114. package/dist/status/setModalStatus.d.mts +29 -0
  115. package/dist/status/setModalStatus.mjs +28 -0
  116. package/dist/status/withModalStatus.d.mts +41 -0
  117. package/dist/status/withModalStatus.mjs +43 -3
  118. package/dist/utils/closeLastModalOnClickOutside.d.mts +18 -0
  119. package/dist/utils/closeLastModalOnClickOutside.mjs +18 -0
  120. package/dist/utils/closeLastModalOnEsc.d.mts +18 -0
  121. package/dist/utils/closeLastModalOnEsc.mjs +18 -0
  122. package/dist/utils/syncModalGroupsToBody.d.mts +37 -0
  123. package/dist/utils/syncModalGroupsToBody.mjs +38 -1
  124. package/package.json +6 -5
package/README.md CHANGED
@@ -2,8 +2,2019 @@
2
2
 
3
3
  <h1>signals-modal</h1>
4
4
 
5
+ ![Minified](https://img.shields.io/badge/Minified-41.12_KB-blue?style=flat-square&labelColor=%2315161D&color=%2369a1ff) ![Minzipped](https://img.shields.io/badge/Minzipped-8.77_KB-blue?style=flat-square&labelColor=%2315161D&color=%2369a1ff)
6
+
5
7
  **Composable modal management.**
6
8
 
7
9
  [Documentation](https://MichaelOstermann.github.io/signals-modal)
8
10
 
9
11
  </div>
12
+
13
+ ## Example
14
+
15
+ ```ts
16
+ const modal = createModal("key", () => {
17
+ const { $status } = withModalStatus();
18
+
19
+ const $anchorElement = withAnchorElement();
20
+ const $anchorMeasurement = withAnchorMeasurement({
21
+ $anchorElement,
22
+ $status,
23
+ });
24
+
25
+ const $floatingElement = withFloatingElement();
26
+ const $floatingMeasurement = withFloatingMeasurement({
27
+ $floatingElement,
28
+ $status,
29
+ });
30
+
31
+ const $boundary = withBoundary({
32
+ $status,
33
+ transform: (rect) => rect,
34
+ });
35
+
36
+ const $placement = withPlacement({
37
+ placement: "down-center",
38
+ $anchorMeasurement,
39
+ $boundary,
40
+ $floatingMeasurement,
41
+ });
42
+
43
+ const $position = withPosition({
44
+ $anchorMeasurement,
45
+ $boundary,
46
+ $floatingMeasurement,
47
+ $placement,
48
+ });
49
+
50
+ return {
51
+ $anchorElement,
52
+ $floatingElement,
53
+ $position,
54
+ $status,
55
+ };
56
+ });
57
+
58
+ const anchor = document.querySelector(".anchor");
59
+ const floating = document.querySelector(".popover");
60
+
61
+ // Directly access the returned properties:
62
+ modal.$anchor(anchor);
63
+ modal.$floating(floating);
64
+ modal.$status("opened");
65
+ const { floatingX, floatingY, maxHeight, maxWidth } = modal.$position();
66
+
67
+ // Or use the global utilities:
68
+ setAnchorElement("key", anchor);
69
+ setFloatingElement("key", floating);
70
+ setModalStatus("key", "opened");
71
+ const { floatingX, floatingY, maxHeight, maxWidth } = getModalPosition("key");
72
+ ```
73
+
74
+ ## Installation
75
+
76
+ ```sh [npm]
77
+ npm install @monstermann/signals-modal
78
+ ```
79
+
80
+ ```sh [pnpm]
81
+ pnpm add @monstermann/signals-modal
82
+ ```
83
+
84
+ ```sh [yarn]
85
+ yarn add @monstermann/signals-modal
86
+ ```
87
+
88
+ ```sh [bun]
89
+ bun add @monstermann/signals-modal
90
+ ```
91
+
92
+ ## Anchor
93
+
94
+ ### getAnchorElement
95
+
96
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
97
+
98
+ ```ts
99
+ function getAnchorElement(key: string): HTMLElement | undefined;
100
+ ```
101
+
102
+ Retrieves the current anchor element for the given `key`.
103
+
104
+ #### Example
105
+
106
+ ```ts
107
+ import {
108
+ createModal,
109
+ withAnchorElement,
110
+ setAnchorElement,
111
+ getAnchorElement,
112
+ } from "@monstermann/signals-modal";
113
+
114
+ createModal("key", () => {
115
+ withAnchorElement();
116
+ });
117
+
118
+ setAnchorElement("key", document.querySelector(".anchor"));
119
+ getAnchorElement("key"); // HTMLElement
120
+ ```
121
+
122
+ ### getAnchorMeasurement
123
+
124
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
125
+
126
+ ```ts
127
+ function getAnchorMeasurement(key: string): Rect;
128
+ ```
129
+
130
+ Retrieves the current result of `withAnchorMeasurement` or `withMouseAnchor`, falling back to an empty `Rect`.
131
+
132
+ #### Example
133
+
134
+ ```ts
135
+ import {
136
+ createModal,
137
+ withAnchorElement,
138
+ withModalStatus,
139
+ withAnchorMeasurement,
140
+ getAnchorMeasurement,
141
+ } from "@monstermann/signals-modal";
142
+
143
+ createModal("key", () => {
144
+ const { $status } = withModalStatus();
145
+ const $anchorElement = withAnchorElement();
146
+ // Memo({ top: number, left: number, width: number, height: number })
147
+ const $anchorMeasurement = withAnchorMeasurement({
148
+ $status,
149
+ $anchorElement,
150
+ });
151
+ });
152
+
153
+ // { top: number, left: number, width: number, height: number }
154
+ getAnchorMeasurement("key");
155
+ ```
156
+
157
+ ### setAnchorElement
158
+
159
+ ```ts
160
+ function setAnchorElement(key: string, element: HTMLElement | null): void;
161
+ ```
162
+
163
+ Sets the current anchor element for the given `key`.
164
+
165
+ #### Example
166
+
167
+ ```ts
168
+ import {
169
+ createModal,
170
+ withAnchorElement,
171
+ getAnchorElement,
172
+ } from "@monstermann/signals-modal";
173
+
174
+ createModal("key", () => {
175
+ withAnchorElement();
176
+ });
177
+
178
+ setAnchorElement("key", document.querySelector(".anchor"));
179
+ ```
180
+
181
+ ### withAnchorElement
182
+
183
+ ```ts
184
+ function withAnchorElement(
185
+ anchorElement?: HTMLElement,
186
+ ): Signal<HTMLElement | null>;
187
+ ```
188
+
189
+ Assigns an anchor element to the current modal. This function must be called inside a `createModal` callback.
190
+
191
+ #### Example
192
+
193
+ ```ts
194
+ import {
195
+ createModal,
196
+ withAnchorElement,
197
+ setAnchorElement,
198
+ getAnchorElement,
199
+ } from "@monstermann/signals-modal";
200
+
201
+ createModal("key", () => {
202
+ withAnchorElement();
203
+ });
204
+
205
+ setAnchorElement("key", document.querySelector(".anchor"));
206
+ getAnchorElement("key"); // HTMLElement
207
+ ```
208
+
209
+ ### withAnchorMeasurement
210
+
211
+ ```ts
212
+ function withAnchorMeasurement(options: {
213
+ $anchorElement: Reactive<HTMLElement | null>;
214
+ $status: Reactive<ModalStatus>;
215
+ transform?: (rect: Rect) => Rect;
216
+ }): Memo<Rect>;
217
+ ```
218
+
219
+ Takes an anchor element and continuously measures its position while the modal is visible, to be used to position eg. a popover next to an element. This function must be called inside a `createModal` callback.
220
+
221
+ The optional `transform` option can be used to eg. make the anchor bigger, resulting with a margin between the anchor and floating popover.
222
+
223
+ #### Example
224
+
225
+ ```ts
226
+ import {
227
+ createModal,
228
+ withAnchorElement,
229
+ withModalStatus,
230
+ withAnchorMeasurement,
231
+ setAnchorElement,
232
+ setModalStatus,
233
+ } from "@monstermann/signals-modal";
234
+
235
+ createModal("key", () => {
236
+ const { $status } = withModalStatus();
237
+ const $anchorElement = withAnchorElement();
238
+ // Memo({ top: number, left: number, width: number, height: number })
239
+ const $anchorMeasurement = withAnchorMeasurement({
240
+ $status,
241
+ $anchorElement,
242
+ });
243
+ });
244
+
245
+ setAnchorElement("key", document.querySelector(".anchor"));
246
+ setModalStatus("key", "opened");
247
+ ```
248
+
249
+ ### withMouseAnchor
250
+
251
+ ```ts
252
+ function withMouseAnchor(options: {
253
+ $status: Reactive<ModalStatus>;
254
+ transform?: (rect: Rect) => Rect;
255
+ }): Memo<Rect>;
256
+ ```
257
+
258
+ This can be used to make the mouse cursor the anchor, instead of an element. This function must be called inside a `createModal` callback.
259
+
260
+ #### Example
261
+
262
+ ```ts
263
+ import {
264
+ createModal,
265
+ withModalStatus,
266
+ withMouseAnchor,
267
+ setModalStatus,
268
+ } from "@monstermann/signals-modal";
269
+
270
+ createModal("key", () => {
271
+ const { $status } = withModalStatus();
272
+ // Memo({ top: number, left: number, width: number, height: number })
273
+ const $anchorMeasurement = withMouseAnchor({ $status });
274
+ });
275
+
276
+ // Updates $anchorMeasurement to the current mouse coordinates (once).
277
+ setModalStatus("key", "opened");
278
+ ```
279
+
280
+ ## Core
281
+
282
+ ### createModal
283
+
284
+ ```ts
285
+ function createModal(
286
+ key: string,
287
+ setup: () => T,
288
+ ): T & {
289
+ key: string;
290
+ dispose: () => void;
291
+ isDisposed: () => boolean;
292
+ onDispose: (dispose: MaybeDispose) => void;
293
+ };
294
+ ```
295
+
296
+ Creates a new modal.
297
+
298
+ #### Example
299
+
300
+ ```ts
301
+ import { createModal } from "@monstermann/signals-modal";
302
+
303
+ const modal = createModal("key", () => ({}));
304
+ modal.key;
305
+ modal.dispose();
306
+ modal.onDispose(callback);
307
+ ```
308
+
309
+ ### currentModal
310
+
311
+ ```ts
312
+ function currentModal(): {
313
+ key: string;
314
+ dispose: () => void;
315
+ onDispose: (dispose: MaybeDispose) => void;
316
+ };
317
+ ```
318
+
319
+ Retrieves the current modal.
320
+
321
+ #### Example
322
+
323
+ ```ts
324
+ import { createModal, currentModal } from "@monstermann/signals-modal";
325
+
326
+ createModal(() => {
327
+ const modal = currentModal();
328
+ });
329
+ ```
330
+
331
+ ### onModalDisposed
332
+
333
+ ```ts
334
+ const onModalDisposed: Emitter<string>;
335
+ ```
336
+
337
+ An emitter that fires when a modal gets disposed. The emitted value is the modal key.
338
+
339
+ #### Example
340
+
341
+ ```ts
342
+ import { createModal, onModalDisposed } from "@monstermann/signals-modal";
343
+
344
+ const modal = createModal("key", () => {});
345
+
346
+ const stopListening = onModalDisposed((key) => {
347
+ console.log(`Modal ${key} disposed`);
348
+ });
349
+
350
+ modal.dispose();
351
+
352
+ stopListening();
353
+ ```
354
+
355
+ ## Floating
356
+
357
+ ### getFloatingElement
358
+
359
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
360
+
361
+ ```ts
362
+ function getFloatingElement(key: string): HTMLElement | undefined;
363
+ ```
364
+
365
+ Retrieves the current floating element for the given `key`.
366
+
367
+ #### Example
368
+
369
+ ```ts
370
+ import {
371
+ createModal,
372
+ withFloatingElement,
373
+ setFloatingElement,
374
+ getFloatingElement,
375
+ } from "@monstermann/signals-modal";
376
+
377
+ createModal("key", () => {
378
+ withFloatingElement();
379
+ });
380
+
381
+ setFloatingElement("key", document.querySelector(".floating"));
382
+ getFloatingElement("key"); // HTMLElement
383
+ ```
384
+
385
+ ### getFloatingMeasurement
386
+
387
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
388
+
389
+ ```ts
390
+ function getFloatingMeasurement(key: string): Rect;
391
+ ```
392
+
393
+ Retrieves the current result of `withFloatingMeasurement`, falling back to an empty `Rect`.
394
+
395
+ #### Example
396
+
397
+ ```ts
398
+ import {
399
+ createModal,
400
+ withFloatingElement,
401
+ withModalStatus,
402
+ withFloatingMeasurement,
403
+ getFloatingMeasurement,
404
+ } from "@monstermann/signals-modal";
405
+
406
+ createModal("key", () => {
407
+ const { $status } = withModalStatus();
408
+ const $floatingElement = withFloatingElement();
409
+ // Memo({ top: number, left: number, width: number, height: number })
410
+ const $floatingMeasurement = withFloatingMeasurement({
411
+ $status,
412
+ $floatingElement,
413
+ });
414
+ });
415
+
416
+ // { top: number, left: number, width: number, height: number }
417
+ getFloatingMeasurement("key");
418
+ ```
419
+
420
+ ### setFloatingElement
421
+
422
+ ```ts
423
+ function setFloatingElement(key: string, element: HTMLElement | null): void;
424
+ ```
425
+
426
+ Sets the current floating element for the given `key`.
427
+
428
+ #### Example
429
+
430
+ ```ts
431
+ import {
432
+ createModal,
433
+ withFloatingElement,
434
+ getFloatingElement,
435
+ } from "@monstermann/signals-modal";
436
+
437
+ createModal("key", () => {
438
+ withFloatingElement();
439
+ });
440
+
441
+ setFloatingElement("key", document.querySelector(".floating"));
442
+ ```
443
+
444
+ ### withFloatingElement
445
+
446
+ ```ts
447
+ function withFloatingElement(
448
+ floatingElement?: HTMLElement,
449
+ ): Signal<HTMLElement | null>;
450
+ ```
451
+
452
+ Assigns an floating element to the current modal. This function must be called inside a `createModal` callback.
453
+
454
+ #### Example
455
+
456
+ ```ts
457
+ import {
458
+ createModal,
459
+ withFloatingElement,
460
+ setFloatingElement,
461
+ getFloatingElement,
462
+ } from "@monstermann/signals-modal";
463
+
464
+ createModal("key", () => {
465
+ withFloatingElement();
466
+ });
467
+
468
+ setFloatingElement("key", document.querySelector(".floating"));
469
+ getFloatingElement("key"); // HTMLElement
470
+ ```
471
+
472
+ ### withFloatingMeasurement
473
+
474
+ ```ts
475
+ function withFloatingMeasurement(options: {
476
+ $floatingElement: Reactive<HTMLElement | null>;
477
+ $status: Reactive<ModalStatus>;
478
+ transform?: (rect: Rect) => Rect;
479
+ }): Memo<Rect>;
480
+ ```
481
+
482
+ Takes an floating element and continuously measures its position while the modal is visible, to be used to position eg. a popover next to an element. This function must be called inside a `createModal` callback.
483
+
484
+ #### Example
485
+
486
+ ```ts
487
+ import {
488
+ createModal,
489
+ withFloatingElement,
490
+ withModalStatus,
491
+ withFloatingMeasurement,
492
+ setFloatingElement,
493
+ setModalStatus,
494
+ } from "@monstermann/signals-modal";
495
+
496
+ createModal("key", () => {
497
+ const { $status } = withModalStatus();
498
+ const $floatingElement = withFloatingElement();
499
+ // Memo({ top: number, left: number, width: number, height: number })
500
+ const $floatingMeasurement = withFloatingMeasurement({
501
+ $status,
502
+ $floatingElement,
503
+ });
504
+ });
505
+
506
+ setFloatingElement("key", document.querySelector(".floating"));
507
+ setModalStatus("key", "opened");
508
+ ```
509
+
510
+ ## Groups
511
+
512
+ ### getDialogs
513
+
514
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
515
+
516
+ ```ts
517
+ function getDialogs(): ReadonlySet<string>;
518
+ ```
519
+
520
+ Returns all dialog keys from the `modalGroups.dialog` group.
521
+
522
+ #### Example
523
+
524
+ ```ts
525
+ import {
526
+ createModal,
527
+ withModalGroups,
528
+ modalGroups,
529
+ getDialogs,
530
+ } from "@monstermann/signals-modal";
531
+
532
+ createModal("key", () => {
533
+ withModalGroups([modalGroups.dialog]);
534
+ });
535
+
536
+ getDialogs(); // Set(["key"])
537
+ ```
538
+
539
+ ### getGroupsForModal
540
+
541
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
542
+
543
+ ```ts
544
+ function getGroupsForModal(key: string): ReadonlySet<string>;
545
+ ```
546
+
547
+ Returns all groups the given `key` belongs to.
548
+
549
+ #### Example
550
+
551
+ ```ts
552
+ import {
553
+ createModal,
554
+ withModalGroups,
555
+ modalGroups,
556
+ getGroupsForModal,
557
+ } from "@monstermann/signals-modal";
558
+
559
+ createModal("key", () => {
560
+ withModalGroups([modalGroups.dialog]);
561
+ });
562
+
563
+ getGroupsForModal("key"); // Set(["dialog"])
564
+ ```
565
+
566
+ ### getModalsForGroup
567
+
568
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
569
+
570
+ ```ts
571
+ function getModalsForGroup(group: string): ReadonlySet<string>;
572
+ ```
573
+
574
+ Returns all keys the given `group` belongs to.
575
+
576
+ #### Example
577
+
578
+ ```ts
579
+ import {
580
+ createModal,
581
+ withModalGroups,
582
+ modalGroups,
583
+ getModalsForGroup,
584
+ } from "@monstermann/signals-modal";
585
+
586
+ createModal("key", () => {
587
+ withModalGroups([modalGroups.dialog]);
588
+ });
589
+
590
+ getModalsForGroup(modalGroups.dialog); // Set(["key"])
591
+ ```
592
+
593
+ ### getPopovers
594
+
595
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
596
+
597
+ ```ts
598
+ function getPopovers(): ReadonlySet<string>;
599
+ ```
600
+
601
+ Returns all popover keys from the `modalGroups.popover` group.
602
+
603
+ #### Example
604
+
605
+ ```ts
606
+ import {
607
+ createModal,
608
+ withModalGroups,
609
+ modalGroups,
610
+ getPopovers,
611
+ } from "@monstermann/signals-modal";
612
+
613
+ createModal("key", () => {
614
+ withModalGroups([modalGroups.popover]);
615
+ });
616
+
617
+ getPopovers(); // Set(["key"])
618
+ ```
619
+
620
+ ### getTooltips
621
+
622
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
623
+
624
+ ```ts
625
+ function getTooltips(): ReadonlySet<string>;
626
+ ```
627
+
628
+ Returns all tooltip keys from the `modalGroups.tooltip` group.
629
+
630
+ #### Example
631
+
632
+ ```ts
633
+ import {
634
+ createModal,
635
+ withModalGroups,
636
+ modalGroups,
637
+ getTooltips,
638
+ } from "@monstermann/signals-modal";
639
+
640
+ createModal("key", () => {
641
+ withModalGroups([modalGroups.tooltip]);
642
+ });
643
+
644
+ getTooltips(); // Set(["key"])
645
+ ```
646
+
647
+ ### isDialog
648
+
649
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
650
+
651
+ ```ts
652
+ function isDialog(key: string): boolean;
653
+ ```
654
+
655
+ Returns a boolean indicating whether the given `key` belongs to the `modalGroups.dialog` group.
656
+
657
+ #### Example
658
+
659
+ ```ts
660
+ import {
661
+ createModal,
662
+ withModalGroups,
663
+ modalGroups,
664
+ isDialog,
665
+ } from "@monstermann/signals-modal";
666
+
667
+ createModal("key", () => {
668
+ withModalGroups([modalGroups.popover]);
669
+ });
670
+
671
+ isDialog("key"); // true
672
+ ```
673
+
674
+ ### isModalInGroup
675
+
676
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
677
+
678
+ ```ts
679
+ function isModalInGroup(key: string, group: string): boolean;
680
+ ```
681
+
682
+ Returns a boolean indicating whether the given `key` belongs to the `group`.
683
+
684
+ #### Example
685
+
686
+ ```ts
687
+ import {
688
+ createModal,
689
+ withModalGroups,
690
+ modalGroups,
691
+ isModalInGroup,
692
+ } from "@monstermann/signals-modal";
693
+
694
+ createModal("key", () => {
695
+ withModalGroups([modalGroups.popover]);
696
+ });
697
+
698
+ isModalInGroup("key", modalGroups.popover); // true
699
+ ```
700
+
701
+ ### isPopover
702
+
703
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
704
+
705
+ ```ts
706
+ function isPopover(key: string): boolean;
707
+ ```
708
+
709
+ Returns a boolean indicating whether the given `key` belongs to the `modalGroups.popover` group.
710
+
711
+ #### Example
712
+
713
+ ```ts
714
+ import {
715
+ createModal,
716
+ withModalGroups,
717
+ modalGroups,
718
+ isPopover,
719
+ } from "@monstermann/signals-modal";
720
+
721
+ createModal("key", () => {
722
+ withModalGroups([modalGroups.popover]);
723
+ });
724
+
725
+ isPopover("key"); // true
726
+ ```
727
+
728
+ ### isTooltip
729
+
730
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
731
+
732
+ ```ts
733
+ function isTooltip(key: string): boolean;
734
+ ```
735
+
736
+ Returns a boolean indicating whether the given `key` belongs to the `modalGroups.tooltip` group.
737
+
738
+ #### Example
739
+
740
+ ```ts
741
+ import {
742
+ createModal,
743
+ withModalGroups,
744
+ modalGroups,
745
+ isTooltip,
746
+ } from "@monstermann/signals-modal";
747
+
748
+ createModal("key", () => {
749
+ withModalGroups([modalGroups.popover]);
750
+ });
751
+
752
+ isTooltip("key"); // true
753
+ ```
754
+
755
+ ### modalGroups
756
+
757
+ ```ts
758
+ const modalGroups = {
759
+ dialog: "dialog",
760
+ popover: "popover",
761
+ tooltip: "tooltip",
762
+ };
763
+ ```
764
+
765
+ A record containing common modal groups.
766
+
767
+ ### withModalGroups
768
+
769
+ ```ts
770
+ function withModalGroups(groups: Iterable<string>): Memo<ReadonlySet<string>>;
771
+ ```
772
+
773
+ Assigns the current modal to a list of groups. Can be used to for example mark the modal as a dialog/popover/tooltip. This function must be called inside a `createModal` callback.
774
+
775
+ #### Example
776
+
777
+ ```ts
778
+ import {
779
+ createModal,
780
+ withModalGroups,
781
+ getDialogs,
782
+ getGroupsForModal,
783
+ getModalsForGroup,
784
+ } from "@monstermann/signals-modal";
785
+
786
+ createModal("key", () => {
787
+ withModalGroups(["dialog"]);
788
+ });
789
+
790
+ getGroupsForModal("key"); // Set(["dialog"])
791
+ getModalsForGroup("dialog"); // Set(["key"])
792
+ ```
793
+
794
+ ## Position
795
+
796
+ ### getModalPlacement
797
+
798
+ ```ts
799
+ function getModalPlacement(key: string): ModalPlacement | undefined;
800
+ ```
801
+
802
+ Returns the current placement for the given `key`.
803
+
804
+ #### Example
805
+
806
+ ```ts
807
+ import {
808
+ createModal,
809
+ withModalStatus,
810
+ withAnchorElement,
811
+ withAnchorMeasurement,
812
+ withFloatingElement,
813
+ withFloatingMeasurement,
814
+ withBoundary,
815
+ withPlacement,
816
+ getModalPlacement,
817
+ } from "@monstermann/signals-modal";
818
+
819
+ createModal("key", () => {
820
+ const { $status } = withModalStatus();
821
+ const $anchorElement = withAnchorElement();
822
+ const $floatingElement = withFloatingElement();
823
+ const $anchorMeasurement = withAnchorMeasurement({
824
+ $status,
825
+ $anchorElement,
826
+ });
827
+ const $floatingMeasurement = withFloatingMeasurement({
828
+ $status,
829
+ $floatingElement,
830
+ });
831
+ const $boundary = withBoundary({ $status });
832
+ const $placement = withPlacement({
833
+ placement: "vertical-center",
834
+ $boundary,
835
+ $anchorMeasurement,
836
+ $floatingMeasurement,
837
+ });
838
+ });
839
+
840
+ getModalPlacement("key"); // up-center | down-center
841
+ ```
842
+
843
+ ### getModalPosition
844
+
845
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
846
+
847
+ ```ts
848
+ function getModalPosition(key: string): ModalPosition | undefined;
849
+ ```
850
+
851
+ Returns the current result of `withPosition`.
852
+
853
+ #### Example
854
+
855
+ ```ts
856
+ import {
857
+ createModal,
858
+ withModalStatus,
859
+ withAnchorElement,
860
+ withAnchorMeasurement,
861
+ withFloatingElement,
862
+ withFloatingMeasurement,
863
+ withBoundary,
864
+ withPlacement,
865
+ withPosition,
866
+ getModalPosition,
867
+ } from "@monstermann/signals-modal";
868
+
869
+ createModal("key", () => {
870
+ const { $status } = withModalStatus();
871
+ const $anchorElement = withAnchorElement();
872
+ const $floatingElement = withFloatingElement();
873
+ const $anchorMeasurement = withAnchorMeasurement({
874
+ $status,
875
+ $anchorElement,
876
+ });
877
+ const $floatingMeasurement = withFloatingMeasurement({
878
+ $status,
879
+ $floatingElement,
880
+ });
881
+ const $boundary = withBoundary({ $status });
882
+ const $placement = withPlacement({
883
+ placement: "vertical-center",
884
+ $boundary,
885
+ $anchorMeasurement,
886
+ $floatingMeasurement,
887
+ });
888
+ const $position = withPosition({
889
+ $boundary,
890
+ $placement,
891
+ $anchorMeasurement,
892
+ $floatingMeasurement,
893
+ });
894
+ });
895
+
896
+ getModalPosition("key"); // ModalPosition | undefined
897
+ ```
898
+
899
+ ### withBoundary
900
+
901
+ ```ts
902
+ function withBoundary(options: {
903
+ $status: Reactive<ModalStatus>;
904
+ transform?: (rect: Rect) => Rect;
905
+ }): Memo<Rect>;
906
+ ```
907
+
908
+ Constructs a `Rect` resembling the window dimensions, to be fed into `withPlacement` and `withPosition`, used to constrain the floating element to be within the window boundary. This function must be called inside a `createModal` callback.
909
+
910
+ The optional `transform` option can be used to eg. make the `Rect` smaller, increasing the distance between the floating element and the edges of the window.
911
+
912
+ #### Example
913
+
914
+ ```ts
915
+ import {
916
+ createModal,
917
+ withModalStatus,
918
+ withBoundary,
919
+ } from "@monstermann/signals-modal";
920
+
921
+ createModal("key", () => {
922
+ const { $status } = withModalStatus();
923
+ const $boundary = withBoundary({ $status });
924
+ });
925
+ ```
926
+
927
+ ### withPlacement
928
+
929
+ ```ts
930
+ function withPlacement(options: {
931
+ placement: ModalPlacementOption;
932
+ $boundary: () => Rect;
933
+ $anchorMeasurement: () => Rect;
934
+ $floatingMeasurement: () => Rect;
935
+ }): Memo<ModalPlacement>;
936
+
937
+ type ModalPlacementOption =
938
+ | "vertical-center"
939
+ | "vertical-left"
940
+ | "vertical-right"
941
+ | "horizontal-center"
942
+ | "horizontal-up"
943
+ | "horizontal-down"
944
+ | "up-center"
945
+ | "down-center"
946
+ | "left-down"
947
+ | "right-down";
948
+
949
+ type ModalPlacement =
950
+ | "down-center"
951
+ | "down-left"
952
+ | "down-right"
953
+ | "left-center"
954
+ | "left-down"
955
+ | "left-up"
956
+ | "right-center"
957
+ | "right-down"
958
+ | "right-up"
959
+ | "up-center"
960
+ | "up-left"
961
+ | "up-right";
962
+ ```
963
+
964
+ Takes a `ModalPlacementOption` and resolves it to `ModalPlacement`, picking whichever side has more space. This function must be called inside a `createModal` callback.
965
+
966
+ #### Example
967
+
968
+ ```ts
969
+ import {
970
+ createModal,
971
+ withModalStatus,
972
+ withAnchorElement,
973
+ withAnchorMeasurement,
974
+ withFloatingElement,
975
+ withFloatingMeasurement,
976
+ withBoundary,
977
+ withPlacement,
978
+ } from "@monstermann/signals-modal";
979
+
980
+ createModal("key", () => {
981
+ const { $status } = withModalStatus();
982
+ const $anchorElement = withAnchorElement();
983
+ const $floatingElement = withFloatingElement();
984
+ const $anchorMeasurement = withAnchorMeasurement({
985
+ $status,
986
+ $anchorElement,
987
+ });
988
+ const $floatingMeasurement = withFloatingMeasurement({
989
+ $status,
990
+ $floatingElement,
991
+ });
992
+ const $boundary = withBoundary({ $status });
993
+ const $placement = withPlacement({
994
+ placement: "vertical-center",
995
+ $boundary,
996
+ $anchorMeasurement,
997
+ $floatingMeasurement,
998
+ });
999
+ });
1000
+ ```
1001
+
1002
+ ### withPosition
1003
+
1004
+ ```ts
1005
+ function withPosition(options: {
1006
+ $boundary: () => Rect;
1007
+ $placement: () => ModalPlacement;
1008
+ $anchorMeasurement: () => Rect;
1009
+ $floatingMeasurement: () => Rect;
1010
+ transform?: (rect: Rect) => Rect;
1011
+ }): Memo<{
1012
+ floatingX: number;
1013
+ floatingY: number;
1014
+ maxHeight: number;
1015
+ maxWidth: number;
1016
+ originX: number;
1017
+ originY: number;
1018
+ }>;
1019
+ ```
1020
+
1021
+ Consumes a range of measurements and calculates the final position for the floating element. This function must be called inside a `createModal` callback.
1022
+
1023
+ #### Example
1024
+
1025
+ ```ts
1026
+ import {
1027
+ createModal,
1028
+ withModalStatus,
1029
+ withAnchorElement,
1030
+ withAnchorMeasurement,
1031
+ withFloatingElement,
1032
+ withFloatingMeasurement,
1033
+ withBoundary,
1034
+ withPlacement,
1035
+ withPosition,
1036
+ } from "@monstermann/signals-modal";
1037
+
1038
+ createModal("key", () => {
1039
+ const { $status } = withModalStatus();
1040
+ const $anchorElement = withAnchorElement();
1041
+ const $floatingElement = withFloatingElement();
1042
+ const $anchorMeasurement = withAnchorMeasurement({
1043
+ $status,
1044
+ $anchorElement,
1045
+ });
1046
+ const $floatingMeasurement = withFloatingMeasurement({
1047
+ $status,
1048
+ $floatingElement,
1049
+ });
1050
+ const $boundary = withBoundary({ $status });
1051
+ const $placement = withPlacement({
1052
+ placement: "vertical-center",
1053
+ $boundary,
1054
+ $anchorMeasurement,
1055
+ $floatingMeasurement,
1056
+ });
1057
+ const $position = withPosition({
1058
+ $boundary,
1059
+ $placement,
1060
+ $anchorMeasurement,
1061
+ $floatingMeasurement,
1062
+ });
1063
+ });
1064
+ ```
1065
+
1066
+ ## Scroll
1067
+
1068
+ ### withCloseOnScroll
1069
+
1070
+ ```ts
1071
+ function withCloseOnScroll(options: {
1072
+ $anchorElement: Reactive<HTMLElement | null>;
1073
+ $status: Reactive<ModalStatus>;
1074
+ }): void;
1075
+ ```
1076
+
1077
+ Automatically closes the modal when any scrollable ancestor of the anchor element is scrolled. This function must be called inside a `createModal` callback.
1078
+
1079
+ The function listens for scroll events on all scrollable parent elements of the anchor element and triggers a close when scrolling occurs. Scroll listeners are only active when the modal is opening or opened (not when closing or closed).
1080
+
1081
+ #### Example
1082
+
1083
+ ```ts
1084
+ import {
1085
+ createModal,
1086
+ withModalStatus,
1087
+ withAnchorElement,
1088
+ withCloseOnScroll,
1089
+ } from "@monstermann/signals-modal";
1090
+
1091
+ createModal("key", () => {
1092
+ const { $status } = withModalStatus();
1093
+ const $anchorElement = withAnchorElement();
1094
+
1095
+ withCloseOnScroll({
1096
+ $status,
1097
+ $anchorElement,
1098
+ });
1099
+ });
1100
+ ```
1101
+
1102
+ ## Status
1103
+
1104
+ ### closeAllModals
1105
+
1106
+ ```ts
1107
+ function closeAllModals(): void;
1108
+ ```
1109
+
1110
+ Closes all modals by setting their status to `"closing"`. Skips modals that are already `"closing"` or `"closed"`.
1111
+
1112
+ #### Example
1113
+
1114
+ ```ts
1115
+ import {
1116
+ createModal,
1117
+ withModalStatus,
1118
+ openModal,
1119
+ closeAllModals,
1120
+ } from "@monstermann/signals-modal";
1121
+
1122
+ createModal("modal1", () => {
1123
+ withModalStatus();
1124
+ });
1125
+
1126
+ createModal("modal2", () => {
1127
+ withModalStatus();
1128
+ });
1129
+
1130
+ openModal("modal1");
1131
+ openModal("modal2");
1132
+ closeAllModals();
1133
+ ```
1134
+
1135
+ ### closeLastModal
1136
+
1137
+ ```ts
1138
+ function closeLastModal(): void;
1139
+ ```
1140
+
1141
+ Closes the last opened modal by setting its status to `"closing"`.
1142
+
1143
+ #### Example
1144
+
1145
+ ```ts
1146
+ import { closeLastModal } from "@monstermann/signals-modal";
1147
+
1148
+ closeLastModal();
1149
+ ```
1150
+
1151
+ ### closeModal
1152
+
1153
+ ```ts
1154
+ function closeModal(key: string): void;
1155
+ ```
1156
+
1157
+ Closes a modal by setting its status to `"closing"`. Does nothing if the modal is already `"closing"` or `"closed"`, or if the modal doesn't exist.
1158
+
1159
+ #### Example
1160
+
1161
+ ```ts
1162
+ import {
1163
+ createModal,
1164
+ withModalStatus,
1165
+ openModal,
1166
+ closeModal,
1167
+ } from "@monstermann/signals-modal";
1168
+
1169
+ createModal("key", () => {
1170
+ withModalStatus();
1171
+ });
1172
+
1173
+ openModal("key");
1174
+ closeModal("key");
1175
+ ```
1176
+
1177
+ ### getClosedModals
1178
+
1179
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1180
+
1181
+ ```ts
1182
+ function getClosedModals(): string[];
1183
+ ```
1184
+
1185
+ Returns an array of all modal keys with status `"closed"`.
1186
+
1187
+ #### Example
1188
+
1189
+ ```ts
1190
+ import {
1191
+ createModal,
1192
+ withModalStatus,
1193
+ getClosedModals,
1194
+ } from "@monstermann/signals-modal";
1195
+
1196
+ createModal("modal1", () => {
1197
+ withModalStatus();
1198
+ });
1199
+
1200
+ createModal("modal2", () => {
1201
+ withModalStatus();
1202
+ });
1203
+
1204
+ getClosedModals(); // ["modal1", "modal2"]
1205
+ ```
1206
+
1207
+ ### getClosingModals
1208
+
1209
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1210
+
1211
+ ```ts
1212
+ function getClosingModals(): string[];
1213
+ ```
1214
+
1215
+ Returns an array of all modal keys with status `"closing"`.
1216
+
1217
+ #### Example
1218
+
1219
+ ```ts
1220
+ import {
1221
+ createModal,
1222
+ withModalStatus,
1223
+ closeModal,
1224
+ getClosingModals,
1225
+ } from "@monstermann/signals-modal";
1226
+
1227
+ createModal("modal1", () => {
1228
+ withModalStatus("opened");
1229
+ });
1230
+
1231
+ createModal("modal2", () => {
1232
+ withModalStatus("opened");
1233
+ });
1234
+
1235
+ closeModal("modal1");
1236
+ closeModal("modal2");
1237
+
1238
+ getClosingModals(); // ["modal1", "modal2"]
1239
+ ```
1240
+
1241
+ ### getModalStatus
1242
+
1243
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1244
+
1245
+ ```ts
1246
+ function getModalStatus(key: string): ModalStatus;
1247
+ ```
1248
+
1249
+ Retrieves the current status of a modal. Returns `"closed"` if the modal doesn't exist.
1250
+
1251
+ **ModalStatus** can be one of: `"closed"`, `"opening"`, `"opened"`, or `"closing"`.
1252
+
1253
+ #### Example
1254
+
1255
+ ```ts
1256
+ import {
1257
+ createModal,
1258
+ withModalStatus,
1259
+ getModalStatus,
1260
+ } from "@monstermann/signals-modal";
1261
+
1262
+ createModal("key", () => {
1263
+ withModalStatus();
1264
+ });
1265
+
1266
+ getModalStatus("key"); // "closed"
1267
+ ```
1268
+
1269
+ ### getOpenedModals
1270
+
1271
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1272
+
1273
+ ```ts
1274
+ function getOpenedModals(): string[];
1275
+ ```
1276
+
1277
+ Returns an array of all modal keys with status `"opened"`.
1278
+
1279
+ #### Example
1280
+
1281
+ ```ts
1282
+ import {
1283
+ createModal,
1284
+ withModalStatus,
1285
+ getOpenedModals,
1286
+ } from "@monstermann/signals-modal";
1287
+
1288
+ createModal("modal1", () => {
1289
+ withModalStatus("opened");
1290
+ });
1291
+
1292
+ createModal("modal2", () => {
1293
+ withModalStatus("opened");
1294
+ });
1295
+
1296
+ getOpenedModals(); // ["modal1", "modal2"]
1297
+ ```
1298
+
1299
+ ### getOpeningModals
1300
+
1301
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1302
+
1303
+ ```ts
1304
+ function getOpeningModals(): string[];
1305
+ ```
1306
+
1307
+ Returns an array of all modal keys with status `"opening"`.
1308
+
1309
+ #### Example
1310
+
1311
+ ```ts
1312
+ import {
1313
+ createModal,
1314
+ withModalStatus,
1315
+ openModal,
1316
+ getOpeningModals,
1317
+ } from "@monstermann/signals-modal";
1318
+
1319
+ createModal("modal1", () => {
1320
+ withModalStatus();
1321
+ });
1322
+
1323
+ createModal("modal2", () => {
1324
+ withModalStatus();
1325
+ });
1326
+
1327
+ openModal("modal1");
1328
+ openModal("modal2");
1329
+
1330
+ getOpeningModals(); // ["modal1", "modal2"]
1331
+ ```
1332
+
1333
+ ### getOpenModals
1334
+
1335
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1336
+
1337
+ ```ts
1338
+ function getOpenModals(): string[];
1339
+ ```
1340
+
1341
+ Returns an array of all modal keys with status `"opening"` or `"opened"`.
1342
+
1343
+ #### Example
1344
+
1345
+ ```ts
1346
+ import {
1347
+ createModal,
1348
+ withModalStatus,
1349
+ getOpenModals,
1350
+ } from "@monstermann/signals-modal";
1351
+
1352
+ createModal("modal1", () => {
1353
+ withModalStatus("opening");
1354
+ });
1355
+
1356
+ createModal("modal2", () => {
1357
+ withModalStatus("opened");
1358
+ });
1359
+
1360
+ createModal("modal3", () => {
1361
+ withModalStatus("closed");
1362
+ });
1363
+
1364
+ getOpenModals(); // ["modal1", "modal2"]
1365
+ ```
1366
+
1367
+ ### getVisibleModals
1368
+
1369
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1370
+
1371
+ ```ts
1372
+ function getVisibleModals(): string[];
1373
+ ```
1374
+
1375
+ Returns an array of all modal keys that are visible (not `"closed"`). This includes `"opening"`, `"opened"`, and `"closing"` statuses.
1376
+
1377
+ #### Example
1378
+
1379
+ ```ts
1380
+ import {
1381
+ createModal,
1382
+ withModalStatus,
1383
+ openModal,
1384
+ getVisibleModals,
1385
+ } from "@monstermann/signals-modal";
1386
+
1387
+ createModal("modal1", () => {
1388
+ withModalStatus();
1389
+ });
1390
+
1391
+ createModal("modal2", () => {
1392
+ withModalStatus();
1393
+ });
1394
+
1395
+ openModal("modal1");
1396
+ openModal("modal2");
1397
+
1398
+ getVisibleModals(); // ["modal1", "modal2"]
1399
+ ```
1400
+
1401
+ ### isAnyModalClosed
1402
+
1403
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1404
+
1405
+ ```ts
1406
+ function isAnyModalClosed(): boolean;
1407
+ ```
1408
+
1409
+ Returns `true` if any modal has status `"closed"`.
1410
+
1411
+ #### Example
1412
+
1413
+ ```ts
1414
+ import {
1415
+ createModal,
1416
+ withModalStatus,
1417
+ isAnyModalClosed,
1418
+ } from "@monstermann/signals-modal";
1419
+
1420
+ createModal("modal1", () => {
1421
+ withModalStatus();
1422
+ });
1423
+
1424
+ createModal("modal2", () => {
1425
+ withModalStatus("opened");
1426
+ });
1427
+
1428
+ isAnyModalClosed(); // true
1429
+ ```
1430
+
1431
+ ### isAnyModalClosing
1432
+
1433
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1434
+
1435
+ ```ts
1436
+ function isAnyModalClosing(): boolean;
1437
+ ```
1438
+
1439
+ Returns `true` if any modal has status `"closing"`.
1440
+
1441
+ #### Example
1442
+
1443
+ ```ts
1444
+ import {
1445
+ createModal,
1446
+ withModalStatus,
1447
+ closeModal,
1448
+ isAnyModalClosing,
1449
+ } from "@monstermann/signals-modal";
1450
+
1451
+ createModal("modal1", () => {
1452
+ withModalStatus("opened");
1453
+ });
1454
+
1455
+ createModal("modal2", () => {
1456
+ withModalStatus();
1457
+ });
1458
+
1459
+ closeModal("modal1");
1460
+
1461
+ isAnyModalClosing(); // true
1462
+ ```
1463
+
1464
+ ### isAnyModalOpen
1465
+
1466
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1467
+
1468
+ ```ts
1469
+ function isAnyModalOpen(): boolean;
1470
+ ```
1471
+
1472
+ Returns `true` if any modal has status `"opening"` or `"opened"`.
1473
+
1474
+ #### Example
1475
+
1476
+ ```ts
1477
+ import {
1478
+ createModal,
1479
+ withModalStatus,
1480
+ isAnyModalOpen,
1481
+ } from "@monstermann/signals-modal";
1482
+
1483
+ createModal("modal1", () => {
1484
+ withModalStatus();
1485
+ });
1486
+
1487
+ createModal("modal2", () => {
1488
+ withModalStatus("opened");
1489
+ });
1490
+
1491
+ isAnyModalOpen(); // true
1492
+ ```
1493
+
1494
+ ### isAnyModalOpened
1495
+
1496
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1497
+
1498
+ ```ts
1499
+ function isAnyModalOpened(): boolean;
1500
+ ```
1501
+
1502
+ Returns `true` if any modal has status `"opened"`.
1503
+
1504
+ #### Example
1505
+
1506
+ ```ts
1507
+ import {
1508
+ createModal,
1509
+ withModalStatus,
1510
+ isAnyModalOpened,
1511
+ } from "@monstermann/signals-modal";
1512
+
1513
+ createModal("modal1", () => {
1514
+ withModalStatus();
1515
+ });
1516
+
1517
+ createModal("modal2", () => {
1518
+ withModalStatus("opened");
1519
+ });
1520
+
1521
+ isAnyModalOpened(); // true
1522
+ ```
1523
+
1524
+ ### isAnyModalOpening
1525
+
1526
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1527
+
1528
+ ```ts
1529
+ function isAnyModalOpening(): boolean;
1530
+ ```
1531
+
1532
+ Returns `true` if any modal has status `"opening"`.
1533
+
1534
+ #### Example
1535
+
1536
+ ```ts
1537
+ import {
1538
+ createModal,
1539
+ withModalStatus,
1540
+ openModal,
1541
+ isAnyModalOpening,
1542
+ } from "@monstermann/signals-modal";
1543
+
1544
+ createModal("modal1", () => {
1545
+ withModalStatus();
1546
+ });
1547
+
1548
+ createModal("modal2", () => {
1549
+ withModalStatus();
1550
+ });
1551
+
1552
+ openModal("modal1");
1553
+
1554
+ isAnyModalOpening(); // true
1555
+ ```
1556
+
1557
+ ### isAnyModalVisible
1558
+
1559
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1560
+
1561
+ ```ts
1562
+ function isAnyModalVisible(): boolean;
1563
+ ```
1564
+
1565
+ Returns `true` if any modal is visible (not `"closed"`). This includes `"opening"`, `"opened"`, and `"closing"` statuses.
1566
+
1567
+ #### Example
1568
+
1569
+ ```ts
1570
+ import {
1571
+ createModal,
1572
+ withModalStatus,
1573
+ isAnyModalVisible,
1574
+ } from "@monstermann/signals-modal";
1575
+
1576
+ createModal("modal1", () => {
1577
+ withModalStatus();
1578
+ });
1579
+
1580
+ createModal("modal2", () => {
1581
+ withModalStatus("opened");
1582
+ });
1583
+
1584
+ isAnyModalVisible(); // true
1585
+ ```
1586
+
1587
+ ### isModalClosed
1588
+
1589
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1590
+
1591
+ ```ts
1592
+ function isModalClosed(key: string): boolean;
1593
+ ```
1594
+
1595
+ Returns `true` if the modal's status is `"closed"` or if the modal doesn't exist.
1596
+
1597
+ #### Example
1598
+
1599
+ ```ts
1600
+ import {
1601
+ createModal,
1602
+ withModalStatus,
1603
+ isModalClosed,
1604
+ } from "@monstermann/signals-modal";
1605
+
1606
+ createModal("key", () => {
1607
+ withModalStatus();
1608
+ });
1609
+
1610
+ isModalClosed("key"); // true
1611
+ ```
1612
+
1613
+ ### isModalClosing
1614
+
1615
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1616
+
1617
+ ```ts
1618
+ function isModalClosing(key: string): boolean;
1619
+ ```
1620
+
1621
+ Returns `true` if the modal's status is `"closing"`.
1622
+
1623
+ #### Example
1624
+
1625
+ ```ts
1626
+ import {
1627
+ createModal,
1628
+ withModalStatus,
1629
+ openModal,
1630
+ closeModal,
1631
+ isModalClosing,
1632
+ } from "@monstermann/signals-modal";
1633
+
1634
+ createModal("key", () => {
1635
+ const { $status } = withModalStatus();
1636
+ $status("opened");
1637
+ });
1638
+
1639
+ closeModal("key");
1640
+ isModalClosing("key"); // true
1641
+ ```
1642
+
1643
+ ### isModalOpen
1644
+
1645
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1646
+
1647
+ ```ts
1648
+ function isModalOpen(key: string): boolean;
1649
+ ```
1650
+
1651
+ Returns `true` if the modal's status is `"opening"` or `"opened"`.
1652
+
1653
+ #### Example
1654
+
1655
+ ```ts
1656
+ import {
1657
+ createModal,
1658
+ withModalStatus,
1659
+ isModalOpen,
1660
+ } from "@monstermann/signals-modal";
1661
+
1662
+ createModal("key", () => {
1663
+ const { $status } = withModalStatus("opening");
1664
+ });
1665
+
1666
+ isModalOpen("key"); // true
1667
+ ```
1668
+
1669
+ ### isModalOpened
1670
+
1671
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1672
+
1673
+ ```ts
1674
+ function isModalOpened(key: string): boolean;
1675
+ ```
1676
+
1677
+ Returns `true` if the modal's status is `"opened"`.
1678
+
1679
+ #### Example
1680
+
1681
+ ```ts
1682
+ import {
1683
+ createModal,
1684
+ withModalStatus,
1685
+ openModal,
1686
+ isModalOpened,
1687
+ } from "@monstermann/signals-modal";
1688
+
1689
+ createModal("key", () => {
1690
+ const { $status } = withModalStatus();
1691
+ $status("opened");
1692
+ });
1693
+
1694
+ isModalOpened("key"); // true
1695
+ ```
1696
+
1697
+ ### isModalOpening
1698
+
1699
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1700
+
1701
+ ```ts
1702
+ function isModalOpening(key: string): boolean;
1703
+ ```
1704
+
1705
+ Returns `true` if the modal's status is `"opening"`.
1706
+
1707
+ #### Example
1708
+
1709
+ ```ts
1710
+ import {
1711
+ createModal,
1712
+ withModalStatus,
1713
+ openModal,
1714
+ isModalOpening,
1715
+ } from "@monstermann/signals-modal";
1716
+
1717
+ createModal("key", () => {
1718
+ withModalStatus();
1719
+ });
1720
+
1721
+ openModal("key");
1722
+ isModalOpening("key"); // true
1723
+ ```
1724
+
1725
+ ### isModalVisible
1726
+
1727
+ ![Reactive](https://img.shields.io/badge/Reactive-blue?style=flat-square&color=%2369a1ff)
1728
+
1729
+ ```ts
1730
+ function isModalVisible(key: string): boolean;
1731
+ ```
1732
+
1733
+ Returns `true` if the modal is visible (not `"closed"`). This includes `"opening"`, `"opened"`, and `"closing"` statuses.
1734
+
1735
+ #### Example
1736
+
1737
+ ```ts
1738
+ import {
1739
+ createModal,
1740
+ withModalStatus,
1741
+ openModal,
1742
+ isModalVisible,
1743
+ } from "@monstermann/signals-modal";
1744
+
1745
+ createModal("key", () => {
1746
+ withModalStatus();
1747
+ });
1748
+
1749
+ openModal("key");
1750
+ isModalVisible("key"); // true
1751
+ ```
1752
+
1753
+ ### onModalClosed
1754
+
1755
+ ```ts
1756
+ const onModalClosed: Emitter<string>;
1757
+ ```
1758
+
1759
+ An emitter that fires when a modal transitions to the `"closed"` status. The emitted value is the modal key.
1760
+
1761
+ #### Example
1762
+
1763
+ ```ts
1764
+ import {
1765
+ createModal,
1766
+ withModalStatus,
1767
+ onModalClosed,
1768
+ } from "@monstermann/signals-modal";
1769
+
1770
+ createModal("key", () => {
1771
+ withModalStatus();
1772
+ });
1773
+
1774
+ const stopListening = onModalClosed((key) => {
1775
+ console.log(`Modal ${key} closed`);
1776
+ });
1777
+
1778
+ stopListening();
1779
+ ```
1780
+
1781
+ ### onModalClosing
1782
+
1783
+ ```ts
1784
+ const onModalClosing: Emitter<string>;
1785
+ ```
1786
+
1787
+ An emitter that fires when a modal transitions to the `"closing"` status. The emitted value is the modal key.
1788
+
1789
+ #### Example
1790
+
1791
+ ```ts
1792
+ import {
1793
+ createModal,
1794
+ withModalStatus,
1795
+ onModalClosing,
1796
+ } from "@monstermann/signals-modal";
1797
+
1798
+ createModal("key", () => {
1799
+ withModalStatus();
1800
+ });
1801
+
1802
+ const stopListening = onModalClosing((key) => {
1803
+ console.log(`Modal ${key} closing`);
1804
+ });
1805
+
1806
+ stopListening();
1807
+ ```
1808
+
1809
+ ### onModalOpened
1810
+
1811
+ ```ts
1812
+ const onModalOpened: Emitter<string>;
1813
+ ```
1814
+
1815
+ An emitter that fires when a modal transitions to the `"opened"` status. The emitted value is the modal key.
1816
+
1817
+ #### Example
1818
+
1819
+ ```ts
1820
+ import {
1821
+ createModal,
1822
+ withModalStatus,
1823
+ onModalOpened,
1824
+ } from "@monstermann/signals-modal";
1825
+
1826
+ createModal("key", () => {
1827
+ withModalStatus();
1828
+ });
1829
+
1830
+ const stopListening = onModalOpened((key) => {
1831
+ console.log(`Modal ${key} opened`);
1832
+ });
1833
+
1834
+ stopListening();
1835
+ ```
1836
+
1837
+ ### onModalOpening
1838
+
1839
+ ```ts
1840
+ const onModalOpening: Emitter<string>;
1841
+ ```
1842
+
1843
+ An emitter that fires when a modal transitions to the `"opening"` status. The emitted value is the modal key.
1844
+
1845
+ #### Example
1846
+
1847
+ ```ts
1848
+ import {
1849
+ createModal,
1850
+ withModalStatus,
1851
+ onModalOpening,
1852
+ } from "@monstermann/signals-modal";
1853
+
1854
+ createModal("key", () => {
1855
+ withModalStatus();
1856
+ });
1857
+
1858
+ const stopListening = onModalOpening((key) => {
1859
+ console.log(`Modal ${key} opening`);
1860
+ });
1861
+
1862
+ stopListening();
1863
+ ```
1864
+
1865
+ ### openModal
1866
+
1867
+ ```ts
1868
+ function openModal(key: string): void;
1869
+ ```
1870
+
1871
+ Opens a modal by setting its status to `"opening"`. Does nothing if the modal is already `"opening"` or `"opened"`, or if the modal doesn't exist.
1872
+
1873
+ #### Example
1874
+
1875
+ ```ts
1876
+ import {
1877
+ createModal,
1878
+ withModalStatus,
1879
+ openModal,
1880
+ } from "@monstermann/signals-modal";
1881
+
1882
+ createModal("key", () => {
1883
+ withModalStatus();
1884
+ });
1885
+
1886
+ openModal("key");
1887
+ ```
1888
+
1889
+ ### setModalStatus
1890
+
1891
+ ```ts
1892
+ function setModalStatus(key: string, status: ModalStatus): void;
1893
+ ```
1894
+
1895
+ Sets the status of a modal. Does nothing if the modal doesn't exist.
1896
+
1897
+ **ModalStatus** can be one of: `"closed"`, `"opening"`, `"opened"`, or `"closing"`.
1898
+
1899
+ #### Example
1900
+
1901
+ ```ts
1902
+ import {
1903
+ createModal,
1904
+ withModalStatus,
1905
+ setModalStatus,
1906
+ } from "@monstermann/signals-modal";
1907
+
1908
+ createModal("key", () => {
1909
+ withModalStatus();
1910
+ });
1911
+
1912
+ setModalStatus("key", "opened");
1913
+ ```
1914
+
1915
+ ### withModalStatus
1916
+
1917
+ ```ts
1918
+ function withModalStatus(status: ModalStatus = "closed"): {
1919
+ $status: Signal<ModalStatus>;
1920
+ $isOpen: Memo<boolean>;
1921
+ close: () => void;
1922
+ open: () => void;
1923
+ };
1924
+ ```
1925
+
1926
+ Creates and returns a status signal for the current modal. This function must be called inside a `createModal` callback.
1927
+
1928
+ The optional `status` parameter sets the initial status of the modal (defaults to `"closed"`).
1929
+
1930
+ **ModalStatus** can be one of: `"closed"`, `"opening"`, `"opened"`, or `"closing"`.
1931
+
1932
+ #### Example
1933
+
1934
+ ```ts
1935
+ import { createModal, withModalStatus } from "@monstermann/signals-modal";
1936
+
1937
+ // Default to "closed"
1938
+ createModal("modal1", () => {
1939
+ const { $status } = withModalStatus();
1940
+ console.log($status()); // "closed"
1941
+ });
1942
+
1943
+ // Start with a different initial status
1944
+ createModal("modal2", () => {
1945
+ const { $status } = withModalStatus("opened");
1946
+ console.log($status()); // "opened"
1947
+
1948
+ // Update the status
1949
+ $status("closing");
1950
+ });
1951
+ ```
1952
+
1953
+ ## Utils
1954
+
1955
+ ### closeLastModalOnClickOutside
1956
+
1957
+ ```ts
1958
+ function closeLastModalOnClickOutside(): void;
1959
+ ```
1960
+
1961
+ Sets up a global `mousedown` listener that closes the last opened modal when clicked outside the floating element.
1962
+
1963
+ #### Example
1964
+
1965
+ ```ts
1966
+ import { closeLastModalOnClickOutside } from "@monstermann/signals-modal";
1967
+
1968
+ closeLastModalOnClickOutside();
1969
+ ```
1970
+
1971
+ ### closeLastModalOnEsc
1972
+
1973
+ ```ts
1974
+ function closeLastModalOnEsc(): void;
1975
+ ```
1976
+
1977
+ Sets up a global `keydown` listener that closes the last opened modal when `esc` is pressed, unless the target was an editable element such as `<input>`.
1978
+
1979
+ #### Example
1980
+
1981
+ ```ts
1982
+ import { closeLastModalOnEsc } from "@monstermann/signals-modal";
1983
+
1984
+ closeLastModalOnEsc();
1985
+ ```
1986
+
1987
+ ### syncModalGroupsToBody
1988
+
1989
+ ```ts
1990
+ function syncModalGroupsToBody(): void;
1991
+ ```
1992
+
1993
+ Sets up a global `Effect` that adds `has-${group}` class names to `document.body` for opened modals.
1994
+
1995
+ #### Example
1996
+
1997
+ ```ts
1998
+ import {
1999
+ createModal,
2000
+ withModalGroups,
2001
+ withModalStatus,
2002
+ syncModalGroupsToBody,
2003
+ openModal,
2004
+ closeModal,
2005
+ } from "@monstermann/signals-modal";
2006
+
2007
+ syncModalGroupsToBody();
2008
+
2009
+ const modal = createModal("key", () => {
2010
+ const $groups = withModalGroups(["dialog"]);
2011
+ const { $status } = withModalStatus();
2012
+ return { $groups, $status };
2013
+ });
2014
+
2015
+ document.body.classList; // []
2016
+ openModal("key");
2017
+ document.body.classList; // ["has-dialog"]
2018
+ closeModal("key");
2019
+ document.body.classList; // []
2020
+ ```