@zag-js/toast 0.23.0 → 0.25.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.
@@ -3,16 +3,7 @@ import type { NormalizeProps, PropTypes } from "@zag-js/types"
3
3
  import { runIfFn, uuid } from "@zag-js/utils"
4
4
  import { parts } from "./toast.anatomy"
5
5
  import { dom } from "./toast.dom"
6
- import type {
7
- GroupMachineApi,
8
- GroupMachineContext,
9
- GroupProps,
10
- GroupSend,
11
- GroupState,
12
- Options,
13
- Placement,
14
- PromiseOptions,
15
- } from "./toast.types"
6
+ import type { GroupMachineApi, GroupSend, GroupState, Options } from "./toast.types"
16
7
  import { getGroupPlacementStyle, getToastsByPlacement } from "./toast.utils"
17
8
 
18
9
  export let toaster = {} as GroupMachineApi
@@ -23,112 +14,115 @@ export function groupConnect<T extends PropTypes>(
23
14
  normalize: NormalizeProps<T>,
24
15
  ): GroupMachineApi<T> {
25
16
  //
26
- const group = {
27
- count: state.context.count,
28
- toasts: state.context.toasts,
29
- toastsByPlacement: getToastsByPlacement(state.context.toasts),
30
- isVisible(id: string) {
31
- if (!state.context.toasts.length) return false
32
- return !!state.context.toasts.find((toast) => toast.id == id)
33
- },
17
+ const toastsByPlacement = getToastsByPlacement(state.context.toasts)
34
18
 
35
- create(options: Options) {
36
- const uid = `toast:${uuid()}`
37
- const id = options.id ? options.id : uid
19
+ function isVisible(id: string) {
20
+ if (!state.context.toasts.length) return false
21
+ return !!state.context.toasts.find((toast) => toast.id == id)
22
+ }
38
23
 
39
- if (group.isVisible(id)) return
40
- send({ type: "ADD_TOAST", toast: { ...options, id } })
24
+ function create(options: Options) {
25
+ const uid = `toast:${uuid()}`
26
+ const id = options.id ? options.id : uid
41
27
 
42
- return id
43
- },
28
+ if (isVisible(id)) return
29
+ send({ type: "ADD_TOAST", toast: { ...options, id } })
44
30
 
45
- upsert(options: Options) {
46
- const { id } = options
47
- const isVisible = id ? group.isVisible(id) : false
48
- if (isVisible && id != null) {
49
- return group.update(id, options)
50
- } else {
51
- return group.create(options)
52
- }
53
- },
31
+ return id
32
+ }
54
33
 
55
- dismiss(id?: string) {
56
- if (id == null) {
57
- send("DISMISS_ALL")
58
- } else if (group.isVisible(id)) {
59
- send({ type: "DISMISS_TOAST", id })
60
- }
61
- },
34
+ function update(id: string, options: Options) {
35
+ if (!isVisible(id)) return
36
+ send({ type: "UPDATE_TOAST", id, toast: options })
37
+ return id
38
+ }
39
+
40
+ function upsert(options: Options) {
41
+ const { id } = options
42
+ const visible = id ? isVisible(id) : false
43
+ if (visible && id != null) {
44
+ return update(id, options)
45
+ } else {
46
+ return create(options)
47
+ }
48
+ }
62
49
 
63
- remove(id?: string) {
50
+ function dismiss(id?: string) {
51
+ if (id == null) {
52
+ send("DISMISS_ALL")
53
+ } else if (isVisible(id)) {
54
+ send({ type: "DISMISS_TOAST", id })
55
+ }
56
+ }
57
+
58
+ return {
59
+ count: state.context.count,
60
+ toasts: state.context.toasts,
61
+ toastsByPlacement,
62
+ isVisible,
63
+
64
+ create,
65
+ update,
66
+ upsert,
67
+ dismiss,
68
+
69
+ remove(id) {
64
70
  if (id == null) {
65
71
  send("REMOVE_ALL")
66
- } else if (group.isVisible(id)) {
72
+ } else if (isVisible(id)) {
67
73
  send({ type: "REMOVE_TOAST", id })
68
74
  }
69
75
  },
70
76
 
71
- dismissByPlacement(placement: Placement) {
72
- const toasts = group.toastsByPlacement[placement]
77
+ dismissByPlacement(placement) {
78
+ const toasts = toastsByPlacement[placement]
73
79
  if (toasts) {
74
- toasts.forEach((toast) => group.dismiss(toast.id))
80
+ toasts.forEach((toast) => dismiss(toast.id))
75
81
  }
76
82
  },
77
-
78
- update(id: string, options: Options) {
79
- if (!group.isVisible(id)) return
80
- send({ type: "UPDATE_TOAST", id, toast: options })
81
- return id
83
+ loading(options) {
84
+ return upsert({ ...options, type: "loading" })
82
85
  },
83
-
84
- loading(options: Options) {
85
- options.type = "loading"
86
- return group.upsert(options)
87
- },
88
-
89
- success(options: Options) {
90
- options.type = "success"
91
- return group.upsert(options)
86
+ success(options) {
87
+ return upsert({ ...options, type: "success" })
92
88
  },
93
-
94
- error(options: Options) {
95
- options.type = "error"
96
- return group.upsert(options)
89
+ error(options) {
90
+ return upsert({ ...options, type: "error" })
97
91
  },
98
92
 
99
- promise<T>(promise: Promise<T>, options: PromiseOptions<T>, shared: Options = {}) {
100
- const id = group.loading({ ...shared, ...options.loading })
93
+ promise(promise, options, shared = {}) {
94
+ const id = upsert({ ...shared, ...options.loading, type: "loading" })
101
95
 
102
96
  promise
103
97
  .then((response) => {
104
98
  const successOptions = runIfFn(options.success, response)
105
- group.success({ ...shared, ...successOptions, id })
99
+ upsert({ ...shared, ...successOptions, id, type: "success" })
106
100
  })
107
101
  .catch((error) => {
108
102
  const errorOptions = runIfFn(options.error, error)
109
- group.error({ ...shared, ...errorOptions, id })
103
+ upsert({ ...shared, ...errorOptions, id, type: "error" })
110
104
  })
111
105
 
112
106
  return promise
113
107
  },
114
108
 
115
- pause(id?: string) {
109
+ pause(id) {
116
110
  if (id == null) {
117
111
  send("PAUSE_ALL")
118
- } else if (group.isVisible(id)) {
112
+ } else if (isVisible(id)) {
119
113
  send({ type: "PAUSE_TOAST", id })
120
114
  }
121
115
  },
122
116
 
123
- resume(id?: string) {
117
+ resume(id) {
124
118
  if (id == null) {
125
119
  send("RESUME_ALL")
126
- } else if (group.isVisible(id)) {
120
+ } else if (isVisible(id)) {
127
121
  send({ type: "RESUME_TOAST", id })
128
122
  }
129
123
  },
130
124
 
131
- getGroupProps(options: GroupProps) {
125
+ getGroupProps(options) {
132
126
  const { placement, label = "Notifications" } = options
133
127
  return normalize.element({
134
128
  ...parts.group.attrs,
@@ -142,12 +136,8 @@ export function groupConnect<T extends PropTypes>(
142
136
  })
143
137
  },
144
138
 
145
- subscribe(fn: (toasts: GroupMachineContext["toasts"]) => void) {
139
+ subscribe(fn) {
146
140
  return subscribe(state.context.toasts, () => fn(state.context.toasts))
147
141
  },
148
142
  }
149
-
150
- Object.assign(toaster, group)
151
-
152
- return group
153
143
  }
@@ -2,7 +2,7 @@ import { createMachine } from "@zag-js/core"
2
2
  import { MAX_Z_INDEX } from "@zag-js/dom-query"
3
3
  import { compact } from "@zag-js/utils"
4
4
  import { createToastMachine } from "./toast.machine"
5
- import type { GroupMachineContext, UserDefinedGroupContext } from "./toast.types"
5
+ import type { GroupMachineContext, MachineContext, UserDefinedGroupContext } from "./toast.types"
6
6
 
7
7
  export function groupMachine(userContext: UserDefinedGroupContext) {
8
8
  const ctx = compact(userContext)
@@ -55,8 +55,11 @@ export function groupMachine(userContext: UserDefinedGroupContext) {
55
55
  ADD_TOAST: {
56
56
  guard: (ctx) => ctx.toasts.length < ctx.max,
57
57
  actions: (ctx, evt, { self }) => {
58
- const options = {
59
- ...ctx.defaultOptions,
58
+ const options: MachineContext = {
59
+ placement: ctx.placement,
60
+ duration: ctx.duration,
61
+ removeDelay: ctx.removeDelay,
62
+ render: ctx.render,
60
63
  ...evt.toast,
61
64
  pauseOnPageIdle: ctx.pauseOnPageIdle,
62
65
  pauseOnInteraction: ctx.pauseOnInteraction,
@@ -8,7 +8,7 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
8
8
  const isPaused = state.hasTag("paused")
9
9
 
10
10
  const pauseOnInteraction = state.context.pauseOnInteraction
11
- const placement = state.context.placement
11
+ const placement = state.context.placement!
12
12
 
13
13
  return {
14
14
  type: state.context.type,
@@ -31,20 +31,6 @@ export function connect<T extends PropTypes>(state: State, send: Send, normalize
31
31
  send("DISMISS")
32
32
  },
33
33
 
34
- render() {
35
- return state.context.render?.({
36
- id: state.context.id,
37
- type: state.context.type,
38
- duration: state.context.duration,
39
- title: state.context.title,
40
- placement: state.context.placement,
41
- description: state.context.description,
42
- dismiss() {
43
- send("DISMISS")
44
- },
45
- })
46
- },
47
-
48
34
  rootProps: normalize.element({
49
35
  ...parts.root.attrs,
50
36
  dir: state.context.dir,
package/src/toast.dom.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createScope } from "@zag-js/dom-query"
2
- import type { GroupMachineContext as GroupCtx, MachineContext as Ctx, Placement } from "./toast.types"
2
+ import type { MachineContext as Ctx, Placement } from "./toast.types"
3
3
 
4
4
  export const dom = createScope({
5
5
  getGroupId: (placement: Placement) => `toast-group:${placement}`,
@@ -7,5 +7,4 @@ export const dom = createScope({
7
7
  getTitleId: (ctx: Ctx) => `toast:${ctx.id}:title`,
8
8
  getDescriptionId: (ctx: Ctx) => `toast:${ctx.id}:description`,
9
9
  getCloseTriggerId: (ctx: Ctx) => `toast${ctx.id}:close`,
10
- getPortalId: (ctx: GroupCtx) => `toast-portal:${ctx.id}`,
11
10
  })
@@ -9,44 +9,59 @@ import type {
9
9
  RootProperties,
10
10
  } from "@zag-js/types"
11
11
 
12
+ /* -----------------------------------------------------------------------------
13
+ * Base types
14
+ * -----------------------------------------------------------------------------*/
15
+
12
16
  export type Type = "success" | "error" | "loading" | "info" | "custom"
13
17
 
14
18
  export type Placement = "top-start" | "top" | "top-end" | "bottom-start" | "bottom" | "bottom-end"
15
19
 
16
- export interface ToastOptions {
20
+ export interface BaseOptions {
17
21
  /**
18
- * The unique id of the toast
22
+ * Whether to pause toast when the user leaves the browser tab
19
23
  */
20
- id: string
24
+ pauseOnPageIdle?: boolean
21
25
  /**
22
- * The type of the toast
26
+ * Whether to pause the toast when interacted with
23
27
  */
24
- type: Type
28
+ pauseOnInteraction?: boolean
29
+ /**
30
+ * The duration the toast will be visible
31
+ */
32
+ duration?: number
33
+ /**
34
+ * The duration for the toast to kept alive before it is removed.
35
+ * Useful for exit transitions.
36
+ */
37
+ removeDelay?: number
25
38
  /**
26
39
  * The placement of the toast
27
40
  */
28
- placement: Placement
41
+ placement?: Placement
29
42
  /**
30
- * The message of the toast
43
+ * Custom function to render the toast element.
31
44
  */
32
- title?: string
45
+ render?: (options: MachineApi<any>) => any
46
+ }
47
+
48
+ export interface ToastOptions {
33
49
  /**
34
- * The description of the toast
50
+ * The unique id of the toast
35
51
  */
36
- description?: string
52
+ id: string
37
53
  /**
38
- * The duration the toast will be visible
54
+ * The type of the toast
39
55
  */
40
- duration: number
56
+ type: Type
41
57
  /**
42
- * Custom function to render the toast element.
58
+ * The message of the toast
43
59
  */
44
- render?: (options: RenderOptions) => any
60
+ title?: string
45
61
  /**
46
- * The duration for the toast to kept alive before it is removed.
47
- * Useful for exit transitions.
62
+ * The description of the toast
48
63
  */
49
- removeDelay?: number
64
+ description?: string
50
65
  /**
51
66
  * Function called when the toast has been closed and removed
52
67
  */
@@ -65,34 +80,14 @@ export interface ToastOptions {
65
80
  onUpdate?: VoidFunction
66
81
  }
67
82
 
68
- export type Options = Partial<ToastOptions>
69
-
70
- export type RenderOptions = Omit<ToastOptions, "render"> & {
71
- dismiss(): void
72
- }
83
+ export type Options = Partial<ToastOptions & BaseOptions>
73
84
 
74
85
  /* -----------------------------------------------------------------------------
75
86
  * Machine context
76
87
  * -----------------------------------------------------------------------------*/
77
88
 
78
- interface SharedContext {
79
- /**
80
- * Whether to pause toast when the user leaves the browser tab
81
- */
82
- pauseOnPageIdle?: boolean
83
- /**
84
- * Whether to pause the toast when interacted with
85
- */
86
- pauseOnInteraction?: boolean
87
-
88
- /**
89
- * The default options for the toast
90
- */
91
- defaultOptions?: Partial<Pick<ToastOptions, "duration" | "removeDelay" | "placement">>
92
- }
93
-
94
89
  export interface MachineContext
95
- extends SharedContext,
90
+ extends BaseOptions,
96
91
  RootProperties,
97
92
  CommonProperties,
98
93
  Omit<ToastOptions, "removeDelay"> {
@@ -126,7 +121,7 @@ export type Send = S.Send
126
121
 
127
122
  export type Service = Machine<MachineContext, MachineState>
128
123
 
129
- interface GroupPublicContext extends SharedContext, DirectionProperty, CommonProperties {
124
+ interface GroupPublicContext extends BaseOptions, DirectionProperty, CommonProperties {
130
125
  /**
131
126
  * The gutter or spacing between toasts
132
127
  */
@@ -254,7 +249,7 @@ export interface GroupMachineApi<T extends PropTypes = PropTypes> {
254
249
  * - When the promise resolves, the toast will be updated with the success options.
255
250
  * - When the promise rejects, the toast will be updated with the error options.
256
251
  */
257
- promise<T>(promise: Promise<T>, options: PromiseOptions<T>, shared?: ToastOptions): Promise<T>
252
+ promise<T>(promise: Promise<T>, options: PromiseOptions<T>, shared?: Partial<ToastOptions>): Promise<T>
258
253
  /**
259
254
  * Function to subscribe to the toast group.
260
255
  */
@@ -303,10 +298,7 @@ export interface MachineApi<T extends PropTypes = PropTypes> {
303
298
  * Function to instantly dismiss the toast.
304
299
  */
305
300
  dismiss(): void
306
- /**
307
- * Function render the toast in the DOM (based on the defined `render` property)
308
- */
309
- render(): any
301
+
310
302
  rootProps: T["element"]
311
303
  titleProps: T["element"]
312
304
  descriptionProps: T["element"]