{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-event-listener",
  "title": "useEventListener",
  "description": "A type-safe window/document/element event listener hook with a latest-ref handler — zero rebinds on handler change, never stale.",
  "files": [
    {
      "path": "src/registry/hooks/use-event-listener.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\nexport interface UseEventListenerOptions {\n  /** Passed straight through as the native addEventListener `passive`. */\n  passive?: boolean\n  /** Passed straight through as the native addEventListener/removeEventListener `capture`. */\n  capture?: boolean\n}\n\ntype TargetRef<T extends HTMLElement> = React.RefObject<T | null>\n\n// Overload 1: no target, or window passed explicitly — event names map to WindowEventMap.\nexport function useEventListener<K extends keyof WindowEventMap>(\n  eventName: K,\n  handler: (event: WindowEventMap[K]) => void,\n  options?: UseEventListenerOptions & { target?: Window },\n): void\n\n// Overload 2: document as the target — event names map to DocumentEventMap.\nexport function useEventListener<K extends keyof DocumentEventMap>(\n  eventName: K,\n  handler: (event: DocumentEventMap[K]) => void,\n  options: UseEventListenerOptions & { target: Document },\n): void\n\n// Overload 3: an element RefObject as the target — event names map to\n// HTMLElementEventMap, and T is inferred from the ref at the call site (say\n// RefObject<HTMLDivElement>).\n// Note: the effect depends on the ref object itself (stable identity). If .current\n// is null when the effect runs, nothing is bound this round and nothing re-binds\n// later — for a conditionally rendered target, remount the consumer with a key or\n// switch to the callback-ref pattern (see use-hover). This fits targets that live as\n// long as the component: window / document / an element always in the tree.\nexport function useEventListener<K extends keyof HTMLElementEventMap, T extends HTMLElement = HTMLElement>(\n  eventName: K,\n  handler: (event: HTMLElementEventMap[K]) => void,\n  options: UseEventListenerOptions & { target: TargetRef<T> },\n): void\n\n/**\n * Typed event listening against three kinds of target — window (the default),\n * document, or any element ref — narrowing `eventName` / `handler` to the native\n * event map that matches.\n *\n * The core is the latest-ref for the handler: `handler` is synced into a ref after\n * every render, while the effect that actually calls `addEventListener` depends only\n * on `eventName` / `target` / the primitives pulled out of `options`\n * (`passive` / `capture`) — never on the `options` object, and never on `handler`.\n * So a consumer passing a freshly built `handler` on every render (closing over the\n * latest state, which is the common case) does not thrash the listener; when it\n * fires it goes through the ref to the current handler, so there is no stale closure.\n *\n * The listener is cleaned up on unmount and whenever the target changes; the effect\n * runs in the browser only, so SSR never touches window/document.\n */\nexport function useEventListener(\n  eventName: string,\n  handler: (event: Event) => void,\n  options: UseEventListenerOptions & {\n    target?: Window | Document | TargetRef<HTMLElement>\n  } = {},\n): void {\n  const { target, passive, capture } = options\n\n  const handlerRef = React.useRef(handler)\n  // Synced to the latest handler after every render — the one assignment allowed\n  // straight inside an effect: it writes ref.current rather than state, so it causes\n  // no re-render and is free of the listener effect's dependency array below.\n  React.useEffect(() => {\n    handlerRef.current = handler\n  })\n\n  React.useEffect(() => {\n    const node = resolveTarget(target)\n    if (!node) return\n\n    const listener = (event: Event) => handlerRef.current(event)\n    node.addEventListener(eventName, listener, { passive, capture })\n    return () => node.removeEventListener(eventName, listener, { capture })\n    // Dependencies are eventName / target / passive / capture only — no handler and\n    // no options object, so a changed handler re-binds nothing.\n  }, [eventName, target, passive, capture])\n}\n\nfunction resolveTarget(\n  target: Window | Document | TargetRef<HTMLElement> | undefined,\n): Window | Document | HTMLElement | null {\n  if (typeof window === \"undefined\") return null\n  if (target === undefined) return window\n  if (\"current\" in target) return target.current\n  return target\n}\n\nexport default useEventListener\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}