UNPKG

@primer/components

Version:
79 lines (65 loc) 2.32 kB
import { useEffect, useCallback, useMemo } from 'react'; // Because events are handled at the document level, we provide a mechanism for early return. const stopPropagation = true; /** * Calls all handlers in reverse order * @param event The MouseEvent generated by the click event. */ function handleClick(event) { if (!event.defaultPrevented) { for (const handler of Object.values(registry).reverse()) { // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (handler(event) === stopPropagation || event.defaultPrevented) { break; } } } } const registry = {}; function register(id, handler) { registry[id] = handler; } function deregister(id) { delete registry[id]; } // For auto-incrementing unique identifiers for registered handlers. let handlerId = 0; export const useOnOutsideClick = ({ containerRef, ignoreClickRefs, onClickOutside }) => { const id = useMemo(() => handlerId++, []); const handler = useCallback(event => { var _containerRef$current; // don't call click handler if the mouse event was triggered by an auxiliary button (right click/wheel button/etc) if (event instanceof MouseEvent && event.button > 0) { return stopPropagation; } // don't call handler if the click happened inside of the container if ((_containerRef$current = containerRef.current) !== null && _containerRef$current !== void 0 && _containerRef$current.contains(event.target)) { return stopPropagation; } // don't call handler if click happened on an ignored ref if (ignoreClickRefs && ignoreClickRefs.some(({ current }) => current === null || current === void 0 ? void 0 : current.contains(event.target))) { return stopPropagation; } onClickOutside(event); }, [containerRef, ignoreClickRefs, onClickOutside]); useEffect(() => { if (Object.keys(registry).length === 0) { // use capture to ensure we get all events document.addEventListener('mousedown', handleClick, { capture: true }); } register(id, handler); return () => { deregister(id); if (Object.keys(registry).length === 0) { document.removeEventListener('mousedown', handleClick, { capture: true }); } }; }, [id, handler]); };