UNPKG

@material-ui/core

Version:

React components that implement Google's Material Design.

273 lines (230 loc) 7.41 kB
import _extends from "@babel/runtime/helpers/extends"; import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties"; import _objectSpread from "@babel/runtime/helpers/objectSpread"; import React from 'react'; import ReactDOM from 'react-dom'; import PropTypes from 'prop-types'; import PopperJS from 'popper.js'; import withTheme from '../styles/withTheme'; import Portal from '../Portal'; function flipPlacement(theme, placement) { if (theme.direction !== 'rtl') { return placement; } switch (placement) { case 'bottom-end': return 'bottom-start'; case 'bottom-start': return 'bottom-end'; case 'top-end': return 'top-start'; case 'top-start': return 'top-end'; default: return placement; } } function getAnchorEl(anchorEl) { return typeof anchorEl === 'function' ? anchorEl() : anchorEl; } /** * Poppers rely on the 3rd party library [Popper.js](https://github.com/FezVrasta/popper.js) for positioning. */ class Popper extends React.Component { constructor(props) { super(); this.handleOpen = () => { const { anchorEl, modifiers, open, placement, popperOptions = {}, theme, disablePortal } = this.props; const popperNode = ReactDOM.findDOMNode(this); if (!popperNode || !anchorEl || !open) { return; } if (this.popper) { this.popper.destroy(); this.popper = null; } this.popper = new PopperJS(getAnchorEl(anchorEl), popperNode, _objectSpread({ placement: flipPlacement(theme, placement) }, popperOptions, { modifiers: _objectSpread({}, disablePortal ? {} : { // It's using scrollParent by default, we can use the viewport when using a portal. preventOverflow: { boundariesElement: 'window' } }, modifiers, popperOptions.modifiers), // We could have been using a custom modifier like react-popper is doing. // But it seems this is the best public API for this use case. onCreate: this.handlePopperUpdate, onUpdate: this.handlePopperUpdate })); }; this.handlePopperUpdate = data => { if (data.placement !== this.state.placement) { this.setState({ placement: data.placement }); } }; this.handleExited = () => { this.setState({ exited: true }); this.handleClose(); }; this.handleClose = () => { if (!this.popper) { return; } this.popper.destroy(); this.popper = null; }; this.state = { exited: !props.open }; } componentDidUpdate(prevProps) { if (prevProps.open !== this.props.open && !this.props.open && !this.props.transition) { // Otherwise handleExited will call this. this.handleClose(); } // Let's update the popper position. if (prevProps.open !== this.props.open || prevProps.anchorEl !== this.props.anchorEl || prevProps.popperOptions !== this.props.popperOptions || prevProps.modifiers !== this.props.modifiers || prevProps.disablePortal !== this.props.disablePortal || prevProps.placement !== this.props.placement) { this.handleOpen(); } } componentWillUnmount() { this.handleClose(); } static getDerivedStateFromProps(nextProps) { if (nextProps.open) { return { exited: false }; } if (!nextProps.transition) { // Otherwise let handleExited take care of marking exited. return { exited: true }; } return null; } render() { const _this$props = this.props, { anchorEl, children, container, disablePortal, keepMounted, modifiers, open, placement: placementProps, popperOptions, theme, transition } = _this$props, other = _objectWithoutProperties(_this$props, ["anchorEl", "children", "container", "disablePortal", "keepMounted", "modifiers", "open", "placement", "popperOptions", "theme", "transition"]); const { exited, placement } = this.state; if (!keepMounted && !open && (!transition || exited)) { return null; } const childProps = { placement: placement || flipPlacement(theme, placementProps) }; if (transition) { childProps.TransitionProps = { in: open, onExited: this.handleExited }; } return React.createElement(Portal, { onRendered: this.handleOpen, disablePortal: disablePortal, container: container }, React.createElement("div", _extends({ role: "tooltip", style: { // Prevents scroll issue, waiting for Popper.js to add this style once initiated. position: 'absolute' } }, other), typeof children === 'function' ? children(childProps) : children)); } } Popper.propTypes = process.env.NODE_ENV !== "production" ? { /** * This is the DOM element, or a function that returns the DOM element, * that may be used to set the position of the popover. * The return value will passed as the reference object of the Popper * instance. */ anchorEl: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), /** * Popper render function or node. */ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired, /** * A node, component instance, or function that returns either. * The `container` will passed to the Modal component. * By default, it uses the body of the anchorEl's top-level document object, * so it's simply `document.body` most of the time. */ container: PropTypes.oneOfType([PropTypes.object, PropTypes.func]), /** * Disable the portal behavior. * The children stay within it's parent DOM hierarchy. */ disablePortal: PropTypes.bool, /** * Always keep the children in the DOM. * This property can be useful in SEO situation or * when you want to maximize the responsiveness of the Popper. */ keepMounted: PropTypes.bool, /** * Popper.js is based on a "plugin-like" architecture, * most of its features are fully encapsulated "modifiers". * * A modifier is a function that is called each time Popper.js needs to * compute the position of the popper. * For this reason, modifiers should be very performant to avoid bottlenecks. * To learn how to create a modifier, [read the modifiers documentation](https://github.com/FezVrasta/popper.js/blob/master/docs/_includes/popper-documentation.md#modifiers--object). */ modifiers: PropTypes.object, /** * If `true`, the popper is visible. */ open: PropTypes.bool.isRequired, /** * Popper placement. */ placement: PropTypes.oneOf(['bottom-end', 'bottom-start', 'bottom', 'left-end', 'left-start', 'left', 'right-end', 'right-start', 'right', 'top-end', 'top-start', 'top']), /** * Options provided to the [`popper.js`](https://github.com/FezVrasta/popper.js) instance. */ popperOptions: PropTypes.object, /** * @ignore */ theme: PropTypes.object.isRequired, /** * Help supporting a react-transition-group/Transition component. */ transition: PropTypes.bool } : {}; Popper.defaultProps = { disablePortal: false, placement: 'bottom', transition: false }; export default withTheme()(Popper);