π File detail
ink/hooks/use-animation-frame.ts
π§© .tsπ 58 linesπΎ 1,933 bytesπ text
β Back to All Filesπ― Use case
This file lives under βink/β, which covers Ink terminal UI (layouts, TTY IO, keyboard, renderer components). On the API surface it exposes useAnimationFrame β mainly functions, hooks, or classes. Dependencies touch React UI. It composes internal code from components, dom, and use-terminal-viewport (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { useContext, useEffect, useState } from 'react' import { ClockContext } from '../components/ClockContext.js' import type { DOMElement } from '../dom.js' import { useTerminalViewport } from './use-terminal-viewport.js'
π€ Exports (heuristic)
useAnimationFrame
π External import roots
Package roots from from "β¦" (relative paths omitted).
react
π₯οΈ Source preview
import { useContext, useEffect, useState } from 'react'
import { ClockContext } from '../components/ClockContext.js'
import type { DOMElement } from '../dom.js'
import { useTerminalViewport } from './use-terminal-viewport.js'
/**
* Hook for synchronized animations that pause when offscreen.
*
* Returns a ref to attach to the animated element and the current animation time.
* All instances share the same clock, so animations stay in sync.
* The clock only runs when at least one keepAlive subscriber exists.
*
* Pass `null` to pause β unsubscribes from the clock so no ticks fire.
* Time freezes at the last value and resumes from the current clock time
* when a number is passed again.
*
* @param intervalMs - How often to update, or null to pause
* @returns [ref, time] - Ref to attach to element, elapsed time in ms
*
* @example
* function Spinner() {
* const [ref, time] = useAnimationFrame(120)
* const frame = Math.floor(time / 120) % FRAMES.length
* return <Box ref={ref}>{FRAMES[frame]}</Box>
* }
*
* The clock automatically slows when the terminal is blurred,
* so consumers don't need to handle focus state.
*/
export function useAnimationFrame(
intervalMs: number | null = 16,
): [ref: (element: DOMElement | null) => void, time: number] {
const clock = useContext(ClockContext)
const [viewportRef, { isVisible }] = useTerminalViewport()
const [time, setTime] = useState(() => clock?.now() ?? 0)
const active = isVisible && intervalMs !== null
useEffect(() => {
if (!clock || !active) return
let lastUpdate = clock.now()
const onChange = (): void => {
const now = clock.now()
if (now - lastUpdate >= intervalMs!) {
lastUpdate = now
setTime(now)
}
}
// keepAlive: true β visible animations drive the clock
return clock.subscribe(onChange, true)
}, [clock, intervalMs, active])
return [viewportRef, time]
}