UNPKG

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