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
JavaScript
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