Home Reference Source Repository

src/select/SeparatePopup.js

/**
 * @file melon/select/SeparatePopup
 * @author leon([email protected])
 */

import React, {Component, PropTypes} from 'react';
import {Motion, spring} from 'react-motion';
import domUtil from '../common/util/dom';

import {create} from 'melon-core/classname/cxBuilder';

const cx = create('SeparatePopup');

export default class SelectSeparatePopup extends Component {

    constructor(props) {

        super(props);

        // 做一个可以随时释放的 debounce 啦
        this.onWindowResize = (() => {

            let handler = this.onWindowResize.bind(this);

            return () => {
                clearTimeout(this.windowResizeTimer);
                this.windowResizeTimer = setTimeout(handler, 500);
            };

        })();

        this.id = Date.now();

        this.state = {
            styles: {
                top: 0,
                left: -5000,
                height: 0,
                opacity: 0,
                width: 0
            }
        };

        this.onClick = this.onClick.bind(this);
        this.onWindowResize = this.onWindowResize.bind(this);

    }

    componentDidMount() {
        domUtil.on(document.body, 'click', this.onClick);
    }

    componentWillReceiveProps(nextProps) {

        let open = nextProps.open;

        domUtil[open ? 'on' : 'off'](window, 'resize', this.onWindowResize);

        this.setState({
            ...this.state,
            styles: this.getStyle(open)
        });

    }

    componentWillUnmount() {
        domUtil.off(window, 'resize', this.onWindowResize);
        domUtil.off(window, 'click', this.onClick);
    }

    getStyle(open) {

        if (!open) {
            return {
                ...this.state.styles,
                height: 0,
                opacity: 0
            };
        }

        let target = this.props.target;
        let main = this.main;

        let targetPosition = domUtil.getPosition(target);

        let {
            top,
            left,
            width,
            height,
            overflow
        } = main.style;

        // 先把这个货弄走,展开计算它的宽度和高度
        main.style.top = '0';
        main.style.left = '-5000px';
        main.style.overflow = 'visible';
        main.style.height = 'auto';
        main.style.width = 'auto';

        let popupPosition = domUtil.getPosition(main);

        main.style.top = top;
        main.style.left = left;
        main.style.overflow = overflow;
        main.style.height = height;
        main.style.width = width;

        let scrollTop = domUtil.getScrollTop();
        let scrollLeft = domUtil.getScrollLeft();
        let clientWidth = domUtil.getClientWidth();
        let clientHeight = domUtil.getClientHeight();

        let rTop;
        let rLeft;

        // 首先计算垂直位置
        // 如垂直位置从目标的上边缘开始,如果加上浮层高度,未溢出视野,那么就取目标上边缘
        if (targetPosition.top + popupPosition.height < scrollTop + clientHeight) {
            rTop = targetPosition.top;
        }
        // 否则尽量贴在视野的底边上,如果此时浮层顶部溢出了视野,那说明视野就是太小了。。。
        else {
            rTop = clientHeight + scrollTop - popupPosition.height - 20;
        }

        // 然后计算水平位置
        // 尝试以目标的左边缘开始,如果加上浮层宽宽未溢出视野,那么取目标左边缘
        if (targetPosition.left + popupPosition.width < clientWidth + scrollLeft) {
            rLeft = targetPosition.left;
        }
        // 否则尽量贴在视野的右边,如果此时浮层左侧溢出视野,那说明视野就是太小了。
        else {
            rLeft = clientWidth + scrollLeft - popupPosition.width - 20;
        }


        return {
            opacity: 1,
            top: rTop,
            left: rLeft,
            height: popupPosition.height,
            width: Math.max(targetPosition.width, popupPosition.width)
        };

    }

    onClick(e) {

        let target = e.target;
        let main = this.main;
        let {onHide, open} = this.props;

        if (open && main !== target && !domUtil.contains(main, target)) {
            onHide && onHide();
        }

    }

    onWindowResize() {
        this.setState({
            ...this.state,
            styles: this.getStyle(true)
        });
    }

    render() {

        const children = this.props.children;

        const className = cx(this.props).build();
        const contentClassName = cx().part('content').build();

        const styles = this.state.styles;
        const {height, opacity} = styles;

        return (
            <Motion
                style={{
                    ...styles,
                    height: spring(height, {stiffness: 120, damping: 15}),
                    opacity: spring(opacity, {stiffness: 120, damping: 15})
                }}>
                {style =>
                    <div
                        className={className}
                        style={{
                            ...style,
                            visibility: style.opacity < 0.1 ? 'hidden' : 'visible'
                        }}
                        ref={main => {
                            if (main) {
                                this.main = main;
                            }
                        }}>
                        <div className={contentClassName} >
                            {children}
                        </div>
                    </div>
                }
            </Motion>
        );

    }

}

SelectSeparatePopup.displayName = 'SelectSeparatePopup';

SelectSeparatePopup.propTypes = {
    target: PropTypes.object.isRequired,
    onHide: PropTypes.func.isRequired
};