1 | import * as React from 'react';
|
2 | import * as ReactDOM from 'react-dom';
|
3 | import PropTypes from 'prop-types';
|
4 | import ownerDocument from '../utils/ownerDocument';
|
5 | import useForkRef from '../utils/useForkRef';
|
6 | import useEventCallback from '../utils/useEventCallback';
|
7 | import { elementAcceptingRef, exactProp } from '@material-ui/utils';
|
8 |
|
9 | function mapEventPropToEvent(eventProp) {
|
10 | return eventProp.substring(2).toLowerCase();
|
11 | }
|
12 |
|
13 | function clickedRootScrollbar(event) {
|
14 | return document.documentElement.clientWidth < event.clientX || document.documentElement.clientHeight < event.clientY;
|
15 | }
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 | function ClickAwayListener(props) {
|
23 | const {
|
24 | children,
|
25 | disableReactTree = false,
|
26 | mouseEvent = 'onClick',
|
27 | onClickAway,
|
28 | touchEvent = 'onTouchEnd'
|
29 | } = props;
|
30 | const movedRef = React.useRef(false);
|
31 | const nodeRef = React.useRef(null);
|
32 | const activatedRef = React.useRef(false);
|
33 | const syntheticEventRef = React.useRef(false);
|
34 | React.useEffect(() => {
|
35 |
|
36 |
|
37 | setTimeout(() => {
|
38 | activatedRef.current = true;
|
39 | }, 0);
|
40 | return () => {
|
41 | activatedRef.current = false;
|
42 | };
|
43 | }, []);
|
44 |
|
45 | const handleOwnRef = React.useCallback(instance => {
|
46 |
|
47 | nodeRef.current = ReactDOM.findDOMNode(instance);
|
48 | }, []);
|
49 | const handleRef = useForkRef(children.ref, handleOwnRef);
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 | const handleClickAway = useEventCallback(event => {
|
57 |
|
58 |
|
59 | const insideReactTree = syntheticEventRef.current;
|
60 | syntheticEventRef.current = false;
|
61 |
|
62 |
|
63 |
|
64 | if (!activatedRef.current || !nodeRef.current || clickedRootScrollbar(event)) {
|
65 | return;
|
66 | }
|
67 |
|
68 |
|
69 | if (movedRef.current) {
|
70 | movedRef.current = false;
|
71 | return;
|
72 | }
|
73 |
|
74 | let insideDOM;
|
75 |
|
76 | if (event.composedPath) {
|
77 | insideDOM = event.composedPath().indexOf(nodeRef.current) > -1;
|
78 | } else {
|
79 |
|
80 | const doc = ownerDocument(nodeRef.current);
|
81 | insideDOM = !doc.documentElement.contains(event.target) || nodeRef.current.contains(event.target);
|
82 | }
|
83 |
|
84 | if (!insideDOM && (disableReactTree || !insideReactTree)) {
|
85 | onClickAway(event);
|
86 | }
|
87 | });
|
88 |
|
89 | const createHandleSynthetic = handlerName => event => {
|
90 | syntheticEventRef.current = true;
|
91 | const childrenPropsHandler = children.props[handlerName];
|
92 |
|
93 | if (childrenPropsHandler) {
|
94 | childrenPropsHandler(event);
|
95 | }
|
96 | };
|
97 |
|
98 | const childrenProps = {
|
99 | ref: handleRef
|
100 | };
|
101 |
|
102 | if (touchEvent !== false) {
|
103 | childrenProps[touchEvent] = createHandleSynthetic(touchEvent);
|
104 | }
|
105 |
|
106 | React.useEffect(() => {
|
107 | if (touchEvent !== false) {
|
108 | const mappedTouchEvent = mapEventPropToEvent(touchEvent);
|
109 | const doc = ownerDocument(nodeRef.current);
|
110 |
|
111 | const handleTouchMove = () => {
|
112 | movedRef.current = true;
|
113 | };
|
114 |
|
115 | doc.addEventListener(mappedTouchEvent, handleClickAway);
|
116 | doc.addEventListener('touchmove', handleTouchMove);
|
117 | return () => {
|
118 | doc.removeEventListener(mappedTouchEvent, handleClickAway);
|
119 | doc.removeEventListener('touchmove', handleTouchMove);
|
120 | };
|
121 | }
|
122 |
|
123 | return undefined;
|
124 | }, [handleClickAway, touchEvent]);
|
125 |
|
126 | if (mouseEvent !== false) {
|
127 | childrenProps[mouseEvent] = createHandleSynthetic(mouseEvent);
|
128 | }
|
129 |
|
130 | React.useEffect(() => {
|
131 | if (mouseEvent !== false) {
|
132 | const mappedMouseEvent = mapEventPropToEvent(mouseEvent);
|
133 | const doc = ownerDocument(nodeRef.current);
|
134 | doc.addEventListener(mappedMouseEvent, handleClickAway);
|
135 | return () => {
|
136 | doc.removeEventListener(mappedMouseEvent, handleClickAway);
|
137 | };
|
138 | }
|
139 |
|
140 | return undefined;
|
141 | }, [handleClickAway, mouseEvent]);
|
142 | return React.createElement(React.Fragment, null, React.cloneElement(children, childrenProps));
|
143 | }
|
144 |
|
145 | process.env.NODE_ENV !== "production" ? ClickAwayListener.propTypes = {
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 | |
152 |
|
153 |
|
154 | children: elementAcceptingRef.isRequired,
|
155 |
|
156 | |
157 |
|
158 |
|
159 |
|
160 | disableReactTree: PropTypes.bool,
|
161 |
|
162 | |
163 |
|
164 |
|
165 | mouseEvent: PropTypes.oneOf(['onClick', 'onMouseDown', 'onMouseUp', false]),
|
166 |
|
167 | |
168 |
|
169 |
|
170 | onClickAway: PropTypes.func.isRequired,
|
171 |
|
172 | |
173 |
|
174 |
|
175 | touchEvent: PropTypes.oneOf(['onTouchEnd', 'onTouchStart', false])
|
176 | } : void 0;
|
177 |
|
178 | if (process.env.NODE_ENV !== 'production') {
|
179 |
|
180 | ClickAwayListener['propTypes' + ''] = exactProp(ClickAwayListener.propTypes);
|
181 | }
|
182 |
|
183 | export default ClickAwayListener; |
\ | No newline at end of file |