UNPKG

7.44 kBJavaScriptView Raw
1"use strict";
2'use client';
3
4var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
5var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
6Object.defineProperty(exports, "__esModule", {
7 value: true
8});
9exports.ClickAwayListener = ClickAwayListener;
10var React = _interopRequireWildcard(require("react"));
11var _propTypes = _interopRequireDefault(require("prop-types"));
12var _utils = require("@mui/utils");
13var _getReactElementRef = _interopRequireDefault(require("@mui/utils/getReactElementRef"));
14var _jsxRuntime = require("react/jsx-runtime");
15// TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase<EventName> : never` once generatePropTypes runs with TS 4.1
16function mapEventPropToEvent(eventProp) {
17 return eventProp.substring(2).toLowerCase();
18}
19function clickedRootScrollbar(event, doc) {
20 return doc.documentElement.clientWidth < event.clientX || doc.documentElement.clientHeight < event.clientY;
21}
22/**
23 * Listen for click events that occur somewhere in the document, outside of the element itself.
24 * For instance, if you need to hide a menu when people click anywhere else on your page.
25 *
26 * Demos:
27 *
28 * - [Click-Away Listener](https://mui.com/material-ui/react-click-away-listener/)
29 * - [Menu](https://mui.com/material-ui/react-menu/)
30 *
31 * API:
32 *
33 * - [ClickAwayListener API](https://mui.com/material-ui/api/click-away-listener/)
34 */
35function ClickAwayListener(props) {
36 const {
37 children,
38 disableReactTree = false,
39 mouseEvent = 'onClick',
40 onClickAway,
41 touchEvent = 'onTouchEnd'
42 } = props;
43 const movedRef = React.useRef(false);
44 const nodeRef = React.useRef(null);
45 const activatedRef = React.useRef(false);
46 const syntheticEventRef = React.useRef(false);
47 React.useEffect(() => {
48 // Ensure that this component is not "activated" synchronously.
49 // https://github.com/facebook/react/issues/20074
50 setTimeout(() => {
51 activatedRef.current = true;
52 }, 0);
53 return () => {
54 activatedRef.current = false;
55 };
56 }, []);
57 const handleRef = (0, _utils.unstable_useForkRef)((0, _getReactElementRef.default)(children), 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().includes(nodeRef.current);
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 the TypeScript types and run `pnpm 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}
\No newline at end of file