{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "super-hover",
  "type": "registry:ui",
  "title": "Super Hover",
  "description": "Headless cursor hit-testing engine: tracks which element is under the pointer frame-by-frame, so hover keeps following the cursor even while the list scrolls beneath it. Ships as a controller plus React hooks, no UI.",
  "files": [
    {
      "path": "registry/crafterui/ui/super-hover.tsx",
      "type": "registry:ui",
      "content": "\"use client\"\n\n// Inspired by Super Hover by Daniel Petho (https://super-hover.danielpetho.com,\n// https://github.com/danielpetho/super-hover) — MIT licensed.\n//\n// Native CSS `:hover` only updates when the pointer moves. Super Hover hit-tests\n// the element under the pointer on every animation frame AND on scroll, so the\n// \"hovered\" element keeps updating while the page scrolls beneath a still cursor.\n// It toggles a `data-super-hover-active` attribute and dispatches bubbling\n// `superhoverenter` / `superhoverleave` / `superhovermove` CustomEvents.\nimport * as React from \"react\"\n\n/** Payload on `superhovermove` events fired each hit-test frame while active (`event.detail`). */\nexport type SuperHoverMoveEventDetail = {\n  x: number\n  y: number\n  current: Element\n}\n\n/** Payload on `superhoverenter` / `superhoverleave` events (`event.detail`). */\nexport type SuperHoverEventDetail = {\n  x: number\n  y: number\n  previous: Element | null\n  current: Element | null\n}\n\nexport type SuperHoverEnterEvent = CustomEvent<SuperHoverEventDetail>\nexport type SuperHoverLeaveEvent = CustomEvent<SuperHoverEventDetail>\nexport type SuperHoverMoveEvent = CustomEvent<SuperHoverMoveEventDetail>\n\n/** Pointer kinds accepted for position updates. */\nexport type SuperHoverPointerType = \"mouse\" | \"pen\" | \"touch\"\n\n/** Controller returned by {@link createSuperHover}. */\nexport type SuperHoverController = {\n  pause(): void\n  resume(): void\n  refresh(): void\n  destroy(): void\n}\n\nexport type SuperHoverOptions = {\n  /** When false, starts paused. @default true */\n  enabled?: boolean\n  /**\n   * Pointer kinds whose `pointermove` events update the hit-test position. Touch\n   * is omitted by default so scrolling on touch devices does not drive \"hover\".\n   * @default [\"mouse\", \"pen\"]\n   */\n  pointerTypes?: SuperHoverPointerType[]\n  /** Clear hover state while a pointer is pressed (e.g. during text selection). @default false */\n  disableWhilePointerDown?: boolean\n  /**\n   * Subtree boundary: the matched element must be contained here (or the active\n   * document when omitted). Pass an iframe `Document` or any element inside it to\n   * hit-test that frame. @default undefined\n   */\n  root?: Document | Element\n  /** Passed to `element.closest` to choose participating elements. @default \"[data-super-hover]\" */\n  selector?: string\n  /** Attribute toggled on the active element while hovered. @default \"data-super-hover-active\" */\n  activeAttribute?: string\n  /** CustomEvent type dispatched when an element becomes active. @default \"superhoverenter\" */\n  enterEventType?: string\n  /** CustomEvent type dispatched when an element stops being active. @default \"superhoverleave\" */\n  leaveEventType?: string\n  /** CustomEvent type dispatched each tick while active; `false` disables it. @default \"superhovermove\" */\n  moveEventType?: string | false\n}\n\nconst DEFAULT_SELECTOR = \"[data-super-hover]\"\nconst DEFAULT_ACTIVE = \"data-super-hover-active\"\nconst DEFAULT_ENTER_EVENT = \"superhoverenter\"\nconst DEFAULT_LEAVE_EVENT = \"superhoverleave\"\nconst DEFAULT_MOVE_EVENT = \"superhovermove\"\nconst DEFAULT_POINTER_TYPES: readonly SuperHoverPointerType[] = [\"mouse\", \"pen\"]\n\n/** `Node.DOCUMENT_NODE` — detected via `nodeType` so cross-realm (iframe) documents resolve. */\nconst DOCUMENT_NODE = 9\n\nfunction getScopeDocument(root?: Document | Element): Document {\n  if (!root) return document\n  if (root.nodeType === DOCUMENT_NODE) return root as Document\n  return root.ownerDocument ?? document\n}\n\nfunction rootContains(\n  root: Document | Element | undefined,\n  el: Element\n): boolean {\n  if (!root) return true\n  if (root.nodeType === DOCUMENT_NODE) {\n    const html = (root as Document).documentElement\n    return html ? html.contains(el) : false\n  }\n  return root.contains(el)\n}\n\n/**\n * Track the pointer and hit-test on every frame (and on scroll) so \"hover\" state\n * keeps updating while scrolling, unlike native `:hover`. Returns a controller you\n * must {@link SuperHoverController.destroy} on cleanup.\n */\nexport function createSuperHover(\n  options: SuperHoverOptions = {}\n): SuperHoverController {\n  const selector = options.selector ?? DEFAULT_SELECTOR\n  const activeAttribute = options.activeAttribute ?? DEFAULT_ACTIVE\n  const enterEventType = options.enterEventType ?? DEFAULT_ENTER_EVENT\n  const leaveEventType = options.leaveEventType ?? DEFAULT_LEAVE_EVENT\n  const moveEventName =\n    options.moveEventType === false\n      ? null\n      : (options.moveEventType ?? DEFAULT_MOVE_EVENT)\n  const root = options.root\n  const scopeDoc = getScopeDocument(root)\n  const scopeWin = scopeDoc.defaultView ?? window\n  const allowedPointerTypes = new Set<string>(\n    options.pointerTypes ?? [...DEFAULT_POINTER_TYPES]\n  )\n  const disableWhilePointerDown = options.disableWhilePointerDown ?? false\n\n  let running = options.enabled ?? true\n  let destroyed = false\n\n  let lastX = 0\n  let lastY = 0\n  let hasPointer = false\n  let pointerDown = false\n  let current: Element | null = null\n  let rafId = 0\n  let pending = false\n\n  function cancelPendingFrame(): void {\n    if (rafId !== 0) {\n      scopeWin.cancelAnimationFrame(rafId)\n      rafId = 0\n      pending = false\n    }\n  }\n\n  function deactivate(prev: Element, next: Element | null): void {\n    prev.removeAttribute(activeAttribute)\n    prev.dispatchEvent(\n      new CustomEvent<SuperHoverEventDetail>(leaveEventType, {\n        bubbles: true,\n        cancelable: false,\n        detail: { x: lastX, y: lastY, previous: prev, current: next },\n      })\n    )\n  }\n\n  function clearActive(): void {\n    if (!current) return\n    const prev = current\n    current = null\n    deactivate(prev, null)\n  }\n\n  function resolveTarget(): Element | null {\n    if (!running || !hasPointer) return null\n    if (disableWhilePointerDown && pointerDown) return null\n\n    const hit = scopeDoc.elementFromPoint(lastX, lastY)\n    if (!hit) return null\n    const el = hit.closest(selector)\n    if (!el) return null\n    if (!rootContains(root, el)) return null\n    return el\n  }\n\n  function apply(): void {\n    if (destroyed || !running) {\n      clearActive()\n      return\n    }\n\n    const next = resolveTarget()\n    if (next === current) return\n\n    const previousElement = current\n\n    if (current) {\n      const prev = current\n      current = null\n      deactivate(prev, next)\n    }\n\n    current = next\n    if (current) {\n      current.setAttribute(activeAttribute, \"\")\n      current.dispatchEvent(\n        new CustomEvent<SuperHoverEventDetail>(enterEventType, {\n          bubbles: true,\n          cancelable: false,\n          detail: { x: lastX, y: lastY, previous: previousElement, current },\n        })\n      )\n    }\n  }\n\n  function schedule(): void {\n    if (destroyed || pending) return\n    // Coalesce many pointer/scroll/resize events into one hit-test before paint.\n    pending = true\n    rafId = scopeWin.requestAnimationFrame(() => {\n      rafId = 0\n      pending = false\n      if (destroyed) return\n      if (!running || !hasPointer) {\n        clearActive()\n        return\n      }\n      apply()\n      if (moveEventName !== null && current !== null) {\n        current.dispatchEvent(\n          new CustomEvent<SuperHoverMoveEventDetail>(moveEventName, {\n            bubbles: true,\n            cancelable: false,\n            detail: { x: lastX, y: lastY, current },\n          })\n        )\n      }\n    })\n  }\n\n  function onPointerMove(e: PointerEvent): void {\n    if (destroyed) return\n    if (!allowedPointerTypes.has(e.pointerType)) return\n    lastX = e.clientX\n    lastY = e.clientY\n    hasPointer = true\n    if (disableWhilePointerDown) pointerDown = e.buttons !== 0\n    if (running) schedule()\n  }\n\n  function onPointerLeaveDocument(): void {\n    hasPointer = false\n    pointerDown = false\n    schedule()\n  }\n\n  function onPointerDown(e: PointerEvent): void {\n    if (destroyed || !disableWhilePointerDown) return\n    if (!allowedPointerTypes.has(e.pointerType)) return\n    lastX = e.clientX\n    lastY = e.clientY\n    hasPointer = true\n    pointerDown = true\n    if (running) schedule()\n  }\n\n  function onPointerUp(e: PointerEvent): void {\n    if (destroyed || !disableWhilePointerDown) return\n    if (allowedPointerTypes.has(e.pointerType)) {\n      lastX = e.clientX\n      lastY = e.clientY\n      hasPointer = true\n    }\n    pointerDown = false\n    if (running) schedule()\n  }\n\n  function onPointerOut(e: PointerEvent): void {\n    if (!e.relatedTarget) onPointerLeaveDocument()\n  }\n\n  function onVisibilityChange(): void {\n    if (scopeDoc.visibilityState === \"hidden\") {\n      hasPointer = false\n      schedule()\n    }\n  }\n\n  scopeWin.addEventListener(\"pointermove\", onPointerMove, { passive: true })\n  scopeWin.addEventListener(\"pointerdown\", onPointerDown, { passive: true })\n  scopeWin.addEventListener(\"pointerup\", onPointerUp, { passive: true })\n  scopeDoc.addEventListener(\"scroll\", schedule, {\n    capture: true,\n    passive: true,\n  })\n  scopeWin.addEventListener(\"resize\", schedule, { passive: true })\n  scopeWin.addEventListener(\"blur\", onPointerLeaveDocument)\n  scopeDoc.addEventListener(\"pointerleave\", onPointerLeaveDocument)\n  scopeDoc.addEventListener(\"pointercancel\", onPointerLeaveDocument)\n  scopeDoc.addEventListener(\"pointerout\", onPointerOut)\n  scopeDoc.addEventListener(\"visibilitychange\", onVisibilityChange)\n\n  schedule()\n\n  return {\n    pause() {\n      if (destroyed) return\n      running = false\n      cancelPendingFrame()\n      clearActive()\n    },\n    resume() {\n      if (destroyed) return\n      running = true\n      schedule()\n    },\n    refresh() {\n      if (destroyed) return\n      schedule()\n    },\n    destroy() {\n      if (destroyed) return\n      destroyed = true\n      scopeWin.removeEventListener(\"pointermove\", onPointerMove)\n      scopeWin.removeEventListener(\"pointerdown\", onPointerDown)\n      scopeWin.removeEventListener(\"pointerup\", onPointerUp)\n      scopeDoc.removeEventListener(\"scroll\", schedule, { capture: true })\n      scopeWin.removeEventListener(\"resize\", schedule)\n      scopeWin.removeEventListener(\"blur\", onPointerLeaveDocument)\n      scopeDoc.removeEventListener(\"pointerleave\", onPointerLeaveDocument)\n      scopeDoc.removeEventListener(\"pointercancel\", onPointerLeaveDocument)\n      scopeDoc.removeEventListener(\"pointerout\", onPointerOut)\n      scopeDoc.removeEventListener(\"visibilitychange\", onVisibilityChange)\n      cancelPendingFrame()\n      hasPointer = false\n      pointerDown = false\n      clearActive()\n    },\n  }\n}\n\n/** Options for {@link useSuperHover} / {@link useSuperHoverRef}. */\nexport type UseSuperHoverOptions = Omit<\n  SuperHoverOptions,\n  \"root\" | \"enabled\"\n> & {\n  /** When false, does not mount Super Hover on `root`. @default true */\n  enabled?: boolean\n  /** Fires when an element becomes active; `event.detail.current` is that element. @default undefined */\n  onEnter?: (event: SuperHoverEnterEvent) => void\n  /** Fires when an element stops being active. @default undefined */\n  onLeave?: (event: SuperHoverLeaveEvent) => void\n  /** Fires each hit-test tick while an element stays active. @default undefined */\n  onMove?: (event: SuperHoverMoveEvent) => void\n}\n\nconst noop = () => {}\n\n/**\n * Mount Super Hover on `root` and forward enter/leave/move events to callbacks.\n * Callbacks are read from refs, so passing inline handlers does not remount.\n */\nexport function useSuperHover(\n  root: HTMLElement | null,\n  {\n    enabled = true,\n    onEnter = noop,\n    onLeave = noop,\n    onMove,\n    selector,\n    activeAttribute,\n    pointerTypes,\n    disableWhilePointerDown,\n    enterEventType = \"superhoverenter\",\n    leaveEventType = \"superhoverleave\",\n    moveEventType,\n  }: UseSuperHoverOptions = {}\n): void {\n  const onEnterRef = React.useRef(onEnter)\n  const onLeaveRef = React.useRef(onLeave)\n  const onMoveRef = React.useRef(onMove)\n  onEnterRef.current = onEnter\n  onLeaveRef.current = onLeave\n  onMoveRef.current = onMove\n\n  const hasOnMove = onMove !== undefined\n\n  React.useEffect(() => {\n    if (!enabled || !root) return\n\n    const handleEnter = (e: Event) =>\n      onEnterRef.current(e as SuperHoverEnterEvent)\n    const handleLeave = (e: Event) =>\n      onLeaveRef.current(e as SuperHoverLeaveEvent)\n    const handleMove = (e: Event) =>\n      onMoveRef.current?.(e as SuperHoverMoveEvent)\n\n    const resolvedMove =\n      moveEventType === false ? null : (moveEventType ?? \"superhovermove\")\n    const listenMove = resolvedMove !== null && hasOnMove\n\n    root.addEventListener(enterEventType, handleEnter)\n    root.addEventListener(leaveEventType, handleLeave)\n    if (listenMove) root.addEventListener(resolvedMove, handleMove)\n\n    const ctrl = createSuperHover({\n      root,\n      ...(selector !== undefined && { selector }),\n      ...(activeAttribute !== undefined && { activeAttribute }),\n      ...(pointerTypes !== undefined && { pointerTypes }),\n      ...(disableWhilePointerDown !== undefined && { disableWhilePointerDown }),\n      enterEventType,\n      leaveEventType,\n      ...(moveEventType !== undefined\n        ? { moveEventType }\n        : hasOnMove\n          ? {}\n          : { moveEventType: false }),\n    })\n\n    return () => {\n      root.removeEventListener(enterEventType, handleEnter)\n      root.removeEventListener(leaveEventType, handleLeave)\n      if (listenMove) root.removeEventListener(resolvedMove, handleMove)\n      ctrl.destroy()\n    }\n  }, [\n    root,\n    enabled,\n    selector,\n    activeAttribute,\n    pointerTypes,\n    disableWhilePointerDown,\n    enterEventType,\n    leaveEventType,\n    moveEventType,\n    hasOnMove,\n  ])\n}\n\n/**\n * Like {@link useSuperHover} but returns a callback ref to attach to the scroll\n * root, so you do not have to manage `ref.current` yourself.\n */\nexport function useSuperHoverRef(\n  options: UseSuperHoverOptions = {}\n): React.RefCallback<HTMLElement> {\n  const [node, setNode] = React.useState<HTMLElement | null>(null)\n  useSuperHover(node, options)\n  return React.useCallback((el: HTMLElement | null) => setNode(el), [])\n}\n"
    }
  ]
}
