332 lines
8.8 KiB
JavaScript
332 lines
8.8 KiB
JavaScript
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
|
|
|
|
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
|
|
|
|
import React from 'react';
|
|
|
|
import ReactDOM from 'react-dom';
|
|
import classNames from 'classnames';
|
|
import warning from 'warning';
|
|
import keycode from 'keycode';
|
|
import canUseDom from 'dom-helpers/util/inDOM';
|
|
import contains from 'dom-helpers/query/contains';
|
|
import activeElement from 'dom-helpers/activeElement';
|
|
import ownerDocument from 'dom-helpers/ownerDocument';
|
|
import addEventListener from '../utils/addEventListener';
|
|
import { createChainedFunction } from '../utils/helpers';
|
|
import Fade from '../transitions/Fade';
|
|
import withStyles from '../styles/withStyles';
|
|
import createModalManager from './modalManager';
|
|
import Backdrop from './Backdrop';
|
|
import Portal from './Portal';
|
|
|
|
|
|
// Modals don't open on the server so this won't break concurrency.
|
|
// Could also put this on context.
|
|
const modalManager = createModalManager();
|
|
|
|
export const styles = theme => ({
|
|
root: {
|
|
display: 'flex',
|
|
width: '100%',
|
|
height: '100%',
|
|
position: 'fixed',
|
|
zIndex: theme.zIndex.dialog,
|
|
top: 0,
|
|
left: 0
|
|
},
|
|
hidden: {
|
|
visibility: 'hidden'
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @ignore - internal component.
|
|
*/
|
|
class Modal extends React.Component {
|
|
constructor(...args) {
|
|
var _temp;
|
|
|
|
return _temp = super(...args), _initialiseProps.call(this), _temp;
|
|
}
|
|
|
|
componentWillMount() {
|
|
if (!this.props.show) {
|
|
this.setState({ exited: true });
|
|
}
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.mounted = true;
|
|
if (this.props.show) {
|
|
this.handleShow();
|
|
}
|
|
}
|
|
|
|
componentWillReceiveProps(nextProps) {
|
|
if (nextProps.show && this.state.exited) {
|
|
this.setState({ exited: false });
|
|
}
|
|
}
|
|
|
|
componentWillUpdate(nextProps) {
|
|
if (!this.props.show && nextProps.show) {
|
|
this.checkForFocus();
|
|
}
|
|
}
|
|
|
|
componentDidUpdate(prevProps) {
|
|
if (!prevProps.show && this.props.show) {
|
|
this.handleShow();
|
|
}
|
|
// We are waiting for the onExited callback to call handleHide.
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
if (this.props.show || !this.state.exited) {
|
|
this.handleHide();
|
|
}
|
|
this.mounted = false;
|
|
}
|
|
|
|
checkForFocus() {
|
|
if (canUseDom) {
|
|
this.lastFocus = activeElement();
|
|
}
|
|
}
|
|
|
|
restoreLastFocus() {
|
|
if (this.lastFocus && this.lastFocus.focus) {
|
|
this.lastFocus.focus();
|
|
this.lastFocus = undefined;
|
|
}
|
|
}
|
|
|
|
handleShow() {
|
|
const doc = ownerDocument(ReactDOM.findDOMNode(this));
|
|
this.props.modalManager.add(this);
|
|
this.onDocumentKeyUpListener = addEventListener(doc, 'keyup', this.handleDocumentKeyUp);
|
|
this.onFocusListener = addEventListener(doc, 'focus', this.handleFocusListener, true);
|
|
this.focus();
|
|
}
|
|
|
|
focus() {
|
|
const currentFocus = activeElement(ownerDocument(ReactDOM.findDOMNode(this)));
|
|
const modalContent = this.modal && this.modal.lastChild;
|
|
const focusInModal = currentFocus && contains(modalContent, currentFocus);
|
|
|
|
if (modalContent && !focusInModal) {
|
|
if (!modalContent.hasAttribute('tabIndex')) {
|
|
modalContent.setAttribute('tabIndex', -1);
|
|
warning(false, 'Material-UI: the modal content node does not accept focus. ' + 'For the benefit of assistive technologies, ' + 'the tabIndex of the node is being set to "-1".');
|
|
}
|
|
|
|
modalContent.focus();
|
|
}
|
|
}
|
|
|
|
handleHide() {
|
|
this.props.modalManager.remove(this);
|
|
if (this.onDocumentKeyUpListener) this.onDocumentKeyUpListener.remove();
|
|
if (this.onFocusListener) this.onFocusListener.remove();
|
|
this.restoreLastFocus();
|
|
}
|
|
|
|
renderBackdrop(other = {}) {
|
|
const {
|
|
BackdropComponent,
|
|
BackdropClassName,
|
|
BackdropTransitionDuration,
|
|
BackdropInvisible,
|
|
show
|
|
} = this.props;
|
|
|
|
return React.createElement(
|
|
Fade,
|
|
_extends({ appear: true, 'in': show, timeout: BackdropTransitionDuration }, other),
|
|
React.createElement(BackdropComponent, {
|
|
invisible: BackdropInvisible,
|
|
className: BackdropClassName,
|
|
onClick: this.handleBackdropClick
|
|
})
|
|
);
|
|
}
|
|
|
|
render() {
|
|
const _props = this.props,
|
|
{
|
|
disableBackdrop,
|
|
BackdropComponent,
|
|
BackdropClassName,
|
|
BackdropTransitionDuration,
|
|
BackdropInvisible,
|
|
ignoreBackdropClick,
|
|
ignoreEscapeKeyUp,
|
|
children,
|
|
classes,
|
|
className,
|
|
keepMounted,
|
|
modalManager: modalManagerProp,
|
|
onBackdropClick,
|
|
onEscapeKeyUp,
|
|
onRequestClose,
|
|
onEnter,
|
|
onEntering,
|
|
onEntered,
|
|
onExit,
|
|
onExiting,
|
|
onExited,
|
|
show
|
|
} = _props,
|
|
other = _objectWithoutProperties(_props, ['disableBackdrop', 'BackdropComponent', 'BackdropClassName', 'BackdropTransitionDuration', 'BackdropInvisible', 'ignoreBackdropClick', 'ignoreEscapeKeyUp', 'children', 'classes', 'className', 'keepMounted', 'modalManager', 'onBackdropClick', 'onEscapeKeyUp', 'onRequestClose', 'onEnter', 'onEntering', 'onEntered', 'onExit', 'onExiting', 'onExited', 'show']);
|
|
|
|
if (!keepMounted && !show && this.state.exited) {
|
|
return null;
|
|
}
|
|
|
|
const transitionCallbacks = {
|
|
onEnter,
|
|
onEntering,
|
|
onEntered,
|
|
onExit,
|
|
onExiting,
|
|
onExited: this.handleTransitionExited
|
|
};
|
|
|
|
let modalChild = React.Children.only(children);
|
|
const { role, tabIndex } = modalChild.props;
|
|
const childProps = {};
|
|
|
|
if (role === undefined) {
|
|
childProps.role = role === undefined ? 'document' : role;
|
|
}
|
|
|
|
if (tabIndex === undefined) {
|
|
childProps.tabIndex = tabIndex == null ? -1 : tabIndex;
|
|
}
|
|
|
|
let backdropProps;
|
|
|
|
// It's a Transition like component
|
|
if (modalChild.props.hasOwnProperty('in')) {
|
|
Object.keys(transitionCallbacks).forEach(key => {
|
|
childProps[key] = createChainedFunction(transitionCallbacks[key], modalChild.props[key]);
|
|
});
|
|
} else {
|
|
backdropProps = transitionCallbacks;
|
|
}
|
|
|
|
if (Object.keys(childProps).length) {
|
|
modalChild = React.cloneElement(modalChild, childProps);
|
|
}
|
|
|
|
return React.createElement(
|
|
Portal,
|
|
{
|
|
open: true,
|
|
ref: node => {
|
|
this.mountNode = node ? node.getLayer() : null;
|
|
}
|
|
},
|
|
React.createElement(
|
|
'div',
|
|
_extends({
|
|
'data-mui-test': 'Modal',
|
|
className: classNames(classes.root, className, {
|
|
[classes.hidden]: this.state.exited
|
|
})
|
|
}, other, {
|
|
ref: node => {
|
|
this.modal = node;
|
|
}
|
|
}),
|
|
!disableBackdrop && (!keepMounted || show || !this.state.exited) && this.renderBackdrop(backdropProps),
|
|
modalChild
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
Modal.defaultProps = {
|
|
BackdropComponent: Backdrop,
|
|
BackdropTransitionDuration: 300,
|
|
BackdropInvisible: false,
|
|
keepMounted: false,
|
|
disableBackdrop: false,
|
|
ignoreBackdropClick: false,
|
|
ignoreEscapeKeyUp: false,
|
|
modalManager,
|
|
show: false
|
|
};
|
|
|
|
var _initialiseProps = function () {
|
|
this.state = {
|
|
exited: false
|
|
};
|
|
this.onDocumentKeyUpListener = null;
|
|
this.onFocusListener = null;
|
|
this.mounted = false;
|
|
this.lastFocus = undefined;
|
|
this.modal = null;
|
|
this.mountNode = null;
|
|
|
|
this.handleFocusListener = () => {
|
|
if (!this.mounted || !this.props.modalManager.isTopModal(this)) {
|
|
return;
|
|
}
|
|
|
|
const currentFocus = activeElement(ownerDocument(ReactDOM.findDOMNode(this)));
|
|
const modalContent = this.modal && this.modal.lastChild;
|
|
|
|
if (modalContent && modalContent !== currentFocus && !contains(modalContent, currentFocus)) {
|
|
modalContent.focus();
|
|
}
|
|
};
|
|
|
|
this.handleDocumentKeyUp = event => {
|
|
if (!this.mounted || !this.props.modalManager.isTopModal(this)) {
|
|
return;
|
|
}
|
|
|
|
if (keycode(event) !== 'esc') {
|
|
return;
|
|
}
|
|
|
|
const { onEscapeKeyUp, onRequestClose, ignoreEscapeKeyUp } = this.props;
|
|
|
|
if (onEscapeKeyUp) {
|
|
onEscapeKeyUp(event);
|
|
}
|
|
|
|
if (onRequestClose && !ignoreEscapeKeyUp) {
|
|
onRequestClose(event);
|
|
}
|
|
};
|
|
|
|
this.handleBackdropClick = event => {
|
|
if (event.target !== event.currentTarget) {
|
|
return;
|
|
}
|
|
|
|
const { onBackdropClick, onRequestClose, ignoreBackdropClick } = this.props;
|
|
|
|
if (onBackdropClick) {
|
|
onBackdropClick(event);
|
|
}
|
|
|
|
if (onRequestClose && !ignoreBackdropClick) {
|
|
onRequestClose(event);
|
|
}
|
|
};
|
|
|
|
this.handleTransitionExited = (...args) => {
|
|
if (this.props.onExited) {
|
|
this.props.onExited(...args);
|
|
}
|
|
|
|
this.setState({ exited: true });
|
|
this.handleHide();
|
|
};
|
|
};
|
|
|
|
export default withStyles(styles, { flip: false, name: 'MuiModal' })(Modal); |