@zag-js/toast 0.44.0 → 0.46.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.
package/dist/index.js CHANGED
@@ -29,24 +29,36 @@ module.exports = __toCommonJS(src_exports);
29
29
 
30
30
  // src/toast-group.connect.ts
31
31
  var import_core = require("@zag-js/core");
32
+ var import_dom_query3 = require("@zag-js/dom-query");
32
33
  var import_utils = require("@zag-js/utils");
33
34
 
34
35
  // src/toast.anatomy.ts
35
36
  var import_anatomy = require("@zag-js/anatomy");
36
- var anatomy = (0, import_anatomy.createAnatomy)("toast").parts("group", "root", "title", "description", "closeTrigger");
37
+ var anatomy = (0, import_anatomy.createAnatomy)("toast").parts(
38
+ "group",
39
+ "root",
40
+ "ghost",
41
+ "title",
42
+ "description",
43
+ "actionTrigger",
44
+ "closeTrigger"
45
+ );
37
46
  var parts = anatomy.build();
38
47
 
39
48
  // src/toast.dom.ts
40
49
  var import_dom_query = require("@zag-js/dom-query");
41
50
  var dom = (0, import_dom_query.createScope)({
42
- getGroupId: (placement) => `toast-group:${placement}`,
51
+ getRegionId: (placement) => `toast-group:${placement}`,
52
+ getRegionEl: (ctx, placement) => dom.getById(ctx, `toast-group:${placement}`),
43
53
  getRootId: (ctx) => `toast:${ctx.id}`,
54
+ getRootEl: (ctx) => dom.getById(ctx, dom.getRootId(ctx)),
44
55
  getTitleId: (ctx) => `toast:${ctx.id}:title`,
45
56
  getDescriptionId: (ctx) => `toast:${ctx.id}:description`,
46
57
  getCloseTriggerId: (ctx) => `toast${ctx.id}:close`
47
58
  });
48
59
 
49
60
  // src/toast.utils.ts
61
+ var import_dom_query2 = require("@zag-js/dom-query");
50
62
  function getToastsByPlacement(toasts) {
51
63
  const result = {};
52
64
  for (const toast of toasts) {
@@ -61,10 +73,10 @@ var defaultTimeouts = {
61
73
  error: 5e3,
62
74
  success: 2e3,
63
75
  loading: Infinity,
64
- custom: 5e3
76
+ DEFAULT: 5e3
65
77
  };
66
78
  function getToastDuration(duration, type) {
67
- return duration ?? defaultTimeouts[type];
79
+ return duration ?? defaultTimeouts[type] ?? defaultTimeouts.DEFAULT;
68
80
  }
69
81
  function getGroupPlacementStyle(ctx, placement) {
70
82
  const offset = ctx.offsets;
@@ -78,8 +90,9 @@ function getGroupPlacementStyle(ctx, placement) {
78
90
  pointerEvents: ctx.count > 0 ? void 0 : "none",
79
91
  display: "flex",
80
92
  flexDirection: "column",
81
- "--toast-gutter": ctx.gutter,
82
- zIndex: ctx.zIndex
93
+ "--gap": `${ctx.gap}px`,
94
+ "--first-height": `${ctx.heights[0]?.height || 0}px`,
95
+ zIndex: import_dom_query2.MAX_Z_INDEX
83
96
  };
84
97
  let alignItems = "center";
85
98
  if (isRighty)
@@ -89,42 +102,150 @@ function getGroupPlacementStyle(ctx, placement) {
89
102
  styles.alignItems = alignItems;
90
103
  if (computedPlacement.includes("top")) {
91
104
  const offset2 = computedOffset.top;
92
- styles.top = `calc(env(safe-area-inset-top, 0px) + ${offset2})`;
105
+ styles.top = `max(env(safe-area-inset-top, 0px), ${offset2})`;
93
106
  }
94
107
  if (computedPlacement.includes("bottom")) {
95
108
  const offset2 = computedOffset.bottom;
96
- styles.bottom = `calc(env(safe-area-inset-bottom, 0px) + ${offset2})`;
109
+ styles.bottom = `max(env(safe-area-inset-bottom, 0px), ${offset2})`;
97
110
  }
98
111
  if (!computedPlacement.includes("left")) {
99
112
  const offset2 = computedOffset.right;
100
- styles.right = `calc(env(safe-area-inset-right, 0px) + ${offset2})`;
113
+ styles.insetInlineEnd = `calc(env(safe-area-inset-right, 0px) + ${offset2})`;
101
114
  }
102
115
  if (!computedPlacement.includes("right")) {
103
116
  const offset2 = computedOffset.left;
104
- styles.left = `calc(env(safe-area-inset-left, 0px) + ${offset2})`;
117
+ styles.insetInlineStart = `calc(env(safe-area-inset-left, 0px) + ${offset2})`;
118
+ }
119
+ return styles;
120
+ }
121
+ function getPlacementStyle(ctx, visible) {
122
+ const [side] = ctx.placement.split("-");
123
+ const sibling = !ctx.frontmost;
124
+ const overlap = !ctx.stacked;
125
+ const styles = {
126
+ position: "absolute",
127
+ pointerEvents: "auto",
128
+ "--opacity": "0",
129
+ "--remove-delay": `${ctx.removeDelay}ms`,
130
+ "--duration": `${ctx.type === "loading" ? Number.MAX_SAFE_INTEGER : ctx.duration}ms`,
131
+ "--initial-height": `${ctx.height}px`,
132
+ "--offset": `${ctx.offset}px`,
133
+ "--index": ctx.index,
134
+ "--z-index": ctx.zIndex,
135
+ "--lift-amount": "calc(var(--lift) * var(--gap))",
136
+ "--y": "100%",
137
+ "--x": "0"
138
+ };
139
+ const assign = (overrides) => Object.assign(styles, overrides);
140
+ if (side === "top") {
141
+ assign({
142
+ top: "0",
143
+ "--sign": "-1",
144
+ "--y": "-100%",
145
+ "--lift": "1"
146
+ });
147
+ } else if (side === "bottom") {
148
+ assign({
149
+ bottom: "0",
150
+ "--sign": "1",
151
+ "--y": "100%",
152
+ "--lift": "-1"
153
+ });
154
+ }
155
+ if (ctx.mounted) {
156
+ assign({
157
+ "--y": "0",
158
+ "--opacity": "1"
159
+ });
160
+ if (ctx.stacked) {
161
+ assign({
162
+ "--y": "calc(var(--lift) * var(--offset))",
163
+ "--height": "var(--initial-height)"
164
+ });
165
+ }
166
+ }
167
+ if (!visible) {
168
+ assign({
169
+ "--opacity": "0",
170
+ pointerEvents: "none"
171
+ });
172
+ }
173
+ if (sibling && overlap) {
174
+ assign({
175
+ "--base-scale": "var(--index) * 0.05 + 1",
176
+ "--y": "calc(var(--lift-amount) * var(--index))",
177
+ "--scale": "calc(-1 * var(--base-scale))",
178
+ "--height": "var(--first-height)"
179
+ });
180
+ if (!visible) {
181
+ assign({
182
+ "--y": "calc(var(--sign) * 40%)"
183
+ });
184
+ }
185
+ }
186
+ if (sibling && ctx.stacked && !visible) {
187
+ assign({
188
+ "--y": "calc(var(--lift) * var(--offset) + var(--lift) * -100%)"
189
+ });
190
+ }
191
+ if (ctx.frontmost && !visible) {
192
+ assign({
193
+ "--y": "calc(var(--lift) * -100%)"
194
+ });
105
195
  }
106
196
  return styles;
107
197
  }
198
+ function getGhostBeforeStyle(ctx, visible) {
199
+ const styles = {
200
+ position: "absolute",
201
+ inset: "0",
202
+ scale: "1 2",
203
+ pointerEvents: visible ? "none" : "auto"
204
+ };
205
+ const assign = (overrides) => Object.assign(styles, overrides);
206
+ if (ctx.frontmost && !visible) {
207
+ assign({
208
+ height: "calc(var(--initial-height) + 80%)"
209
+ });
210
+ }
211
+ return styles;
212
+ }
213
+ function getGhostAfterStyle(_ctx, _visible) {
214
+ return {
215
+ position: "absolute",
216
+ left: "0",
217
+ height: "calc(var(--gap) + 2px)",
218
+ bottom: "100%",
219
+ width: "100%"
220
+ };
221
+ }
108
222
 
109
223
  // src/toast-group.connect.ts
110
- function groupConnect(state, send, normalize) {
111
- const toastsByPlacement = getToastsByPlacement(state.context.toasts);
224
+ function groupConnect(serviceOrState, send, normalize) {
225
+ function getState() {
226
+ const result = (0, import_core.isMachine)(serviceOrState) ? serviceOrState.getState() : serviceOrState;
227
+ return result;
228
+ }
229
+ function getToastsByPlacementImpl() {
230
+ return getToastsByPlacement(getState().context.toasts);
231
+ }
112
232
  function isVisible(id) {
113
- if (!state.context.toasts.length)
233
+ const toasts = getState().context.toasts;
234
+ if (!toasts.length)
114
235
  return false;
115
- return !!state.context.toasts.find((toast) => toast.id == id);
236
+ return !!toasts.find((toast) => toast.id == id);
116
237
  }
117
238
  function create(options) {
118
239
  const uid = `toast:${(0, import_utils.uuid)()}`;
119
240
  const id = options.id ? options.id : uid;
120
241
  if (isVisible(id))
121
- return;
242
+ return id;
122
243
  send({ type: "ADD_TOAST", toast: { ...options, id } });
123
244
  return id;
124
245
  }
125
246
  function update(id, options) {
126
247
  if (!isVisible(id))
127
- return;
248
+ return id;
128
249
  send({ type: "UPDATE_TOAST", id, toast: options });
129
250
  return id;
130
251
  }
@@ -145,9 +266,13 @@ function groupConnect(state, send, normalize) {
145
266
  }
146
267
  }
147
268
  return {
148
- count: state.context.count,
149
- toasts: state.context.toasts,
150
- toastsByPlacement,
269
+ getCount() {
270
+ return getState().context.count;
271
+ },
272
+ getToasts() {
273
+ return getState().context.toasts;
274
+ },
275
+ getToastsByPlacement: getToastsByPlacementImpl,
151
276
  isVisible,
152
277
  create,
153
278
  update,
@@ -161,10 +286,11 @@ function groupConnect(state, send, normalize) {
161
286
  }
162
287
  },
163
288
  dismissByPlacement(placement) {
289
+ const toastsByPlacement = getToastsByPlacementImpl();
164
290
  const toasts = toastsByPlacement[placement];
165
- if (toasts) {
166
- toasts.forEach((toast) => dismiss(toast.id));
167
- }
291
+ if (!toasts)
292
+ return;
293
+ toasts.forEach((toast) => dismiss(toast.id));
168
294
  },
169
295
  loading(options) {
170
296
  return upsert({ ...options, type: "loading" });
@@ -177,14 +303,16 @@ function groupConnect(state, send, normalize) {
177
303
  },
178
304
  promise(promise, options, shared = {}) {
179
305
  const id = upsert({ ...shared, ...options.loading, type: "loading" });
180
- promise.then((response) => {
306
+ (0, import_utils.runIfFn)(promise).then((response) => {
181
307
  const successOptions = (0, import_utils.runIfFn)(options.success, response);
182
308
  upsert({ ...shared, ...successOptions, id, type: "success" });
183
309
  }).catch((error) => {
184
310
  const errorOptions = (0, import_utils.runIfFn)(options.error, error);
185
311
  upsert({ ...shared, ...errorOptions, id, type: "error" });
312
+ }).finally(() => {
313
+ options.finally?.();
186
314
  });
187
- return promise;
315
+ return id;
188
316
  },
189
317
  pause(id) {
190
318
  if (id == null) {
@@ -202,43 +330,66 @@ function groupConnect(state, send, normalize) {
202
330
  },
203
331
  getGroupProps(options) {
204
332
  const { placement, label = "Notifications" } = options;
333
+ const state = getState();
334
+ const hotkeyLabel = state.context.hotkey.join("+").replace(/Key/g, "").replace(/Digit/g, "");
335
+ const [side, align = "center"] = placement.split("-");
205
336
  return normalize.element({
206
337
  ...parts.group.attrs,
207
338
  dir: state.context.dir,
208
339
  tabIndex: -1,
209
- "aria-label": `${placement} ${label}`,
210
- id: dom.getGroupId(placement),
340
+ "aria-label": `${placement} ${label} ${hotkeyLabel}`,
341
+ id: dom.getRegionId(placement),
211
342
  "data-placement": placement,
343
+ "data-side": side,
344
+ "data-align": align,
212
345
  "aria-live": "polite",
213
346
  role: "region",
214
- style: getGroupPlacementStyle(state.context, placement)
347
+ style: getGroupPlacementStyle(state.context, placement),
348
+ onMouseMove() {
349
+ send({ type: "REGION.POINTER_ENTER", placement });
350
+ },
351
+ onMouseLeave() {
352
+ send({ type: "REGION.POINTER_LEAVE", placement });
353
+ },
354
+ onFocus(event) {
355
+ send({ type: "REGION.FOCUS", target: event.relatedTarget });
356
+ },
357
+ onBlur(event) {
358
+ if (state.context.isFocusWithin && !(0, import_dom_query3.contains)(event.currentTarget, event.relatedTarget)) {
359
+ send({ type: "REGION.BLUR" });
360
+ }
361
+ }
215
362
  });
216
363
  },
217
364
  subscribe(fn) {
218
- return (0, import_core.subscribe)(state.context.toasts, () => fn(state.context.toasts));
365
+ const state = getState();
366
+ return (0, import_core.subscribe)(state.context.toasts, () => {
367
+ const toasts = getToastsByPlacementImpl()[state.context.placement] ?? [];
368
+ const contexts = toasts.map((toast) => toast.getState().context);
369
+ fn(contexts);
370
+ });
219
371
  }
220
372
  };
221
373
  }
222
374
 
223
375
  // src/toast-group.machine.ts
224
376
  var import_core3 = require("@zag-js/core");
225
- var import_dom_query2 = require("@zag-js/dom-query");
377
+ var import_dismissable = require("@zag-js/dismissable");
378
+ var import_dom_event = require("@zag-js/dom-event");
226
379
  var import_utils3 = require("@zag-js/utils");
227
380
 
228
381
  // src/toast.machine.ts
229
382
  var import_core2 = require("@zag-js/core");
230
- var import_dom_event = require("@zag-js/dom-event");
383
+ var import_dom_query4 = require("@zag-js/dom-query");
231
384
  var import_utils2 = require("@zag-js/utils");
232
385
  var { not, and, or } = import_core2.guards;
233
- function createToastMachine(options = {}) {
234
- const { type = "info", duration, id = "toast", placement = "bottom", removeDelay = 0, ...restProps } = options;
386
+ function createToastMachine(options) {
387
+ const { type = "info", duration, id = "1", placement = "bottom", removeDelay = 200, ...restProps } = options;
235
388
  const ctx = (0, import_utils2.compact)(restProps);
236
389
  const computedDuration = getToastDuration(duration, type);
237
390
  return (0, import_core2.createMachine)(
238
391
  {
239
392
  id,
240
- entry: "invokeOnOpen",
241
- initial: type === "loading" ? "persist" : "active",
242
393
  context: {
243
394
  id,
244
395
  type,
@@ -247,82 +398,112 @@ function createToastMachine(options = {}) {
247
398
  removeDelay,
248
399
  createdAt: Date.now(),
249
400
  placement,
250
- ...ctx
401
+ ...ctx,
402
+ height: 0,
403
+ offset: 0,
404
+ frontmost: false,
405
+ mounted: false,
406
+ index: -1,
407
+ zIndex: 0
251
408
  },
409
+ initial: type === "loading" ? "visible:persist" : "visible",
252
410
  on: {
253
411
  UPDATE: [
254
412
  {
255
413
  guard: and("hasTypeChanged", "isChangingToLoading"),
256
- target: "persist",
257
- actions: ["setContext", "invokeOnUpdate"]
414
+ target: "visible:persist",
415
+ actions: ["setContext"]
258
416
  },
259
417
  {
260
418
  guard: or("hasDurationChanged", "hasTypeChanged"),
261
- target: "active:temp",
262
- actions: ["setContext", "invokeOnUpdate"]
419
+ target: "visible:updating",
420
+ actions: ["setContext"]
263
421
  },
264
422
  {
265
- actions: ["setContext", "invokeOnUpdate"]
423
+ actions: ["setContext"]
266
424
  }
267
- ]
425
+ ],
426
+ MEASURE: {
427
+ actions: ["measureHeight"]
428
+ }
268
429
  },
430
+ entry: ["invokeOnVisible"],
431
+ activities: ["trackHeight"],
269
432
  states: {
270
- "active:temp": {
433
+ "visible:updating": {
271
434
  tags: ["visible", "updating"],
272
435
  after: {
273
- 0: "active"
436
+ 0: "visible"
274
437
  }
275
438
  },
276
- persist: {
439
+ "visible:persist": {
277
440
  tags: ["visible", "paused"],
278
- activities: "trackDocumentVisibility",
279
441
  on: {
280
442
  RESUME: {
281
443
  guard: not("isLoadingType"),
282
- target: "active",
444
+ target: "visible",
283
445
  actions: ["setCreatedAt"]
284
446
  },
285
447
  DISMISS: "dismissing"
286
448
  }
287
449
  },
288
- active: {
450
+ visible: {
289
451
  tags: ["visible"],
290
- activities: "trackDocumentVisibility",
291
452
  after: {
292
453
  VISIBLE_DURATION: "dismissing"
293
454
  },
294
455
  on: {
295
456
  DISMISS: "dismissing",
296
457
  PAUSE: {
297
- target: "persist",
458
+ target: "visible:persist",
298
459
  actions: "setRemainingDuration"
299
460
  }
300
461
  }
301
462
  },
302
463
  dismissing: {
303
- entry: "invokeOnClosing",
464
+ entry: "invokeOnDismiss",
304
465
  after: {
305
466
  REMOVE_DELAY: {
306
- target: "inactive",
467
+ target: "unmounted",
307
468
  actions: "notifyParentToRemove"
308
469
  }
309
470
  }
310
471
  },
311
- inactive: {
312
- entry: "invokeOnClose",
472
+ unmounted: {
473
+ entry: "invokeOnUnmount",
313
474
  type: "final"
314
475
  }
315
476
  }
316
477
  },
317
478
  {
318
479
  activities: {
319
- trackDocumentVisibility(ctx2, _evt, { send }) {
320
- if (!ctx2.pauseOnPageIdle)
321
- return;
322
- const doc = dom.getDoc(ctx2);
323
- return (0, import_dom_event.addDomEvent)(doc, "visibilitychange", () => {
324
- send(doc.visibilityState === "hidden" ? "PAUSE" : "RESUME");
480
+ trackHeight(ctx2, _evt, { self }) {
481
+ let cleanup;
482
+ (0, import_dom_query4.raf)(() => {
483
+ const rootEl = dom.getRootEl(ctx2);
484
+ if (!rootEl)
485
+ return;
486
+ ctx2.mounted = true;
487
+ const ghosts = (0, import_dom_query4.queryAll)(rootEl, "[data-part=ghost]");
488
+ (0, import_utils2.warn)(
489
+ ghosts.length !== 2,
490
+ "[toast] No ghost element found in toast. Render the `ghostBefore` and `ghostAfter` elements"
491
+ );
492
+ const syncHeight = () => {
493
+ const originalHeight = rootEl.style.height;
494
+ rootEl.style.height = "auto";
495
+ const newHeight = rootEl.offsetHeight;
496
+ rootEl.style.height = originalHeight;
497
+ ctx2.height = newHeight;
498
+ self.sendParent({ type: "UPDATE_HEIGHT", id: self.id, height: newHeight, placement: ctx2.placement });
499
+ };
500
+ syncHeight();
501
+ const win = dom.getWin(ctx2);
502
+ const observer = new win.MutationObserver(syncHeight);
503
+ observer.observe(rootEl, { childList: true, subtree: true, characterData: true });
504
+ cleanup = () => observer.disconnect();
325
505
  });
506
+ return () => cleanup?.();
326
507
  }
327
508
  },
328
509
  guards: {
@@ -336,6 +517,20 @@ function createToastMachine(options = {}) {
336
517
  REMOVE_DELAY: (ctx2) => ctx2.removeDelay
337
518
  },
338
519
  actions: {
520
+ measureHeight(ctx2, _evt, { self }) {
521
+ (0, import_dom_query4.raf)(() => {
522
+ const rootEl = dom.getRootEl(ctx2);
523
+ if (!rootEl)
524
+ return;
525
+ ctx2.mounted = true;
526
+ const originalHeight = rootEl.style.height;
527
+ rootEl.style.height = "auto";
528
+ const newHeight = rootEl.offsetHeight;
529
+ rootEl.style.height = originalHeight;
530
+ ctx2.height = newHeight;
531
+ self.sendParent({ type: "UPDATE_HEIGHT", id: self.id, height: newHeight, placement: ctx2.placement });
532
+ });
533
+ },
339
534
  setRemainingDuration(ctx2) {
340
535
  ctx2.remaining -= Date.now() - ctx2.createdAt;
341
536
  },
@@ -345,22 +540,23 @@ function createToastMachine(options = {}) {
345
540
  notifyParentToRemove(_ctx, _evt, { self }) {
346
541
  self.sendParent({ type: "REMOVE_TOAST", id: self.id });
347
542
  },
348
- invokeOnClosing(ctx2) {
349
- ctx2.onClosing?.();
543
+ invokeOnDismiss(ctx2) {
544
+ ctx2.onStatusChange?.({ status: "dismissing" });
350
545
  },
351
- invokeOnClose(ctx2) {
352
- ctx2.onClose?.();
546
+ invokeOnUnmount(ctx2) {
547
+ ctx2.onStatusChange?.({ status: "unmounted" });
353
548
  },
354
- invokeOnOpen(ctx2) {
355
- ctx2.onOpen?.();
356
- },
357
- invokeOnUpdate(ctx2) {
358
- ctx2.onUpdate?.();
549
+ invokeOnVisible(ctx2) {
550
+ ctx2.onStatusChange?.({ status: "visible" });
359
551
  },
360
552
  setContext(ctx2, evt) {
361
553
  const { duration: duration2, type: type2 } = evt.toast;
362
- const time = getToastDuration(duration2, type2);
363
- Object.assign(ctx2, { ...evt.toast, duration: time, remaining: time });
554
+ if (duration2 == null && type2 == null) {
555
+ Object.assign(ctx2, evt.toast);
556
+ } else {
557
+ const time = getToastDuration(duration2, type2);
558
+ Object.assign(ctx2, { ...evt.toast, duration: time, remaining: time });
559
+ }
364
560
  }
365
561
  }
366
562
  }
@@ -370,104 +566,307 @@ function createToastMachine(options = {}) {
370
566
  // src/toast-group.machine.ts
371
567
  function groupMachine(userContext) {
372
568
  const ctx = (0, import_utils3.compact)(userContext);
373
- return (0, import_core3.createMachine)({
374
- id: "toaster",
375
- initial: "active",
376
- context: {
377
- dir: "ltr",
378
- max: Number.MAX_SAFE_INTEGER,
379
- toasts: [],
380
- gutter: "1rem",
381
- zIndex: import_dom_query2.MAX_Z_INDEX,
382
- pauseOnPageIdle: false,
383
- pauseOnInteraction: true,
384
- offsets: { left: "0px", right: "0px", top: "0px", bottom: "0px" },
385
- ...ctx
386
- },
387
- computed: {
388
- count: (ctx2) => ctx2.toasts.length
389
- },
390
- on: {
391
- PAUSE_TOAST: {
392
- actions: (_ctx, evt, { self }) => {
393
- self.sendChild("PAUSE", evt.id);
394
- }
569
+ return (0, import_core3.createMachine)(
570
+ {
571
+ id: "toaster",
572
+ initial: ctx.overlap ? "overlap" : "stack",
573
+ context: {
574
+ dir: "ltr",
575
+ max: Number.MAX_SAFE_INTEGER,
576
+ toasts: [],
577
+ gap: 16,
578
+ pauseOnPageIdle: false,
579
+ hotkey: ["altKey", "KeyT"],
580
+ offsets: "1rem",
581
+ placement: "bottom",
582
+ removeDelay: 200,
583
+ ...ctx,
584
+ lastFocusedEl: null,
585
+ isFocusWithin: false,
586
+ heights: []
395
587
  },
396
- PAUSE_ALL: {
397
- actions: (ctx2) => {
398
- ctx2.toasts.forEach((toast) => toast.send("PAUSE"));
399
- }
588
+ computed: {
589
+ count: (ctx2) => ctx2.toasts.length
400
590
  },
401
- RESUME_TOAST: {
402
- actions: (_ctx, evt, { self }) => {
403
- self.sendChild("RESUME", evt.id);
591
+ activities: ["trackDocumentVisibility", "trackHotKeyPress"],
592
+ watch: {
593
+ toasts: ["collapsedIfEmpty", "setDismissableBranch"]
594
+ },
595
+ exit: ["removeToasts", "clearDismissableBranch", "clearLastFocusedEl"],
596
+ on: {
597
+ PAUSE_TOAST: {
598
+ actions: ["pauseToast"]
599
+ },
600
+ PAUSE_ALL: {
601
+ actions: ["pauseToasts"]
602
+ },
603
+ RESUME_TOAST: {
604
+ actions: ["resumeToast"]
605
+ },
606
+ RESUME_ALL: {
607
+ actions: ["resumeToasts"]
608
+ },
609
+ ADD_TOAST: {
610
+ guard: "isWithinRange",
611
+ actions: ["createToast", "syncToastIndex"]
612
+ },
613
+ UPDATE_TOAST: {
614
+ actions: ["updateToast"]
615
+ },
616
+ DISMISS_TOAST: {
617
+ actions: ["dismissToast"]
618
+ },
619
+ DISMISS_ALL: {
620
+ actions: ["dismissToasts"]
621
+ },
622
+ REMOVE_TOAST: {
623
+ actions: ["removeToast", "syncToastIndex", "syncToastOffset"]
624
+ },
625
+ REMOVE_ALL: {
626
+ actions: ["removeToasts"]
627
+ },
628
+ UPDATE_HEIGHT: {
629
+ actions: ["syncHeights", "syncToastOffset"]
630
+ },
631
+ "DOC.HOTKEY": {
632
+ actions: ["focusRegionEl"]
633
+ },
634
+ "REGION.BLUR": [
635
+ {
636
+ guard: "isOverlapping",
637
+ target: "overlap",
638
+ actions: ["resumeToasts", "restoreLastFocusedEl"]
639
+ },
640
+ {
641
+ actions: ["resumeToasts", "restoreLastFocusedEl"]
642
+ }
643
+ ]
644
+ },
645
+ states: {
646
+ stack: {
647
+ entry: ["expandToasts"],
648
+ on: {
649
+ "REGION.POINTER_LEAVE": [
650
+ {
651
+ guard: "isOverlapping",
652
+ target: "overlap",
653
+ actions: ["resumeToasts"]
654
+ },
655
+ {
656
+ actions: ["resumeToasts"]
657
+ }
658
+ ],
659
+ "REGION.OVERLAP": {
660
+ target: "overlap"
661
+ },
662
+ "REGION.FOCUS": {
663
+ actions: ["setLastFocusedEl", "pauseToasts"]
664
+ },
665
+ "REGION.POINTER_ENTER": {
666
+ actions: ["pauseToasts"]
667
+ }
668
+ }
669
+ },
670
+ overlap: {
671
+ entry: ["collapseToasts"],
672
+ on: {
673
+ "REGION.STACK": {
674
+ target: "stack"
675
+ },
676
+ "REGION.POINTER_ENTER": {
677
+ target: "stack",
678
+ actions: ["pauseToasts"]
679
+ },
680
+ "REGION.FOCUS": {
681
+ target: "stack",
682
+ actions: ["setLastFocusedEl", "pauseToasts"]
683
+ }
684
+ }
404
685
  }
686
+ }
687
+ },
688
+ {
689
+ guards: {
690
+ isWithinRange: (ctx2) => ctx2.toasts.length < ctx2.max,
691
+ isOverlapping: (ctx2) => !!ctx2.overlap
405
692
  },
406
- RESUME_ALL: {
407
- actions: (ctx2) => {
408
- ctx2.toasts.forEach((toast) => toast.send("RESUME"));
693
+ activities: {
694
+ trackHotKeyPress(ctx2, _evt, { send }) {
695
+ const handleKeyDown = (event) => {
696
+ const isHotkeyPressed = ctx2.hotkey.every((key) => event[key] || event.code === key);
697
+ if (!isHotkeyPressed)
698
+ return;
699
+ send({ type: "DOC.HOTKEY" });
700
+ };
701
+ return (0, import_dom_event.addDomEvent)(document, "keydown", handleKeyDown, { capture: true });
702
+ },
703
+ trackDocumentVisibility(ctx2, _evt, { send }) {
704
+ if (!ctx2.pauseOnPageIdle)
705
+ return;
706
+ const doc = dom.getDoc(ctx2);
707
+ return (0, import_dom_event.addDomEvent)(doc, "visibilitychange", () => {
708
+ send(doc.visibilityState === "hidden" ? "PAUSE_ALL" : "RESUME_ALL");
709
+ });
409
710
  }
410
711
  },
411
- ADD_TOAST: {
412
- guard: (ctx2) => ctx2.toasts.length < ctx2.max,
413
- actions: (ctx2, evt, { self }) => {
712
+ actions: {
713
+ setDismissableBranch(ctx2) {
714
+ const toastsByPlacement = getToastsByPlacement(ctx2.toasts);
715
+ const currentToasts = toastsByPlacement[ctx2.placement] ?? [];
716
+ const hasToasts = currentToasts.length > 0;
717
+ if (!hasToasts) {
718
+ ctx2._cleanup?.();
719
+ return;
720
+ }
721
+ if (hasToasts && ctx2._cleanup) {
722
+ return;
723
+ }
724
+ const groupEl = () => dom.getRegionEl(ctx2, ctx2.placement);
725
+ ctx2._cleanup = (0, import_dismissable.trackDismissableBranch)(groupEl, { defer: true });
726
+ },
727
+ clearDismissableBranch(ctx2) {
728
+ ctx2._cleanup?.();
729
+ },
730
+ focusRegionEl(ctx2) {
731
+ queueMicrotask(() => {
732
+ dom.getRegionEl(ctx2, ctx2.placement)?.focus();
733
+ });
734
+ },
735
+ expandToasts(ctx2) {
736
+ each(ctx2, (toast) => {
737
+ toast.state.context.stacked = true;
738
+ });
739
+ },
740
+ collapseToasts(ctx2) {
741
+ each(ctx2, (toast) => {
742
+ toast.state.context.stacked = false;
743
+ });
744
+ },
745
+ collapsedIfEmpty(ctx2, _evt, { send }) {
746
+ if (!ctx2.overlap || ctx2.toasts.length > 1)
747
+ return;
748
+ send("REGION.OVERLAP");
749
+ },
750
+ pauseToast(_ctx, evt, { self }) {
751
+ self.sendChild("PAUSE", evt.id);
752
+ },
753
+ pauseToasts(ctx2) {
754
+ ctx2.toasts.forEach((toast) => toast.send("PAUSE"));
755
+ },
756
+ resumeToast(_ctx, evt, { self }) {
757
+ self.sendChild("RESUME", evt.id);
758
+ },
759
+ resumeToasts(ctx2) {
760
+ ctx2.toasts.forEach((toast) => toast.send("RESUME"));
761
+ },
762
+ measureToasts(ctx2) {
763
+ ctx2.toasts.forEach((toast) => toast.send("MEASURE"));
764
+ },
765
+ createToast(ctx2, evt, { self, getState }) {
414
766
  const options = {
415
767
  placement: ctx2.placement,
416
768
  duration: ctx2.duration,
417
769
  removeDelay: ctx2.removeDelay,
418
- render: ctx2.render,
419
770
  ...evt.toast,
420
- pauseOnPageIdle: ctx2.pauseOnPageIdle,
421
- pauseOnInteraction: ctx2.pauseOnInteraction,
422
771
  dir: ctx2.dir,
423
- getRootNode: ctx2.getRootNode
772
+ getRootNode: ctx2.getRootNode,
773
+ stacked: getState().matches("stack")
424
774
  };
425
775
  const toast = createToastMachine(options);
426
776
  const actor = self.spawn(toast);
427
- ctx2.toasts.push(actor);
428
- }
429
- },
430
- UPDATE_TOAST: {
431
- actions: (_ctx, evt, { self }) => {
777
+ ctx2.toasts = [actor, ...ctx2.toasts];
778
+ },
779
+ updateToast(_ctx, evt, { self }) {
432
780
  self.sendChild({ type: "UPDATE", toast: evt.toast }, evt.id);
433
- }
434
- },
435
- DISMISS_TOAST: {
436
- actions: (_ctx, evt, { self }) => {
781
+ },
782
+ dismissToast(_ctx, evt, { self }) {
437
783
  self.sendChild("DISMISS", evt.id);
438
- }
439
- },
440
- DISMISS_ALL: {
441
- actions: (ctx2) => {
784
+ },
785
+ dismissToasts(ctx2) {
442
786
  ctx2.toasts.forEach((toast) => toast.send("DISMISS"));
443
- }
444
- },
445
- REMOVE_TOAST: {
446
- actions: (ctx2, evt, { self }) => {
787
+ },
788
+ removeToast(ctx2, evt, { self }) {
447
789
  self.stopChild(evt.id);
448
- const index = ctx2.toasts.findIndex((toast) => toast.id === evt.id);
449
- ctx2.toasts.splice(index, 1);
450
- }
451
- },
452
- REMOVE_ALL: {
453
- actions: (ctx2, _evt, { self }) => {
790
+ ctx2.toasts = ctx2.toasts.filter((toast) => toast.id !== evt.id);
791
+ ctx2.heights = ctx2.heights.filter((height) => height.id !== evt.id);
792
+ },
793
+ removeToasts(ctx2, _evt, { self }) {
454
794
  ctx2.toasts.forEach((toast) => self.stopChild(toast.id));
455
- while (ctx2.toasts.length)
456
- ctx2.toasts.pop();
795
+ ctx2.toasts = [];
796
+ ctx2.heights = [];
797
+ },
798
+ syncHeights(ctx2, evt) {
799
+ const existing = ctx2.heights.find((height) => height.id === evt.id);
800
+ if (existing) {
801
+ existing.height = evt.height;
802
+ existing.placement = evt.placement;
803
+ } else {
804
+ const newHeight = { id: evt.id, height: evt.height, placement: evt.placement };
805
+ ctx2.heights = [newHeight, ...ctx2.heights];
806
+ }
807
+ },
808
+ syncToastIndex(ctx2) {
809
+ each(ctx2, (toast, index, toasts) => {
810
+ toast.state.context.index = index;
811
+ toast.state.context.frontmost = index === 0;
812
+ toast.state.context.zIndex = toasts.length - index;
813
+ });
814
+ },
815
+ syncToastOffset(ctx2, evt) {
816
+ const placement = evt.placement ?? ctx2.placement;
817
+ each({ ...ctx2, placement }, (toast) => {
818
+ const heightIndex = Math.max(
819
+ ctx2.heights.findIndex((height) => height.id === toast.id),
820
+ 0
821
+ );
822
+ const toastsHeightBefore = ctx2.heights.reduce((prev, curr, reducerIndex) => {
823
+ if (reducerIndex >= heightIndex)
824
+ return prev;
825
+ return prev + curr.height;
826
+ }, 0);
827
+ toast.state.context.offset = heightIndex * ctx2.gap + toastsHeightBefore;
828
+ });
829
+ },
830
+ setLastFocusedEl(ctx2, evt) {
831
+ if (ctx2.isFocusWithin || !evt.target)
832
+ return;
833
+ ctx2.isFocusWithin = true;
834
+ ctx2.lastFocusedEl = (0, import_core3.ref)(evt.target);
835
+ },
836
+ restoreLastFocusedEl(ctx2) {
837
+ ctx2.isFocusWithin = false;
838
+ if (!ctx2.lastFocusedEl)
839
+ return;
840
+ ctx2.lastFocusedEl.focus({ preventScroll: true });
841
+ ctx2.lastFocusedEl = null;
842
+ },
843
+ clearLastFocusedEl(ctx2) {
844
+ if (!ctx2.lastFocusedEl)
845
+ return;
846
+ ctx2.lastFocusedEl.focus({ preventScroll: true });
847
+ ctx2.lastFocusedEl = null;
848
+ ctx2.isFocusWithin = false;
457
849
  }
458
850
  }
459
851
  }
460
- });
852
+ );
853
+ }
854
+ function each(ctx, fn) {
855
+ const toastsByPlacement = getToastsByPlacement(ctx.toasts);
856
+ const currentToasts = toastsByPlacement[ctx.placement] ?? [];
857
+ currentToasts.forEach(fn);
461
858
  }
462
859
 
463
860
  // src/toast.connect.ts
861
+ var import_dom_query5 = require("@zag-js/dom-query");
464
862
  function connect(state, send, normalize) {
465
863
  const isVisible = state.hasTag("visible");
466
864
  const isPaused = state.hasTag("paused");
467
- const pauseOnInteraction = state.context.pauseOnInteraction;
468
865
  const placement = state.context.placement;
866
+ const type = state.context.type;
867
+ const [side, align = "center"] = placement.split("-");
469
868
  return {
470
- type: state.context.type,
869
+ type,
471
870
  title: state.context.title,
472
871
  description: state.context.description,
473
872
  placement,
@@ -488,45 +887,39 @@ function connect(state, send, normalize) {
488
887
  dir: state.context.dir,
489
888
  id: dom.getRootId(state.context),
490
889
  "data-state": isVisible ? "open" : "closed",
491
- "data-type": state.context.type,
890
+ "data-type": type,
492
891
  "data-placement": placement,
892
+ "data-align": align,
893
+ "data-side": side,
894
+ "data-mounted": (0, import_dom_query5.dataAttr)(state.context.mounted),
895
+ "data-paused": (0, import_dom_query5.dataAttr)(isPaused),
896
+ "data-first": (0, import_dom_query5.dataAttr)(state.context.frontmost),
897
+ "data-sibling": (0, import_dom_query5.dataAttr)(!state.context.frontmost),
898
+ "data-stack": (0, import_dom_query5.dataAttr)(state.context.stacked),
899
+ "data-overlap": (0, import_dom_query5.dataAttr)(!state.context.stacked),
493
900
  role: "status",
494
901
  "aria-atomic": "true",
495
902
  tabIndex: 0,
496
- style: {
497
- position: "relative",
498
- pointerEvents: "auto",
499
- margin: "calc(var(--toast-gutter) / 2)",
500
- "--remove-delay": `${state.context.removeDelay}ms`,
501
- "--duration": `${state.context.duration}ms`
502
- },
903
+ style: getPlacementStyle(state.context, isVisible),
503
904
  onKeyDown(event) {
504
905
  if (event.key == "Escape") {
505
906
  send("DISMISS");
506
907
  event.preventDefault();
507
908
  }
508
- },
509
- onFocus() {
510
- if (pauseOnInteraction) {
511
- send("PAUSE");
512
- }
513
- },
514
- onBlur() {
515
- if (pauseOnInteraction) {
516
- send("RESUME");
517
- }
518
- },
519
- onPointerEnter() {
520
- if (pauseOnInteraction) {
521
- send("PAUSE");
522
- }
523
- },
524
- onPointerLeave() {
525
- if (pauseOnInteraction) {
526
- send("RESUME");
527
- }
528
909
  }
529
910
  }),
911
+ /* Leave a ghost div to avoid setting hover to false when transitioning out */
912
+ ghostBeforeProps: normalize.element({
913
+ ...parts.ghost.attrs,
914
+ "data-type": "before",
915
+ style: getGhostBeforeStyle(state.context, isVisible)
916
+ }),
917
+ /* Needed to avoid setting hover to false when in between toasts */
918
+ ghostAfterProps: normalize.element({
919
+ ...parts.ghost.attrs,
920
+ "data-type": "after",
921
+ style: getGhostAfterStyle(state.context, isVisible)
922
+ }),
530
923
  titleProps: normalize.element({
531
924
  ...parts.title.attrs,
532
925
  id: dom.getTitleId(state.context)
@@ -535,6 +928,13 @@ function connect(state, send, normalize) {
535
928
  ...parts.description.attrs,
536
929
  id: dom.getDescriptionId(state.context)
537
930
  }),
931
+ actionTriggerProps: normalize.button({
932
+ ...parts.actionTrigger.attrs,
933
+ type: "button",
934
+ onClick() {
935
+ send("DISMISS");
936
+ }
937
+ }),
538
938
  closeTriggerProps: normalize.button({
539
939
  id: dom.getCloseTriggerId(state.context),
540
940
  ...parts.closeTrigger.attrs,