@zag-js/toast 1.34.1 → 1.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/index.d.mts +11 -365
  2. package/dist/index.d.ts +11 -365
  3. package/dist/index.js +40 -1084
  4. package/dist/index.mjs +13 -1080
  5. package/dist/toast-group.connect.d.mts +8 -0
  6. package/dist/toast-group.connect.d.ts +8 -0
  7. package/dist/toast-group.connect.js +98 -0
  8. package/dist/toast-group.connect.mjs +63 -0
  9. package/dist/toast-group.machine.d.mts +8 -0
  10. package/dist/toast-group.machine.d.ts +8 -0
  11. package/dist/toast-group.machine.js +292 -0
  12. package/dist/toast-group.machine.mjs +257 -0
  13. package/dist/toast.anatomy.d.mts +6 -0
  14. package/dist/toast.anatomy.d.ts +6 -0
  15. package/dist/toast.anatomy.js +41 -0
  16. package/dist/toast.anatomy.mjs +15 -0
  17. package/dist/toast.connect.d.mts +8 -0
  18. package/dist/toast.connect.d.ts +8 -0
  19. package/dist/toast.connect.js +155 -0
  20. package/dist/toast.connect.mjs +120 -0
  21. package/dist/toast.dom.d.mts +14 -0
  22. package/dist/toast.dom.d.ts +14 -0
  23. package/dist/toast.dom.js +48 -0
  24. package/dist/toast.dom.mjs +17 -0
  25. package/dist/toast.machine.d.mts +8 -0
  26. package/dist/toast.machine.d.ts +8 -0
  27. package/dist/toast.machine.js +291 -0
  28. package/dist/toast.machine.mjs +256 -0
  29. package/dist/toast.store.d.mts +8 -0
  30. package/dist/toast.store.d.ts +8 -0
  31. package/dist/toast.store.js +249 -0
  32. package/dist/toast.store.mjs +224 -0
  33. package/dist/toast.types.d.mts +371 -0
  34. package/dist/toast.types.d.ts +371 -0
  35. package/dist/toast.types.js +18 -0
  36. package/dist/toast.types.mjs +0 -0
  37. package/dist/toast.utils.d.mts +13 -0
  38. package/dist/toast.utils.d.ts +13 -0
  39. package/dist/toast.utils.js +209 -0
  40. package/dist/toast.utils.mjs +179 -0
  41. package/package.json +18 -8
package/dist/index.mjs CHANGED
@@ -1,1085 +1,18 @@
1
- import { addDomEvent, AnimationFrame, raf, contains, dataAttr, MAX_Z_INDEX } from '@zag-js/dom-query';
2
- import { createAnatomy } from '@zag-js/anatomy';
3
- import { setup, createGuards, createMachine as createMachine$1 } from '@zag-js/core';
4
- import { trackDismissableBranch } from '@zag-js/dismissable';
5
- import { uuid, setRafTimeout, ensureProps, warn, runIfFn, compact } from '@zag-js/utils';
6
-
7
- // src/toast-group.connect.ts
8
- var anatomy = createAnatomy("toast").parts(
9
- "group",
10
- "root",
11
- "title",
12
- "description",
13
- "actionTrigger",
14
- "closeTrigger"
15
- );
16
- var parts = anatomy.build();
17
-
18
- // src/toast.dom.ts
19
- var getRegionId = (placement) => `toast-group:${placement}`;
20
- var getRegionEl = (ctx, placement) => ctx.getById(`toast-group:${placement}`);
21
- var getRootId = (ctx) => `toast:${ctx.id}`;
22
- var getRootEl = (ctx) => ctx.getById(getRootId(ctx));
23
- var getTitleId = (ctx) => `toast:${ctx.id}:title`;
24
- var getDescriptionId = (ctx) => `toast:${ctx.id}:description`;
25
- var getCloseTriggerId = (ctx) => `toast${ctx.id}:close`;
26
- var defaultTimeouts = {
27
- info: 5e3,
28
- error: 5e3,
29
- success: 2e3,
30
- loading: Infinity,
31
- DEFAULT: 5e3
32
- };
33
- function getToastDuration(duration, type) {
34
- return duration ?? defaultTimeouts[type] ?? defaultTimeouts.DEFAULT;
35
- }
36
- var getOffsets = (offsets) => typeof offsets === "string" ? { left: offsets, right: offsets, bottom: offsets, top: offsets } : offsets;
37
- function getGroupPlacementStyle(service, placement) {
38
- const { prop, computed, context } = service;
39
- const { offsets, gap } = prop("store").attrs;
40
- const heights = context.get("heights");
41
- const computedOffset = getOffsets(offsets);
42
- const rtl = prop("dir") === "rtl";
43
- const computedPlacement = placement.replace("-start", rtl ? "-right" : "-left").replace("-end", rtl ? "-left" : "-right");
44
- const isRighty = computedPlacement.includes("right");
45
- const isLefty = computedPlacement.includes("left");
46
- const styles = {
47
- position: "fixed",
48
- pointerEvents: computed("count") > 0 ? void 0 : "none",
49
- display: "flex",
50
- flexDirection: "column",
51
- "--gap": `${gap}px`,
52
- "--first-height": `${heights[0]?.height || 0}px`,
53
- "--viewport-offset-left": computedOffset.left,
54
- "--viewport-offset-right": computedOffset.right,
55
- "--viewport-offset-top": computedOffset.top,
56
- "--viewport-offset-bottom": computedOffset.bottom,
57
- zIndex: MAX_Z_INDEX
58
- };
59
- let alignItems = "center";
60
- if (isRighty) alignItems = "flex-end";
61
- if (isLefty) alignItems = "flex-start";
62
- styles.alignItems = alignItems;
63
- if (computedPlacement.includes("top")) {
64
- const offset = computedOffset.top;
65
- styles.top = `max(env(safe-area-inset-top, 0px), ${offset})`;
66
- }
67
- if (computedPlacement.includes("bottom")) {
68
- const offset = computedOffset.bottom;
69
- styles.bottom = `max(env(safe-area-inset-bottom, 0px), ${offset})`;
70
- }
71
- if (!computedPlacement.includes("left")) {
72
- const offset = computedOffset.right;
73
- styles.insetInlineEnd = `calc(env(safe-area-inset-right, 0px) + ${offset})`;
74
- }
75
- if (!computedPlacement.includes("right")) {
76
- const offset = computedOffset.left;
77
- styles.insetInlineStart = `calc(env(safe-area-inset-left, 0px) + ${offset})`;
78
- }
79
- return styles;
80
- }
81
- function getPlacementStyle(service, visible) {
82
- const { prop, context, computed } = service;
83
- const parent = prop("parent");
84
- const placement = parent.computed("placement");
85
- const { gap } = parent.prop("store").attrs;
86
- const [side] = placement.split("-");
87
- const mounted = context.get("mounted");
88
- const remainingTime = context.get("remainingTime");
89
- const height = computed("height");
90
- const frontmost = computed("frontmost");
91
- const sibling = !frontmost;
92
- const overlap = !prop("stacked");
93
- const stacked = prop("stacked");
94
- const type = prop("type");
95
- const duration = type === "loading" ? Number.MAX_SAFE_INTEGER : remainingTime;
96
- const offset = computed("heightIndex") * gap + computed("heightBefore");
97
- const styles = {
98
- position: "absolute",
99
- pointerEvents: "auto",
100
- "--opacity": "0",
101
- "--remove-delay": `${prop("removeDelay")}ms`,
102
- "--duration": `${duration}ms`,
103
- "--initial-height": `${height}px`,
104
- "--offset": `${offset}px`,
105
- "--index": prop("index"),
106
- "--z-index": computed("zIndex"),
107
- "--lift-amount": "calc(var(--lift) * var(--gap))",
108
- "--y": "100%",
109
- "--x": "0"
110
- };
111
- const assign = (overrides) => Object.assign(styles, overrides);
112
- if (side === "top") {
113
- assign({
114
- top: "0",
115
- "--sign": "-1",
116
- "--y": "-100%",
117
- "--lift": "1"
118
- });
119
- } else if (side === "bottom") {
120
- assign({
121
- bottom: "0",
122
- "--sign": "1",
123
- "--y": "100%",
124
- "--lift": "-1"
125
- });
126
- }
127
- if (mounted) {
128
- assign({
129
- "--y": "0",
130
- "--opacity": "1"
131
- });
132
- if (stacked) {
133
- assign({
134
- "--y": "calc(var(--lift) * var(--offset))",
135
- "--height": "var(--initial-height)"
136
- });
137
- }
138
- }
139
- if (!visible) {
140
- assign({
141
- "--opacity": "0",
142
- pointerEvents: "none"
143
- });
144
- }
145
- if (sibling && overlap) {
146
- assign({
147
- "--base-scale": "var(--index) * 0.05 + 1",
148
- "--y": "calc(var(--lift-amount) * var(--index))",
149
- "--scale": "calc(-1 * var(--base-scale))",
150
- "--height": "var(--first-height)"
151
- });
152
- if (!visible) {
153
- assign({
154
- "--y": "calc(var(--sign) * 40%)"
155
- });
156
- }
157
- }
158
- if (sibling && stacked && !visible) {
159
- assign({
160
- "--y": "calc(var(--lift) * var(--offset) + var(--lift) * -100%)"
161
- });
162
- }
163
- if (frontmost && !visible) {
164
- assign({
165
- "--y": "calc(var(--lift) * -100%)"
166
- });
167
- }
168
- return styles;
169
- }
170
- function getGhostBeforeStyle(service, visible) {
171
- const { computed } = service;
172
- const styles = {
173
- position: "absolute",
174
- inset: "0",
175
- scale: "1 2",
176
- pointerEvents: visible ? "none" : "auto"
177
- };
178
- const assign = (overrides) => Object.assign(styles, overrides);
179
- if (computed("frontmost") && !visible) {
180
- assign({
181
- height: "calc(var(--initial-height) + 80%)"
182
- });
183
- }
184
- return styles;
185
- }
186
- function getGhostAfterStyle() {
187
- return {
188
- position: "absolute",
189
- left: "0",
190
- height: "calc(var(--gap) + 2px)",
191
- bottom: "100%",
192
- width: "100%"
193
- };
194
- }
195
-
196
- // src/toast-group.connect.ts
197
- function groupConnect(service, normalize) {
198
- const { context, prop, send, refs, computed } = service;
199
- return {
200
- getCount() {
201
- return context.get("toasts").length;
202
- },
203
- getToasts() {
204
- return context.get("toasts");
205
- },
206
- getGroupProps(options = {}) {
207
- const { label = "Notifications" } = options;
208
- const { hotkey } = prop("store").attrs;
209
- const hotkeyLabel = hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, "");
210
- const placement = computed("placement");
211
- const [side, align = "center"] = placement.split("-");
212
- return normalize.element({
213
- ...parts.group.attrs,
214
- dir: prop("dir"),
215
- tabIndex: -1,
216
- "aria-label": `${placement} ${label} ${hotkeyLabel}`,
217
- id: getRegionId(placement),
218
- "data-placement": placement,
219
- "data-side": side,
220
- "data-align": align,
221
- "aria-live": "polite",
222
- role: "region",
223
- style: getGroupPlacementStyle(service, placement),
224
- onMouseEnter() {
225
- if (refs.get("ignoreMouseTimer").isActive()) return;
226
- send({ type: "REGION.POINTER_ENTER", placement });
227
- },
228
- onMouseMove() {
229
- if (refs.get("ignoreMouseTimer").isActive()) return;
230
- send({ type: "REGION.POINTER_ENTER", placement });
231
- },
232
- onMouseLeave() {
233
- if (refs.get("ignoreMouseTimer").isActive()) return;
234
- send({ type: "REGION.POINTER_LEAVE", placement });
235
- },
236
- onFocus(event) {
237
- send({ type: "REGION.FOCUS", target: event.relatedTarget });
238
- },
239
- onBlur(event) {
240
- if (refs.get("isFocusWithin") && !contains(event.currentTarget, event.relatedTarget)) {
241
- queueMicrotask(() => send({ type: "REGION.BLUR" }));
242
- }
243
- }
244
- });
245
- },
246
- subscribe(fn) {
247
- const store = prop("store");
248
- return store.subscribe(() => fn(context.get("toasts")));
249
- }
250
- };
251
- }
252
- var { guards, createMachine } = setup();
253
- var { and } = guards;
254
- var groupMachine = createMachine({
255
- props({ props }) {
256
- return {
257
- dir: "ltr",
258
- id: uuid(),
259
- ...props,
260
- store: props.store
261
- };
262
- },
263
- initialState({ prop }) {
264
- return prop("store").attrs.overlap ? "overlap" : "stack";
265
- },
266
- refs() {
267
- return {
268
- lastFocusedEl: null,
269
- isFocusWithin: false,
270
- isPointerWithin: false,
271
- ignoreMouseTimer: AnimationFrame.create(),
272
- dismissableCleanup: void 0
273
- };
274
- },
275
- context({ bindable }) {
276
- return {
277
- toasts: bindable(() => ({
278
- defaultValue: [],
279
- sync: true,
280
- hash: (toasts) => toasts.map((t) => t.id).join(",")
281
- })),
282
- heights: bindable(() => ({
283
- defaultValue: [],
284
- sync: true
285
- }))
286
- };
287
- },
288
- computed: {
289
- count: ({ context }) => context.get("toasts").length,
290
- overlap: ({ prop }) => prop("store").attrs.overlap,
291
- placement: ({ prop }) => prop("store").attrs.placement
292
- },
293
- effects: ["subscribeToStore", "trackDocumentVisibility", "trackHotKeyPress"],
294
- watch({ track, context, action }) {
295
- track([() => context.hash("toasts")], () => {
296
- queueMicrotask(() => {
297
- action(["collapsedIfEmpty", "setDismissableBranch"]);
298
- });
299
- });
300
- },
301
- exit: ["clearDismissableBranch", "clearLastFocusedEl", "clearMouseEventTimer"],
302
- on: {
303
- "DOC.HOTKEY": {
304
- actions: ["focusRegionEl"]
305
- },
306
- "REGION.BLUR": [
307
- {
308
- guard: and("isOverlapping", "isPointerOut"),
309
- target: "overlap",
310
- actions: ["collapseToasts", "resumeToasts", "restoreFocusIfPointerOut"]
311
- },
312
- {
313
- guard: "isPointerOut",
314
- target: "stack",
315
- actions: ["resumeToasts", "restoreFocusIfPointerOut"]
316
- },
317
- {
318
- actions: ["clearFocusWithin"]
319
- }
320
- ],
321
- "TOAST.REMOVE": {
322
- actions: ["removeToast", "removeHeight", "ignoreMouseEventsTemporarily"]
323
- },
324
- "TOAST.PAUSE": {
325
- actions: ["pauseToasts"]
326
- }
327
- },
328
- states: {
329
- stack: {
330
- on: {
331
- "REGION.POINTER_LEAVE": [
332
- {
333
- guard: "isOverlapping",
334
- target: "overlap",
335
- actions: ["clearPointerWithin", "resumeToasts", "collapseToasts"]
336
- },
337
- {
338
- actions: ["clearPointerWithin", "resumeToasts"]
339
- }
340
- ],
341
- "REGION.OVERLAP": {
342
- target: "overlap",
343
- actions: ["collapseToasts"]
344
- },
345
- "REGION.FOCUS": {
346
- actions: ["setLastFocusedEl", "pauseToasts"]
347
- },
348
- "REGION.POINTER_ENTER": {
349
- actions: ["setPointerWithin", "pauseToasts"]
350
- }
351
- }
352
- },
353
- overlap: {
354
- on: {
355
- "REGION.STACK": {
356
- target: "stack",
357
- actions: ["expandToasts"]
358
- },
359
- "REGION.POINTER_ENTER": {
360
- target: "stack",
361
- actions: ["setPointerWithin", "pauseToasts", "expandToasts"]
362
- },
363
- "REGION.FOCUS": {
364
- target: "stack",
365
- actions: ["setLastFocusedEl", "pauseToasts", "expandToasts"]
366
- }
367
- }
368
- }
369
- },
370
- implementations: {
371
- guards: {
372
- isOverlapping: ({ computed }) => computed("overlap"),
373
- isPointerOut: ({ refs }) => !refs.get("isPointerWithin")
374
- },
375
- effects: {
376
- subscribeToStore({ context, prop }) {
377
- const store = prop("store");
378
- context.set("toasts", store.getVisibleToasts());
379
- return store.subscribe((toast) => {
380
- if (toast.dismiss) {
381
- context.set("toasts", (prev) => prev.filter((t) => t.id !== toast.id));
382
- return;
383
- }
384
- context.set("toasts", (prev) => {
385
- const index = prev.findIndex((t) => t.id === toast.id);
386
- if (index !== -1) {
387
- return [...prev.slice(0, index), { ...prev[index], ...toast }, ...prev.slice(index + 1)];
388
- }
389
- return [toast, ...prev];
390
- });
391
- });
392
- },
393
- trackHotKeyPress({ prop, send }) {
394
- const handleKeyDown = (event) => {
395
- const { hotkey } = prop("store").attrs;
396
- const isHotkeyPressed = hotkey.every((key) => event[key] || event.code === key);
397
- if (!isHotkeyPressed) return;
398
- send({ type: "DOC.HOTKEY" });
399
- };
400
- return addDomEvent(document, "keydown", handleKeyDown, { capture: true });
401
- },
402
- trackDocumentVisibility({ prop, send, scope }) {
403
- const { pauseOnPageIdle } = prop("store").attrs;
404
- if (!pauseOnPageIdle) return;
405
- const doc = scope.getDoc();
406
- return addDomEvent(doc, "visibilitychange", () => {
407
- const isHidden = doc.visibilityState === "hidden";
408
- send({ type: isHidden ? "PAUSE_ALL" : "RESUME_ALL" });
409
- });
410
- }
411
- },
412
- actions: {
413
- setDismissableBranch({ refs, context, computed, scope }) {
414
- const toasts = context.get("toasts");
415
- const placement = computed("placement");
416
- const hasToasts = toasts.length > 0;
417
- if (!hasToasts) {
418
- refs.get("dismissableCleanup")?.();
419
- return;
420
- }
421
- if (hasToasts && refs.get("dismissableCleanup")) {
422
- return;
423
- }
424
- const groupEl = () => getRegionEl(scope, placement);
425
- const cleanup = trackDismissableBranch(groupEl, { defer: true });
426
- refs.set("dismissableCleanup", cleanup);
427
- },
428
- clearDismissableBranch({ refs }) {
429
- refs.get("dismissableCleanup")?.();
430
- },
431
- focusRegionEl({ scope, computed }) {
432
- queueMicrotask(() => {
433
- getRegionEl(scope, computed("placement"))?.focus();
434
- });
435
- },
436
- pauseToasts({ prop }) {
437
- prop("store").pause();
438
- },
439
- resumeToasts({ prop }) {
440
- prop("store").resume();
441
- },
442
- expandToasts({ prop }) {
443
- prop("store").expand();
444
- },
445
- collapseToasts({ prop }) {
446
- prop("store").collapse();
447
- },
448
- removeToast({ prop, event }) {
449
- prop("store").remove(event.id);
450
- },
451
- removeHeight({ event, context }) {
452
- if (event?.id == null) return;
453
- queueMicrotask(() => {
454
- context.set("heights", (heights) => heights.filter((height) => height.id !== event.id));
455
- });
456
- },
457
- collapsedIfEmpty({ send, computed }) {
458
- if (!computed("overlap") || computed("count") > 1) return;
459
- send({ type: "REGION.OVERLAP" });
460
- },
461
- setLastFocusedEl({ refs, event }) {
462
- if (refs.get("isFocusWithin") || !event.target) return;
463
- refs.set("isFocusWithin", true);
464
- refs.set("lastFocusedEl", event.target);
465
- },
466
- restoreFocusIfPointerOut({ refs }) {
467
- if (!refs.get("lastFocusedEl") || refs.get("isPointerWithin")) return;
468
- refs.get("lastFocusedEl")?.focus({ preventScroll: true });
469
- refs.set("lastFocusedEl", null);
470
- refs.set("isFocusWithin", false);
471
- },
472
- setPointerWithin({ refs }) {
473
- refs.set("isPointerWithin", true);
474
- },
475
- clearPointerWithin({ refs }) {
476
- refs.set("isPointerWithin", false);
477
- if (refs.get("lastFocusedEl") && !refs.get("isFocusWithin")) {
478
- refs.get("lastFocusedEl")?.focus({ preventScroll: true });
479
- refs.set("lastFocusedEl", null);
480
- }
481
- },
482
- clearFocusWithin({ refs }) {
483
- refs.set("isFocusWithin", false);
484
- },
485
- clearLastFocusedEl({ refs }) {
486
- if (!refs.get("lastFocusedEl")) return;
487
- refs.get("lastFocusedEl")?.focus({ preventScroll: true });
488
- refs.set("lastFocusedEl", null);
489
- refs.set("isFocusWithin", false);
490
- },
491
- ignoreMouseEventsTemporarily({ refs }) {
492
- refs.get("ignoreMouseTimer").request();
493
- },
494
- clearMouseEventTimer({ refs }) {
495
- refs.get("ignoreMouseTimer").cancel();
496
- }
497
- }
498
- }
499
- });
500
- function connect(service, normalize) {
501
- const { state, send, prop, scope, context, computed } = service;
502
- const visible = state.hasTag("visible");
503
- const paused = state.hasTag("paused");
504
- const mounted = context.get("mounted");
505
- const frontmost = computed("frontmost");
506
- const placement = prop("parent").computed("placement");
507
- const type = prop("type");
508
- const stacked = prop("stacked");
509
- const title = prop("title");
510
- const description = prop("description");
511
- const action = prop("action");
512
- const [side, align = "center"] = placement.split("-");
513
- return {
514
- type,
515
- title,
516
- description,
517
- placement,
518
- visible,
519
- paused,
520
- closable: !!prop("closable"),
521
- pause() {
522
- send({ type: "PAUSE" });
523
- },
524
- resume() {
525
- send({ type: "RESUME" });
526
- },
527
- dismiss() {
528
- send({ type: "DISMISS", src: "programmatic" });
529
- },
530
- getRootProps() {
531
- return normalize.element({
532
- ...parts.root.attrs,
533
- dir: prop("dir"),
534
- id: getRootId(scope),
535
- "data-state": visible ? "open" : "closed",
536
- "data-type": type,
537
- "data-placement": placement,
538
- "data-align": align,
539
- "data-side": side,
540
- "data-mounted": dataAttr(mounted),
541
- "data-paused": dataAttr(paused),
542
- "data-first": dataAttr(frontmost),
543
- "data-sibling": dataAttr(!frontmost),
544
- "data-stack": dataAttr(stacked),
545
- "data-overlap": dataAttr(!stacked),
546
- role: "status",
547
- "aria-atomic": "true",
548
- "aria-describedby": description ? getDescriptionId(scope) : void 0,
549
- "aria-labelledby": title ? getTitleId(scope) : void 0,
550
- tabIndex: 0,
551
- style: getPlacementStyle(service, visible),
552
- onKeyDown(event) {
553
- if (event.defaultPrevented) return;
554
- if (event.key == "Escape") {
555
- send({ type: "DISMISS", src: "keyboard" });
556
- event.preventDefault();
557
- }
558
- }
559
- });
560
- },
561
- /* Leave a ghost div to avoid setting hover to false when transitioning out */
562
- getGhostBeforeProps() {
563
- return normalize.element({
564
- "data-ghost": "before",
565
- style: getGhostBeforeStyle(service, visible)
566
- });
567
- },
568
- /* Needed to avoid setting hover to false when in between toasts */
569
- getGhostAfterProps() {
570
- return normalize.element({
571
- "data-ghost": "after",
572
- style: getGhostAfterStyle()
573
- });
574
- },
575
- getTitleProps() {
576
- return normalize.element({
577
- ...parts.title.attrs,
578
- id: getTitleId(scope)
579
- });
580
- },
581
- getDescriptionProps() {
582
- return normalize.element({
583
- ...parts.description.attrs,
584
- id: getDescriptionId(scope)
585
- });
586
- },
587
- getActionTriggerProps() {
588
- return normalize.button({
589
- ...parts.actionTrigger.attrs,
590
- type: "button",
591
- onClick(event) {
592
- if (event.defaultPrevented) return;
593
- action?.onClick?.();
594
- send({ type: "DISMISS", src: "user" });
595
- }
596
- });
597
- },
598
- getCloseTriggerProps() {
599
- return normalize.button({
600
- id: getCloseTriggerId(scope),
601
- ...parts.closeTrigger.attrs,
602
- type: "button",
603
- "aria-label": "Dismiss notification",
604
- onClick(event) {
605
- if (event.defaultPrevented) return;
606
- send({ type: "DISMISS", src: "user" });
607
- }
608
- });
609
- }
610
- };
611
- }
612
- var { not } = createGuards();
613
- var machine = createMachine$1({
614
- props({ props }) {
615
- ensureProps(props, ["id", "type", "parent", "removeDelay"], "toast");
616
- return {
617
- closable: true,
618
- ...props,
619
- duration: getToastDuration(props.duration, props.type)
620
- };
621
- },
622
- initialState({ prop }) {
623
- const persist = prop("type") === "loading" || prop("duration") === Infinity;
624
- return persist ? "visible:persist" : "visible";
625
- },
626
- context({ prop, bindable }) {
627
- return {
628
- remainingTime: bindable(() => ({
629
- defaultValue: getToastDuration(prop("duration"), prop("type"))
630
- })),
631
- createdAt: bindable(() => ({
632
- defaultValue: Date.now()
633
- })),
634
- mounted: bindable(() => ({
635
- defaultValue: false
636
- })),
637
- initialHeight: bindable(() => ({
638
- defaultValue: 0
639
- }))
640
- };
641
- },
642
- refs() {
643
- return {
644
- closeTimerStartTime: Date.now(),
645
- lastCloseStartTimerStartTime: 0
646
- };
647
- },
648
- computed: {
649
- zIndex: ({ prop }) => {
650
- const toasts = prop("parent").context.get("toasts");
651
- const index = toasts.findIndex((toast) => toast.id === prop("id"));
652
- return toasts.length - index;
653
- },
654
- height: ({ prop }) => {
655
- const heights = prop("parent").context.get("heights");
656
- const height = heights.find((height2) => height2.id === prop("id"));
657
- return height?.height ?? 0;
658
- },
659
- heightIndex: ({ prop }) => {
660
- const heights = prop("parent").context.get("heights");
661
- return heights.findIndex((height) => height.id === prop("id"));
662
- },
663
- frontmost: ({ prop }) => prop("index") === 0,
664
- heightBefore: ({ prop }) => {
665
- const heights = prop("parent").context.get("heights");
666
- const heightIndex = heights.findIndex((height) => height.id === prop("id"));
667
- return heights.reduce((prev, curr, reducerIndex) => {
668
- if (reducerIndex >= heightIndex) return prev;
669
- return prev + curr.height;
670
- }, 0);
671
- },
672
- shouldPersist: ({ prop }) => prop("type") === "loading" || prop("duration") === Infinity
673
- },
674
- watch({ track, prop, send }) {
675
- track([() => prop("message")], () => {
676
- const message = prop("message");
677
- if (message) send({ type: message, src: "programmatic" });
678
- });
679
- track([() => prop("type"), () => prop("duration")], () => {
680
- send({ type: "UPDATE" });
681
- });
682
- },
683
- on: {
684
- UPDATE: [
685
- {
686
- guard: "shouldPersist",
687
- target: "visible:persist",
688
- actions: ["resetCloseTimer"]
689
- },
690
- {
691
- target: "visible:updating",
692
- actions: ["resetCloseTimer"]
693
- }
694
- ],
695
- MEASURE: {
696
- actions: ["measureHeight"]
697
- }
698
- },
699
- entry: ["setMounted", "measureHeight", "invokeOnVisible"],
700
- effects: ["trackHeight"],
701
- states: {
702
- "visible:updating": {
703
- tags: ["visible", "updating"],
704
- effects: ["waitForNextTick"],
705
- on: {
706
- SHOW: {
707
- target: "visible"
708
- }
709
- }
710
- },
711
- "visible:persist": {
712
- tags: ["visible", "paused"],
713
- on: {
714
- RESUME: {
715
- guard: not("isLoadingType"),
716
- target: "visible",
717
- actions: ["setCloseTimer"]
718
- },
719
- DISMISS: {
720
- target: "dismissing"
721
- }
722
- }
723
- },
724
- visible: {
725
- tags: ["visible"],
726
- effects: ["waitForDuration"],
727
- on: {
728
- DISMISS: {
729
- target: "dismissing"
730
- },
731
- PAUSE: {
732
- target: "visible:persist",
733
- actions: ["syncRemainingTime"]
734
- }
735
- }
736
- },
737
- dismissing: {
738
- entry: ["invokeOnDismiss"],
739
- effects: ["waitForRemoveDelay"],
740
- on: {
741
- REMOVE: {
742
- target: "unmounted",
743
- actions: ["notifyParentToRemove"]
744
- }
745
- }
746
- },
747
- unmounted: {
748
- entry: ["invokeOnUnmount"]
749
- }
750
- },
751
- implementations: {
752
- effects: {
753
- waitForRemoveDelay({ prop, send }) {
754
- return setRafTimeout(() => {
755
- send({ type: "REMOVE", src: "timer" });
756
- }, prop("removeDelay"));
757
- },
758
- waitForDuration({ send, context, computed }) {
759
- if (computed("shouldPersist")) return;
760
- return setRafTimeout(() => {
761
- send({ type: "DISMISS", src: "timer" });
762
- }, context.get("remainingTime"));
763
- },
764
- waitForNextTick({ send }) {
765
- return setRafTimeout(() => {
766
- send({ type: "SHOW", src: "timer" });
767
- }, 0);
768
- },
769
- trackHeight({ scope, prop }) {
770
- let cleanup;
771
- raf(() => {
772
- const rootEl = getRootEl(scope);
773
- if (!rootEl) return;
774
- const syncHeight = () => {
775
- const originalHeight = rootEl.style.height;
776
- rootEl.style.height = "auto";
777
- const height = rootEl.getBoundingClientRect().height;
778
- rootEl.style.height = originalHeight;
779
- const item = { id: prop("id"), height };
780
- setHeight(prop("parent"), item);
781
- };
782
- const win = scope.getWin();
783
- const observer = new win.MutationObserver(syncHeight);
784
- observer.observe(rootEl, {
785
- childList: true,
786
- subtree: true,
787
- characterData: true
788
- });
789
- cleanup = () => observer.disconnect();
790
- });
791
- return () => cleanup?.();
792
- }
793
- },
794
- guards: {
795
- isLoadingType: ({ prop }) => prop("type") === "loading",
796
- shouldPersist: ({ computed }) => computed("shouldPersist")
797
- },
798
- actions: {
799
- setMounted({ context }) {
800
- raf(() => {
801
- context.set("mounted", true);
802
- });
803
- },
804
- measureHeight({ scope, prop, context }) {
805
- queueMicrotask(() => {
806
- const rootEl = getRootEl(scope);
807
- if (!rootEl) return;
808
- const originalHeight = rootEl.style.height;
809
- rootEl.style.height = "auto";
810
- const height = rootEl.getBoundingClientRect().height;
811
- rootEl.style.height = originalHeight;
812
- context.set("initialHeight", height);
813
- const item = { id: prop("id"), height };
814
- setHeight(prop("parent"), item);
815
- });
816
- },
817
- setCloseTimer({ refs }) {
818
- refs.set("closeTimerStartTime", Date.now());
819
- },
820
- resetCloseTimer({ context, refs, prop }) {
821
- refs.set("closeTimerStartTime", Date.now());
822
- context.set("remainingTime", getToastDuration(prop("duration"), prop("type")));
823
- },
824
- syncRemainingTime({ context, refs }) {
825
- context.set("remainingTime", (prev) => {
826
- const closeTimerStartTime = refs.get("closeTimerStartTime");
827
- const elapsedTime = Date.now() - closeTimerStartTime;
828
- refs.set("lastCloseStartTimerStartTime", Date.now());
829
- return prev - elapsedTime;
830
- });
831
- },
832
- notifyParentToRemove({ prop }) {
833
- const parent = prop("parent");
834
- parent.send({ type: "TOAST.REMOVE", id: prop("id") });
835
- },
836
- invokeOnDismiss({ prop, event }) {
837
- prop("onStatusChange")?.({ status: "dismissing", src: event.src });
838
- },
839
- invokeOnUnmount({ prop }) {
840
- prop("onStatusChange")?.({ status: "unmounted" });
841
- },
842
- invokeOnVisible({ prop }) {
843
- prop("onStatusChange")?.({ status: "visible" });
844
- }
845
- }
846
- }
847
- });
848
- function setHeight(parent, item) {
849
- const { id, height } = item;
850
- parent.context.set("heights", (prev) => {
851
- const alreadyExists = prev.find((i) => i.id === id);
852
- if (!alreadyExists) {
853
- return [{ id, height }, ...prev];
854
- } else {
855
- return prev.map((i) => i.id === id ? { ...i, height } : i);
856
- }
857
- });
858
- }
859
- var withDefaults = (options, defaults) => {
860
- return { ...defaults, ...compact(options) };
861
- };
862
- function createToastStore(props = {}) {
863
- const attrs = withDefaults(props, {
864
- placement: "bottom",
865
- overlap: false,
866
- max: 24,
867
- gap: 16,
868
- offsets: "1rem",
869
- hotkey: ["altKey", "KeyT"],
870
- removeDelay: 200,
871
- pauseOnPageIdle: true
872
- });
873
- let subscribers = [];
874
- let toasts = [];
875
- let dismissedToasts = /* @__PURE__ */ new Set();
876
- let toastQueue = [];
877
- const subscribe = (subscriber) => {
878
- subscribers.push(subscriber);
879
- return () => {
880
- const index = subscribers.indexOf(subscriber);
881
- subscribers.splice(index, 1);
882
- };
883
- };
884
- const publish = (data) => {
885
- subscribers.forEach((subscriber) => subscriber(data));
886
- return data;
887
- };
888
- const addToast = (data) => {
889
- if (toasts.length >= attrs.max) {
890
- toastQueue.push(data);
891
- return;
892
- }
893
- publish(data);
894
- toasts.unshift(data);
895
- };
896
- const processQueue = () => {
897
- while (toastQueue.length > 0 && toasts.length < attrs.max) {
898
- const nextToast = toastQueue.shift();
899
- if (nextToast) {
900
- publish(nextToast);
901
- toasts.unshift(nextToast);
902
- }
903
- }
904
- };
905
- const create = (data) => {
906
- const id = data.id ?? `toast:${uuid()}`;
907
- const exists = toasts.find((toast) => toast.id === id);
908
- if (dismissedToasts.has(id)) dismissedToasts.delete(id);
909
- if (exists) {
910
- toasts = toasts.map((toast) => {
911
- if (toast.id === id) {
912
- return publish({ ...toast, ...data, id });
913
- }
914
- return toast;
915
- });
916
- } else {
917
- addToast({
918
- id,
919
- duration: attrs.duration,
920
- removeDelay: attrs.removeDelay,
921
- type: "info",
922
- ...data,
923
- stacked: !attrs.overlap,
924
- gap: attrs.gap
925
- });
926
- }
927
- return id;
928
- };
929
- const remove = (id) => {
930
- dismissedToasts.add(id);
931
- if (!id) {
932
- toasts.forEach((toast) => {
933
- subscribers.forEach((subscriber) => subscriber({ id: toast.id, dismiss: true }));
934
- });
935
- toasts = [];
936
- toastQueue = [];
937
- } else {
938
- subscribers.forEach((subscriber) => subscriber({ id, dismiss: true }));
939
- toasts = toasts.filter((toast) => toast.id !== id);
940
- processQueue();
941
- }
942
- return id;
943
- };
944
- const error = (data) => {
945
- return create({ ...data, type: "error" });
946
- };
947
- const success = (data) => {
948
- return create({ ...data, type: "success" });
949
- };
950
- const info = (data) => {
951
- return create({ ...data, type: "info" });
952
- };
953
- const warning = (data) => {
954
- return create({ ...data, type: "warning" });
955
- };
956
- const loading = (data) => {
957
- return create({ ...data, type: "loading" });
958
- };
959
- const getVisibleToasts = () => {
960
- return toasts.filter((toast) => !dismissedToasts.has(toast.id));
961
- };
962
- const getCount = () => {
963
- return toasts.length;
964
- };
965
- const promise = (promise2, options, shared = {}) => {
966
- if (!options || !options.loading) {
967
- warn("[zag-js > toast] toaster.promise() requires at least a 'loading' option to be specified");
968
- return;
969
- }
970
- const id = create({
971
- ...shared,
972
- ...options.loading,
973
- promise: promise2,
974
- type: "loading"
975
- });
976
- let removable = true;
977
- let result;
978
- const prom = runIfFn(promise2).then(async (response) => {
979
- result = ["resolve", response];
980
- if (isHttpResponse(response) && !response.ok) {
981
- removable = false;
982
- const errorOptions = runIfFn(options.error, `HTTP Error! status: ${response.status}`);
983
- create({ ...shared, ...errorOptions, id, type: "error" });
984
- } else if (options.success !== void 0) {
985
- removable = false;
986
- const successOptions = runIfFn(options.success, response);
987
- create({ ...shared, ...successOptions, id, type: "success" });
988
- }
989
- }).catch(async (error2) => {
990
- result = ["reject", error2];
991
- if (options.error !== void 0) {
992
- removable = false;
993
- const errorOptions = runIfFn(options.error, error2);
994
- create({ ...shared, ...errorOptions, id, type: "error" });
995
- }
996
- }).finally(() => {
997
- if (removable) {
998
- remove(id);
999
- }
1000
- options.finally?.();
1001
- });
1002
- const unwrap = () => new Promise(
1003
- (resolve, reject) => prom.then(() => result[0] === "reject" ? reject(result[1]) : resolve(result[1])).catch(reject)
1004
- );
1005
- return { id, unwrap };
1006
- };
1007
- const update = (id, data) => {
1008
- return create({ id, ...data });
1009
- };
1010
- const pause = (id) => {
1011
- if (id != null) {
1012
- toasts = toasts.map((toast) => {
1013
- if (toast.id === id) return publish({ ...toast, message: "PAUSE" });
1014
- return toast;
1015
- });
1016
- } else {
1017
- toasts = toasts.map((toast) => publish({ ...toast, message: "PAUSE" }));
1018
- }
1019
- };
1020
- const resume = (id) => {
1021
- if (id != null) {
1022
- toasts = toasts.map((toast) => {
1023
- if (toast.id === id) return publish({ ...toast, message: "RESUME" });
1024
- return toast;
1025
- });
1026
- } else {
1027
- toasts = toasts.map((toast) => publish({ ...toast, message: "RESUME" }));
1028
- }
1029
- };
1030
- const dismiss = (id) => {
1031
- if (id != null) {
1032
- toasts = toasts.map((toast) => {
1033
- if (toast.id === id) return publish({ ...toast, message: "DISMISS" });
1034
- return toast;
1035
- });
1036
- } else {
1037
- toasts = toasts.map((toast) => publish({ ...toast, message: "DISMISS" }));
1038
- }
1039
- };
1040
- const isVisible = (id) => {
1041
- return !dismissedToasts.has(id) && !!toasts.find((toast) => toast.id === id);
1042
- };
1043
- const isDismissed = (id) => {
1044
- return dismissedToasts.has(id);
1045
- };
1046
- const expand = () => {
1047
- toasts = toasts.map((toast) => publish({ ...toast, stacked: true }));
1048
- };
1049
- const collapse = () => {
1050
- toasts = toasts.map((toast) => publish({ ...toast, stacked: false }));
1051
- };
1052
- return {
1053
- attrs,
1054
- subscribe,
1055
- create,
1056
- update,
1057
- remove,
1058
- dismiss,
1059
- error,
1060
- success,
1061
- info,
1062
- warning,
1063
- loading,
1064
- getVisibleToasts,
1065
- getCount,
1066
- promise,
1067
- pause,
1068
- resume,
1069
- isVisible,
1070
- isDismissed,
1071
- expand,
1072
- collapse
1073
- };
1074
- }
1075
- var isHttpResponse = (data) => {
1076
- return data && typeof data === "object" && "ok" in data && typeof data.ok === "boolean" && "status" in data && typeof data.status === "number";
1077
- };
1078
-
1079
1
  // src/index.ts
2
+ import { groupConnect } from "./toast-group.connect.mjs";
3
+ import { groupMachine } from "./toast-group.machine.mjs";
4
+ import { anatomy } from "./toast.anatomy.mjs";
5
+ import { connect } from "./toast.connect.mjs";
6
+ import { machine } from "./toast.machine.mjs";
7
+ import { createToastStore } from "./toast.store.mjs";
1080
8
  var group = {
1081
9
  connect: groupConnect,
1082
10
  machine: groupMachine
1083
11
  };
1084
-
1085
- export { anatomy, connect, createToastStore as createStore, group, machine };
12
+ export {
13
+ anatomy,
14
+ connect,
15
+ createToastStore as createStore,
16
+ group,
17
+ machine
18
+ };