1 | import _extends from "@babel/runtime/helpers/esm/extends";
|
2 | import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
3 | import * as React from 'react';
|
4 | import PropTypes from 'prop-types';
|
5 | import * as ReactDOM from 'react-dom';
|
6 | import { chainPropTypes, elementTypeAcceptingRef, refType, HTMLElementType } from '@material-ui/utils';
|
7 | import debounce from '../utils/debounce';
|
8 | import clsx from 'clsx';
|
9 | import ownerDocument from '../utils/ownerDocument';
|
10 | import ownerWindow from '../utils/ownerWindow';
|
11 | import createChainedFunction from '../utils/createChainedFunction';
|
12 | import deprecatedPropType from '../utils/deprecatedPropType';
|
13 | import withStyles from '../styles/withStyles';
|
14 | import Modal from '../Modal';
|
15 | import Grow from '../Grow';
|
16 | import Paper from '../Paper';
|
17 | export function getOffsetTop(rect, vertical) {
|
18 | let offset = 0;
|
19 |
|
20 | if (typeof vertical === 'number') {
|
21 | offset = vertical;
|
22 | } else if (vertical === 'center') {
|
23 | offset = rect.height / 2;
|
24 | } else if (vertical === 'bottom') {
|
25 | offset = rect.height;
|
26 | }
|
27 |
|
28 | return offset;
|
29 | }
|
30 | export function getOffsetLeft(rect, horizontal) {
|
31 | let offset = 0;
|
32 |
|
33 | if (typeof horizontal === 'number') {
|
34 | offset = horizontal;
|
35 | } else if (horizontal === 'center') {
|
36 | offset = rect.width / 2;
|
37 | } else if (horizontal === 'right') {
|
38 | offset = rect.width;
|
39 | }
|
40 |
|
41 | return offset;
|
42 | }
|
43 |
|
44 | function getTransformOriginValue(transformOrigin) {
|
45 | return [transformOrigin.horizontal, transformOrigin.vertical].map(n => typeof n === 'number' ? `${n}px` : n).join(' ');
|
46 | }
|
47 |
|
48 |
|
49 | function getScrollParent(parent, child) {
|
50 | let element = child;
|
51 | let scrollTop = 0;
|
52 |
|
53 | while (element && element !== parent) {
|
54 | element = element.parentElement;
|
55 | scrollTop += element.scrollTop;
|
56 | }
|
57 |
|
58 | return scrollTop;
|
59 | }
|
60 |
|
61 | function getAnchorEl(anchorEl) {
|
62 | return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
|
63 | }
|
64 |
|
65 | export const styles = {
|
66 |
|
67 | root: {},
|
68 |
|
69 |
|
70 | paper: {
|
71 | position: 'absolute',
|
72 | overflowY: 'auto',
|
73 | overflowX: 'hidden',
|
74 |
|
75 |
|
76 | minWidth: 16,
|
77 | minHeight: 16,
|
78 | maxWidth: 'calc(100% - 32px)',
|
79 | maxHeight: 'calc(100% - 32px)',
|
80 |
|
81 | outline: 0
|
82 | }
|
83 | };
|
84 | const Popover = React.forwardRef(function Popover(props, ref) {
|
85 | const {
|
86 | action,
|
87 | anchorEl,
|
88 | anchorOrigin = {
|
89 | vertical: 'top',
|
90 | horizontal: 'left'
|
91 | },
|
92 | anchorPosition,
|
93 | anchorReference = 'anchorEl',
|
94 | children,
|
95 | classes,
|
96 | className,
|
97 | container: containerProp,
|
98 | elevation = 8,
|
99 | getContentAnchorEl,
|
100 | marginThreshold = 16,
|
101 | onEnter,
|
102 | onEntered,
|
103 | onEntering,
|
104 | onExit,
|
105 | onExited,
|
106 | onExiting,
|
107 | open,
|
108 | PaperProps = {},
|
109 | transformOrigin = {
|
110 | vertical: 'top',
|
111 | horizontal: 'left'
|
112 | },
|
113 | TransitionComponent = Grow,
|
114 | transitionDuration: transitionDurationProp = 'auto',
|
115 | TransitionProps = {}
|
116 | } = props,
|
117 | other = _objectWithoutPropertiesLoose(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"]);
|
118 |
|
119 | const paperRef = React.useRef();
|
120 |
|
121 |
|
122 | const getAnchorOffset = React.useCallback(contentAnchorOffset => {
|
123 | if (anchorReference === 'anchorPosition') {
|
124 | if (process.env.NODE_ENV !== 'production') {
|
125 | if (!anchorPosition) {
|
126 | console.error('Material-UI: You need to provide a `anchorPosition` prop when using ' + '<Popover anchorReference="anchorPosition" />.');
|
127 | }
|
128 | }
|
129 |
|
130 | return anchorPosition;
|
131 | }
|
132 |
|
133 | const resolvedAnchorEl = getAnchorEl(anchorEl);
|
134 |
|
135 | const anchorElement = resolvedAnchorEl && resolvedAnchorEl.nodeType === 1 ? resolvedAnchorEl : ownerDocument(paperRef.current).body;
|
136 | const anchorRect = anchorElement.getBoundingClientRect();
|
137 |
|
138 | if (process.env.NODE_ENV !== 'production') {
|
139 | const box = anchorElement.getBoundingClientRect();
|
140 |
|
141 | if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
|
142 | 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'));
|
143 | }
|
144 | }
|
145 |
|
146 | const anchorVertical = contentAnchorOffset === 0 ? anchorOrigin.vertical : 'center';
|
147 | return {
|
148 | top: anchorRect.top + getOffsetTop(anchorRect, anchorVertical),
|
149 | left: anchorRect.left + getOffsetLeft(anchorRect, anchorOrigin.horizontal)
|
150 | };
|
151 | }, [anchorEl, anchorOrigin.horizontal, anchorOrigin.vertical, anchorPosition, anchorReference]);
|
152 |
|
153 | const getContentAnchorOffset = React.useCallback(element => {
|
154 | let contentAnchorOffset = 0;
|
155 |
|
156 | if (getContentAnchorEl && anchorReference === 'anchorEl') {
|
157 | const contentAnchorEl = getContentAnchorEl(element);
|
158 |
|
159 | if (contentAnchorEl && element.contains(contentAnchorEl)) {
|
160 | const scrollTop = getScrollParent(element, contentAnchorEl);
|
161 | contentAnchorOffset = contentAnchorEl.offsetTop + contentAnchorEl.clientHeight / 2 - scrollTop || 0;
|
162 | }
|
163 |
|
164 |
|
165 | if (process.env.NODE_ENV !== 'production') {
|
166 | if (anchorOrigin.vertical !== 'top') {
|
167 | 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'));
|
168 | }
|
169 | }
|
170 | }
|
171 |
|
172 | return contentAnchorOffset;
|
173 | }, [anchorOrigin.vertical, anchorReference, getContentAnchorEl]);
|
174 |
|
175 |
|
176 | const getTransformOrigin = React.useCallback((elemRect, contentAnchorOffset = 0) => {
|
177 | return {
|
178 | vertical: getOffsetTop(elemRect, transformOrigin.vertical) + contentAnchorOffset,
|
179 | horizontal: getOffsetLeft(elemRect, transformOrigin.horizontal)
|
180 | };
|
181 | }, [transformOrigin.horizontal, transformOrigin.vertical]);
|
182 | const getPositioningStyle = React.useCallback(element => {
|
183 |
|
184 | const contentAnchorOffset = getContentAnchorOffset(element);
|
185 | const elemRect = {
|
186 | width: element.offsetWidth,
|
187 | height: element.offsetHeight
|
188 | };
|
189 |
|
190 | const elemTransformOrigin = getTransformOrigin(elemRect, contentAnchorOffset);
|
191 |
|
192 | if (anchorReference === 'none') {
|
193 | return {
|
194 | top: null,
|
195 | left: null,
|
196 | transformOrigin: getTransformOriginValue(elemTransformOrigin)
|
197 | };
|
198 | }
|
199 |
|
200 |
|
201 | const anchorOffset = getAnchorOffset(contentAnchorOffset);
|
202 |
|
203 | let top = anchorOffset.top - elemTransformOrigin.vertical;
|
204 | let left = anchorOffset.left - elemTransformOrigin.horizontal;
|
205 | const bottom = top + elemRect.height;
|
206 | const right = left + elemRect.width;
|
207 |
|
208 | const containerWindow = ownerWindow(getAnchorEl(anchorEl));
|
209 |
|
210 | const heightThreshold = containerWindow.innerHeight - marginThreshold;
|
211 | const widthThreshold = containerWindow.innerWidth - marginThreshold;
|
212 |
|
213 | if (top < marginThreshold) {
|
214 | const diff = top - marginThreshold;
|
215 | top -= diff;
|
216 | elemTransformOrigin.vertical += diff;
|
217 | } else if (bottom > heightThreshold) {
|
218 | const diff = bottom - heightThreshold;
|
219 | top -= diff;
|
220 | elemTransformOrigin.vertical += diff;
|
221 | }
|
222 |
|
223 | if (process.env.NODE_ENV !== 'production') {
|
224 | if (elemRect.height > heightThreshold && elemRect.height && heightThreshold) {
|
225 | console.error(['Material-UI: 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 |
|
230 | if (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 |
|
240 | return {
|
241 | top: `${Math.round(top)}px`,
|
242 | left: `${Math.round(left)}px`,
|
243 | transformOrigin: getTransformOriginValue(elemTransformOrigin)
|
244 | };
|
245 | }, [anchorEl, anchorReference, getAnchorOffset, getContentAnchorOffset, getTransformOrigin, marginThreshold]);
|
246 | const setPositioningStyles = React.useCallback(() => {
|
247 | const element = paperRef.current;
|
248 |
|
249 | if (!element) {
|
250 | return;
|
251 | }
|
252 |
|
253 | const positioning = getPositioningStyle(element);
|
254 |
|
255 | if (positioning.top !== null) {
|
256 | element.style.top = positioning.top;
|
257 | }
|
258 |
|
259 | if (positioning.left !== null) {
|
260 | element.style.left = positioning.left;
|
261 | }
|
262 |
|
263 | element.style.transformOrigin = positioning.transformOrigin;
|
264 | }, [getPositioningStyle]);
|
265 |
|
266 | const handleEntering = (element, isAppearing) => {
|
267 | if (onEntering) {
|
268 | onEntering(element, isAppearing);
|
269 | }
|
270 |
|
271 | setPositioningStyles();
|
272 | };
|
273 |
|
274 | const handlePaperRef = React.useCallback(instance => {
|
275 |
|
276 | paperRef.current = ReactDOM.findDOMNode(instance);
|
277 | }, []);
|
278 | React.useEffect(() => {
|
279 | if (open) {
|
280 | setPositioningStyles();
|
281 | }
|
282 | });
|
283 | React.useImperativeHandle(action, () => open ? {
|
284 | updatePosition: () => {
|
285 | setPositioningStyles();
|
286 | }
|
287 | } : null, [open, setPositioningStyles]);
|
288 | React.useEffect(() => {
|
289 | if (!open) {
|
290 | return undefined;
|
291 | }
|
292 |
|
293 | const handleResize = debounce(() => {
|
294 | setPositioningStyles();
|
295 | });
|
296 | window.addEventListener('resize', handleResize);
|
297 | return () => {
|
298 | handleResize.clear();
|
299 | window.removeEventListener('resize', handleResize);
|
300 | };
|
301 | }, [open, setPositioningStyles]);
|
302 | let transitionDuration = transitionDurationProp;
|
303 |
|
304 | if (transitionDurationProp === 'auto' && !TransitionComponent.muiSupportAuto) {
|
305 | transitionDuration = undefined;
|
306 | }
|
307 |
|
308 |
|
309 |
|
310 |
|
311 | const container = containerProp || (anchorEl ? ownerDocument(getAnchorEl(anchorEl)).body : undefined);
|
312 | return React.createElement(Modal, _extends({
|
313 | container: container,
|
314 | open: open,
|
315 | ref: ref,
|
316 | BackdropProps: {
|
317 | invisible: true
|
318 | },
|
319 | className: clsx(classes.root, className)
|
320 | }, other), React.createElement(TransitionComponent, _extends({
|
321 | appear: true,
|
322 | in: open,
|
323 | onEnter: onEnter,
|
324 | onEntered: onEntered,
|
325 | onExit: onExit,
|
326 | onExited: onExited,
|
327 | onExiting: onExiting,
|
328 | timeout: transitionDuration
|
329 | }, TransitionProps, {
|
330 | onEntering: createChainedFunction(handleEntering, TransitionProps.onEntering)
|
331 | }), React.createElement(Paper, _extends({
|
332 | elevation: elevation,
|
333 | ref: handlePaperRef
|
334 | }, PaperProps, {
|
335 | className: clsx(classes.paper, PaperProps.className)
|
336 | }), children)));
|
337 | });
|
338 | process.env.NODE_ENV !== "production" ? Popover.propTypes = {
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 | |
345 |
|
346 |
|
347 |
|
348 | action: refType,
|
349 |
|
350 | |
351 |
|
352 |
|
353 |
|
354 | anchorEl: chainPropTypes(PropTypes.oneOfType([HTMLElementType, PropTypes.func]), props => {
|
355 | if (props.open && (!props.anchorReference || props.anchorReference === 'anchorEl')) {
|
356 | const resolvedAnchorEl = getAnchorEl(props.anchorEl);
|
357 |
|
358 | if (resolvedAnchorEl && resolvedAnchorEl.nodeType === 1) {
|
359 | const box = resolvedAnchorEl.getBoundingClientRect();
|
360 |
|
361 | if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
|
362 | 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'));
|
363 | }
|
364 | } else {
|
365 | return new Error(['Material-UI: The `anchorEl` prop provided to the component is invalid.', `It should be an Element instance but it's \`${resolvedAnchorEl}\` instead.`].join('\n'));
|
366 | }
|
367 | }
|
368 |
|
369 | return null;
|
370 | }),
|
371 |
|
372 | |
373 |
|
374 |
|
375 |
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 | anchorOrigin: PropTypes.shape({
|
382 | horizontal: PropTypes.oneOfType([PropTypes.oneOf(['center', 'left', 'right']), PropTypes.number]).isRequired,
|
383 | vertical: PropTypes.oneOfType([PropTypes.oneOf(['bottom', 'center', 'top']), PropTypes.number]).isRequired
|
384 | }),
|
385 |
|
386 | |
387 |
|
388 |
|
389 |
|
390 |
|
391 |
|
392 | anchorPosition: PropTypes.shape({
|
393 | left: PropTypes.number.isRequired,
|
394 | top: PropTypes.number.isRequired
|
395 | }),
|
396 |
|
397 | |
398 |
|
399 |
|
400 |
|
401 | anchorReference: PropTypes.oneOf(['anchorEl', 'anchorPosition', 'none']),
|
402 |
|
403 | |
404 |
|
405 |
|
406 | children: PropTypes.node,
|
407 |
|
408 | |
409 |
|
410 |
|
411 |
|
412 | classes: PropTypes.object,
|
413 |
|
414 | |
415 |
|
416 |
|
417 | className: PropTypes.string,
|
418 |
|
419 | |
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 | container: PropTypes
|
427 |
|
428 | .oneOfType([HTMLElementType, PropTypes.instanceOf(React.Component), PropTypes.func]),
|
429 |
|
430 | |
431 |
|
432 |
|
433 | elevation: PropTypes.number,
|
434 |
|
435 | |
436 |
|
437 |
|
438 |
|
439 |
|
440 |
|
441 |
|
442 |
|
443 | getContentAnchorEl: PropTypes.func,
|
444 |
|
445 | |
446 |
|
447 |
|
448 | marginThreshold: PropTypes.number,
|
449 |
|
450 | |
451 |
|
452 |
|
453 | onClose: PropTypes.func,
|
454 |
|
455 | |
456 |
|
457 |
|
458 |
|
459 | onEnter: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
|
460 |
|
461 | |
462 |
|
463 |
|
464 |
|
465 | onEntered: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
|
466 |
|
467 | |
468 |
|
469 |
|
470 |
|
471 | onEntering: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
|
472 |
|
473 | |
474 |
|
475 |
|
476 |
|
477 | onExit: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
|
478 |
|
479 | |
480 |
|
481 |
|
482 |
|
483 | onExited: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
|
484 |
|
485 | |
486 |
|
487 |
|
488 |
|
489 | onExiting: deprecatedPropType(PropTypes.func, 'Use the `TransitionProps` prop instead.'),
|
490 |
|
491 | |
492 |
|
493 |
|
494 | open: PropTypes.bool.isRequired,
|
495 |
|
496 | |
497 |
|
498 |
|
499 | PaperProps: PropTypes
|
500 |
|
501 | .shape({
|
502 | component: elementTypeAcceptingRef
|
503 | }),
|
504 |
|
505 | |
506 |
|
507 |
|
508 |
|
509 |
|
510 |
|
511 |
|
512 |
|
513 | transformOrigin: PropTypes.shape({
|
514 | horizontal: PropTypes.oneOfType([PropTypes.oneOf(['center', 'left', 'right']), PropTypes.number]).isRequired,
|
515 | vertical: PropTypes.oneOfType([PropTypes.oneOf(['bottom', 'center', 'top']), PropTypes.number]).isRequired
|
516 | }),
|
517 |
|
518 | |
519 |
|
520 |
|
521 |
|
522 | TransitionComponent: PropTypes.elementType,
|
523 |
|
524 | |
525 |
|
526 |
|
527 | transitionDuration: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.number, PropTypes.shape({
|
528 | appear: PropTypes.number,
|
529 | enter: PropTypes.number,
|
530 | exit: PropTypes.number
|
531 | })]),
|
532 |
|
533 | |
534 |
|
535 |
|
536 | TransitionProps: PropTypes.object
|
537 | } : void 0;
|
538 | export default withStyles(styles, {
|
539 | name: 'MuiPopover'
|
540 | })(Popover); |
\ | No newline at end of file |