UNPKG

click-away-listener

Version:

A React component and hook library for detecting clicks outside an element, compatible with React 18 and Next.js

104 lines 4.63 kB
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; import React, { useEffect, useRef, useCallback, useState } from 'react'; import { isMouseEvent, isTouchEvent, isFocusEvent, isKeyboardEvent, mergeRefs, isInExcluded, getEventGroup } from './utils'; var DEFAULT_EVENTS = { mouseEvents: ['mousedown'], touchEvents: ['touchstart'] }; export var ClickAwayListener = function (_a) { var children = _a.children, onClickAway = _a.onClickAway, _b = _a.disabled, disabled = _b === void 0 ? false : _b, _c = _a.eventConfig, eventConfig = _c === void 0 ? DEFAULT_EVENTS : _c, _d = _a.excludeRefs, excludeRefs = _d === void 0 ? [] : _d, _e = _a.debounceTimeout, debounceTimeout = _e === void 0 ? 0 : _e, _f = _a.clickTolerance, clickTolerance = _f === void 0 ? 5 : _f, _g = _a.testMode, testMode = _g === void 0 ? false : _g; var nodeRef = useRef(null); var _h = useState(null), coords = _h[0], setCoords = _h[1]; var mountedRef = useRef(false); var timerRef = useRef(); // Handle React 18+ double mount in strict mode useEffect(function () { mountedRef.current = true; return function () { mountedRef.current = false; if (timerRef.current) { window.clearTimeout(timerRef.current); } }; }, []); var handleEvent = useCallback(function (event) { var _a; if (!mountedRef.current || disabled) return; var target = event.target; var eventGroup = getEventGroup(event); // Skip if target is null or not in document if (!target || !document.contains(target)) return; // Skip if inside our element or excluded elements if (((_a = nodeRef.current) === null || _a === void 0 ? void 0 : _a.contains(target)) || isInExcluded(target, excludeRefs)) { return; } // For mouse events, check movement tolerance if (isMouseEvent(event) && clickTolerance > 0) { var clientX = event.clientX, clientY = event.clientY; if (coords) { var distance = Math.sqrt(Math.pow(clientX - coords.x, 2) + Math.pow(clientY - coords.y, 2)); if (distance > clickTolerance) return; } setCoords({ x: clientX, y: clientY }); } var handler = function () { if (eventGroup === 'mouse' && isMouseEvent(event)) { onClickAway(event); } else if (eventGroup === 'touch' && isTouchEvent(event)) { onClickAway(event); } else if (eventGroup === 'focus' && isFocusEvent(event)) { onClickAway(event); } else if (eventGroup === 'keyboard' && isKeyboardEvent(event)) { onClickAway(event); } }; if (debounceTimeout > 0) { if (timerRef.current) { window.clearTimeout(timerRef.current); } timerRef.current = window.setTimeout(handler, debounceTimeout); } else { handler(); } }, [disabled, excludeRefs, onClickAway, clickTolerance, coords, debounceTimeout]); // Register event listeners useEffect(function () { if (testMode || disabled) return; var events = __spreadArray(__spreadArray(__spreadArray(__spreadArray([], (eventConfig.mouseEvents || []), true), (eventConfig.touchEvents || []), true), (eventConfig.focusEvents || []), true), (eventConfig.keyboardEvents || []), true); var options = { capture: true }; events.forEach(function (event) { document.addEventListener(event, handleEvent, options); }); return function () { events.forEach(function (event) { document.removeEventListener(event, handleEvent, options); }); }; }, [eventConfig, handleEvent, disabled, testMode]); // Handle ref forwarding var childRef = children.ref; var combinedRef = mergeRefs(nodeRef, childRef); return React.cloneElement(React.Children.only(children), { ref: combinedRef }); }; ClickAwayListener.displayName = 'ClickAwayListener'; //# sourceMappingURL=index.js.map