@zag-js/toast 0.82.1 → 1.0.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.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;
197
+ return context.get("toasts").length;
224
198
  },
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));
199
+ getToasts() {
200
+ return context.get("toasts");
229
201
  },
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
- }
242
- },
243
- dismissByPlacement(placement) {
244
- const toasts = getToastsByPlacementImpl(placement);
245
- toasts.forEach((toast) => dismiss(toast.id));
246
- },
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,294 @@ 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" });
312
- }
230
+ queueMicrotask(() => {
231
+ if (refs.get("isFocusWithin") && !contains(event.currentTarget, event.relatedTarget)) {
232
+ send({ type: "REGION.BLUR" });
233
+ }
234
+ });
313
235
  }
314
236
  });
315
237
  },
316
238
  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
- });
239
+ const store = prop("store");
240
+ return store.subscribe(() => fn(context.get("toasts")));
323
241
  }
324
242
  };
325
243
  }
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
244
+ var groupMachine = createMachine({
245
+ props({ props }) {
246
+ return {
247
+ dir: "ltr",
248
+ id: uuid(),
249
+ ...props,
250
+ store: props.store
251
+ };
252
+ },
253
+ initialState({ prop }) {
254
+ return prop("store").attrs.overlap ? "overlap" : "stack";
255
+ },
256
+ refs() {
257
+ return {
258
+ lastFocusedEl: null,
259
+ isFocusWithin: false,
260
+ dismissableCleanup: void 0
261
+ };
262
+ },
263
+ context({ bindable }) {
264
+ return {
265
+ toasts: bindable(() => ({
266
+ defaultValue: [],
267
+ sync: true,
268
+ hash: (toasts) => toasts.map((t) => t.id).join(",")
269
+ })),
270
+ heights: bindable(() => ({
271
+ defaultValue: [],
272
+ sync: true
273
+ }))
274
+ };
275
+ },
276
+ computed: {
277
+ count: ({ context }) => context.get("toasts").length,
278
+ overlap: ({ prop }) => prop("store").attrs.overlap,
279
+ placement: ({ prop }) => prop("store").attrs.placement
280
+ },
281
+ effects: ["subscribeToStore", "trackDocumentVisibility", "trackHotKeyPress"],
282
+ watch({ track, context, action }) {
283
+ track([() => context.hash("toasts")], () => {
284
+ queueMicrotask(() => {
285
+ action(["collapsedIfEmpty", "setDismissableBranch"]);
286
+ });
287
+ });
288
+ },
289
+ exit: ["clearDismissableBranch", "clearLastFocusedEl"],
290
+ on: {
291
+ "DOC.HOTKEY": {
292
+ actions: ["focusRegionEl"]
293
+ },
294
+ "REGION.BLUR": [
295
+ {
296
+ guard: "isOverlapping",
297
+ target: "overlap",
298
+ actions: ["collapseToasts", "resumeToasts", "restoreLastFocusedEl"]
349
299
  },
350
- initial: type === "loading" ? "visible:persist" : "visible",
300
+ {
301
+ target: "stack",
302
+ actions: ["resumeToasts", "restoreLastFocusedEl"]
303
+ }
304
+ ],
305
+ "TOAST.REMOVE": {
306
+ actions: ["removeToast", "removeHeight"]
307
+ },
308
+ "TOAST.PAUSE": {
309
+ actions: ["pauseToasts"]
310
+ }
311
+ },
312
+ states: {
313
+ stack: {
351
314
  on: {
352
- UPDATE: [
315
+ "REGION.POINTER_LEAVE": [
353
316
  {
354
- guard: and("hasTypeChanged", "isChangingToLoading"),
355
- target: "visible:persist",
356
- actions: ["setContext"]
357
- },
358
- {
359
- guard: or("hasDurationChanged", "hasTypeChanged"),
360
- target: "visible:updating",
361
- actions: ["setContext"]
317
+ guard: "isOverlapping",
318
+ target: "overlap",
319
+ actions: ["resumeToasts", "collapseToasts"]
362
320
  },
363
321
  {
364
- actions: ["setContext"]
322
+ actions: ["resumeToasts"]
365
323
  }
366
324
  ],
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
- }
325
+ "REGION.OVERLAP": {
326
+ target: "overlap",
327
+ actions: ["collapseToasts"]
379
328
  },
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
- }
329
+ "REGION.FOCUS": {
330
+ actions: ["setLastFocusedEl", "pauseToasts"]
390
331
  },
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
- }
332
+ "REGION.POINTER_ENTER": {
333
+ actions: ["pauseToasts"]
334
+ }
335
+ }
336
+ },
337
+ overlap: {
338
+ on: {
339
+ "REGION.STACK": {
340
+ target: "stack",
341
+ actions: ["expandToasts"]
403
342
  },
404
- dismissing: {
405
- entry: "invokeOnDismiss",
406
- after: {
407
- REMOVE_DELAY: {
408
- target: "unmounted",
409
- actions: "notifyParentToRemove"
410
- }
411
- }
343
+ "REGION.POINTER_ENTER": {
344
+ target: "stack",
345
+ actions: ["pauseToasts", "expandToasts"]
412
346
  },
413
- unmounted: {
414
- entry: "invokeOnUnmount",
415
- type: "final"
347
+ "REGION.FOCUS": {
348
+ target: "stack",
349
+ actions: ["setLastFocusedEl", "pauseToasts", "expandToasts"]
416
350
  }
417
351
  }
352
+ }
353
+ },
354
+ implementations: {
355
+ guards: {
356
+ isOverlapping: ({ computed }) => computed("overlap")
418
357
  },
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();
358
+ effects: {
359
+ subscribeToStore({ context, prop }) {
360
+ return prop("store").subscribe((toast) => {
361
+ if (toast.dismiss) {
362
+ context.set("toasts", (prev) => prev.filter((t) => t.id !== toast.id));
363
+ return;
364
+ }
365
+ context.set("toasts", (prev) => {
366
+ const index = prev.findIndex((t) => t.id === toast.id);
367
+ if (index !== -1) {
368
+ return [...prev.slice(0, index), { ...prev[index], ...toast }, ...prev.slice(index + 1)];
369
+ }
370
+ return [toast, ...prev];
445
371
  });
446
- return () => cleanup?.();
372
+ });
373
+ },
374
+ trackHotKeyPress({ prop, send }) {
375
+ const handleKeyDown = (event) => {
376
+ const { hotkey } = prop("store").attrs;
377
+ const isHotkeyPressed = hotkey.every((key) => event[key] || event.code === key);
378
+ if (!isHotkeyPressed) return;
379
+ send({ type: "DOC.HOTKEY" });
380
+ };
381
+ return addDomEvent(document, "keydown", handleKeyDown, { capture: true });
382
+ },
383
+ trackDocumentVisibility({ prop, send, scope }) {
384
+ const { pauseOnPageIdle } = prop("store").attrs;
385
+ if (!pauseOnPageIdle) return;
386
+ const doc = scope.getDoc();
387
+ return addDomEvent(doc, "visibilitychange", () => {
388
+ const isHidden = doc.visibilityState === "hidden";
389
+ send({ type: isHidden ? "PAUSE_ALL" : "RESUME_ALL" });
390
+ });
391
+ }
392
+ },
393
+ actions: {
394
+ setDismissableBranch({ refs, context, computed, scope }) {
395
+ const toasts = context.get("toasts");
396
+ const placement = computed("placement");
397
+ const hasToasts = toasts.length > 0;
398
+ if (!hasToasts) {
399
+ refs.get("dismissableCleanup")?.();
400
+ return;
401
+ }
402
+ if (hasToasts && refs.get("dismissableCleanup")) {
403
+ return;
447
404
  }
405
+ const groupEl = () => getRegionEl(scope, placement);
406
+ const cleanup = trackDismissableBranch(groupEl, { defer: true });
407
+ refs.set("dismissableCleanup", cleanup);
448
408
  },
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
409
+ clearDismissableBranch({ refs }) {
410
+ refs.get("dismissableCleanup")?.();
454
411
  },
455
- delays: {
456
- VISIBLE_DURATION: (ctx2) => ctx2.remaining,
457
- REMOVE_DELAY: (ctx2) => ctx2.removeDelay
412
+ focusRegionEl({ scope, computed }) {
413
+ queueMicrotask(() => {
414
+ getRegionEl(scope, computed("placement"))?.focus();
415
+ });
458
416
  },
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: []
417
+ pauseToasts({ prop }) {
418
+ prop("store").pause();
527
419
  },
528
- computed: {
529
- count: (ctx2) => ctx2.toasts.length
420
+ resumeToasts({ prop }) {
421
+ prop("store").resume();
530
422
  },
531
- activities: ["trackDocumentVisibility", "trackHotKeyPress"],
532
- watch: {
533
- toasts: ["collapsedIfEmpty", "setDismissableBranch"]
423
+ expandToasts({ prop }) {
424
+ prop("store").expand();
534
425
  },
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
- ]
426
+ collapseToasts({ prop }) {
427
+ prop("store").collapse();
584
428
  },
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
429
+ removeToast({ prop, event }) {
430
+ prop("store").remove(event.id);
632
431
  },
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
- }
432
+ removeHeight({ event, context }) {
433
+ if (event?.id == null) return;
434
+ queueMicrotask(() => {
435
+ context.set("heights", (heights) => heights.filter((height) => height.id !== event.id));
436
+ });
649
437
  },
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
- }
438
+ collapsedIfEmpty({ send, computed }) {
439
+ if (!computed("overlap") || computed("count") > 1) return;
440
+ send({ type: "REGION.OVERLAP" });
441
+ },
442
+ setLastFocusedEl({ refs, event }) {
443
+ if (refs.get("isFocusWithin") || !event.target) return;
444
+ refs.set("isFocusWithin", true);
445
+ refs.set("lastFocusedEl", event.target);
446
+ },
447
+ restoreLastFocusedEl({ refs }) {
448
+ if (!refs.get("lastFocusedEl")) return;
449
+ refs.get("lastFocusedEl")?.focus({ preventScroll: true });
450
+ refs.set("lastFocusedEl", null);
451
+ refs.set("isFocusWithin", false);
452
+ },
453
+ clearLastFocusedEl({ refs }) {
454
+ if (!refs.get("lastFocusedEl")) return;
455
+ refs.get("lastFocusedEl")?.focus({ preventScroll: true });
456
+ refs.set("lastFocusedEl", null);
457
+ refs.set("isFocusWithin", false);
782
458
  }
783
459
  }
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) {
460
+ }
461
+ });
462
+ function connect(service, normalize) {
463
+ const { state, send, prop, scope, context, computed } = service;
791
464
  const visible = state.hasTag("visible");
792
465
  const paused = state.hasTag("paused");
793
- const placement = state.context.placement;
794
- const type = state.context.type;
466
+ const mounted = context.get("mounted");
467
+ const frontmost = computed("frontmost");
468
+ const placement = prop("parent").computed("placement");
469
+ const type = prop("type");
470
+ const stacked = prop("stacked");
471
+ const title = prop("title");
472
+ const description = prop("description");
473
+ const action = prop("action");
795
474
  const [side, align = "center"] = placement.split("-");
796
475
  return {
797
476
  type,
798
- title: state.context.title,
799
- description: state.context.description,
477
+ title,
478
+ description,
800
479
  placement,
801
480
  visible,
802
481
  paused,
482
+ closable: !!prop("closable"),
803
483
  pause() {
804
- send("PAUSE");
484
+ send({ type: "PAUSE" });
805
485
  },
806
486
  resume() {
807
- send("RESUME");
487
+ send({ type: "RESUME" });
808
488
  },
809
489
  dismiss() {
810
- send("DISMISS");
490
+ send({ type: "DISMISS", src: "programmatic" });
811
491
  },
812
492
  getRootProps() {
813
493
  return normalize.element({
814
494
  ...parts.root.attrs,
815
- dir: state.context.dir,
816
- id: dom.getRootId(state.context),
495
+ dir: prop("dir"),
496
+ id: getRootId(scope),
817
497
  "data-state": visible ? "open" : "closed",
818
498
  "data-type": type,
819
499
  "data-placement": placement,
820
500
  "data-align": align,
821
501
  "data-side": side,
822
- "data-mounted": dataAttr(state.context.mounted),
502
+ "data-mounted": dataAttr(mounted),
823
503
  "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),
504
+ "data-first": dataAttr(frontmost),
505
+ "data-sibling": dataAttr(!frontmost),
506
+ "data-stack": dataAttr(stacked),
507
+ "data-overlap": dataAttr(!stacked),
828
508
  role: "status",
829
509
  "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,
510
+ "aria-describedby": description ? getDescriptionId(scope) : void 0,
511
+ "aria-labelledby": title ? getTitleId(scope) : void 0,
832
512
  tabIndex: 0,
833
- style: getPlacementStyle(state.context, visible),
513
+ style: getPlacementStyle(service, visible),
834
514
  onKeyDown(event) {
835
515
  if (event.defaultPrevented) return;
836
516
  if (event.key == "Escape") {
837
- send("DISMISS");
517
+ send({ type: "DISMISS", src: "keyboard" });
838
518
  event.preventDefault();
839
519
  }
840
520
  }
@@ -844,7 +524,7 @@ function connect(state, send, normalize) {
844
524
  getGhostBeforeProps() {
845
525
  return normalize.element({
846
526
  "data-ghost": "before",
847
- style: getGhostBeforeStyle(state.context, visible)
527
+ style: getGhostBeforeStyle(service, visible)
848
528
  });
849
529
  },
850
530
  /* Needed to avoid setting hover to false when in between toasts */
@@ -857,13 +537,13 @@ function connect(state, send, normalize) {
857
537
  getTitleProps() {
858
538
  return normalize.element({
859
539
  ...parts.title.attrs,
860
- id: dom.getTitleId(state.context)
540
+ id: getTitleId(scope)
861
541
  });
862
542
  },
863
543
  getDescriptionProps() {
864
544
  return normalize.element({
865
545
  ...parts.description.attrs,
866
- id: dom.getDescriptionId(state.context)
546
+ id: getDescriptionId(scope)
867
547
  });
868
548
  },
869
549
  getActionTriggerProps() {
@@ -872,25 +552,479 @@ function connect(state, send, normalize) {
872
552
  type: "button",
873
553
  onClick(event) {
874
554
  if (event.defaultPrevented) return;
875
- state.context.action?.onClick?.();
876
- send("DISMISS");
555
+ action?.onClick?.();
556
+ send({ type: "DISMISS", src: "user" });
877
557
  }
878
558
  });
879
559
  },
880
560
  getCloseTriggerProps() {
881
561
  return normalize.button({
882
- id: dom.getCloseTriggerId(state.context),
562
+ id: getCloseTriggerId(scope),
883
563
  ...parts.closeTrigger.attrs,
884
564
  type: "button",
885
565
  "aria-label": "Dismiss notification",
886
566
  onClick(event) {
887
567
  if (event.defaultPrevented) return;
888
- send("DISMISS");
568
+ send({ type: "DISMISS", src: "user" });
569
+ }
570
+ });
571
+ }
572
+ };
573
+ }
574
+ var { not } = createGuards();
575
+ var machine = createMachine({
576
+ props({ props }) {
577
+ return {
578
+ ...props,
579
+ id: props.id,
580
+ type: props.type,
581
+ parent: props.parent,
582
+ closable: props.closable ?? true,
583
+ duration: getToastDuration(props.duration, props.type)
584
+ };
585
+ },
586
+ initialState({ prop }) {
587
+ const persist = prop("type") === "loading" || prop("duration") === Infinity;
588
+ return persist ? "visible:persist" : "visible";
589
+ },
590
+ context({ prop, bindable }) {
591
+ return {
592
+ remainingTime: bindable(() => ({
593
+ defaultValue: getToastDuration(prop("duration"), prop("type"))
594
+ })),
595
+ createdAt: bindable(() => ({
596
+ defaultValue: Date.now()
597
+ })),
598
+ mounted: bindable(() => ({
599
+ defaultValue: false
600
+ })),
601
+ initialHeight: bindable(() => ({
602
+ defaultValue: 0
603
+ }))
604
+ };
605
+ },
606
+ refs() {
607
+ return {
608
+ closeTimerStartTime: Date.now(),
609
+ lastCloseStartTimerStartTime: 0
610
+ };
611
+ },
612
+ computed: {
613
+ zIndex: ({ prop }) => {
614
+ const toasts = prop("parent").context.get("toasts");
615
+ const index = toasts.findIndex((toast) => toast.id === prop("id"));
616
+ return toasts.length - index;
617
+ },
618
+ height: ({ prop }) => {
619
+ const heights = prop("parent").context.get("heights");
620
+ const height = heights.find((height2) => height2.id === prop("id"));
621
+ return height?.height ?? 0;
622
+ },
623
+ heightIndex: ({ prop }) => {
624
+ const heights = prop("parent").context.get("heights");
625
+ return heights.findIndex((height) => height.id === prop("id"));
626
+ },
627
+ frontmost: ({ prop }) => prop("index") === 0,
628
+ heightBefore: ({ prop }) => {
629
+ const heights = prop("parent").context.get("heights");
630
+ const heightIndex = heights.findIndex((height) => height.id === prop("id"));
631
+ return heights.reduce((prev, curr, reducerIndex) => {
632
+ if (reducerIndex >= heightIndex) return prev;
633
+ return prev + curr.height;
634
+ }, 0);
635
+ },
636
+ shouldPersist: ({ prop }) => prop("type") === "loading" || prop("duration") === Infinity
637
+ },
638
+ watch({ track, prop, send }) {
639
+ track([() => prop("message")], () => {
640
+ const message = prop("message");
641
+ if (message) send({ type: message, src: "programmatic" });
642
+ });
643
+ track([() => prop("type"), () => prop("duration")], () => {
644
+ send({ type: "UPDATE" });
645
+ });
646
+ },
647
+ on: {
648
+ UPDATE: [
649
+ {
650
+ guard: "shouldPersist",
651
+ target: "visible:persist",
652
+ actions: ["resetCloseTimer"]
653
+ },
654
+ {
655
+ target: "visible:updating",
656
+ actions: ["resetCloseTimer"]
657
+ }
658
+ ],
659
+ MEASURE: {
660
+ actions: ["measureHeight"]
661
+ }
662
+ },
663
+ entry: ["setMounted", "measureHeight", "invokeOnVisible"],
664
+ effects: ["trackHeight"],
665
+ states: {
666
+ "visible:updating": {
667
+ tags: ["visible", "updating"],
668
+ effects: ["waitForNextTick"],
669
+ on: {
670
+ SHOW: {
671
+ target: "visible"
672
+ }
673
+ }
674
+ },
675
+ "visible:persist": {
676
+ tags: ["visible", "paused"],
677
+ on: {
678
+ RESUME: {
679
+ guard: not("isLoadingType"),
680
+ target: "visible",
681
+ actions: ["setCloseTimer"]
682
+ },
683
+ DISMISS: {
684
+ target: "dismissing"
685
+ }
686
+ }
687
+ },
688
+ visible: {
689
+ tags: ["visible"],
690
+ effects: ["waitForDuration"],
691
+ on: {
692
+ DISMISS: {
693
+ target: "dismissing"
694
+ },
695
+ PAUSE: {
696
+ target: "visible:persist",
697
+ actions: ["syncRemainingTime"]
698
+ }
699
+ }
700
+ },
701
+ dismissing: {
702
+ entry: ["invokeOnDismiss"],
703
+ effects: ["waitForRemoveDelay"],
704
+ on: {
705
+ REMOVE: {
706
+ target: "unmounted",
707
+ actions: ["notifyParentToRemove"]
708
+ }
709
+ }
710
+ },
711
+ unmounted: {
712
+ entry: ["invokeOnUnmount"]
713
+ }
714
+ },
715
+ implementations: {
716
+ effects: {
717
+ waitForRemoveDelay({ prop, send }) {
718
+ return setRafTimeout(() => {
719
+ send({ type: "REMOVE", src: "timer" });
720
+ }, prop("removeDelay"));
721
+ },
722
+ waitForDuration({ send, context, computed }) {
723
+ if (computed("shouldPersist")) return;
724
+ return setRafTimeout(() => {
725
+ send({ type: "DISMISS", src: "timer" });
726
+ }, context.get("remainingTime"));
727
+ },
728
+ waitForNextTick({ send }) {
729
+ return setRafTimeout(() => {
730
+ send({ type: "SHOW", src: "timer" });
731
+ }, 0);
732
+ },
733
+ trackHeight({ scope, prop }) {
734
+ let cleanup;
735
+ raf(() => {
736
+ const rootEl = getRootEl(scope);
737
+ if (!rootEl) return;
738
+ const syncHeight = () => {
739
+ const originalHeight = rootEl.style.height;
740
+ rootEl.style.height = "auto";
741
+ const height = rootEl.getBoundingClientRect().height;
742
+ rootEl.style.height = originalHeight;
743
+ const item = { id: prop("id"), height };
744
+ setHeight(prop("parent"), item);
745
+ };
746
+ const win = scope.getWin();
747
+ const observer = new win.MutationObserver(syncHeight);
748
+ observer.observe(rootEl, {
749
+ childList: true,
750
+ subtree: true,
751
+ characterData: true
752
+ });
753
+ cleanup = () => observer.disconnect();
754
+ });
755
+ return () => cleanup?.();
756
+ }
757
+ },
758
+ guards: {
759
+ isLoadingType: ({ prop }) => prop("type") === "loading",
760
+ shouldPersist: ({ computed }) => computed("shouldPersist")
761
+ },
762
+ actions: {
763
+ setMounted({ context }) {
764
+ raf(() => {
765
+ context.set("mounted", true);
766
+ });
767
+ },
768
+ measureHeight({ scope, prop, context }) {
769
+ queueMicrotask(() => {
770
+ const rootEl = getRootEl(scope);
771
+ if (!rootEl) return;
772
+ const originalHeight = rootEl.style.height;
773
+ rootEl.style.height = "auto";
774
+ const height = rootEl.getBoundingClientRect().height;
775
+ rootEl.style.height = originalHeight;
776
+ context.set("initialHeight", height);
777
+ const item = { id: prop("id"), height };
778
+ setHeight(prop("parent"), item);
779
+ });
780
+ },
781
+ setCloseTimer({ refs }) {
782
+ refs.set("closeTimerStartTime", Date.now());
783
+ },
784
+ resetCloseTimer({ context, refs, prop }) {
785
+ refs.set("closeTimerStartTime", Date.now());
786
+ context.set("remainingTime", getToastDuration(prop("duration"), prop("type")));
787
+ },
788
+ syncRemainingTime({ context, refs }) {
789
+ context.set("remainingTime", (prev) => {
790
+ const closeTimerStartTime = refs.get("closeTimerStartTime");
791
+ const elapsedTime = Date.now() - closeTimerStartTime;
792
+ refs.set("lastCloseStartTimerStartTime", Date.now());
793
+ return prev - elapsedTime;
794
+ });
795
+ },
796
+ notifyParentToRemove({ prop }) {
797
+ const parent = prop("parent");
798
+ parent.send({ type: "TOAST.REMOVE", id: prop("id") });
799
+ },
800
+ invokeOnDismiss({ prop, event }) {
801
+ prop("onStatusChange")?.({ status: "dismissing", src: event.src });
802
+ },
803
+ invokeOnUnmount({ prop }) {
804
+ prop("onStatusChange")?.({ status: "unmounted" });
805
+ },
806
+ invokeOnVisible({ prop }) {
807
+ prop("onStatusChange")?.({ status: "visible" });
808
+ }
809
+ }
810
+ }
811
+ });
812
+ function setHeight(parent, item) {
813
+ const { id, height } = item;
814
+ parent.context.set("heights", (prev) => {
815
+ const alreadyExists = prev.find((i) => i.id === id);
816
+ if (!alreadyExists) {
817
+ return [{ id, height }, ...prev];
818
+ } else {
819
+ return prev.map((i) => i.id === id ? { ...i, height } : i);
820
+ }
821
+ });
822
+ }
823
+ var withDefaults = (options, defaults) => {
824
+ return { ...defaults, ...options };
825
+ };
826
+ function createToastStore(props) {
827
+ const attrs = withDefaults(props, {
828
+ placement: "bottom",
829
+ overlap: false,
830
+ max: 24,
831
+ gap: 16,
832
+ offsets: "1rem",
833
+ hotkey: ["altKey", "KeyT"],
834
+ removeDelay: 200,
835
+ pauseOnPageIdle: true
836
+ });
837
+ let subscribers = [];
838
+ let toasts = [];
839
+ let dismissedToasts = /* @__PURE__ */ new Set();
840
+ const subscribe = (subscriber) => {
841
+ subscribers.push(subscriber);
842
+ return () => {
843
+ const index = subscribers.indexOf(subscriber);
844
+ subscribers.splice(index, 1);
845
+ };
846
+ };
847
+ const publish = (data) => {
848
+ subscribers.forEach((subscriber) => subscriber(data));
849
+ return data;
850
+ };
851
+ const addToast = (data) => {
852
+ if (toasts.length >= attrs.max) return;
853
+ publish(data);
854
+ toasts.unshift(data);
855
+ };
856
+ const create = (data) => {
857
+ const id = data.id ?? `toast:${uuid()}`;
858
+ const exists = toasts.find((toast) => toast.id === id);
859
+ if (dismissedToasts.has(id)) dismissedToasts.delete(id);
860
+ if (exists) {
861
+ toasts = toasts.map((toast) => {
862
+ if (toast.id === id) {
863
+ return publish({ ...toast, ...data, id });
889
864
  }
865
+ return toast;
866
+ });
867
+ } else {
868
+ addToast({
869
+ id,
870
+ duration: attrs.duration,
871
+ removeDelay: attrs.removeDelay,
872
+ type: "info",
873
+ ...data,
874
+ stacked: !attrs.overlap,
875
+ gap: attrs.gap
876
+ });
877
+ }
878
+ return id;
879
+ };
880
+ const remove = (id) => {
881
+ dismissedToasts.add(id);
882
+ if (!id) {
883
+ toasts.forEach((toast) => {
884
+ subscribers.forEach((subscriber) => subscriber({ id: toast.id, dismiss: true }));
885
+ });
886
+ toasts = [];
887
+ } else {
888
+ subscribers.forEach((subscriber) => subscriber({ id, dismiss: true }));
889
+ toasts = toasts.filter((toast) => toast.id !== id);
890
+ }
891
+ return id;
892
+ };
893
+ const error = (data) => {
894
+ return create({ ...data, type: "error" });
895
+ };
896
+ const success = (data) => {
897
+ return create({ ...data, type: "success" });
898
+ };
899
+ const info = (data) => {
900
+ return create({ ...data, type: "info" });
901
+ };
902
+ const warning = (data) => {
903
+ return create({ ...data, type: "warning" });
904
+ };
905
+ const loading = (data) => {
906
+ return create({ ...data, type: "loading" });
907
+ };
908
+ const getVisibleToasts = () => {
909
+ return toasts.filter((toast) => !dismissedToasts.has(toast.id));
910
+ };
911
+ const getCount = () => {
912
+ return toasts.length;
913
+ };
914
+ const promise = (promise2, options, shared = {}) => {
915
+ if (!options) return;
916
+ let id = void 0;
917
+ if (options.loading !== void 0) {
918
+ id = create({
919
+ ...shared,
920
+ ...options.loading,
921
+ promise: promise2,
922
+ type: "loading"
923
+ });
924
+ }
925
+ let removable = id !== void 0;
926
+ let result;
927
+ const prom = runIfFn(promise2).then(async (response) => {
928
+ result = ["resolve", response];
929
+ if (isHttpResponse(response) && !response.ok) {
930
+ removable = false;
931
+ const errorOptions = runIfFn(options.error, `HTTP Error! status: ${response.status}`);
932
+ create({ ...shared, ...errorOptions, id, type: "error" });
933
+ } else if (options.success !== void 0) {
934
+ removable = false;
935
+ const successOptions = runIfFn(options.success, response);
936
+ create({ ...shared, ...successOptions, id, type: "success" });
937
+ }
938
+ }).catch(async (error2) => {
939
+ result = ["reject", error2];
940
+ if (options.error !== void 0) {
941
+ removable = false;
942
+ const errorOptions = runIfFn(options.error, error2);
943
+ create({ ...shared, ...errorOptions, id, type: "error" });
944
+ }
945
+ }).finally(() => {
946
+ if (removable) {
947
+ remove(id);
948
+ id = void 0;
949
+ }
950
+ options.finally?.();
951
+ });
952
+ const unwrap = () => new Promise(
953
+ (resolve, reject) => prom.then(() => result[0] === "reject" ? reject(result[1]) : resolve(result[1])).catch(reject)
954
+ );
955
+ return { id, unwrap };
956
+ };
957
+ const update = (id, data) => {
958
+ return create({ id, ...data });
959
+ };
960
+ const pause = (id) => {
961
+ if (id != null) {
962
+ toasts = toasts.map((toast) => {
963
+ if (toast.id === id) return publish({ ...toast, message: "PAUSE" });
964
+ return toast;
965
+ });
966
+ } else {
967
+ toasts = toasts.map((toast) => publish({ ...toast, message: "PAUSE" }));
968
+ }
969
+ };
970
+ const resume = (id) => {
971
+ if (id != null) {
972
+ toasts = toasts.map((toast) => {
973
+ if (toast.id === id) return publish({ ...toast, message: "RESUME" });
974
+ return toast;
890
975
  });
976
+ } else {
977
+ toasts = toasts.map((toast) => publish({ ...toast, message: "RESUME" }));
891
978
  }
892
979
  };
980
+ const dismiss = (id) => {
981
+ if (id != null) {
982
+ toasts = toasts.map((toast) => {
983
+ if (toast.id === id) return publish({ ...toast, message: "DISMISS" });
984
+ return toast;
985
+ });
986
+ } else {
987
+ toasts = toasts.map((toast) => publish({ ...toast, message: "DISMISS" }));
988
+ }
989
+ };
990
+ const isVisible = (id) => {
991
+ return !dismissedToasts.has(id) && !!toasts.find((toast) => toast.id === id);
992
+ };
993
+ const isDismissed = (id) => {
994
+ return dismissedToasts.has(id);
995
+ };
996
+ const expand = () => {
997
+ toasts = toasts.map((toast) => publish({ ...toast, stacked: true }));
998
+ };
999
+ const collapse = () => {
1000
+ toasts = toasts.map((toast) => publish({ ...toast, stacked: false }));
1001
+ };
1002
+ return {
1003
+ attrs,
1004
+ subscribe,
1005
+ create,
1006
+ update,
1007
+ remove,
1008
+ dismiss,
1009
+ error,
1010
+ success,
1011
+ info,
1012
+ warning,
1013
+ loading,
1014
+ getVisibleToasts,
1015
+ getCount,
1016
+ promise,
1017
+ pause,
1018
+ resume,
1019
+ isVisible,
1020
+ isDismissed,
1021
+ expand,
1022
+ collapse
1023
+ };
893
1024
  }
1025
+ var isHttpResponse = (data) => {
1026
+ return data && typeof data === "object" && "ok" in data && typeof data.ok === "boolean" && "status" in data && typeof data.status === "number";
1027
+ };
894
1028
 
895
1029
  // src/index.ts
896
1030
  var group = {
@@ -898,4 +1032,4 @@ var group = {
898
1032
  machine: groupMachine
899
1033
  };
900
1034
 
901
- export { anatomy, connect, createToastMachine as createMachine, group };
1035
+ export { anatomy, connect, createToastStore as createStore, group, machine };