UNPKG

@mui/base

Version:

Base UI is a library of headless ('unstyled') React components and low-level hooks. You gain complete control over your app's CSS and accessibility features.

183 lines (177 loc) 8.03 kB
"use strict"; 'use client'; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.ClickAwayListener = ClickAwayListener; var React = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _utils = require("@mui/utils"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } // TODO: return `EventHandlerName extends `on${infer EventName}` ? Lowercase<EventName> : never` once generatePropTypes runs with TS 4.1 function mapEventPropToEvent(eventProp) { return eventProp.substring(2).toLowerCase(); } function clickedRootScrollbar(event, doc) { return doc.documentElement.clientWidth < event.clientX || doc.documentElement.clientHeight < event.clientY; } /** * Listen for click events that occur somewhere in the document, outside of the element itself. * For instance, if you need to hide a menu when people click anywhere else on your page. * * Demos: * * - [Click-Away Listener](https://mui.com/base-ui/react-click-away-listener/) * * API: * * - [ClickAwayListener API](https://mui.com/base-ui/react-click-away-listener/components-api/#click-away-listener) */ function ClickAwayListener(props) { const { children, disableReactTree = false, mouseEvent = 'onClick', onClickAway, touchEvent = 'onTouchEnd' } = props; const movedRef = React.useRef(false); const nodeRef = React.useRef(null); const activatedRef = React.useRef(false); const syntheticEventRef = React.useRef(false); React.useEffect(() => { // Ensure that this component is not "activated" synchronously. // https://github.com/facebook/react/issues/20074 setTimeout(() => { activatedRef.current = true; }, 0); return () => { activatedRef.current = false; }; }, []); const handleRef = (0, _utils.unstable_useForkRef)( // @ts-expect-error TODO upstream fix children.ref, nodeRef); // The handler doesn't take event.defaultPrevented into account: // // event.preventDefault() is meant to stop default behaviors like // clicking a checkbox to check it, hitting a button to submit a form, // and hitting left arrow to move the cursor in a text input etc. // Only special HTML elements have these default behaviors. const handleClickAway = (0, _utils.unstable_useEventCallback)(event => { // Given developers can stop the propagation of the synthetic event, // we can only be confident with a positive value. const insideReactTree = syntheticEventRef.current; syntheticEventRef.current = false; const doc = (0, _utils.unstable_ownerDocument)(nodeRef.current); // 1. IE11 support, which trigger the handleClickAway even after the unbind // 2. The child might render null. // 3. Behave like a blur listener. if (!activatedRef.current || !nodeRef.current || 'clientX' in event && clickedRootScrollbar(event, doc)) { return; } // Do not act if user performed touchmove if (movedRef.current) { movedRef.current = false; return; } let insideDOM; // If not enough, can use https://github.com/DieterHolvoet/event-propagation-path/blob/master/propagationPath.js if (event.composedPath) { insideDOM = event.composedPath().indexOf(nodeRef.current) > -1; } else { insideDOM = !doc.documentElement.contains( // @ts-expect-error returns `false` as intended when not dispatched from a Node event.target) || nodeRef.current.contains( // @ts-expect-error returns `false` as intended when not dispatched from a Node event.target); } if (!insideDOM && (disableReactTree || !insideReactTree)) { onClickAway(event); } }); // Keep track of mouse/touch events that bubbled up through the portal. const createHandleSynthetic = handlerName => event => { syntheticEventRef.current = true; const childrenPropsHandler = children.props[handlerName]; if (childrenPropsHandler) { childrenPropsHandler(event); } }; const childrenProps = { ref: handleRef }; if (touchEvent !== false) { childrenProps[touchEvent] = createHandleSynthetic(touchEvent); } React.useEffect(() => { if (touchEvent !== false) { const mappedTouchEvent = mapEventPropToEvent(touchEvent); const doc = (0, _utils.unstable_ownerDocument)(nodeRef.current); const handleTouchMove = () => { movedRef.current = true; }; doc.addEventListener(mappedTouchEvent, handleClickAway); doc.addEventListener('touchmove', handleTouchMove); return () => { doc.removeEventListener(mappedTouchEvent, handleClickAway); doc.removeEventListener('touchmove', handleTouchMove); }; } return undefined; }, [handleClickAway, touchEvent]); if (mouseEvent !== false) { childrenProps[mouseEvent] = createHandleSynthetic(mouseEvent); } React.useEffect(() => { if (mouseEvent !== false) { const mappedMouseEvent = mapEventPropToEvent(mouseEvent); const doc = (0, _utils.unstable_ownerDocument)(nodeRef.current); doc.addEventListener(mappedMouseEvent, handleClickAway); return () => { doc.removeEventListener(mappedMouseEvent, handleClickAway); }; } return undefined; }, [handleClickAway, mouseEvent]); return /*#__PURE__*/(0, _jsxRuntime.jsx)(React.Fragment, { children: /*#__PURE__*/React.cloneElement(children, childrenProps) }); } process.env.NODE_ENV !== "production" ? ClickAwayListener.propTypes /* remove-proptypes */ = { // ┌────────────────────────────── Warning ──────────────────────────────┐ // │ These PropTypes are generated from the TypeScript type definitions. │ // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ // └─────────────────────────────────────────────────────────────────────┘ /** * The wrapped element. */ children: _utils.elementAcceptingRef.isRequired, /** * If `true`, the React tree is ignored and only the DOM tree is considered. * This prop changes how portaled elements are handled. * @default false */ disableReactTree: _propTypes.default.bool, /** * The mouse event to listen to. You can disable the listener by providing `false`. * @default 'onClick' */ mouseEvent: _propTypes.default.oneOf(['onClick', 'onMouseDown', 'onMouseUp', 'onPointerDown', 'onPointerUp', false]), /** * Callback fired when a "click away" event is detected. */ onClickAway: _propTypes.default.func.isRequired, /** * The touch event to listen to. You can disable the listener by providing `false`. * @default 'onTouchEnd' */ touchEvent: _propTypes.default.oneOf(['onTouchEnd', 'onTouchStart', false]) } : void 0; if (process.env.NODE_ENV !== 'production') { // eslint-disable-next-line ClickAwayListener['propTypes' + ''] = (0, _utils.exactProp)(ClickAwayListener.propTypes); }