58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
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]
|
|
}
|