@material-ui/core
Version:
React components that implement Google's Material Design.
273 lines (230 loc) • 7.41 kB
JavaScript
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);