@opencode/ui 0.0.0-dev-20607 → 0.0.0-dev-20609

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 (68) hide show
  1. package/package.json +1 -1
  2. package/src/actions/button/button.tsx +1 -0
  3. package/src/actions/icon-button/icon-button.tsx +1 -0
  4. package/src/actions/split-button/split-button.tsx +3 -0
  5. package/src/components/animated-number.tsx +13 -0
  6. package/src/components/app-icon.tsx +2 -0
  7. package/src/components/card.tsx +21 -0
  8. package/src/components/collapsible.tsx +1 -0
  9. package/src/components/context-menu.tsx +34 -0
  10. package/src/components/dock-surface.tsx +3 -0
  11. package/src/components/file-icon.tsx +12 -0
  12. package/src/components/image-preview.tsx +1 -0
  13. package/src/components/list.tsx +32 -0
  14. package/src/components/motion-spring.tsx +5 -0
  15. package/src/components/popover.tsx +12 -0
  16. package/src/components/provider-icon.tsx +1 -0
  17. package/src/components/resize-handle.tsx +8 -0
  18. package/src/components/scroll-view.tsx +39 -0
  19. package/src/components/spinner.tsx +2 -0
  20. package/src/components/text-field.tsx +6 -0
  21. package/src/components/text-reveal.tsx +19 -0
  22. package/src/components/text-strikethrough.tsx +8 -0
  23. package/src/components/typewriter.tsx +4 -0
  24. package/src/context/dialog.tsx +12 -0
  25. package/src/context/file.tsx +1 -0
  26. package/src/context/helper.tsx +4 -0
  27. package/src/context/i18n.tsx +20 -0
  28. package/src/context/index.ts +3 -0
  29. package/src/context/marked-base.tsx +7 -0
  30. package/src/context/marked-parser.tsx +7 -0
  31. package/src/context/marked.tsx +3 -0
  32. package/src/context/worker-pool.tsx +2 -0
  33. package/src/data-display/avatar/avatar.tsx +4 -0
  34. package/src/data-display/badge/badge.tsx +1 -0
  35. package/src/data-display/diff-changes/diff-changes.tsx +2 -0
  36. package/src/data-display/keybind/keybind.tsx +1 -0
  37. package/src/data-display/line-comment/line-comment.tsx +22 -0
  38. package/src/data-display/project-avatar/project-avatar.tsx +10 -0
  39. package/src/feedback/loader/loader.tsx +1 -0
  40. package/src/feedback/toast/description.ts +1 -0
  41. package/src/forms/checkbox/checkbox.tsx +1 -0
  42. package/src/forms/field/field.tsx +8 -0
  43. package/src/forms/inline-input/inline-input.tsx +4 -0
  44. package/src/forms/radio/radio.tsx +2 -0
  45. package/src/forms/select/select.tsx +11 -0
  46. package/src/forms/switch/switch.tsx +1 -0
  47. package/src/forms/text-input/text-input.tsx +3 -0
  48. package/src/hooks/create-auto-scroll.tsx +34 -0
  49. package/src/hooks/index.ts +1 -0
  50. package/src/hooks/use-filtered-list.tsx +14 -0
  51. package/src/i18n/no.ts +1 -0
  52. package/src/layout/divider/divider.tsx +1 -0
  53. package/src/navigation/menu/menu.tsx +13 -0
  54. package/src/navigation/segmented-control/segmented-control.tsx +16 -0
  55. package/src/navigation/tab-state-indicator.tsx +1 -0
  56. package/src/navigation/tabs/tabs.tsx +18 -0
  57. package/src/overlays/dialog/dialog.tsx +3 -0
  58. package/src/overlays/tooltip/tooltip.tsx +15 -0
  59. package/src/storybook/scaffold.tsx +3 -0
  60. package/src/theme/color.ts +27 -0
  61. package/src/theme/context.tsx +58 -0
  62. package/src/theme/default-themes.ts +35 -0
  63. package/src/theme/index.ts +3 -0
  64. package/src/theme/loader.ts +11 -0
  65. package/src/theme/resolve.ts +28 -0
  66. package/src/theme/v2/foreground.ts +8 -0
  67. package/src/theme/v2/resolve.ts +10 -0
  68. package/src/typography/text-shimmer/text-shimmer.tsx +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencode/ui",
3
- "version": "0.0.0-dev-20607",
3
+ "version": "0.0.0-dev-20609",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,6 +23,7 @@ export interface ButtonProps
23
23
 
24
24
  export function Button(props: ButtonProps) {
25
25
  const [split, rest] = splitProps(props, ["variant", "size", "icon", "class", "classList"])
26
+
26
27
  return (
27
28
  <Root
28
29
  {...rest}
@@ -14,6 +14,7 @@ export interface IconButtonProps
14
14
 
15
15
  export function IconButton(props: ComponentProps<"button"> & IconButtonProps) {
16
16
  const [local, rest] = splitProps(props, ["variant", "size", "icon", "class", "classList", "state"])
17
+
17
18
  return (
18
19
  <Root
19
20
  {...rest}
@@ -3,6 +3,7 @@ import "./split-button.css"
3
3
 
4
4
  export function SplitButton(props: ParentProps<ComponentProps<"div">>) {
5
5
  const [split, rest] = splitProps(props, ["class", "classList", "children"])
6
+
6
7
  return (
7
8
  <div
8
9
  data-component="split-button-v2"
@@ -19,6 +20,7 @@ export function SplitButton(props: ParentProps<ComponentProps<"div">>) {
19
20
 
20
21
  export function SplitButtonAction(props: ComponentProps<"button">) {
21
22
  const [split, rest] = splitProps(props, ["class", "classList"])
23
+
22
24
  return (
23
25
  <button
24
26
  type="button"
@@ -34,6 +36,7 @@ export function SplitButtonAction(props: ComponentProps<"button">) {
34
36
 
35
37
  export function SplitButtonMenuTrigger(props: ComponentProps<"button">) {
36
38
  const [split, rest] = splitProps(props, ["class", "classList"])
39
+
37
40
  return (
38
41
  <button
39
42
  type="button"
@@ -2,6 +2,7 @@ import { For, Index, createEffect, createMemo, on } from "solid-js"
2
2
  import { createStore } from "solid-js/store"
3
3
 
4
4
  const TRACK = Array.from({ length: 30 }, (_, index) => index % 10)
5
+
5
6
  const DURATION = 600
6
7
 
7
8
  function normalize(value: number) {
@@ -10,7 +11,9 @@ function normalize(value: number) {
10
11
 
11
12
  function spin(from: number, to: number, direction: 1 | -1) {
12
13
  if (from === to) return 0
14
+
13
15
  if (direction > 0) return (to - from + 10) % 10
16
+
14
17
  return -((from - to + 10) % 10)
15
18
  }
16
19
 
@@ -19,6 +22,7 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
19
22
  step: props.value + 10,
20
23
  animating: false,
21
24
  })
25
+
22
26
  const step = () => state.step
23
27
  const animating = () => state.animating
24
28
  let last = props.value
@@ -29,9 +33,11 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
29
33
  (next) => {
30
34
  const delta = spin(last, next, props.direction)
31
35
  last = next
36
+
32
37
  if (!delta) {
33
38
  setState("animating", false)
34
39
  setState("step", next + 10)
40
+
35
41
  return
36
42
  }
37
43
 
@@ -65,6 +71,7 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
65
71
  export function AnimatedNumber(props: { value: number; class?: string }) {
66
72
  const target = createMemo(() => {
67
73
  if (!Number.isFinite(props.value)) return 0
74
+
68
75
  return Math.max(0, Math.round(props.value))
69
76
  })
70
77
 
@@ -72,6 +79,7 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
72
79
  value: target(),
73
80
  direction: 1 as 1 | -1,
74
81
  })
82
+
75
83
  const value = () => state.value
76
84
  const direction = () => state.direction
77
85
 
@@ -80,6 +88,7 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
80
88
  target,
81
89
  (next) => {
82
90
  const current = value()
91
+
83
92
  if (next === current) return
84
93
 
85
94
  setState("direction", next > current ? 1 : -1)
@@ -90,13 +99,17 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
90
99
  )
91
100
 
92
101
  const label = createMemo(() => value().toString())
102
+
93
103
  const digits = createMemo(() =>
94
104
  Array.from(label(), (char) => {
95
105
  const code = char.charCodeAt(0) - 48
106
+
96
107
  if (code < 0 || code > 9) return 0
108
+
97
109
  return code
98
110
  }).reverse(),
99
111
  )
112
+
100
113
  const width = createMemo(() => `${digits().length}ch`)
101
114
 
102
115
  return (
@@ -46,7 +46,9 @@ const themed: Partial<Record<IconName, { light: string; dark: string }>> = {
46
46
 
47
47
  const scheme = () => {
48
48
  if (typeof document !== "object") return "light" as const
49
+
49
50
  if (document.documentElement.dataset.colorScheme === "dark") return "dark" as const
51
+
50
52
  return "light" as const
51
53
  }
52
54
 
@@ -22,30 +22,44 @@ export interface CardTitleProps extends ComponentProps<"div"> {
22
22
 
23
23
  function pick(variant: Variant) {
24
24
  if (variant === "error") return "circle-ban-sign" as const
25
+
25
26
  if (variant === "warning") return "warning" as const
27
+
26
28
  if (variant === "success") return "circle-check" as const
29
+
27
30
  if (variant === "info") return "help" as const
31
+
28
32
  return
29
33
  }
30
34
 
31
35
  function mix(style: ComponentProps<"div">["style"], value?: string) {
32
36
  if (!value) return style
37
+
33
38
  if (!style) return { "--card-accent": value }
39
+
34
40
  if (typeof style === "string") return `${style};--card-accent:${value};`
41
+
35
42
  return { ...(style as Record<string, string | number>), "--card-accent": value }
36
43
  }
37
44
 
38
45
  export function Card(props: CardProps) {
39
46
  const [split, rest] = splitProps(props, ["variant", "style", "class", "classList"])
40
47
  const variant = () => split.variant ?? "normal"
48
+
41
49
  const accent = () => {
42
50
  const v = variant()
51
+
43
52
  if (v === "error") return "var(--v2-state-fg-danger)"
53
+
44
54
  if (v === "warning") return "var(--icon-warning-active)"
55
+
45
56
  if (v === "success") return "var(--icon-success-active)"
57
+
46
58
  if (v === "info") return "var(--icon-info-active)"
59
+
47
60
  return
48
61
  }
62
+
49
63
  return (
50
64
  <div
51
65
  {...rest}
@@ -65,12 +79,17 @@ export function Card(props: CardProps) {
65
79
  export function CardTitle(props: CardTitleProps) {
66
80
  const [split, rest] = splitProps(props, ["variant", "icon", "class", "classList", "children"])
67
81
  const show = () => split.icon !== false && split.icon !== null
82
+
68
83
  const name = () => {
69
84
  if (split.icon === false || split.icon === null) return
85
+
70
86
  if (typeof split.icon === "string") return split.icon
87
+
71
88
  return pick(split.variant ?? "normal")
72
89
  }
90
+
73
91
  const placeholder = () => !name()
92
+
74
93
  return (
75
94
  <div
76
95
  {...rest}
@@ -92,6 +111,7 @@ export function CardTitle(props: CardTitleProps) {
92
111
 
93
112
  export function CardDescription(props: ComponentProps<"div">) {
94
113
  const [split, rest] = splitProps(props, ["class", "classList", "children"])
114
+
95
115
  return (
96
116
  <div
97
117
  {...rest}
@@ -108,6 +128,7 @@ export function CardDescription(props: ComponentProps<"div">) {
108
128
 
109
129
  export function CardActions(props: ComponentProps<"div">) {
110
130
  const [split, rest] = splitProps(props, ["class", "classList", "children"])
131
+
111
132
  return (
112
133
  <div
113
134
  {...rest}
@@ -10,6 +10,7 @@ export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
10
10
 
11
11
  function CollapsibleRoot(props: CollapsibleProps) {
12
12
  const [local, others] = splitProps(props, ["class", "classList", "variant"])
13
+
13
14
  return (
14
15
  <Kobalte
15
16
  data-component="collapsible"
@@ -3,23 +3,41 @@ import { splitProps } from "solid-js"
3
3
  import type { ComponentProps, ParentProps } from "solid-js"
4
4
 
5
5
  export interface ContextMenuProps extends ComponentProps<typeof Kobalte> {}
6
+
6
7
  export interface ContextMenuTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
8
+
7
9
  export interface ContextMenuIconProps extends ComponentProps<typeof Kobalte.Icon> {}
10
+
8
11
  export interface ContextMenuPortalProps extends ComponentProps<typeof Kobalte.Portal> {}
12
+
9
13
  export interface ContextMenuContentProps extends ComponentProps<typeof Kobalte.Content> {}
14
+
10
15
  export interface ContextMenuArrowProps extends ComponentProps<typeof Kobalte.Arrow> {}
16
+
11
17
  export interface ContextMenuSeparatorProps extends ComponentProps<typeof Kobalte.Separator> {}
18
+
12
19
  export interface ContextMenuGroupProps extends ComponentProps<typeof Kobalte.Group> {}
20
+
13
21
  export interface ContextMenuGroupLabelProps extends ComponentProps<typeof Kobalte.GroupLabel> {}
22
+
14
23
  export interface ContextMenuItemProps extends ComponentProps<typeof Kobalte.Item> {}
24
+
15
25
  export interface ContextMenuItemLabelProps extends ComponentProps<typeof Kobalte.ItemLabel> {}
26
+
16
27
  export interface ContextMenuItemDescriptionProps extends ComponentProps<typeof Kobalte.ItemDescription> {}
28
+
17
29
  export interface ContextMenuItemIndicatorProps extends ComponentProps<typeof Kobalte.ItemIndicator> {}
30
+
18
31
  export interface ContextMenuRadioGroupProps extends ComponentProps<typeof Kobalte.RadioGroup> {}
32
+
19
33
  export interface ContextMenuRadioItemProps extends ComponentProps<typeof Kobalte.RadioItem> {}
34
+
20
35
  export interface ContextMenuCheckboxItemProps extends ComponentProps<typeof Kobalte.CheckboxItem> {}
36
+
21
37
  export interface ContextMenuSubProps extends ComponentProps<typeof Kobalte.Sub> {}
38
+
22
39
  export interface ContextMenuSubTriggerProps extends ComponentProps<typeof Kobalte.SubTrigger> {}
40
+
23
41
  export interface ContextMenuSubContentProps extends ComponentProps<typeof Kobalte.SubContent> {}
24
42
 
25
43
  function ContextMenuRoot(props: ContextMenuProps) {
@@ -28,6 +46,7 @@ function ContextMenuRoot(props: ContextMenuProps) {
28
46
 
29
47
  function ContextMenuTrigger(props: ParentProps<ContextMenuTriggerProps>) {
30
48
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
49
+
31
50
  return (
32
51
  <Kobalte.Trigger
33
52
  {...rest}
@@ -44,6 +63,7 @@ function ContextMenuTrigger(props: ParentProps<ContextMenuTriggerProps>) {
44
63
 
45
64
  function ContextMenuIcon(props: ParentProps<ContextMenuIconProps>) {
46
65
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
66
+
47
67
  return (
48
68
  <Kobalte.Icon
49
69
  {...rest}
@@ -64,6 +84,7 @@ function ContextMenuPortal(props: ContextMenuPortalProps) {
64
84
 
65
85
  function ContextMenuContent(props: ParentProps<ContextMenuContentProps>) {
66
86
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
87
+
67
88
  return (
68
89
  <Kobalte.Content
69
90
  {...rest}
@@ -80,6 +101,7 @@ function ContextMenuContent(props: ParentProps<ContextMenuContentProps>) {
80
101
 
81
102
  function ContextMenuArrow(props: ContextMenuArrowProps) {
82
103
  const [local, rest] = splitProps(props, ["class", "classList"])
104
+
83
105
  return (
84
106
  <Kobalte.Arrow
85
107
  {...rest}
@@ -94,6 +116,7 @@ function ContextMenuArrow(props: ContextMenuArrowProps) {
94
116
 
95
117
  function ContextMenuSeparator(props: ContextMenuSeparatorProps) {
96
118
  const [local, rest] = splitProps(props, ["class", "classList"])
119
+
97
120
  return (
98
121
  <Kobalte.Separator
99
122
  {...rest}
@@ -108,6 +131,7 @@ function ContextMenuSeparator(props: ContextMenuSeparatorProps) {
108
131
 
109
132
  function ContextMenuGroup(props: ParentProps<ContextMenuGroupProps>) {
110
133
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
134
+
111
135
  return (
112
136
  <Kobalte.Group
113
137
  {...rest}
@@ -124,6 +148,7 @@ function ContextMenuGroup(props: ParentProps<ContextMenuGroupProps>) {
124
148
 
125
149
  function ContextMenuGroupLabel(props: ParentProps<ContextMenuGroupLabelProps>) {
126
150
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
151
+
127
152
  return (
128
153
  <Kobalte.GroupLabel
129
154
  {...rest}
@@ -140,6 +165,7 @@ function ContextMenuGroupLabel(props: ParentProps<ContextMenuGroupLabelProps>) {
140
165
 
141
166
  function ContextMenuItem(props: ParentProps<ContextMenuItemProps>) {
142
167
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
168
+
143
169
  return (
144
170
  <Kobalte.Item
145
171
  {...rest}
@@ -156,6 +182,7 @@ function ContextMenuItem(props: ParentProps<ContextMenuItemProps>) {
156
182
 
157
183
  function ContextMenuItemLabel(props: ParentProps<ContextMenuItemLabelProps>) {
158
184
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
185
+
159
186
  return (
160
187
  <Kobalte.ItemLabel
161
188
  {...rest}
@@ -172,6 +199,7 @@ function ContextMenuItemLabel(props: ParentProps<ContextMenuItemLabelProps>) {
172
199
 
173
200
  function ContextMenuItemDescription(props: ParentProps<ContextMenuItemDescriptionProps>) {
174
201
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
202
+
175
203
  return (
176
204
  <Kobalte.ItemDescription
177
205
  {...rest}
@@ -188,6 +216,7 @@ function ContextMenuItemDescription(props: ParentProps<ContextMenuItemDescriptio
188
216
 
189
217
  function ContextMenuItemIndicator(props: ParentProps<ContextMenuItemIndicatorProps>) {
190
218
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
219
+
191
220
  return (
192
221
  <Kobalte.ItemIndicator
193
222
  {...rest}
@@ -204,6 +233,7 @@ function ContextMenuItemIndicator(props: ParentProps<ContextMenuItemIndicatorPro
204
233
 
205
234
  function ContextMenuRadioGroup(props: ParentProps<ContextMenuRadioGroupProps>) {
206
235
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
236
+
207
237
  return (
208
238
  <Kobalte.RadioGroup
209
239
  {...rest}
@@ -220,6 +250,7 @@ function ContextMenuRadioGroup(props: ParentProps<ContextMenuRadioGroupProps>) {
220
250
 
221
251
  function ContextMenuRadioItem(props: ParentProps<ContextMenuRadioItemProps>) {
222
252
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
253
+
223
254
  return (
224
255
  <Kobalte.RadioItem
225
256
  {...rest}
@@ -236,6 +267,7 @@ function ContextMenuRadioItem(props: ParentProps<ContextMenuRadioItemProps>) {
236
267
 
237
268
  function ContextMenuCheckboxItem(props: ParentProps<ContextMenuCheckboxItemProps>) {
238
269
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
270
+
239
271
  return (
240
272
  <Kobalte.CheckboxItem
241
273
  {...rest}
@@ -256,6 +288,7 @@ function ContextMenuSub(props: ContextMenuSubProps) {
256
288
 
257
289
  function ContextMenuSubTrigger(props: ParentProps<ContextMenuSubTriggerProps>) {
258
290
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
291
+
259
292
  return (
260
293
  <Kobalte.SubTrigger
261
294
  {...rest}
@@ -272,6 +305,7 @@ function ContextMenuSubTrigger(props: ParentProps<ContextMenuSubTriggerProps>) {
272
305
 
273
306
  function ContextMenuSubContent(props: ParentProps<ContextMenuSubContentProps>) {
274
307
  const [local, rest] = splitProps(props, ["class", "classList", "children"])
308
+
275
309
  return (
276
310
  <Kobalte.SubContent
277
311
  {...rest}
@@ -6,6 +6,7 @@ export interface DockTrayProps extends ComponentProps<"div"> {
6
6
 
7
7
  export function DockShell(props: ComponentProps<"div">) {
8
8
  const [split, rest] = splitProps(props, ["children", "class", "classList"])
9
+
9
10
  return (
10
11
  <div
11
12
  {...rest}
@@ -22,6 +23,7 @@ export function DockShell(props: ComponentProps<"div">) {
22
23
 
23
24
  export function DockShellForm(props: ComponentProps<"form">) {
24
25
  const [split, rest] = splitProps(props, ["children", "class", "classList"])
26
+
25
27
  return (
26
28
  <form
27
29
  {...rest}
@@ -38,6 +40,7 @@ export function DockShellForm(props: ComponentProps<"form">) {
38
40
 
39
41
  export function DockTray(props: DockTrayProps) {
40
42
  const [split, rest] = splitProps(props, ["attach", "children", "class", "classList"])
43
+
41
44
  return (
42
45
  <div
43
46
  {...rest}
@@ -13,6 +13,7 @@ export const FileIcon: Component<FileIconProps> = (props) => {
13
13
  const [local, rest] = splitProps(props, ["node", "class", "classList", "expanded", "mono"])
14
14
  const name = createMemo(() => chooseIconName(local.node.path, local.node.type, local.expanded || false))
15
15
  const id = `file-icon-mono-${createUniqueId()}`
16
+
16
17
  return (
17
18
  <svg
18
19
  data-component="file-icon"
@@ -542,8 +543,11 @@ const ICON_MAPS: IconMaps = {
542
543
 
543
544
  const toOpenVariant = (icon: IconName): IconName => {
544
545
  if (!icon.startsWith("Folder")) return icon
546
+
545
547
  if (icon.endsWith("_light")) return icon.replace("_light", "Open_light") as IconName
548
+
546
549
  if (!icon.endsWith("Open")) return (icon + "Open") as IconName
550
+
547
551
  return icon
548
552
  }
549
553
 
@@ -551,16 +555,20 @@ const basenameOf = (p: string) => p.split("\\").join("/").split("/").filter(Bool
551
555
 
552
556
  const folderNameVariants = (name: string) => {
553
557
  const n = name.toLowerCase()
558
+
554
559
  return [n, `.${n}`, `_${n}`, `__${n}__`]
555
560
  }
556
561
 
557
562
  const dottedSuffixesDesc = (name: string) => {
558
563
  const n = name.toLowerCase()
559
564
  const idxs: number[] = []
565
+
560
566
  for (let i = 0; i < n.length; i++) if (n[i] === ".") idxs.push(i)
561
567
  const out = new Set<string>()
562
568
  out.add(n) // allow exact whole-name "extensions" like "dockerfile"
569
+
563
570
  for (const i of idxs) if (i + 1 < n.length) out.add(n.slice(i + 1))
571
+
564
572
  return Array.from(out).sort((a, b) => b.length - a.length) // longest first
565
573
  }
566
574
 
@@ -571,16 +579,20 @@ export function chooseIconName(path: string, type: "directory" | "file", expande
571
579
  if (type === "directory") {
572
580
  for (const cand of folderNameVariants(baseLower)) {
573
581
  const icon = ICON_MAPS.folderNames[cand]
582
+
574
583
  if (icon) return expanded ? toOpenVariant(icon) : icon
575
584
  }
585
+
576
586
  return expanded ? ICON_MAPS.defaults.folderOpen : ICON_MAPS.defaults.folder
577
587
  }
578
588
 
579
589
  const byName = ICON_MAPS.fileNames[baseLower]
590
+
580
591
  if (byName) return byName
581
592
 
582
593
  for (const ext of dottedSuffixesDesc(baseLower)) {
583
594
  const icon = ICON_MAPS.fileExtensions[ext]
595
+
584
596
  if (icon) return icon
585
597
  }
586
598
 
@@ -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>