zarm-web
Version:
基于 React 的桌面端UI库
351 lines (302 loc) • 8.27 kB
JavaScript
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;