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