UNPKG

7.31 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");
6
7Object.defineProperty(exports, "__esModule", {
8 value: true
9});
10exports.default = void 0;
11
12var React = _interopRequireWildcard(require("react"));
13
14var ReactDOM = _interopRequireWildcard(require("react-dom"));
15
16var _propTypes = _interopRequireDefault(require("prop-types"));
17
18var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
19
20var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
21
22var _useEventCallback = _interopRequireDefault(require("../utils/useEventCallback"));
23
24var _utils = require("@material-ui/utils");
25
26function mapEventPropToEvent(eventProp) {
27 return eventProp.substring(2).toLowerCase();
28}
29
30function clickedRootScrollbar(event) {
31 return document.documentElement.clientWidth < event.clientX || document.documentElement.clientHeight < event.clientY;
32}
33/**
34 * Listen for click events that occur somewhere in the document, outside of the element itself.
35 * For instance, if you need to hide a menu when people click anywhere else on your page.
36 */
37
38
39function ClickAwayListener(props) {
40 var children = props.children,
41 _props$disableReactTr = props.disableReactTree,
42 disableReactTree = _props$disableReactTr === void 0 ? false : _props$disableReactTr,
43 _props$mouseEvent = props.mouseEvent,
44 mouseEvent = _props$mouseEvent === void 0 ? 'onClick' : _props$mouseEvent,
45 onClickAway = props.onClickAway,
46 _props$touchEvent = props.touchEvent,
47 touchEvent = _props$touchEvent === void 0 ? 'onTouchEnd' : _props$touchEvent;
48 var movedRef = React.useRef(false);
49 var nodeRef = React.useRef(null);
50 var activatedRef = React.useRef(false);
51 var syntheticEventRef = React.useRef(false);
52 React.useEffect(function () {
53 // Ensure that this component is not "activated" synchronously.
54 // https://github.com/facebook/react/issues/20074
55 setTimeout(function () {
56 activatedRef.current = true;
57 }, 0);
58 return function () {
59 activatedRef.current = false;
60 };
61 }, []); // can be removed once we drop support for non ref forwarding class components
62
63 var handleOwnRef = React.useCallback(function (instance) {
64 // #StrictMode ready
65 nodeRef.current = ReactDOM.findDOMNode(instance);
66 }, []);
67 var handleRef = (0, _useForkRef.default)(children.ref, handleOwnRef); // The handler doesn't take event.defaultPrevented into account:
68 //
69 // event.preventDefault() is meant to stop default behaviours like
70 // clicking a checkbox to check it, hitting a button to submit a form,
71 // and hitting left arrow to move the cursor in a text input etc.
72 // Only special HTML elements have these default behaviors.
73
74 var handleClickAway = (0, _useEventCallback.default)(function (event) {
75 // Given developers can stop the propagation of the synthetic event,
76 // we can only be confident with a positive value.
77 var insideReactTree = syntheticEventRef.current;
78 syntheticEventRef.current = false; // 1. IE 11 support, which trigger the handleClickAway even after the unbind
79 // 2. The child might render null.
80 // 3. Behave like a blur listener.
81
82 if (!activatedRef.current || !nodeRef.current || clickedRootScrollbar(event)) {
83 return;
84 } // Do not act if user performed touchmove
85
86
87 if (movedRef.current) {
88 movedRef.current = false;
89 return;
90 }
91
92 var insideDOM; // If not enough, can use https://github.com/DieterHolvoet/event-propagation-path/blob/master/propagationPath.js
93
94 if (event.composedPath) {
95 insideDOM = event.composedPath().indexOf(nodeRef.current) > -1;
96 } else {
97 // TODO v6 remove dead logic https://caniuse.com/#search=composedPath.
98 var doc = (0, _ownerDocument.default)(nodeRef.current);
99 insideDOM = !doc.documentElement.contains(event.target) || nodeRef.current.contains(event.target);
100 }
101
102 if (!insideDOM && (disableReactTree || !insideReactTree)) {
103 onClickAway(event);
104 }
105 }); // Keep track of mouse/touch events that bubbled up through the portal.
106
107 var createHandleSynthetic = function createHandleSynthetic(handlerName) {
108 return function (event) {
109 syntheticEventRef.current = true;
110 var childrenPropsHandler = children.props[handlerName];
111
112 if (childrenPropsHandler) {
113 childrenPropsHandler(event);
114 }
115 };
116 };
117
118 var childrenProps = {
119 ref: handleRef
120 };
121
122 if (touchEvent !== false) {
123 childrenProps[touchEvent] = createHandleSynthetic(touchEvent);
124 }
125
126 React.useEffect(function () {
127 if (touchEvent !== false) {
128 var mappedTouchEvent = mapEventPropToEvent(touchEvent);
129 var doc = (0, _ownerDocument.default)(nodeRef.current);
130
131 var handleTouchMove = function handleTouchMove() {
132 movedRef.current = true;
133 };
134
135 doc.addEventListener(mappedTouchEvent, handleClickAway);
136 doc.addEventListener('touchmove', handleTouchMove);
137 return function () {
138 doc.removeEventListener(mappedTouchEvent, handleClickAway);
139 doc.removeEventListener('touchmove', handleTouchMove);
140 };
141 }
142
143 return undefined;
144 }, [handleClickAway, touchEvent]);
145
146 if (mouseEvent !== false) {
147 childrenProps[mouseEvent] = createHandleSynthetic(mouseEvent);
148 }
149
150 React.useEffect(function () {
151 if (mouseEvent !== false) {
152 var mappedMouseEvent = mapEventPropToEvent(mouseEvent);
153 var doc = (0, _ownerDocument.default)(nodeRef.current);
154 doc.addEventListener(mappedMouseEvent, handleClickAway);
155 return function () {
156 doc.removeEventListener(mappedMouseEvent, handleClickAway);
157 };
158 }
159
160 return undefined;
161 }, [handleClickAway, mouseEvent]);
162 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.cloneElement(children, childrenProps));
163}
164
165process.env.NODE_ENV !== "production" ? ClickAwayListener.propTypes = {
166 // ----------------------------- Warning --------------------------------
167 // | These PropTypes are generated from the TypeScript type definitions |
168 // | To update them edit the d.ts file and run "yarn proptypes" |
169 // ----------------------------------------------------------------------
170
171 /**
172 * The wrapped element.
173 */
174 children: _utils.elementAcceptingRef.isRequired,
175
176 /**
177 * If `true`, the React tree is ignored and only the DOM tree is considered.
178 * This prop changes how portaled elements are handled.
179 */
180 disableReactTree: _propTypes.default.bool,
181
182 /**
183 * The mouse event to listen to. You can disable the listener by providing `false`.
184 */
185 mouseEvent: _propTypes.default.oneOf(['onClick', 'onMouseDown', 'onMouseUp', false]),
186
187 /**
188 * Callback fired when a "click away" event is detected.
189 */
190 onClickAway: _propTypes.default.func.isRequired,
191
192 /**
193 * The touch event to listen to. You can disable the listener by providing `false`.
194 */
195 touchEvent: _propTypes.default.oneOf(['onTouchEnd', 'onTouchStart', false])
196} : void 0;
197
198if (process.env.NODE_ENV !== 'production') {
199 // eslint-disable-next-line
200 ClickAwayListener['propTypes' + ''] = (0, _utils.exactProp)(ClickAwayListener.propTypes);
201}
202
203var _default = ClickAwayListener;
204exports.default = _default;
\No newline at end of file