{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-hover",
  "title": "useHover",
  "description": "A callback-ref hover hook that exposes a hovered boolean via pointerenter/pointerleave, for hover-driven side effects like prefetching — not for CSS-replaceable visual hover styling.",
  "files": [
    {
      "path": "src/registry/hooks/use-hover.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nexport interface UseHoverResult<T extends HTMLElement> {\n  /** Callback ref — attach it to the element you want to track hover on. */\n  ref: (node: T | null) => void\n  hovered: boolean\n}\n\n/**\n * 用 **callback ref**(不是 `useRef` + `useEffect`)订阅一个元素的悬停状态——\n * 与 `useIntersectionObserver` 同一个理由:元素被条件渲染替换(换 key、换分支)\n * 时,callback ref 由 React 在挂载/替换/卸载时逐次调用,天然知道「现在挂的是\n * 哪个节点」,重新绑定监听器因此是免费的、不需要额外 `useEffect` 去追节点变化。\n *\n * 监听 `pointerenter`/`pointerleave`,而不是 `mouseenter`/`mouseleave`:两者对\n * 鼠标语义等价,但 Pointer Events 是 mouse/pen/touch 的统一入口,多覆盖了触屏\n * 笔(pen)这一类真实存在指针悬停的设备。**触屏手指(touch)不产生真正的“悬\n * 停”**——纯触屏上 `pointerenter` 要么不触发,要么只在 tap 瞬间触发一下就立刻\n * `pointerleave`,这是平台语义使然,不是本 hook 需要 polyfill 的缺陷:调用方\n * 不应该指望 `hovered` 在纯触屏设备上表现得像“按下态”。\n *\n * 生命周期:节点挂载 → 绑定 `pointerenter`/`pointerleave`;`hovered` 只在这两\n * 个(异步触发的)事件回调里 `setState`,渲染期和 callback ref 本体都不同步\n * 触碰它,除了下面这一种情况——节点被替换或组件卸载时,React 用 `null` 调用\n * callback ref,此时用保存的引用对**上一个节点** `removeEventListener`(不能假\n * 设卸载时还能拿到同一个 `node` 参数),并把 `hovered` 收回 `false`,避免下一个\n * 替换上来的节点在真正被悬停之前继续读到上一个节点残留的 `true`。\n *\n * **诚实边界**:纯视觉的 hover 效果(变色、上浮阴影、显隐箭头)用 CSS\n * `:hover` / `group-hover` 就够了,不需要引入这个 hook、也不该为此把逻辑搬进\n * JS 里重新做一遍浏览器已经免费给的东西。这个 hook 只该用在「hover 需要驱动\n * 非样式的副作用」时:预取数据、延迟弹出富预览、悬停播放视频/动画预览。它也\n * **不是**可访问性(a11y)等价物——键盘用户永远不会触发 `pointerenter`;如果\n * 同一个交互也必须在键盘 focus 时触发,需要调用方另外监听 `focus`/`blur` 并入\n * 同一个状态(见组件 Prompt 的 Customization levers)。\n */\nexport function useHover<T extends HTMLElement = HTMLElement>(): UseHoverResult<T> {\n  const [hovered, setHovered] = React.useState(false)\n  const nodeRef = React.useRef<T | null>(null)\n\n  const handlePointerEnter = React.useCallback(() => setHovered(true), [])\n  const handlePointerLeave = React.useCallback(() => setHovered(false), [])\n\n  const ref = React.useCallback(\n    (node: T | null) => {\n      const previous = nodeRef.current\n      if (previous) {\n        previous.removeEventListener(\"pointerenter\", handlePointerEnter)\n        previous.removeEventListener(\"pointerleave\", handlePointerLeave)\n      }\n      nodeRef.current = node\n\n      if (!node) {\n        setHovered(false)\n        return\n      }\n\n      node.addEventListener(\"pointerenter\", handlePointerEnter)\n      node.addEventListener(\"pointerleave\", handlePointerLeave)\n    },\n    [handlePointerEnter, handlePointerLeave],\n  )\n\n  return { ref, hovered }\n}\n\nexport default useHover\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}