UNPKG

zarm-web

Version:
351 lines (302 loc) 8.27 kB
import React, { Component } from 'react'; import { createPortal, unmountComponentAtNode } from 'react-dom'; import classnames from 'classnames'; import Events from '../utils/events'; import domUtil from '../utils/dom'; const { getSupportedPropertyName } = domUtil; let animationDurationKey = getSupportedPropertyName('animationDuration') || 'animationDuration'; if (animationDurationKey && animationDurationKey !== 'animationDuration' && !animationDurationKey.startsWith('ms')) { animationDurationKey = animationDurationKey.charAt(0).toUpperCase() + animationDurationKey.slice(1); } function toggleBodyOverflow(show) { const scrollBarWidth = window.innerWidth - document.documentElement.offsetWidth; if (show === true) { document.body.classList.add('ui-modal-body-overflow'); if (scrollBarWidth > 0) { document.body.style.setProperty('padding-right', `${scrollBarWidth}px`); } } else { document.body.classList.remove('ui-modal-body-overflow'); document.body.style.setProperty('padding-right', null); } } class Modal extends Component { static handleVisbibleList(instance, visible, noAnimation) { if (visible) { const lastIndex = Modal.visibleList.length - 1; if (lastIndex >= 0) { Modal.visibleList[lastIndex].sleep = true; if (noAnimation) { Modal.visibleList[lastIndex].setState({ isPending: true, isShow: false }); } else { Modal.visibleList[lastIndex].leave(); } } Modal.visibleList.push(instance); } else { Modal.visibleList.pop(); let index = Modal.visibleList.length; if (index > 0) { const modal = Modal.visibleList[index - 1]; const currentVisible = modal.props.visible; if (currentVisible) { modal.enter(); modal.sleep = false; } } // eslint-disable-next-line no-plusplus while (index--) { const modal = Modal.visibleList[index]; const currentVisible = modal.props.visible; if (!currentVisible) { modal.sleep = false; Modal.visibleList.splice(index, 1); } } } } static unmountModalInstance(instance, callback) { const instanceIndex = Modal.instanceList.findIndex(item => item === instance); if (instanceIndex >= 0) { Modal.instanceList.splice(instanceIndex, 1); } if (Modal.instanceList.length === 0) { callback(); } } constructor(props) { super(props); this.sleep = false; this.modal = void 0; this.div = document.createElement('div'); this.modalContent = void 0; this.appended = false; this.animationEnd = () => { if (this.state.animationState === 'leave') { this.setState({ isShow: false, isPending: false }); } else { this.setState({ isShow: true, isPending: false }); } }; this.onKeyDown = e => { if (this.state.isShow && this.state.animationState !== 'leave') { if (e.keyCode === 27) { React.Children.forEach(this.props.children, elem => { if (elem && typeof elem !== 'string' && typeof elem !== 'number') { if (elem.props.onClose) { elem.props.onClose(); } } }); } } }; this.onKeyPress = e => { if (document.activeElement === this.modalContent) { if (this.state.isShow && this.state.animationState !== 'leave') { if (this.props.onKeyPress) { this.props.onKeyPress(e.nativeEvent); } } } }; this.onMaskClick = e => e.stopPropagation(); this.getModalRef = ele => { if (ele) { this.modal = ele; } }; this.modalContentRef = elem => { this.modalContent = elem; }; this.state = { isShow: false, isPending: false, animationState: 'leave' }; Modal.instanceList.push(this); } componentDidMount() { const { visible } = this.props; if (this.sleep === true) { return; } if (visible) { if (!this.appended) { document.body.appendChild(this.div); this.appended = true; } this.enter(); Modal.handleVisbibleList(this, true, true); } if (this.modal) { Events.on(this.modal, 'webkitAnimationEnd', this.animationEnd); Events.on(this.modal, 'animationend', this.animationEnd); } } componentWillReceiveProps(nextProps) { const { visible } = this.props; if (this.sleep === true) { return; } if (!visible && nextProps.visible) { if (!this.appended) { document.body.appendChild(this.div); this.appended = true; } Modal.visibleList.forEach(item => { item.setState({ isShow: false }); }); this.enter(); Modal.handleVisbibleList(this, true); } else if (visible && !nextProps.visible) { Modal.handleVisbibleList(this, false); this.leave(); } } componentDidUpdate() { const { isShow } = this.state; if (this.modalContent) { if (isShow) { this.modalContent.focus(); } else { this.modalContent.blur(); } } } componentWillUnmount() { Events.off(this.modal, 'webkitAnimationEnd', this.animationEnd); Events.off(this.modal, 'animationend', this.animationEnd); Modal.unmountModalInstance(this, () => { toggleBodyOverflow(false); }); setTimeout(() => { unmountComponentAtNode(this.div); const { parentNode } = this.div; if (parentNode) { // 对已插入document的节点进行删除 document.body.removeChild(this.div); } }); } enter() { if (Modal.visibleList.length === 0) { toggleBodyOverflow(true); } this.setState({ isShow: true, isPending: true, animationState: 'enter' }); } leave() { this.setState({ isShow: true, isPending: true, animationState: 'leave' }); if (Modal.visibleList.length === 0) { toggleBodyOverflow(false); } } render() { const { prefixCls, animationType, animationDuration, width, minWidth, isRadius, isRound, className, onMaskClick, children } = this.props; const { isShow, isPending, animationState } = this.state; const classes = { modal: classnames({ [prefixCls]: true, radius: 'radius' in this.props || isRadius, round: 'round' in this.props || isRound, [`fade-${animationState}`]: isPending, [className]: !!className }), dialog: classnames({ [`${prefixCls}-dialog`]: true, [`${animationType}-${animationState}`]: true }) }; const style = { modal: { [animationDurationKey]: `${animationDuration}ms`, position: 'fixed' }, dialog: { width: Number(width), minWidth: Number(minWidth), [animationDurationKey]: `${animationDuration}ms` } }; if (!isShow) { style.modal.display = 'none'; } return createPortal(React.createElement("div", { className: classes.modal, style: style.modal, onClick: onMaskClick, ref: this.getModalRef }, React.createElement("div", { className: `${prefixCls}-wrapper` }, React.createElement("div", { ref: this.modalContentRef, tabIndex: -1, className: classes.dialog, style: style.dialog, onClick: this.onMaskClick, onKeyDown: this.onKeyDown, onKeyPress: this.onKeyPress }, children))), this.div); } } // tslint:disable-next-line:no-namespace // eslint-disable-next-line no-redeclare Modal.Header = void 0; Modal.Body = void 0; Modal.Footer = void 0; Modal.defaultProps = { prefixCls: 'ui-modal', visible: false, animationType: 'zoom', animationDuration: 300, width: 600, minWidth: 270, isRadius: false, isRound: false, onMaskClick() {} }; Modal.instanceList = []; Modal.visibleList = []; export default Modal;