Files
goTorrent/goTorrentWebUI/node_modules/material-ui/Modal/Modal.js.flow

405 lines
11 KiB
Plaintext

// @inheritedComponent Portal
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import warning from 'warning';
import keycode from 'keycode';
import activeElement from 'dom-helpers/activeElement';
import contains from 'dom-helpers/query/contains';
import inDOM from 'dom-helpers/util/inDOM';
import ownerDocument from 'dom-helpers/ownerDocument';
import RefHolder from '../internal/RefHolder';
import Portal from '../Portal';
import addEventListener from '../utils/addEventListener';
import { createChainedFunction } from '../utils/helpers';
import withStyles from '../styles/withStyles';
import ModalManager from './ModalManager';
import Backdrop from './Backdrop';
function getContainer(container, defaultContainer) {
container = typeof container === 'function' ? container() : container;
return ReactDOM.findDOMNode(container) || defaultContainer;
}
function getHasTransition(props) {
return props.children ? props.children.props.hasOwnProperty('in') : false;
}
export const styles = theme => ({
root: {
display: 'flex',
width: '100%',
height: '100%',
position: 'fixed',
zIndex: theme.zIndex.modal,
top: 0,
left: 0,
},
hidden: {
visibility: 'hidden',
},
});
class Modal extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
exited: !this.props.open,
};
}
componentDidMount() {
this.mounted = true;
if (this.props.open) {
this.handleOpen();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.open) {
this.setState({ exited: false });
} else if (!getHasTransition(nextProps)) {
// Otherwise let handleExited take care of marking exited.
this.setState({ exited: true });
}
}
componentWillUpdate(nextProps) {
if (!this.props.open && nextProps.open) {
this.checkForFocus();
}
}
componentDidUpdate(prevProps) {
if (prevProps.open && !this.props.open && !getHasTransition(this.props)) {
// Otherwise handleExited will call this.
this.handleClose();
} else if (!prevProps.open && this.props.open) {
this.handleOpen();
}
}
componentWillUnmount() {
this.mounted = false;
if (this.props.open || (getHasTransition(this.props) && !this.state.exited)) {
this.handleClose();
}
}
getDialogElement() {
return ReactDOM.findDOMNode(this.dialog);
}
dialog = null;
mounted = false;
mountNode = null;
handleRendered = () => {
this.autoFocus();
if (this.props.onRendered) {
this.props.onRendered();
}
};
handleOpen = () => {
const doc = ownerDocument(this.mountNode);
const container = getContainer(this.props.container, doc.body);
this.props.manager.add(this, container);
this.onDocumentKeydownListener = addEventListener(doc, 'keydown', this.handleDocumentKeyDown);
this.onFocusinListener = addEventListener(doc, 'focus', this.enforceFocus, true);
};
handleClose = () => {
this.props.manager.remove(this);
this.onDocumentKeydownListener.remove();
this.onFocusinListener.remove();
this.restoreLastFocus();
};
handleExited = () => {
this.setState({ exited: true });
this.handleClose();
};
handleBackdropClick = 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');
}
};
handleDocumentKeyDown = event => {
if (!this.isTopModal() || keycode(event) !== 'esc') {
return;
}
if (this.props.onEscapeKeyDown) {
this.props.onEscapeKeyDown(event);
}
if (!this.props.disableEscapeKeyDown && this.props.onClose) {
this.props.onClose(event, 'escapeKeyDown');
}
};
checkForFocus = () => {
if (inDOM) {
this.lastFocus = activeElement();
}
};
autoFocus() {
if (this.props.disableAutoFocus) {
return;
}
const dialogElement = this.getDialogElement();
const currentActiveElement = activeElement(ownerDocument(this.mountNode));
if (dialogElement && !contains(dialogElement, currentActiveElement)) {
this.lastFocus = currentActiveElement;
if (!dialogElement.hasAttribute('tabIndex')) {
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".',
].join('\n'),
);
dialogElement.setAttribute('tabIndex', -1);
}
dialogElement.focus();
}
}
restoreLastFocus() {
if (this.props.disableRestoreFocus) {
return;
}
if (this.lastFocus) {
this.lastFocus.focus();
this.lastFocus = null;
}
}
enforceFocus = () => {
if (this.props.disableEnforceFocus || !this.mounted || !this.isTopModal()) {
return;
}
const dialogElement = this.getDialogElement();
const currentActiveElement = activeElement(ownerDocument(this.mountNode));
if (dialogElement && !contains(dialogElement, currentActiveElement)) {
dialogElement.focus();
}
};
isTopModal() {
return this.props.manager.isTopModal(this);
}
render() {
const {
BackdropComponent,
BackdropProps,
children,
classes,
className,
container,
disableAutoFocus,
disableBackdropClick,
disableEnforceFocus,
disableEscapeKeyDown,
disableRestoreFocus,
hideBackdrop,
keepMounted,
onBackdropClick,
onClose,
onEscapeKeyDown,
onRendered,
open,
manager,
...other
} = this.props;
const { exited } = this.state;
const hasTransition = getHasTransition(this.props);
const childProps = {};
if (!keepMounted && !open && (!hasTransition || exited)) {
return null;
}
// It's a Transition like component
if (hasTransition) {
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';
}
return (
<Portal
ref={node => {
this.mountNode = node ? node.getMountNode() : node;
}}
container={container}
onRendered={this.handleRendered}
>
<div
data-mui-test="Modal"
className={classNames(classes.root, className, {
[classes.hidden]: exited,
})}
{...other}
>
{hideBackdrop ? null : (
<BackdropComponent open={open} onClick={this.handleBackdropClick} {...BackdropProps} />
)}
<RefHolder
ref={node => {
this.dialog = node;
}}
>
{React.cloneElement(children, childProps)}
</RefHolder>
</div>
</Portal>
);
}
}
Modal.propTypes = {
/**
* A backdrop component. Useful for custom backdrop rendering.
*/
BackdropComponent: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
/**
* Properties applied to the `Backdrop` element.
*/
BackdropProps: PropTypes.object,
/**
* A single child content element.
*/
children: PropTypes.element,
/**
* Useful to extend the style applied to components.
*/
classes: PropTypes.object.isRequired,
/**
* @ignore
*/
className: PropTypes.string,
/**
* 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,
/**
* 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,
/**
* 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,
/**
* A modal manager used to track and manage the state of open
* Modals. Useful when 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,
};
Modal.defaultProps = {
disableAutoFocus: false,
disableBackdropClick: false,
disableEnforceFocus: false,
disableEscapeKeyDown: 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(),
BackdropComponent: Backdrop,
};
export default withStyles(styles, { flip: false, name: 'MuiModal' })(Modal);