UNPKG

@ducor/hooks

Version:

A collection of useful React hooks for building modern web applications. Includes hooks for clipboard operations, window events, intervals, timeouts, and more.

81 lines (80 loc) 3.07 kB
import { useCallback, useEffect, useRef } from "react"; const isValidEvent = (ev, ref) => { var _a; const target = ev.target; if ("button" in ev && ev.button > 0) return false; if (target) if (!getOwnerDocument(target).contains(target)) return false; return !((_a = ref.current) === null || _a === void 0 ? void 0 : _a.contains(target)); }; function isElement(el) { return (el != null && typeof el == "object" && "nodeType" in el && el.nodeType === Node.ELEMENT_NODE); } function getOwnerDocument(el) { return isElement(el) ? el.ownerDocument : document; } export function useCallbackRef(callback, deps = []) { const callbackRef = useRef(callback); useEffect(() => { callbackRef.current = callback; }); return useCallback(((...args) => { var _a; return (_a = callbackRef.current) === null || _a === void 0 ? void 0 : _a.call(callbackRef, ...args); }), // eslint-disable-next-line react-hooks/exhaustive-deps deps); } /** * `useOutsideClick` is a custom hook that detects click events outside of an element. * * @see Docs https://ui.ducor.net/hooks/use-outside-click */ const useOutsideClick = ({ ref: elementRef, enabled = true, handler, }) => { const ref = (elementRef ? elementRef : useRef(null)); const handlerRef = useCallbackRef(handler); const state = useRef({ ignoreEmulatedMouseEvents: false, isPointerDown: false, }); useEffect(() => { if (!enabled) return; const onPointerDown = (ev) => { if (isValidEvent(ev, ref)) state.current.isPointerDown = true; }; const onMouseUp = (ev) => { if (state.current.ignoreEmulatedMouseEvents) { state.current.ignoreEmulatedMouseEvents = false; return; } if (state.current.isPointerDown && handler && isValidEvent(ev, ref)) { state.current.isPointerDown = false; handlerRef(ev); } }; const onTouchEnd = (ev) => { state.current.ignoreEmulatedMouseEvents = true; if (handler && state.current.isPointerDown && isValidEvent(ev, ref)) { state.current.isPointerDown = false; handlerRef(ev); } }; const doc = getOwnerDocument(ref.current); doc.addEventListener("mousedown", onPointerDown, true); doc.addEventListener("mouseup", onMouseUp, true); doc.addEventListener("touchstart", onPointerDown, true); doc.addEventListener("touchend", onTouchEnd, true); return () => { doc.removeEventListener("mousedown", onPointerDown, true); doc.removeEventListener("mouseup", onMouseUp, true); doc.removeEventListener("touchstart", onPointerDown, true); doc.removeEventListener("touchend", onTouchEnd, true); }; }, [handler, ref, handlerRef, state, enabled]); return ref; }; export default useOutsideClick;