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

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
@@ -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))
@@ -21,15 +21,18 @@ export function TextStrikethrough(props: {
21
21
 
22
22
  let baseRef: HTMLSpanElement | undefined
23
23
  let containerRef: HTMLSpanElement | undefined
24
+
24
25
  const [state, setState] = createStore({
25
26
  textWidth: 0,
26
27
  containerWidth: 0,
27
28
  })
29
+
28
30
  const textWidth = () => state.textWidth
29
31
  const containerWidth = () => state.containerWidth
30
32
 
31
33
  const measure = () => {
32
34
  if (baseRef) setState("textWidth", baseRef.scrollWidth)
35
+
33
36
  if (containerRef) setState("containerWidth", containerRef.offsetWidth)
34
37
  }
35
38
 
@@ -39,6 +42,7 @@ export function TextStrikethrough(props: {
39
42
  // Revealed pixels from left = progress * textWidth
40
43
  const revealedPx = () => {
41
44
  const tw = textWidth()
45
+
42
46
  return tw > 0 ? progress() * tw : 0
43
47
  }
44
48
 
@@ -46,15 +50,19 @@ export function TextStrikethrough(props: {
46
50
  const overlayClip = () => {
47
51
  const cw = containerWidth()
48
52
  const tw = textWidth()
53
+
49
54
  if (cw <= 0 || tw <= 0) return `inset(0 ${(1 - progress()) * 100}% 0 0)`
50
55
  const remaining = Math.max(0, cw - revealedPx())
56
+
51
57
  return `inset(0 ${remaining}px 0 0)`
52
58
  }
53
59
 
54
60
  // Base clip: hide everything to the left of revealed area (complementary)
55
61
  const baseClip = () => {
56
62
  const px = revealedPx()
63
+
57
64
  if (px <= 0.5) return "none"
65
+
58
66
  return `inset(0 0 0 ${px}px)`
59
67
  }
60
68
 
@@ -11,6 +11,7 @@ export const Typewriter = <T extends ValidComponent = "p">(props: { text?: strin
11
11
 
12
12
  createEffect(() => {
13
13
  const text = props.text
14
+
14
15
  if (!text) return
15
16
 
16
17
  let i = 0
@@ -21,8 +22,11 @@ export const Typewriter = <T extends ValidComponent = "p">(props: { text?: strin
21
22
 
22
23
  const getTypingDelay = () => {
23
24
  const random = Math.random()
25
+
24
26
  if (random < 0.05) return 150 + Math.random() * 100
27
+
25
28
  if (random < 0.15) return 80 + Math.random() * 60
29
+
26
30
  return 30 + Math.random() * 50
27
31
  }
28
32
 
@@ -28,6 +28,7 @@ type Active = {
28
28
  }
29
29
 
30
30
  const Context = createContext<ReturnType<typeof init>>()
31
+
31
32
  // Lets the dialog rendered in a layer opt out of closing on a backdrop click.
32
33
  const LayerContext = createContext<{ setBackdropDismiss: (value: boolean) => void }>()
33
34
 
@@ -70,6 +71,7 @@ function init() {
70
71
  current.id,
71
72
  setTimeout(() => {
72
73
  closing.delete(current.id)
74
+
73
75
  if (closing.size === 0) lock.value = false
74
76
  setExiting((ids) => new Set([...ids].filter((id) => id !== current.id)))
75
77
  setStack((items) => items.filter((item) => item.id !== current.id))
@@ -84,6 +86,7 @@ function init() {
84
86
  /** Programmatic close. Without an id it closes the top dialog, one at a time; with an id it never waits. */
85
87
  const close = (id?: string) => {
86
88
  const current = id ? stack().find((item) => item.id === id) : stack().at(-1)
89
+
87
90
  if (!current || closing.has(current.id) || (!id && lock.value)) return
88
91
  closing.set(current.id, undefined)
89
92
  lock.value = true
@@ -93,6 +96,7 @@ function init() {
93
96
  /** Escape, a backdrop click, or Kobalte dismissing: only the top dialog, and one per exit animation. */
94
97
  const dismiss = (id?: string) => {
95
98
  const current = stack().at(-1)
99
+
96
100
  if (!current || (id && current.id !== id) || closing.has(current.id) || lock.value) return
97
101
  closing.set(current.id, undefined)
98
102
  lock.value = true
@@ -116,12 +120,14 @@ function init() {
116
120
  // A deferred open (e.g. from a focus callback) must not mount after the provider is gone.
117
121
  if (state.disposed) return
118
122
  const id = key ?? Math.random().toString(36).slice(2)
123
+
119
124
  // The layer follows the dialog's current place in the stack, so a new top dialog always renders above.
120
125
  const layer = () =>
121
126
  Math.max(
122
127
  0,
123
128
  stack().findIndex((item) => item.id === id),
124
129
  )
130
+
125
131
  const zIndex = () => String(50 + layer() * 10)
126
132
  let dispose: (() => void) | undefined
127
133
  let setClosing: ((closing: boolean) => void) | undefined
@@ -133,6 +139,7 @@ function init() {
133
139
  const [closing, setClosingSignal] = createSignal(false)
134
140
  const [backdropDismiss, setBackdropDismiss] = createSignal(true)
135
141
  setClosing = setClosingSignal
142
+
136
143
  return (
137
144
  <Kobalte
138
145
  modal={stack().findLast((item) => !exiting().has(item.id))?.id === id}
@@ -193,6 +200,7 @@ function init() {
193
200
  push,
194
201
  }
195
202
  }
203
+
196
204
  /** Runs a dialog callback so its throw is reported without stopping the caller's cleanup. */
197
205
  function isolate(fn: () => void) {
198
206
  try {
@@ -204,6 +212,7 @@ function isolate(fn: () => void) {
204
212
 
205
213
  export function DialogProvider(props: ParentProps) {
206
214
  const ctx = init()
215
+
207
216
  return (
208
217
  <Context.Provider value={ctx}>
209
218
  {props.children}
@@ -221,6 +230,7 @@ export function useDialog() {
221
230
  if (!owner) {
222
231
  throw new Error("useDialog must be used within a DialogProvider")
223
232
  }
233
+
224
234
  if (!ctx) {
225
235
  throw new Error("useDialog must be used within a DialogProvider")
226
236
  }
@@ -235,6 +245,7 @@ export function useDialog() {
235
245
  */
236
246
  show(element: DialogElement, onClose?: () => void, id?: string, signal?: AbortSignal) {
237
247
  const base = ctx.stack().at(-1)?.owner ?? owner
248
+
238
249
  return startTransition(() => {
239
250
  if (signal?.aborted) return isolate(() => onClose?.())
240
251
  ctx.show(element, base, onClose, id)
@@ -242,6 +253,7 @@ export function useDialog() {
242
253
  },
243
254
  push(element: DialogElement, onClose?: () => void, id?: string, signal?: AbortSignal) {
244
255
  const base = ctx.stack().at(-1)?.owner ?? owner
256
+
245
257
  return startTransition(() => {
246
258
  if (signal?.aborted) return isolate(() => onClose?.())
247
259
  ctx.push(element, base, onClose, id)
@@ -7,4 +7,5 @@ const ctx = createSimpleContext<ValidComponent, { component: ValidComponent }>({
7
7
  })
8
8
 
9
9
  export const FileComponentProvider = ctx.provider
10
+
10
11
  export const useFileComponent = ctx.use
@@ -21,8 +21,10 @@ export function createSimpleContext<T, Props extends Record<string, any>>(
21
21
  const isReady = createMemo(() => {
22
22
  // @ts-expect-error
23
23
  const ready = init.ready as Accessor<boolean> | boolean | undefined
24
+
24
25
  return ready === undefined || (typeof ready === "function" ? ready() : ready)
25
26
  })
27
+
26
28
  return (
27
29
  <Show when={isReady()}>
28
30
  <ctx.Provider value={init}>{props.children}</ctx.Provider>
@@ -31,7 +33,9 @@ export function createSimpleContext<T, Props extends Record<string, any>>(
31
33
  },
32
34
  use() {
33
35
  const value = useContext(ctx)
36
+
34
37
  if (!value) throw new Error(`${input.name} context must be used within a context provider`)
38
+
35
39
  return value
36
40
  },
37
41
  }