UNPKG

chowa

Version:

UI component library based on React

327 lines (326 loc) 11.8 kB
/** * @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;