UNPKG

10.1 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import PopperJs from 'popper.js';
6import { chainPropTypes, refType, HTMLElementType } from '@material-ui/utils';
7import { useTheme } from '@material-ui/styles';
8import Portal from '../Portal';
9import createChainedFunction from '../utils/createChainedFunction';
10import setRef from '../utils/setRef';
11import useForkRef from '../utils/useForkRef';
12
13function flipPlacement(placement, theme) {
14 const direction = theme && theme.direction || 'ltr';
15
16 if (direction === 'ltr') {
17 return placement;
18 }
19
20 switch (placement) {
21 case 'bottom-end':
22 return 'bottom-start';
23
24 case 'bottom-start':
25 return 'bottom-end';
26
27 case 'top-end':
28 return 'top-start';
29
30 case 'top-start':
31 return 'top-end';
32
33 default:
34 return placement;
35 }
36}
37
38function getAnchorEl(anchorEl) {
39 return typeof anchorEl === 'function' ? anchorEl() : anchorEl;
40}
41
42const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
43const defaultPopperOptions = {};
44/**
45 * Poppers rely on the 3rd party library [Popper.js](https://popper.js.org/docs/v1/) for positioning.
46 */
47
48const Popper = /*#__PURE__*/React.forwardRef(function Popper(props, ref) {
49 const {
50 anchorEl,
51 children,
52 container,
53 disablePortal = false,
54 keepMounted = false,
55 modifiers,
56 open,
57 placement: initialPlacement = 'bottom',
58 popperOptions = defaultPopperOptions,
59 popperRef: popperRefProp,
60 style,
61 transition = false
62 } = props,
63 other = _objectWithoutPropertiesLoose(props, ["anchorEl", "children", "container", "disablePortal", "keepMounted", "modifiers", "open", "placement", "popperOptions", "popperRef", "style", "transition"]);
64
65 const tooltipRef = React.useRef(null);
66 const ownRef = useForkRef(tooltipRef, ref);
67 const popperRef = React.useRef(null);
68 const handlePopperRef = useForkRef(popperRef, popperRefProp);
69 const handlePopperRefRef = React.useRef(handlePopperRef);
70 useEnhancedEffect(() => {
71 handlePopperRefRef.current = handlePopperRef;
72 }, [handlePopperRef]);
73 React.useImperativeHandle(popperRefProp, () => popperRef.current, []);
74 const [exited, setExited] = React.useState(true);
75 const theme = useTheme();
76 const rtlPlacement = flipPlacement(initialPlacement, theme);
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
82 const [placement, setPlacement] = React.useState(rtlPlacement);
83 React.useEffect(() => {
84 if (popperRef.current) {
85 popperRef.current.update();
86 }
87 });
88 const handleOpen = React.useCallback(() => {
89 if (!tooltipRef.current || !anchorEl || !open) {
90 return;
91 }
92
93 if (popperRef.current) {
94 popperRef.current.destroy();
95 handlePopperRefRef.current(null);
96 }
97
98 const handlePopperUpdate = data => {
99 setPlacement(data.placement);
100 };
101
102 const resolvedAnchorEl = getAnchorEl(anchorEl);
103
104 if (process.env.NODE_ENV !== 'production') {
105 if (resolvedAnchorEl && resolvedAnchorEl.nodeType === 1) {
106 const box = resolvedAnchorEl.getBoundingClientRect();
107
108 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
109 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'));
110 }
111 }
112 }
113
114 const popper = new PopperJs(getAnchorEl(anchorEl), tooltipRef.current, _extends({
115 placement: rtlPlacement
116 }, popperOptions, {
117 modifiers: _extends({}, disablePortal ? {} : {
118 // It's using scrollParent by default, we can use the viewport when using a portal.
119 preventOverflow: {
120 boundariesElement: 'window'
121 }
122 }, modifiers, popperOptions.modifiers),
123 // We could have been using a custom modifier like react-popper is doing.
124 // But it seems this is the best public API for this use case.
125 onCreate: createChainedFunction(handlePopperUpdate, popperOptions.onCreate),
126 onUpdate: createChainedFunction(handlePopperUpdate, popperOptions.onUpdate)
127 }));
128 handlePopperRefRef.current(popper);
129 }, [anchorEl, disablePortal, modifiers, open, rtlPlacement, popperOptions]);
130 const handleRef = React.useCallback(node => {
131 setRef(ownRef, node);
132 handleOpen();
133 }, [ownRef, handleOpen]);
134
135 const handleEnter = () => {
136 setExited(false);
137 };
138
139 const handleClose = () => {
140 if (!popperRef.current) {
141 return;
142 }
143
144 popperRef.current.destroy();
145 handlePopperRefRef.current(null);
146 };
147
148 const handleExited = () => {
149 setExited(true);
150 handleClose();
151 };
152
153 React.useEffect(() => {
154 return () => {
155 handleClose();
156 };
157 }, []);
158 React.useEffect(() => {
159 if (!open && !transition) {
160 // Otherwise handleExited will call this.
161 handleClose();
162 }
163 }, [open, transition]);
164
165 if (!keepMounted && !open && (!transition || exited)) {
166 return null;
167 }
168
169 const childProps = {
170 placement
171 };
172
173 if (transition) {
174 childProps.TransitionProps = {
175 in: open,
176 onEnter: handleEnter,
177 onExited: handleExited
178 };
179 }
180
181 return /*#__PURE__*/React.createElement(Portal, {
182 disablePortal: disablePortal,
183 container: container
184 }, /*#__PURE__*/React.createElement("div", _extends({
185 ref: handleRef,
186 role: "tooltip"
187 }, other, {
188 style: _extends({
189 // Prevents scroll issue, waiting for Popper.js to add this style once initiated.
190 position: 'fixed',
191 // Fix Popper.js display issue
192 top: 0,
193 left: 0,
194 display: !open && keepMounted && !transition ? 'none' : null
195 }, style)
196 }), typeof children === 'function' ? children(childProps) : children));
197});
198process.env.NODE_ENV !== "production" ? Popper.propTypes = {
199 // ----------------------------- Warning --------------------------------
200 // | These PropTypes are generated from the TypeScript type definitions |
201 // | To update them edit the d.ts file and run "yarn proptypes" |
202 // ----------------------------------------------------------------------
203
204 /**
205 * A HTML element, [referenceObject](https://popper.js.org/docs/v1/#referenceObject),
206 * or a function that returns either.
207 * It's used to set the position of the popper.
208 * The return value will passed as the reference object of the Popper instance.
209 */
210 anchorEl: chainPropTypes(PropTypes.oneOfType([HTMLElementType, PropTypes.object, PropTypes.func]), props => {
211 if (props.open) {
212 const resolvedAnchorEl = getAnchorEl(props.anchorEl);
213
214 if (resolvedAnchorEl && resolvedAnchorEl.nodeType === 1) {
215 const box = resolvedAnchorEl.getBoundingClientRect();
216
217 if (process.env.NODE_ENV !== 'test' && box.top === 0 && box.left === 0 && box.right === 0 && box.bottom === 0) {
218 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'));
219 }
220 } else if (!resolvedAnchorEl || typeof resolvedAnchorEl.clientWidth !== 'number' || typeof resolvedAnchorEl.clientHeight !== 'number' || typeof resolvedAnchorEl.getBoundingClientRect !== 'function') {
221 return new Error(['Material-UI: The `anchorEl` prop provided to the component is invalid.', 'It should be an HTML element instance or a referenceObject ', '(https://popper.js.org/docs/v1/#referenceObject).'].join('\n'));
222 }
223 }
224
225 return null;
226 }),
227
228 /**
229 * Popper render function or node.
230 */
231 children: PropTypes
232 /* @typescript-to-proptypes-ignore */
233 .oneOfType([PropTypes.node, PropTypes.func]).isRequired,
234
235 /**
236 * A HTML element, component instance, or function that returns either.
237 * The `container` will have the portal children appended to it.
238 *
239 * By default, it uses the body of the top-level document object,
240 * so it's simply `document.body` most of the time.
241 */
242 container: PropTypes
243 /* @typescript-to-proptypes-ignore */
244 .oneOfType([HTMLElementType, PropTypes.instanceOf(React.Component), PropTypes.func]),
245
246 /**
247 * Disable the portal behavior.
248 * The children stay within it's parent DOM hierarchy.
249 */
250 disablePortal: PropTypes.bool,
251
252 /**
253 * Always keep the children in the DOM.
254 * This prop can be useful in SEO situation or
255 * when you want to maximize the responsiveness of the Popper.
256 */
257 keepMounted: PropTypes.bool,
258
259 /**
260 * Popper.js is based on a "plugin-like" architecture,
261 * most of its features are fully encapsulated "modifiers".
262 *
263 * A modifier is a function that is called each time Popper.js needs to
264 * compute the position of the popper.
265 * For this reason, modifiers should be very performant to avoid bottlenecks.
266 * To learn how to create a modifier, [read the modifiers documentation](https://popper.js.org/docs/v1/#modifiers).
267 */
268 modifiers: PropTypes.object,
269
270 /**
271 * If `true`, the popper is visible.
272 */
273 open: PropTypes.bool.isRequired,
274
275 /**
276 * Popper placement.
277 */
278 placement: PropTypes.oneOf(['bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top']),
279
280 /**
281 * Options provided to the [`popper.js`](https://popper.js.org/docs/v1/) instance.
282 */
283 popperOptions: PropTypes.object,
284
285 /**
286 * A ref that points to the used popper instance.
287 */
288 popperRef: refType,
289
290 /**
291 * @ignore
292 */
293 style: PropTypes.object,
294
295 /**
296 * Help supporting a react-transition-group/Transition component.
297 */
298 transition: PropTypes.bool
299} : void 0;
300export default Popper;
\No newline at end of file