zent
Version:
一套前端设计语言和基于React的实现
152 lines (119 loc) • 3.92 kB
JavaScript
import React, { Component, PropTypes } from 'react';
import cx from 'zent-utils/classnames';
import Portal from 'zent-portal';
import WindowResizeHandler from 'zent-utils/lib/component/WindowResizeHandler';
import findPositionedParent from 'zent-utils/lib/dom/findPositionedParent';
import throttle from 'zent-utils/lodash/throttle';
import invisiblePlacement from './placement/invisible';
function translateToContainerCoordinates(containerBB, bb) {
const { left, top } = containerBB;
return {
width: bb.width,
height: bb.height,
top: bb.top - top,
left: bb.left - left,
bottom: bb.bottom - top,
right: bb.right - left
};
}
/**
* Like triggers, content can be replaced with your own implementation, all you have to do is extend this base class.
*
* The props on this class are all private.
*/
export default class PopoverContent extends Component {
static propTypes = {
children: PropTypes.node,
prefix: PropTypes.string,
id: PropTypes.string,
getContentNode: PropTypes.func,
visible: PropTypes.bool,
// placement strategy
placement: PropTypes.func,
cushion: PropTypes.number,
// A function that returns the anchor for this popover
// () => Node
getAnchor: PropTypes.func,
// defaults to body
containerSelector: PropTypes.string,
};
state = {
position: null
};
getAnchor() {
return this.props.getAnchor();
}
getPositionedParent() {
// findPositionedParent returns null on fail
if (this.positionedParent !== undefined) {
return this.positionedParent;
}
const { containerSelector } = this.props;
const container = document.querySelector(containerSelector);
const parent = findPositionedParent(container, true);
this.positionedParent = parent;
return parent;
}
adjustPosition = () => {
const content = this.props.getContentNode();
// 可能还未渲染出来,先放到一个不可见的位置
if (!content) {
this.setState({
position: invisiblePlacement(this.props.prefix)
});
setTimeout(this.adjustPosition, 0);
return;
}
const anchor = this.getAnchor();
const boundingBox = anchor.getBoundingClientRect();
const parent = this.getPositionedParent();
const parentBoundingBox = parent.getBoundingClientRect();
const contentBoundingBox = content.getBoundingClientRect();
const relativeBB = translateToContainerCoordinates(parentBoundingBox, boundingBox);
const relativeContainerBB = translateToContainerCoordinates(parentBoundingBox, parentBoundingBox);
const position = this.props.placement(this.props.prefix, relativeBB, relativeContainerBB, {
width: contentBoundingBox.width,
height: contentBoundingBox.height
}, { cushion: this.props.cushion });
this.setState({
position
});
};
onWindowResize = throttle((evt, delta) => {
if (this.props.visible && (delta.deltaX !== 0 || delta.deltaY !== 0)) {
this.adjustPosition();
}
}, 16);
componentDidMount() {
const { visible } = this.props;
if (visible) {
this.adjustPosition();
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.visible && nextProps.visible !== this.props.visible) {
this.adjustPosition();
}
}
render() {
const { prefix, className, id, visible, children, containerSelector } = this.props;
const { position } = this.state;
if (!position) {
return null;
}
const cls = cx(
className,
`${prefix}-popover`,
id,
position.toString()
);
return (
<Portal prefix={prefix} visible={visible} selector={containerSelector} className={cls} css={position.getCSSStyle()}>
<div className={`${prefix}-popover-content`}>
{children}
<WindowResizeHandler onResize={this.onWindowResize} />
</div>
</Portal>
);
}
}