UNPKG

21.7 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.default = exports.PopoverRoot = exports.PopoverPaper = void 0;
10exports.getOffsetLeft = getOffsetLeft;
11exports.getOffsetTop = getOffsetTop;
12var React = _interopRequireWildcard(require("react"));
13var _propTypes = _interopRequireDefault(require("prop-types"));
14var _clsx = _interopRequireDefault(require("clsx"));
15var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
16var _HTMLElementType = _interopRequireDefault(require("@mui/utils/HTMLElementType"));
17var _refType = _interopRequireDefault(require("@mui/utils/refType"));
18var _elementTypeAcceptingRef = _interopRequireDefault(require("@mui/utils/elementTypeAcceptingRef"));
19var _integerPropType = _interopRequireDefault(require("@mui/utils/integerPropType"));
20var _chainPropTypes = _interopRequireDefault(require("@mui/utils/chainPropTypes"));
21var _isHostComponent = _interopRequireDefault(require("../utils/isHostComponent"));
22var _zeroStyled = require("../zero-styled");
23var _DefaultPropsProvider = require("../DefaultPropsProvider");
24var _debounce = _interopRequireDefault(require("../utils/debounce"));
25var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
26var _ownerWindow = _interopRequireDefault(require("../utils/ownerWindow"));
27var _useForkRef = _interopRequireDefault(require("../utils/useForkRef"));
28var _Grow = _interopRequireDefault(require("../Grow"));
29var _Modal = _interopRequireDefault(require("../Modal"));
30var _Paper = _interopRequireDefault(require("../Paper"));
31var _popoverClasses = require("./popoverClasses");
32var _useSlot = _interopRequireDefault(require("../utils/useSlot"));
33var _jsxRuntime = require("react/jsx-runtime");
34function getOffsetTop(rect, vertical) {
35 let offset = 0;
36 if (typeof vertical === 'number') {
37 offset = vertical;
38 } else if (vertical === 'center') {
39 offset = rect.height / 2;
40 } else if (vertical === 'bottom') {
41 offset = rect.height;
42 }
43 return offset;
44}
45function getOffsetLeft(rect, horizontal) {
46 let offset = 0;
47 if (typeof horizontal === 'number') {
48 offset = horizontal;
49 } else if (horizontal === 'center') {
50 offset = rect.width / 2;
51 } else if (horizontal === 'right') {
52 offset = rect.width;
53 }
54 return offset;
55}
56function getTransformOriginValue(transformOrigin) {
57 return [transformOrigin.horizontal, transformOrigin.vertical].map(n => typeof n === 'number' ? `${n}px` : n).join(' ');
58}
59function resolveAnchorEl(anchorEl) {
60 return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
61}
62const useUtilityClasses = ownerState => {
63 const {
64 classes
65 } = ownerState;
66 const slots = {
67 root: ['root'],
68 paper: ['paper']
69 };
70 return (0, _composeClasses.default)(slots, _popoverClasses.getPopoverUtilityClass, classes);
71};
72const PopoverRoot = exports.PopoverRoot = (0, _zeroStyled.styled)(_Modal.default, {
73 name: 'MuiPopover',
74 slot: 'Root',
75 overridesResolver: (props, styles) => styles.root
76})({});
77const PopoverPaper = exports.PopoverPaper = (0, _zeroStyled.styled)(_Paper.default, {
78 name: 'MuiPopover',
79 slot: 'Paper',
80 overridesResolver: (props, styles) => styles.paper
81})({
82 position: 'absolute',
83 overflowY: 'auto',
84 overflowX: 'hidden',
85 // So we see the popover when it's empty.
86 // It's most likely on issue on userland.
87 minWidth: 16,
88 minHeight: 16,
89 maxWidth: 'calc(100% - 32px)',
90 maxHeight: 'calc(100% - 32px)',
91 // We disable the focus ring for mouse, touch and keyboard users.
92 outline: 0
93});
94const Popover = /*#__PURE__*/React.forwardRef(function Popover(inProps, ref) {
95 const props = (0, _DefaultPropsProvider.useDefaultProps)({
96 props: inProps,
97 name: 'MuiPopover'
98 });
99 const {
100 action,
101 anchorEl,
102 anchorOrigin = {
103 vertical: 'top',
104 horizontal: 'left'
105 },
106 anchorPosition,
107 anchorReference = 'anchorEl',
108 children,
109 className,
110 container: containerProp,
111 elevation = 8,
112 marginThreshold = 16,
113 open,
114 PaperProps: PaperPropsProp = {},
115 slots = {},
116 slotProps = {},
117 transformOrigin = {
118 vertical: 'top',
119 horizontal: 'left'
120 },
121 TransitionComponent = _Grow.default,
122 transitionDuration: transitionDurationProp = 'auto',
123 TransitionProps: {
124 onEntering,
125 ...TransitionProps
126 } = {},
127 disableScrollLock = false,
128 ...other
129 } = props;
130 const externalPaperSlotProps = slotProps?.paper ?? PaperPropsProp;
131 const paperRef = React.useRef();
132 const ownerState = {
133 ...props,
134 anchorOrigin,
135 anchorReference,
136 elevation,
137 marginThreshold,
138 externalPaperSlotProps,
139 transformOrigin,
140 TransitionComponent,
141 transitionDuration: transitionDurationProp,
142 TransitionProps
143 };
144 const classes = useUtilityClasses(ownerState);
145
146 // Returns the top/left offset of the position
147 // to attach to on the anchor element (or body if none is provided)
148 const getAnchorOffset = React.useCallback(() => {
149 if (anchorReference === 'anchorPosition') {
150 if (process.env.NODE_ENV !== 'production') {
151 if (!anchorPosition) {
152 console.error('MUI: You need to provide a `anchorPosition` prop when using ' + '<Popover anchorReference="anchorPosition" />.');
153 }
154 }
155 return anchorPosition;
156 }
157 const resolvedAnchorEl = resolveAnchorEl(anchorEl);
158
159 // If an anchor element wasn't provided, just use the parent body element of this Popover
160 const anchorElement = resolvedAnchorEl && resolvedAnchorEl.nodeType === 1 ? resolvedAnchorEl : (0, _ownerDocument.default)(paperRef.current).body;
161 const anchorRect = anchorElement.getBoundingClientRect();
162 if (process.env.NODE_ENV !== 'production') {
163 const box = anchorElement.getBoundingClientRect();
164 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
165 console.warn(['MUI: The `anchorEl` prop provided to the component is invalid.', 'The anchor element should be part of the document layout.', "Make sure the element is present in the document or that it's not display none."].join('\n'));
166 }
167 }
168 return {
169 top: anchorRect.top + getOffsetTop(anchorRect, anchorOrigin.vertical),
170 left: anchorRect.left + getOffsetLeft(anchorRect, anchorOrigin.horizontal)
171 };
172 }, [anchorEl, anchorOrigin.horizontal, anchorOrigin.vertical, anchorPosition, anchorReference]);
173
174 // Returns the base transform origin using the element
175 const getTransformOrigin = React.useCallback(elemRect => {
176 return {
177 vertical: getOffsetTop(elemRect, transformOrigin.vertical),
178 horizontal: getOffsetLeft(elemRect, transformOrigin.horizontal)
179 };
180 }, [transformOrigin.horizontal, transformOrigin.vertical]);
181 const getPositioningStyle = React.useCallback(element => {
182 const elemRect = {
183 width: element.offsetWidth,
184 height: element.offsetHeight
185 };
186
187 // Get the transform origin point on the element itself
188 const elemTransformOrigin = getTransformOrigin(elemRect);
189 if (anchorReference === 'none') {
190 return {
191 top: null,
192 left: null,
193 transformOrigin: getTransformOriginValue(elemTransformOrigin)
194 };
195 }
196
197 // Get the offset of the anchoring element
198 const anchorOffset = getAnchorOffset();
199
200 // Calculate element positioning
201 let top = anchorOffset.top - elemTransformOrigin.vertical;
202 let left = anchorOffset.left - elemTransformOrigin.horizontal;
203 const bottom = top + elemRect.height;
204 const right = left + elemRect.width;
205
206 // Use the parent window of the anchorEl if provided
207 const containerWindow = (0, _ownerWindow.default)(resolveAnchorEl(anchorEl));
208
209 // Window thresholds taking required margin into account
210 const heightThreshold = containerWindow.innerHeight - marginThreshold;
211 const widthThreshold = containerWindow.innerWidth - marginThreshold;
212
213 // Check if the vertical axis needs shifting
214 if (marginThreshold !== null && top < marginThreshold) {
215 const diff = top - marginThreshold;
216 top -= diff;
217 elemTransformOrigin.vertical += diff;
218 } else if (marginThreshold !== null && bottom > heightThreshold) {
219 const diff = bottom - heightThreshold;
220 top -= diff;
221 elemTransformOrigin.vertical += diff;
222 }
223 if (process.env.NODE_ENV !== 'production') {
224 if (elemRect.height > heightThreshold && elemRect.height && heightThreshold) {
225 console.error(['MUI: The popover component is too tall.', `Some part of it can not be seen on the screen (${elemRect.height - heightThreshold}px).`, 'Please consider adding a `max-height` to improve the user-experience.'].join('\n'));
226 }
227 }
228
229 // Check if the horizontal axis needs shifting
230 if (marginThreshold !== null && left < marginThreshold) {
231 const diff = left - marginThreshold;
232 left -= diff;
233 elemTransformOrigin.horizontal += diff;
234 } else if (right > widthThreshold) {
235 const diff = right - widthThreshold;
236 left -= diff;
237 elemTransformOrigin.horizontal += diff;
238 }
239 return {
240 top: `${Math.round(top)}px`,
241 left: `${Math.round(left)}px`,
242 transformOrigin: getTransformOriginValue(elemTransformOrigin)
243 };
244 }, [anchorEl, anchorReference, getAnchorOffset, getTransformOrigin, marginThreshold]);
245 const [isPositioned, setIsPositioned] = React.useState(open);
246 const setPositioningStyles = React.useCallback(() => {
247 const element = paperRef.current;
248 if (!element) {
249 return;
250 }
251 const positioning = getPositioningStyle(element);
252 if (positioning.top !== null) {
253 element.style.setProperty('top', positioning.top);
254 }
255 if (positioning.left !== null) {
256 element.style.left = positioning.left;
257 }
258 element.style.transformOrigin = positioning.transformOrigin;
259 setIsPositioned(true);
260 }, [getPositioningStyle]);
261 React.useEffect(() => {
262 if (disableScrollLock) {
263 window.addEventListener('scroll', setPositioningStyles);
264 }
265 return () => window.removeEventListener('scroll', setPositioningStyles);
266 }, [anchorEl, disableScrollLock, setPositioningStyles]);
267 const handleEntering = (element, isAppearing) => {
268 if (onEntering) {
269 onEntering(element, isAppearing);
270 }
271 setPositioningStyles();
272 };
273 const handleExited = () => {
274 setIsPositioned(false);
275 };
276 React.useEffect(() => {
277 if (open) {
278 setPositioningStyles();
279 }
280 });
281 React.useImperativeHandle(action, () => open ? {
282 updatePosition: () => {
283 setPositioningStyles();
284 }
285 } : null, [open, setPositioningStyles]);
286 React.useEffect(() => {
287 if (!open) {
288 return undefined;
289 }
290 const handleResize = (0, _debounce.default)(() => {
291 setPositioningStyles();
292 });
293 const containerWindow = (0, _ownerWindow.default)(anchorEl);
294 containerWindow.addEventListener('resize', handleResize);
295 return () => {
296 handleResize.clear();
297 containerWindow.removeEventListener('resize', handleResize);
298 };
299 }, [anchorEl, open, setPositioningStyles]);
300 let transitionDuration = transitionDurationProp;
301 if (transitionDurationProp === 'auto' && !TransitionComponent.muiSupportAuto) {
302 transitionDuration = undefined;
303 }
304
305 // If the container prop is provided, use that
306 // If the anchorEl prop is provided, use its parent body element as the container
307 // If neither are provided let the Modal take care of choosing the container
308 const container = containerProp || (anchorEl ? (0, _ownerDocument.default)(resolveAnchorEl(anchorEl)).body : undefined);
309 const externalForwardedProps = {
310 slots,
311 slotProps: {
312 ...slotProps,
313 paper: externalPaperSlotProps
314 }
315 };
316 const [PaperSlot, paperProps] = (0, _useSlot.default)('paper', {
317 elementType: PopoverPaper,
318 externalForwardedProps,
319 additionalProps: {
320 elevation,
321 className: (0, _clsx.default)(classes.paper, externalPaperSlotProps?.className),
322 style: isPositioned ? externalPaperSlotProps.style : {
323 ...externalPaperSlotProps.style,
324 opacity: 0
325 }
326 },
327 ownerState
328 });
329 const [RootSlot, {
330 slotProps: rootSlotPropsProp,
331 ...rootProps
332 }] = (0, _useSlot.default)('root', {
333 elementType: PopoverRoot,
334 externalForwardedProps,
335 additionalProps: {
336 slotProps: {
337 backdrop: {
338 invisible: true
339 }
340 },
341 container,
342 open
343 },
344 ownerState,
345 className: (0, _clsx.default)(classes.root, className)
346 });
347 const handlePaperRef = (0, _useForkRef.default)(paperRef, paperProps.ref);
348 return /*#__PURE__*/(0, _jsxRuntime.jsx)(RootSlot, {
349 ...rootProps,
350 ...(!(0, _isHostComponent.default)(RootSlot) && {
351 slotProps: rootSlotPropsProp,
352 disableScrollLock
353 }),
354 ...other,
355 ref: ref,
356 children: /*#__PURE__*/(0, _jsxRuntime.jsx)(TransitionComponent, {
357 appear: true,
358 in: open,
359 onEntering: handleEntering,
360 onExited: handleExited,
361 timeout: transitionDuration,
362 ...TransitionProps,
363 children: /*#__PURE__*/(0, _jsxRuntime.jsx)(PaperSlot, {
364 ...paperProps,
365 ref: handlePaperRef,
366 children: children
367 })
368 })
369 });
370});
371process.env.NODE_ENV !== "production" ? Popover.propTypes /* remove-proptypes */ = {
372 // ┌────────────────────────────── Warning ──────────────────────────────┐
373 // │ These PropTypes are generated from the TypeScript type definitions. │
374 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
375 // └─────────────────────────────────────────────────────────────────────┘
376 /**
377 * A ref for imperative actions.
378 * It currently only supports updatePosition() action.
379 */
380 action: _refType.default,
381 /**
382 * An HTML element, [PopoverVirtualElement](https://mui.com/material-ui/react-popover/#virtual-element),
383 * or a function that returns either.
384 * It's used to set the position of the popover.
385 */
386 anchorEl: (0, _chainPropTypes.default)(_propTypes.default.oneOfType([_HTMLElementType.default, _propTypes.default.func]), props => {
387 if (props.open && (!props.anchorReference || props.anchorReference === 'anchorEl')) {
388 const resolvedAnchorEl = resolveAnchorEl(props.anchorEl);
389 if (resolvedAnchorEl && resolvedAnchorEl.nodeType === 1) {
390 const box = resolvedAnchorEl.getBoundingClientRect();
391 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
392 return new Error(['MUI: The `anchorEl` prop provided to the component is invalid.', 'The anchor element should be part of the document layout.', "Make sure the element is present in the document or that it's not display none."].join('\n'));
393 }
394 } else {
395 return new Error(['MUI: The `anchorEl` prop provided to the component is invalid.', `It should be an Element or PopoverVirtualElement instance but it's \`${resolvedAnchorEl}\` instead.`].join('\n'));
396 }
397 }
398 return null;
399 }),
400 /**
401 * This is the point on the anchor where the popover's
402 * `anchorEl` will attach to. This is not used when the
403 * anchorReference is 'anchorPosition'.
404 *
405 * Options:
406 * vertical: [top, center, bottom];
407 * horizontal: [left, center, right].
408 * @default {
409 * vertical: 'top',
410 * horizontal: 'left',
411 * }
412 */
413 anchorOrigin: _propTypes.default.shape({
414 horizontal: _propTypes.default.oneOfType([_propTypes.default.oneOf(['center', 'left', 'right']), _propTypes.default.number]).isRequired,
415 vertical: _propTypes.default.oneOfType([_propTypes.default.oneOf(['bottom', 'center', 'top']), _propTypes.default.number]).isRequired
416 }),
417 /**
418 * This is the position that may be used to set the position of the popover.
419 * The coordinates are relative to the application's client area.
420 */
421 anchorPosition: _propTypes.default.shape({
422 left: _propTypes.default.number.isRequired,
423 top: _propTypes.default.number.isRequired
424 }),
425 /**
426 * This determines which anchor prop to refer to when setting
427 * the position of the popover.
428 * @default 'anchorEl'
429 */
430 anchorReference: _propTypes.default.oneOf(['anchorEl', 'anchorPosition', 'none']),
431 /**
432 * A backdrop component. This prop enables custom backdrop rendering.
433 * @deprecated Use `slotProps.root.slots.backdrop` instead. While this prop currently works, it will be removed in the next major version.
434 * Use the `slotProps.root.slots.backdrop` prop to make your application ready for the next version of Material UI.
435 * @default styled(Backdrop, {
436 * name: 'MuiModal',
437 * slot: 'Backdrop',
438 * overridesResolver: (props, styles) => {
439 * return styles.backdrop;
440 * },
441 * })({
442 * zIndex: -1,
443 * })
444 */
445 BackdropComponent: _propTypes.default.elementType,
446 /**
447 * Props applied to the [`Backdrop`](/material-ui/api/backdrop/) element.
448 * @deprecated Use `slotProps.root.slotProps.backdrop` instead.
449 */
450 BackdropProps: _propTypes.default.object,
451 /**
452 * The content of the component.
453 */
454 children: _propTypes.default.node,
455 /**
456 * Override or extend the styles applied to the component.
457 */
458 classes: _propTypes.default.object,
459 /**
460 * @ignore
461 */
462 className: _propTypes.default.string,
463 /**
464 * An HTML element, component instance, or function that returns either.
465 * The `container` will passed to the Modal component.
466 *
467 * By default, it uses the body of the anchorEl's top-level document object,
468 * so it's simply `document.body` most of the time.
469 */
470 container: _propTypes.default /* @typescript-to-proptypes-ignore */.oneOfType([_HTMLElementType.default, _propTypes.default.func]),
471 /**
472 * Disable the scroll lock behavior.
473 * @default false
474 */
475 disableScrollLock: _propTypes.default.bool,
476 /**
477 * The elevation of the popover.
478 * @default 8
479 */
480 elevation: _integerPropType.default,
481 /**
482 * Specifies how close to the edge of the window the popover can appear.
483 * If null, the popover will not be constrained by the window.
484 * @default 16
485 */
486 marginThreshold: _propTypes.default.number,
487 /**
488 * Callback fired when the component requests to be closed.
489 * The `reason` parameter can optionally be used to control the response to `onClose`.
490 */
491 onClose: _propTypes.default.func,
492 /**
493 * If `true`, the component is shown.
494 */
495 open: _propTypes.default.bool.isRequired,
496 /**
497 * Props applied to the [`Paper`](https://mui.com/material-ui/api/paper/) element.
498 *
499 * This prop is an alias for `slotProps.paper` and will be overriden by it if both are used.
500 * @deprecated Use `slotProps.paper` instead.
501 *
502 * @default {}
503 */
504 PaperProps: _propTypes.default /* @typescript-to-proptypes-ignore */.shape({
505 component: _elementTypeAcceptingRef.default
506 }),
507 /**
508 * The props used for each slot inside.
509 * @default {}
510 */
511 slotProps: _propTypes.default.shape({
512 paper: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object]),
513 root: _propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object])
514 }),
515 /**
516 * The components used for each slot inside.
517 * @default {}
518 */
519 slots: _propTypes.default.shape({
520 paper: _propTypes.default.elementType,
521 root: _propTypes.default.elementType
522 }),
523 /**
524 * The system prop that allows defining system overrides as well as additional CSS styles.
525 */
526 sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]),
527 /**
528 * This is the point on the popover which
529 * will attach to the anchor's origin.
530 *
531 * Options:
532 * vertical: [top, center, bottom, x(px)];
533 * horizontal: [left, center, right, x(px)].
534 * @default {
535 * vertical: 'top',
536 * horizontal: 'left',
537 * }
538 */
539 transformOrigin: _propTypes.default.shape({
540 horizontal: _propTypes.default.oneOfType([_propTypes.default.oneOf(['center', 'left', 'right']), _propTypes.default.number]).isRequired,
541 vertical: _propTypes.default.oneOfType([_propTypes.default.oneOf(['bottom', 'center', 'top']), _propTypes.default.number]).isRequired
542 }),
543 /**
544 * The component used for the transition.
545 * [Follow this guide](https://mui.com/material-ui/transitions/#transitioncomponent-prop) to learn more about the requirements for this component.
546 * @default Grow
547 */
548 TransitionComponent: _propTypes.default.elementType,
549 /**
550 * Set to 'auto' to automatically calculate transition time based on height.
551 * @default 'auto'
552 */
553 transitionDuration: _propTypes.default.oneOfType([_propTypes.default.oneOf(['auto']), _propTypes.default.number, _propTypes.default.shape({
554 appear: _propTypes.default.number,
555 enter: _propTypes.default.number,
556 exit: _propTypes.default.number
557 })]),
558 /**
559 * Props applied to the transition element.
560 * By default, the element is based on this [`Transition`](https://reactcommunity.org/react-transition-group/transition/) component.
561 * @default {}
562 */
563 TransitionProps: _propTypes.default.object
564} : void 0;
565var _default = exports.default = Popover;
\No newline at end of file