UNPKG

react-cool-onclickoutside

Version:

React hook to listen for clicks outside of the component(s).

142 lines (118 loc) 4.56 kB
import { useState, useRef, useCallback, useEffect } from 'react'; var canUsePassiveEvents = (function () { if (typeof window === "undefined" || typeof window.addEventListener !== "function") return false; var passive = false; var options = Object.defineProperty({}, "passive", { // eslint-disable-next-line getter-return get: function get() { passive = true; } }); var noop = function noop() { return null; }; window.addEventListener("test", noop, options); window.removeEventListener("test", noop, options); return passive; }); var DEFAULT_IGNORE_CLASS = "ignore-onclickoutside"; var checkClass = function checkClass(el, cl) { var _el$classList; return (_el$classList = el.classList) == null ? void 0 : _el$classList.contains(cl); }; var hasIgnoreClass = function hasIgnoreClass(e, ignoreClass) { var el = e.target || e; while (el) { if (Array.isArray(ignoreClass)) { // eslint-disable-next-line no-loop-func if (ignoreClass.some(function (c) { return checkClass(el, c); })) return true; } else if (checkClass(el, ignoreClass)) { return true; } el = el.parentElement; } return false; }; var clickedOnScrollbar = function clickedOnScrollbar(e) { return document.documentElement.clientWidth <= e.clientX || document.documentElement.clientHeight <= e.clientY; }; var getEventOptions = function getEventOptions(type) { return type.includes("touch") && canUsePassiveEvents() ? { passive: true } : false; }; var useOnclickOutside = function useOnclickOutside(callback, _temp) { var _ref = _temp === void 0 ? {} : _temp, refsOpt = _ref.refs, disabled = _ref.disabled, _ref$eventTypes = _ref.eventTypes, eventTypes = _ref$eventTypes === void 0 ? ["mousedown", "touchstart"] : _ref$eventTypes, excludeScrollbar = _ref.excludeScrollbar, _ref$ignoreClass = _ref.ignoreClass, ignoreClass = _ref$ignoreClass === void 0 ? DEFAULT_IGNORE_CLASS : _ref$ignoreClass, _ref$detectIFrame = _ref.detectIFrame, detectIFrame = _ref$detectIFrame === void 0 ? true : _ref$detectIFrame; var _useState = useState([]), refsState = _useState[0], setRefsState = _useState[1]; var callbackRef = useRef(callback); callbackRef.current = callback; var ref = useCallback(function (el) { return setRefsState(function (prevState) { return [].concat(prevState, [{ current: el }]); }); }, []); useEffect(function () { if (!(refsOpt != null && refsOpt.length) && !refsState.length) return; var getEls = function getEls() { var els = []; (refsOpt || refsState).forEach(function (_ref2) { var current = _ref2.current; return current && els.push(current); }); return els; }; var handler = function handler(e) { if (!hasIgnoreClass(e, ignoreClass) && !(excludeScrollbar && clickedOnScrollbar(e)) && getEls().every(function (el) { return !el.contains(e.target); })) callbackRef.current(e); }; var blurHandler = function blurHandler(e) { return (// On firefox the iframe becomes document.activeElement in the next event loop setTimeout(function () { var _document = document, activeElement = _document.activeElement; if ((activeElement == null ? void 0 : activeElement.tagName) === "IFRAME" && !hasIgnoreClass(activeElement, ignoreClass) && !getEls().includes(activeElement)) callbackRef.current(e); }, 0) ); }; var removeEventListener = function removeEventListener() { eventTypes.forEach(function (type) { return (// @ts-expect-error document.removeEventListener(type, handler, getEventOptions(type)) ); }); if (detectIFrame) window.removeEventListener("blur", blurHandler); }; if (disabled) { removeEventListener(); return; } eventTypes.forEach(function (type) { return document.addEventListener(type, handler, getEventOptions(type)); }); if (detectIFrame) window.addEventListener("blur", blurHandler); // eslint-disable-next-line consistent-return return function () { return removeEventListener(); }; }, // eslint-disable-next-line react-hooks/exhaustive-deps [refsState, ignoreClass, excludeScrollbar, disabled, detectIFrame, // eslint-disable-next-line react-hooks/exhaustive-deps JSON.stringify(eventTypes)]); return ref; }; export default useOnclickOutside; export { DEFAULT_IGNORE_CLASS };