@opencode/ui 2.0.7 → 2.0.9

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.
@@ -0,0 +1,4 @@
1
+ export declare const toastDescriptionLimit = 2000;
2
+ export declare function boundToastDescription<T extends {
3
+ description?: string;
4
+ }>(options: T): T;
@@ -31,6 +31,8 @@ export declare const toaster: {
31
31
  }) => JSX.Element, options?: {
32
32
  duration?: number;
33
33
  persistent?: boolean;
34
+ /** Reactive value the toast's height depends on; sonner re-measures when it changes. */
35
+ resize?: () => unknown;
34
36
  }): number;
35
37
  dismiss(toastId?: number): string | number | undefined;
36
38
  };
package/dist/i18n/en.d.ts CHANGED
@@ -133,6 +133,8 @@ declare const source: {
133
133
  "ui.promptInput.dropFiles.pdf": string;
134
134
  "ui.promptInput.dropFiles.imagePdf": string;
135
135
  "ui.promptInput.removeAttachment": string;
136
+ "ui.promptInput.cancelUpload": string;
137
+ "ui.promptInput.uploading": string;
136
138
  "ui.promptInput.label": string;
137
139
  "ui.promptInput.placeholder.shell": string;
138
140
  "ui.promptInput.placeholder.normal": string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode/ui",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,4 +1,4 @@
1
- import { createEffect, mergeProps, onCleanup, onMount, Show, splitProps, type ComponentProps } from "solid-js"
1
+ import { createEffect, mergeProps, on, onCleanup, onMount, Show, splitProps, type ComponentProps } from "solid-js"
2
2
  import { Portal } from "solid-js/web"
3
3
  import { createResizeObserver } from "@solid-primitives/resize-observer"
4
4
  import { createStore } from "solid-js/store"
@@ -236,18 +236,22 @@ export function ScrollView(props: ScrollViewProps) {
236
236
  local.viewportRef(viewportRef)
237
237
  }
238
238
 
239
+ // The observer reports every target once when it starts observing (including a thumb mount that
240
+ // appears later), after layout. Measuring here as well would force a synchronous layout per
241
+ // ScrollView while the page is still rendering.
239
242
  createResizeObserver(
240
243
  () => [viewportRef, viewportRef.firstElementChild, thumbMount()].filter(Boolean) as HTMLElement[],
241
244
  updateThumb,
242
245
  )
243
-
244
- updateThumb()
245
246
  })
246
247
 
247
- createEffect(() => {
248
- thumbMount()
249
- updateThumb()
250
- })
248
+ // Inputs that change the thumb without resizing anything: re-measure on change, but leave the
249
+ // first measurement to the observer.
250
+ createEffect(
251
+ on([() => local.verticalScrollAdjustment, vertical, horizontal, thumbMount], () => updateThumb(), {
252
+ defer: true,
253
+ }),
254
+ )
251
255
 
252
256
  createEffect(() => {
253
257
  if (!horizontal() || !viewportRef) return
@@ -0,0 +1,9 @@
1
+ // A toast shows a few lines; the rest of a long description would only be retained (in the dedupe
2
+ // key, the active-toast registry and the toaster's store) for as long as the toast lives. Server
3
+ // errors have carried a whole data URL here.
4
+ export const toastDescriptionLimit = 2000
5
+
6
+ export function boundToastDescription<T extends { description?: string }>(options: T): T {
7
+ if (!options.description || options.description.length <= toastDescriptionLimit) return options
8
+ return { ...options, description: `${options.description.slice(0, toastDescriptionLimit)}…` }
9
+ }
@@ -79,6 +79,15 @@
79
79
  grid-column: 2;
80
80
  }
81
81
 
82
+ /* Custom toasts render no close button, so their content takes the whole width. */
83
+ &:not(:has(> [data-close-button])) > [data-content] {
84
+ grid-column: 1 / -1;
85
+ }
86
+
87
+ [data-description]:empty {
88
+ display: none;
89
+ }
90
+
82
91
  > [data-close-button] {
83
92
  grid-column: 2;
84
93
  grid-row: 1;
@@ -1,4 +1,5 @@
1
1
  import { Toaster, toast, type ToasterProps } from "solid-sonner"
2
+ import { boundToastDescription } from "./description"
2
3
  import { isRTL } from "@kobalte/core/i18n"
3
4
  import type { ComponentProps, JSX } from "solid-js"
4
5
  import { createContext, onCleanup, onMount, splitProps, useContext } from "solid-js"
@@ -140,13 +141,30 @@ export const Toast = Object.assign(ToastRoot, {
140
141
  let toastV2Id = 0
141
142
 
142
143
  export const toaster = {
143
- show(render: (props: { toastId: number }) => JSX.Element, options?: { duration?: number; persistent?: boolean }) {
144
+ show(
145
+ render: (props: { toastId: number }) => JSX.Element,
146
+ options?: {
147
+ duration?: number
148
+ persistent?: boolean
149
+ /** Reactive value the toast's height depends on; sonner re-measures when it changes. */
150
+ resize?: () => unknown
151
+ },
152
+ ) {
144
153
  const toastId = --toastV2Id
145
154
  toast.custom((id) => render({ toastId: Number(id) }), {
146
155
  id: toastId,
147
156
  className: "toast-v2",
148
157
  duration: options?.persistent ? Number.POSITIVE_INFINITY : options?.duration,
149
158
  unstyled: true,
159
+ // Sonner only re-measures on title/description changes, so a function-valued description is
160
+ // the hook for a custom toast whose height depends on reactive state. It renders nothing;
161
+ // toast.css collapses the empty description element.
162
+ description: options?.resize
163
+ ? (): JSX.Element => {
164
+ options.resize?.()
165
+ return undefined
166
+ }
167
+ : undefined,
150
168
  })
151
169
  return toastId
152
170
  },
@@ -188,7 +206,7 @@ const activeToastByKey = new Map<string, ActiveToast>()
188
206
  const activeToastById = new Map<number, ActiveToast>()
189
207
 
190
208
  export function showToast(options: ToastOptions | string) {
191
- const opts: ToastOptions = typeof options === "string" ? { description: options } : options
209
+ const opts = boundToastDescription(typeof options === "string" ? { description: options } : options)
192
210
  const key = JSON.stringify({
193
211
  title: opts.title,
194
212
  description: opts.description,
package/src/i18n/en.ts CHANGED
@@ -147,6 +147,8 @@ const source = {
147
147
  "ui.promptInput.dropFiles.pdf": "Drop PDFs or files to add",
148
148
  "ui.promptInput.dropFiles.imagePdf": "Drop images, PDFs, or files to add",
149
149
  "ui.promptInput.removeAttachment": "Remove attachment",
150
+ "ui.promptInput.cancelUpload": "Cancel upload",
151
+ "ui.promptInput.uploading": "{{percent}}%",
150
152
  "ui.promptInput.label": "Prompt",
151
153
  "ui.promptInput.placeholder.shell": "Enter shell command…",
152
154
  "ui.promptInput.placeholder.normal": "Ask anything, {{slash}} for commands, {{at}} for context…",