@opencode-ai/ui 1.18.2 → 1.18.4

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.
@@ -7,6 +7,8 @@ export interface ResizeHandleProps extends Omit<JSX.HTMLAttributes<HTMLDivElemen
7
7
  max: number;
8
8
  onResize: (size: number) => void;
9
9
  onCollapse?: () => void;
10
+ /** Called while dragging when size crosses `collapseThreshold`. */
11
+ onCollapseChange?: (collapsed: boolean) => void;
10
12
  collapseThreshold?: number;
11
13
  }
12
14
  export declare function ResizeHandle(props: ResizeHandleProps): JSX.Element;
@@ -1,9 +1,19 @@
1
- import { type ComponentProps } from "solid-js";
1
+ import { type Accessor, type ComponentProps } from "solid-js";
2
2
  export type ScrollViewThumbVisibility = "hover" | "scroll";
3
3
  export interface ScrollViewProps extends ComponentProps<"div"> {
4
4
  viewportRef?: (el: HTMLDivElement) => void;
5
5
  orientation?: "vertical" | "horizontal";
6
+ /**
7
+ * `hover`: show while hovered or scrolling. `scroll`: show only while scrolling.
8
+ *
9
+ * In most cases, scrolling a container = hovering over it, so this change has no effect.
10
+ * This is a special case to account for the home page scroll, where scrolling a container != hovering over it
11
+ * */
6
12
  thumbVisibility?: ScrollViewThumbVisibility;
13
+ /** Mount the thumb into an external track. Scroll metrics still come from this ScrollView. */
14
+ thumbContainer?: HTMLElement | Accessor<HTMLElement | undefined>;
15
+ /** Element whose hover reveals the thumb. Defaults to the ScrollView root when unset. */
16
+ thumbHoverTarget?: HTMLElement | Accessor<HTMLElement | undefined>;
7
17
  }
8
18
  export declare const scrollKey: (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => "end" | "home" | "page-down" | "page-up" | "up" | "down" | undefined;
9
19
  export declare function canScrollKey(element: HTMLElement, key: NonNullable<ReturnType<typeof scrollKey>>): boolean;
@@ -16,5 +26,7 @@ export declare function scrollTopFromThumbPointer(input: {
16
26
  clientHeight: number;
17
27
  scrollHeight: number;
18
28
  thumbHeight: number;
29
+ /** Viewport height used for max scroll. Defaults to `clientHeight` (track == viewport). */
30
+ scrollClientHeight?: number;
19
31
  }): number;
20
32
  export declare function ScrollView(props: ScrollViewProps): import("solid-js").JSX.Element;
@@ -4,7 +4,7 @@ import { type IconProps } from "./icon";
4
4
  import "./button-v2.css";
5
5
  export interface ButtonV2Props extends ComponentProps<typeof Kobalte>, Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
6
6
  size?: "small" | "normal" | "large";
7
- variant?: "neutral" | "danger" | "outline" | "contrast" | "ghost" | "ghost-muted" | "loading";
7
+ variant?: "neutral" | "danger" | "warning" | "outline" | "contrast" | "ghost" | "ghost-muted" | "loading";
8
8
  icon?: IconProps["name"];
9
9
  }
10
10
  export declare function ButtonV2(props: ButtonV2Props): import("solid-js").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode-ai/ui",
3
- "version": "1.18.2",
3
+ "version": "1.18.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -7,6 +7,20 @@
7
7
  overflow: clip;
8
8
  }
9
9
 
10
+ [data-dock-border-underlay] {
11
+ /* Later shadows paint underneath, so this solid ring masks overlapping surfaces without changing border geometry. */
12
+ box-shadow:
13
+ var(--dock-shell-visual-shadow, var(--shadow-xs-border)),
14
+ 0 0 0 var(--dock-shell-border-underlay-width, 1px)
15
+ var(--dock-shell-border-underlay-background, var(--surface-raised-stronger-non-alpha));
16
+ }
17
+
18
+ [data-dock-border-underlay="v2"] {
19
+ --dock-shell-visual-shadow: var(--v2-elevation-raised);
20
+ --dock-shell-border-underlay-width: 0.5px;
21
+ --dock-shell-border-underlay-background: var(--v2-background-bg-base);
22
+ }
23
+
10
24
  [data-dock-surface="tray"] {
11
25
  background-color: var(--background-base);
12
26
  border: 1px solid var(--border-weak-base);
@@ -8,6 +8,8 @@ export interface ResizeHandleProps extends Omit<JSX.HTMLAttributes<HTMLDivElemen
8
8
  max: number
9
9
  onResize: (size: number) => void
10
10
  onCollapse?: () => void
11
+ /** Called while dragging when size crosses `collapseThreshold`. */
12
+ onCollapseChange?: (collapsed: boolean) => void
11
13
  collapseThreshold?: number
12
14
  }
13
15
 
@@ -20,17 +22,26 @@ export function ResizeHandle(props: ResizeHandleProps) {
20
22
  "max",
21
23
  "onResize",
22
24
  "onCollapse",
25
+ "onCollapseChange",
23
26
  "collapseThreshold",
24
27
  "class",
25
28
  "classList",
26
29
  ])
27
30
 
28
31
  const handleMouseDown = (e: MouseEvent) => {
32
+ if (e.detail > 1) return
29
33
  e.preventDefault()
30
34
  const edge = local.edge ?? (local.direction === "vertical" ? "start" : "end")
31
35
  const start = local.direction === "horizontal" ? e.clientX : e.clientY
32
36
  const startSize = local.size
37
+ const min = local.min
38
+ const max = local.max
39
+ const threshold = local.collapseThreshold ?? 0
40
+ const onResize = local.onResize
41
+ const onCollapse = local.onCollapse
42
+ const onCollapseChange = local.onCollapseChange
33
43
  let current = startSize
44
+ let collapsed = false
34
45
 
35
46
  document.body.style.userSelect = "none"
36
47
  document.body.style.overflow = "hidden"
@@ -46,8 +57,12 @@ export function ResizeHandle(props: ResizeHandleProps) {
46
57
  ? start - pos
47
58
  : pos - start
48
59
  current = startSize + delta
49
- const clamped = Math.min(local.max, Math.max(local.min, current))
50
- local.onResize(clamped)
60
+ const nextCollapsed = threshold > 0 && current < threshold
61
+ if (nextCollapsed !== collapsed) {
62
+ collapsed = nextCollapsed
63
+ onCollapseChange?.(collapsed)
64
+ }
65
+ onResize(Math.min(max, Math.max(min, current)))
51
66
  }
52
67
 
53
68
  const onMouseUp = () => {
@@ -56,10 +71,11 @@ export function ResizeHandle(props: ResizeHandleProps) {
56
71
  document.removeEventListener("mousemove", onMouseMove)
57
72
  document.removeEventListener("mouseup", onMouseUp)
58
73
 
59
- const threshold = local.collapseThreshold ?? 0
60
- if (local.onCollapse && threshold > 0 && current < threshold) {
61
- local.onCollapse()
74
+ if (collapsed) {
75
+ onCollapse?.()
76
+ return
62
77
  }
78
+ onCollapseChange?.(false)
63
79
  }
64
80
 
65
81
  document.addEventListener("mousemove", onMouseMove)
@@ -27,6 +27,7 @@
27
27
  transition: opacity 200ms ease;
28
28
  cursor: default;
29
29
  user-select: none;
30
+ pointer-events: auto;
30
31
  opacity: 0;
31
32
  }
32
33
 
@@ -1,4 +1,15 @@
1
- import { onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
1
+ import {
2
+ createEffect,
3
+ createMemo,
4
+ mergeProps,
5
+ onCleanup,
6
+ onMount,
7
+ Show,
8
+ splitProps,
9
+ type Accessor,
10
+ type ComponentProps,
11
+ } from "solid-js"
12
+ import { Portal } from "solid-js/web"
2
13
  import { createResizeObserver } from "@solid-primitives/resize-observer"
3
14
  import { createStore } from "solid-js/store"
4
15
  import { useI18n } from "../context/i18n"
@@ -8,7 +19,17 @@ export type ScrollViewThumbVisibility = "hover" | "scroll"
8
19
  export interface ScrollViewProps extends ComponentProps<"div"> {
9
20
  viewportRef?: (el: HTMLDivElement) => void
10
21
  orientation?: "vertical" | "horizontal" // currently only vertical is fully implemented for thumb
22
+ /**
23
+ * `hover`: show while hovered or scrolling. `scroll`: show only while scrolling.
24
+ *
25
+ * In most cases, scrolling a container = hovering over it, so this change has no effect.
26
+ * This is a special case to account for the home page scroll, where scrolling a container != hovering over it
27
+ * */
11
28
  thumbVisibility?: ScrollViewThumbVisibility
29
+ /** Mount the thumb into an external track. Scroll metrics still come from this ScrollView. */
30
+ thumbContainer?: HTMLElement | Accessor<HTMLElement | undefined>
31
+ /** Element whose hover reveals the thumb. Defaults to the ScrollView root when unset. */
32
+ thumbHoverTarget?: HTMLElement | Accessor<HTMLElement | undefined>
12
33
  }
13
34
 
14
35
  export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => {
@@ -65,12 +86,14 @@ export function scrollTopFromThumbPointer(input: {
65
86
  clientHeight: number
66
87
  scrollHeight: number
67
88
  thumbHeight: number
89
+ /** Viewport height used for max scroll. Defaults to `clientHeight` (track == viewport). */
90
+ scrollClientHeight?: number
68
91
  }) {
69
92
  const padding = 8
70
93
  const maxThumbTop = input.clientHeight - padding * 2 - input.thumbHeight
71
94
  if (maxThumbTop <= 0) return 0
72
95
  const thumbTop = Math.max(0, Math.min(input.pointer - input.viewportTop - padding - input.grabOffset, maxThumbTop))
73
- return (thumbTop / maxThumbTop) * Math.max(0, input.scrollHeight - input.clientHeight)
96
+ return (thumbTop / maxThumbTop) * Math.max(0, input.scrollHeight - (input.scrollClientHeight ?? input.clientHeight))
74
97
  }
75
98
 
76
99
  export function ScrollView(props: ScrollViewProps) {
@@ -78,7 +101,16 @@ export function ScrollView(props: ScrollViewProps) {
78
101
  const merged = mergeProps({ orientation: "vertical", thumbVisibility: "hover" }, props)
79
102
  const [local, events, rest] = splitProps(
80
103
  merged,
81
- ["class", "children", "viewportRef", "orientation", "thumbVisibility", "style"],
104
+ [
105
+ "class",
106
+ "children",
107
+ "viewportRef",
108
+ "orientation",
109
+ "thumbVisibility",
110
+ "thumbContainer",
111
+ "thumbHoverTarget",
112
+ "style",
113
+ ],
82
114
  [
83
115
  "onScroll",
84
116
  "onWheel",
@@ -96,6 +128,15 @@ export function ScrollView(props: ScrollViewProps) {
96
128
  let viewportRef!: HTMLDivElement
97
129
  let thumbRef!: HTMLDivElement
98
130
 
131
+ const resolveEl = (value: HTMLElement | Accessor<HTMLElement | undefined> | undefined) => {
132
+ if (typeof value === "function") return value()
133
+ return value
134
+ }
135
+
136
+ const thumbMount = createMemo(() => resolveEl(local.thumbContainer))
137
+ const thumbHover = createMemo(() => resolveEl(local.thumbHoverTarget))
138
+ const hoverRoot = () => !local.thumbHoverTarget && !local.thumbContainer
139
+
99
140
  const [state, setState] = createStore({
100
141
  isHovered: false,
101
142
  isDragging: false,
@@ -114,7 +155,6 @@ export function ScrollView(props: ScrollViewProps) {
114
155
  let scrollIdleTimer: ReturnType<typeof setTimeout> | undefined
115
156
 
116
157
  const markScrolling = () => {
117
- if (local.thumbVisibility !== "scroll") return
118
158
  setState("isScrolling", true)
119
159
  if (scrollIdleTimer !== undefined) clearTimeout(scrollIdleTimer)
120
160
  scrollIdleTimer = setTimeout(() => setState("isScrolling", false), 800)
@@ -122,8 +162,8 @@ export function ScrollView(props: ScrollViewProps) {
122
162
 
123
163
  const thumbVisible = () => {
124
164
  if (isDragging()) return true
125
- if (local.thumbVisibility === "scroll") return isScrolling()
126
- return isHovered()
165
+ if (isScrolling()) return true
166
+ return local.thumbVisibility === "hover" && isHovered()
127
167
  }
128
168
 
129
169
  onCleanup(() => {
@@ -141,7 +181,8 @@ export function ScrollView(props: ScrollViewProps) {
141
181
 
142
182
  setState("showThumb", true)
143
183
  const trackPadding = 8
144
- const trackHeight = clientHeight - trackPadding * 2
184
+ const trackClientHeight = thumbMount()?.clientHeight || clientHeight
185
+ const trackHeight = trackClientHeight - trackPadding * 2
145
186
 
146
187
  const minThumbHeight = 32
147
188
  // Calculate raw thumb height based on ratio
@@ -165,16 +206,40 @@ export function ScrollView(props: ScrollViewProps) {
165
206
  local.viewportRef(viewportRef)
166
207
  }
167
208
 
168
- createResizeObserver([viewportRef, viewportRef.firstElementChild], updateThumb)
209
+ createResizeObserver(
210
+ () => [viewportRef, viewportRef.firstElementChild, thumbMount()].filter(Boolean) as HTMLElement[],
211
+ updateThumb,
212
+ )
169
213
 
170
214
  updateThumb()
171
215
  })
172
216
 
217
+ createEffect(() => {
218
+ thumbMount()
219
+ updateThumb()
220
+ })
221
+
222
+ createEffect(() => {
223
+ const target = thumbHover()
224
+ if (!target) return
225
+
226
+ const enter = () => setState("isHovered", true)
227
+ const leave = () => setState("isHovered", false)
228
+ target.addEventListener("pointerenter", enter)
229
+ target.addEventListener("pointerleave", leave)
230
+ onCleanup(() => {
231
+ target.removeEventListener("pointerenter", enter)
232
+ target.removeEventListener("pointerleave", leave)
233
+ setState("isHovered", false)
234
+ })
235
+ })
236
+
173
237
  const onThumbPointerDown = (e: PointerEvent) => {
174
238
  e.preventDefault()
175
239
  e.stopPropagation()
176
240
  setState("isDragging", true)
177
241
  const grabOffset = e.clientY - thumbRef.getBoundingClientRect().top
242
+ const track = thumbMount() ?? viewportRef
178
243
 
179
244
  thumbRef.setPointerCapture(e.pointerId)
180
245
 
@@ -182,9 +247,10 @@ export function ScrollView(props: ScrollViewProps) {
182
247
  const { scrollHeight, clientHeight } = viewportRef
183
248
  viewportRef.scrollTop = scrollTopFromThumbPointer({
184
249
  pointer: e.clientY,
185
- viewportTop: viewportRef.getBoundingClientRect().top,
250
+ viewportTop: track.getBoundingClientRect().top,
186
251
  grabOffset,
187
- clientHeight,
252
+ clientHeight: track.clientHeight,
253
+ scrollClientHeight: clientHeight,
188
254
  scrollHeight,
189
255
  thumbHeight: thumbHeight(),
190
256
  })
@@ -203,6 +269,23 @@ export function ScrollView(props: ScrollViewProps) {
203
269
  thumbRef.addEventListener("pointercancel", done)
204
270
  }
205
271
 
272
+ const renderThumb = () => (
273
+ <div
274
+ ref={(el) => {
275
+ thumbRef = el
276
+ }}
277
+ onPointerDown={onThumbPointerDown}
278
+ class="scroll-view__thumb"
279
+ data-visible={thumbVisible()}
280
+ data-dragging={isDragging()}
281
+ style={{
282
+ height: `${thumbHeight()}px`,
283
+ transform: `translateY(${thumbTop()}px)`,
284
+ "z-index": 100, // ensure it displays over content
285
+ }}
286
+ />
287
+ )
288
+
206
289
  // Keybinds implementation
207
290
  // We ensure the viewport has a tabindex so it can receive focus
208
291
  // We can also explicitly catch PageUp/Down if we want smooth scroll or specific behavior,
@@ -253,8 +336,12 @@ export function ScrollView(props: ScrollViewProps) {
253
336
  ref={rootRef}
254
337
  class={`scroll-view ${local.class || ""}`}
255
338
  style={local.style}
256
- onPointerEnter={() => setState("isHovered", true)}
257
- onPointerLeave={() => setState("isHovered", false)}
339
+ onPointerEnter={() => {
340
+ if (hoverRoot()) setState("isHovered", true)
341
+ }}
342
+ onPointerLeave={() => {
343
+ if (hoverRoot()) setState("isHovered", false)
344
+ }}
258
345
  {...rest}
259
346
  >
260
347
  {/* Viewport */}
@@ -290,20 +377,11 @@ export function ScrollView(props: ScrollViewProps) {
290
377
  {local.children}
291
378
  </div>
292
379
 
293
- {/* Thumb Overlay */}
380
+ {/* Thumb Overlay — optionally portaled into an external track */}
294
381
  <Show when={showThumb()}>
295
- <div
296
- ref={thumbRef}
297
- onPointerDown={onThumbPointerDown}
298
- class="scroll-view__thumb"
299
- data-visible={thumbVisible()}
300
- data-dragging={isDragging()}
301
- style={{
302
- height: `${thumbHeight()}px`,
303
- transform: `translateY(${thumbTop()}px)`,
304
- "z-index": 100, // ensure it displays over content
305
- }}
306
- />
382
+ <Show when={thumbMount()} fallback={renderThumb()}>
383
+ {(mount) => <Portal mount={mount()}>{renderThumb()}</Portal>}
384
+ </Show>
307
385
  </Show>
308
386
  </div>
309
387
  )
@@ -723,7 +723,11 @@ body[data-new-layout] #review-panel [data-component="tabs"][data-variant="normal
723
723
  }
724
724
 
725
725
  &:has([data-slot="tabs-trigger-close-button"]) {
726
- padding-right: 10px;
726
+ padding-right: 8px;
727
+
728
+ [data-slot="tabs-trigger-close-button"] {
729
+ margin-left: 2px;
730
+ }
727
731
  }
728
732
 
729
733
  &:has([data-selected]) {
@@ -789,6 +793,62 @@ body[data-new-layout]
789
793
  [data-slot="tabs-list"]
790
794
  > .sticky {
791
795
  padding-right: 0;
796
+
797
+ [data-component="icon-button-v2"] {
798
+ position: relative;
799
+
800
+ &::after {
801
+ content: "";
802
+ position: absolute;
803
+ left: 100%;
804
+ top: 50%;
805
+ transform: translateY(-50%);
806
+ width: 20px;
807
+ height: 28px;
808
+ background-color: var(--v2-background-bg-base);
809
+ pointer-events: none;
810
+ }
811
+ }
812
+ }
813
+
814
+ body[data-new-layout]
815
+ #review-panel
816
+ [data-component="tabs"]
817
+ .session-review-v2-tabs-bar
818
+ [data-slot="tabs-list"]
819
+ > .session-review-v2-sidebar-toggle-slot.sticky {
820
+ padding-right: 0;
821
+
822
+ &::before {
823
+ content: "";
824
+ position: absolute;
825
+ top: 50%;
826
+ bottom: auto;
827
+ left: auto;
828
+ right: 100%;
829
+ transform: translateY(-50%);
830
+ width: 12px;
831
+ height: 28px;
832
+ background-color: var(--v2-background-bg-base);
833
+ background-image: none;
834
+ pointer-events: none;
835
+ }
836
+
837
+ &::after {
838
+ content: "";
839
+ position: absolute;
840
+ top: 50%;
841
+ left: 100%;
842
+ transform: translateY(-50%);
843
+ width: 8px;
844
+ height: 28px;
845
+ pointer-events: none;
846
+ background: linear-gradient(90deg, var(--v2-background-bg-base), transparent);
847
+ }
848
+
849
+ [data-component="icon-button-v2"]::after {
850
+ display: none;
851
+ }
792
852
  }
793
853
 
794
854
  body[data-new-layout] #review-panel [data-component="tabs"] .session-review-v2-open-in-app-slot {
@@ -113,6 +113,29 @@
113
113
  cursor: not-allowed;
114
114
  }
115
115
 
116
+ [data-component="button-v2"][data-variant="warning"] {
117
+ background-color: var(--v2-background-bg-button-neutral);
118
+ color: var(--v2-state-fg-warning);
119
+ box-shadow: var(--v2-elevation-button-neutral);
120
+ }
121
+
122
+ [data-component="button-v2"][data-variant="warning"]:is(:hover, [data-state="hover"]):not(:disabled) {
123
+ background-image:
124
+ linear-gradient(90deg, var(--v2-overlay-simple-overlay-hover) 0%, var(--v2-overlay-simple-overlay-hover) 100%),
125
+ linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
126
+ }
127
+
128
+ [data-component="button-v2"][data-variant="warning"]:is(:active, [data-state="pressed"]):not(:disabled) {
129
+ background-image:
130
+ linear-gradient(90deg, var(--v2-overlay-simple-overlay-pressed) 0%, var(--v2-overlay-simple-overlay-pressed) 100%),
131
+ linear-gradient(90deg, var(--v2-background-bg-button-neutral) 0%, var(--v2-background-bg-button-neutral) 100%);
132
+ }
133
+
134
+ [data-component="button-v2"][data-variant="warning"]:is(:disabled, [data-state="disabled"]) {
135
+ opacity: 0.5;
136
+ cursor: not-allowed;
137
+ }
138
+
116
139
  /* Outline */
117
140
  [data-component="button-v2"][data-variant="outline"] {
118
141
  background-color: transparent;
@@ -7,7 +7,7 @@ export interface ButtonV2Props
7
7
  extends ComponentProps<typeof Kobalte>,
8
8
  Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
9
9
  size?: "small" | "normal" | "large"
10
- variant?: "neutral" | "danger" | "outline" | "contrast" | "ghost" | "ghost-muted" | "loading"
10
+ variant?: "neutral" | "danger" | "warning" | "outline" | "contrast" | "ghost" | "ghost-muted" | "loading"
11
11
  icon?: IconProps["name"]
12
12
  }
13
13
 
@@ -101,7 +101,7 @@ export function Dialog(props: DialogProps) {
101
101
  const autofocusEl = target?.querySelector("[autofocus]") as HTMLElement | null
102
102
  if (autofocusEl) {
103
103
  e.preventDefault()
104
- autofocusEl.focus()
104
+ autofocusEl.focus({ preventScroll: true })
105
105
  }
106
106
  }}
107
107
  >
@@ -1,8 +1,8 @@
1
1
  [data-component="divider-v2"] {
2
2
  box-sizing: border-box;
3
3
  width: 100%;
4
- height: 0.5px;
5
- margin-block: 0.25px;
4
+ height: 1px;
5
+ transform: scaleY(0.5);
6
6
  border: none;
7
7
  background: var(--v2-border-border-strong);
8
8
  flex: none;
@@ -67,9 +67,14 @@
67
67
  gap: 4px;
68
68
  background: var(--v2-background-bg-layer-01);
69
69
  border-radius: 4px 0 0 4px;
70
+ cursor: default;
70
71
  transition: background 85ms ease-out;
71
72
  }
72
73
 
74
+ [data-component="inline-input-v2"]:where([data-disabled]) [data-slot="inline-input-v2-prefix"] {
75
+ cursor: not-allowed;
76
+ }
77
+
73
78
  [data-component="inline-input-v2"][data-label-width] [data-slot="inline-input-v2-prefix"] {
74
79
  width: var(--inline-input-v2-label-width);
75
80
  min-width: var(--inline-input-v2-label-width);
@@ -89,6 +94,7 @@
89
94
  letter-spacing: -0.04px;
90
95
  color: var(--v2-text-text-muted);
91
96
  font-variation-settings: "slnt" 0;
97
+ user-select: none;
92
98
  }
93
99
 
94
100
  [data-component="inline-input-v2"][data-numeric] [data-slot="inline-input-v2-prefix-text"] {
@@ -149,6 +155,7 @@
149
155
 
150
156
  [data-component="inline-input-v2"] [data-slot="inline-input-v2-input"]::placeholder {
151
157
  color: var(--v2-text-text-faint);
158
+ user-select: none;
152
159
  }
153
160
 
154
161
  [data-component="inline-input-v2"][data-numeric] [data-slot="inline-input-v2-input"] {
@@ -37,6 +37,8 @@ export function InlineInputV2(props: InlineInputV2Props) {
37
37
  "style",
38
38
  ])
39
39
 
40
+ let input: HTMLInputElement | undefined
41
+
40
42
  return (
41
43
  <div
42
44
  data-component="inline-input-v2"
@@ -59,7 +61,15 @@ export function InlineInputV2(props: InlineInputV2Props) {
59
61
  : {}),
60
62
  }}
61
63
  >
62
- <div data-slot="inline-input-v2-prefix">
64
+ <div
65
+ data-slot="inline-input-v2-prefix"
66
+ onMouseDown={(event) => {
67
+ if (local.disabled || event.button !== 0) return
68
+ // Keep focus on the input without using a native <label>, so external labels still work.
69
+ event.preventDefault()
70
+ input?.focus()
71
+ }}
72
+ >
63
73
  <span data-slot="inline-input-v2-prefix-text">{local.prefix}</span>
64
74
  </div>
65
75
  <div data-slot="inline-input-v2-divider" aria-hidden="true" />
@@ -67,6 +77,11 @@ export function InlineInputV2(props: InlineInputV2Props) {
67
77
  <div data-slot="inline-input-v2-value">
68
78
  <input
69
79
  {...inputProps}
80
+ ref={(el) => {
81
+ input = el
82
+ const ref = inputProps.ref
83
+ if (typeof ref === "function") ref(el)
84
+ }}
70
85
  type={inputProps.type ?? "text"}
71
86
  disabled={local.disabled}
72
87
  aria-invalid={local.invalid ? true : undefined}
@@ -98,6 +98,7 @@
98
98
 
99
99
  [data-component="text-input-v2"] [data-slot="text-input-v2-input"]::placeholder {
100
100
  color: var(--v2-text-text-faint);
101
+ user-select: none;
101
102
  }
102
103
 
103
104
  [data-component="text-input-v2"] [data-slot="text-input-v2-input"][type="search"]::-webkit-search-cancel-button {