{"version":3,"sources":["../src/useEventListener.ts"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, type RefObject } from \"react\";\n\n// SSR-safe layout effect: sync the stored handler before paint on the client,\n// fall back to useEffect on the server (mirrors the house useEventCallback).\nconst useIsomorphicLayoutEffect =\n  typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n\n/**\n * Options for useEventListener hook\n */\nexport interface UseEventListenerOptions {\n  /**\n   * Whether the event listener is enabled\n   * @default true\n   */\n  enabled?: boolean;\n\n  /**\n   * Whether to use event capture phase\n   * @default false\n   */\n  capture?: boolean;\n\n  /**\n   * Whether to use passive event listener for performance optimization\n   * Useful for scroll/touch events\n   * @default undefined (browser default)\n   */\n  passive?: boolean;\n\n  /**\n   * Whether the handler should be invoked only once and then removed\n   * @default false\n   */\n  once?: boolean;\n}\n\n/**\n * Supported event target types\n */\nexport type EventTargetType<T extends HTMLElement = HTMLElement> =\n  | Window\n  | Document\n  | HTMLElement\n  | RefObject<T | null>\n  | null\n  | undefined;\n\n/**\n * Check if target is a RefObject\n *\n * @internal Exported for unit testing only — not re-exported from the package\n * entry, so it stays out of the public API.\n */\nexport function isRefObject<T extends HTMLElement>(\n  target: EventTargetType<T>\n): target is RefObject<T | null> {\n  return (\n    target !== null &&\n    target !== undefined &&\n    typeof target === \"object\" &&\n    \"current\" in target\n  );\n}\n\n/**\n * Extract actual DOM element from target.\n *\n * Returns `null` when there is no element to attach to — an explicit `null`\n * target, a `RefObject` that hasn't populated yet, or a server environment with\n * no `window` (SSR).\n *\n * @internal Exported for unit testing only — not re-exported from the package\n * entry, so it stays out of the public API.\n */\nexport function getTargetElement<T extends HTMLElement>(\n  target: EventTargetType<T>\n): Window | Document | HTMLElement | null {\n  // Default to window if target is undefined (not provided)\n  if (target === undefined) {\n    return typeof window !== \"undefined\" ? window : null;\n  }\n\n  // If null is passed, return null (no listener)\n  if (target === null) {\n    return null;\n  }\n\n  // Extract element from RefObject\n  if (isRefObject(target)) {\n    return target.current;\n  }\n\n  return target;\n}\n\n// Overload 1: Window events (default when element is omitted or Window)\n/**\n * Adds an event listener to the window (default) or specified element.\n *\n * @param eventName - The event type to listen for\n * @param handler - The callback function called when the event fires\n * @param element - The target element (defaults to window)\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * // Window resize event\n * useEventListener(\"resize\", (e) => {\n *   console.log(\"Window resized:\", window.innerWidth);\n * });\n * ```\n */\nexport function useEventListener<K extends keyof WindowEventMap>(\n  eventName: K,\n  handler: (event: WindowEventMap[K]) => void,\n  element?: Window | null,\n  options?: UseEventListenerOptions\n): void;\n\n// Overload 2: Document events\n/**\n * Adds an event listener to the document.\n *\n * @example\n * ```tsx\n * // Document keydown event\n * useEventListener(\"keydown\", (e) => {\n *   console.log(\"Key pressed:\", e.key);\n * }, document);\n * ```\n */\nexport function useEventListener<K extends keyof DocumentEventMap>(\n  eventName: K,\n  handler: (event: DocumentEventMap[K]) => void,\n  element: Document,\n  options?: UseEventListenerOptions\n): void;\n\n// Overload 3: HTMLElement events\n/**\n * Adds an event listener to an HTMLElement.\n *\n * @example\n * ```tsx\n * // HTMLElement click event\n * const button = document.getElementById(\"myButton\");\n * useEventListener(\"click\", (e) => {\n *   console.log(\"Button clicked\");\n * }, button);\n * ```\n */\nexport function useEventListener<K extends keyof HTMLElementEventMap>(\n  eventName: K,\n  handler: (event: HTMLElementEventMap[K]) => void,\n  element: HTMLElement | null,\n  options?: UseEventListenerOptions\n): void;\n\n// Overload 4: RefObject<HTMLElement>\n/**\n * Adds an event listener to an element referenced by a RefObject.\n *\n * @example\n * ```tsx\n * // RefObject event\n * const ref = useRef<HTMLDivElement>(null);\n * useEventListener(\"scroll\", (e) => {\n *   console.log(\"Element scrolled\");\n * }, ref);\n * ```\n */\nexport function useEventListener<\n  K extends keyof HTMLElementEventMap,\n  T extends HTMLElement\n>(\n  eventName: K,\n  handler: (event: HTMLElementEventMap[K]) => void,\n  element: RefObject<T | null>,\n  options?: UseEventListenerOptions\n): void;\n\n// Overload 5: Custom events (fallback for non-standard events)\n/**\n * Adds an event listener for custom events.\n *\n * @example\n * ```tsx\n * // Custom event\n * useEventListener(\"myCustomEvent\", (e) => {\n *   console.log(\"Custom event fired\");\n * }, document);\n * ```\n */\nexport function useEventListener(\n  eventName: string,\n  handler: (event: Event) => void,\n  element?: EventTargetType,\n  options?: UseEventListenerOptions\n): void;\n\n/**\n * A React hook for adding event listeners to DOM elements with automatic cleanup.\n * Supports window, document, HTMLElement, and RefObject targets.\n *\n * Features:\n * - Type-safe event handling with TypeScript inference\n * - Automatic cleanup on unmount\n * - Handler stability (no re-registration on handler change)\n * - Late-mount aware: a `RefObject` target that populates after the initial\n *   render (conditional/late mount) or changes on a remount is subscribed as\n *   soon as its element appears\n * - SSR compatible\n *\n * @param eventName - The event type to listen for\n * @param handler - The callback function called when the event fires\n * @param element - The target element (defaults to window)\n * @param options - Configuration options\n *\n * @example\n * ```tsx\n * // Window resize event (default target)\n * useEventListener(\"resize\", (e) => {\n *   console.log(\"Window resized:\", window.innerWidth);\n * });\n *\n * // Document keydown event\n * useEventListener(\"keydown\", (e) => {\n *   if (e.key === \"Escape\") closeModal();\n * }, document);\n *\n * // Element click event with ref\n * const buttonRef = useRef<HTMLButtonElement>(null);\n * useEventListener(\"click\", handleClick, buttonRef);\n *\n * // With options\n * useEventListener(\"scroll\", handleScroll, window, {\n *   passive: true,\n *   capture: false,\n * });\n *\n * // Conditional activation\n * useEventListener(\"mousemove\", handleMouseMove, document, {\n *   enabled: isTracking,\n * });\n * ```\n */\nexport function useEventListener(\n  eventName: string,\n  handler: (event: Event) => void,\n  element?: EventTargetType,\n  options: UseEventListenerOptions = {}\n): void {\n  const { enabled = true, capture = false, passive, once = false } = options;\n\n  // Store handler in ref to avoid re-registering event listeners\n  const handlerRef = useRef(handler);\n\n  // Sync the latest handler in a layout effect (not during render) so we never\n  // mutate a ref while rendering — a render may be thrown away under concurrent\n  // rendering / StrictMode. This mirrors the house useEventCallback pattern.\n  useIsomorphicLayoutEffect(() => {\n    handlerRef.current = handler;\n  }, [handler]);\n\n  // Resolve the target to a concrete element on every render and depend on the\n  // *resolved element* below (not the raw `element` arg). A RefObject's identity\n  // is stable, so depending on the ref itself would never re-run the effect when\n  // `ref.current` populates after mount (late/conditional mount) or changes on a\n  // remount — the listener would silently never attach. Depending on the\n  // resolved element re-subscribes as soon as it flips null -> element.\n  const targetElement = getTargetElement(element);\n\n  useEffect(() => {\n    // Don't add listener if disabled\n    if (!enabled) {\n      return;\n    }\n\n    // Don't add listener if there is no element (null target / unpopulated ref /\n    // SSR). getTargetElement already collapses all of these to null.\n    if (!targetElement) {\n      return;\n    }\n\n    // Internal handler that calls the ref (always latest handler)\n    const internalHandler = (event: Event) => {\n      handlerRef.current(event);\n    };\n\n    // Build event listener options\n    const eventOptions: AddEventListenerOptions = {\n      capture,\n      once,\n    };\n\n    // Only set passive if explicitly provided (respect browser defaults)\n    if (passive !== undefined) {\n      eventOptions.passive = passive;\n    }\n\n    // Add event listener\n    targetElement.addEventListener(eventName, internalHandler, eventOptions);\n\n    // Cleanup\n    return () => {\n      targetElement.removeEventListener(eventName, internalHandler, {\n        capture,\n      });\n    };\n  }, [eventName, targetElement, enabled, capture, passive, once]);\n}\n"],"mappings":";AAAA,SAAS,WAAW,iBAAiB,cAA8B;AAInE,IAAM,4BACJ,OAAO,WAAW,cAAc,kBAAkB;AAiD7C,SAAS,YACd,QAC+B;AAC/B,SACE,WAAW,QACX,WAAW,UACX,OAAO,WAAW,YAClB,aAAa;AAEjB;AAYO,SAAS,iBACd,QACwC;AAExC,MAAI,WAAW,QAAW;AACxB,WAAO,OAAO,WAAW,cAAc,SAAS;AAAA,EAClD;AAGA,MAAI,WAAW,MAAM;AACnB,WAAO;AAAA,EACT;AAGA,MAAI,YAAY,MAAM,GAAG;AACvB,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAyJO,SAAS,iBACd,WACA,SACA,SACA,UAAmC,CAAC,GAC9B;AACN,QAAM,EAAE,UAAU,MAAM,UAAU,OAAO,SAAS,OAAO,MAAM,IAAI;AAGnE,QAAM,aAAa,OAAO,OAAO;AAKjC,4BAA0B,MAAM;AAC9B,eAAW,UAAU;AAAA,EACvB,GAAG,CAAC,OAAO,CAAC;AAQZ,QAAM,gBAAgB,iBAAiB,OAAO;AAE9C,YAAU,MAAM;AAEd,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAIA,QAAI,CAAC,eAAe;AAClB;AAAA,IACF;AAGA,UAAM,kBAAkB,CAAC,UAAiB;AACxC,iBAAW,QAAQ,KAAK;AAAA,IAC1B;AAGA,UAAM,eAAwC;AAAA,MAC5C;AAAA,MACA;AAAA,IACF;AAGA,QAAI,YAAY,QAAW;AACzB,mBAAa,UAAU;AAAA,IACzB;AAGA,kBAAc,iBAAiB,WAAW,iBAAiB,YAAY;AAGvE,WAAO,MAAM;AACX,oBAAc,oBAAoB,WAAW,iBAAiB;AAAA,QAC5D;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,WAAW,eAAe,SAAS,SAAS,SAAS,IAAI,CAAC;AAChE;","names":[]}