import contains from 'dom-helpers/contains'; import listen from 'dom-helpers/listen'; import { useCallback, useEffect, useRef } from 'react'; import useEventCallback from '@restart/hooks/useEventCallback'; import warning from 'warning'; import ownerDocument from './ownerDocument'; var escapeKeyCode = 27; var noop = function noop() {}; function isLeftClickEvent(event) { return event.button === 0; } function isModifiedEvent(event) { return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey); } var getRefTarget = function getRefTarget(ref) { return ref && ('current' in ref ? ref.current : ref); }; /** * The `useRootClose` hook registers your callback on the document * when rendered. Powers the `` component. This is used achieve modal * style behavior where your callback is triggered when the user tries to * interact with the rest of the document or hits the `esc` key. * * @param {Ref| HTMLElement} ref The element boundary * @param {function} onRootClose * @param {object=} options * @param {boolean=} options.disabled * @param {string=} options.clickTrigger The DOM event name (click, mousedown, etc) to attach listeners on */ function useRootClose(ref, onRootClose, _temp) { var _ref = _temp === void 0 ? {} : _temp, disabled = _ref.disabled, _ref$clickTrigger = _ref.clickTrigger, clickTrigger = _ref$clickTrigger === void 0 ? 'click' : _ref$clickTrigger; var preventMouseRootCloseRef = useRef(false); var onClose = onRootClose || noop; var handleMouseCapture = useCallback(function (e) { var currentTarget = getRefTarget(ref); warning(!!currentTarget, 'RootClose captured a close event but does not have a ref to compare it to. ' + 'useRootClose(), should be passed a ref that resolves to a DOM node'); preventMouseRootCloseRef.current = !currentTarget || isModifiedEvent(e) || !isLeftClickEvent(e) || !!contains(currentTarget, e.target); }, [ref]); var handleMouse = useEventCallback(function (e) { if (!preventMouseRootCloseRef.current) { onClose(e); } }); var handleKeyUp = useEventCallback(function (e) { if (e.keyCode === escapeKeyCode) { onClose(e); } }); useEffect(function () { if (disabled || ref == null) return undefined; // Store the current event to avoid triggering handlers immediately // https://github.com/facebook/react/issues/20074 var currentEvent = window.event; var doc = ownerDocument(getRefTarget(ref)); // Use capture for this listener so it fires before React's listener, to // avoid false positives in the contains() check below if the target DOM // element is removed in the React mouse callback. var removeMouseCaptureListener = listen(doc, clickTrigger, handleMouseCapture, true); var removeMouseListener = listen(doc, clickTrigger, function (e) { // skip if this event is the same as the one running when we added the handlers if (e === currentEvent) { currentEvent = undefined; return; } handleMouse(e); }); var removeKeyupListener = listen(doc, 'keyup', function (e) { // skip if this event is the same as the one running when we added the handlers if (e === currentEvent) { currentEvent = undefined; return; } handleKeyUp(e); }); var mobileSafariHackListeners = []; if ('ontouchstart' in doc.documentElement) { mobileSafariHackListeners = [].slice.call(doc.body.children).map(function (el) { return listen(el, 'mousemove', noop); }); } return function () { removeMouseCaptureListener(); removeMouseListener(); removeKeyupListener(); mobileSafariHackListeners.forEach(function (remove) { return remove(); }); }; }, [ref, disabled, clickTrigger, handleMouseCapture, handleMouse, handleKeyUp]); } export default useRootClose;