@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.
- package/package.json +1 -1
- package/src/actions/button/button.tsx +1 -0
- package/src/actions/icon-button/icon-button.tsx +1 -0
- package/src/actions/split-button/split-button.tsx +3 -0
- package/src/components/animated-number.tsx +13 -0
- package/src/components/app-icon.tsx +2 -0
- package/src/components/card.tsx +21 -0
- package/src/components/collapsible.tsx +1 -0
- package/src/components/context-menu.tsx +34 -0
- package/src/components/dock-surface.tsx +3 -0
- package/src/components/file-icon.tsx +12 -0
- package/src/components/image-preview.tsx +1 -0
- package/src/components/list.tsx +32 -0
- package/src/components/motion-spring.tsx +5 -0
- package/src/components/popover.tsx +12 -0
- package/src/components/provider-icon.tsx +1 -0
- package/src/components/resize-handle.tsx +8 -0
- package/src/components/scroll-view.tsx +39 -0
- package/src/components/spinner.tsx +2 -0
- package/src/components/text-field.tsx +6 -0
- package/src/components/text-reveal.tsx +19 -0
- package/src/components/text-strikethrough.tsx +8 -0
- package/src/components/typewriter.tsx +4 -0
- package/src/context/dialog.tsx +12 -0
- package/src/context/file.tsx +1 -0
- package/src/context/helper.tsx +4 -0
- package/src/context/i18n.tsx +20 -0
- package/src/context/index.ts +3 -0
- package/src/context/marked-base.tsx +7 -0
- package/src/context/marked-parser.tsx +7 -0
- package/src/context/marked.tsx +3 -0
- package/src/context/worker-pool.tsx +2 -0
- package/src/data-display/avatar/avatar.tsx +4 -0
- package/src/data-display/badge/badge.tsx +1 -0
- package/src/data-display/diff-changes/diff-changes.tsx +2 -0
- package/src/data-display/keybind/keybind.tsx +1 -0
- package/src/data-display/line-comment/line-comment.tsx +22 -0
- package/src/data-display/project-avatar/project-avatar.tsx +10 -0
- package/src/feedback/loader/loader.tsx +1 -0
- package/src/feedback/toast/description.ts +1 -0
- package/src/forms/checkbox/checkbox.tsx +1 -0
- package/src/forms/field/field.tsx +8 -0
- package/src/forms/inline-input/inline-input.tsx +4 -0
- package/src/forms/radio/radio.tsx +2 -0
- package/src/forms/select/select.tsx +11 -0
- package/src/forms/switch/switch.tsx +1 -0
- package/src/forms/text-input/text-input.tsx +3 -0
- package/src/hooks/create-auto-scroll.tsx +34 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-filtered-list.tsx +14 -0
- package/src/i18n/no.ts +1 -0
- package/src/layout/divider/divider.tsx +1 -0
- package/src/navigation/menu/menu.tsx +13 -0
- package/src/navigation/segmented-control/segmented-control.tsx +16 -0
- package/src/navigation/tab-state-indicator.tsx +1 -0
- package/src/navigation/tabs/tabs.tsx +18 -0
- package/src/overlays/dialog/dialog.tsx +3 -0
- package/src/overlays/tooltip/tooltip.tsx +15 -0
- package/src/storybook/scaffold.tsx +3 -0
- package/src/theme/color.ts +27 -0
- package/src/theme/context.tsx +58 -0
- package/src/theme/default-themes.ts +35 -0
- package/src/theme/index.ts +3 -0
- package/src/theme/loader.ts +11 -0
- package/src/theme/resolve.ts +28 -0
- package/src/theme/v2/foreground.ts +8 -0
- package/src/theme/v2/resolve.ts +10 -0
- 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
|
>
|
|
@@ -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
|
|
package/src/context/dialog.tsx
CHANGED
|
@@ -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)
|
package/src/context/file.tsx
CHANGED
package/src/context/helper.tsx
CHANGED
|
@@ -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
|
}
|