react-cool-onclickoutside
Version:
React hook to listen for clicks outside of the component(s).
142 lines (118 loc) • 4.56 kB
JavaScript
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 };