import React, {Component} from 'react'; import {findDOMNode} from 'react-dom'; import {observer} from 'mobx-react'; import noop from '../rc-util/noop'; import KeyCode from '../rc-util/KeyCode'; import Animate from '../rc-animate/Animate'; import LazyRenderBox from './LazyRenderBox'; import getScrollBarSize from '../rc-util/getScrollBarSize'; import IDialogPropTypes from './IDialogPropTypes'; import * as assign from 'object-assign'; let uuid = 0; let openCount = 0; function getScroll(w, top?: boolean) { let ret = w[`page${top ? 'Y' : 'X'}Offset`]; const method = `scroll${top ? 'Top' : 'Left'}`; if (typeof ret !== 'number') { const d = w.document; ret = d.documentElement[method]; if (typeof ret !== 'number') { ret = d.body[method]; } } return ret; } function setTransformOrigin(node, value) { const style = node.style; ['Webkit', 'Moz', 'Ms', 'ms'].forEach((prefix) => { style[`${prefix}TransformOrigin`] = value; }); style[`transformOrigin`] = value; } function offset(el) { const rect = el.getBoundingClientRect(); const pos = { left: rect.left, top: rect.top, }; const doc = el.ownerDocument; const w = doc.defaultView || doc.parentWindow; pos.left += getScroll(w); pos.top += getScroll(w, true); return pos; } @observer export default class Dialog extends Component { static defaultProps = { afterClose: noop, className: '', mask: true, visible: false, keyboard: true, closable: true, maskClosable: true, prefixCls: 'rc-dialog', onClose: noop }; inTransition; titleId; openTime; lastOutSideFocusNode; bodyIsOverflowing; scrollbarWidth; refs; componentWillMount() { this.inTransition = false; this.titleId = `rcDialogTitle${uuid++}`; } componentDidMount() { this.componentDidUpdate({}); } componentDidUpdate(prevProps) { const props = this.props; const mousePosition = this.props.mousePosition; if (props.visible) { // first show if (!prevProps.visible) { this.openTime = Date.now(); this.lastOutSideFocusNode = document.activeElement; this.addScrollingEffect(); this.refs.wrap.focus(); const dialogNode = findDOMNode(this.refs.dialog); if (mousePosition) { const elOffset = offset(dialogNode); setTransformOrigin(dialogNode, `${mousePosition.x - elOffset.left}px ${mousePosition.y - elOffset.top}px`); } else { setTransformOrigin(dialogNode, ''); } } } else if (prevProps.visible) { this.inTransition = true; if (props.mask && this.lastOutSideFocusNode) { try { this.lastOutSideFocusNode.focus(); } catch (e) { this.lastOutSideFocusNode = null; } this.lastOutSideFocusNode = null; } } } componentWillUnmount() { if (this.props.visible || this.inTransition) { this.removeScrollingEffect(); } } onAnimateLeave() { // need demo? // https://github.com/react-component/dialog/pull/28 if (this.refs.wrap) { this.refs.wrap.style.display = 'none'; } this.inTransition = false; this.removeScrollingEffect(); this.props.afterClose(); } onMaskClick(e) { // android trigger click on open (fastclick??) if (Date.now() - this.openTime < 300) { return; } if (e.target === e.currentTarget) { this.close(e); } } onKeyDown(e) { const props = this.props; if (props.keyboard && e.keyCode === KeyCode.ESC) { this.close(e); } // keep focus inside dialog if (props.visible) { if (e.keyCode === KeyCode.TAB) { const activeElement = document.activeElement; const dialogRoot = this.refs.wrap; const sentinel = this.refs.sentinel; if (e.shiftKey) { if (activeElement === dialogRoot) { sentinel.focus(); } } else if (activeElement === this.refs.sentinel) { dialogRoot.focus(); } } } } getDialogElement() { const props = this.props; const closable = props.closable; const prefixCls = props.prefixCls; const dest: any = {}; if (props.width !== undefined) { dest.width = props.width; } if (props.height !== undefined) { dest.height = props.height; } let footer; if (props.footer) { footer = (
{props.footer}
); } let header; if (props.title) { header = (
{props.title}
); } let closer; if (closable) { closer = ( ); } const style = assign({}, props.style, dest); const transitionName = this.getTransitionName(); const dialogElement = (
{closer} {header}
{props.children}
{footer}
sentinel
); return ( {dialogElement} ); } getZIndexStyle() { const style: any = {}; const props = this.props; if (props.zIndex !== undefined) { style.zIndex = props.zIndex; } return style; } getWrapStyle(): any { return assign({}, this.getZIndexStyle(), this.props.wrapStyle); } getMaskStyle() { return assign({}, this.getZIndexStyle(), this.props.maskStyle); } getMaskElement() { const props = this.props; let maskElement; if (props.mask) { const maskTransition = this.getMaskTransitionName(); maskElement = ( ); if (maskTransition) { maskElement = ( {maskElement} ); } } return maskElement; } getMaskTransitionName() { const props = this.props; let transitionName = props.maskTransitionName; const animation = props.maskAnimation; if (!transitionName && animation) { transitionName = `${props.prefixCls}-${animation}`; } return transitionName; } getTransitionName() { const props = this.props; let transitionName = props.transitionName; const animation = props.animation; if (!transitionName && animation) { transitionName = `${props.prefixCls}-${animation}`; } return transitionName; } getElement(part) { return this.refs[part]; } setScrollbar() { if (this.bodyIsOverflowing && this.scrollbarWidth !== undefined) { document.body.style.paddingRight = `${this.scrollbarWidth}px`; } } addScrollingEffect() { openCount++; if (openCount !== 1) { return; } this.checkScrollbar(); this.setScrollbar(); document.body.style.overflow = 'hidden'; // this.adjustDialog(); } removeScrollingEffect() { openCount--; if (openCount !== 0) { return; } document.body.style.overflow = ''; this.resetScrollbar(); // this.resetAdjustments(); } close(e) { this.props.onClose(e); } checkScrollbar() { let fullWindowWidth = window.innerWidth; if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 const documentElementRect = document.documentElement.getBoundingClientRect(); fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left); } this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth; if (this.bodyIsOverflowing) { this.scrollbarWidth = getScrollBarSize(); } } resetScrollbar() { document.body.style.paddingRight = ''; } adjustDialog() { if (this.refs.wrap && this.scrollbarWidth !== undefined) { const modalIsOverflowing = this.refs.wrap.scrollHeight > document.documentElement.clientHeight; this.refs.wrap.style.paddingLeft = `${!this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : ''}px`; this.refs.wrap.style.paddingRight = `${this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''}px`; } } resetAdjustments() { if (this.refs.wrap) { this.refs.wrap.style.paddingLeft = this.refs.wrap.style.paddingLeft = ''; } } render() { const {props} = this; const {prefixCls, maskClosable} = props; const style = this.getWrapStyle(); // clear hide display // and only set display after async anim, not here for hide if (props.visible) { style.display = null; } return (
{this.getMaskElement()}
{this.getDialogElement()}
); } }