useBlink.ts
1 import { type DOMElement, useAnimationFrame, useTerminalFocus } from '../ink.js' 2 3 const BLINK_INTERVAL_MS = 600 4 5 /** 6 * Hook for synchronized blinking animations that pause when offscreen. 7 * 8 * Returns a ref to attach to the animated element and the current blink state. 9 * All instances blink together because they derive state from the same 10 * animation clock. The clock only runs when at least one subscriber is visible. 11 * Pauses when the terminal is blurred. 12 * 13 * @param enabled - Whether blinking is active 14 * @returns [ref, isVisible] - Ref to attach to element, true when visible in blink cycle 15 * 16 * @example 17 * function BlinkingDot({ shouldAnimate }) { 18 * const [ref, isVisible] = useBlink(shouldAnimate) 19 * return <Box ref={ref}>{isVisible ? '●' : ' '}</Box> 20 * } 21 */ 22 export function useBlink( 23 enabled: boolean, 24 intervalMs: number = BLINK_INTERVAL_MS, 25 ): [ref: (element: DOMElement | null) => void, isVisible: boolean] { 26 const focused = useTerminalFocus() 27 const [ref, time] = useAnimationFrame(enabled && focused ? intervalMs : null) 28 29 if (!enabled || !focused) return [ref, true] 30 31 // Derive blink state from time - all instances see the same time so they sync 32 const isVisible = Math.floor(time / intervalMs) % 2 === 0 33 return [ref, isVisible] 34 }