@opencode-ai/ui 0.0.0-beta-202606251302

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 (346) hide show
  1. package/LICENSE +21 -0
  2. package/package.json +110 -0
  3. package/src/assets/audio/alert-01.aac +0 -0
  4. package/src/assets/audio/alert-01.mp3 +0 -0
  5. package/src/assets/audio/alert-02.aac +0 -0
  6. package/src/assets/audio/alert-02.mp3 +0 -0
  7. package/src/assets/audio/alert-03.aac +0 -0
  8. package/src/assets/audio/alert-03.mp3 +0 -0
  9. package/src/assets/audio/alert-04.aac +0 -0
  10. package/src/assets/audio/alert-04.mp3 +0 -0
  11. package/src/assets/audio/alert-05.aac +0 -0
  12. package/src/assets/audio/alert-05.mp3 +0 -0
  13. package/src/assets/audio/alert-06.aac +0 -0
  14. package/src/assets/audio/alert-06.mp3 +0 -0
  15. package/src/assets/audio/alert-07.aac +0 -0
  16. package/src/assets/audio/alert-07.mp3 +0 -0
  17. package/src/assets/audio/alert-08.aac +0 -0
  18. package/src/assets/audio/alert-08.mp3 +0 -0
  19. package/src/assets/audio/alert-09.aac +0 -0
  20. package/src/assets/audio/alert-09.mp3 +0 -0
  21. package/src/assets/audio/alert-10.aac +0 -0
  22. package/src/assets/audio/alert-10.mp3 +0 -0
  23. package/src/assets/audio/bip-bop-01.aac +0 -0
  24. package/src/assets/audio/bip-bop-01.mp3 +0 -0
  25. package/src/assets/audio/bip-bop-02.aac +0 -0
  26. package/src/assets/audio/bip-bop-02.mp3 +0 -0
  27. package/src/assets/audio/bip-bop-03.aac +0 -0
  28. package/src/assets/audio/bip-bop-03.mp3 +0 -0
  29. package/src/assets/audio/bip-bop-04.aac +0 -0
  30. package/src/assets/audio/bip-bop-04.mp3 +0 -0
  31. package/src/assets/audio/bip-bop-05.aac +0 -0
  32. package/src/assets/audio/bip-bop-05.mp3 +0 -0
  33. package/src/assets/audio/bip-bop-06.aac +0 -0
  34. package/src/assets/audio/bip-bop-06.mp3 +0 -0
  35. package/src/assets/audio/bip-bop-07.aac +0 -0
  36. package/src/assets/audio/bip-bop-07.mp3 +0 -0
  37. package/src/assets/audio/bip-bop-08.aac +0 -0
  38. package/src/assets/audio/bip-bop-08.mp3 +0 -0
  39. package/src/assets/audio/bip-bop-09.aac +0 -0
  40. package/src/assets/audio/bip-bop-09.mp3 +0 -0
  41. package/src/assets/audio/bip-bop-10.aac +0 -0
  42. package/src/assets/audio/bip-bop-10.mp3 +0 -0
  43. package/src/assets/audio/nope-01.aac +0 -0
  44. package/src/assets/audio/nope-01.mp3 +0 -0
  45. package/src/assets/audio/nope-02.aac +0 -0
  46. package/src/assets/audio/nope-02.mp3 +0 -0
  47. package/src/assets/audio/nope-03.aac +0 -0
  48. package/src/assets/audio/nope-03.mp3 +0 -0
  49. package/src/assets/audio/nope-04.aac +0 -0
  50. package/src/assets/audio/nope-04.mp3 +0 -0
  51. package/src/assets/audio/nope-05.aac +0 -0
  52. package/src/assets/audio/nope-05.mp3 +0 -0
  53. package/src/assets/audio/nope-06.aac +0 -0
  54. package/src/assets/audio/nope-06.mp3 +0 -0
  55. package/src/assets/audio/nope-07.aac +0 -0
  56. package/src/assets/audio/nope-07.mp3 +0 -0
  57. package/src/assets/audio/nope-08.aac +0 -0
  58. package/src/assets/audio/nope-08.mp3 +0 -0
  59. package/src/assets/audio/nope-09.aac +0 -0
  60. package/src/assets/audio/nope-09.mp3 +0 -0
  61. package/src/assets/audio/nope-10.aac +0 -0
  62. package/src/assets/audio/nope-10.mp3 +0 -0
  63. package/src/assets/audio/nope-11.aac +0 -0
  64. package/src/assets/audio/nope-11.mp3 +0 -0
  65. package/src/assets/audio/nope-12.aac +0 -0
  66. package/src/assets/audio/nope-12.mp3 +0 -0
  67. package/src/assets/audio/staplebops-01.aac +0 -0
  68. package/src/assets/audio/staplebops-01.mp3 +0 -0
  69. package/src/assets/audio/staplebops-02.aac +0 -0
  70. package/src/assets/audio/staplebops-02.mp3 +0 -0
  71. package/src/assets/audio/staplebops-03.aac +0 -0
  72. package/src/assets/audio/staplebops-03.mp3 +0 -0
  73. package/src/assets/audio/staplebops-04.aac +0 -0
  74. package/src/assets/audio/staplebops-04.mp3 +0 -0
  75. package/src/assets/audio/staplebops-05.aac +0 -0
  76. package/src/assets/audio/staplebops-05.mp3 +0 -0
  77. package/src/assets/audio/staplebops-06.aac +0 -0
  78. package/src/assets/audio/staplebops-06.mp3 +0 -0
  79. package/src/assets/audio/staplebops-07.aac +0 -0
  80. package/src/assets/audio/staplebops-07.mp3 +0 -0
  81. package/src/assets/audio/yup-01.aac +0 -0
  82. package/src/assets/audio/yup-01.mp3 +0 -0
  83. package/src/assets/audio/yup-02.aac +0 -0
  84. package/src/assets/audio/yup-02.mp3 +0 -0
  85. package/src/assets/audio/yup-03.aac +0 -0
  86. package/src/assets/audio/yup-03.mp3 +0 -0
  87. package/src/assets/audio/yup-04.aac +0 -0
  88. package/src/assets/audio/yup-04.mp3 +0 -0
  89. package/src/assets/audio/yup-05.aac +0 -0
  90. package/src/assets/audio/yup-05.mp3 +0 -0
  91. package/src/assets/audio/yup-06.aac +0 -0
  92. package/src/assets/audio/yup-06.mp3 +0 -0
  93. package/src/assets/fonts/Inter.ttf +0 -0
  94. package/src/assets/fonts/JetBrainsMonoNerdFontMono-Regular.woff2 +0 -0
  95. package/src/assets/icons/app/android-studio.svg +369 -0
  96. package/src/assets/icons/app/antigravity.svg +97 -0
  97. package/src/assets/icons/app/cursor.svg +16 -0
  98. package/src/assets/icons/app/file-explorer.svg +20 -0
  99. package/src/assets/icons/app/finder.png +0 -0
  100. package/src/assets/icons/app/ghostty.svg +13 -0
  101. package/src/assets/icons/app/iterm2.svg +13 -0
  102. package/src/assets/icons/app/powershell.svg +14 -0
  103. package/src/assets/icons/app/sublimetext.svg +17 -0
  104. package/src/assets/icons/app/terminal.png +0 -0
  105. package/src/assets/icons/app/textmate.png +0 -0
  106. package/src/assets/icons/app/vscode.svg +39 -0
  107. package/src/assets/icons/app/warp.png +0 -0
  108. package/src/assets/icons/app/xcode.png +0 -0
  109. package/src/assets/icons/app/zed-dark.svg +15 -0
  110. package/src/assets/icons/app/zed.svg +15 -0
  111. package/src/components/accordion.css +123 -0
  112. package/src/components/accordion.tsx +92 -0
  113. package/src/components/animated-number.css +75 -0
  114. package/src/components/animated-number.tsx +109 -0
  115. package/src/components/app-icon.css +5 -0
  116. package/src/components/app-icon.tsx +85 -0
  117. package/src/components/app-icons/sprite.svg +114 -0
  118. package/src/components/app-icons/types.ts +21 -0
  119. package/src/components/avatar.css +49 -0
  120. package/src/components/avatar.tsx +55 -0
  121. package/src/components/button.css +194 -0
  122. package/src/components/button.tsx +33 -0
  123. package/src/components/card.css +94 -0
  124. package/src/components/card.tsx +123 -0
  125. package/src/components/checkbox.css +131 -0
  126. package/src/components/checkbox.tsx +43 -0
  127. package/src/components/collapsible.css +148 -0
  128. package/src/components/collapsible.tsx +48 -0
  129. package/src/components/context-menu.css +134 -0
  130. package/src/components/context-menu.tsx +308 -0
  131. package/src/components/dialog.css +181 -0
  132. package/src/components/dialog.tsx +72 -0
  133. package/src/components/diff-changes.css +42 -0
  134. package/src/components/diff-changes.tsx +115 -0
  135. package/src/components/dock-surface.css +23 -0
  136. package/src/components/dock-surface.tsx +54 -0
  137. package/src/components/dropdown-menu.css +135 -0
  138. package/src/components/dropdown-menu.tsx +308 -0
  139. package/src/components/favicon.tsx +13 -0
  140. package/src/components/file-icon.css +26 -0
  141. package/src/components/file-icon.tsx +588 -0
  142. package/src/components/file-icons/sprite.svg +11707 -0
  143. package/src/components/file-icons/types.ts +1095 -0
  144. package/src/components/font.tsx +1 -0
  145. package/src/components/hover-card.css +61 -0
  146. package/src/components/hover-card.tsx +32 -0
  147. package/src/components/icon-button.css +181 -0
  148. package/src/components/icon-button.tsx +29 -0
  149. package/src/components/icon.css +34 -0
  150. package/src/components/icon.tsx +169 -0
  151. package/src/components/image-preview.css +63 -0
  152. package/src/components/image-preview.tsx +32 -0
  153. package/src/components/inline-input.css +17 -0
  154. package/src/components/inline-input.tsx +22 -0
  155. package/src/components/keybind.css +18 -0
  156. package/src/components/keybind.tsx +20 -0
  157. package/src/components/list.css +331 -0
  158. package/src/components/list.tsx +394 -0
  159. package/src/components/logo.css +4 -0
  160. package/src/components/logo.tsx +62 -0
  161. package/src/components/motion-spring.tsx +58 -0
  162. package/src/components/popover.css +98 -0
  163. package/src/components/popover.tsx +153 -0
  164. package/src/components/progress-circle.css +12 -0
  165. package/src/components/progress-circle.tsx +57 -0
  166. package/src/components/progress.css +63 -0
  167. package/src/components/progress.tsx +39 -0
  168. package/src/components/provider-icon.css +5 -0
  169. package/src/components/provider-icon.tsx +25 -0
  170. package/src/components/provider-icons/sprite.svg +1135 -0
  171. package/src/components/provider-icons/types.ts +105 -0
  172. package/src/components/radio-group.css +187 -0
  173. package/src/components/radio-group.tsx +83 -0
  174. package/src/components/resize-handle.css +58 -0
  175. package/src/components/resize-handle.tsx +82 -0
  176. package/src/components/scroll-view.css +66 -0
  177. package/src/components/scroll-view.tsx +250 -0
  178. package/src/components/select.css +202 -0
  179. package/src/components/select.tsx +174 -0
  180. package/src/components/spinner.css +6 -0
  181. package/src/components/spinner.tsx +52 -0
  182. package/src/components/sticky-accordion-header.css +6 -0
  183. package/src/components/sticky-accordion-header.tsx +18 -0
  184. package/src/components/switch.css +132 -0
  185. package/src/components/switch.tsx +29 -0
  186. package/src/components/tabs.css +635 -0
  187. package/src/components/tabs.tsx +125 -0
  188. package/src/components/tag.css +37 -0
  189. package/src/components/tag.tsx +22 -0
  190. package/src/components/text-field.css +134 -0
  191. package/src/components/text-field.tsx +128 -0
  192. package/src/components/text-reveal.css +150 -0
  193. package/src/components/text-reveal.tsx +143 -0
  194. package/src/components/text-shimmer.css +119 -0
  195. package/src/components/text-shimmer.tsx +62 -0
  196. package/src/components/text-strikethrough.css +27 -0
  197. package/src/components/text-strikethrough.tsx +84 -0
  198. package/src/components/toast.css +236 -0
  199. package/src/components/toast.tsx +185 -0
  200. package/src/components/tooltip.css +74 -0
  201. package/src/components/tooltip.tsx +161 -0
  202. package/src/components/typewriter.css +14 -0
  203. package/src/components/typewriter.tsx +55 -0
  204. package/src/context/dialog.tsx +197 -0
  205. package/src/context/file.tsx +10 -0
  206. package/src/context/helper.tsx +38 -0
  207. package/src/context/i18n.tsx +38 -0
  208. package/src/context/index.ts +4 -0
  209. package/src/context/marked.tsx +522 -0
  210. package/src/context/worker-pool.tsx +20 -0
  211. package/src/custom-elements.d.ts +17 -0
  212. package/src/hooks/create-auto-scroll.tsx +237 -0
  213. package/src/hooks/index.ts +2 -0
  214. package/src/hooks/use-filtered-list.tsx +134 -0
  215. package/src/i18n/ar.ts +168 -0
  216. package/src/i18n/br.ts +168 -0
  217. package/src/i18n/bs.ts +172 -0
  218. package/src/i18n/da.ts +167 -0
  219. package/src/i18n/de.ts +173 -0
  220. package/src/i18n/en.ts +176 -0
  221. package/src/i18n/es.ts +168 -0
  222. package/src/i18n/fr.ts +168 -0
  223. package/src/i18n/ja.ts +167 -0
  224. package/src/i18n/ko.ts +168 -0
  225. package/src/i18n/no.ts +171 -0
  226. package/src/i18n/pl.ts +167 -0
  227. package/src/i18n/ru.ts +167 -0
  228. package/src/i18n/th.ts +169 -0
  229. package/src/i18n/tr.ts +174 -0
  230. package/src/i18n/uk.ts +167 -0
  231. package/src/i18n/zh.ts +171 -0
  232. package/src/i18n/zht.ts +171 -0
  233. package/src/storybook/fixtures.ts +51 -0
  234. package/src/storybook/scaffold.tsx +62 -0
  235. package/src/styles/animations.css +141 -0
  236. package/src/styles/base.css +404 -0
  237. package/src/styles/colors.css +772 -0
  238. package/src/styles/index.css +53 -0
  239. package/src/styles/tailwind/colors.css +285 -0
  240. package/src/styles/tailwind/index.css +78 -0
  241. package/src/styles/tailwind/utilities.css +131 -0
  242. package/src/styles/theme.css +609 -0
  243. package/src/styles/utilities.css +118 -0
  244. package/src/theme/color.ts +299 -0
  245. package/src/theme/context.tsx +370 -0
  246. package/src/theme/default-themes.ts +116 -0
  247. package/src/theme/index.ts +78 -0
  248. package/src/theme/loader.ts +112 -0
  249. package/src/theme/resolve.ts +540 -0
  250. package/src/theme/themes/amoled.json +49 -0
  251. package/src/theme/themes/aura.json +51 -0
  252. package/src/theme/themes/ayu.json +51 -0
  253. package/src/theme/themes/carbonfox.json +53 -0
  254. package/src/theme/themes/catppuccin-frappe.json +85 -0
  255. package/src/theme/themes/catppuccin-macchiato.json +85 -0
  256. package/src/theme/themes/catppuccin.json +45 -0
  257. package/src/theme/themes/cobalt2.json +87 -0
  258. package/src/theme/themes/cursor.json +91 -0
  259. package/src/theme/themes/dracula.json +49 -0
  260. package/src/theme/themes/everforest.json +89 -0
  261. package/src/theme/themes/flexoki.json +86 -0
  262. package/src/theme/themes/github.json +85 -0
  263. package/src/theme/themes/gruvbox.json +45 -0
  264. package/src/theme/themes/kanagawa.json +89 -0
  265. package/src/theme/themes/lucent-orng.json +87 -0
  266. package/src/theme/themes/material.json +87 -0
  267. package/src/theme/themes/matrix.json +113 -0
  268. package/src/theme/themes/mercury.json +86 -0
  269. package/src/theme/themes/monokai.json +49 -0
  270. package/src/theme/themes/nightowl.json +46 -0
  271. package/src/theme/themes/nord.json +46 -0
  272. package/src/theme/themes/oc-2.json +468 -0
  273. package/src/theme/themes/one-dark.json +89 -0
  274. package/src/theme/themes/onedarkpro.json +45 -0
  275. package/src/theme/themes/opencode.json +89 -0
  276. package/src/theme/themes/orng.json +87 -0
  277. package/src/theme/themes/osaka-jade.json +88 -0
  278. package/src/theme/themes/palenight.json +85 -0
  279. package/src/theme/themes/rosepine.json +85 -0
  280. package/src/theme/themes/shadesofpurple.json +51 -0
  281. package/src/theme/themes/solarized.json +49 -0
  282. package/src/theme/themes/synthwave84.json +87 -0
  283. package/src/theme/themes/tokyonight.json +47 -0
  284. package/src/theme/themes/vercel.json +90 -0
  285. package/src/theme/themes/vesper.json +51 -0
  286. package/src/theme/themes/zenburn.json +87 -0
  287. package/src/theme/types.ts +75 -0
  288. package/src/theme/v2/avatar.ts +48 -0
  289. package/src/theme/v2/default-primitives.ts +114 -0
  290. package/src/theme/v2/foreground.ts +60 -0
  291. package/src/theme/v2/mapping.ts +138 -0
  292. package/src/theme/v2/resolve.ts +153 -0
  293. package/src/v2/components/accordion-v2.css +139 -0
  294. package/src/v2/components/accordion-v2.tsx +86 -0
  295. package/src/v2/components/avatar-v2.css +70 -0
  296. package/src/v2/components/avatar-v2.tsx +59 -0
  297. package/src/v2/components/badge-v2.css +27 -0
  298. package/src/v2/components/badge-v2.tsx +20 -0
  299. package/src/v2/components/button-v2.css +186 -0
  300. package/src/v2/components/button-v2.tsx +35 -0
  301. package/src/v2/components/checkbox-v2.css +184 -0
  302. package/src/v2/components/checkbox-v2.tsx +65 -0
  303. package/src/v2/components/dialog-v2.css +150 -0
  304. package/src/v2/components/dialog-v2.tsx +93 -0
  305. package/src/v2/components/diff-changes-v2.css +24 -0
  306. package/src/v2/components/diff-changes-v2.tsx +28 -0
  307. package/src/v2/components/field-v2.css +94 -0
  308. package/src/v2/components/field-v2.tsx +265 -0
  309. package/src/v2/components/icon-button-v2.css +155 -0
  310. package/src/v2/components/icon-button-v2.tsx +37 -0
  311. package/src/v2/components/icon.tsx +129 -0
  312. package/src/v2/components/inline-input-v2.css +218 -0
  313. package/src/v2/components/inline-input-v2.tsx +90 -0
  314. package/src/v2/components/keybind-v2.css +76 -0
  315. package/src/v2/components/keybind-v2.tsx +30 -0
  316. package/src/v2/components/line-comment-v2.css +204 -0
  317. package/src/v2/components/line-comment-v2.tsx +155 -0
  318. package/src/v2/components/menu-v2.css +190 -0
  319. package/src/v2/components/menu-v2.tsx +225 -0
  320. package/src/v2/components/project-avatar-v2.css +126 -0
  321. package/src/v2/components/project-avatar-v2.tsx +64 -0
  322. package/src/v2/components/radio-v2.css +202 -0
  323. package/src/v2/components/radio-v2.tsx +72 -0
  324. package/src/v2/components/segmented-control-v2.css +80 -0
  325. package/src/v2/components/segmented-control-v2.tsx +208 -0
  326. package/src/v2/components/select-v2.css +285 -0
  327. package/src/v2/components/select-v2.tsx +208 -0
  328. package/src/v2/components/switch-v2.css +154 -0
  329. package/src/v2/components/switch-v2.tsx +28 -0
  330. package/src/v2/components/tab-state-indicator.tsx +37 -0
  331. package/src/v2/components/tabs-v2.css +225 -0
  332. package/src/v2/components/tabs-v2.tsx +147 -0
  333. package/src/v2/components/text-input-v2.css +145 -0
  334. package/src/v2/components/text-input-v2.tsx +67 -0
  335. package/src/v2/components/text-shimmer-v2.css +125 -0
  336. package/src/v2/components/text-shimmer-v2.tsx +63 -0
  337. package/src/v2/components/textarea-v2.css +78 -0
  338. package/src/v2/components/textarea-v2.tsx +31 -0
  339. package/src/v2/components/toast-v2.css +215 -0
  340. package/src/v2/components/toast-v2.tsx +144 -0
  341. package/src/v2/components/tooltip-v2.css +53 -0
  342. package/src/v2/components/tooltip-v2.tsx +146 -0
  343. package/src/v2/components/wordmark-v2.tsx +92 -0
  344. package/src/v2/styles/colors.css +172 -0
  345. package/src/v2/styles/tailwind.css +2 -0
  346. package/src/v2/styles/theme.css +441 -0
@@ -0,0 +1,237 @@
1
+ import { createEffect, on, onCleanup } from "solid-js"
2
+ import { createStore } from "solid-js/store"
3
+ import { createEventListener } from "@solid-primitives/event-listener"
4
+ import { createResizeObserver } from "@solid-primitives/resize-observer"
5
+
6
+ export interface AutoScrollOptions {
7
+ working: () => boolean
8
+ onUserInteracted?: () => void
9
+ overflowAnchor?: "none" | "auto" | "dynamic"
10
+ bottomThreshold?: number
11
+ }
12
+
13
+ export function createAutoScroll(options: AutoScrollOptions) {
14
+ let settling = false
15
+ let settleTimer: ReturnType<typeof setTimeout> | undefined
16
+ let autoTimer: ReturnType<typeof setTimeout> | undefined
17
+ let auto: { top: number; time: number } | undefined
18
+
19
+ const threshold = () => options.bottomThreshold ?? 10
20
+
21
+ const [store, setStore] = createStore({
22
+ contentRef: undefined as HTMLElement | undefined,
23
+ scrollRef: undefined as HTMLElement | undefined,
24
+ userScrolled: false,
25
+ })
26
+
27
+ const active = () => options.working() || settling
28
+
29
+ const distanceFromBottom = (el: HTMLElement) => {
30
+ return el.scrollHeight - el.clientHeight - el.scrollTop
31
+ }
32
+
33
+ const canScroll = (el: HTMLElement) => {
34
+ return el.scrollHeight - el.clientHeight > 1
35
+ }
36
+
37
+ // Browsers can dispatch scroll events asynchronously. If new content arrives
38
+ // between us calling `scrollTo()` and the subsequent `scroll` event firing,
39
+ // the handler can see a non-zero `distanceFromBottom` and incorrectly assume
40
+ // the user scrolled.
41
+ const markAuto = (el: HTMLElement) => {
42
+ auto = {
43
+ top: Math.max(0, el.scrollHeight - el.clientHeight),
44
+ time: Date.now(),
45
+ }
46
+
47
+ if (autoTimer) clearTimeout(autoTimer)
48
+ autoTimer = setTimeout(() => {
49
+ auto = undefined
50
+ autoTimer = undefined
51
+ }, 1500)
52
+ }
53
+
54
+ const isAuto = (el: HTMLElement) => {
55
+ const a = auto
56
+ if (!a) return false
57
+
58
+ if (Date.now() - a.time > 1500) {
59
+ auto = undefined
60
+ return false
61
+ }
62
+
63
+ return Math.abs(el.scrollTop - a.top) < 2
64
+ }
65
+
66
+ const scrollToBottomNow = (behavior: ScrollBehavior) => {
67
+ const el = store.scrollRef
68
+ if (!el) return
69
+ markAuto(el)
70
+ if (behavior === "smooth") {
71
+ el.scrollTo({ top: el.scrollHeight, behavior })
72
+ return
73
+ }
74
+
75
+ // `scrollTop` assignment bypasses any CSS `scroll-behavior: smooth`.
76
+ el.scrollTop = el.scrollHeight
77
+ }
78
+
79
+ const scrollToBottom = (force: boolean) => {
80
+ if (!force && !active()) return
81
+
82
+ if (force && store.userScrolled) setStore("userScrolled", false)
83
+
84
+ const el = store.scrollRef
85
+ if (!el) return
86
+
87
+ if (!force && store.userScrolled) return
88
+
89
+ const distance = distanceFromBottom(el)
90
+ if (distance < 2) {
91
+ markAuto(el)
92
+ return
93
+ }
94
+
95
+ // For auto-following content we prefer immediate updates to avoid
96
+ // visible "catch up" animations while content is still settling.
97
+ scrollToBottomNow("auto")
98
+ }
99
+
100
+ const stop = () => {
101
+ const el = store.scrollRef
102
+ if (!el) return
103
+ if (!canScroll(el)) {
104
+ if (store.userScrolled) setStore("userScrolled", false)
105
+ return
106
+ }
107
+ if (store.userScrolled) return
108
+
109
+ setStore("userScrolled", true)
110
+ options.onUserInteracted?.()
111
+ }
112
+
113
+ const handleWheel = (e: WheelEvent) => {
114
+ if (e.deltaY >= 0) return
115
+ // If the user is scrolling within a nested scrollable region (tool output,
116
+ // code block, etc), don't treat it as leaving the "follow bottom" mode.
117
+ // Those regions opt in via `data-scrollable`.
118
+ const el = store.scrollRef
119
+ const target = e.target instanceof Element ? e.target : undefined
120
+ const nested = target?.closest("[data-scrollable]")
121
+ if (el && nested && nested !== el) return
122
+ stop()
123
+ }
124
+
125
+ const handleScroll = () => {
126
+ const el = store.scrollRef
127
+ if (!el) return
128
+
129
+ if (!canScroll(el)) {
130
+ if (store.userScrolled) setStore("userScrolled", false)
131
+ return
132
+ }
133
+
134
+ if (distanceFromBottom(el) < threshold()) {
135
+ if (store.userScrolled) setStore("userScrolled", false)
136
+ return
137
+ }
138
+
139
+ // Ignore scroll events triggered by our own scrollToBottom calls.
140
+ if (!store.userScrolled && isAuto(el)) {
141
+ scrollToBottom(false)
142
+ return
143
+ }
144
+
145
+ stop()
146
+ }
147
+
148
+ const handleInteraction = () => {
149
+ if (!active()) return
150
+ const selection = window.getSelection()
151
+ if (selection && selection.toString().length > 0) {
152
+ stop()
153
+ }
154
+ }
155
+
156
+ const updateOverflowAnchor = (el: HTMLElement) => {
157
+ const mode = options.overflowAnchor ?? "dynamic"
158
+
159
+ if (mode === "none") {
160
+ el.style.overflowAnchor = "none"
161
+ return
162
+ }
163
+
164
+ if (mode === "auto") {
165
+ el.style.overflowAnchor = "auto"
166
+ return
167
+ }
168
+
169
+ el.style.overflowAnchor = store.userScrolled ? "auto" : "none"
170
+ }
171
+
172
+ createResizeObserver(
173
+ () => store.contentRef,
174
+ () => {
175
+ const el = store.scrollRef
176
+ if (el && !canScroll(el)) {
177
+ if (store.userScrolled) setStore("userScrolled", false)
178
+ return
179
+ }
180
+ if (!active()) return
181
+ if (store.userScrolled) return
182
+ // ResizeObserver fires after layout, before paint.
183
+ // Keep the bottom locked in the same frame to avoid visible
184
+ // "jump up then catch up" artifacts while streaming content.
185
+ scrollToBottom(false)
186
+ },
187
+ )
188
+
189
+ createEffect(
190
+ on(options.working, (working: boolean) => {
191
+ settling = false
192
+ if (settleTimer) clearTimeout(settleTimer)
193
+ settleTimer = undefined
194
+
195
+ if (working) {
196
+ if (!store.userScrolled) scrollToBottom(true)
197
+ return
198
+ }
199
+
200
+ settling = true
201
+ settleTimer = setTimeout(() => {
202
+ settling = false
203
+ }, 300)
204
+ }),
205
+ )
206
+
207
+ createEffect(() => {
208
+ // Track `userScrolled` even before `scrollRef` is attached, so we can
209
+ // update overflow anchoring once the element exists.
210
+ store.userScrolled
211
+ const el = store.scrollRef
212
+ if (!el) return
213
+ updateOverflowAnchor(el)
214
+ })
215
+
216
+ createEventListener(() => store.scrollRef, "wheel", handleWheel, { passive: true })
217
+
218
+ onCleanup(() => {
219
+ if (settleTimer) clearTimeout(settleTimer)
220
+ if (autoTimer) clearTimeout(autoTimer)
221
+ })
222
+
223
+ return {
224
+ scrollRef: (el: HTMLElement | undefined) => setStore("scrollRef", el),
225
+ contentRef: (el: HTMLElement | undefined) => setStore("contentRef", el),
226
+ handleScroll,
227
+ handleInteraction,
228
+ pause: stop,
229
+ resume: () => {
230
+ if (store.userScrolled) setStore("userScrolled", false)
231
+ scrollToBottom(true)
232
+ },
233
+ scrollToBottom: () => scrollToBottom(false),
234
+ forceScrollToBottom: () => scrollToBottom(true),
235
+ userScrolled: () => store.userScrolled,
236
+ }
237
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./use-filtered-list"
2
+ export * from "./create-auto-scroll"
@@ -0,0 +1,134 @@
1
+ import fuzzysort from "fuzzysort"
2
+ import { entries, flatMap, groupBy, map, pipe } from "remeda"
3
+ import { createEffect, createMemo, createResource, on } from "solid-js"
4
+ import { createStore } from "solid-js/store"
5
+ import { createList } from "solid-list"
6
+
7
+ export interface FilteredListProps<T> {
8
+ items: T[] | ((filter: string) => T[] | Promise<T[]>)
9
+ key: (item: T) => string
10
+ filterKeys?: string[]
11
+ current?: T
12
+ groupBy?: (x: T) => string
13
+ sortBy?: (a: T, b: T) => number
14
+ sortGroupsBy?: (a: { category: string; items: T[] }, b: { category: string; items: T[] }) => number
15
+ skipFilter?: (item: T) => boolean
16
+ onSelect?: (value: T | undefined, index: number) => void
17
+ noInitialSelection?: boolean
18
+ }
19
+
20
+ export function useFilteredList<T>(props: FilteredListProps<T>) {
21
+ const [store, setStore] = createStore<{ filter: string }>({ filter: "" })
22
+
23
+ type Group = { category: string; items: [T, ...T[]] }
24
+ const empty: Group[] = []
25
+
26
+ const [grouped, { refetch }] = createResource(
27
+ () => ({
28
+ filter: store.filter,
29
+ items: typeof props.items === "function" ? props.items(store.filter) : props.items,
30
+ }),
31
+ async ({ filter, items }) => {
32
+ const query = filter ?? ""
33
+ const needle = query.toLowerCase()
34
+ const all = (await Promise.resolve(items)) || []
35
+ const result = pipe(
36
+ all,
37
+ (x) => {
38
+ if (!needle) return x
39
+ const skipFilter = props.skipFilter
40
+ const filterable = skipFilter ? x.filter((item) => !skipFilter(item)) : x
41
+ const skipped = skipFilter ? x.filter(skipFilter) : []
42
+ const filtered =
43
+ !props.filterKeys && Array.isArray(filterable) && filterable.every((e) => typeof e === "string")
44
+ ? (fuzzysort.go(needle, filterable).map((x) => x.target) as T[])
45
+ : fuzzysort.go(needle, filterable, { keys: props.filterKeys! }).map((x) => x.obj)
46
+ return skipped.length ? [...filtered, ...skipped] : filtered
47
+ },
48
+ groupBy((x) => (props.groupBy ? props.groupBy(x) : "")),
49
+ entries(),
50
+ map(([k, v]) => ({ category: k, items: props.sortBy ? v.sort(props.sortBy) : v })),
51
+ (groups) => (props.sortGroupsBy ? groups.sort(props.sortGroupsBy) : groups),
52
+ )
53
+ return result
54
+ },
55
+ { initialValue: empty },
56
+ )
57
+
58
+ const flat = createMemo(() => {
59
+ return pipe(
60
+ grouped.latest || [],
61
+ flatMap((x) => x.items),
62
+ )
63
+ })
64
+
65
+ function initialActive() {
66
+ if (props.noInitialSelection) return ""
67
+ if (props.current) return props.key(props.current)
68
+
69
+ const items = flat()
70
+ if (items.length === 0) return ""
71
+ return props.key(items[0])
72
+ }
73
+
74
+ const list = createList({
75
+ items: () => flat().map(props.key),
76
+ initialActive: initialActive(),
77
+ loop: true,
78
+ })
79
+
80
+ const reset = () => {
81
+ if (props.noInitialSelection) {
82
+ list.setActive("")
83
+ return
84
+ }
85
+ const all = flat()
86
+ if (all.length === 0) return
87
+ list.setActive(props.key(all[0]))
88
+ }
89
+
90
+ const onKeyDown = (event: KeyboardEvent) => {
91
+ if (event.key === "Enter" && !event.isComposing) {
92
+ event.preventDefault()
93
+ const selectedIndex = flat().findIndex((x) => props.key(x) === list.active())
94
+ const selected = flat()[selectedIndex]
95
+ if (selected) props.onSelect?.(selected, selectedIndex)
96
+ } else if (event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) {
97
+ if (event.key === "n" || event.key === "p") {
98
+ event.preventDefault()
99
+ const navEvent = new KeyboardEvent("keydown", {
100
+ key: event.key === "n" ? "ArrowDown" : "ArrowUp",
101
+ bubbles: true,
102
+ })
103
+ list.onKeyDown(navEvent)
104
+ }
105
+ } else {
106
+ // Skip list navigation for text editing shortcuts (e.g., Option+Arrow, Option+Backspace on macOS)
107
+ if (event.altKey || event.metaKey) return
108
+ list.onKeyDown(event)
109
+ }
110
+ }
111
+
112
+ createEffect(
113
+ on(grouped, () => {
114
+ reset()
115
+ }),
116
+ )
117
+
118
+ const onInput = (value: string) => {
119
+ setStore("filter", value)
120
+ }
121
+
122
+ return {
123
+ grouped,
124
+ filter: () => store.filter,
125
+ flat,
126
+ reset,
127
+ refetch,
128
+ clear: () => setStore("filter", ""),
129
+ onKeyDown,
130
+ onInput,
131
+ active: list.active,
132
+ setActive: list.setActive,
133
+ }
134
+ }
package/src/i18n/ar.ts ADDED
@@ -0,0 +1,168 @@
1
+ export const dict = {
2
+ "ui.sessionReview.title": "تغييرات الجلسة",
3
+ "ui.sessionReview.title.lastTurn": "تغييرات آخر دور",
4
+ "ui.sessionReview.diffStyle.unified": "موحد",
5
+ "ui.sessionReview.diffStyle.split": "منقسم",
6
+ "ui.sessionReview.openFile": "فتح ملف",
7
+ "ui.sessionReview.selection.line": "سطر {{line}}",
8
+ "ui.sessionReview.selection.lines": "الأسطر {{start}}-{{end}}",
9
+ "ui.sessionReview.expandAll": "توسيع الكل",
10
+ "ui.sessionReview.collapseAll": "طي الكل",
11
+ "ui.sessionReview.change.added": "مضاف",
12
+ "ui.sessionReview.change.removed": "محذوف",
13
+ "ui.sessionReview.change.modified": "معدل",
14
+ "ui.sessionReview.image.loading": "جار التحميل...",
15
+ "ui.sessionReview.image.placeholder": "صورة",
16
+ "ui.sessionReview.largeDiff.title": "Diff كبير جدا لعرضه",
17
+ "ui.sessionReview.largeDiff.meta": "الحد: {{limit}} سطرًا متغيرًا. الحالي: {{current}} سطرًا متغيرًا.",
18
+ "ui.sessionReview.largeDiff.renderAnyway": "اعرض على أي حال",
19
+ "ui.fileMedia.kind.image": "صورة",
20
+ "ui.fileMedia.kind.audio": "صوت",
21
+ "ui.fileMedia.state.removed": "تمت إزالة {{kind}}",
22
+ "ui.fileMedia.state.loading": "جاري تحميل {{kind}}...",
23
+ "ui.fileMedia.state.error": "خطأ في تحميل {{kind}}",
24
+ "ui.fileMedia.state.unavailable": "{{kind}} غير متوفر",
25
+ "ui.fileMedia.binary.title": "ملف ثنائي",
26
+ "ui.fileMedia.binary.description.path": "{{path}} عبارة عن ملف ثنائي ولا يمكن عرضه.",
27
+ "ui.fileMedia.binary.description.default": "هذا ملف ثنائي ولا يمكن عرضه.",
28
+
29
+ "ui.lineComment.label.prefix": "تعليق على ",
30
+ "ui.lineComment.label.suffix": "",
31
+ "ui.lineComment.editorLabel.prefix": "جارٍ التعليق على ",
32
+ "ui.lineComment.editorLabel.suffix": "",
33
+ "ui.lineComment.placeholder": "أضف تعليقًا",
34
+ "ui.lineComment.submit": "تعليق",
35
+
36
+ "ui.sessionTurn.steps.show": "إظهار الخطوات",
37
+ "ui.sessionTurn.steps.hide": "إخفاء الخطوات",
38
+ "ui.sessionTurn.summary.response": "استجابة",
39
+ "ui.sessionTurn.diff.showMore": "إظهار المزيد من التغييرات ({{count}})",
40
+
41
+ "ui.sessionTurn.retry.retrying": "إعادة المحاولة",
42
+ "ui.sessionTurn.retry.inSeconds": "خلال {{seconds}} ثواني",
43
+ "ui.sessionTurn.retry.attempt": "المحاولة رقم {{attempt}}",
44
+ "ui.sessionTurn.retry.attemptLine": "{{line}} - المحاولة رقم {{attempt}}",
45
+ "ui.sessionTurn.retry.geminiHot": "gemini مزدحم حاليا",
46
+ "ui.sessionTurn.error.freeUsageExceeded": "تم تجاوز حد الاستخدام المجاني",
47
+ "ui.sessionTurn.error.addCredits": "إضافة رصيد",
48
+
49
+ "dialog.usageExceeded.freeTier.title": "تم الوصول إلى الحد المجاني",
50
+ "dialog.usageExceeded.freeTier.description":
51
+ "اشترك في OpenCode Go للحصول على وصول موثوق إلى أفضل النماذج مفتوحة المصدر، ابتداءً من $5/شهر.",
52
+ "dialog.usageExceeded.freeTier.actionLabel": "اشترك",
53
+ "dialog.usageExceeded.accountRateLimit.title": "تم الوصول إلى حد Go",
54
+ "dialog.usageExceeded.accountRateLimit.description":
55
+ "تم الوصول إلى حد الاستخدام. لمتابعة استخدام هذا النموذج الآن، قم بتفعيل الاستخدام من رصيدك المتاح",
56
+ "dialog.usageExceeded.accountRateLimit.actionLabel": "فتح الإعدادات",
57
+
58
+ "ui.sessionTurn.status.delegating": "تفويض العمل",
59
+ "ui.sessionTurn.status.planning": "تخطيط الخطوات التالية",
60
+ "ui.sessionTurn.status.gatheringContext": "استكشاف",
61
+ "ui.sessionTurn.status.gatheredContext": "تم الاستكشاف",
62
+ "ui.sessionTurn.status.searchingCodebase": "البحث في قاعدة التعليمات البرمجية",
63
+ "ui.sessionTurn.status.searchingWeb": "البحث في الويب",
64
+ "ui.sessionTurn.status.makingEdits": "إجراء تعديلات",
65
+ "ui.sessionTurn.status.runningCommands": "تشغيل الأوامر",
66
+ "ui.sessionTurn.status.thinking": "تفكير",
67
+ "ui.sessionTurn.status.thinkingWithTopic": "تفكير - {{topic}}",
68
+ "ui.sessionTurn.status.gatheringThoughts": "جمع الأفكار",
69
+ "ui.sessionTurn.status.consideringNextSteps": "النظر في الخطوات التالية",
70
+
71
+ "ui.messagePart.questions.dismissed": "تم رفض الأسئلة",
72
+ "ui.messagePart.compaction": "تم ضغط الجلسة",
73
+ "ui.messagePart.context.read.one": "{{count}} قراءة",
74
+ "ui.messagePart.context.read.other": "{{count}} قراءات",
75
+ "ui.messagePart.context.search.one": "{{count}} بحث",
76
+ "ui.messagePart.context.search.other": "{{count}} عمليات بحث",
77
+ "ui.messagePart.context.list.one": "{{count}} قائمة",
78
+ "ui.messagePart.context.list.other": "{{count}} قوائم",
79
+ "ui.messagePart.diagnostic.error": "خطأ",
80
+ "ui.messagePart.title.edit": "تحرير",
81
+ "ui.messagePart.title.write": "كتابة",
82
+ "ui.messagePart.option.typeOwnAnswer": "اكتب إجابتك الخاصة",
83
+ "ui.messagePart.review.title": "مراجعة إجاباتك",
84
+
85
+ "ui.list.loading": "جارٍ التحميل",
86
+ "ui.list.empty": "لا توجد نتائج",
87
+ "ui.list.clearFilter": "مسح عامل التصفية",
88
+ "ui.list.emptyWithFilter.prefix": "لا توجد نتائج لـ",
89
+ "ui.list.emptyWithFilter.suffix": "",
90
+
91
+ "ui.messageNav.newMessage": "رسالة جديدة",
92
+
93
+ "ui.textField.copyToClipboard": "نسخ إلى الحافظة",
94
+ "ui.textField.copyLink": "نسخ الرابط",
95
+ "ui.textField.copied": "تم النسخ",
96
+
97
+ "ui.imagePreview.alt": "معاينة الصورة",
98
+ "ui.scrollView.ariaLabel": "محتوى قابل للتمرير",
99
+
100
+ "ui.tool.read": "قراءة",
101
+ "ui.tool.loaded": "تم التحميل",
102
+ "ui.tool.list": "قائمة",
103
+ "ui.tool.glob": "Glob",
104
+ "ui.tool.grep": "Grep",
105
+ "ui.tool.webfetch": "جلب الويب",
106
+ "ui.tool.websearch": "بحث الويب",
107
+ "ui.tool.shell": "Shell",
108
+ "ui.tool.patch": "تصحيح",
109
+ "ui.tool.todos": "المهام",
110
+ "ui.tool.todos.read": "قراءة المهام",
111
+ "ui.tool.questions": "أسئلة",
112
+ "ui.tool.agent": "وكيل {{type}}",
113
+ "ui.tool.agent.default": "وكيل",
114
+
115
+ "ui.common.file.one": "ملف",
116
+ "ui.common.file.other": "ملفات",
117
+ "ui.common.question.one": "سؤال",
118
+ "ui.common.question.other": "أسئلة",
119
+
120
+ "ui.common.add": "إضافة",
121
+ "ui.common.back": "رجوع",
122
+ "ui.common.cancel": "إلغاء",
123
+ "ui.common.confirm": "تأكيد",
124
+ "ui.common.dismiss": "رفض",
125
+ "ui.common.close": "إغلاق",
126
+ "ui.common.next": "التالي",
127
+ "ui.common.submit": "إرسال",
128
+
129
+ "ui.permission.deny": "رفض",
130
+ "ui.permission.allowAlways": "السماح دائمًا",
131
+ "ui.permission.allowOnce": "السماح مرة واحدة",
132
+
133
+ "ui.message.expand": "توسيع الرسالة",
134
+ "ui.message.collapse": "طي الرسالة",
135
+ "ui.message.copy": "نسخ",
136
+ "ui.message.copyMessage": "نسخ الرسالة",
137
+ "ui.message.forkMessage": "تشعب إلى جلسة جديدة",
138
+ "ui.message.revertMessage": "إعادة التعيين إلى هذه النقطة",
139
+ "ui.message.copyResponse": "نسخ الرد",
140
+ "ui.message.copied": "تم النسخ!",
141
+ "ui.message.interrupted": "تمت المقاطعة",
142
+ "ui.message.queued": "في الانتظار",
143
+ "ui.message.attachment.alt": "مرفق",
144
+
145
+ "ui.patch.action.deleted": "محذوف",
146
+ "ui.patch.action.created": "تم الإنشاء",
147
+ "ui.patch.action.moved": "منقول",
148
+ "ui.patch.action.patched": "مصحح",
149
+
150
+ "ui.question.subtitle.answered": "{{count}} أجيب",
151
+ "ui.question.answer.none": "(لا توجد إجابة)",
152
+ "ui.question.review.notAnswered": "(لم يتم الرد)",
153
+ "ui.question.multiHint": "حدد كل ما ينطبق",
154
+ "ui.question.singleHint": "حدد إجابة واحدة",
155
+ "ui.question.custom.placeholder": "اكتب إجابتك...",
156
+
157
+ "ui.fileSearch.placeholder": "بحث",
158
+ "ui.fileSearch.previousMatch": "المطابقة السابقة",
159
+ "ui.fileSearch.nextMatch": "المطابقة التالية",
160
+ "ui.fileSearch.close": "إغلاق البحث",
161
+ "ui.tool.task": "مهمة",
162
+ "ui.tool.skill": "مهارة",
163
+ "ui.basicTool.called": "تم استدعاء `{{tool}}`",
164
+ "ui.toolErrorCard.failed": "فشل",
165
+ "ui.toolErrorCard.copyError": "نسخ الخطأ",
166
+ "ui.message.duration.seconds": "{{count}}ث",
167
+ "ui.message.duration.minutesSeconds": "{{minutes}}د {{seconds}}ث",
168
+ }