Files
goTorrent/torrent-project/node_modules/material-ui/internal/Modal.js.flow

436 lines
10 KiB
Plaintext

// @flow
import React from 'react';
import type { Element, ElementType } 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';
import type { TransitionDuration, TransitionCallback } from '../internal/transition';
// 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: Object) => ({
root: {
display: 'flex',
width: '100%',
height: '100%',
position: 'fixed',
zIndex: theme.zIndex.dialog,
top: 0,
left: 0,
},
hidden: {
visibility: 'hidden',
},
});
type ProvidedProps = {
BackdropComponent: ElementType,
classes: Object,
modalManager: Object,
show: boolean,
};
export type Props = {
/**
* The CSS class name of the backdrop element.
*/
BackdropClassName?: string,
/**
* Pass a component class to use as the backdrop.
*/
BackdropComponent?: ElementType,
/**
* If `true`, the backdrop is invisible.
*/
BackdropInvisible?: boolean,
/**
* The duration for the backdrop transition, in milliseconds.
* You may specify a single timeout for all transitions, or individually with an object.
*/
BackdropTransitionDuration?: TransitionDuration,
/**
* A single child content element.
*/
children?: Element<any>,
/**
* Useful to extend the style applied to components.
*/
classes?: Object,
/**
* @ignore
*/
className?: string,
/**
* 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?: boolean,
/**
* If `true`, the backdrop is disabled.
*/
disableBackdrop?: boolean,
/**
* If `true`, clicking the backdrop will not fire the `onRequestClose` callback.
*/
ignoreBackdropClick?: boolean,
/**
* If `true`, hitting escape will not fire the `onRequestClose` callback.
*/
ignoreEscapeKeyUp?: boolean,
/**
* @ignore
*/
modalManager?: Object,
/**
* Callback fires when the backdrop is clicked on.
*/
onBackdropClick?: Function,
/**
* Callback fired before the modal is entering.
*/
onEnter?: TransitionCallback,
/**
* Callback fired when the modal is entering.
*/
onEntering?: TransitionCallback,
/**
* Callback fired when the modal has entered.
*/
onEntered?: TransitionCallback,
/**
* Callback fires when the escape key is pressed and the modal is in focus.
*/
onEscapeKeyUp?: Function,
/**
* Callback fired before the modal is exiting.
*/
onExit?: TransitionCallback,
/**
* Callback fired when the modal is exiting.
*/
onExiting?: TransitionCallback,
/**
* Callback fired when the modal has exited.
*/
onExited?: TransitionCallback,
/**
* Callback fired when the component requests to be closed.
*
* @param {object} event The event source of the callback
*/
onRequestClose?: Function,
/**
* If `true`, the Modal is visible.
*/
show?: boolean,
};
type State = {
exited: boolean,
};
/**
* @ignore - internal component.
*/
class Modal extends React.Component<ProvidedProps & Props, State> {
static defaultProps = {
BackdropComponent: Backdrop,
BackdropTransitionDuration: 300,
BackdropInvisible: false,
keepMounted: false,
disableBackdrop: false,
ignoreBackdropClick: false,
ignoreEscapeKeyUp: false,
modalManager,
show: false,
};
state = {
exited: false,
};
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;
}
onDocumentKeyUpListener = null;
onFocusListener = null;
mounted = false;
lastFocus = undefined;
modal = null;
mountNode = null;
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();
}
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();
}
};
handleDocumentKeyUp = (event: 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);
}
};
handleBackdropClick = (event: Event) => {
if (event.target !== event.currentTarget) {
return;
}
const { onBackdropClick, onRequestClose, ignoreBackdropClick } = this.props;
if (onBackdropClick) {
onBackdropClick(event);
}
if (onRequestClose && !ignoreBackdropClick) {
onRequestClose(event);
}
};
handleTransitionExited = (...args) => {
if (this.props.onExited) {
this.props.onExited(...args);
}
this.setState({ exited: true });
this.handleHide();
};
renderBackdrop(other: { [key: string]: any } = {}) {
const {
BackdropComponent,
BackdropClassName,
BackdropTransitionDuration,
BackdropInvisible,
show,
} = this.props;
return (
<Fade appear in={show} timeout={BackdropTransitionDuration} {...other}>
<BackdropComponent
invisible={BackdropInvisible}
className={BackdropClassName}
onClick={this.handleBackdropClick}
/>
</Fade>
);
}
render() {
const {
disableBackdrop,
BackdropComponent,
BackdropClassName,
BackdropTransitionDuration,
BackdropInvisible,
ignoreBackdropClick,
ignoreEscapeKeyUp,
children,
classes,
className,
keepMounted,
modalManager: modalManagerProp,
onBackdropClick,
onEscapeKeyUp,
onRequestClose,
onEnter,
onEntering,
onEntered,
onExit,
onExiting,
onExited,
show,
...other
} = this.props;
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 (
<Portal
open
ref={node => {
this.mountNode = node ? node.getLayer() : null;
}}
>
<div
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}
</div>
</Portal>
);
}
}
export default withStyles(styles, { flip: false, name: 'MuiModal' })(Modal);