@zag-js/toast 0.82.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,292 @@ 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" });
232
+ if (refs.get("isFocusWithin") && !domQuery.contains(event.currentTarget, event.relatedTarget)) {
233
+ queueMicrotask(() => send({ type: "REGION.BLUR" }));
314
234
  }
315
235
  }
316
236
  });
317
237
  },
318
238
  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
- });
239
+ const store = prop("store");
240
+ return store.subscribe(() => fn(context.get("toasts")));
325
241
  }
326
242
  };
327
243
  }
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
244
+ var groupMachine = core.createMachine({
245
+ props({ props }) {
246
+ return {
247
+ dir: "ltr",
248
+ id: utils.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"]
351
299
  },
352
- 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: {
353
314
  on: {
354
- UPDATE: [
355
- {
356
- guard: and("hasTypeChanged", "isChangingToLoading"),
357
- target: "visible:persist",
358
- actions: ["setContext"]
359
- },
315
+ "REGION.POINTER_LEAVE": [
360
316
  {
361
- guard: or("hasDurationChanged", "hasTypeChanged"),
362
- target: "visible:updating",
363
- actions: ["setContext"]
317
+ guard: "isOverlapping",
318
+ target: "overlap",
319
+ actions: ["resumeToasts", "collapseToasts"]
364
320
  },
365
321
  {
366
- actions: ["setContext"]
322
+ actions: ["resumeToasts"]
367
323
  }
368
324
  ],
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
- }
325
+ "REGION.OVERLAP": {
326
+ target: "overlap",
327
+ actions: ["collapseToasts"]
381
328
  },
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
- }
329
+ "REGION.FOCUS": {
330
+ actions: ["setLastFocusedEl", "pauseToasts"]
392
331
  },
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
- }
332
+ "REGION.POINTER_ENTER": {
333
+ actions: ["pauseToasts"]
334
+ }
335
+ }
336
+ },
337
+ overlap: {
338
+ on: {
339
+ "REGION.STACK": {
340
+ target: "stack",
341
+ actions: ["expandToasts"]
405
342
  },
406
- dismissing: {
407
- entry: "invokeOnDismiss",
408
- after: {
409
- REMOVE_DELAY: {
410
- target: "unmounted",
411
- actions: "notifyParentToRemove"
412
- }
413
- }
343
+ "REGION.POINTER_ENTER": {
344
+ target: "stack",
345
+ actions: ["pauseToasts", "expandToasts"]
414
346
  },
415
- unmounted: {
416
- entry: "invokeOnUnmount",
417
- type: "final"
347
+ "REGION.FOCUS": {
348
+ target: "stack",
349
+ actions: ["setLastFocusedEl", "pauseToasts", "expandToasts"]
418
350
  }
419
351
  }
352
+ }
353
+ },
354
+ implementations: {
355
+ guards: {
356
+ isOverlapping: ({ computed }) => computed("overlap")
420
357
  },
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();
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];
447
371
  });
448
- 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 domQuery.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 domQuery.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;
449
404
  }
405
+ const groupEl = () => getRegionEl(scope, placement);
406
+ const cleanup = dismissable.trackDismissableBranch(groupEl, { defer: true });
407
+ refs.set("dismissableCleanup", cleanup);
450
408
  },
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
409
+ clearDismissableBranch({ refs }) {
410
+ refs.get("dismissableCleanup")?.();
456
411
  },
457
- delays: {
458
- VISIBLE_DURATION: (ctx2) => ctx2.remaining,
459
- REMOVE_DELAY: (ctx2) => ctx2.removeDelay
412
+ focusRegionEl({ scope, computed }) {
413
+ queueMicrotask(() => {
414
+ getRegionEl(scope, computed("placement"))?.focus();
415
+ });
460
416
  },
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: []
417
+ pauseToasts({ prop }) {
418
+ prop("store").pause();
529
419
  },
530
- computed: {
531
- count: (ctx2) => ctx2.toasts.length
420
+ resumeToasts({ prop }) {
421
+ prop("store").resume();
532
422
  },
533
- activities: ["trackDocumentVisibility", "trackHotKeyPress"],
534
- watch: {
535
- toasts: ["collapsedIfEmpty", "setDismissableBranch"]
423
+ expandToasts({ prop }) {
424
+ prop("store").expand();
536
425
  },
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
- ]
426
+ collapseToasts({ prop }) {
427
+ prop("store").collapse();
586
428
  },
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
429
+ removeToast({ prop, event }) {
430
+ prop("store").remove(event.id);
634
431
  },
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
- }
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
+ });
651
437
  },
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
- }
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);
784
458
  }
785
459
  }
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) {
460
+ }
461
+ });
462
+ function connect(service, normalize) {
463
+ const { state, send, prop, scope, context, computed } = service;
793
464
  const visible = state.hasTag("visible");
794
465
  const paused = state.hasTag("paused");
795
- const placement = state.context.placement;
796
- 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");
797
474
  const [side, align = "center"] = placement.split("-");
798
475
  return {
799
476
  type,
800
- title: state.context.title,
801
- description: state.context.description,
477
+ title,
478
+ description,
802
479
  placement,
803
480
  visible,
804
481
  paused,
482
+ closable: !!prop("closable"),
805
483
  pause() {
806
- send("PAUSE");
484
+ send({ type: "PAUSE" });
807
485
  },
808
486
  resume() {
809
- send("RESUME");
487
+ send({ type: "RESUME" });
810
488
  },
811
489
  dismiss() {
812
- send("DISMISS");
490
+ send({ type: "DISMISS", src: "programmatic" });
813
491
  },
814
492
  getRootProps() {
815
493
  return normalize.element({
816
494
  ...parts.root.attrs,
817
- dir: state.context.dir,
818
- id: dom.getRootId(state.context),
495
+ dir: prop("dir"),
496
+ id: getRootId(scope),
819
497
  "data-state": visible ? "open" : "closed",
820
498
  "data-type": type,
821
499
  "data-placement": placement,
822
500
  "data-align": align,
823
501
  "data-side": side,
824
- "data-mounted": domQuery.dataAttr(state.context.mounted),
502
+ "data-mounted": domQuery.dataAttr(mounted),
825
503
  "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),
504
+ "data-first": domQuery.dataAttr(frontmost),
505
+ "data-sibling": domQuery.dataAttr(!frontmost),
506
+ "data-stack": domQuery.dataAttr(stacked),
507
+ "data-overlap": domQuery.dataAttr(!stacked),
830
508
  role: "status",
831
509
  "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,
510
+ "aria-describedby": description ? getDescriptionId(scope) : void 0,
511
+ "aria-labelledby": title ? getTitleId(scope) : void 0,
834
512
  tabIndex: 0,
835
- style: getPlacementStyle(state.context, visible),
513
+ style: getPlacementStyle(service, visible),
836
514
  onKeyDown(event) {
837
515
  if (event.defaultPrevented) return;
838
516
  if (event.key == "Escape") {
839
- send("DISMISS");
517
+ send({ type: "DISMISS", src: "keyboard" });
840
518
  event.preventDefault();
841
519
  }
842
520
  }
@@ -846,7 +524,7 @@ function connect(state, send, normalize) {
846
524
  getGhostBeforeProps() {
847
525
  return normalize.element({
848
526
  "data-ghost": "before",
849
- style: getGhostBeforeStyle(state.context, visible)
527
+ style: getGhostBeforeStyle(service, visible)
850
528
  });
851
529
  },
852
530
  /* Needed to avoid setting hover to false when in between toasts */
@@ -859,13 +537,13 @@ function connect(state, send, normalize) {
859
537
  getTitleProps() {
860
538
  return normalize.element({
861
539
  ...parts.title.attrs,
862
- id: dom.getTitleId(state.context)
540
+ id: getTitleId(scope)
863
541
  });
864
542
  },
865
543
  getDescriptionProps() {
866
544
  return normalize.element({
867
545
  ...parts.description.attrs,
868
- id: dom.getDescriptionId(state.context)
546
+ id: getDescriptionId(scope)
869
547
  });
870
548
  },
871
549
  getActionTriggerProps() {
@@ -874,25 +552,479 @@ function connect(state, send, normalize) {
874
552
  type: "button",
875
553
  onClick(event) {
876
554
  if (event.defaultPrevented) return;
877
- state.context.action?.onClick?.();
878
- send("DISMISS");
555
+ action?.onClick?.();
556
+ send({ type: "DISMISS", src: "user" });
879
557
  }
880
558
  });
881
559
  },
882
560
  getCloseTriggerProps() {
883
561
  return normalize.button({
884
- id: dom.getCloseTriggerId(state.context),
562
+ id: getCloseTriggerId(scope),
885
563
  ...parts.closeTrigger.attrs,
886
564
  type: "button",
887
565
  "aria-label": "Dismiss notification",
888
566
  onClick(event) {
889
567
  if (event.defaultPrevented) return;
890
- send("DISMISS");
568
+ send({ type: "DISMISS", src: "user" });
569
+ }
570
+ });
571
+ }
572
+ };
573
+ }
574
+ var { not } = core.createGuards();
575
+ var machine = core.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 utils.setRafTimeout(() => {
719
+ send({ type: "REMOVE", src: "timer" });
720
+ }, prop("removeDelay"));
721
+ },
722
+ waitForDuration({ send, context, computed }) {
723
+ if (computed("shouldPersist")) return;
724
+ return utils.setRafTimeout(() => {
725
+ send({ type: "DISMISS", src: "timer" });
726
+ }, context.get("remainingTime"));
727
+ },
728
+ waitForNextTick({ send }) {
729
+ return utils.setRafTimeout(() => {
730
+ send({ type: "SHOW", src: "timer" });
731
+ }, 0);
732
+ },
733
+ trackHeight({ scope, prop }) {
734
+ let cleanup;
735
+ domQuery.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
+ domQuery.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:${utils.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 });
891
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 = utils.runIfFn(promise2).then(async (response) => {
928
+ result = ["resolve", response];
929
+ if (isHttpResponse(response) && !response.ok) {
930
+ removable = false;
931
+ const errorOptions = utils.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 = utils.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 = utils.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;
892
975
  });
976
+ } else {
977
+ toasts = toasts.map((toast) => publish({ ...toast, message: "RESUME" }));
893
978
  }
894
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
+ };
895
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
+ };
896
1028
 
897
1029
  // src/index.ts
898
1030
  var group = {
@@ -902,5 +1034,6 @@ var group = {
902
1034
 
903
1035
  exports.anatomy = anatomy;
904
1036
  exports.connect = connect;
905
- exports.createMachine = createToastMachine;
1037
+ exports.createStore = createToastStore;
906
1038
  exports.group = group;
1039
+ exports.machine = machine;