@opencode/ui 2.0.23 → 2.0.25

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.
Files changed (82) hide show
  1. package/dist/context/marked-parser.d.ts +2 -0
  2. package/dist/i18n/en.d.ts +1 -0
  3. package/dist/icons/catalog.d.ts +36 -0
  4. package/dist/navigation/segmented-control/segmented-control.d.ts +21 -2
  5. package/package.json +2 -1
  6. package/src/actions/button/button.css +11 -9
  7. package/src/actions/button/button.tsx +1 -0
  8. package/src/actions/icon-button/icon-button.css +1 -5
  9. package/src/actions/icon-button/icon-button.tsx +1 -0
  10. package/src/actions/split-button/split-button.tsx +3 -0
  11. package/src/actions/submit.css +14 -12
  12. package/src/components/animated-number.tsx +13 -0
  13. package/src/components/app-icon.tsx +2 -0
  14. package/src/components/card.tsx +21 -0
  15. package/src/components/collapsible.tsx +1 -0
  16. package/src/components/context-menu.tsx +34 -0
  17. package/src/components/dock-surface.tsx +3 -0
  18. package/src/components/file-icon.tsx +12 -0
  19. package/src/components/image-preview.tsx +1 -0
  20. package/src/components/list.tsx +32 -0
  21. package/src/components/motion-spring.tsx +5 -0
  22. package/src/components/popover.tsx +12 -0
  23. package/src/components/provider-icon.tsx +1 -0
  24. package/src/components/resize-handle.tsx +8 -0
  25. package/src/components/scroll-view.tsx +39 -0
  26. package/src/components/spinner.tsx +2 -0
  27. package/src/components/text-field.tsx +6 -0
  28. package/src/components/text-reveal.tsx +19 -0
  29. package/src/components/text-strikethrough.tsx +8 -0
  30. package/src/components/typewriter.tsx +4 -0
  31. package/src/context/dialog.tsx +12 -0
  32. package/src/context/file.tsx +1 -0
  33. package/src/context/helper.tsx +4 -0
  34. package/src/context/i18n.tsx +20 -0
  35. package/src/context/index.ts +3 -0
  36. package/src/context/marked-base.tsx +9 -1
  37. package/src/context/marked-parser.tsx +57 -18
  38. package/src/context/marked.tsx +3 -0
  39. package/src/context/worker-pool.tsx +2 -0
  40. package/src/data-display/avatar/avatar.tsx +4 -0
  41. package/src/data-display/badge/badge.tsx +1 -0
  42. package/src/data-display/diff-changes/diff-changes.tsx +2 -0
  43. package/src/data-display/keybind/keybind.tsx +1 -0
  44. package/src/data-display/line-comment/line-comment.tsx +22 -0
  45. package/src/data-display/project-avatar/project-avatar.tsx +10 -0
  46. package/src/feedback/loader/loader.tsx +1 -0
  47. package/src/feedback/toast/description.ts +1 -0
  48. package/src/forms/checkbox/checkbox.tsx +1 -0
  49. package/src/forms/field/field.tsx +8 -0
  50. package/src/forms/inline-input/inline-input.tsx +4 -0
  51. package/src/forms/radio/radio.tsx +2 -0
  52. package/src/forms/select/select.tsx +11 -0
  53. package/src/forms/switch/switch.tsx +1 -0
  54. package/src/forms/text-input/text-input.tsx +3 -0
  55. package/src/hooks/create-auto-scroll.tsx +34 -0
  56. package/src/hooks/index.ts +1 -0
  57. package/src/hooks/use-filtered-list.tsx +14 -0
  58. package/src/i18n/en.ts +1 -0
  59. package/src/i18n/no.ts +1 -0
  60. package/src/icons/catalog.ts +36 -0
  61. package/src/icons/icon/icon.tsx +2 -0
  62. package/src/layout/divider/divider.tsx +1 -0
  63. package/src/navigation/menu/menu.tsx +13 -0
  64. package/src/navigation/segmented-control/segmented-control.css +37 -4
  65. package/src/navigation/segmented-control/segmented-control.tsx +210 -14
  66. package/src/navigation/tab-state-indicator.tsx +1 -0
  67. package/src/navigation/tabs/tabs-current.css +1 -5
  68. package/src/navigation/tabs/tabs.css +5 -22
  69. package/src/navigation/tabs/tabs.tsx +18 -0
  70. package/src/overlays/dialog/dialog.tsx +3 -0
  71. package/src/overlays/tooltip/tooltip.tsx +15 -0
  72. package/src/storybook/scaffold.tsx +3 -0
  73. package/src/styles/theme.css +1 -1
  74. package/src/theme/color.ts +27 -0
  75. package/src/theme/context.tsx +58 -0
  76. package/src/theme/default-themes.ts +35 -0
  77. package/src/theme/index.ts +3 -0
  78. package/src/theme/loader.ts +11 -0
  79. package/src/theme/resolve.ts +28 -0
  80. package/src/theme/v2/foreground.ts +8 -0
  81. package/src/theme/v2/resolve.ts +10 -0
  82. package/src/typography/text-shimmer/text-shimmer.tsx +1 -0
@@ -10,6 +10,7 @@ export interface ImagePreviewProps {
10
10
 
11
11
  export function ImagePreview(props: ImagePreviewProps) {
12
12
  const i18n = useI18n()
13
+
13
14
  return (
14
15
  <div data-component="image-preview">
15
16
  <div data-slot="image-preview-container">
@@ -9,6 +9,7 @@ import { TextField } from "./text-field"
9
9
 
10
10
  function findByKey(container: HTMLElement, key: string) {
11
11
  const nodes = container.querySelectorAll<HTMLElement>('[data-slot="list-item"][data-key]')
12
+
12
13
  for (const node of nodes) {
13
14
  if (node.getAttribute("data-key") === key) return node
14
15
  }
@@ -58,11 +59,13 @@ export interface ListRef {
58
59
  export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void }) {
59
60
  const i18n = useI18n()
60
61
  let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
62
+
61
63
  const [store, setStore] = createStore({
62
64
  mouseActive: false,
63
65
  scrollRef: undefined as HTMLDivElement | undefined,
64
66
  internalFilter: "",
65
67
  })
68
+
66
69
  const scrollRef = () => store.scrollRef
67
70
  const setScrollRef = (el: HTMLDivElement | undefined) => setStore("scrollRef", el)
68
71
  const internalFilter = () => store.internalFilter
@@ -75,6 +78,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
75
78
  const bottom = top + nodeRect.height
76
79
  const viewTop = container.scrollTop
77
80
  const viewBottom = viewTop + container.clientHeight
81
+
78
82
  const target =
79
83
  block === "center"
80
84
  ? top - container.clientHeight / 2 + nodeRect.height / 2
@@ -83,6 +87,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
83
87
  : bottom > viewBottom
84
88
  ? bottom - container.clientHeight
85
89
  : viewTop
90
+
86
91
  const max = Math.max(0, container.scrollHeight - container.clientHeight)
87
92
  container.scrollTop = Math.max(0, Math.min(target, max))
88
93
  }
@@ -108,13 +113,16 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
108
113
  // This is important for programmatic changes like Tab completion.
109
114
  if (prev === value) {
110
115
  void refetch()
116
+
111
117
  return
112
118
  }
119
+
113
120
  queueMicrotask(() => refetch())
114
121
  }
115
122
 
116
123
  createEffect(() => {
117
124
  if (props.filter === undefined) return
125
+
118
126
  if (props.filter === internalFilter()) return
119
127
  setInternalFilter(props.filter)
120
128
  onInput(props.filter)
@@ -132,11 +140,14 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
132
140
 
133
141
  createEffect(() => {
134
142
  const scroll = scrollRef()
143
+
135
144
  if (!scroll) return
145
+
136
146
  if (!props.current) return
137
147
  const key = props.key(props.current)
138
148
  requestAnimationFrame(() => {
139
149
  const element = findByKey(scroll, key)
150
+
140
151
  if (!element) return
141
152
  scrollIntoView(scroll, element, "center")
142
153
  })
@@ -144,16 +155,23 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
144
155
 
145
156
  createEffect(() => {
146
157
  const all = flat()
158
+
147
159
  if (store.mouseActive || all.length === 0) return
148
160
  const scroll = scrollRef()
161
+
149
162
  if (!scroll) return
163
+
150
164
  if (active() === props.key(all[0])) {
151
165
  scroll.scrollTo(0, 0)
166
+
152
167
  return
153
168
  }
169
+
154
170
  const key = active()
171
+
155
172
  if (!key) return
156
173
  const element = findByKey(scroll, key)
174
+
157
175
  if (!element) return
158
176
  scrollIntoView(scroll, element, "center")
159
177
  })
@@ -171,6 +189,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
171
189
 
172
190
  const handleKey = (e: KeyboardEvent) => {
173
191
  setStore("mouseActive", false)
192
+
174
193
  if (e.key === "Escape") return
175
194
 
176
195
  const all = flat()
@@ -182,12 +201,15 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
182
201
 
183
202
  if (e.key === "Enter" && !e.isComposing) {
184
203
  e.preventDefault()
204
+
185
205
  if (selected) handleSelect(selected, index)
186
206
  } else if (props.search) {
187
207
  if (e.ctrlKey && !e.metaKey && !e.altKey && !e.shiftKey && (e.key === "n" || e.key === "p")) {
188
208
  onKeyDown(e)
209
+
189
210
  return
190
211
  }
212
+
191
213
  if (e.key === "ArrowDown" || e.key === "ArrowUp") {
192
214
  onKeyDown(e)
193
215
  }
@@ -204,7 +226,9 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
204
226
 
205
227
  const renderAdd = () => {
206
228
  const add = addProps()
229
+
207
230
  if (!add) return null
231
+
208
232
  return (
209
233
  <div data-slot="list-item-add" classList={{ [add.class ?? ""]: !!add.class }}>
210
234
  {add.render()}
@@ -221,6 +245,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
221
245
  createEffect(() => {
222
246
  const scroll = scrollRef()
223
247
  const node = state.header
248
+
224
249
  if (!scroll || !node) return
225
250
 
226
251
  const handler = () => {
@@ -242,12 +267,15 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
242
267
 
243
268
  const emptyMessage = () => {
244
269
  if (grouped.loading) return props.loadingMessage ?? i18n.t("ui.list.loading")
270
+
245
271
  if (props.emptyMessage) return props.emptyMessage
246
272
 
247
273
  const query = filter()
274
+
248
275
  if (!query) return i18n.t("ui.list.empty")
249
276
 
250
277
  const suffix = i18n.t("ui.list.emptyWithFilter.suffix")
278
+
251
279
  return (
252
280
  <>
253
281
  <span>{i18n.t("ui.list.emptyWithFilter.prefix")}</span>
@@ -268,6 +296,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
268
296
  classList={{ [searchProps().class ?? ""]: !!searchProps().class }}
269
297
  onPointerDown={(event) => {
270
298
  const container = event.currentTarget
299
+
271
300
  if (!(container instanceof HTMLElement)) return
272
301
 
273
302
  const node = container.querySelector("input, textarea")
@@ -327,6 +356,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
327
356
  <For each={grouped.latest}>
328
357
  {(group, groupIndex) => {
329
358
  const isLastGroup = () => groupIndex() === grouped.latest.length - 1
359
+
330
360
  return (
331
361
  <div data-slot="list-group">
332
362
  <Show when={group.category}>
@@ -374,7 +404,9 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
374
404
  </Show>
375
405
  </button>
376
406
  )
407
+
377
408
  if (props.itemWrapper) return props.itemWrapper(item, node)
409
+
378
410
  return node
379
411
  }}
380
412
  </For>
@@ -3,6 +3,7 @@ import type { SpringOptions } from "motion"
3
3
  import { createComputed, createEffect, createSignal, onCleanup } from "solid-js"
4
4
 
5
5
  type Opt = Partial<Pick<SpringOptions, "visualDuration" | "bounce" | "stiffness" | "damping" | "mass" | "velocity">>
6
+
6
7
  const eq = (a: Opt | undefined, b: Opt | undefined) =>
7
8
  a?.visualDuration === b?.visualDuration &&
8
9
  a?.bounce === b?.bounce &&
@@ -24,6 +25,7 @@ export function useSpring(target: () => number, options?: Opt | (() => Opt), sna
24
25
  createComputed(() => {
25
26
  const next = target()
26
27
  const nextSnap = snapKey?.()
28
+
27
29
  if (snapKey && nextSnap !== snapValue) {
28
30
  // State boundaries should adopt their target without animating from the previous context.
29
31
  snapValue = nextSnap
@@ -32,14 +34,17 @@ export function useSpring(target: () => number, options?: Opt | (() => Opt), sna
32
34
  source.jump(next)
33
35
  stop = attachSpring(spring, source, config)
34
36
  setValue(next)
37
+
35
38
  return
36
39
  }
40
+
37
41
  source.set(next)
38
42
  })
39
43
 
40
44
  createEffect(() => {
41
45
  if (!options) return
42
46
  const next = read()
47
+
43
48
  if (eq(config, next)) return
44
49
  config = next
45
50
  stop()
@@ -22,6 +22,7 @@ export interface PopoverProps<T extends ValidComponent = "div">
22
22
 
23
23
  export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>) {
24
24
  const i18n = useI18n()
25
+
25
26
  const [local, rest] = splitProps(props, [
26
27
  "trigger",
27
28
  "triggerAs",
@@ -47,14 +48,18 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
47
48
  })
48
49
 
49
50
  const controlled = () => local.open !== undefined
51
+
50
52
  const opened = () => {
51
53
  if (controlled()) return local.open ?? false
54
+
52
55
  return state.uncontrolledOpen
53
56
  }
54
57
 
55
58
  const onOpenChange = (next: boolean) => {
56
59
  if (next) setState("dismiss", null)
60
+
57
61
  if (local.onOpenChange) local.onOpenChange(next)
62
+
58
63
  if (controlled()) return
59
64
  setState("uncontrolledOpen", next)
60
65
  }
@@ -65,9 +70,12 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
65
70
  const inside = (node: Node | null | undefined) => {
66
71
  if (!node) return false
67
72
  const content = state.contentRef
73
+
68
74
  if (content && content.contains(node)) return true
69
75
  const trigger = state.triggerRef
76
+
70
77
  if (trigger && trigger.contains(node)) return true
78
+
71
79
  return false
72
80
  }
73
81
 
@@ -85,14 +93,18 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
85
93
 
86
94
  const onPointerDown = (event: PointerEvent) => {
87
95
  const target = event.target
96
+
88
97
  if (!(target instanceof Node)) return
98
+
89
99
  if (inside(target)) return
90
100
  close("outside")
91
101
  }
92
102
 
93
103
  const onFocusIn = (event: FocusEvent) => {
94
104
  const target = event.target
105
+
95
106
  if (!(target instanceof Node)) return
107
+
96
108
  if (inside(target)) return
97
109
  close("outside")
98
110
  }
@@ -10,6 +10,7 @@ export type ProviderIconProps = JSX.SVGElementTags["svg"] & {
10
10
  export const ProviderIcon: Component<ProviderIconProps> = (props) => {
11
11
  const [local, rest] = splitProps(props, ["id", "class", "classList"])
12
12
  const resolved = createMemo(() => (iconNames.includes(local.id as IconName) ? local.id : "synthetic"))
13
+
13
14
  return (
14
15
  <svg
15
16
  data-component="provider-icon"
@@ -33,10 +33,12 @@ export function ResizeHandle(props: ResizeHandleProps) {
33
33
  e.preventDefault()
34
34
  const edge = local.edge ?? (local.direction === "vertical" ? "start" : "end")
35
35
  const start = local.direction === "horizontal" ? e.clientX : e.clientY
36
+
36
37
  const rtl =
37
38
  local.direction === "horizontal" &&
38
39
  e.currentTarget instanceof Element &&
39
40
  getComputedStyle(e.currentTarget).direction === "rtl"
41
+
40
42
  const startSize = local.size
41
43
  const min = local.min
42
44
  const max = local.max
@@ -52,6 +54,7 @@ export function ResizeHandle(props: ResizeHandleProps) {
52
54
 
53
55
  const onMouseMove = (moveEvent: MouseEvent) => {
54
56
  const pos = local.direction === "horizontal" ? moveEvent.clientX : moveEvent.clientY
57
+
55
58
  const delta =
56
59
  local.direction === "vertical"
57
60
  ? edge === "end"
@@ -60,12 +63,15 @@ export function ResizeHandle(props: ResizeHandleProps) {
60
63
  : (edge === "start") !== rtl
61
64
  ? start - pos
62
65
  : pos - start
66
+
63
67
  current = startSize + delta
64
68
  const nextCollapsed = threshold > 0 && current < threshold
69
+
65
70
  if (nextCollapsed !== collapsed) {
66
71
  collapsed = nextCollapsed
67
72
  onCollapseChange?.(collapsed)
68
73
  }
74
+
69
75
  onResize(Math.min(max, Math.max(min, current)))
70
76
  }
71
77
 
@@ -77,8 +83,10 @@ export function ResizeHandle(props: ResizeHandleProps) {
77
83
 
78
84
  if (collapsed) {
79
85
  onCollapse?.()
86
+
80
87
  return
81
88
  }
89
+
82
90
  onCollapseChange?.(false)
83
91
  }
84
92
 
@@ -28,6 +28,7 @@ export interface ScrollViewProps extends ComponentProps<"div"> {
28
28
 
29
29
  export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey" | "metaKey" | "shiftKey">) => {
30
30
  if (event.altKey || event.ctrlKey || event.metaKey) return
31
+
31
32
  if (event.shiftKey && event.key !== " ") return
32
33
 
33
34
  switch (event.key) {
@@ -50,6 +51,7 @@ export const scrollKey = (event: Pick<KeyboardEvent, "key" | "altKey" | "ctrlKey
50
51
 
51
52
  export function canScrollKey(element: HTMLElement, key: NonNullable<ReturnType<typeof scrollKey>>) {
52
53
  const up = key === "up" || key === "page-up" || key === "home"
54
+
53
55
  return up ? element.scrollTop > 0 : element.scrollTop + element.clientHeight < element.scrollHeight
54
56
  }
55
57
 
@@ -60,16 +62,23 @@ export function scrollKeyOwner(
60
62
  ) {
61
63
  const element = target instanceof Element ? target : undefined
62
64
  const owner = element?.closest<HTMLElement>("[data-scrollable]")
65
+
63
66
  if (!owner || owner === root) return root
67
+
64
68
  if (!root.contains(owner)) return owner
69
+
65
70
  return canScrollKey(owner, key) ? owner : root
66
71
  }
67
72
 
68
73
  export function isScrollKeyTarget(target: EventTarget | null, key: NonNullable<ReturnType<typeof scrollKey>>) {
69
74
  const element = target instanceof HTMLElement ? target : undefined
75
+
70
76
  if (!element) return true
77
+
71
78
  if (["INPUT", "TEXTAREA", "SELECT"].includes(element.tagName) || element.isContentEditable) return false
79
+
72
80
  if ((key === "page-up" || key === "page-down") && element.closest('button, a[href], [role="button"]')) return false
81
+
73
82
  return true
74
83
  }
75
84
 
@@ -106,18 +115,23 @@ export function scrollOffsetFromThumbPointer(input: {
106
115
  }) {
107
116
  const padding = 8
108
117
  const maxThumbStart = input.clientSize - padding * 2 - input.thumbSize
118
+
109
119
  if (maxThumbStart <= 0) return 0
120
+
110
121
  const thumbStart = Math.max(
111
122
  0,
112
123
  Math.min(input.pointer - input.viewportStart - padding - input.grabOffset, maxThumbStart),
113
124
  )
125
+
114
126
  const progress = input.reverse ? 1 - thumbStart / maxThumbStart : thumbStart / maxThumbStart
127
+
115
128
  return progress * Math.max(0, input.scrollSize - (input.scrollClientSize ?? input.clientSize))
116
129
  }
117
130
 
118
131
  export function ScrollView(props: ScrollViewProps) {
119
132
  const i18n = useI18n()
120
133
  const merged = mergeProps({ orientation: "vertical", thumbVisibility: "hover" }, props)
134
+
121
135
  const [local, events, rest] = splitProps(
122
136
  merged,
123
137
  [
@@ -165,6 +179,7 @@ export function ScrollView(props: ScrollViewProps) {
165
179
  horizontalThumbStart: 0,
166
180
  showHorizontalThumb: false,
167
181
  })
182
+
168
183
  const isHovered = () => state.isHovered
169
184
  const isDragging = () => state.dragging !== undefined
170
185
  const isScrolling = () => state.isScrolling
@@ -175,13 +190,16 @@ export function ScrollView(props: ScrollViewProps) {
175
190
 
176
191
  const markScrolling = () => {
177
192
  setState("isScrolling", true)
193
+
178
194
  if (scrollIdleTimer !== undefined) clearTimeout(scrollIdleTimer)
179
195
  scrollIdleTimer = setTimeout(() => setState("isScrolling", false), 800)
180
196
  }
181
197
 
182
198
  const thumbVisible = () => {
183
199
  if (isDragging()) return true
200
+
184
201
  if (isScrolling()) return true
202
+
185
203
  return local.thumbVisibility === "hover" && isHovered()
186
204
  }
187
205
 
@@ -198,9 +216,11 @@ export function ScrollView(props: ScrollViewProps) {
198
216
  const adjustment = local.verticalScrollAdjustment ?? 0
199
217
  const scrollHeight = viewportRef.scrollHeight + adjustment
200
218
  const trackSize = Math.max(0, (thumbMount()?.clientHeight || viewportRef.clientHeight) - trackPadding * 2)
219
+
201
220
  const size = trackSize
202
221
  ? Math.min(trackSize, Math.max((viewportRef.clientHeight / scrollHeight) * trackSize, minThumbSize))
203
222
  : 0
223
+
204
224
  const maxScroll = scrollHeight - viewportRef.clientHeight
205
225
  const maxStart = trackSize - size
206
226
  setState("showVerticalThumb", maxScroll > 0)
@@ -215,9 +235,11 @@ export function ScrollView(props: ScrollViewProps) {
215
235
 
216
236
  if (horizontal()) {
217
237
  const trackSize = Math.max(0, (thumbMount()?.clientWidth || viewportRef.clientWidth) - trackPadding * 2)
238
+
218
239
  const size = trackSize
219
240
  ? Math.min(trackSize, Math.max((viewportRef.clientWidth / viewportRef.scrollWidth) * trackSize, minThumbSize))
220
241
  : 0
242
+
221
243
  const maxScroll = viewportRef.scrollWidth - viewportRef.clientWidth
222
244
  const maxStart = trackSize - size
223
245
  const rtl = getComputedStyle(viewportRef).direction === "rtl"
@@ -262,6 +284,7 @@ export function ScrollView(props: ScrollViewProps) {
262
284
 
263
285
  createEffect(() => {
264
286
  const target = thumbHover()
287
+
265
288
  if (!target) return
266
289
 
267
290
  const enter = () => setState("isHovered", true)
@@ -288,10 +311,12 @@ export function ScrollView(props: ScrollViewProps) {
288
311
  prepareScroll()
289
312
  setState("dragging", axis)
290
313
  const thumb = axis === "vertical" ? verticalThumbRef : horizontalThumbRef
314
+
291
315
  const grabOffset =
292
316
  axis === "vertical"
293
317
  ? e.clientY - thumb.getBoundingClientRect().top
294
318
  : e.clientX - thumb.getBoundingClientRect().left
319
+
295
320
  const track = thumbMount() ?? viewportRef
296
321
 
297
322
  thumb.setPointerCapture(e.pointerId)
@@ -300,6 +325,7 @@ export function ScrollView(props: ScrollViewProps) {
300
325
  prepareScroll()
301
326
  const vertical = axis === "vertical"
302
327
  const rtl = !vertical && getComputedStyle(viewportRef).direction === "rtl"
328
+
303
329
  const offset = scrollOffsetFromThumbPointer({
304
330
  pointer: vertical ? e.clientY : e.clientX,
305
331
  viewportStart: vertical ? track.getBoundingClientRect().top : track.getBoundingClientRect().left,
@@ -310,10 +336,13 @@ export function ScrollView(props: ScrollViewProps) {
310
336
  thumbSize: vertical ? state.verticalThumbSize : state.horizontalThumbSize,
311
337
  reverse: rtl,
312
338
  })
339
+
313
340
  if (vertical) {
314
341
  viewportRef.scrollTop = offset
342
+
315
343
  return
316
344
  }
345
+
317
346
  viewportRef.scrollLeft = rtl ? -offset : offset
318
347
  }
319
348
 
@@ -375,7 +404,9 @@ export function ScrollView(props: ScrollViewProps) {
375
404
  if (document.activeElement && ["INPUT", "TEXTAREA", "SELECT"].includes(document.activeElement.tagName)) {
376
405
  return
377
406
  }
407
+
378
408
  const next = scrollKey(e)
409
+
379
410
  // Modified navigation (for example Ctrl+Home) stays native, but must read
380
411
  // the same reconciled geometry as the keys handled by this component.
381
412
  const intent =
@@ -387,11 +418,15 @@ export function ScrollView(props: ScrollViewProps) {
387
418
  ctrlKey: false,
388
419
  metaKey: false,
389
420
  })
421
+
390
422
  if (!intent) return
423
+
391
424
  if (!isScrollKeyTarget(e.target, intent)) return
425
+
392
426
  if (scrollKeyOwner(viewportRef, e.target, intent) !== viewportRef) return
393
427
 
394
428
  prepareScroll()
429
+
395
430
  if (!next) return
396
431
  const scrollAmount = viewportRef.clientHeight * 0.8
397
432
  const lineAmount = 40
@@ -446,12 +481,15 @@ export function ScrollView(props: ScrollViewProps) {
446
481
  onScroll={(e) => {
447
482
  updateThumb()
448
483
  markScrolling()
484
+
449
485
  if (typeof events.onScroll === "function") events.onScroll(e as any)
450
486
  }}
451
487
  onWheel={(e) => {
452
488
  markScrolling()
453
489
  const handler = events.onWheel
490
+
454
491
  if (typeof handler === "function") handler(e as any)
492
+
455
493
  if (Array.isArray(handler)) handler[0](handler[1], e as any)
456
494
  }}
457
495
  onTouchStart={events.onTouchStart as any}
@@ -465,6 +503,7 @@ export function ScrollView(props: ScrollViewProps) {
465
503
  aria-label={i18n.t("ui.scrollView.ariaLabel")}
466
504
  onKeyDown={(e) => {
467
505
  onKeyDown(e)
506
+
468
507
  if (typeof events.onKeyDown === "function") events.onKeyDown(e as any)
469
508
  }}
470
509
  >
@@ -1,7 +1,9 @@
1
1
  import { ComponentProps, For } from "solid-js"
2
2
 
3
3
  const outerIndices = new Set([1, 2, 4, 7, 8, 11, 13, 14])
4
+
4
5
  const cornerIndices = new Set([0, 3, 12, 15])
6
+
5
7
  const squares = Array.from({ length: 16 }, (_, i) => ({
6
8
  id: i,
7
9
  x: (i % 4) * 4,
@@ -34,6 +34,7 @@ export interface TextFieldProps
34
34
 
35
35
  export function TextField(props: TextFieldProps) {
36
36
  const i18n = useI18n()
37
+
37
38
  const [local, others] = splitProps(props, [
38
39
  "name",
39
40
  "defaultValue",
@@ -54,17 +55,22 @@ export function TextField(props: TextFieldProps) {
54
55
  "copyKind",
55
56
  "multiline",
56
57
  ])
58
+
57
59
  const [copied, setCopied] = createSignal(false)
58
60
 
59
61
  const label = () => {
60
62
  if (copied()) return i18n.t("ui.textField.copied")
63
+
61
64
  if (local.copyKind === "link") return i18n.t("ui.textField.copyLink")
65
+
62
66
  return i18n.t("ui.textField.copyToClipboard")
63
67
  }
64
68
 
65
69
  const icon = () => {
66
70
  if (copied()) return "check"
71
+
67
72
  if (local.copyKind === "link") return "link"
73
+
68
74
  return "copy"
69
75
  }
70
76
 
@@ -3,18 +3,23 @@ import { createStore } from "solid-js/store"
3
3
 
4
4
  const px = (value: number | string | undefined, fallback: number) => {
5
5
  if (typeof value === "number") return `${value}px`
6
+
6
7
  if (typeof value === "string") return value
8
+
7
9
  return `${fallback}px`
8
10
  }
9
11
 
10
12
  const ms = (value: number | string | undefined, fallback: number) => {
11
13
  if (typeof value === "number") return `${value}ms`
14
+
12
15
  if (typeof value === "string") return value
16
+
13
17
  return `${fallback}ms`
14
18
  }
15
19
 
16
20
  const pct = (value: number | undefined, fallback: number) => {
17
21
  const v = value ?? fallback
22
+
18
23
  return `${v}%`
19
24
  }
20
25
 
@@ -38,6 +43,7 @@ export function TextReveal(props: {
38
43
  ready: false,
39
44
  swapping: false,
40
45
  })
46
+
41
47
  const cur = () => state.cur
42
48
  const old = () => state.old
43
49
  const width = () => state.width
@@ -53,10 +59,13 @@ export function TextReveal(props: {
53
59
 
54
60
  const widen = (next: number) => {
55
61
  if (next <= 0) return
62
+
56
63
  if (props.growOnly ?? true) {
57
64
  const prev = Number.parseFloat(width())
65
+
58
66
  if (Number.isFinite(prev) && next <= prev) return
59
67
  }
68
+
60
69
  setState("width", `${next}px`)
61
70
  }
62
71
 
@@ -65,11 +74,14 @@ export function TextReveal(props: {
65
74
  () => props.text,
66
75
  (next, prev) => {
67
76
  if (next === prev) return
77
+
68
78
  if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) {
69
79
  setState("cur", next)
70
80
  widen(win())
81
+
71
82
  return
72
83
  }
84
+
73
85
  setState("swapping", true)
74
86
  setState("old", prev)
75
87
  setState("cur", next)
@@ -78,8 +90,10 @@ export function TextReveal(props: {
78
90
  widen(Math.max(win(), wout()))
79
91
  rootRef?.offsetHeight
80
92
  setState("swapping", false)
93
+
81
94
  return
82
95
  }
96
+
83
97
  if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame)
84
98
  frame = requestAnimationFrame(() => {
85
99
  widen(Math.max(win(), wout()))
@@ -94,14 +108,19 @@ export function TextReveal(props: {
94
108
  onMount(() => {
95
109
  widen(win())
96
110
  const fonts = typeof document !== "undefined" ? document.fonts : undefined
111
+
97
112
  if (typeof requestAnimationFrame !== "function") {
98
113
  setState("ready", true)
114
+
99
115
  return
100
116
  }
117
+
101
118
  if (!fonts) {
102
119
  requestAnimationFrame(() => setState("ready", true))
120
+
103
121
  return
104
122
  }
123
+
105
124
  void fonts.ready.finally(() => {
106
125
  widen(win())
107
126
  requestAnimationFrame(() => setState("ready", true))