@zag-js/toast 0.82.2 → 1.0.1

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