UNPKG

3.92 kBJavaScriptView Raw
1import contains from 'dom-helpers/contains';
2import listen from 'dom-helpers/listen';
3import { useCallback, useEffect, useRef } from 'react';
4import useEventCallback from '@restart/hooks/useEventCallback';
5import warning from 'warning';
6import ownerDocument from './ownerDocument';
7var escapeKeyCode = 27;
8
9var noop = function noop() {};
10
11function isLeftClickEvent(event) {
12 return event.button === 0;
13}
14
15function isModifiedEvent(event) {
16 return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
17}
18
19var getRefTarget = function getRefTarget(ref) {
20 return ref && ('current' in ref ? ref.current : ref);
21};
22
23/**
24 * The `useRootClose` hook registers your callback on the document
25 * when rendered. Powers the `<Overlay/>` component. This is used achieve modal
26 * style behavior where your callback is triggered when the user tries to
27 * interact with the rest of the document or hits the `esc` key.
28 *
29 * @param {Ref<HTMLElement>| HTMLElement} ref The element boundary
30 * @param {function} onRootClose
31 * @param {object=} options
32 * @param {boolean=} options.disabled
33 * @param {string=} options.clickTrigger The DOM event name (click, mousedown, etc) to attach listeners on
34 */
35function useRootClose(ref, onRootClose, _temp) {
36 var _ref = _temp === void 0 ? {} : _temp,
37 disabled = _ref.disabled,
38 _ref$clickTrigger = _ref.clickTrigger,
39 clickTrigger = _ref$clickTrigger === void 0 ? 'click' : _ref$clickTrigger;
40
41 var preventMouseRootCloseRef = useRef(false);
42 var onClose = onRootClose || noop;
43 var handleMouseCapture = useCallback(function (e) {
44 var currentTarget = getRefTarget(ref);
45 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');
46 preventMouseRootCloseRef.current = !currentTarget || isModifiedEvent(e) || !isLeftClickEvent(e) || !!contains(currentTarget, e.target);
47 }, [ref]);
48 var handleMouse = useEventCallback(function (e) {
49 if (!preventMouseRootCloseRef.current) {
50 onClose(e);
51 }
52 });
53 var handleKeyUp = useEventCallback(function (e) {
54 if (e.keyCode === escapeKeyCode) {
55 onClose(e);
56 }
57 });
58 useEffect(function () {
59 if (disabled || ref == null) return undefined; // Store the current event to avoid triggering handlers immediately
60 // https://github.com/facebook/react/issues/20074
61
62 var currentEvent = window.event;
63 var doc = ownerDocument(getRefTarget(ref)); // Use capture for this listener so it fires before React's listener, to
64 // avoid false positives in the contains() check below if the target DOM
65 // element is removed in the React mouse callback.
66
67 var removeMouseCaptureListener = listen(doc, clickTrigger, handleMouseCapture, true);
68 var removeMouseListener = listen(doc, clickTrigger, function (e) {
69 // skip if this event is the same as the one running when we added the handlers
70 if (e === currentEvent) {
71 currentEvent = undefined;
72 return;
73 }
74
75 handleMouse(e);
76 });
77 var removeKeyupListener = listen(doc, 'keyup', function (e) {
78 // skip if this event is the same as the one running when we added the handlers
79 if (e === currentEvent) {
80 currentEvent = undefined;
81 return;
82 }
83
84 handleKeyUp(e);
85 });
86 var mobileSafariHackListeners = [];
87
88 if ('ontouchstart' in doc.documentElement) {
89 mobileSafariHackListeners = [].slice.call(doc.body.children).map(function (el) {
90 return listen(el, 'mousemove', noop);
91 });
92 }
93
94 return function () {
95 removeMouseCaptureListener();
96 removeMouseListener();
97 removeKeyupListener();
98 mobileSafariHackListeners.forEach(function (remove) {
99 return remove();
100 });
101 };
102 }, [ref, disabled, clickTrigger, handleMouseCapture, handleMouse, handleKeyUp]);
103}
104
105export default useRootClose;
\No newline at end of file