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