@material-ui/core
Version:
React components that implement Google's Material Design.
440 lines (376 loc) • 13.9 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import _objectWithoutProperties from "@babel/runtime/helpers/objectWithoutProperties";
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
import _createClass from "@babel/runtime/helpers/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
import _assertThisInitialized from "@babel/runtime/helpers/assertThisInitialized";
import _inherits from "@babel/runtime/helpers/inherits";
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import ownerDocument from '../utils/ownerDocument';
import Portal from '../Portal';
import { createChainedFunction } from '../utils/helpers';
import { setRef } from '../utils/reactHelpers';
import withForwardedRef from '../utils/withForwardedRef';
import { withTheme } from '@material-ui/styles';
import { elementAcceptingRef } from '@material-ui/utils';
import zIndex from '../styles/zIndex';
import ModalManager from './ModalManager';
import TrapFocus from './TrapFocus';
import SimpleBackdrop from './SimpleBackdrop';
import { ariaHidden } from './manageAriaHidden';
function getContainer(container) {
container = typeof container === 'function' ? container() : container;
return ReactDOM.findDOMNode(container);
}
function getHasTransition(props) {
return props.children ? props.children.props.hasOwnProperty('in') : false;
}
export var styles = function styles(theme) {
return {
/* Styles applied to the root element. */
root: {
position: 'fixed',
zIndex: theme.zIndex.modal,
right: 0,
bottom: 0,
top: 0,
left: 0
},
/* Styles applied to the root element if the `Modal` has exited. */
hidden: {
visibility: 'hidden'
}
};
};
/**
* Modal is a lower-level construct that is leveraged by the following components:
*
* - [Dialog](/api/dialog/)
* - [Drawer](/api/drawer/)
* - [Menu](/api/menu/)
* - [Popover](/api/popover/)
*
* If you are creating a modal dialog, you probably want to use the [Dialog](/api/dialog/) component
* rather than directly using Modal.
*
* This component shares many concepts with [react-overlays](https://react-bootstrap.github.io/react-overlays/#modals).
*/
var Modal =
/*#__PURE__*/
function (_React$Component) {
_inherits(Modal, _React$Component);
function Modal(props) {
var _this;
_classCallCheck(this, Modal);
_this = _possibleConstructorReturn(this, _getPrototypeOf(Modal).call(this));
_this.handleOpen = function () {
var container = getContainer(_this.props.container) || _this.getDoc().body;
_this.props.manager.add(_assertThisInitialized(_this), container);
if (_this.modalRef) {
_this.handleOpened();
}
};
_this.handleRendered = function () {
if (_this.props.onRendered) {
_this.props.onRendered();
}
if (_this.props.open) {
_this.handleOpened();
} else {
ariaHidden(_this.modalRef, true);
}
};
_this.handleOpened = function () {
_this.props.manager.mount(_assertThisInitialized(_this)); // Fix a bug on Chrome where the scroll isn't initially 0.
_this.modalRef.scrollTop = 0;
};
_this.handleClose = function (reason) {
var hasTransition = getHasTransition(_this.props);
/* If the component does not have a transition or is unmounting remove the Modal
otherwise let the transition handle removing the style, this prevents elements
moving around when the Modal is closed. */
if (!(hasTransition && _this.props.closeAfterTransition) || reason === 'unmount') {
_this.props.manager.remove(_assertThisInitialized(_this));
}
};
_this.handleEnter = function () {
_this.setState({
exited: false
});
};
_this.handleExited = function () {
if (_this.props.closeAfterTransition) {
_this.props.manager.remove(_assertThisInitialized(_this));
}
_this.setState({
exited: true
});
};
_this.handleBackdropClick = function (event) {
if (event.target !== event.currentTarget) {
return;
}
if (_this.props.onBackdropClick) {
_this.props.onBackdropClick(event);
}
if (!_this.props.disableBackdropClick && _this.props.onClose) {
_this.props.onClose(event, 'backdropClick');
}
};
_this.handleKeyDown = function (event) {
// We don't take event.defaultPrevented into account:
//
// event.preventDefault() is meant to stop default behaviours like
// clicking a checkbox to check it, hitting a button to submit a form,
// and hitting left arrow to move the cursor in a text input etc.
// Only special HTML elements have these default behaviors.
if (event.key !== 'Escape' || !_this.isTopModal()) {
return;
} // Swallow the event, in case someone is listening for the escape key on the body.
event.stopPropagation();
if (_this.props.onEscapeKeyDown) {
_this.props.onEscapeKeyDown(event);
}
if (!_this.props.disableEscapeKeyDown && _this.props.onClose) {
_this.props.onClose(event, 'escapeKeyDown');
}
};
_this.handlePortalRef = function (ref) {
_this.mountNode = ref;
};
_this.handleModalRef = function (ref) {
_this.modalRef = ref;
setRef(_this.props.innerRef, ref);
};
_this.isTopModal = function () {
return _this.props.manager.isTopModal(_assertThisInitialized(_this));
};
_this.getDoc = function () {
return ownerDocument(_this.mountNode);
};
_this.state = {
exited: !props.open
};
return _this;
}
_createClass(Modal, [{
key: "componentDidMount",
value: function componentDidMount() {
if (this.props.open) {
this.handleOpen();
}
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate(prevProps) {
if (prevProps.open && !this.props.open) {
this.handleClose();
} else if (!prevProps.open && this.props.open) {
this.handleOpen();
}
}
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
if (this.props.open || getHasTransition(this.props) && !this.state.exited) {
this.handleClose('unmount');
}
}
}, {
key: "render",
value: function render() {
var _this$props = this.props,
BackdropComponent = _this$props.BackdropComponent,
BackdropProps = _this$props.BackdropProps,
children = _this$props.children,
closeAfterTransition = _this$props.closeAfterTransition,
container = _this$props.container,
disableAutoFocus = _this$props.disableAutoFocus,
disableBackdropClick = _this$props.disableBackdropClick,
disableEnforceFocus = _this$props.disableEnforceFocus,
disableEscapeKeyDown = _this$props.disableEscapeKeyDown,
disablePortal = _this$props.disablePortal,
disableRestoreFocus = _this$props.disableRestoreFocus,
hideBackdrop = _this$props.hideBackdrop,
innerRef = _this$props.innerRef,
keepMounted = _this$props.keepMounted,
manager = _this$props.manager,
onBackdropClick = _this$props.onBackdropClick,
onClose = _this$props.onClose,
onEscapeKeyDown = _this$props.onEscapeKeyDown,
onRendered = _this$props.onRendered,
open = _this$props.open,
theme = _this$props.theme,
other = _objectWithoutProperties(_this$props, ["BackdropComponent", "BackdropProps", "children", "closeAfterTransition", "container", "disableAutoFocus", "disableBackdropClick", "disableEnforceFocus", "disableEscapeKeyDown", "disablePortal", "disableRestoreFocus", "hideBackdrop", "innerRef", "keepMounted", "manager", "onBackdropClick", "onClose", "onEscapeKeyDown", "onRendered", "open", "theme"]);
var exited = this.state.exited;
var hasTransition = getHasTransition(this.props);
if (!keepMounted && !open && (!hasTransition || exited)) {
return null;
}
var childProps = {}; // It's a Transition like component
if (hasTransition) {
childProps.onEnter = createChainedFunction(this.handleEnter, children.props.onEnter);
childProps.onExited = createChainedFunction(this.handleExited, children.props.onExited);
}
if (children.props.role === undefined) {
childProps.role = children.props.role || 'document';
}
if (children.props.tabIndex === undefined) {
childProps.tabIndex = children.props.tabIndex || '-1';
}
var stylesRender = styles(theme || {
zIndex: zIndex
});
return React.createElement(Portal, {
ref: this.handlePortalRef,
container: container,
disablePortal: disablePortal,
onRendered: this.handleRendered
}, React.createElement("div", _extends({
ref: this.handleModalRef,
onKeyDown: this.handleKeyDown,
role: "presentation"
}, other, {
style: _extends({}, stylesRender.root, !open && exited ? stylesRender.hidden : {}, other.style)
}), hideBackdrop ? null : React.createElement(BackdropComponent, _extends({
open: open,
onClick: this.handleBackdropClick
}, BackdropProps)), React.createElement(TrapFocus, {
disableEnforceFocus: disableEnforceFocus,
disableAutoFocus: disableAutoFocus,
disableRestoreFocus: disableRestoreFocus,
getDoc: this.getDoc,
isEnabled: this.isTopModal,
open: open
}, React.cloneElement(children, childProps))));
}
}]);
return Modal;
}(React.Component);
process.env.NODE_ENV !== "production" ? Modal.propTypes = {
/**
* A backdrop component. This property enables custom backdrop rendering.
*/
BackdropComponent: PropTypes.elementType,
/**
* Properties applied to the [`Backdrop`](/api/backdrop/) element.
*/
BackdropProps: PropTypes.object,
/**
* A single child content element.
*/
children: elementAcceptingRef.isRequired,
/**
* When set to true the Modal waits until a nested Transition is completed before closing.
*/
closeAfterTransition: PropTypes.bool,
/**
* A node, component instance, or function that returns either.
* The `container` will have the portal children appended to it.
*/
container: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
/**
* If `true`, the modal will not automatically shift focus to itself when it opens, and
* replace it to the last focused element when it closes.
* This also works correctly with any modal children that have the `disableAutoFocus` prop.
*
* Generally this should never be set to `true` as it makes the modal less
* accessible to assistive technologies, like screen readers.
*/
disableAutoFocus: PropTypes.bool,
/**
* If `true`, clicking the backdrop will not fire any callback.
*/
disableBackdropClick: PropTypes.bool,
/**
* If `true`, the modal will not prevent focus from leaving the modal while open.
*
* Generally this should never be set to `true` as it makes the modal less
* accessible to assistive technologies, like screen readers.
*/
disableEnforceFocus: PropTypes.bool,
/**
* If `true`, hitting escape will not fire any callback.
*/
disableEscapeKeyDown: PropTypes.bool,
/**
* Disable the portal behavior.
* The children stay within it's parent DOM hierarchy.
*/
disablePortal: PropTypes.bool,
/**
* If `true`, the modal will not restore focus to previously focused element once
* modal is hidden.
*/
disableRestoreFocus: PropTypes.bool,
/**
* If `true`, the backdrop is not rendered.
*/
hideBackdrop: PropTypes.bool,
/**
* @ignore
* from `withForwardRef`
*/
innerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* 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 Modal.
*/
keepMounted: PropTypes.bool,
/**
* @ignore
*
* A modal manager used to track and manage the state of open
* Modals. This enables customizing how modals interact within a container.
*/
manager: PropTypes.object,
/**
* Callback fired when the backdrop is clicked.
*/
onBackdropClick: PropTypes.func,
/**
* Callback fired when the component requests to be closed.
* The `reason` parameter can optionally be used to control the response to `onClose`.
*
* @param {object} event The event source of the callback
* @param {string} reason Can be:`"escapeKeyDown"`, `"backdropClick"`
*/
onClose: PropTypes.func,
/**
* Callback fired when the escape key is pressed,
* `disableEscapeKeyDown` is false and the modal is in focus.
*/
onEscapeKeyDown: PropTypes.func,
/**
* Callback fired once the children has been mounted into the `container`.
* It signals that the `open={true}` property took effect.
*/
onRendered: PropTypes.func,
/**
* If `true`, the modal is open.
*/
open: PropTypes.bool.isRequired,
/**
* @ignore
*/
theme: PropTypes.object
} : void 0;
Modal.defaultProps = {
BackdropComponent: SimpleBackdrop,
closeAfterTransition: false,
disableAutoFocus: false,
disableBackdropClick: false,
disableEnforceFocus: false,
disableEscapeKeyDown: false,
disablePortal: false,
disableRestoreFocus: false,
hideBackdrop: false,
keepMounted: false,
// Modals don't open on the server so this won't conflict with concurrent requests.
manager: new ModalManager()
};
export default withTheme(withForwardedRef(Modal));