UNPKG

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