UNPKG

20.7 kBJavaScriptView Raw
1"use strict";
2
3var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
5Object.defineProperty(exports, "__esModule", {
6 value: true
7});
8exports.getOffsetTop = getOffsetTop;
9exports.getOffsetLeft = getOffsetLeft;
10exports.default = exports.styles = void 0;
11
12var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
13
14var _objectWithoutProperties2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutProperties"));
15
16var _react = _interopRequireDefault(require("react"));
17
18var _propTypes = _interopRequireDefault(require("prop-types"));
19
20var _reactDom = _interopRequireDefault(require("react-dom"));
21
22var _debounce = _interopRequireDefault(require("../utils/debounce"));
23
24var _clsx = _interopRequireDefault(require("clsx"));
25
26var _utils = require("@material-ui/utils");
27
28var _ownerDocument = _interopRequireDefault(require("../utils/ownerDocument"));
29
30var _ownerWindow = _interopRequireDefault(require("../utils/ownerWindow"));
31
32var _createChainedFunction = _interopRequireDefault(require("../utils/createChainedFunction"));
33
34var _withStyles = _interopRequireDefault(require("../styles/withStyles"));
35
36var _Modal = _interopRequireDefault(require("../Modal"));
37
38var _Grow = _interopRequireDefault(require("../Grow"));
39
40var _Paper = _interopRequireDefault(require("../Paper"));
41
42function getOffsetTop(rect, vertical) {
43 var offset = 0;
44
45 if (typeof vertical === 'number') {
46 offset = vertical;
47 } else if (vertical === 'center') {
48 offset = rect.height / 2;
49 } else if (vertical === 'bottom') {
50 offset = rect.height;
51 }
52
53 return offset;
54}
55
56function getOffsetLeft(rect, horizontal) {
57 var offset = 0;
58
59 if (typeof horizontal === 'number') {
60 offset = horizontal;
61 } else if (horizontal === 'center') {
62 offset = rect.width / 2;
63 } else if (horizontal === 'right') {
64 offset = rect.width;
65 }
66
67 return offset;
68}
69
70function getTransformOriginValue(transformOrigin) {
71 return [transformOrigin.horizontal, transformOrigin.vertical].map(function (n) {
72 return typeof n === 'number' ? "".concat(n, "px") : n;
73 }).join(' ');
74} // Sum the scrollTop between two elements.
75
76
77function getScrollParent(parent, child) {
78 var element = child;
79 var scrollTop = 0;
80
81 while (element && element !== parent) {
82 element = element.parentElement;
83 scrollTop += element.scrollTop;
84 }
85
86 return scrollTop;
87}
88
89function getAnchorEl(anchorEl) {
90 return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
91}
92
93var styles = {
94 /* Styles applied to the root element */
95 root: {},
96
97 /* Styles applied to the `Paper` component. */
98 paper: {
99 position: 'absolute',
100 overflowY: 'auto',
101 overflowX: 'hidden',
102 // So we see the popover when it's empty.
103 // It's most likely on issue on userland.
104 minWidth: 16,
105 minHeight: 16,
106 maxWidth: 'calc(100% - 32px)',
107 maxHeight: 'calc(100% - 32px)',
108 // We disable the focus ring for mouse, touch and keyboard users.
109 outline: 0
110 }
111};
112exports.styles = styles;
113
114var Popover = _react.default.forwardRef(function Popover(props, ref) {
115 var action = props.action,
116 anchorEl = props.anchorEl,
117 _props$anchorOrigin = props.anchorOrigin,
118 anchorOrigin = _props$anchorOrigin === void 0 ? {
119 vertical: 'top',
120 horizontal: 'left'
121 } : _props$anchorOrigin,
122 anchorPosition = props.anchorPosition,
123 _props$anchorReferenc = props.anchorReference,
124 anchorReference = _props$anchorReferenc === void 0 ? 'anchorEl' : _props$anchorReferenc,
125 children = props.children,
126 classes = props.classes,
127 className = props.className,
128 containerProp = props.container,
129 _props$elevation = props.elevation,
130 elevation = _props$elevation === void 0 ? 8 : _props$elevation,
131 getContentAnchorEl = props.getContentAnchorEl,
132 _props$marginThreshol = props.marginThreshold,
133 marginThreshold = _props$marginThreshol === void 0 ? 16 : _props$marginThreshol,
134 onEnter = props.onEnter,
135 onEntered = props.onEntered,
136 onEntering = props.onEntering,
137 onExit = props.onExit,
138 onExited = props.onExited,
139 onExiting = props.onExiting,
140 open = props.open,
141 _props$PaperProps = props.PaperProps,
142 PaperProps = _props$PaperProps === void 0 ? {} : _props$PaperProps,
143 _props$transformOrigi = props.transformOrigin,
144 transformOrigin = _props$transformOrigi === void 0 ? {
145 vertical: 'top',
146 horizontal: 'left'
147 } : _props$transformOrigi,
148 _props$TransitionComp = props.TransitionComponent,
149 TransitionComponent = _props$TransitionComp === void 0 ? _Grow.default : _props$TransitionComp,
150 _props$transitionDura = props.transitionDuration,
151 transitionDurationProp = _props$transitionDura === void 0 ? 'auto' : _props$transitionDura,
152 _props$TransitionProp = props.TransitionProps,
153 TransitionProps = _props$TransitionProp === void 0 ? {} : _props$TransitionProp,
154 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"]);
155
156 var paperRef = _react.default.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
160 var getAnchorOffset = _react.default.useCallback(function (contentAnchorOffset) {
161 if (anchorReference === 'anchorPosition') {
162 if (process.env.NODE_ENV !== 'production') {
163 if (!anchorPosition) {
164 console.error('Material-UI: you need to provide a `anchorPosition` prop when using ' + '<Popover anchorReference="anchorPosition" />.');
165 }
166 }
167
168 return anchorPosition;
169 }
170
171 var resolvedAnchorEl = getAnchorEl(anchorEl);
172 var containerWindow = (0, _ownerWindow.default)(resolvedAnchorEl); // If an anchor element wasn't provided, just use the parent body element of this Popover
173
174 var anchorElement = resolvedAnchorEl instanceof containerWindow.Element ? resolvedAnchorEl : (0, _ownerDocument.default)(paperRef.current).body;
175 var anchorRect = anchorElement.getBoundingClientRect();
176
177 if (process.env.NODE_ENV !== 'production') {
178 var box = anchorElement.getBoundingClientRect();
179
180 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
181 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'));
182 }
183 }
184
185 var anchorVertical = contentAnchorOffset === 0 ? anchorOrigin.vertical : 'center';
186 return {
187 top: anchorRect.top + getOffsetTop(anchorRect, anchorVertical),
188 left: anchorRect.left + getOffsetLeft(anchorRect, anchorOrigin.horizontal)
189 };
190 }, [anchorEl, anchorOrigin.horizontal, anchorOrigin.vertical, anchorPosition, anchorReference]); // Returns the vertical offset of inner content to anchor the transform on if provided
191
192
193 var getContentAnchorOffset = _react.default.useCallback(function (element) {
194 var contentAnchorOffset = 0;
195
196 if (getContentAnchorEl && anchorReference === 'anchorEl') {
197 var contentAnchorEl = getContentAnchorEl(element);
198
199 if (contentAnchorEl && element.contains(contentAnchorEl)) {
200 var scrollTop = getScrollParent(element, contentAnchorEl);
201 contentAnchorOffset = contentAnchorEl.offsetTop + contentAnchorEl.clientHeight / 2 - scrollTop || 0;
202 } // != the default value
203
204
205 if (process.env.NODE_ENV !== 'production') {
206 if (anchorOrigin.vertical !== 'top') {
207 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'));
208 }
209 }
210 }
211
212 return contentAnchorOffset;
213 }, [anchorOrigin.vertical, anchorReference, getContentAnchorEl]); // Return the base transform origin using the element
214 // and taking the content anchor offset into account if in use
215
216
217 var getTransformOrigin = _react.default.useCallback(function (elemRect) {
218 var contentAnchorOffset = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
219 return {
220 vertical: getOffsetTop(elemRect, transformOrigin.vertical) + contentAnchorOffset,
221 horizontal: getOffsetLeft(elemRect, transformOrigin.horizontal)
222 };
223 }, [transformOrigin.horizontal, transformOrigin.vertical]);
224
225 var getPositioningStyle = _react.default.useCallback(function (element) {
226 // Check if the parent has requested anchoring on an inner content node
227 var contentAnchorOffset = getContentAnchorOffset(element);
228 var elemRect = {
229 width: element.offsetWidth,
230 height: element.offsetHeight
231 }; // Get the transform origin point on the element itself
232
233 var elemTransformOrigin = getTransformOrigin(elemRect, contentAnchorOffset);
234
235 if (anchorReference === 'none') {
236 return {
237 top: null,
238 left: null,
239 transformOrigin: getTransformOriginValue(elemTransformOrigin)
240 };
241 } // Get the offset of of the anchoring element
242
243
244 var anchorOffset = getAnchorOffset(contentAnchorOffset); // Calculate element positioning
245
246 var top = anchorOffset.top - elemTransformOrigin.vertical;
247 var left = anchorOffset.left - elemTransformOrigin.horizontal;
248 var bottom = top + elemRect.height;
249 var right = left + elemRect.width; // Use the parent window of the anchorEl if provided
250
251 var containerWindow = (0, _ownerWindow.default)(getAnchorEl(anchorEl)); // Window thresholds taking required margin into account
252
253 var heightThreshold = containerWindow.innerHeight - marginThreshold;
254 var widthThreshold = containerWindow.innerWidth - marginThreshold; // Check if the vertical axis needs shifting
255
256 if (top < marginThreshold) {
257 var diff = top - marginThreshold;
258 top -= diff;
259 elemTransformOrigin.vertical += diff;
260 } else if (bottom > heightThreshold) {
261 var _diff = bottom - heightThreshold;
262
263 top -= _diff;
264 elemTransformOrigin.vertical += _diff;
265 }
266
267 if (process.env.NODE_ENV !== 'production') {
268 if (elemRect.height > heightThreshold && elemRect.height && heightThreshold) {
269 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'));
270 }
271 } // Check if the horizontal axis needs shifting
272
273
274 if (left < marginThreshold) {
275 var _diff2 = left - marginThreshold;
276
277 left -= _diff2;
278 elemTransformOrigin.horizontal += _diff2;
279 } else if (right > widthThreshold) {
280 var _diff3 = right - widthThreshold;
281
282 left -= _diff3;
283 elemTransformOrigin.horizontal += _diff3;
284 }
285
286 return {
287 top: "".concat(top, "px"),
288 left: "".concat(left, "px"),
289 transformOrigin: getTransformOriginValue(elemTransformOrigin)
290 };
291 }, [anchorEl, anchorReference, getAnchorOffset, getContentAnchorOffset, getTransformOrigin, marginThreshold]);
292
293 var setPositioningStyles = _react.default.useCallback(function (element) {
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(element);
313 };
314
315 var handlePaperRef = _react.default.useCallback(function (instance) {
316 // #StrictMode ready
317 paperRef.current = _reactDom.default.findDOMNode(instance);
318 }, []);
319
320 var updatePosition = _react.default.useMemo(function () {
321 if (!open) {
322 return undefined;
323 }
324
325 return (0, _debounce.default)(function () {
326 setPositioningStyles(paperRef.current);
327 });
328 }, [open, setPositioningStyles]);
329
330 _react.default.useImperativeHandle(action, function () {
331 return open ? {
332 updatePosition: updatePosition
333 } : null;
334 }, [open, updatePosition]);
335
336 _react.default.useEffect(function () {
337 if (!updatePosition) {
338 return undefined;
339 }
340
341 window.addEventListener('resize', updatePosition);
342 return function () {
343 window.removeEventListener('resize', updatePosition);
344 updatePosition.clear();
345 };
346 }, [updatePosition]);
347
348 var transitionDuration = transitionDurationProp;
349
350 if (transitionDurationProp === 'auto' && !TransitionComponent.muiSupportAuto) {
351 transitionDuration = undefined;
352 } // If the container prop is provided, use that
353 // If the anchorEl prop is provided, use its parent body element as the container
354 // If neither are provided let the Modal take care of choosing the container
355
356
357 var container = containerProp || (anchorEl ? (0, _ownerDocument.default)(getAnchorEl(anchorEl)).body : undefined);
358 return _react.default.createElement(_Modal.default, (0, _extends2.default)({
359 container: container,
360 open: open,
361 ref: ref,
362 BackdropProps: {
363 invisible: true
364 },
365 className: (0, _clsx.default)(classes.root, className)
366 }, other), _react.default.createElement(TransitionComponent, (0, _extends2.default)({
367 appear: true,
368 in: open,
369 onEnter: onEnter,
370 onEntered: onEntered,
371 onExit: onExit,
372 onExited: onExited,
373 onExiting: onExiting,
374 timeout: transitionDuration
375 }, TransitionProps, {
376 onEntering: (0, _createChainedFunction.default)(handleEntering, TransitionProps.onEntering)
377 }), _react.default.createElement(_Paper.default, (0, _extends2.default)({
378 elevation: elevation,
379 ref: handlePaperRef
380 }, PaperProps, {
381 className: (0, _clsx.default)(classes.paper, PaperProps.className)
382 }), children)));
383});
384
385process.env.NODE_ENV !== "production" ? Popover.propTypes = {
386 /**
387 * A ref for imperative actions.
388 * It currently only supports updatePosition() action.
389 */
390 action: _utils.refType,
391
392 /**
393 * This is the DOM element, or a function that returns the DOM element,
394 * that may be used to set the position of the popover.
395 */
396 anchorEl: (0, _utils.chainPropTypes)(_propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.func]), function (props) {
397 if (props.open && (!props.anchorReference || props.anchorReference === 'anchorEl')) {
398 var resolvedAnchorEl = getAnchorEl(props.anchorEl);
399 var containerWindow = (0, _ownerWindow.default)(resolvedAnchorEl);
400
401 if (resolvedAnchorEl instanceof containerWindow.Element) {
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.number, _propTypes.default.oneOf(['left', 'center', 'right'])]).isRequired,
426 vertical: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.oneOf(['top', 'center', 'bottom'])]).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.isRequired,
456
457 /**
458 * @ignore
459 */
460 className: _propTypes.default.string,
461
462 /**
463 * A node, component instance, or function that returns either.
464 * The `container` will passed to the Modal component.
465 * By default, it uses the body of the anchorEl's top-level document object,
466 * so it's simply `document.body` most of the time.
467 */
468 container: _propTypes.default.oneOfType([_propTypes.default.object, _propTypes.default.func]),
469
470 /**
471 * The elevation of the popover.
472 */
473 elevation: _propTypes.default.number,
474
475 /**
476 * This function is called in order to retrieve the content anchor element.
477 * It's the opposite of the `anchorEl` prop.
478 * The content anchor element should be an element inside the popover.
479 * It's used to correctly scroll and set the position of the popover.
480 * The positioning strategy tries to make the content anchor element just above the
481 * anchor element.
482 */
483 getContentAnchorEl: _propTypes.default.func,
484
485 /**
486 * Specifies how close to the edge of the window the popover can appear.
487 */
488 marginThreshold: _propTypes.default.number,
489
490 /**
491 * Callback fired when the component requests to be closed.
492 *
493 * @param {object} event The event source of the callback.
494 * @param {string} reason Can be:`"escapeKeyDown"`, `"backdropClick"`
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.shape({
537 component: _utils.elementTypeAcceptingRef
538 }),
539
540 /**
541 * This is the point on the popover which
542 * will attach to the anchor's origin.
543 *
544 * Options:
545 * vertical: [top, center, bottom, x(px)];
546 * horizontal: [left, center, right, x(px)].
547 */
548 transformOrigin: _propTypes.default.shape({
549 horizontal: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.oneOf(['left', 'center', 'right'])]).isRequired,
550 vertical: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.oneOf(['top', 'center', 'bottom'])]).isRequired
551 }),
552
553 /**
554 * The component used for the transition.
555 */
556 TransitionComponent: _propTypes.default.elementType,
557
558 /**
559 * Set to 'auto' to automatically calculate transition time based on height.
560 */
561 transitionDuration: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.shape({
562 enter: _propTypes.default.number,
563 exit: _propTypes.default.number
564 }), _propTypes.default.oneOf(['auto'])]),
565
566 /**
567 * Props applied to the `Transition` element.
568 */
569 TransitionProps: _propTypes.default.object
570} : void 0;
571
572var _default = (0, _withStyles.default)(styles, {
573 name: 'MuiPopover'
574})(Popover);
575
576exports.default = _default;
\No newline at end of file