{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/use-animation",
  "title": "Use Animation",
  "description": "Frame-based animation helper for terminal UI components.",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/use-motion.json"
  ],
  "files": [
    {
      "path": "registry/hooks/use-animation.ts",
      "content": "import { useIsScreenReaderEnabled } from \"ink\";\nimport * as React from \"react\";\n\nimport { useMotion } from \"@/hooks/use-motion\";\n\ntype Subscriber = (tick: number) => void;\nconst pool = new Map<\n  number,\n  { id: ReturnType<typeof setInterval>; subs: Set<Subscriber>; tick: number }\n>();\n\nconst subscribe = (milliseconds: number, subscriber: Subscriber) => {\n  if (!pool.has(milliseconds)) {\n    const entry = {\n      id: null as unknown as ReturnType<typeof setInterval>,\n      subs: new Set<Subscriber>(),\n      tick: 0,\n    };\n\n    entry.id = setInterval(() => {\n      entry.tick += 1;\n      for (const sub of entry.subs) {\n        sub(entry.tick);\n      }\n    }, milliseconds);\n\n    pool.set(milliseconds, entry);\n  }\n\n  pool.get(milliseconds)?.subs.add(subscriber);\n};\n\nconst unsubscribe = (milliseconds: number, subscriber: Subscriber) => {\n  const entry = pool.get(milliseconds);\n  if (!entry) {\n    return;\n  }\n\n  entry.subs.delete(subscriber);\n  if (entry.subs.size === 0) {\n    clearInterval(entry.id);\n    pool.delete(milliseconds);\n  }\n};\n\nexport interface UseAnimationOptions {\n  intervalMs: number;\n  isActive?: boolean;\n}\n\nexport const useAnimation = (\n  rate: number | UseAnimationOptions = 12\n): number => {\n  const [frame, setFrame] = React.useState(0);\n  const { reduced } = useMotion();\n  const isScreenReaderEnabled = useIsScreenReaderEnabled();\n  const milliseconds =\n    typeof rate === \"number\" ? Math.round(1000 / rate) : rate.intervalMs;\n  const isActive =\n    (typeof rate === \"number\" ? true : (rate.isActive ?? true)) &&\n    !reduced &&\n    !isScreenReaderEnabled;\n\n  React.useEffect(() => {\n    if (!isActive) {\n      setFrame(0);\n      return;\n    }\n\n    const callback: Subscriber = (tick) => setFrame(tick);\n    subscribe(milliseconds, callback);\n\n    return () => unsubscribe(milliseconds, callback);\n  }, [isActive, milliseconds]);\n\n  return frame;\n};\n",
      "type": "registry:hook"
    }
  ],
  "categories": [
    "core"
  ],
  "type": "registry:hook"
}