mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-29 21:33:54 +00:00
chore: cleanup (#17284)
This commit is contained in:
parent
46ba9c8170
commit
270cb0b8b4
@ -86,15 +86,17 @@ const useServerHealth = (servers: Accessor<ServerConnection.Any[]>) => {
|
||||
const useDefaultServerKey = (
|
||||
get: (() => string | Promise<string | null | undefined> | null | undefined) | undefined,
|
||||
) => {
|
||||
const [url, setUrl] = createSignal<string | undefined>()
|
||||
const [tick, setTick] = createSignal(0)
|
||||
const [state, setState] = createStore({
|
||||
url: undefined as string | undefined,
|
||||
tick: 0,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
tick()
|
||||
state.tick
|
||||
let dead = false
|
||||
const result = get?.()
|
||||
if (!result) {
|
||||
setUrl(undefined)
|
||||
setState("url", undefined)
|
||||
onCleanup(() => {
|
||||
dead = true
|
||||
})
|
||||
@ -104,7 +106,7 @@ const useDefaultServerKey = (
|
||||
if (result instanceof Promise) {
|
||||
void result.then((next) => {
|
||||
if (dead) return
|
||||
setUrl(next ? normalizeServerUrl(next) : undefined)
|
||||
setState("url", next ? normalizeServerUrl(next) : undefined)
|
||||
})
|
||||
onCleanup(() => {
|
||||
dead = true
|
||||
@ -112,7 +114,7 @@ const useDefaultServerKey = (
|
||||
return
|
||||
}
|
||||
|
||||
setUrl(normalizeServerUrl(result))
|
||||
setState("url", normalizeServerUrl(result))
|
||||
onCleanup(() => {
|
||||
dead = true
|
||||
})
|
||||
@ -120,11 +122,11 @@ const useDefaultServerKey = (
|
||||
|
||||
return {
|
||||
key: () => {
|
||||
const u = url()
|
||||
const u = state.url
|
||||
if (!u) return
|
||||
return ServerConnection.key({ type: "http", http: { url: u } })
|
||||
},
|
||||
refresh: () => setTick((value) => value + 1),
|
||||
refresh: () => setState("tick", (value) => value + 1),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,16 +1,4 @@
|
||||
import {
|
||||
batch,
|
||||
createEffect,
|
||||
createMemo,
|
||||
createSignal,
|
||||
For,
|
||||
on,
|
||||
onCleanup,
|
||||
onMount,
|
||||
ParentProps,
|
||||
Show,
|
||||
untrack,
|
||||
} from "solid-js"
|
||||
import { batch, createEffect, createMemo, For, on, onCleanup, onMount, ParentProps, Show, untrack } from "solid-js"
|
||||
import { useNavigate, useParams } from "@solidjs/router"
|
||||
import { useLayout, LocalProject } from "@/context/layout"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
@ -145,6 +133,10 @@ export default function Layout(props: ParentProps) {
|
||||
hoverProject: undefined as string | undefined,
|
||||
scrollSessionKey: undefined as string | undefined,
|
||||
nav: undefined as HTMLElement | undefined,
|
||||
sortNow: Date.now(),
|
||||
sizing: false,
|
||||
peek: undefined as LocalProject | undefined,
|
||||
peeked: false,
|
||||
})
|
||||
|
||||
const editor = createInlineEditorController()
|
||||
@ -163,14 +155,13 @@ export default function Layout(props: ParentProps) {
|
||||
}
|
||||
const isBusy = (directory: string) => !!state.busyWorkspaces[workspaceKey(directory)]
|
||||
const navLeave = { current: undefined as number | undefined }
|
||||
const [sortNow, setSortNow] = createSignal(Date.now())
|
||||
const [sizing, setSizing] = createSignal(false)
|
||||
const sortNow = () => state.sortNow
|
||||
let sizet: number | undefined
|
||||
let sortNowInterval: ReturnType<typeof setInterval> | undefined
|
||||
const sortNowTimeout = setTimeout(
|
||||
() => {
|
||||
setSortNow(Date.now())
|
||||
sortNowInterval = setInterval(() => setSortNow(Date.now()), 60_000)
|
||||
setState("sortNow", Date.now())
|
||||
sortNowInterval = setInterval(() => setState("sortNow", Date.now()), 60_000)
|
||||
},
|
||||
60_000 - (Date.now() % 60_000),
|
||||
)
|
||||
@ -196,7 +187,7 @@ export default function Layout(props: ParentProps) {
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
const stop = () => setSizing(false)
|
||||
const stop = () => setState("sizing", false)
|
||||
window.addEventListener("pointerup", stop)
|
||||
window.addEventListener("pointercancel", stop)
|
||||
window.addEventListener("blur", stop)
|
||||
@ -234,8 +225,6 @@ export default function Layout(props: ParentProps) {
|
||||
}, 300)
|
||||
}
|
||||
|
||||
const [peek, setPeek] = createSignal<LocalProject | undefined>(undefined)
|
||||
const [peeked, setPeeked] = createSignal(false)
|
||||
let peekt: number | undefined
|
||||
|
||||
const hoverProjectData = createMemo(() => {
|
||||
@ -251,17 +240,17 @@ export default function Layout(props: ParentProps) {
|
||||
clearTimeout(peekt)
|
||||
peekt = undefined
|
||||
}
|
||||
setPeek(p)
|
||||
setPeeked(true)
|
||||
setState("peek", p)
|
||||
setState("peeked", true)
|
||||
return
|
||||
}
|
||||
|
||||
setPeeked(false)
|
||||
if (peek() === undefined) return
|
||||
setState("peeked", false)
|
||||
if (state.peek === undefined) return
|
||||
if (peekt !== undefined) clearTimeout(peekt)
|
||||
peekt = window.setTimeout(() => {
|
||||
peekt = undefined
|
||||
setPeek(undefined)
|
||||
setState("peek", undefined)
|
||||
}, 180)
|
||||
})
|
||||
|
||||
@ -2245,7 +2234,7 @@ export default function Layout(props: ParentProps) {
|
||||
>
|
||||
<div class="@container w-full h-full contain-strict">{sidebarContent()}</div>
|
||||
<Show when={layout.sidebar.opened()}>
|
||||
<div onPointerDown={() => setSizing(true)}>
|
||||
<div onPointerDown={() => setState("sizing", true)}>
|
||||
<ResizeHandle
|
||||
direction="horizontal"
|
||||
size={layout.sidebar.width()}
|
||||
@ -2253,9 +2242,9 @@ export default function Layout(props: ParentProps) {
|
||||
max={typeof window === "undefined" ? 1000 : window.innerWidth * 0.3 + 64}
|
||||
collapseThreshold={244}
|
||||
onResize={(w) => {
|
||||
setSizing(true)
|
||||
setState("sizing", true)
|
||||
if (sizet !== undefined) clearTimeout(sizet)
|
||||
sizet = window.setTimeout(() => setSizing(false), 120)
|
||||
sizet = window.setTimeout(() => setState("sizing", false), 120)
|
||||
layout.sidebar.resize(w)
|
||||
}}
|
||||
onCollapse={layout.sidebar.close}
|
||||
@ -2300,7 +2289,7 @@ export default function Layout(props: ParentProps) {
|
||||
"xl:inset-y-0 xl:right-0 xl:left-[var(--main-left)]": true,
|
||||
"z-20": true,
|
||||
"transition-[left] duration-200 ease-[cubic-bezier(0.22,1,0.36,1)] will-change-[left] motion-reduce:transition-none":
|
||||
!sizing(),
|
||||
!state.sizing,
|
||||
}}
|
||||
style={{
|
||||
"--main-left": layout.sidebar.opened() ? `${Math.max(layout.sidebar.width(), 244)}px` : "4rem",
|
||||
@ -2320,11 +2309,11 @@ export default function Layout(props: ParentProps) {
|
||||
<div
|
||||
classList={{
|
||||
"hidden xl:flex absolute inset-y-0 left-16 z-30": true,
|
||||
"opacity-100 translate-x-0 pointer-events-auto": peeked() && !layout.sidebar.opened(),
|
||||
"opacity-0 -translate-x-2 pointer-events-none": !peeked() || layout.sidebar.opened(),
|
||||
"opacity-100 translate-x-0 pointer-events-auto": state.peeked && !layout.sidebar.opened(),
|
||||
"opacity-0 -translate-x-2 pointer-events-none": !state.peeked || layout.sidebar.opened(),
|
||||
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
||||
"duration-180 ease-out": peeked() && !layout.sidebar.opened(),
|
||||
"duration-120 ease-in": !peeked() || layout.sidebar.opened(),
|
||||
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||
}}
|
||||
onMouseMove={disarm}
|
||||
onMouseEnter={() => {
|
||||
@ -2336,19 +2325,19 @@ export default function Layout(props: ParentProps) {
|
||||
arm()
|
||||
}}
|
||||
>
|
||||
<Show when={peek()}>
|
||||
<SidebarPanel project={peek()} merged={false} />
|
||||
<Show when={state.peek}>
|
||||
<SidebarPanel project={state.peek} merged={false} />
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<div
|
||||
classList={{
|
||||
"hidden xl:block pointer-events-none absolute inset-y-0 right-0 z-25 overflow-hidden": true,
|
||||
"opacity-100 translate-x-0": peeked() && !layout.sidebar.opened(),
|
||||
"opacity-0 -translate-x-2": !peeked() || layout.sidebar.opened(),
|
||||
"opacity-100 translate-x-0": state.peeked && !layout.sidebar.opened(),
|
||||
"opacity-0 -translate-x-2": !state.peeked || layout.sidebar.opened(),
|
||||
"transition-[opacity,transform] motion-reduce:transition-none": true,
|
||||
"duration-180 ease-out": peeked() && !layout.sidebar.opened(),
|
||||
"duration-120 ease-in": !peeked() || layout.sidebar.opened(),
|
||||
"duration-180 ease-out": state.peeked && !layout.sidebar.opened(),
|
||||
"duration-120 ease-in": !state.peeked || layout.sidebar.opened(),
|
||||
}}
|
||||
style={{ left: `calc(4rem + ${Math.max(Math.max(layout.sidebar.width(), 244) - 64, 0)}px)` }}
|
||||
>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { For, Index, createEffect, createMemo, createSignal, on } from "solid-js"
|
||||
import { For, Index, createEffect, createMemo, on } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
||||
const TRACK = Array.from({ length: 30 }, (_, index) => index % 10)
|
||||
const DURATION = 600
|
||||
@ -14,8 +15,12 @@ function spin(from: number, to: number, direction: 1 | -1) {
|
||||
}
|
||||
|
||||
function Digit(props: { value: number; direction: 1 | -1 }) {
|
||||
const [step, setStep] = createSignal(props.value + 10)
|
||||
const [animating, setAnimating] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
step: props.value + 10,
|
||||
animating: false,
|
||||
})
|
||||
const step = () => state.step
|
||||
const animating = () => state.animating
|
||||
let last = props.value
|
||||
|
||||
createEffect(
|
||||
@ -25,13 +30,13 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
|
||||
const delta = spin(last, next, props.direction)
|
||||
last = next
|
||||
if (!delta) {
|
||||
setAnimating(false)
|
||||
setStep(next + 10)
|
||||
setState("animating", false)
|
||||
setState("step", next + 10)
|
||||
return
|
||||
}
|
||||
|
||||
setAnimating(true)
|
||||
setStep((value) => value + delta)
|
||||
setState("animating", true)
|
||||
setState("step", (value) => value + delta)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
@ -43,8 +48,8 @@ function Digit(props: { value: number; direction: 1 | -1 }) {
|
||||
data-slot="animated-number-strip"
|
||||
data-animating={animating() ? "true" : "false"}
|
||||
onTransitionEnd={() => {
|
||||
setAnimating(false)
|
||||
setStep((value) => normalize(value) + 10)
|
||||
setState("animating", false)
|
||||
setState("step", (value) => normalize(value) + 10)
|
||||
}}
|
||||
style={{
|
||||
"--animated-number-offset": `${step()}`,
|
||||
@ -63,8 +68,12 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
|
||||
return Math.max(0, Math.round(props.value))
|
||||
})
|
||||
|
||||
const [value, setValue] = createSignal(target())
|
||||
const [direction, setDirection] = createSignal<1 | -1>(1)
|
||||
const [state, setState] = createStore({
|
||||
value: target(),
|
||||
direction: 1 as 1 | -1,
|
||||
})
|
||||
const value = () => state.value
|
||||
const direction = () => state.direction
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
@ -73,8 +82,8 @@ export function AnimatedNumber(props: { value: number; class?: string }) {
|
||||
const current = value()
|
||||
if (next === current) return
|
||||
|
||||
setDirection(next > current ? 1 : -1)
|
||||
setValue(next)
|
||||
setState("direction", next > current ? 1 : -1)
|
||||
setState("value", next)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { createEffect, createSignal, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
|
||||
import { createEffect, For, Match, on, onCleanup, Show, Switch, type JSX } from "solid-js"
|
||||
import { animate, type AnimationPlaybackControls } from "motion"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Collapsible } from "./collapsible"
|
||||
import type { IconProps } from "./icon"
|
||||
import { TextShimmer } from "./text-shimmer"
|
||||
@ -37,8 +38,12 @@ export interface BasicToolProps {
|
||||
const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 }
|
||||
|
||||
export function BasicTool(props: BasicToolProps) {
|
||||
const [open, setOpen] = createSignal(props.defaultOpen ?? false)
|
||||
const [ready, setReady] = createSignal(open())
|
||||
const [state, setState] = createStore({
|
||||
open: props.defaultOpen ?? false,
|
||||
ready: props.defaultOpen ?? false,
|
||||
})
|
||||
const open = () => state.open
|
||||
const ready = () => state.ready
|
||||
const pending = () => props.status === "pending" || props.status === "running"
|
||||
|
||||
let frame: number | undefined
|
||||
@ -52,7 +57,7 @@ export function BasicTool(props: BasicToolProps) {
|
||||
onCleanup(cancel)
|
||||
|
||||
createEffect(() => {
|
||||
if (props.forceOpen) setOpen(true)
|
||||
if (props.forceOpen) setState("open", true)
|
||||
})
|
||||
|
||||
createEffect(
|
||||
@ -62,7 +67,7 @@ export function BasicTool(props: BasicToolProps) {
|
||||
if (!props.defer) return
|
||||
if (!value) {
|
||||
cancel()
|
||||
setReady(false)
|
||||
setState("ready", false)
|
||||
return
|
||||
}
|
||||
|
||||
@ -70,7 +75,7 @@ export function BasicTool(props: BasicToolProps) {
|
||||
frame = requestAnimationFrame(() => {
|
||||
frame = undefined
|
||||
if (!open()) return
|
||||
setReady(true)
|
||||
setState("ready", true)
|
||||
})
|
||||
},
|
||||
{ defer: true },
|
||||
@ -112,7 +117,7 @@ export function BasicTool(props: BasicToolProps) {
|
||||
const handleOpenChange = (value: boolean) => {
|
||||
if (pending()) return
|
||||
if (props.locked && !value) return
|
||||
setOpen(value)
|
||||
setState("open", value)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { type DiffLineAnnotation, type SelectedLineRange } from "@pierre/diffs"
|
||||
import { createEffect, createMemo, createSignal, onCleanup, Show, type Accessor, type JSX } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { render as renderSolid } from "solid-js/web"
|
||||
import { createHoverCommentUtility } from "../pierre/comment-hover"
|
||||
import { cloneSelectedLineRange, formatSelectedLineLabel, lineInSelectedRange } from "../pierre/selection-bridge"
|
||||
@ -200,8 +201,14 @@ export function createLineCommentAnnotationRenderer<T>(props: {
|
||||
}
|
||||
|
||||
export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
|
||||
const [draft, setDraft] = createSignal("")
|
||||
const [editing, setEditing] = createSignal<T | null>(null)
|
||||
const [state, setState] = createStore({
|
||||
draft: "",
|
||||
editing: null as T | null,
|
||||
})
|
||||
const draft = () => state.draft
|
||||
const setDraft = (value: string) => setState("draft", value)
|
||||
const editing = () => state.editing
|
||||
const setEditing = (value: T | null) => setState("editing", typeof value === "function" ? () => value : value)
|
||||
|
||||
const toRange = (range: SelectedLineRange | null) => (range ? cloneSelectedLineRange(range) : null)
|
||||
const setSelected = (range: SelectedLineRange | null) => {
|
||||
@ -261,7 +268,7 @@ export function createLineCommentState<T>(props: LineCommentStateProps<T>) {
|
||||
closeComment()
|
||||
setSelected(range)
|
||||
props.setCommenting(null)
|
||||
setEditing(() => id)
|
||||
setEditing(id)
|
||||
setDraft(value)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { type FilteredListProps, useFilteredList } from "@opencode-ai/ui/hooks"
|
||||
import { createEffect, createSignal, For, onCleanup, type JSX, on, Show } from "solid-js"
|
||||
import { createEffect, For, onCleanup, type JSX, on, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useI18n } from "../context/i18n"
|
||||
import { Icon, type IconProps } from "./icon"
|
||||
@ -56,12 +56,16 @@ export interface ListRef {
|
||||
|
||||
export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void }) {
|
||||
const i18n = useI18n()
|
||||
const [scrollRef, setScrollRef] = createSignal<HTMLDivElement | undefined>(undefined)
|
||||
const [internalFilter, setInternalFilter] = createSignal("")
|
||||
let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined
|
||||
const [store, setStore] = createStore({
|
||||
mouseActive: false,
|
||||
scrollRef: undefined as HTMLDivElement | undefined,
|
||||
internalFilter: "",
|
||||
})
|
||||
const scrollRef = () => store.scrollRef
|
||||
const setScrollRef = (el: HTMLDivElement | undefined) => setStore("scrollRef", el)
|
||||
const internalFilter = () => store.internalFilter
|
||||
const setInternalFilter = (value: string) => setStore("internalFilter", value)
|
||||
|
||||
const scrollIntoView = (container: HTMLDivElement, node: HTMLElement, block: "center" | "nearest") => {
|
||||
const containerRect = container.getBoundingClientRect()
|
||||
@ -208,18 +212,20 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
||||
}
|
||||
|
||||
function GroupHeader(groupProps: { group: { category: string; items: T[] } }): JSX.Element {
|
||||
const [stuck, setStuck] = createSignal(false)
|
||||
const [header, setHeader] = createSignal<HTMLDivElement | undefined>(undefined)
|
||||
const [state, setState] = createStore({
|
||||
stuck: false,
|
||||
header: undefined as HTMLDivElement | undefined,
|
||||
})
|
||||
|
||||
createEffect(() => {
|
||||
const scroll = scrollRef()
|
||||
const node = header()
|
||||
const node = state.header
|
||||
if (!scroll || !node) return
|
||||
|
||||
const handler = () => {
|
||||
const rect = node.getBoundingClientRect()
|
||||
const scrollRect = scroll.getBoundingClientRect()
|
||||
setStuck(rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
|
||||
setState("stuck", rect.top <= scrollRect.top + 1 && scroll.scrollTop > 0)
|
||||
}
|
||||
|
||||
scroll.addEventListener("scroll", handler, { passive: true })
|
||||
@ -228,7 +234,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
|
||||
})
|
||||
|
||||
return (
|
||||
<div data-slot="list-header" data-stuck={stuck()} ref={setHeader}>
|
||||
<div data-slot="list-header" data-stuck={state.stuck} ref={(el) => setState("header", el)}>
|
||||
{props.groupHeader?.(groupProps.group) ?? groupProps.group.category}
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
Index,
|
||||
type JSX,
|
||||
} from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import stripAnsi from "strip-ansi"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import {
|
||||
@ -885,8 +886,12 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||
const data = useData()
|
||||
const dialog = useDialog()
|
||||
const i18n = useI18n()
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const [busy, setBusy] = createSignal<"fork" | "revert" | undefined>()
|
||||
const [state, setState] = createStore({
|
||||
copied: false,
|
||||
busy: undefined as "fork" | "revert" | undefined,
|
||||
})
|
||||
const copied = () => state.copied
|
||||
const busy = () => state.busy
|
||||
|
||||
const textPart = createMemo(
|
||||
() => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined,
|
||||
@ -946,14 +951,14 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||
const content = text()
|
||||
if (!content) return
|
||||
await navigator.clipboard.writeText(content)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
setState("copied", true)
|
||||
setTimeout(() => setState("copied", false), 2000)
|
||||
}
|
||||
|
||||
const run = (kind: "fork" | "revert") => {
|
||||
const act = kind === "fork" ? props.actions?.fork : props.actions?.revert
|
||||
if (!act || busy()) return
|
||||
setBusy(kind)
|
||||
setState("busy", kind)
|
||||
void Promise.resolve()
|
||||
.then(() =>
|
||||
act({
|
||||
@ -962,7 +967,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
|
||||
}),
|
||||
)
|
||||
.finally(() => {
|
||||
if (busy() === kind) setBusy(undefined)
|
||||
if (busy() === kind) setState("busy", undefined)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -5,11 +5,11 @@ import {
|
||||
ParentProps,
|
||||
Show,
|
||||
createEffect,
|
||||
createSignal,
|
||||
onCleanup,
|
||||
splitProps,
|
||||
ValidComponent,
|
||||
} from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useI18n } from "../context/i18n"
|
||||
import { IconButton } from "./icon-button"
|
||||
|
||||
@ -46,23 +46,24 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
||||
"modal",
|
||||
])
|
||||
|
||||
const [contentRef, setContentRef] = createSignal<HTMLElement | undefined>(undefined)
|
||||
const [triggerRef, setTriggerRef] = createSignal<HTMLElement | undefined>(undefined)
|
||||
const [dismiss, setDismiss] = createSignal<"escape" | "outside" | null>(null)
|
||||
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = createSignal<boolean>(local.defaultOpen ?? false)
|
||||
const [state, setState] = createStore({
|
||||
contentRef: undefined as HTMLElement | undefined,
|
||||
triggerRef: undefined as HTMLElement | undefined,
|
||||
dismiss: null as "escape" | "outside" | null,
|
||||
uncontrolledOpen: local.defaultOpen ?? false,
|
||||
})
|
||||
|
||||
const controlled = () => local.open !== undefined
|
||||
const opened = () => {
|
||||
if (controlled()) return local.open ?? false
|
||||
return uncontrolledOpen()
|
||||
return state.uncontrolledOpen
|
||||
}
|
||||
|
||||
const onOpenChange = (next: boolean) => {
|
||||
if (next) setDismiss(null)
|
||||
if (next) setState("dismiss", null)
|
||||
if (local.onOpenChange) local.onOpenChange(next)
|
||||
if (controlled()) return
|
||||
setUncontrolledOpen(next)
|
||||
setState("uncontrolledOpen", next)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
@ -70,15 +71,15 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
||||
|
||||
const inside = (node: Node | null | undefined) => {
|
||||
if (!node) return false
|
||||
const content = contentRef()
|
||||
const content = state.contentRef
|
||||
if (content && content.contains(node)) return true
|
||||
const trigger = triggerRef()
|
||||
const trigger = state.triggerRef
|
||||
if (trigger && trigger.contains(node)) return true
|
||||
return false
|
||||
}
|
||||
|
||||
const close = (reason: "escape" | "outside") => {
|
||||
setDismiss(reason)
|
||||
setState("dismiss", reason)
|
||||
onOpenChange(false)
|
||||
}
|
||||
|
||||
@ -116,7 +117,7 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
||||
|
||||
const content = () => (
|
||||
<Kobalte.Content
|
||||
ref={(el: HTMLElement | undefined) => setContentRef(el)}
|
||||
ref={(el: HTMLElement | undefined) => setState("contentRef", el)}
|
||||
data-component="popover-content"
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
@ -124,8 +125,8 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
||||
}}
|
||||
style={local.style}
|
||||
onCloseAutoFocus={(event: Event) => {
|
||||
if (dismiss() === "outside") event.preventDefault()
|
||||
setDismiss(null)
|
||||
if (state.dismiss === "outside") event.preventDefault()
|
||||
setState("dismiss", null)
|
||||
}}
|
||||
>
|
||||
{/* <Kobalte.Arrow data-slot="popover-arrow" /> */}
|
||||
@ -151,7 +152,7 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
|
||||
return (
|
||||
<Kobalte gutter={4} {...rest} open={opened()} onOpenChange={onOpenChange} modal={local.modal ?? false}>
|
||||
<Kobalte.Trigger
|
||||
ref={(el: HTMLElement) => setTriggerRef(el)}
|
||||
ref={(el: HTMLElement) => setState("triggerRef", el)}
|
||||
as={local.triggerAs ?? "div"}
|
||||
data-slot="popover-trigger"
|
||||
{...(local.triggerProps as any)}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createSignal } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import * as mod from "./resize-handle"
|
||||
|
||||
const docs = `### Overview
|
||||
@ -94,8 +95,12 @@ export const Vertical = {
|
||||
|
||||
export const Collapse = {
|
||||
render: () => {
|
||||
const [size, setSize] = createSignal(200)
|
||||
const [collapsed, setCollapsed] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
size: 200,
|
||||
collapsed: false,
|
||||
})
|
||||
const size = () => state.size
|
||||
const collapsed = () => state.collapsed
|
||||
return (
|
||||
<div style={{ display: "grid", gap: "8px" }}>
|
||||
<div style={{ color: "var(--text-weak)", "font-size": "12px" }}>
|
||||
@ -116,10 +121,10 @@ export const Collapse = {
|
||||
max={360}
|
||||
collapseThreshold={100}
|
||||
onResize={(next) => {
|
||||
setCollapsed(false)
|
||||
setSize(next)
|
||||
setState("collapsed", false)
|
||||
setState("size", next)
|
||||
}}
|
||||
onCollapse={() => setCollapsed(true)}
|
||||
onCollapse={() => setState("collapsed", true)}
|
||||
style="height:24px;border:1px dashed color-mix(in oklab, var(--text-base) 20%, transparent)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createSignal, onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
|
||||
import { onCleanup, onMount, splitProps, type ComponentProps, Show, mergeProps } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useI18n } from "../context/i18n"
|
||||
|
||||
export interface ScrollViewProps extends ComponentProps<"div"> {
|
||||
@ -48,23 +49,29 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
let viewportRef!: HTMLDivElement
|
||||
let thumbRef!: HTMLDivElement
|
||||
|
||||
const [isHovered, setIsHovered] = createSignal(false)
|
||||
const [isDragging, setIsDragging] = createSignal(false)
|
||||
|
||||
const [thumbHeight, setThumbHeight] = createSignal(0)
|
||||
const [thumbTop, setThumbTop] = createSignal(0)
|
||||
const [showThumb, setShowThumb] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
isHovered: false,
|
||||
isDragging: false,
|
||||
thumbHeight: 0,
|
||||
thumbTop: 0,
|
||||
showThumb: false,
|
||||
})
|
||||
const isHovered = () => state.isHovered
|
||||
const isDragging = () => state.isDragging
|
||||
const thumbHeight = () => state.thumbHeight
|
||||
const thumbTop = () => state.thumbTop
|
||||
const showThumb = () => state.showThumb
|
||||
|
||||
const updateThumb = () => {
|
||||
if (!viewportRef) return
|
||||
const { scrollTop, scrollHeight, clientHeight } = viewportRef
|
||||
|
||||
if (scrollHeight <= clientHeight || scrollHeight === 0) {
|
||||
setShowThumb(false)
|
||||
setState("showThumb", false)
|
||||
return
|
||||
}
|
||||
|
||||
setShowThumb(true)
|
||||
setState("showThumb", true)
|
||||
const trackPadding = 8
|
||||
const trackHeight = clientHeight - trackPadding * 2
|
||||
|
||||
@ -81,8 +88,8 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
// Ensure thumb stays within bounds (shouldn't be necessary due to math above, but good for safety)
|
||||
const boundedTop = trackPadding + Math.max(0, Math.min(top, maxThumbTop))
|
||||
|
||||
setThumbHeight(height)
|
||||
setThumbTop(boundedTop)
|
||||
setState("thumbHeight", height)
|
||||
setState("thumbTop", boundedTop)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@ -113,7 +120,7 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
const onThumbPointerDown = (e: PointerEvent) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
setIsDragging(true)
|
||||
setState("isDragging", true)
|
||||
startY = e.clientY
|
||||
startScrollTop = viewportRef.scrollTop
|
||||
|
||||
@ -132,7 +139,7 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
}
|
||||
|
||||
const onPointerUp = (e: PointerEvent) => {
|
||||
setIsDragging(false)
|
||||
setState("isDragging", false)
|
||||
thumbRef.releasePointerCapture(e.pointerId)
|
||||
thumbRef.removeEventListener("pointermove", onPointerMove)
|
||||
thumbRef.removeEventListener("pointerup", onPointerUp)
|
||||
@ -191,8 +198,8 @@ export function ScrollView(props: ScrollViewProps) {
|
||||
ref={rootRef}
|
||||
class={`scroll-view ${local.class || ""}`}
|
||||
style={local.style}
|
||||
onPointerEnter={() => setIsHovered(true)}
|
||||
onPointerLeave={() => setIsHovered(false)}
|
||||
onPointerEnter={() => setState("isHovered", true)}
|
||||
onPointerLeave={() => setState("isHovered", false)}
|
||||
{...rest}
|
||||
>
|
||||
{/* Viewport */}
|
||||
|
||||
@ -13,7 +13,7 @@ import { useFileComponent } from "../context/file"
|
||||
import { useI18n } from "../context/i18n"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { checksum } from "@opencode-ai/util/encode"
|
||||
import { createEffect, createMemo, createSignal, For, Match, Show, Switch, untrack, type JSX } from "solid-js"
|
||||
import { createEffect, createMemo, For, Match, Show, Switch, untrack, type JSX } from "solid-js"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { type FileContent, type FileDiff } from "@opencode-ai/sdk/v2"
|
||||
@ -138,14 +138,16 @@ export const SessionReview = (props: SessionReviewProps) => {
|
||||
const i18n = useI18n()
|
||||
const fileComponent = useFileComponent()
|
||||
const anchors = new Map<string, HTMLElement>()
|
||||
const [store, setStore] = createStore<{ open: string[]; force: Record<string, boolean> }>({
|
||||
open: [],
|
||||
force: {},
|
||||
const [store, setStore] = createStore({
|
||||
open: [] as string[],
|
||||
force: {} as Record<string, boolean>,
|
||||
selection: null as SessionReviewSelection | null,
|
||||
commenting: null as SessionReviewSelection | null,
|
||||
opened: null as SessionReviewFocus | null,
|
||||
})
|
||||
|
||||
const [selection, setSelection] = createSignal<SessionReviewSelection | null>(null)
|
||||
const [commenting, setCommenting] = createSignal<SessionReviewSelection | null>(null)
|
||||
const [opened, setOpened] = createSignal<SessionReviewFocus | null>(null)
|
||||
const selection = () => store.selection
|
||||
const commenting = () => store.commenting
|
||||
const opened = () => store.opened
|
||||
|
||||
const open = () => props.open ?? store.open
|
||||
const files = createMemo(() => props.diffs.map((diff) => diff.file))
|
||||
@ -184,10 +186,10 @@ export const SessionReview = (props: SessionReviewProps) => {
|
||||
focusToken++
|
||||
const token = focusToken
|
||||
|
||||
setOpened(focus)
|
||||
setStore("opened", focus)
|
||||
|
||||
const comment = (props.comments ?? []).find((c) => c.file === focus.file && c.id === focus.id)
|
||||
if (comment) setSelection({ file: comment.file, range: cloneSelectedLineRange(comment.selection) })
|
||||
if (comment) setStore("selection", { file: comment.file, range: cloneSelectedLineRange(comment.selection) })
|
||||
|
||||
const current = open()
|
||||
if (!current.includes(focus.file)) {
|
||||
@ -331,11 +333,11 @@ export const SessionReview = (props: SessionReviewProps) => {
|
||||
if (!current || current.file !== file) return null
|
||||
return current.id
|
||||
},
|
||||
setOpened: (id) => setOpened(id ? { file, id } : null),
|
||||
setOpened: (id) => setStore("opened", id ? { file, id } : null),
|
||||
selected: selectedLines,
|
||||
setSelected: (range) => setSelection(range ? { file, range } : null),
|
||||
setSelected: (range) => setStore("selection", range ? { file, range } : null),
|
||||
commenting: draftRange,
|
||||
setCommenting: (range) => setCommenting(range ? { file, range } : null),
|
||||
setCommenting: (range) => setStore("commenting", range ? { file, range } : null),
|
||||
},
|
||||
getSide: selectionSide,
|
||||
clearSelectionOnSelectionEndNull: false,
|
||||
|
||||
@ -6,6 +6,7 @@ import { useFileComponent } from "../context/file"
|
||||
import { Binary } from "@opencode-ai/util/binary"
|
||||
import { getDirectory, getFilename } from "@opencode-ai/util/path"
|
||||
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Dynamic } from "solid-js/web"
|
||||
import { AssistantParts, Message, MessageDivider, PART_MAPPING, type UserActions } from "./message-part"
|
||||
import { Card } from "./card"
|
||||
@ -240,14 +241,18 @@ export function SessionTurn(
|
||||
.reverse()
|
||||
})
|
||||
const edited = createMemo(() => diffs().length)
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [expanded, setExpanded] = createSignal<string[]>([])
|
||||
const [state, setState] = createStore({
|
||||
open: false,
|
||||
expanded: [] as string[],
|
||||
})
|
||||
const open = () => state.open
|
||||
const expanded = () => state.expanded
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
open,
|
||||
(value, prev) => {
|
||||
if (!value && prev) setExpanded([])
|
||||
if (!value && prev) setState("expanded", [])
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
@ -425,7 +430,7 @@ export function SessionTurn(
|
||||
<SessionRetry status={status()} show={active()} />
|
||||
<Show when={edited() > 0 && !working()}>
|
||||
<div data-slot="session-turn-diffs">
|
||||
<Collapsible open={open()} onOpenChange={setOpen} variant="ghost">
|
||||
<Collapsible open={open()} onOpenChange={(value) => setState("open", value)} variant="ghost">
|
||||
<Collapsible.Trigger>
|
||||
<div data-component="session-turn-diffs-trigger">
|
||||
<div data-slot="session-turn-diffs-title">
|
||||
@ -447,7 +452,9 @@ export function SessionTurn(
|
||||
multiple
|
||||
style={{ "--sticky-accordion-offset": "40px" }}
|
||||
value={expanded()}
|
||||
onChange={(value) => setExpanded(Array.isArray(value) ? value : value ? [value] : [])}
|
||||
onChange={(value) =>
|
||||
setState("expanded", Array.isArray(value) ? value : value ? [value] : [])
|
||||
}
|
||||
>
|
||||
<For each={diffs()}>
|
||||
{(diff) => {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createEffect, createSignal, onCleanup } from "solid-js"
|
||||
import { createEffect, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { BasicTool } from "./basic-tool"
|
||||
import { animate } from "motion"
|
||||
|
||||
@ -138,29 +139,39 @@ function SpringSubmessage(props: { text: string; visible: boolean; visualDuratio
|
||||
|
||||
export const Playground = {
|
||||
render: () => {
|
||||
const [text, setText] = createSignal("Prints five topic blocks between timed commands")
|
||||
const [show, setShow] = createSignal(true)
|
||||
const [visualDuration, setVisualDuration] = createSignal(0.35)
|
||||
const [bounce, setBounce] = createSignal(0)
|
||||
const [fadeMs, setFadeMs] = createSignal(320)
|
||||
const [blur, setBlur] = createSignal(2)
|
||||
const [fadeEase, setFadeEase] = createSignal<keyof typeof ease>("snappy")
|
||||
const [auto, setAuto] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
text: "Prints five topic blocks between timed commands",
|
||||
show: true,
|
||||
visualDuration: 0.35,
|
||||
bounce: 0,
|
||||
fadeMs: 320,
|
||||
blur: 2,
|
||||
fadeEase: "snappy",
|
||||
auto: false,
|
||||
})
|
||||
const text = () => state.text
|
||||
const show = () => state.show
|
||||
const visualDuration = () => state.visualDuration
|
||||
const bounce = () => state.bounce
|
||||
const fadeMs = () => state.fadeMs
|
||||
const blur = () => state.blur
|
||||
const fadeEase = () => state.fadeEase
|
||||
const auto = () => state.auto
|
||||
let replayTimer
|
||||
let autoTimer
|
||||
|
||||
const replay = () => {
|
||||
setShow(false)
|
||||
setState("show", false)
|
||||
if (replayTimer) clearTimeout(replayTimer)
|
||||
replayTimer = setTimeout(() => {
|
||||
setShow(true)
|
||||
setState("show", true)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
const stopAuto = () => {
|
||||
if (autoTimer) clearInterval(autoTimer)
|
||||
autoTimer = undefined
|
||||
setAuto(false)
|
||||
setState("auto", false)
|
||||
}
|
||||
|
||||
const toggleAuto = () => {
|
||||
@ -168,7 +179,7 @@ export const Playground = {
|
||||
stopAuto()
|
||||
return
|
||||
}
|
||||
setAuto(true)
|
||||
setState("auto", true)
|
||||
autoTimer = setInterval(replay, 2200)
|
||||
}
|
||||
|
||||
@ -224,7 +235,7 @@ export const Playground = {
|
||||
<button onClick={replay} style={btn()}>
|
||||
Replay entry
|
||||
</button>
|
||||
<button onClick={() => setShow((v) => !v)} style={btn(show())}>
|
||||
<button onClick={() => setState("show", (value) => !value)} style={btn(show())}>
|
||||
{show() ? "Hide subtitle" : "Show subtitle"}
|
||||
</button>
|
||||
<button onClick={toggleAuto} style={btn(auto())}>
|
||||
@ -244,7 +255,7 @@ export const Playground = {
|
||||
<span style={sliderLabel}>subtitle</span>
|
||||
<input
|
||||
value={text()}
|
||||
onInput={(e) => setText(e.currentTarget.value)}
|
||||
onInput={(e) => setState("text", e.currentTarget.value)}
|
||||
style={{
|
||||
width: "420px",
|
||||
"max-width": "100%",
|
||||
@ -265,7 +276,7 @@ export const Playground = {
|
||||
max={1.5}
|
||||
step={0.01}
|
||||
value={visualDuration()}
|
||||
onInput={(e) => setVisualDuration(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("visualDuration", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{visualDuration().toFixed(2)}s</span>
|
||||
</div>
|
||||
@ -278,7 +289,7 @@ export const Playground = {
|
||||
max={0.5}
|
||||
step={0.01}
|
||||
value={bounce()}
|
||||
onInput={(e) => setBounce(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("bounce", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{bounce().toFixed(2)}</span>
|
||||
</div>
|
||||
@ -287,8 +298,14 @@ export const Playground = {
|
||||
<span style={sliderLabel}>fade ease</span>
|
||||
<button
|
||||
onClick={() =>
|
||||
setFadeEase((v) =>
|
||||
v === "snappy" ? "smooth" : v === "smooth" ? "standard" : v === "standard" ? "linear" : "snappy",
|
||||
setState("fadeEase", (value) =>
|
||||
value === "snappy"
|
||||
? "smooth"
|
||||
: value === "smooth"
|
||||
? "standard"
|
||||
: value === "standard"
|
||||
? "linear"
|
||||
: "snappy",
|
||||
)
|
||||
}
|
||||
style={btn()}
|
||||
@ -305,7 +322,7 @@ export const Playground = {
|
||||
max={1400}
|
||||
step={10}
|
||||
value={fadeMs()}
|
||||
onInput={(e) => setFadeMs(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("fadeMs", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{fadeMs()}ms</span>
|
||||
</div>
|
||||
@ -318,7 +335,7 @@ export const Playground = {
|
||||
max={14}
|
||||
step={0.5}
|
||||
value={blur()}
|
||||
onInput={(e) => setBlur(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("blur", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{blur()}px</span>
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createSignal, onCleanup } from "solid-js"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { TextReveal } from "./text-reveal"
|
||||
|
||||
export default {
|
||||
@ -87,33 +88,42 @@ const headingSlot = {
|
||||
|
||||
export const Playground = {
|
||||
render: () => {
|
||||
const [index, setIndex] = createSignal(0)
|
||||
const [cycling, setCycling] = createSignal(false)
|
||||
const [growOnly, setGrowOnly] = createSignal(true)
|
||||
|
||||
const [duration, setDuration] = createSignal(600)
|
||||
const [bounce, setBounce] = createSignal(1.0)
|
||||
const [bounceSoft, setBounceSoft] = createSignal(1.0)
|
||||
|
||||
const [hybridTravel, setHybridTravel] = createSignal(25)
|
||||
const [hybridEdge, setHybridEdge] = createSignal(17)
|
||||
|
||||
const [edge, setEdge] = createSignal(17)
|
||||
const [revealTravel, setRevealTravel] = createSignal(0)
|
||||
const [state, setState] = createStore({
|
||||
index: 0,
|
||||
cycling: false,
|
||||
growOnly: true,
|
||||
duration: 600,
|
||||
bounce: 1.0,
|
||||
bounceSoft: 1.0,
|
||||
hybridTravel: 25,
|
||||
hybridEdge: 17,
|
||||
edge: 17,
|
||||
revealTravel: 0,
|
||||
})
|
||||
const index = () => state.index
|
||||
const cycling = () => state.cycling
|
||||
const growOnly = () => state.growOnly
|
||||
const duration = () => state.duration
|
||||
const bounce = () => state.bounce
|
||||
const bounceSoft = () => state.bounceSoft
|
||||
const hybridTravel = () => state.hybridTravel
|
||||
const hybridEdge = () => state.hybridEdge
|
||||
const edge = () => state.edge
|
||||
const revealTravel = () => state.revealTravel
|
||||
|
||||
let timer: number | undefined
|
||||
const text = () => TEXTS[index()]
|
||||
const next = () => setIndex((i) => (i + 1) % TEXTS.length)
|
||||
const prev = () => setIndex((i) => (i - 1 + TEXTS.length) % TEXTS.length)
|
||||
const next = () => setState("index", (value) => (value + 1) % TEXTS.length)
|
||||
const prev = () => setState("index", (value) => (value - 1 + TEXTS.length) % TEXTS.length)
|
||||
|
||||
const toggleCycle = () => {
|
||||
if (cycling()) {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = undefined
|
||||
setCycling(false)
|
||||
setState("cycling", false)
|
||||
return
|
||||
}
|
||||
setCycling(true)
|
||||
setState("cycling", true)
|
||||
const tick = () => {
|
||||
next()
|
||||
timer = window.setTimeout(tick, 700 + Math.floor(Math.random() * 600))
|
||||
@ -172,7 +182,7 @@ export const Playground = {
|
||||
|
||||
<div style={{ display: "flex", gap: "6px", "flex-wrap": "wrap" }}>
|
||||
{TEXTS.map((t, i) => (
|
||||
<button onClick={() => setIndex(i)} style={btn(index() === i)}>
|
||||
<button onClick={() => setState("index", i)} style={btn(index() === i)}>
|
||||
{t ?? "(none)"}
|
||||
</button>
|
||||
))}
|
||||
@ -188,7 +198,7 @@ export const Playground = {
|
||||
<button onClick={toggleCycle} style={btn(cycling())}>
|
||||
{cycling() ? "Stop cycle" : "Auto cycle"}
|
||||
</button>
|
||||
<button onClick={() => setGrowOnly((v) => !v)} style={btn(growOnly())}>
|
||||
<button onClick={() => setState("growOnly", (value) => !value)} style={btn(growOnly())}>
|
||||
{growOnly() ? "growOnly: on" : "growOnly: off"}
|
||||
</button>
|
||||
</div>
|
||||
@ -204,7 +214,7 @@ export const Playground = {
|
||||
max="40"
|
||||
step="1"
|
||||
value={hybridEdge()}
|
||||
onInput={(e) => setHybridEdge(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("hybridEdge", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{hybridEdge()}%</span>
|
||||
@ -218,7 +228,7 @@ export const Playground = {
|
||||
max="40"
|
||||
step="1"
|
||||
value={hybridTravel()}
|
||||
onInput={(e) => setHybridTravel(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("hybridTravel", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{hybridTravel()}px</span>
|
||||
@ -234,7 +244,7 @@ export const Playground = {
|
||||
max="1400"
|
||||
step="10"
|
||||
value={duration()}
|
||||
onInput={(e) => setDuration(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("duration", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{duration()}ms</span>
|
||||
@ -248,7 +258,7 @@ export const Playground = {
|
||||
max="2"
|
||||
step="0.01"
|
||||
value={bounce()}
|
||||
onInput={(e) => setBounce(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("bounce", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{bounce().toFixed(2)}</span>
|
||||
@ -262,7 +272,7 @@ export const Playground = {
|
||||
max="1.5"
|
||||
step="0.01"
|
||||
value={bounceSoft()}
|
||||
onInput={(e) => setBounceSoft(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("bounceSoft", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{bounceSoft().toFixed(2)}</span>
|
||||
@ -280,7 +290,7 @@ export const Playground = {
|
||||
max="40"
|
||||
step="1"
|
||||
value={edge()}
|
||||
onInput={(e) => setEdge(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("edge", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{edge()}%</span>
|
||||
@ -294,7 +304,7 @@ export const Playground = {
|
||||
max="16"
|
||||
step="1"
|
||||
value={revealTravel()}
|
||||
onInput={(e) => setRevealTravel(e.currentTarget.valueAsNumber)}
|
||||
onInput={(e) => setState("revealTravel", e.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "60px", "text-align": "right", "font-size": "12px" }}>{revealTravel()}px</span>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"
|
||||
import { createEffect, on, onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
||||
const px = (value: number | string | undefined, fallback: number) => {
|
||||
if (typeof value === "number") return `${value}px`
|
||||
@ -30,11 +31,18 @@ export function TextReveal(props: {
|
||||
growOnly?: boolean
|
||||
truncate?: boolean
|
||||
}) {
|
||||
const [cur, setCur] = createSignal(props.text)
|
||||
const [old, setOld] = createSignal<string | undefined>()
|
||||
const [width, setWidth] = createSignal("auto")
|
||||
const [ready, setReady] = createSignal(false)
|
||||
const [swapping, setSwapping] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
cur: props.text,
|
||||
old: undefined as string | undefined,
|
||||
width: "auto",
|
||||
ready: false,
|
||||
swapping: false,
|
||||
})
|
||||
const cur = () => state.cur
|
||||
const old = () => state.old
|
||||
const width = () => state.width
|
||||
const ready = () => state.ready
|
||||
const swapping = () => state.swapping
|
||||
let inRef: HTMLSpanElement | undefined
|
||||
let outRef: HTMLSpanElement | undefined
|
||||
let rootRef: HTMLSpanElement | undefined
|
||||
@ -49,7 +57,7 @@ export function TextReveal(props: {
|
||||
const prev = Number.parseFloat(width())
|
||||
if (Number.isFinite(prev) && next <= prev) return
|
||||
}
|
||||
setWidth(`${next}px`)
|
||||
setState("width", `${next}px`)
|
||||
}
|
||||
|
||||
createEffect(
|
||||
@ -58,25 +66,25 @@ export function TextReveal(props: {
|
||||
(next, prev) => {
|
||||
if (next === prev) return
|
||||
if (typeof next === "string" && typeof prev === "string" && next.startsWith(prev)) {
|
||||
setCur(next)
|
||||
setState("cur", next)
|
||||
widen(win())
|
||||
return
|
||||
}
|
||||
setSwapping(true)
|
||||
setOld(prev)
|
||||
setCur(next)
|
||||
setState("swapping", true)
|
||||
setState("old", prev)
|
||||
setState("cur", next)
|
||||
|
||||
if (typeof requestAnimationFrame !== "function") {
|
||||
widen(Math.max(win(), wout()))
|
||||
rootRef?.offsetHeight
|
||||
setSwapping(false)
|
||||
setState("swapping", false)
|
||||
return
|
||||
}
|
||||
if (frame !== undefined && typeof cancelAnimationFrame === "function") cancelAnimationFrame(frame)
|
||||
frame = requestAnimationFrame(() => {
|
||||
widen(Math.max(win(), wout()))
|
||||
rootRef?.offsetHeight
|
||||
setSwapping(false)
|
||||
setState("swapping", false)
|
||||
frame = undefined
|
||||
})
|
||||
},
|
||||
@ -87,16 +95,16 @@ export function TextReveal(props: {
|
||||
widen(win())
|
||||
const fonts = typeof document !== "undefined" ? document.fonts : undefined
|
||||
if (typeof requestAnimationFrame !== "function") {
|
||||
setReady(true)
|
||||
setState("ready", true)
|
||||
return
|
||||
}
|
||||
if (!fonts) {
|
||||
requestAnimationFrame(() => setReady(true))
|
||||
requestAnimationFrame(() => setState("ready", true))
|
||||
return
|
||||
}
|
||||
fonts.ready.finally(() => {
|
||||
widen(win())
|
||||
requestAnimationFrame(() => setReady(true))
|
||||
requestAnimationFrame(() => setState("ready", true))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useSpring } from "./motion-spring"
|
||||
import { TextStrikethrough } from "./text-strikethrough"
|
||||
|
||||
@ -130,12 +131,16 @@ function VariantF(props: { active: boolean; text: string }) {
|
||||
)
|
||||
let baseRef: HTMLSpanElement | undefined
|
||||
let containerRef: HTMLSpanElement | undefined
|
||||
const [textWidth, setTextWidth] = createSignal(0)
|
||||
const [containerWidth, setContainerWidth] = createSignal(0)
|
||||
const [state, setState] = createStore({
|
||||
textWidth: 0,
|
||||
containerWidth: 0,
|
||||
})
|
||||
const textWidth = () => state.textWidth
|
||||
const containerWidth = () => state.containerWidth
|
||||
|
||||
const measure = () => {
|
||||
if (baseRef) setTextWidth(baseRef.scrollWidth)
|
||||
if (containerRef) setContainerWidth(containerRef.offsetWidth)
|
||||
if (baseRef) setState("textWidth", baseRef.scrollWidth)
|
||||
if (containerRef) setState("containerWidth", containerRef.offsetWidth)
|
||||
}
|
||||
|
||||
onMount(measure)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { JSX } from "solid-js"
|
||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { createEffect, onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useSpring } from "./motion-spring"
|
||||
|
||||
export function TextStrikethrough(props: {
|
||||
@ -19,12 +20,16 @@ export function TextStrikethrough(props: {
|
||||
|
||||
let baseRef: HTMLSpanElement | undefined
|
||||
let containerRef: HTMLSpanElement | undefined
|
||||
const [textWidth, setTextWidth] = createSignal(0)
|
||||
const [containerWidth, setContainerWidth] = createSignal(0)
|
||||
const [state, setState] = createStore({
|
||||
textWidth: 0,
|
||||
containerWidth: 0,
|
||||
})
|
||||
const textWidth = () => state.textWidth
|
||||
const containerWidth = () => state.containerWidth
|
||||
|
||||
const measure = () => {
|
||||
if (baseRef) setTextWidth(baseRef.scrollWidth)
|
||||
if (containerRef) setContainerWidth(containerRef.offsetWidth)
|
||||
if (baseRef) setState("textWidth", baseRef.scrollWidth)
|
||||
if (containerRef) setState("containerWidth", containerRef.offsetWidth)
|
||||
}
|
||||
|
||||
onMount(measure)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createSignal, createEffect, on, onMount, onCleanup } from "solid-js"
|
||||
import { createEffect, on, onMount, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { TextShimmer } from "./text-shimmer"
|
||||
import { TextReveal } from "./text-reveal"
|
||||
|
||||
@ -375,11 +376,18 @@ input[type="range"].heading-slider::-webkit-slider-thumb {
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function AnimatedHeading(props) {
|
||||
const [current, setCurrent] = createSignal(props.text)
|
||||
const [leaving, setLeaving] = createSignal(undefined)
|
||||
const [width, setWidth] = createSignal("auto")
|
||||
const [ready, setReady] = createSignal(false)
|
||||
const [swapping, setSwapping] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
current: props.text,
|
||||
leaving: undefined,
|
||||
width: "auto",
|
||||
ready: false,
|
||||
swapping: false,
|
||||
})
|
||||
const current = () => state.current
|
||||
const leaving = () => state.leaving
|
||||
const width = () => state.width
|
||||
const ready = () => state.ready
|
||||
const swapping = () => state.swapping
|
||||
let enterRef
|
||||
let leaveRef
|
||||
let containerRef
|
||||
@ -391,16 +399,16 @@ function AnimatedHeading(props) {
|
||||
if (px <= 0) return
|
||||
const w = Number.parseFloat(width())
|
||||
if (Number.isFinite(w) && px <= w) return
|
||||
setWidth(`${px}px`)
|
||||
setState("width", `${px}px`)
|
||||
}
|
||||
|
||||
const measure = () => {
|
||||
if (!current()) {
|
||||
setWidth("0px")
|
||||
setState("width", "0px")
|
||||
return
|
||||
}
|
||||
const px = measureEnter()
|
||||
if (px > 0) setWidth(`${px}px`)
|
||||
if (px > 0) setState("width", `${px}px`)
|
||||
}
|
||||
|
||||
createEffect(
|
||||
@ -408,9 +416,9 @@ function AnimatedHeading(props) {
|
||||
() => props.text,
|
||||
(next, prev) => {
|
||||
if (next === prev) return
|
||||
setSwapping(true)
|
||||
setLeaving(prev)
|
||||
setCurrent(next)
|
||||
setState("swapping", true)
|
||||
setState("leaving", prev)
|
||||
setState("current", next)
|
||||
|
||||
if (frame) cancelAnimationFrame(frame)
|
||||
frame = requestAnimationFrame(() => {
|
||||
@ -420,10 +428,10 @@ function AnimatedHeading(props) {
|
||||
const leaveW = measureLeave()
|
||||
widen(Math.max(enterW, leaveW))
|
||||
containerRef?.offsetHeight // reflow with max width + swap positions
|
||||
setSwapping(false)
|
||||
setState("swapping", false)
|
||||
} else {
|
||||
containerRef?.offsetHeight
|
||||
setSwapping(false)
|
||||
setState("swapping", false)
|
||||
measure()
|
||||
}
|
||||
frame = undefined
|
||||
@ -436,7 +444,7 @@ function AnimatedHeading(props) {
|
||||
measure()
|
||||
document.fonts?.ready.finally(() => {
|
||||
measure()
|
||||
requestAnimationFrame(() => setReady(true))
|
||||
requestAnimationFrame(() => setState("ready", true))
|
||||
})
|
||||
})
|
||||
|
||||
@ -552,47 +560,56 @@ const VARIANTS: { key: string; label: string }[] = []
|
||||
|
||||
export const Playground = {
|
||||
render: () => {
|
||||
const [heading, setHeading] = createSignal(HEADINGS[0])
|
||||
const [headingIndex, setHeadingIndex] = createSignal(0)
|
||||
const [active, setActive] = createSignal(true)
|
||||
const [cycling, setCycling] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
heading: HEADINGS[0],
|
||||
headingIndex: 0,
|
||||
active: true,
|
||||
cycling: false,
|
||||
duration: 550,
|
||||
blur: 2,
|
||||
travel: 4,
|
||||
bounce: 1.35,
|
||||
maskSize: 12,
|
||||
maskPad: 9,
|
||||
maskHeight: 0,
|
||||
debug: false,
|
||||
odoBlur: false,
|
||||
})
|
||||
const heading = () => state.heading
|
||||
const headingIndex = () => state.headingIndex
|
||||
const active = () => state.active
|
||||
const cycling = () => state.cycling
|
||||
const duration = () => state.duration
|
||||
const blur = () => state.blur
|
||||
const travel = () => state.travel
|
||||
const bounce = () => state.bounce
|
||||
const maskSize = () => state.maskSize
|
||||
const maskPad = () => state.maskPad
|
||||
const maskHeight = () => state.maskHeight
|
||||
const debug = () => state.debug
|
||||
const odoBlur = () => state.odoBlur
|
||||
let cycleTimer
|
||||
|
||||
// tunable params
|
||||
const [duration, setDuration] = createSignal(550)
|
||||
const [blur, setBlur] = createSignal(2)
|
||||
const [travel, setTravel] = createSignal(4)
|
||||
const [bounce, setBounce] = createSignal(1.35)
|
||||
const [maskSize, setMaskSize] = createSignal(12)
|
||||
const [maskPad, setMaskPad] = createSignal(9)
|
||||
const [maskHeight, setMaskHeight] = createSignal(0)
|
||||
const [debug, setDebug] = createSignal(false)
|
||||
const [odoBlur, setOdoBlur] = createSignal(false)
|
||||
|
||||
const nextHeading = () => {
|
||||
setHeadingIndex((i) => {
|
||||
const next = (i + 1) % HEADINGS.length
|
||||
setHeading(HEADINGS[next])
|
||||
return next
|
||||
})
|
||||
const next = (headingIndex() + 1) % HEADINGS.length
|
||||
setState("headingIndex", next)
|
||||
setState("heading", HEADINGS[next])
|
||||
}
|
||||
|
||||
const prevHeading = () => {
|
||||
setHeadingIndex((i) => {
|
||||
const prev = (i - 1 + HEADINGS.length) % HEADINGS.length
|
||||
setHeading(HEADINGS[prev])
|
||||
return prev
|
||||
})
|
||||
const prev = (headingIndex() - 1 + HEADINGS.length) % HEADINGS.length
|
||||
setState("headingIndex", prev)
|
||||
setState("heading", HEADINGS[prev])
|
||||
}
|
||||
|
||||
const toggleCycling = () => {
|
||||
if (cycling()) {
|
||||
clearTimeout(cycleTimer)
|
||||
cycleTimer = undefined
|
||||
setCycling(false)
|
||||
setState("cycling", false)
|
||||
return
|
||||
}
|
||||
setCycling(true)
|
||||
setState("cycling", true)
|
||||
const tick = () => {
|
||||
if (!cycling()) return
|
||||
nextHeading()
|
||||
@ -602,11 +619,11 @@ export const Playground = {
|
||||
}
|
||||
|
||||
const clearHeading = () => {
|
||||
setHeading(undefined)
|
||||
setState("heading", undefined)
|
||||
if (cycling()) {
|
||||
clearTimeout(cycleTimer)
|
||||
cycleTimer = undefined
|
||||
setCycling(false)
|
||||
setState("cycling", false)
|
||||
}
|
||||
}
|
||||
|
||||
@ -686,7 +703,7 @@ export const Playground = {
|
||||
max={1400}
|
||||
step={50}
|
||||
value={duration()}
|
||||
onInput={(e) => setDuration(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("duration", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{duration()}ms</span>
|
||||
</div>
|
||||
@ -700,7 +717,7 @@ export const Playground = {
|
||||
max={16}
|
||||
step={0.5}
|
||||
value={blur()}
|
||||
onInput={(e) => setBlur(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("blur", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{blur()}px</span>
|
||||
</div>
|
||||
@ -714,7 +731,7 @@ export const Playground = {
|
||||
max={120}
|
||||
step={1}
|
||||
value={travel()}
|
||||
onInput={(e) => setTravel(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("travel", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{travel()}px</span>
|
||||
</div>
|
||||
@ -728,7 +745,7 @@ export const Playground = {
|
||||
max={2.2}
|
||||
step={0.05}
|
||||
value={bounce()}
|
||||
onInput={(e) => setBounce(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("bounce", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>
|
||||
{bounce().toFixed(2)} {bounce() <= 1.05 ? "(none)" : bounce() >= 1.9 ? "(heavy)" : ""}
|
||||
@ -744,7 +761,7 @@ export const Playground = {
|
||||
max={50}
|
||||
step={1}
|
||||
value={maskSize()}
|
||||
onInput={(e) => setMaskSize(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("maskSize", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>
|
||||
{maskSize()}px {maskSize() === 0 ? "(hard)" : ""}
|
||||
@ -760,7 +777,7 @@ export const Playground = {
|
||||
max={60}
|
||||
step={1}
|
||||
value={maskPad()}
|
||||
onInput={(e) => setMaskPad(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("maskPad", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{maskPad()}px</span>
|
||||
</div>
|
||||
@ -774,7 +791,7 @@ export const Playground = {
|
||||
max={80}
|
||||
step={1}
|
||||
value={maskHeight()}
|
||||
onInput={(e) => setMaskHeight(Number(e.currentTarget.value))}
|
||||
onInput={(e) => setState("maskHeight", Number(e.currentTarget.value))}
|
||||
/>
|
||||
<span style={sliderValue}>{maskHeight()}px</span>
|
||||
</div>
|
||||
@ -795,13 +812,13 @@ export const Playground = {
|
||||
<button onClick={clearHeading} style={btn()}>
|
||||
Clear
|
||||
</button>
|
||||
<button onClick={() => setActive((v) => !v)} style={smallBtn(active())}>
|
||||
<button onClick={() => setState("active", (value) => !value)} style={smallBtn(active())}>
|
||||
{active() ? "Shimmer: on" : "Shimmer: off"}
|
||||
</button>
|
||||
<button onClick={() => setDebug((v) => !v)} style={smallBtn(debug())}>
|
||||
<button onClick={() => setState("debug", (value) => !value)} style={smallBtn(debug())}>
|
||||
{debug() ? "Debug mask: on" : "Debug mask"}
|
||||
</button>
|
||||
<button onClick={() => setOdoBlur((v) => !v)} style={smallBtn(odoBlur())}>
|
||||
<button onClick={() => setState("odoBlur", (value) => !value)} style={smallBtn(odoBlur())}>
|
||||
{odoBlur() ? "Odo blur: on" : "Odo blur"}
|
||||
</button>
|
||||
</div>
|
||||
@ -810,8 +827,8 @@ export const Playground = {
|
||||
{HEADINGS.map((h, i) => (
|
||||
<button
|
||||
onClick={() => {
|
||||
setHeadingIndex(i)
|
||||
setHeading(h)
|
||||
setState("headingIndex", i)
|
||||
setState("heading", h)
|
||||
}}
|
||||
style={smallBtn(headingIndex() === i)}
|
||||
>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js"
|
||||
import { createEffect, createMemo, onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import type { Todo } from "@opencode-ai/sdk/v2"
|
||||
import { useGlobalSync } from "@/context/global-sync"
|
||||
import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer"
|
||||
@ -129,24 +130,44 @@ const css = `
|
||||
export const Playground = {
|
||||
render: () => {
|
||||
const global = useGlobalSync()
|
||||
const [open, setOpen] = createSignal(true)
|
||||
const [step, setStep] = createSignal(1)
|
||||
const [dockOpenDuration, setDockOpenDuration] = createSignal(0.3)
|
||||
const [dockOpenBounce, setDockOpenBounce] = createSignal(0)
|
||||
const [dockCloseDuration, setDockCloseDuration] = createSignal(0.3)
|
||||
const [dockCloseBounce, setDockCloseBounce] = createSignal(0)
|
||||
const [drawerExpandDuration, setDrawerExpandDuration] = createSignal(0.3)
|
||||
const [drawerExpandBounce, setDrawerExpandBounce] = createSignal(0)
|
||||
const [drawerCollapseDuration, setDrawerCollapseDuration] = createSignal(0.3)
|
||||
const [drawerCollapseBounce, setDrawerCollapseBounce] = createSignal(0)
|
||||
const [subtitleDuration, setSubtitleDuration] = createSignal(600)
|
||||
const [subtitleAuto, setSubtitleAuto] = createSignal(true)
|
||||
const [subtitleTravel, setSubtitleTravel] = createSignal(25)
|
||||
const [subtitleEdge, setSubtitleEdge] = createSignal(17)
|
||||
const [countDuration, setCountDuration] = createSignal(600)
|
||||
const [countMask, setCountMask] = createSignal(18)
|
||||
const [countMaskHeight, setCountMaskHeight] = createSignal(0)
|
||||
const [countWidthDuration, setCountWidthDuration] = createSignal(560)
|
||||
const [cfg, setCfg] = createStore({
|
||||
open: true,
|
||||
step: 1,
|
||||
dockOpenDuration: 0.3,
|
||||
dockOpenBounce: 0,
|
||||
dockCloseDuration: 0.3,
|
||||
dockCloseBounce: 0,
|
||||
drawerExpandDuration: 0.3,
|
||||
drawerExpandBounce: 0,
|
||||
drawerCollapseDuration: 0.3,
|
||||
drawerCollapseBounce: 0,
|
||||
subtitleDuration: 600,
|
||||
subtitleAuto: true,
|
||||
subtitleTravel: 25,
|
||||
subtitleEdge: 17,
|
||||
countDuration: 600,
|
||||
countMask: 18,
|
||||
countMaskHeight: 0,
|
||||
countWidthDuration: 560,
|
||||
})
|
||||
const open = () => cfg.open
|
||||
const step = () => cfg.step
|
||||
const dockOpenDuration = () => cfg.dockOpenDuration
|
||||
const dockOpenBounce = () => cfg.dockOpenBounce
|
||||
const dockCloseDuration = () => cfg.dockCloseDuration
|
||||
const dockCloseBounce = () => cfg.dockCloseBounce
|
||||
const drawerExpandDuration = () => cfg.drawerExpandDuration
|
||||
const drawerExpandBounce = () => cfg.drawerExpandBounce
|
||||
const drawerCollapseDuration = () => cfg.drawerCollapseDuration
|
||||
const drawerCollapseBounce = () => cfg.drawerCollapseBounce
|
||||
const subtitleDuration = () => cfg.subtitleDuration
|
||||
const subtitleAuto = () => cfg.subtitleAuto
|
||||
const subtitleTravel = () => cfg.subtitleTravel
|
||||
const subtitleEdge = () => cfg.subtitleEdge
|
||||
const countDuration = () => cfg.countDuration
|
||||
const countMask = () => cfg.countMask
|
||||
const countMaskHeight = () => cfg.countMaskHeight
|
||||
const countWidthDuration = () => cfg.countWidthDuration
|
||||
const state = createSessionComposerState({ closeMs: () => Math.round(dockCloseDuration() * 1000) })
|
||||
let frame
|
||||
let composerRef
|
||||
@ -187,7 +208,7 @@ export const Playground = {
|
||||
|
||||
const openDock = () => {
|
||||
clear()
|
||||
setOpen(true)
|
||||
setCfg("open", true)
|
||||
frame = requestAnimationFrame(() => {
|
||||
pin()
|
||||
frame = undefined
|
||||
@ -196,7 +217,7 @@ export const Playground = {
|
||||
|
||||
const closeDock = () => {
|
||||
clear()
|
||||
setOpen(false)
|
||||
setCfg("open", false)
|
||||
}
|
||||
|
||||
const dockOpen = () => open()
|
||||
@ -223,7 +244,7 @@ export const Playground = {
|
||||
}
|
||||
|
||||
const cycle = () => {
|
||||
setStep((value) => (value + 1) % 4)
|
||||
setCfg("step", (value) => (value + 1) % 4)
|
||||
}
|
||||
|
||||
onCleanup(clear)
|
||||
@ -289,7 +310,7 @@ export const Playground = {
|
||||
Cycle progress ({step()}/3 done)
|
||||
</button>
|
||||
{[0, 1, 2, 3].map((value) => (
|
||||
<button onClick={() => setStep(value)} style={btn(step() === value)}>
|
||||
<button onClick={() => setCfg("step", value)} style={btn(step() === value)}>
|
||||
{value} done
|
||||
</button>
|
||||
))}
|
||||
@ -307,7 +328,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={dockOpenDuration()}
|
||||
onInput={(event) => setDockOpenDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("dockOpenDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -324,7 +345,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={dockOpenBounce()}
|
||||
onInput={(event) => setDockOpenBounce(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("dockOpenBounce", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -345,7 +366,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={dockCloseDuration()}
|
||||
onInput={(event) => setDockCloseDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("dockCloseDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -362,7 +383,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={dockCloseBounce()}
|
||||
onInput={(event) => setDockCloseBounce(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("dockCloseBounce", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -383,7 +404,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={drawerExpandDuration()}
|
||||
onInput={(event) => setDrawerExpandDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("drawerExpandDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -400,7 +421,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={drawerExpandBounce()}
|
||||
onInput={(event) => setDrawerExpandBounce(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("drawerExpandBounce", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -421,7 +442,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={drawerCollapseDuration()}
|
||||
onInput={(event) => setDrawerCollapseDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("drawerCollapseDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -438,7 +459,7 @@ export const Playground = {
|
||||
max="1"
|
||||
step="0.01"
|
||||
value={drawerCollapseBounce()}
|
||||
onInput={(event) => setDrawerCollapseBounce(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("drawerCollapseBounce", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -459,7 +480,7 @@ export const Playground = {
|
||||
max="1400"
|
||||
step="10"
|
||||
value={subtitleDuration()}
|
||||
onInput={(event) => setSubtitleDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("subtitleDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -473,7 +494,7 @@ export const Playground = {
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={subtitleAuto()}
|
||||
onInput={(event) => setSubtitleAuto(event.currentTarget.checked)}
|
||||
onInput={(event) => setCfg("subtitleAuto", event.currentTarget.checked)}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
{subtitleAuto() ? "on" : "off"}
|
||||
@ -489,7 +510,7 @@ export const Playground = {
|
||||
max="40"
|
||||
step="1"
|
||||
value={subtitleTravel()}
|
||||
onInput={(event) => setSubtitleTravel(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("subtitleTravel", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{subtitleTravel()}px</span>
|
||||
@ -504,7 +525,7 @@ export const Playground = {
|
||||
max="40"
|
||||
step="1"
|
||||
value={subtitleEdge()}
|
||||
onInput={(event) => setSubtitleEdge(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("subtitleEdge", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{subtitleEdge()}%</span>
|
||||
@ -523,7 +544,7 @@ export const Playground = {
|
||||
max="1400"
|
||||
step="10"
|
||||
value={countDuration()}
|
||||
onInput={(event) => setCountDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("countDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
@ -540,7 +561,7 @@ export const Playground = {
|
||||
max="40"
|
||||
step="1"
|
||||
value={countMask()}
|
||||
onInput={(event) => setCountMask(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("countMask", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{countMask()}%</span>
|
||||
@ -555,7 +576,7 @@ export const Playground = {
|
||||
max="14"
|
||||
step="1"
|
||||
value={countMaskHeight()}
|
||||
onInput={(event) => setCountMaskHeight(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("countMaskHeight", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>{countMaskHeight()}px</span>
|
||||
@ -570,7 +591,7 @@ export const Playground = {
|
||||
max="1200"
|
||||
step="10"
|
||||
value={countWidthDuration()}
|
||||
onInput={(event) => setCountWidthDuration(event.currentTarget.valueAsNumber)}
|
||||
onInput={(event) => setCfg("countWidthDuration", event.currentTarget.valueAsNumber)}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
<span style={{ width: "64px", "text-align": "right", "font-size": "13px" }}>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import { createSignal, onCleanup } from "solid-js"
|
||||
import { onCleanup } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { AnimatedCountList, type CountItem } from "./tool-count-summary"
|
||||
import { ToolStatusTitle } from "./tool-status-title"
|
||||
|
||||
@ -57,11 +58,18 @@ const smallBtn = (active?: boolean) =>
|
||||
|
||||
export const Playground = {
|
||||
render: () => {
|
||||
const [reads, setReads] = createSignal(0)
|
||||
const [searches, setSearches] = createSignal(0)
|
||||
const [lists, setLists] = createSignal(0)
|
||||
const [active, setActive] = createSignal(false)
|
||||
const [reducedMotion, setReducedMotion] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
reads: 0,
|
||||
searches: 0,
|
||||
lists: 0,
|
||||
active: false,
|
||||
reducedMotion: false,
|
||||
})
|
||||
const reads = () => state.reads
|
||||
const searches = () => state.searches
|
||||
const lists = () => state.lists
|
||||
const active = () => state.active
|
||||
const reducedMotion = () => state.reducedMotion
|
||||
|
||||
let timeouts: ReturnType<typeof setTimeout>[] = []
|
||||
|
||||
@ -74,10 +82,10 @@ export const Playground = {
|
||||
|
||||
const startSim = () => {
|
||||
clearAll()
|
||||
setReads(0)
|
||||
setSearches(0)
|
||||
setLists(0)
|
||||
setActive(true)
|
||||
setState("reads", 0)
|
||||
setState("searches", 0)
|
||||
setState("lists", 0)
|
||||
setState("active", true)
|
||||
const steps = rand(3, 10)
|
||||
let elapsed = 0
|
||||
|
||||
@ -86,27 +94,27 @@ export const Playground = {
|
||||
elapsed += delay
|
||||
const t = setTimeout(() => {
|
||||
const pick = rand(0, 2)
|
||||
if (pick === 0) setReads((n) => n + 1)
|
||||
else if (pick === 1) setSearches((n) => n + 1)
|
||||
else setLists((n) => n + 1)
|
||||
if (pick === 0) setState("reads", (value) => value + 1)
|
||||
else if (pick === 1) setState("searches", (value) => value + 1)
|
||||
else setState("lists", (value) => value + 1)
|
||||
}, elapsed)
|
||||
timeouts.push(t)
|
||||
}
|
||||
|
||||
const end = setTimeout(() => setActive(false), elapsed + 100)
|
||||
const end = setTimeout(() => setState("active", false), elapsed + 100)
|
||||
timeouts.push(end)
|
||||
}
|
||||
|
||||
const stopSim = () => {
|
||||
clearAll()
|
||||
setActive(false)
|
||||
setState("active", false)
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
stopSim()
|
||||
setReads(0)
|
||||
setSearches(0)
|
||||
setLists(0)
|
||||
setState("reads", 0)
|
||||
setState("searches", 0)
|
||||
setState("lists", 0)
|
||||
}
|
||||
|
||||
const items = (): CountItem[] => [
|
||||
@ -164,19 +172,19 @@ export const Playground = {
|
||||
<button onClick={reset} style={btn()}>
|
||||
Reset
|
||||
</button>
|
||||
<button onClick={() => setReducedMotion((v) => !v)} style={smallBtn(reducedMotion())}>
|
||||
<button onClick={() => setState("reducedMotion", (value) => !value)} style={smallBtn(reducedMotion())}>
|
||||
{reducedMotion() ? "Motion: reduced" : "Motion: normal"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{ display: "flex", gap: "8px", "flex-wrap": "wrap" }}>
|
||||
<button onClick={() => setReads((n) => n + 1)} style={smallBtn()}>
|
||||
<button onClick={() => setState("reads", (value) => value + 1)} style={smallBtn()}>
|
||||
+ read
|
||||
</button>
|
||||
<button onClick={() => setSearches((n) => n + 1)} style={smallBtn()}>
|
||||
<button onClick={() => setState("searches", (value) => value + 1)} style={smallBtn()}>
|
||||
+ search
|
||||
</button>
|
||||
<button onClick={() => setLists((n) => n + 1)} style={smallBtn()}>
|
||||
<button onClick={() => setState("lists", (value) => value + 1)} style={smallBtn()}>
|
||||
+ list
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { type ComponentProps, createMemo, createSignal, Show, splitProps } from "solid-js"
|
||||
import { type ComponentProps, createMemo, Show, splitProps } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { Card, CardDescription } from "./card"
|
||||
import { Collapsible } from "./collapsible"
|
||||
import { Icon } from "./icon"
|
||||
@ -16,8 +17,12 @@ export interface ToolErrorCardProps extends Omit<ComponentProps<typeof Card>, "c
|
||||
|
||||
export function ToolErrorCard(props: ToolErrorCardProps) {
|
||||
const i18n = useI18n()
|
||||
const [open, setOpen] = createSignal(props.defaultOpen ?? false)
|
||||
const [copied, setCopied] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
open: props.defaultOpen ?? false,
|
||||
copied: false,
|
||||
})
|
||||
const open = () => state.open
|
||||
const copied = () => state.copied
|
||||
const [split, rest] = splitProps(props, ["tool", "error", "defaultOpen", "subtitle", "href"])
|
||||
const name = createMemo(() => {
|
||||
const map: Record<string, string> = {
|
||||
@ -65,13 +70,18 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
|
||||
const text = cleaned()
|
||||
if (!text) return
|
||||
await navigator.clipboard.writeText(text)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
setState("copied", true)
|
||||
setTimeout(() => setState("copied", false), 2000)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card {...rest} data-kind="tool-error-card" data-open={open() ? "true" : "false"} variant="error">
|
||||
<Collapsible class="tool-collapsible" data-open={open() ? "true" : "false"} open={open()} onOpenChange={setOpen}>
|
||||
<Collapsible
|
||||
class="tool-collapsible"
|
||||
data-open={open() ? "true" : "false"}
|
||||
open={open()}
|
||||
onOpenChange={(value) => setState("open", value)}
|
||||
>
|
||||
<Collapsible.Trigger>
|
||||
<div data-component="tool-trigger">
|
||||
<div data-slot="basic-tool-tool-trigger-content">
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js"
|
||||
import { Show, createEffect, createMemo, on, onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { TextShimmer } from "./text-shimmer"
|
||||
|
||||
function common(active: string, done: string) {
|
||||
@ -35,8 +36,12 @@ export function ToolStatusTitle(props: {
|
||||
const activeTail = createMemo(() => (suffix() ? split().active : props.activeText))
|
||||
const doneTail = createMemo(() => (suffix() ? split().done : props.doneText))
|
||||
|
||||
const [width, setWidth] = createSignal("auto")
|
||||
const [ready, setReady] = createSignal(false)
|
||||
const [state, setState] = createStore({
|
||||
width: "auto",
|
||||
ready: false,
|
||||
})
|
||||
const width = () => state.width
|
||||
const ready = () => state.ready
|
||||
let activeRef: HTMLSpanElement | undefined
|
||||
let doneRef: HTMLSpanElement | undefined
|
||||
let frame: number | undefined
|
||||
@ -45,7 +50,7 @@ export function ToolStatusTitle(props: {
|
||||
const measure = () => {
|
||||
const target = props.active ? activeRef : doneRef
|
||||
const px = contentWidth(target)
|
||||
if (px > 0) setWidth(`${px}px`)
|
||||
if (px > 0) setState("width", `${px}px`)
|
||||
}
|
||||
|
||||
const schedule = () => {
|
||||
@ -62,13 +67,13 @@ export function ToolStatusTitle(props: {
|
||||
|
||||
const finish = () => {
|
||||
if (typeof requestAnimationFrame !== "function") {
|
||||
setReady(true)
|
||||
setState("ready", true)
|
||||
return
|
||||
}
|
||||
if (readyFrame !== undefined) cancelAnimationFrame(readyFrame)
|
||||
readyFrame = requestAnimationFrame(() => {
|
||||
readyFrame = undefined
|
||||
setReady(true)
|
||||
setState("ready", true)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createEffect, createSignal, onCleanup, onMount } from "solid-js"
|
||||
import { createEffect, onCleanup, onMount } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
|
||||
export type FindHost = {
|
||||
element: () => HTMLElement | undefined
|
||||
@ -107,11 +108,18 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
let mode: "highlights" | "overlay" = "overlay"
|
||||
let hits: Range[] = []
|
||||
|
||||
const [open, setOpen] = createSignal(false)
|
||||
const [query, setQuery] = createSignal("")
|
||||
const [index, setIndex] = createSignal(0)
|
||||
const [count, setCount] = createSignal(0)
|
||||
const [pos, setPos] = createSignal({ top: 8, right: 8 })
|
||||
const [state, setState] = createStore({
|
||||
open: false,
|
||||
query: "",
|
||||
index: 0,
|
||||
count: 0,
|
||||
pos: { top: 8, right: 8 },
|
||||
})
|
||||
const open = () => state.open
|
||||
const query = () => state.query
|
||||
const index = () => state.index
|
||||
const count = () => state.count
|
||||
const pos = () => state.pos
|
||||
|
||||
const clearOverlayScroll = () => {
|
||||
for (const el of overlayScroll) el.removeEventListener("scroll", scheduleOverlay)
|
||||
@ -200,8 +208,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
clearOverlay()
|
||||
clearOverlayScroll()
|
||||
hits = []
|
||||
setCount(0)
|
||||
setIndex(0)
|
||||
setState("count", 0)
|
||||
setState("index", 0)
|
||||
}
|
||||
|
||||
const positionBar = () => {
|
||||
@ -214,7 +222,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
const title = parseFloat(getComputedStyle(root).getPropertyValue("--session-title-height"))
|
||||
const header = Number.isNaN(title) ? 0 : title
|
||||
|
||||
setPos({
|
||||
setState("pos", {
|
||||
top: Math.round(rect.top) + header - 4,
|
||||
right: Math.round(window.innerWidth - rect.right) + 8,
|
||||
})
|
||||
@ -318,8 +326,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
const currentIndex = total ? Math.min(desired, total - 1) : 0
|
||||
|
||||
hits = ranges
|
||||
setCount(total)
|
||||
setIndex(currentIndex)
|
||||
setState("count", total)
|
||||
setState("index", currentIndex)
|
||||
|
||||
const active = ranges[currentIndex]
|
||||
if (mode === "highlights") {
|
||||
@ -342,8 +350,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
setOpen(false)
|
||||
setQuery("")
|
||||
setState("open", false)
|
||||
setState("query", "")
|
||||
clearFind()
|
||||
if (current === host) current = undefined
|
||||
}
|
||||
@ -352,7 +360,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
if (current && current !== host) current.close()
|
||||
current = host
|
||||
target = host
|
||||
if (!open()) setOpen(true)
|
||||
if (!open()) setState("open", true)
|
||||
requestAnimationFrame(() => {
|
||||
apply({ scroll: true })
|
||||
input?.focus()
|
||||
@ -366,7 +374,7 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
if (total <= 0) return
|
||||
|
||||
const currentIndex = (index() + dir + total) % total
|
||||
setIndex(currentIndex)
|
||||
setState("index", currentIndex)
|
||||
|
||||
const active = hits[currentIndex]
|
||||
if (!active) return
|
||||
@ -449,8 +457,8 @@ export function createFileFind(opts: CreateFileFindOptions) {
|
||||
input = el
|
||||
},
|
||||
setQuery: (value: string) => {
|
||||
setQuery(value)
|
||||
setIndex(0)
|
||||
setState("query", value)
|
||||
setState("index", 0)
|
||||
apply({ reset: true, scroll: true })
|
||||
},
|
||||
focus,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user