mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-29 21:33:54 +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:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/bounds": "0.1.3",
|
||||
"@solid-primitives/lifecycle": "0.1.2",
|
||||
"@solid-primitives/media": "2.3.3",
|
||||
"@solid-primitives/page-visibility": "2.1.1",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/rootless": "1.5.2",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"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/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/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/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 { useNavigate, useParams } from "@solidjs/router"
|
||||
import { Button } from "@opencode-ai/ui/button"
|
||||
import { useReducedMotion } from "@opencode-ai/ui/hooks"
|
||||
import { IconButton } from "@opencode-ai/ui/icon-button"
|
||||
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
|
||||
import { Dialog } from "@opencode-ai/ui/dialog"
|
||||
import { prefersReducedMotion } from "@opencode-ai/ui/hooks"
|
||||
import { InlineInput } from "@opencode-ai/ui/inline-input"
|
||||
import { animate, type AnimationPlaybackControls, clearFadeStyles, FAST_SPRING } from "@opencode-ai/ui/motion"
|
||||
import { showToast } from "@opencode-ai/ui/toast"
|
||||
@ -32,7 +32,7 @@ export function SessionTimelineHeader(props: {
|
||||
const sync = useSync()
|
||||
const dialog = useDialog()
|
||||
const language = useLanguage()
|
||||
const reduce = prefersReducedMotion
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
const [title, setTitle] = createStore({
|
||||
draft: "",
|
||||
|
||||
@ -48,8 +48,11 @@
|
||||
"@pierre/diffs": "catalog:",
|
||||
"@shikijs/transformers": "3.9.2",
|
||||
"@solid-primitives/bounds": "0.1.3",
|
||||
"@solid-primitives/lifecycle": "0.1.2",
|
||||
"@solid-primitives/media": "2.3.3",
|
||||
"@solid-primitives/page-visibility": "2.1.1",
|
||||
"@solid-primitives/resize-observer": "2.1.3",
|
||||
"@solid-primitives/rootless": "1.5.2",
|
||||
"@solidjs/meta": "catalog:",
|
||||
"@solidjs/router": "catalog:",
|
||||
"dompurify": "3.3.1",
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { createMemo, createSignal, For, onMount } from "solid-js"
|
||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
||||
import { getFilename } from "@opencode-ai/util/path"
|
||||
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import { useI18n } from "../context/i18n"
|
||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import { ToolCall } from "./basic-tool"
|
||||
import { ToolStatusTitle } from "./tool-status-title"
|
||||
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 }) {
|
||||
const reduce = useReducedMotion()
|
||||
const wiped = new Set<string>()
|
||||
const [mounted, setMounted] = createSignal(false)
|
||||
onMount(() => setMounted(true))
|
||||
const reduce = prefersReducedMotion
|
||||
const show = () => mounted() && props.pending
|
||||
const opacity = useSpring(() => (show() ? 1 : 0), 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 { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import { animate, tunableSpringValue, type AnimationPlaybackControls, GROW_SPRING, type SpringConfig } from "./motion"
|
||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
||||
|
||||
export interface GrowBoxProps {
|
||||
children: JSX.Element
|
||||
@ -49,7 +49,7 @@ export interface GrowBoxProps {
|
||||
* Used for timeline turns, assistant part groups, and user messages.
|
||||
*/
|
||||
export function GrowBox(props: GrowBoxProps) {
|
||||
const reduce = prefersReducedMotion
|
||||
const reduce = useReducedMotion()
|
||||
const spring = () => props.spring ?? GROW_SPRING
|
||||
const toggleSpring = () => props.toggleSpring ?? spring()
|
||||
let mode: "mount" | "toggle" = "mount"
|
||||
@ -293,6 +293,18 @@ export function GrowBox(props: GrowBoxProps) {
|
||||
offChange()
|
||||
})
|
||||
|
||||
if (watch()) {
|
||||
observer = new ResizeObserver(() => {
|
||||
if (!open()) return
|
||||
if (resizeFrame !== undefined) return
|
||||
resizeFrame = requestAnimationFrame(() => {
|
||||
resizeFrame = undefined
|
||||
setHeight("mount")
|
||||
})
|
||||
})
|
||||
observer.observe(body)
|
||||
}
|
||||
|
||||
if (!animated()) {
|
||||
setInstant(open())
|
||||
return
|
||||
@ -318,17 +330,6 @@ export function GrowBox(props: GrowBoxProps) {
|
||||
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(
|
||||
@ -402,7 +403,12 @@ export function GrowBox(props: GrowBoxProps) {
|
||||
ref={root}
|
||||
data-slot={props.slot}
|
||||
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 }}>
|
||||
{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 stripAnsi from "strip-ansi"
|
||||
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 HIDDEN_TOOLS = new Set(["todowrite", "todoread"])
|
||||
|
||||
import { pageVisible } from "../hooks/use-page-visible"
|
||||
|
||||
function createGroupOpenState() {
|
||||
const [state, setState] = createStore<Record<string, boolean>>({})
|
||||
const read = (key?: string, collapse?: boolean) => {
|
||||
@ -277,6 +276,7 @@ function createGroupOpenState() {
|
||||
function shouldCollapseGroup(
|
||||
statuses: (string | undefined)[],
|
||||
opts: { afterTool?: boolean; groupTail?: boolean; working?: boolean },
|
||||
pageVisible: () => boolean,
|
||||
) {
|
||||
if (opts.afterTool) return true
|
||||
if (opts.groupTail === false) return true
|
||||
@ -363,6 +363,7 @@ export function AssistantParts(props: {
|
||||
}) {
|
||||
const data = useData()
|
||||
const emptyParts: PartType[] = []
|
||||
const pageVisible = usePageVisibility()
|
||||
const groupState = createGroupOpenState()
|
||||
const grouped = createMemo(() => {
|
||||
const keys: string[] = []
|
||||
@ -485,11 +486,15 @@ export function AssistantParts(props: {
|
||||
groupTail?: boolean,
|
||||
group?: { part: ToolPart; message: AssistantMessage }[],
|
||||
) =>
|
||||
shouldCollapseGroup(group?.map((item) => item.part.state.status) ?? [], {
|
||||
afterTool,
|
||||
groupTail,
|
||||
working: props.working,
|
||||
})
|
||||
shouldCollapseGroup(
|
||||
group?.map((item) => item.part.state.status) ?? [],
|
||||
{
|
||||
afterTool,
|
||||
groupTail,
|
||||
working: props.working,
|
||||
},
|
||||
pageVisible,
|
||||
)
|
||||
const value = ctx()
|
||||
if (value) return groupState.read(value.groupKey, collapse(value.afterTool, value.tail, value.parts))
|
||||
const entry = part()
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { attachSpring, motionValue } from "motion"
|
||||
import type { SpringOptions } from "motion"
|
||||
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">
|
||||
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)) {
|
||||
const read = () => (typeof options === "function" ? options() : options)
|
||||
const reduce = prefersReducedMotion
|
||||
const reduce = useReducedMotion()
|
||||
const [value, setValue] = createSignal(target())
|
||||
const source = 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 { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import { animate, clearMaskStyles, GROW_SPRING, type AnimationPlaybackControls, type SpringConfig } from "./motion"
|
||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
||||
|
||||
export type RollingResultsProps<T> = {
|
||||
items: T[]
|
||||
@ -27,8 +27,7 @@ export function RollingResults<T>(props: RollingResultsProps<T>) {
|
||||
let shift: AnimationPlaybackControls | undefined
|
||||
let resize: AnimationPlaybackControls | undefined
|
||||
let edgeFade: AnimationPlaybackControls | undefined
|
||||
|
||||
const reducedMotion = prefersReducedMotion
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
const rows = createMemo(() => Math.max(1, Math.round(props.rows ?? 3)))
|
||||
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
|
||||
})
|
||||
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 overflowing = createMemo(() => count() > rows())
|
||||
const shown = createMemo(() => Math.min(rows(), count()))
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { createEffect, createMemo, createSignal, onCleanup, onMount, Show } from "solid-js"
|
||||
import stripAnsi from "strip-ansi"
|
||||
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 { RollingResults } from "./rolling-results"
|
||||
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 }) {
|
||||
const i18n = useI18n()
|
||||
const reduce = useReducedMotion()
|
||||
const wiped = new Set<string>()
|
||||
const [mounted, setMounted] = 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
|
||||
return ""
|
||||
})
|
||||
const reduce = prefersReducedMotion
|
||||
const skip = () => reduce() || props.animate === false
|
||||
const opacity = useSpring(() => (mounted() ? 1 : 0), GROW_SPRING)
|
||||
const blur = useSpring(() => (mounted() ? 0 : 2), GROW_SPRING)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { createEffect, createSignal, on, onCleanup, onMount } from "solid-js"
|
||||
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import {
|
||||
animate,
|
||||
type AnimationPlaybackControls,
|
||||
@ -7,7 +8,6 @@ import {
|
||||
GROW_SPRING,
|
||||
WIPE_MASK,
|
||||
} from "./motion"
|
||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
||||
|
||||
const px = (value: number | string | undefined, fallback: number) => {
|
||||
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 frame: number | undefined
|
||||
let anim: AnimationPlaybackControls | undefined
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
const run = () => {
|
||||
if (props.animate === false) return
|
||||
const el = ref
|
||||
if (!el || !props.text || typeof window === "undefined") return
|
||||
if (prefersReducedMotion()) return
|
||||
if (reduce()) return
|
||||
|
||||
const mask =
|
||||
typeof CSS !== "undefined" &&
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
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 { TextShimmer } from "./text-shimmer"
|
||||
import { commonPrefix } from "./text-utils"
|
||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
||||
|
||||
function contentWidth(el: HTMLSpanElement | undefined) {
|
||||
if (!el) return 0
|
||||
@ -18,6 +18,7 @@ export function ToolStatusTitle(props: {
|
||||
class?: string
|
||||
split?: boolean
|
||||
}) {
|
||||
const reduce = useReducedMotion()
|
||||
const split = createMemo(() => commonPrefix(props.activeText, props.doneText))
|
||||
const suffix = createMemo(
|
||||
() =>
|
||||
@ -38,8 +39,6 @@ export function ToolStatusTitle(props: {
|
||||
|
||||
const node = () => (suffix() ? tailRef : swapRef)
|
||||
|
||||
const reduce = prefersReducedMotion
|
||||
|
||||
const setNodeWidth = (width: string) => {
|
||||
if (swapRef) swapRef.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 { useReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import {
|
||||
animate,
|
||||
type AnimationPlaybackControls,
|
||||
@ -8,8 +10,6 @@ import {
|
||||
GROW_SPRING,
|
||||
WIPE_MASK,
|
||||
} from "./motion"
|
||||
import { prefersReducedMotion } from "../hooks/use-reduced-motion"
|
||||
import type { ToolPart } from "@opencode-ai/sdk/v2"
|
||||
|
||||
export const TEXT_RENDER_THROTTLE_MS = 100
|
||||
|
||||
@ -106,57 +106,67 @@ export function useCollapsible(options: {
|
||||
measure?: () => number
|
||||
onOpen?: () => void
|
||||
}) {
|
||||
const reduce = useReducedMotion()
|
||||
let heightAnim: AnimationPlaybackControls | undefined
|
||||
let fadeAnim: AnimationPlaybackControls | undefined
|
||||
let gen = 0
|
||||
|
||||
createEffect(
|
||||
on(
|
||||
options.open,
|
||||
(isOpen) => {
|
||||
const content = options.content()
|
||||
const body = options.body()
|
||||
if (!content || !body) return
|
||||
heightAnim?.stop()
|
||||
fadeAnim?.stop()
|
||||
const id = ++gen
|
||||
on(options.open, (isOpen) => {
|
||||
const content = options.content()
|
||||
const body = options.body()
|
||||
if (!content || !body) return
|
||||
heightAnim?.stop()
|
||||
fadeAnim?.stop()
|
||||
if (reduce()) {
|
||||
body.style.opacity = ""
|
||||
body.style.filter = ""
|
||||
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?.()
|
||||
},
|
||||
() => {},
|
||||
)
|
||||
})
|
||||
content.style.height = "auto"
|
||||
options.onOpen?.()
|
||||
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
|
||||
heightAnim = animate(content, { height: [`${h}px`, "0px"] }, COLLAPSIBLE_SPRING)
|
||||
fadeAnim = animate(body, { opacity: [1, 0], filter: ["blur(0px)", "blur(2px)"] }, COLLAPSIBLE_SPRING)
|
||||
heightAnim.finished.then(
|
||||
() => {
|
||||
if (gen !== id) return
|
||||
content.style.display = "none"
|
||||
},
|
||||
() => {},
|
||||
)
|
||||
},
|
||||
{ defer: true },
|
||||
),
|
||||
const h = content.getBoundingClientRect().height
|
||||
heightAnim = animate(content, { height: [`${h}px`, "0px"] }, COLLAPSIBLE_SPRING)
|
||||
fadeAnim = animate(body, { opacity: [1, 0], filter: ["blur(0px)", "blur(2px)"] }, COLLAPSIBLE_SPRING)
|
||||
heightAnim.finished.then(
|
||||
() => {
|
||||
if (gen !== id) return
|
||||
content.style.display = "none"
|
||||
},
|
||||
() => {},
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
onCleanup(() => {
|
||||
@ -181,7 +191,7 @@ export function useRowWipe(opts: {
|
||||
ref: () => HTMLElement | undefined
|
||||
seen: Set<string>
|
||||
}) {
|
||||
const reduce = prefersReducedMotion
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
createEffect(() => {
|
||||
const id = opts.id()
|
||||
@ -265,13 +275,14 @@ export function useToolFade(
|
||||
const delay = options?.delay ?? 0
|
||||
const wipe = options?.wipe ?? false
|
||||
const active = options?.animate !== false
|
||||
const reduce = useReducedMotion()
|
||||
|
||||
onMount(() => {
|
||||
if (!active) return
|
||||
|
||||
const el = ref()
|
||||
if (!el || typeof window === "undefined") return
|
||||
if (prefersReducedMotion()) return
|
||||
if (reduce()) return
|
||||
|
||||
const mask =
|
||||
wipe &&
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
export * from "./use-filtered-list"
|
||||
export * from "./create-auto-scroll"
|
||||
export * from "./use-element-height"
|
||||
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__ */ (() => {
|
||||
if (typeof window === "undefined") return () => false
|
||||
const mql = window.matchMedia("(prefers-reduced-motion: reduce)")
|
||||
const [reduced, setReduced] = createSignal(mql.matches)
|
||||
mql.addEventListener("change", () => setReduced(mql.matches))
|
||||
return reduced
|
||||
})()
|
||||
const query = "(prefers-reduced-motion: reduce)"
|
||||
|
||||
export const useReducedMotion = createHydratableSingletonRoot(() => {
|
||||
const value = createMediaQuery(query)
|
||||
return () => !isHydrated() || value()
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user