UNPKG

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