chowa
Version:
UI component library based on React
327 lines (326 loc) • 11.8 kB
JavaScript
/**
* @license chowa v1.1.3
*
* Copyright (c) Chowa Techonlogies Co.,Ltd.(http://www.chowa.cn).
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const React = require("react");
const ReactDom = require("react-dom");
const PropTypes = require("prop-types");
const utils_1 = require("../utils");
const transition_1 = require("../transition");
const overlay_trigger_1 = require("./overlay-trigger");
class Overlay extends React.PureComponent {
constructor(props) {
super(props);
this.timer = null;
const selfVisible = props.visible || props.defaultVisible;
this.state = {
selfVisible,
zIndex: selfVisible ? Overlay.getZIndex() : 1,
top: 0,
left: 0,
triggerWidth: 0
};
this.overlayMountElement = document.createElement('section');
utils_1.doms.attr(this.overlayMountElement, 'role', props.role);
utils_1.doms.attr(this.overlayMountElement, 'class', utils_1.preClass('overlay'));
[
'onVisibleChangeHandler',
'updatePosition',
'onExternalWheelHandler',
'setTriggerElement',
'onOverlayMouseLeaveHandler',
'onOverlayMouseEnterHandler',
'onOverlayMouseDownHandler',
'externalMouseDownListener'
].forEach((fn) => {
this[fn] = this[fn].bind(this);
});
}
componentDidMount() {
document.body.appendChild(this.overlayMountElement);
if (this.state.selfVisible) {
this.bindListeners();
}
}
componentDidUpdate(preProps) {
if (preProps.visible !== this.props.visible && this.props.visible !== this.state.selfVisible) {
this.onVisibleChangeHandler(this.props.visible);
}
}
componentWillUnmount() {
if (this.state.selfVisible) {
this.setVisible(false);
this.clearTimer();
}
document.body.removeChild(this.overlayMountElement);
}
setVisible(v) {
let newState = {
selfVisible: v
};
if (v) {
newState = Object.assign(newState, {
zIndex: Overlay.getZIndex()
});
this.bindListeners();
}
else {
this.unbindListeners();
if (this.props.action === 'focus') {
this.triggerElement.blur();
}
}
this.setState(newState, () => {
if (this.props.onVisibleChange) {
this.props.onVisibleChange(v);
}
});
}
bindListeners() {
utils_1.doms.on(window, 'resize', this.updatePosition);
if (this.props.externalWheelHide) {
utils_1.doms.on(document, 'wheel', this.onExternalWheelHandler);
}
}
unbindListeners() {
utils_1.doms.off(window, 'resize', this.updatePosition);
if (this.props.externalWheelHide) {
utils_1.doms.off(document, 'wheel', this.onExternalWheelHandler);
}
}
onVisibleChangeHandler(v) {
const { action, delay } = this.props;
if (action === 'hover' && delay > 0) {
this.clearTimer();
this.timer = window.setTimeout(() => {
this.setVisible(v);
}, delay);
}
else {
this.setVisible(v);
}
}
clearTimer() {
if (this.props.delay > 0 && this.timer !== null) {
clearTimeout(this.timer);
this.timer = null;
}
}
onOverlayMouseLeaveHandler(e) {
this.onVisibleChangeHandler(false);
if (this.props.onMouseLeave) {
this.props.onMouseLeave(e);
}
}
onOverlayMouseEnterHandler(e) {
this.clearTimer();
if (this.props.onMouseEnter) {
this.props.onMouseEnter(e);
}
}
externalMouseDownListener(e) {
if (this.triggerElement
&& (this.triggerElement.contains(e.target)
|| this.triggerElement.isEqualNode(e.target))) {
return;
}
if (!this.overlayElenment.contains(e.target)) {
this.setVisible(false);
}
}
setTriggerElement(node) {
this.triggerElement = node;
}
onExternalWheelHandler(e) {
if (this.overlayElenment.contains(e.target)) {
return;
}
this.onVisibleChangeHandler(false);
}
updatePosition() {
if (!this.state.selfVisible || !utils_1.isExist(this.triggerElement)) {
return;
}
const { width: triggerWidth, height: triggerHeight, top: triggerTop, left: triggerLeft } = utils_1.doms.offset(this.triggerElement);
const { fixSpace, placement, offsetX, offsetY } = this.props;
const { width, height } = utils_1.doms.rect(this.overlayElenment);
const { width: docWidth, height: docHeight } = utils_1.doms.pageOffset();
let left = 0;
let top = 0;
switch (placement) {
case 'top':
left = triggerLeft + offsetX - (width - triggerWidth) / 2;
top = triggerTop + offsetY - height - fixSpace;
break;
case 'top-left':
left = triggerLeft + offsetX;
top = triggerTop + offsetY - height - fixSpace;
break;
case 'top-right':
left = triggerLeft + offsetX - (width - triggerWidth);
top = triggerTop + offsetY - height - fixSpace;
break;
case 'bottom':
left = triggerLeft + offsetX - (width - triggerWidth) / 2;
top = triggerTop + offsetY + triggerHeight + fixSpace;
break;
case 'bottom-left':
left = triggerLeft + offsetX;
top = triggerTop + offsetY + triggerHeight + fixSpace;
break;
case 'bottom-right':
left = triggerLeft + offsetX - (width - triggerWidth);
top = triggerTop + offsetY + triggerHeight + fixSpace;
break;
case 'left':
left = triggerLeft + offsetX - width - fixSpace;
top = triggerTop + offsetY - (height - triggerHeight) / 2;
break;
case 'left-top':
left = triggerLeft + offsetX - width - fixSpace;
top = triggerTop + offsetY;
break;
case 'left-bottom':
left = triggerLeft + offsetX - width - fixSpace;
top = triggerTop + offsetY - (height - triggerHeight);
break;
case 'right':
left = triggerLeft + offsetX + triggerWidth + fixSpace;
top = triggerTop + offsetY - (height - triggerHeight) / 2;
break;
case 'right-top':
left = triggerLeft + offsetX + triggerWidth + fixSpace;
top = triggerTop + offsetY;
break;
case 'right-bottom':
left = triggerLeft + offsetX + triggerWidth + fixSpace;
top = triggerTop + offsetY - (height - triggerHeight);
break;
}
if (width + left > docWidth) {
left = docWidth - width;
}
if (height + top > docHeight) {
top = docHeight - height;
}
if (top < 0) {
top = 0;
}
if (left < 0) {
left = 0;
}
this.setState({
top,
left,
triggerWidth
});
}
onOverlayMouseDownHandler(e) {
e.preventDefault();
utils_1.stopReactPropagation(e);
if (this.props.onMouseDown) {
this.props.onMouseDown(e);
}
}
renderOverlay() {
const { selfVisible, top, left, zIndex, triggerWidth } = this.state;
const { children, style, enter, appear, leave, className, trigger, action, matchTriggerWidth, onShow, onHide, onEnter, onLeave } = this.props;
let overlayInnerStyle = Object.assign({ zIndex }, style);
if (trigger) {
overlayInnerStyle = Object.assign(overlayInnerStyle, {
position: 'absolute',
top,
left
});
}
if (matchTriggerWidth && triggerWidth > 0) {
overlayInnerStyle = Object.assign(overlayInnerStyle, { minWidth: triggerWidth });
}
const onEnterHandler = () => {
if (onEnter) {
onEnter();
}
this.updatePosition();
};
return (React.createElement(transition_1.default, { onEnter: onEnterHandler, onShow: onShow, onHide: onHide, onLeave: onLeave, visible: selfVisible, enter: enter, appear: appear, leave: leave },
React.createElement("div", Object.assign({}, utils_1.otherProps(Overlay.propTypes, this.props), { className: className, ref: (ele) => {
this.overlayElenment = ele;
}, onMouseDown: action === 'focus' ? this.onOverlayMouseDownHandler : null, onMouseLeave: action === 'hover' ? this.onOverlayMouseLeaveHandler : null, onMouseEnter: action === 'hover' ? this.onOverlayMouseEnterHandler : null, style: overlayInnerStyle }), children)));
}
render() {
const { trigger, action, disabled } = this.props;
const { selfVisible } = this.state;
const fragments = [];
if (trigger) {
fragments.push(React.createElement(overlay_trigger_1.default, { key: 'overlay-trigger', action: action, updateDropPosition: this.updatePosition, onVisibleChange: this.onVisibleChangeHandler, visible: selfVisible, setTriggerElement: this.setTriggerElement, externalMouseDownListener: this.externalMouseDownListener, disabled: disabled }, trigger));
}
return fragments.concat(ReactDom.createPortal(this.renderOverlay(), this.overlayMountElement));
}
}
Overlay.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
trigger: PropTypes.node,
disabled: PropTypes.bool,
externalWheelHide: PropTypes.bool,
role: PropTypes.string,
delay: PropTypes.number,
action: PropTypes.oneOf(['click', 'hover', 'focus', 'contextMenu']),
enter: PropTypes.string.isRequired,
appear: PropTypes.string.isRequired,
leave: PropTypes.string.isRequired,
defaultVisible: PropTypes.bool,
visible: PropTypes.bool,
onVisibleChange: PropTypes.func,
placement: PropTypes.oneOf([
'none',
'top',
'left',
'bottom',
'right',
'left-top',
'left-bottom',
'right-top',
'right-bottom',
'top-left',
'top-right',
'bottom-left',
'bottom-right'
]),
fixSpace: PropTypes.number,
offsetX: PropTypes.number,
offsetY: PropTypes.number,
matchTriggerWidth: PropTypes.bool,
onShow: PropTypes.func,
onHide: PropTypes.func,
onEnter: PropTypes.func,
onLeave: PropTypes.func
};
Overlay.defaultProps = {
disabled: false,
externalWheelHide: false,
role: 'overlay',
delay: 150,
action: 'click',
enter: transition_1.default.defaultProps.enter,
appear: transition_1.default.defaultProps.appear,
leave: transition_1.default.defaultProps.leave,
defaultVisible: false,
visible: false,
fixSpace: 2,
size: 'default',
offsetX: 0,
offsetY: 0,
matchTriggerWidth: false
};
Overlay.zIndex = 1000;
Overlay.getZIndex = () => {
return Overlay.zIndex++;
};
exports.default = Overlay;