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