{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-click-outside",
  "title": "useClickOutside",
  "description": "A hook that runs a handler when a pointer interaction lands outside one or more referenced elements.",
  "files": [
    {
      "path": "src/registry/hooks/use-click-outside.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\n\n/** Document events that can be listened for. `pointerdown` alone covers mouse, touch and pen. */\nexport type ClickOutsideEvent = \"pointerdown\" | \"mousedown\" | \"click\" | \"touchstart\"\n\n/** Any element ref — `useRef<HTMLDivElement>(null)` can be handed straight in. */\nexport type ClickOutsideRef = React.RefObject<Element | null>\n\nexport interface UseClickOutsideOptions {\n  /** Unbinds the listener (no point sitting on document while the layer is closed). Defaults to true. */\n  enabled?: boolean\n  /** Which document events to listen for. Defaults to [\"pointerdown\"]. */\n  events?: ClickOutsideEvent[]\n}\n\n/** Module-level constant: the default reuses one array identity, saving a pointless dependency change. */\nconst DEFAULT_EVENTS: ClickOutsideEvent[] = [\"pointerdown\"]\n\n/**\n * Calls `handler` when an interaction lands outside **all** of the given refs. An array is\n * accepted for the classic case of a trigger and a layer being two DOM nodes with no nesting\n * between them (dropdowns, portalled popovers): a press on the trigger must not count as\n * outside, or the button is closed and then reopened and the layer never appears to open.\n *\n * - **SSR safe**: `document` is only touched inside the effect, never during render.\n * - **No listener churn**: `handler` and `refs` live in refs refreshed each render, and\n *   `events` is serialised into a string dependency — inline arrows and inline arrays are\n *   what consumers actually write, and passing those straight into the dependency array\n *   would rebind the document listener on every render.\n * - **Detached nodes**: see the `isConnected` comment in `listener`; that is the trap this\n *   hook exists to avoid.\n * - `pointerdown` rather than `click` by default: dismissal happens the moment you press,\n *   which feels more direct and sidesteps the drag that starts inside the layer and\n *   releases outside it.\n */\nexport function useClickOutside(\n  refs: ClickOutsideRef | ClickOutsideRef[],\n  handler: (event: Event) => void,\n  options: UseClickOutsideOptions = {},\n): void {\n  const { enabled = true, events = DEFAULT_EVENTS } = options\n\n  const handlerRef = React.useRef(handler)\n  const refsRef = React.useRef(refs)\n  React.useEffect(() => {\n    handlerRef.current = handler\n    refsRef.current = refs\n  })\n\n  // `events` is usually an array literal written at the call site, so by reference it counts\n  // as changed on every render; serialised to a string, equal contents mean an equal\n  // dependency and the listener stays put.\n  const eventsKey = events.join(\",\")\n\n  React.useEffect(() => {\n    if (!enabled) return\n\n    const eventNames = eventsKey.split(\",\")\n\n    const listener = (event: Event) => {\n      const target = event.target\n      if (!(target instanceof Node)) return\n\n      // the edge case that matters: the clicked node may already be out of the document by\n      // the time the event is dispatched — a menu item that unmounts itself, a row the list\n      // just removed. `contains()` returns false for such a target against every node, so it\n      // reads as a click outside and takes the layer down with it. Ignore anything detached.\n      if (!target.isConnected) return\n\n      const current = refsRef.current\n      const list = Array.isArray(current) ? current : [current]\n      for (const ref of list) {\n        if (ref.current?.contains(target)) return\n      }\n\n      handlerRef.current(event)\n    }\n\n    for (const name of eventNames) {\n      document.addEventListener(name, listener)\n    }\n    return () => {\n      for (const name of eventNames) {\n        document.removeEventListener(name, listener)\n      }\n    }\n  }, [enabled, eventsKey])\n}\n\nexport default useClickOutside\n",
      "type": "registry:hook"
    }
  ],
  "type": "registry:hook"
}