forked from react-bootstrap/react-overlays
-
Notifications
You must be signed in to change notification settings - Fork 0
/
useRootClose.js
108 lines (93 loc) · 3.05 KB
/
useRootClose.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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';
const escapeKeyCode = 27;
const noop = () => {};
function isLeftClickEvent(event) {
return event.button === 0;
}
function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
/**
* The `useRootClose` hook registers your callback on the document
* when rendered. Powers the `<Overlay/>` 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>|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,
{ disabled, clickTrigger = 'click' } = {},
) {
const preventMouseRootCloseRef = useRef(false);
const onClose = onRootClose || noop;
const handleMouseCapture = useCallback(
e => {
const currentTarget = ref && ('current' in ref ? ref.current : 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],
);
const handleMouse = useEventCallback(e => {
if (!preventMouseRootCloseRef.current) {
onClose(e);
}
});
const handleKeyUp = useEventCallback(e => {
if (e.keyCode === escapeKeyCode) {
onClose(e);
}
});
useEffect(() => {
if (disabled || ref == null) return undefined;
// 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.
const removeMouseCaptureListener = listen(
document,
clickTrigger,
handleMouseCapture,
true,
);
const removeMouseListener = listen(document, clickTrigger, handleMouse);
const removeKeyupListener = listen(document, 'keyup', handleKeyUp);
let mobileSafariHackListeners = [];
if ('ontouchstart' in document.documentElement) {
mobileSafariHackListeners = [].slice
.call(document.body.children)
.map(el => listen(el, 'mousemove', noop));
}
return () => {
removeMouseCaptureListener();
removeMouseListener();
removeKeyupListener();
mobileSafariHackListeners.forEach(remove => remove());
};
}, [
ref,
disabled,
clickTrigger,
handleMouseCapture,
handleMouse,
handleKeyUp,
]);
}
export default useRootClose;