All files / use-click-any-where/src useClickAnyWhere.ts

92.3% Statements 12/13
87.5% Branches 7/8
100% Functions 4/4
92.3% Lines 12/13

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                                                                                                                                                      35x     35x     35x   35x   33x         33x 7x       26x 14x       26x     26x 26x        
import { useEffect, useRef } from "react";
 
/**
 * Options for useClickAnyWhere hook
 */
export interface UseClickAnyWhereOptions {
  /**
   * Whether the event listener is enabled
   * @default true
   */
  enabled?: boolean;
  /**
   * Whether to use event capture phase
   * @default false
   */
  capture?: boolean;
  /**
   * Whether to use passive event listener
   * @default true
   */
  passive?: boolean;
}
 
/**
 * Handler type for click events
 */
export type ClickAnyWhereHandler = (event: MouseEvent) => void;
 
/**
 * Detects document-wide click events and calls the provided handler.
 * Useful for closing dropdowns, modals, or any component when clicking outside.
 *
 * @param handler - Callback function called when a click is detected anywhere on the document
 * @param options - Configuration options for the event listener
 *
 * @example
 * ```tsx
 * function ClickTracker() {
 *   const [lastClick, setLastClick] = useState({ x: 0, y: 0 });
 *
 *   useClickAnyWhere((event) => {
 *     setLastClick({ x: event.clientX, y: event.clientY });
 *   });
 *
 *   return (
 *     <div>
 *       Last click: ({lastClick.x}, {lastClick.y})
 *     </div>
 *   );
 * }
 * ```
 *
 * @example
 * ```tsx
 * // Conditional activation
 * function Dropdown({ isOpen, onClose }) {
 *   useClickAnyWhere(
 *     () => onClose(),
 *     { enabled: isOpen }
 *   );
 *
 *   return isOpen ? <div>Dropdown content</div> : null;
 * }
 * ```
 *
 * @example
 * ```tsx
 * // With capture phase
 * useClickAnyWhere(handleClick, { capture: true });
 * ```
 */
export function useClickAnyWhere(
  handler: ClickAnyWhereHandler,
  options: UseClickAnyWhereOptions = {}
): void {
  const { enabled = true, capture = false, passive = true } = options;
 
  // Store handler in ref to avoid re-registering event listener
  const handlerRef = useRef<ClickAnyWhereHandler>(handler);
 
  // Update ref when handler changes
  handlerRef.current = handler;
 
  useEffect(() => {
    // SSR check
    Iif (typeof document === "undefined") {
      return;
    }
 
    // Don't add listener if disabled
    if (!enabled) {
      return;
    }
 
    // Internal handler that calls the latest handler ref
    const internalHandler = (event: MouseEvent) => {
      handlerRef.current(event);
    };
 
    // Add event listener
    document.addEventListener("click", internalHandler, { capture, passive });
 
    // Cleanup
    return () => {
      document.removeEventListener("click", internalHandler, { capture });
    };
  }, [enabled, capture, passive]);
}