@opencode/ui 0.0.0-dev-20497 → 0.0.0-dev-20498

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.
@@ -32,7 +32,7 @@ export declare const toaster: {
32
32
  duration?: number;
33
33
  persistent?: boolean;
34
34
  /** Reactive value the toast's height depends on; sonner re-measures when it changes. */
35
- resize?: () => unknown;
35
+ resize?: () => void;
36
36
  }): number;
37
37
  dismiss(toastId?: number): string | number | undefined;
38
38
  };
@@ -44,7 +44,8 @@ export interface ToastAction {
44
44
  export interface ToastOptions {
45
45
  title?: string;
46
46
  description?: string;
47
- icon?: JSX.Element;
47
+ /** An element, or a render function the toast calls under its own owner, for icons created from imperative code. */
48
+ icon?: JSX.Element | (() => JSX.Element);
48
49
  variant?: "default" | "success" | "error" | "loading";
49
50
  duration?: number;
50
51
  persistent?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode/ui",
3
- "version": "0.0.0-dev-20497",
3
+ "version": "0.0.0-dev-20498",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -21,9 +21,12 @@ function ToastRegion(props: ToastRegionProps) {
21
21
  element.tabIndex = hidden ? -1 : 0
22
22
  })
23
23
  }
24
+
24
25
  let connected = false
26
+
25
27
  const connect = () => {
26
28
  const regions = document.querySelectorAll(".toast-v2-region")
29
+
27
30
  if (!regions.length) return
28
31
  observer.disconnect()
29
32
  regions.forEach((region) => {
@@ -37,14 +40,17 @@ function ToastRegion(props: ToastRegionProps) {
37
40
  connected = true
38
41
  sync()
39
42
  }
43
+
40
44
  const observer = new MutationObserver(() => {
41
45
  if (!connected) connect()
42
46
  else sync()
43
47
  })
48
+
44
49
  observer.observe(document.body, { subtree: true, childList: true })
45
50
  queueMicrotask(connect)
46
51
  onCleanup(() => observer.disconnect())
47
52
  })
53
+
48
54
  return (
49
55
  <Portal>
50
56
  <Toaster
@@ -55,7 +61,7 @@ function ToastRegion(props: ToastRegionProps) {
55
61
  duration={5000}
56
62
  swipeDirections={["bottom"]}
57
63
  className={["toast-v2-region", local.className, local.class].filter(Boolean).join(" ")}
58
- style={{ "--width": "320px", ...local.style } as JSX.CSSProperties}
64
+ style={{ "--width": "320px", ...local.style }}
59
65
  toastOptions={{
60
66
  ...local.toastOptions,
61
67
  unstyled: true,
@@ -103,6 +109,7 @@ function ToastCloseButton(props: ComponentProps<"button">) {
103
109
  const i18n = useI18n()
104
110
  const toastId = useContext(ToastContext)
105
111
  const [local, rest] = splitProps(props, ["children", "onClick"])
112
+
106
113
  return (
107
114
  <button
108
115
  type="button"
@@ -110,7 +117,8 @@ function ToastCloseButton(props: ComponentProps<"button">) {
110
117
  aria-label={i18n.t("ui.common.dismiss")}
111
118
  {...rest}
112
119
  onClick={(event) => {
113
- if (typeof local.onClick === "function") local.onClick(event)
120
+ if (local.onClick instanceof Function) local.onClick(event)
121
+
114
122
  if (!event.defaultPrevented && toastId !== undefined) toaster.dismiss(toastId)
115
123
  }}
116
124
  >
@@ -147,7 +155,7 @@ export const toaster = {
147
155
  duration?: number
148
156
  persistent?: boolean
149
157
  /** Reactive value the toast's height depends on; sonner re-measures when it changes. */
150
- resize?: () => unknown
158
+ resize?: () => void
151
159
  },
152
160
  ) {
153
161
  const toastId = --toastV2Id
@@ -162,10 +170,12 @@ export const toaster = {
162
170
  description: options?.resize
163
171
  ? (): JSX.Element => {
164
172
  options.resize?.()
173
+
165
174
  return undefined
166
175
  }
167
176
  : undefined,
168
177
  })
178
+
169
179
  return toastId
170
180
  },
171
181
  dismiss(toastId?: number) {
@@ -175,6 +185,7 @@ export const toaster = {
175
185
  } else {
176
186
  releaseToast(activeToastById.get(toastId))
177
187
  }
188
+
178
189
  return toast.dismiss(toastId)
179
190
  },
180
191
  }
@@ -188,7 +199,8 @@ export interface ToastAction {
188
199
  export interface ToastOptions {
189
200
  title?: string
190
201
  description?: string
191
- icon?: JSX.Element
202
+ /** An element, or a render function the toast calls under its own owner, for icons created from imperative code. */
203
+ icon?: JSX.Element | (() => JSX.Element)
192
204
  variant?: "default" | "success" | "error" | "loading"
193
205
  duration?: number
194
206
  persistent?: boolean
@@ -203,10 +215,14 @@ interface ActiveToast {
203
215
  }
204
216
 
205
217
  const activeToastByKey = new Map<string, ActiveToast>()
218
+
206
219
  const activeToastById = new Map<number, ActiveToast>()
207
220
 
208
221
  export function showToast(options: ToastOptions | string) {
222
+ // SAFETY: `options` is declared `ToastOptions | string`; `typeof` separates the string shorthand from the object.
223
+ // oxlint-disable-next-line anti-slop/no-runtime-typeof -- see SAFETY above
209
224
  const opts = boundToastDescription(typeof options === "string" ? { description: options } : options)
225
+
210
226
  const key = JSON.stringify({
211
227
  title: opts.title,
212
228
  description: opts.description,
@@ -215,6 +231,7 @@ export function showToast(options: ToastOptions | string) {
215
231
  persistent: opts.persistent,
216
232
  actions: opts.actions?.map((action) => [action.label, action.variant]),
217
233
  })
234
+
218
235
  const active = activeToastByKey.get(key)
219
236
  const toasts = toast.getToasts()
220
237
 
@@ -222,6 +239,7 @@ export function showToast(options: ToastOptions | string) {
222
239
  active.options = opts
223
240
  publishToast(active)
224
241
  pulseToast(active.id)
242
+
225
243
  return active.id
226
244
  }
227
245
 
@@ -233,6 +251,7 @@ export function showToast(options: ToastOptions | string) {
233
251
  activeToastByKey.set(key, entry)
234
252
  activeToastById.set(entry.id, entry)
235
253
  publishToast(entry)
254
+
236
255
  return entry.id
237
256
  }
238
257
 
@@ -241,7 +260,9 @@ function publishToast(entry: ActiveToast) {
241
260
  toast(entry.options.title ?? "", {
242
261
  id: entry.id,
243
262
  description: entry.options.description,
244
- icon: entry.options.icon,
263
+ // SAFETY: solid-sonner inserts `icon` as a JSX child, and Solid calls a function child under the toast's render
264
+ // owner; solid-sonner's type omits function children.
265
+ icon: entry.options.icon as JSX.Element,
245
266
  action: entry.actions,
246
267
  closeButton: true,
247
268
  duration: entry.options.persistent ? Number.POSITIVE_INFINITY : entry.options.duration,
@@ -255,6 +276,7 @@ function publishToast(entry: ActiveToast) {
255
276
 
256
277
  function createToastActions(entry: ActiveToast) {
257
278
  if (!entry.options.actions?.length) return undefined
279
+
258
280
  return (
259
281
  <Toast.Actions>
260
282
  {/* Static map, not <For>: this JSX is created imperatively outside any Solid
@@ -268,7 +290,8 @@ function createToastActions(entry: ActiveToast) {
268
290
  data-action-variant={action.variant ?? "primary"}
269
291
  onClick={() => {
270
292
  const onClick = entry.options.actions?.[index]?.onClick
271
- if (typeof onClick === "function") onClick()
293
+
294
+ if (onClick && onClick !== "dismiss") onClick()
272
295
  toaster.dismiss(entry.id)
273
296
  }}
274
297
  >
@@ -283,6 +306,7 @@ function pulseToast(toastId: number) {
283
306
  if (typeof document === "undefined" || typeof requestAnimationFrame === "undefined") return
284
307
  requestAnimationFrame(() => {
285
308
  const element = document.querySelector<HTMLElement>(`[data-testid="toast-v2-${toastId}"]`)
309
+
286
310
  if (!element || window.matchMedia("(prefers-reduced-motion: reduce)").matches) return
287
311
  element.animate([{ scale: 1 }, { scale: 1.025 }, { scale: 1 }], { duration: 160, easing: "ease-out" })
288
312
  })
@@ -290,7 +314,9 @@ function pulseToast(toastId: number) {
290
314
 
291
315
  function releaseToast(entry: ActiveToast | undefined) {
292
316
  if (!entry) return
317
+
293
318
  if (activeToastByKey.get(entry.key) === entry) activeToastByKey.delete(entry.key)
319
+
294
320
  if (activeToastById.get(entry.id) === entry) activeToastById.delete(entry.id)
295
321
  }
296
322