UNPKG

13.5 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import { chainPropTypes, HTMLElementType, refType, unstable_ownerDocument as ownerDocument, unstable_useEnhancedEffect as useEnhancedEffect, unstable_useForkRef as useForkRef } from '@mui/utils';
5import { createPopper } from '@popperjs/core';
6import PropTypes from 'prop-types';
7import composeClasses from '@mui/utils/composeClasses';
8import useSlotProps from '@mui/utils/useSlotProps';
9import Portal from "../Portal/index.js";
10import { getPopperUtilityClass } from "./popperClasses.js";
11import { jsx as _jsx } from "react/jsx-runtime";
12function flipPlacement(placement, direction) {
13 if (direction === 'ltr') {
14 return placement;
15 }
16 switch (placement) {
17 case 'bottom-end':
18 return 'bottom-start';
19 case 'bottom-start':
20 return 'bottom-end';
21 case 'top-end':
22 return 'top-start';
23 case 'top-start':
24 return 'top-end';
25 default:
26 return placement;
27 }
28}
29function resolveAnchorEl(anchorEl) {
30 return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
31}
32function isHTMLElement(element) {
33 return element.nodeType !== undefined;
34}
35function isVirtualElement(element) {
36 return !isHTMLElement(element);
37}
38const useUtilityClasses = ownerState => {
39 const {
40 classes
41 } = ownerState;
42 const slots = {
43 root: ['root']
44 };
45 return composeClasses(slots, getPopperUtilityClass, classes);
46};
47const defaultPopperOptions = {};
48const PopperTooltip = /*#__PURE__*/React.forwardRef(function PopperTooltip(props, forwardedRef) {
49 const {
50 anchorEl,
51 children,
52 direction,
53 disablePortal,
54 modifiers,
55 open,
56 placement: initialPlacement,
57 popperOptions,
58 popperRef: popperRefProp,
59 slotProps = {},
60 slots = {},
61 TransitionProps,
62 // @ts-ignore internal logic
63 ownerState: ownerStateProp,
64 // prevent from spreading to DOM, it can come from the parent component e.g. Select.
65 ...other
66 } = props;
67 const tooltipRef = React.useRef(null);
68 const ownRef = useForkRef(tooltipRef, forwardedRef);
69 const popperRef = React.useRef(null);
70 const handlePopperRef = useForkRef(popperRef, popperRefProp);
71 const handlePopperRefRef = React.useRef(handlePopperRef);
72 useEnhancedEffect(() => {
73 handlePopperRefRef.current = handlePopperRef;
74 }, [handlePopperRef]);
75 React.useImperativeHandle(popperRefProp, () => popperRef.current, []);
76 const rtlPlacement = flipPlacement(initialPlacement, direction);
77 /**
78 * placement initialized from prop but can change during lifetime if modifiers.flip.
79 * modifiers.flip is essentially a flip for controlled/uncontrolled behavior
80 */
81 const [placement, setPlacement] = React.useState(rtlPlacement);
82 const [resolvedAnchorElement, setResolvedAnchorElement] = React.useState(resolveAnchorEl(anchorEl));
83 React.useEffect(() => {
84 if (popperRef.current) {
85 popperRef.current.forceUpdate();
86 }
87 });
88 React.useEffect(() => {
89 if (anchorEl) {
90 setResolvedAnchorElement(resolveAnchorEl(anchorEl));
91 }
92 }, [anchorEl]);
93 useEnhancedEffect(() => {
94 if (!resolvedAnchorElement || !open) {
95 return undefined;
96 }
97 const handlePopperUpdate = data => {
98 setPlacement(data.placement);
99 };
100 if (process.env.NODE_ENV !== 'production') {
101 if (resolvedAnchorElement && isHTMLElement(resolvedAnchorElement) && resolvedAnchorElement.nodeType === 1) {
102 const box = resolvedAnchorElement.getBoundingClientRect();
103 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
104 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'));
105 }
106 }
107 }
108 let popperModifiers = [{
109 name: 'preventOverflow',
110 options: {
111 altBoundary: disablePortal
112 }
113 }, {
114 name: 'flip',
115 options: {
116 altBoundary: disablePortal
117 }
118 }, {
119 name: 'onUpdate',
120 enabled: true,
121 phase: 'afterWrite',
122 fn: ({
123 state
124 }) => {
125 handlePopperUpdate(state);
126 }
127 }];
128 if (modifiers != null) {
129 popperModifiers = popperModifiers.concat(modifiers);
130 }
131 if (popperOptions && popperOptions.modifiers != null) {
132 popperModifiers = popperModifiers.concat(popperOptions.modifiers);
133 }
134 const popper = createPopper(resolvedAnchorElement, tooltipRef.current, {
135 placement: rtlPlacement,
136 ...popperOptions,
137 modifiers: popperModifiers
138 });
139 handlePopperRefRef.current(popper);
140 return () => {
141 popper.destroy();
142 handlePopperRefRef.current(null);
143 };
144 }, [resolvedAnchorElement, disablePortal, modifiers, open, popperOptions, rtlPlacement]);
145 const childProps = {
146 placement: placement
147 };
148 if (TransitionProps !== null) {
149 childProps.TransitionProps = TransitionProps;
150 }
151 const classes = useUtilityClasses(props);
152 const Root = slots.root ?? 'div';
153 const rootProps = useSlotProps({
154 elementType: Root,
155 externalSlotProps: slotProps.root,
156 externalForwardedProps: other,
157 additionalProps: {
158 role: 'tooltip',
159 ref: ownRef
160 },
161 ownerState: props,
162 className: classes.root
163 });
164 return /*#__PURE__*/_jsx(Root, {
165 ...rootProps,
166 children: typeof children === 'function' ? children(childProps) : children
167 });
168});
169
170/**
171 * @ignore - internal component.
172 */
173const Popper = /*#__PURE__*/React.forwardRef(function Popper(props, forwardedRef) {
174 const {
175 anchorEl,
176 children,
177 container: containerProp,
178 direction = 'ltr',
179 disablePortal = false,
180 keepMounted = false,
181 modifiers,
182 open,
183 placement = 'bottom',
184 popperOptions = defaultPopperOptions,
185 popperRef,
186 style,
187 transition = false,
188 slotProps = {},
189 slots = {},
190 ...other
191 } = props;
192 const [exited, setExited] = React.useState(true);
193 const handleEnter = () => {
194 setExited(false);
195 };
196 const handleExited = () => {
197 setExited(true);
198 };
199 if (!keepMounted && !open && (!transition || exited)) {
200 return null;
201 }
202
203 // If the container prop is provided, use that
204 // If the anchorEl prop is provided, use its parent body element as the container
205 // If neither are provided let the Modal take care of choosing the container
206 let container;
207 if (containerProp) {
208 container = containerProp;
209 } else if (anchorEl) {
210 const resolvedAnchorEl = resolveAnchorEl(anchorEl);
211 container = resolvedAnchorEl && isHTMLElement(resolvedAnchorEl) ? ownerDocument(resolvedAnchorEl).body : ownerDocument(null).body;
212 }
213 const display = !open && keepMounted && (!transition || exited) ? 'none' : undefined;
214 const transitionProps = transition ? {
215 in: open,
216 onEnter: handleEnter,
217 onExited: handleExited
218 } : undefined;
219 return /*#__PURE__*/_jsx(Portal, {
220 disablePortal: disablePortal,
221 container: container,
222 children: /*#__PURE__*/_jsx(PopperTooltip, {
223 anchorEl: anchorEl,
224 direction: direction,
225 disablePortal: disablePortal,
226 modifiers: modifiers,
227 ref: forwardedRef,
228 open: transition ? !exited : open,
229 placement: placement,
230 popperOptions: popperOptions,
231 popperRef: popperRef,
232 slotProps: slotProps,
233 slots: slots,
234 ...other,
235 style: {
236 // Prevents scroll issue, waiting for Popper.js to add this style once initiated.
237 position: 'fixed',
238 // Fix Popper.js display issue
239 top: 0,
240 left: 0,
241 display,
242 ...style
243 },
244 TransitionProps: transitionProps,
245 children: children
246 })
247 });
248});
249process.env.NODE_ENV !== "production" ? Popper.propTypes /* remove-proptypes */ = {
250 // ┌────────────────────────────── Warning ──────────────────────────────┐
251 // │ These PropTypes are generated from the TypeScript type definitions. │
252 // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
253 // └─────────────────────────────────────────────────────────────────────┘
254 /**
255 * An HTML element, [virtualElement](https://popper.js.org/docs/v2/virtual-elements/),
256 * or a function that returns either.
257 * It's used to set the position of the popper.
258 * The return value will passed as the reference object of the Popper instance.
259 */
260 anchorEl: chainPropTypes(PropTypes.oneOfType([HTMLElementType, PropTypes.object, PropTypes.func]), props => {
261 if (props.open) {
262 const resolvedAnchorEl = resolveAnchorEl(props.anchorEl);
263 if (resolvedAnchorEl && isHTMLElement(resolvedAnchorEl) && resolvedAnchorEl.nodeType === 1) {
264 const box = resolvedAnchorEl.getBoundingClientRect();
265 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
266 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'));
267 }
268 } else if (!resolvedAnchorEl || typeof resolvedAnchorEl.getBoundingClientRect !== 'function' || isVirtualElement(resolvedAnchorEl) && resolvedAnchorEl.contextElement != null && resolvedAnchorEl.contextElement.nodeType !== 1) {
269 return new Error(['MUI: The `anchorEl` prop provided to the component is invalid.', 'It should be an HTML element instance or a virtualElement ', '(https://popper.js.org/docs/v2/virtual-elements/).'].join('\n'));
270 }
271 }
272 return null;
273 }),
274 /**
275 * Popper render function or node.
276 */
277 children: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([PropTypes.node, PropTypes.func]),
278 /**
279 * An HTML element or function that returns one.
280 * The `container` will have the portal children appended to it.
281 *
282 * You can also provide a callback, which is called in a React layout effect.
283 * This lets you set the container from a ref, and also makes server-side rendering possible.
284 *
285 * By default, it uses the body of the top-level document object,
286 * so it's simply `document.body` most of the time.
287 */
288 container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([HTMLElementType, PropTypes.func]),
289 /**
290 * Direction of the text.
291 * @default 'ltr'
292 */
293 direction: PropTypes.oneOf(['ltr', 'rtl']),
294 /**
295 * The `children` will be under the DOM hierarchy of the parent component.
296 * @default false
297 */
298 disablePortal: PropTypes.bool,
299 /**
300 * Always keep the children in the DOM.
301 * This prop can be useful in SEO situation or
302 * when you want to maximize the responsiveness of the Popper.
303 * @default false
304 */
305 keepMounted: PropTypes.bool,
306 /**
307 * Popper.js is based on a "plugin-like" architecture,
308 * most of its features are fully encapsulated "modifiers".
309 *
310 * A modifier is a function that is called each time Popper.js needs to
311 * compute the position of the popper.
312 * For this reason, modifiers should be very performant to avoid bottlenecks.
313 * To learn how to create a modifier, [read the modifiers documentation](https://popper.js.org/docs/v2/modifiers/).
314 */
315 modifiers: PropTypes.arrayOf(PropTypes.shape({
316 data: PropTypes.object,
317 effect: PropTypes.func,
318 enabled: PropTypes.bool,
319 fn: PropTypes.func,
320 name: PropTypes.any,
321 options: PropTypes.object,
322 phase: PropTypes.oneOf(['afterMain', 'afterRead', 'afterWrite', 'beforeMain', 'beforeRead', 'beforeWrite', 'main', 'read', 'write']),
323 requires: PropTypes.arrayOf(PropTypes.string),
324 requiresIfExists: PropTypes.arrayOf(PropTypes.string)
325 })),
326 /**
327 * If `true`, the component is shown.
328 */
329 open: PropTypes.bool.isRequired,
330 /**
331 * Popper placement.
332 * @default 'bottom'
333 */
334 placement: PropTypes.oneOf(['auto-end', 'auto-start', 'auto', 'bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top']),
335 /**
336 * Options provided to the [`Popper.js`](https://popper.js.org/docs/v2/constructors/#options) instance.
337 * @default {}
338 */
339 popperOptions: PropTypes.shape({
340 modifiers: PropTypes.array,
341 onFirstUpdate: PropTypes.func,
342 placement: PropTypes.oneOf(['auto-end', 'auto-start', 'auto', 'bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top']),
343 strategy: PropTypes.oneOf(['absolute', 'fixed'])
344 }),
345 /**
346 * A ref that points to the used popper instance.
347 */
348 popperRef: refType,
349 /**
350 * The props used for each slot inside the Popper.
351 * @default {}
352 */
353 slotProps: PropTypes.shape({
354 root: PropTypes.oneOfType([PropTypes.func, PropTypes.object])
355 }),
356 /**
357 * The components used for each slot inside the Popper.
358 * Either a string to use a HTML element or a component.
359 * @default {}
360 */
361 slots: PropTypes.shape({
362 root: PropTypes.elementType
363 }),
364 /**
365 * Help supporting a react-transition-group/Transition component.
366 * @default false
367 */
368 transition: PropTypes.bool
369} : void 0;
370export default Popper;
\No newline at end of file