mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 05:43:55 +00:00
chore: refactoring ui hooks
This commit is contained in:
parent
c797b60069
commit
f386137fba
7
bun.lock
7
bun.lock
@ -483,8 +483,11 @@
|
|||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@shikijs/transformers": "3.9.2",
|
"@shikijs/transformers": "3.9.2",
|
||||||
"@solid-primitives/bounds": "0.1.3",
|
"@solid-primitives/bounds": "0.1.3",
|
||||||
|
"@solid-primitives/lifecycle": "0.1.2",
|
||||||
"@solid-primitives/media": "2.3.3",
|
"@solid-primitives/media": "2.3.3",
|
||||||
|
"@solid-primitives/page-visibility": "2.1.1",
|
||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
|
"@solid-primitives/rootless": "1.5.2",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
@ -1834,10 +1837,14 @@
|
|||||||
|
|
||||||
"@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA=="],
|
"@solid-primitives/keyed": ["@solid-primitives/keyed@1.5.3", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA=="],
|
||||||
|
|
||||||
|
"@solid-primitives/lifecycle": ["@solid-primitives/lifecycle@0.1.2", "", { "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-+K0T10kZXqorocFj0coIqt8NYm2UqoZfpF3nm2RwrDMZMV+C+SC0Oi3N6Dnq2i7W/n1cHAnfpoV4CBLsW21lJw=="],
|
||||||
|
|
||||||
"@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="],
|
"@solid-primitives/map": ["@solid-primitives/map@0.4.13", "", { "dependencies": { "@solid-primitives/trigger": "^1.1.0" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew=="],
|
||||||
|
|
||||||
"@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="],
|
"@solid-primitives/media": ["@solid-primitives/media@2.3.3", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.3", "@solid-primitives/rootless": "^1.5.2", "@solid-primitives/static-store": "^0.1.2", "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-hQ4hLOGvfbugQi5Eu1BFWAIJGIAzztq9x0h02xgBGl2l0Jaa3h7tg6bz5tV1NSuNYVGio4rPoa7zVQQLkkx9dA=="],
|
||||||
|
|
||||||
|
"@solid-primitives/page-visibility": ["@solid-primitives/page-visibility@2.1.1", "", { "dependencies": { "@solid-primitives/event-listener": "^2.4.1", "@solid-primitives/rootless": "^1.5.1", "@solid-primitives/utils": "^6.3.1" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-CV9BqMqhunf4OOyBkhJCH9f5ivg0ADavdcaBsrqoFvwIk1FoD/blPSHYM4CK8IjS/AEXNcsjlNVc34lMu+2Wdg=="],
|
||||||
|
|
||||||
"@solid-primitives/props": ["@solid-primitives/props@3.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw=="],
|
"@solid-primitives/props": ["@solid-primitives/props@3.2.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-lZOTwFJajBrshSyg14nBMEP0h8MXzPowGO0s3OeiR3z6nXHTfj0FhzDtJMv+VYoRJKQHG2QRnJTgCzK6erARAw=="],
|
||||||
|
|
||||||
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg=="],
|
"@solid-primitives/refs": ["@solid-primitives/refs@1.1.2", "", { "dependencies": { "@solid-primitives/utils": "^6.3.2" }, "peerDependencies": { "solid-js": "^1.6.12" } }, "sha512-K7tf2thy7L+YJjdqXspXOg5xvNEOH8tgEWsp0+1mQk3obHBRD6hEjYZk7p7FlJphSZImS35je3UfmWuD7MhDfg=="],
|
||||||
|
|||||||
@ -2,10 +2,10 @@ import { createEffect, createMemo, on, onCleanup, Show } from "solid-js"
|
|||||||
import { createStore, produce } from "solid-js/store"
|
import { createStore, produce } from "solid-js/store"
|
||||||
import { useNavigate, useParams } from "@solidjs/router"
|
import { useNavigate, useParams } from "@solidjs/router"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
|
import { useReducedMotion } from "@opencode-ai/ui/hooks"
|
||||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||||
import { prefersReducedMotion } from "@opencode-ai/ui/hooks"
|
|
||||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||||
import { animate, type AnimationPlaybackControls, clearFadeStyles, FAST_SPRING } from "@opencode-ai/ui/motion"
|
import { animate, type AnimationPlaybackControls, clearFadeStyles, FAST_SPRING } from "@opencode-ai/ui/motion"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
@ -32,7 +32,7 @@ export function SessionTimelineHeader(props: {
|
|||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
const reduce = prefersReducedMotion
|
const reduce = useReducedMotion()
|
||||||
|
|
||||||
const [title, setTitle] = createStore({
|
const [title, setTitle] = createStore({
|
||||||
draft: "",
|
draft: "",
|
||||||
|
|||||||
@ -48,8 +48,11 @@
|
|||||||
"@pierre/diffs": "catalog:",
|
"@pierre/diffs": "catalog:",
|
||||||
"@shikijs/transformers": "3.9.2",
|
"@shikijs/transformers": "3.9.2",
|
||||||
"@solid-primitives/bounds": "0.1.3",
|
"@solid-primitives/bounds": "0.1.3",
|
||||||
|
"@solid-primitives/lifecycle": "0.1.2",
|
||||||
"@solid-primitives/media": "2.3.3",
|
"@solid-primitives/media": "2.3.3",
|
||||||
|
"@solid-primitives/page-visibility": "2.1.1",
|
||||||
"@solid-primitives/resize-observer": "2.1.3",
|
"@solid-primitives/resize-observer": "2.1.3",
|
||||||
|
"@solid-primitives/rootless": "1.5.2",
|
||||||
"@solidjs/meta": "catalog:",
|
"@solidjs/meta": "catalog:",
|
||||||
"@solidjs/router": "catalog:",
|
"@solidjs/router": "catalog:",
|
||||||
"dompurify": "3.3.1",
|
"dompurify": "3.3.1",
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { createMemo, createSignal, For, onMount } from "solid-js"
|
import { createMemo, createSignal, For, onMount } from "solid-js"
|
||||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
||||||
import { getFilename } from "@opencode-ai/util/path"
|
import { getFilename } from "@opencode-ai/util/path"
|
||||||
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
|
||||||
import { ToolCall } from "./basic-tool"
|
import { ToolCall } from "./basic-tool"
|
||||||
import { ToolStatusTitle } from "./tool-status-title"
|
import { ToolStatusTitle } from "./tool-status-title"
|
||||||
import { AnimatedCountList } from "./tool-count-summary"
|
import { AnimatedCountList } from "./tool-count-summary"
|
||||||
@ -149,10 +149,10 @@ export function ContextToolExpandedList(props: { parts: ToolPart[]; expanded: bo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ContextToolRollingResults(props: { parts: ToolPart[]; pending: boolean }) {
|
export function ContextToolRollingResults(props: { parts: ToolPart[]; pending: boolean }) {
|
||||||
|
const reduce = useReducedMotion()
|
||||||
const wiped = new Set<string>()
|
const wiped = new Set<string>()
|
||||||
const [mounted, setMounted] = createSignal(false)
|
const [mounted, setMounted] = createSignal(false)
|
||||||
onMount(() => setMounted(true))
|
onMount(() => setMounted(true))
|
||||||
const reduce = prefersReducedMotion
|
|
||||||
const show = () => mounted() && props.pending
|
const show = () => mounted() && props.pending
|
||||||
const opacity = useSpring(() => (show() ? 1 : 0), GROW_SPRING)
|
const opacity = useSpring(() => (show() ? 1 : 0), GROW_SPRING)
|
||||||
const blur = useSpring(() => (show() ? 0 : 2), GROW_SPRING)
|
const blur = useSpring(() => (show() ? 0 : 2), GROW_SPRING)
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { createEffect, on, type JSX, onMount, onCleanup } from "solid-js"
|
import { createEffect, on, type JSX, onMount, onCleanup } from "solid-js"
|
||||||
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import { animate, tunableSpringValue, type AnimationPlaybackControls, GROW_SPRING, type SpringConfig } from "./motion"
|
import { animate, tunableSpringValue, type AnimationPlaybackControls, GROW_SPRING, type SpringConfig } from "./motion"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
|
||||||
|
|
||||||
export interface GrowBoxProps {
|
export interface GrowBoxProps {
|
||||||
children: JSX.Element
|
children: JSX.Element
|
||||||
@ -49,7 +49,7 @@ export interface GrowBoxProps {
|
|||||||
* Used for timeline turns, assistant part groups, and user messages.
|
* Used for timeline turns, assistant part groups, and user messages.
|
||||||
*/
|
*/
|
||||||
export function GrowBox(props: GrowBoxProps) {
|
export function GrowBox(props: GrowBoxProps) {
|
||||||
const reduce = prefersReducedMotion
|
const reduce = useReducedMotion()
|
||||||
const spring = () => props.spring ?? GROW_SPRING
|
const spring = () => props.spring ?? GROW_SPRING
|
||||||
const toggleSpring = () => props.toggleSpring ?? spring()
|
const toggleSpring = () => props.toggleSpring ?? spring()
|
||||||
let mode: "mount" | "toggle" = "mount"
|
let mode: "mount" | "toggle" = "mount"
|
||||||
@ -293,6 +293,18 @@ export function GrowBox(props: GrowBoxProps) {
|
|||||||
offChange()
|
offChange()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (watch()) {
|
||||||
|
observer = new ResizeObserver(() => {
|
||||||
|
if (!open()) return
|
||||||
|
if (resizeFrame !== undefined) return
|
||||||
|
resizeFrame = requestAnimationFrame(() => {
|
||||||
|
resizeFrame = undefined
|
||||||
|
setHeight("mount")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
observer.observe(body)
|
||||||
|
}
|
||||||
|
|
||||||
if (!animated()) {
|
if (!animated()) {
|
||||||
setInstant(open())
|
setInstant(open())
|
||||||
return
|
return
|
||||||
@ -318,17 +330,6 @@ export function GrowBox(props: GrowBoxProps) {
|
|||||||
if (grow()) setHeight("mount")
|
if (grow()) setHeight("mount")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (watch()) {
|
|
||||||
observer = new ResizeObserver(() => {
|
|
||||||
if (!open()) return
|
|
||||||
if (resizeFrame !== undefined) return
|
|
||||||
resizeFrame = requestAnimationFrame(() => {
|
|
||||||
resizeFrame = undefined
|
|
||||||
setHeight("mount")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
observer.observe(body)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
@ -402,7 +403,12 @@ export function GrowBox(props: GrowBoxProps) {
|
|||||||
ref={root}
|
ref={root}
|
||||||
data-slot={props.slot}
|
data-slot={props.slot}
|
||||||
class={props.class}
|
class={props.class}
|
||||||
style={{ transform: "translateZ(0)", position: "relative" }}
|
style={{
|
||||||
|
transform: "translateZ(0)",
|
||||||
|
position: "relative",
|
||||||
|
height: open() ? undefined : "0px",
|
||||||
|
overflow: open() ? undefined : "clip",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div ref={body} style={{ "padding-top": gap() > 0 ? `${gap()}px` : undefined }}>
|
<div ref={body} style={{ "padding-top": gap() > 0 ? `${gap()}px` : undefined }}>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { usePageVisibility } from "@solid-primitives/page-visibility"
|
||||||
import { Component, createEffect, createMemo, createSignal, For, Match, on, Show, Switch, type JSX } from "solid-js"
|
import { Component, createEffect, createMemo, createSignal, For, Match, on, Show, Switch, type JSX } from "solid-js"
|
||||||
import stripAnsi from "strip-ansi"
|
import stripAnsi from "strip-ansi"
|
||||||
import { createStore } from "solid-js/store"
|
import { createStore } from "solid-js/store"
|
||||||
@ -254,8 +255,6 @@ function urls(text: string | undefined) {
|
|||||||
const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"])
|
const CONTEXT_GROUP_TOOLS = new Set(["read", "glob", "grep", "list"])
|
||||||
const HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
|
const HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
|
||||||
|
|
||||||
import { pageVisible } from "../hooks/use-page-visible"
|
|
||||||
|
|
||||||
function createGroupOpenState() {
|
function createGroupOpenState() {
|
||||||
const [state, setState] = createStore<Record<string, boolean>>({})
|
const [state, setState] = createStore<Record<string, boolean>>({})
|
||||||
const read = (key?: string, collapse?: boolean) => {
|
const read = (key?: string, collapse?: boolean) => {
|
||||||
@ -277,6 +276,7 @@ function createGroupOpenState() {
|
|||||||
function shouldCollapseGroup(
|
function shouldCollapseGroup(
|
||||||
statuses: (string | undefined)[],
|
statuses: (string | undefined)[],
|
||||||
opts: { afterTool?: boolean; groupTail?: boolean; working?: boolean },
|
opts: { afterTool?: boolean; groupTail?: boolean; working?: boolean },
|
||||||
|
pageVisible: () => boolean,
|
||||||
) {
|
) {
|
||||||
if (opts.afterTool) return true
|
if (opts.afterTool) return true
|
||||||
if (opts.groupTail === false) return true
|
if (opts.groupTail === false) return true
|
||||||
@ -363,6 +363,7 @@ export function AssistantParts(props: {
|
|||||||
}) {
|
}) {
|
||||||
const data = useData()
|
const data = useData()
|
||||||
const emptyParts: PartType[] = []
|
const emptyParts: PartType[] = []
|
||||||
|
const pageVisible = usePageVisibility()
|
||||||
const groupState = createGroupOpenState()
|
const groupState = createGroupOpenState()
|
||||||
const grouped = createMemo(() => {
|
const grouped = createMemo(() => {
|
||||||
const keys: string[] = []
|
const keys: string[] = []
|
||||||
@ -485,11 +486,15 @@ export function AssistantParts(props: {
|
|||||||
groupTail?: boolean,
|
groupTail?: boolean,
|
||||||
group?: { part: ToolPart; message: AssistantMessage }[],
|
group?: { part: ToolPart; message: AssistantMessage }[],
|
||||||
) =>
|
) =>
|
||||||
shouldCollapseGroup(group?.map((item) => item.part.state.status) ?? [], {
|
shouldCollapseGroup(
|
||||||
afterTool,
|
group?.map((item) => item.part.state.status) ?? [],
|
||||||
groupTail,
|
{
|
||||||
working: props.working,
|
afterTool,
|
||||||
})
|
groupTail,
|
||||||
|
working: props.working,
|
||||||
|
},
|
||||||
|
pageVisible,
|
||||||
|
)
|
||||||
const value = ctx()
|
const value = ctx()
|
||||||
if (value) return groupState.read(value.groupKey, collapse(value.afterTool, value.tail, value.parts))
|
if (value) return groupState.read(value.groupKey, collapse(value.afterTool, value.tail, value.parts))
|
||||||
const entry = part()
|
const entry = part()
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { attachSpring, motionValue } from "motion"
|
import { attachSpring, motionValue } from "motion"
|
||||||
import type { SpringOptions } from "motion"
|
import type { SpringOptions } from "motion"
|
||||||
import { createEffect, createSignal, onCleanup } from "solid-js"
|
import { createEffect, createSignal, onCleanup } from "solid-js"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
|
|
||||||
type Opt = Pick<SpringOptions, "visualDuration" | "bounce" | "stiffness" | "damping" | "mass" | "velocity">
|
type Opt = Pick<SpringOptions, "visualDuration" | "bounce" | "stiffness" | "damping" | "mass" | "velocity">
|
||||||
const eq = (a: Opt | undefined, b: Opt | undefined) =>
|
const eq = (a: Opt | undefined, b: Opt | undefined) =>
|
||||||
@ -14,7 +14,7 @@ const eq = (a: Opt | undefined, b: Opt | undefined) =>
|
|||||||
|
|
||||||
export function useSpring(target: () => number, options?: Opt | (() => Opt)) {
|
export function useSpring(target: () => number, options?: Opt | (() => Opt)) {
|
||||||
const read = () => (typeof options === "function" ? options() : options)
|
const read = () => (typeof options === "function" ? options() : options)
|
||||||
const reduce = prefersReducedMotion
|
const reduce = useReducedMotion()
|
||||||
const [value, setValue] = createSignal(target())
|
const [value, setValue] = createSignal(target())
|
||||||
const source = motionValue(value())
|
const source = motionValue(value())
|
||||||
const spring = motionValue(value())
|
const spring = motionValue(value())
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { For, Show, batch, createEffect, createMemo, createSignal, on, onCleanup, onMount, type JSX } from "solid-js"
|
import { For, Show, batch, createEffect, createMemo, createSignal, on, onCleanup, onMount, type JSX } from "solid-js"
|
||||||
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import { animate, clearMaskStyles, GROW_SPRING, type AnimationPlaybackControls, type SpringConfig } from "./motion"
|
import { animate, clearMaskStyles, GROW_SPRING, type AnimationPlaybackControls, type SpringConfig } from "./motion"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
|
||||||
|
|
||||||
export type RollingResultsProps<T> = {
|
export type RollingResultsProps<T> = {
|
||||||
items: T[]
|
items: T[]
|
||||||
@ -27,8 +27,7 @@ export function RollingResults<T>(props: RollingResultsProps<T>) {
|
|||||||
let shift: AnimationPlaybackControls | undefined
|
let shift: AnimationPlaybackControls | undefined
|
||||||
let resize: AnimationPlaybackControls | undefined
|
let resize: AnimationPlaybackControls | undefined
|
||||||
let edgeFade: AnimationPlaybackControls | undefined
|
let edgeFade: AnimationPlaybackControls | undefined
|
||||||
|
const reduce = useReducedMotion()
|
||||||
const reducedMotion = prefersReducedMotion
|
|
||||||
|
|
||||||
const rows = createMemo(() => Math.max(1, Math.round(props.rows ?? 3)))
|
const rows = createMemo(() => Math.max(1, Math.round(props.rows ?? 3)))
|
||||||
const rowHeight = createMemo(() => Math.max(16, Math.round(props.rowHeight ?? 22)))
|
const rowHeight = createMemo(() => Math.max(16, Math.round(props.rowHeight ?? 22)))
|
||||||
@ -54,7 +53,7 @@ export function RollingResults<T>(props: RollingResultsProps<T>) {
|
|||||||
return count() - rendered().length
|
return count() - rendered().length
|
||||||
})
|
})
|
||||||
const open = createMemo(() => props.open !== false)
|
const open = createMemo(() => props.open !== false)
|
||||||
const active = createMemo(() => (props.animate !== false || props.spring !== undefined) && !reducedMotion())
|
const active = createMemo(() => (props.animate !== false || props.spring !== undefined) && !reduce())
|
||||||
const noFade = () => props.noFadeOnCollapse === true
|
const noFade = () => props.noFadeOnCollapse === true
|
||||||
const overflowing = createMemo(() => count() > rows())
|
const overflowing = createMemo(() => count() > rows())
|
||||||
const shown = createMemo(() => Math.min(rows(), count()))
|
const shown = createMemo(() => Math.min(rows(), count()))
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||||
import stripAnsi from "strip-ansi"
|
import stripAnsi from "strip-ansi"
|
||||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import { useI18n } from "../context/i18n"
|
import { useI18n } from "../context/i18n"
|
||||||
import { RollingResults } from "./rolling-results"
|
import { RollingResults } from "./rolling-results"
|
||||||
import { Icon } from "./icon"
|
import { Icon } from "./icon"
|
||||||
@ -178,6 +178,7 @@ function ShellExpanded(props: { cmd: string; out: string; open: boolean }) {
|
|||||||
|
|
||||||
export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }) {
|
export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }) {
|
||||||
const i18n = useI18n()
|
const i18n = useI18n()
|
||||||
|
const reduce = useReducedMotion()
|
||||||
const wiped = new Set<string>()
|
const wiped = new Set<string>()
|
||||||
const [mounted, setMounted] = createSignal(false)
|
const [mounted, setMounted] = createSignal(false)
|
||||||
const [userToggled, setUserToggled] = createSignal(false)
|
const [userToggled, setUserToggled] = createSignal(false)
|
||||||
@ -208,7 +209,6 @@ export function ShellRollingResults(props: { part: ToolPart; animate?: boolean }
|
|||||||
if (typeof value === "string") return value
|
if (typeof value === "string") return value
|
||||||
return ""
|
return ""
|
||||||
})
|
})
|
||||||
const reduce = prefersReducedMotion
|
|
||||||
const skip = () => reduce() || props.animate === false
|
const skip = () => reduce() || props.animate === false
|
||||||
const opacity = useSpring(() => (mounted() ? 1 : 0), GROW_SPRING)
|
const opacity = useSpring(() => (mounted() ? 1 : 0), GROW_SPRING)
|
||||||
const blur = useSpring(() => (mounted() ? 0 : 2), GROW_SPRING)
|
const blur = useSpring(() => (mounted() ? 0 : 2), GROW_SPRING)
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"
|
import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"
|
||||||
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import {
|
import {
|
||||||
animate,
|
animate,
|
||||||
type AnimationPlaybackControls,
|
type AnimationPlaybackControls,
|
||||||
@ -7,7 +8,6 @@ import {
|
|||||||
GROW_SPRING,
|
GROW_SPRING,
|
||||||
WIPE_MASK,
|
WIPE_MASK,
|
||||||
} from "./motion"
|
} from "./motion"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
|
||||||
|
|
||||||
const px = (value: number | string | undefined, fallback: number) => {
|
const px = (value: number | string | undefined, fallback: number) => {
|
||||||
if (typeof value === "number") return `${value}px`
|
if (typeof value === "number") return `${value}px`
|
||||||
@ -143,12 +143,13 @@ export function TextWipe(props: { text?: string; class?: string; delay?: number;
|
|||||||
let ref: HTMLSpanElement | undefined
|
let ref: HTMLSpanElement | undefined
|
||||||
let frame: number | undefined
|
let frame: number | undefined
|
||||||
let anim: AnimationPlaybackControls | undefined
|
let anim: AnimationPlaybackControls | undefined
|
||||||
|
const reduce = useReducedMotion()
|
||||||
|
|
||||||
const run = () => {
|
const run = () => {
|
||||||
if (props.animate === false) return
|
if (props.animate === false) return
|
||||||
const el = ref
|
const el = ref
|
||||||
if (!el || !props.text || typeof window === "undefined") return
|
if (!el || !props.text || typeof window === "undefined") return
|
||||||
if (prefersReducedMotion()) return
|
if (reduce()) return
|
||||||
|
|
||||||
const mask =
|
const mask =
|
||||||
typeof CSS !== "undefined" &&
|
typeof CSS !== "undefined" &&
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js"
|
import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js"
|
||||||
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import { animate, type AnimationPlaybackControls, GROW_SPRING } from "./motion"
|
import { animate, type AnimationPlaybackControls, GROW_SPRING } from "./motion"
|
||||||
import { TextShimmer } from "./text-shimmer"
|
import { TextShimmer } from "./text-shimmer"
|
||||||
import { commonPrefix } from "./text-utils"
|
import { commonPrefix } from "./text-utils"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
|
||||||
|
|
||||||
function contentWidth(el: HTMLSpanElement | undefined) {
|
function contentWidth(el: HTMLSpanElement | undefined) {
|
||||||
if (!el) return 0
|
if (!el) return 0
|
||||||
@ -18,6 +18,7 @@ export function ToolStatusTitle(props: {
|
|||||||
class?: string
|
class?: string
|
||||||
split?: boolean
|
split?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
const reduce = useReducedMotion()
|
||||||
const split = createMemo(() => commonPrefix(props.activeText, props.doneText))
|
const split = createMemo(() => commonPrefix(props.activeText, props.doneText))
|
||||||
const suffix = createMemo(
|
const suffix = createMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -38,8 +39,6 @@ export function ToolStatusTitle(props: {
|
|||||||
|
|
||||||
const node = () => (suffix() ? tailRef : swapRef)
|
const node = () => (suffix() ? tailRef : swapRef)
|
||||||
|
|
||||||
const reduce = prefersReducedMotion
|
|
||||||
|
|
||||||
const setNodeWidth = (width: string) => {
|
const setNodeWidth = (width: string) => {
|
||||||
if (swapRef) swapRef.style.width = width
|
if (swapRef) swapRef.style.width = width
|
||||||
if (tailRef) tailRef.style.width = width
|
if (tailRef) tailRef.style.width = width
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
|
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
||||||
import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js"
|
import { createEffect, createMemo, createSignal, on, onCleanup, onMount } from "solid-js"
|
||||||
|
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||||
import {
|
import {
|
||||||
animate,
|
animate,
|
||||||
type AnimationPlaybackControls,
|
type AnimationPlaybackControls,
|
||||||
@ -8,8 +10,6 @@ import {
|
|||||||
GROW_SPRING,
|
GROW_SPRING,
|
||||||
WIPE_MASK,
|
WIPE_MASK,
|
||||||
} from "./motion"
|
} from "./motion"
|
||||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
|
||||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
|
||||||
|
|
||||||
export const TEXT_RENDER_THROTTLE_MS = 100
|
export const TEXT_RENDER_THROTTLE_MS = 100
|
||||||
|
|
||||||
@ -106,57 +106,67 @@ export function useCollapsible(options: {
|
|||||||
measure?: () => number
|
measure?: () => number
|
||||||
onOpen?: () => void
|
onOpen?: () => void
|
||||||
}) {
|
}) {
|
||||||
|
const reduce = useReducedMotion()
|
||||||
let heightAnim: AnimationPlaybackControls | undefined
|
let heightAnim: AnimationPlaybackControls | undefined
|
||||||
let fadeAnim: AnimationPlaybackControls | undefined
|
let fadeAnim: AnimationPlaybackControls | undefined
|
||||||
let gen = 0
|
let gen = 0
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(options.open, (isOpen) => {
|
||||||
options.open,
|
const content = options.content()
|
||||||
(isOpen) => {
|
const body = options.body()
|
||||||
const content = options.content()
|
if (!content || !body) return
|
||||||
const body = options.body()
|
heightAnim?.stop()
|
||||||
if (!content || !body) return
|
fadeAnim?.stop()
|
||||||
heightAnim?.stop()
|
if (reduce()) {
|
||||||
fadeAnim?.stop()
|
body.style.opacity = ""
|
||||||
const id = ++gen
|
body.style.filter = ""
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
content.style.display = ""
|
content.style.display = ""
|
||||||
content.style.height = "0px"
|
content.style.height = "auto"
|
||||||
body.style.opacity = "0"
|
options.onOpen?.()
|
||||||
body.style.filter = "blur(2px)"
|
|
||||||
fadeAnim = animate(body, { opacity: [0, 1], filter: ["blur(2px)", "blur(0px)"] }, COLLAPSIBLE_SPRING)
|
|
||||||
queueMicrotask(() => {
|
|
||||||
if (gen !== id) return
|
|
||||||
const c = options.content()
|
|
||||||
if (!c) return
|
|
||||||
const h = options.measure?.() ?? Math.ceil(body.getBoundingClientRect().height)
|
|
||||||
heightAnim = animate(c, { height: ["0px", `${h}px`] }, COLLAPSIBLE_SPRING)
|
|
||||||
heightAnim.finished.then(
|
|
||||||
() => {
|
|
||||||
if (gen !== id) return
|
|
||||||
c.style.height = "auto"
|
|
||||||
options.onOpen?.()
|
|
||||||
},
|
|
||||||
() => {},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
content.style.height = "0px"
|
||||||
|
content.style.display = "none"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const id = ++gen
|
||||||
|
if (isOpen) {
|
||||||
|
content.style.display = ""
|
||||||
|
content.style.height = "0px"
|
||||||
|
body.style.opacity = "0"
|
||||||
|
body.style.filter = "blur(2px)"
|
||||||
|
fadeAnim = animate(body, { opacity: [0, 1], filter: ["blur(2px)", "blur(0px)"] }, COLLAPSIBLE_SPRING)
|
||||||
|
queueMicrotask(() => {
|
||||||
|
if (gen !== id) return
|
||||||
|
const c = options.content()
|
||||||
|
if (!c) return
|
||||||
|
const h = options.measure?.() ?? Math.ceil(body.getBoundingClientRect().height)
|
||||||
|
heightAnim = animate(c, { height: ["0px", `${h}px`] }, COLLAPSIBLE_SPRING)
|
||||||
|
heightAnim.finished.then(
|
||||||
|
() => {
|
||||||
|
if (gen !== id) return
|
||||||
|
c.style.height = "auto"
|
||||||
|
options.onOpen?.()
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const h = content.getBoundingClientRect().height
|
const h = content.getBoundingClientRect().height
|
||||||
heightAnim = animate(content, { height: [`${h}px`, "0px"] }, COLLAPSIBLE_SPRING)
|
heightAnim = animate(content, { height: [`${h}px`, "0px"] }, COLLAPSIBLE_SPRING)
|
||||||
fadeAnim = animate(body, { opacity: [1, 0], filter: ["blur(0px)", "blur(2px)"] }, COLLAPSIBLE_SPRING)
|
fadeAnim = animate(body, { opacity: [1, 0], filter: ["blur(0px)", "blur(2px)"] }, COLLAPSIBLE_SPRING)
|
||||||
heightAnim.finished.then(
|
heightAnim.finished.then(
|
||||||
() => {
|
() => {
|
||||||
if (gen !== id) return
|
if (gen !== id) return
|
||||||
content.style.display = "none"
|
content.style.display = "none"
|
||||||
},
|
},
|
||||||
() => {},
|
() => {},
|
||||||
)
|
)
|
||||||
},
|
}),
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
@ -181,7 +191,7 @@ export function useRowWipe(opts: {
|
|||||||
ref: () => HTMLElement | undefined
|
ref: () => HTMLElement | undefined
|
||||||
seen: Set<string>
|
seen: Set<string>
|
||||||
}) {
|
}) {
|
||||||
const reduce = prefersReducedMotion
|
const reduce = useReducedMotion()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const id = opts.id()
|
const id = opts.id()
|
||||||
@ -265,13 +275,14 @@ export function useToolFade(
|
|||||||
const delay = options?.delay ?? 0
|
const delay = options?.delay ?? 0
|
||||||
const wipe = options?.wipe ?? false
|
const wipe = options?.wipe ?? false
|
||||||
const active = options?.animate !== false
|
const active = options?.animate !== false
|
||||||
|
const reduce = useReducedMotion()
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!active) return
|
if (!active) return
|
||||||
|
|
||||||
const el = ref()
|
const el = ref()
|
||||||
if (!el || typeof window === "undefined") return
|
if (!el || typeof window === "undefined") return
|
||||||
if (prefersReducedMotion()) return
|
if (reduce()) return
|
||||||
|
|
||||||
const mask =
|
const mask =
|
||||||
wipe &&
|
wipe &&
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
export * from "./use-filtered-list"
|
export * from "./use-filtered-list"
|
||||||
export * from "./create-auto-scroll"
|
export * from "./create-auto-scroll"
|
||||||
export * from "./use-element-height"
|
|
||||||
export * from "./use-reduced-motion"
|
export * from "./use-reduced-motion"
|
||||||
export * from "./use-page-visible"
|
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
import { createEffect, createSignal, onCleanup, type Accessor } from "solid-js"
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tracks an element's height via ResizeObserver.
|
|
||||||
* Returns a reactive signal that updates whenever the element resizes.
|
|
||||||
*/
|
|
||||||
export function useElementHeight(
|
|
||||||
ref: Accessor<HTMLElement | undefined> | (() => HTMLElement | undefined),
|
|
||||||
initial = 0,
|
|
||||||
): Accessor<number> {
|
|
||||||
const [height, setHeight] = createSignal(initial)
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const el = ref()
|
|
||||||
if (!el) return
|
|
||||||
setHeight(el.getBoundingClientRect().height)
|
|
||||||
const observer = new ResizeObserver(() => {
|
|
||||||
setHeight(el.getBoundingClientRect().height)
|
|
||||||
})
|
|
||||||
observer.observe(el)
|
|
||||||
onCleanup(() => observer.disconnect())
|
|
||||||
})
|
|
||||||
|
|
||||||
return height
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { createSignal } from "solid-js"
|
|
||||||
|
|
||||||
export const pageVisible = /* @__PURE__ */ (() => {
|
|
||||||
const [visible, setVisible] = createSignal(true)
|
|
||||||
if (typeof document !== "undefined") {
|
|
||||||
const sync = () => setVisible(document.visibilityState !== "hidden")
|
|
||||||
sync()
|
|
||||||
document.addEventListener("visibilitychange", sync)
|
|
||||||
}
|
|
||||||
return visible
|
|
||||||
})()
|
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import { createSignal } from "solid-js"
|
import { isHydrated } from "@solid-primitives/lifecycle"
|
||||||
|
import { createMediaQuery } from "@solid-primitives/media"
|
||||||
|
import { createHydratableSingletonRoot } from "@solid-primitives/rootless"
|
||||||
|
|
||||||
export const prefersReducedMotion = /* @__PURE__ */ (() => {
|
const query = "(prefers-reduced-motion: reduce)"
|
||||||
if (typeof window === "undefined") return () => false
|
|
||||||
const mql = window.matchMedia("(prefers-reduced-motion: reduce)")
|
export const useReducedMotion = createHydratableSingletonRoot(() => {
|
||||||
const [reduced, setReduced] = createSignal(mql.matches)
|
const value = createMediaQuery(query)
|
||||||
mql.addEventListener("change", () => setReduced(mql.matches))
|
return () => !isHydrated() || value()
|
||||||
return reduced
|
})
|
||||||
})()
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user