/ hooks / useBlink.ts
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  }