chowa
Version:
UI component library based on React
192 lines (191 loc) • 6.97 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 resize_observer_polyfill_1 = require("resize-observer-polyfill");
const classNames = require("classnames");
const utils_1 = require("../utils");
class Affix extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
contentTop: 0,
contentLeft: 0,
contentWidth: 0,
contentHeight: 0,
targetTop: 0,
targetHeight: 0,
targetLeft: 0,
affixTop: 0,
affixBottom: 0,
affixLeft: 0,
position: 'static',
affixed: false,
isBlock: false,
isWindow: true
};
[
'updatePosition',
'onScrollHandler',
'onResizeHandler'
].forEach((fn) => {
this[fn] = this[fn].bind(this);
});
}
componentDidMount() {
const affixwrapperNode = ReactDom.findDOMNode(this);
this.node = affixwrapperNode.firstElementChild;
const { target } = this.props;
const listeners = () => {
const targetEle = target();
utils_1.doms.on(targetEle, 'scroll', this.onScrollHandler);
utils_1.doms.on(window, 'resize', this.onResizeHandler);
this.resizeObserver = new resize_observer_polyfill_1.default(this.onResizeHandler);
this.resizeObserver.observe(targetEle === window ? document.body : targetEle);
};
if (!target()) {
setTimeout(listeners, 200);
}
else {
listeners();
}
}
componentWillUnmount() {
const targetEle = this.props.target();
utils_1.doms.off(targetEle, 'scroll', this.onScrollHandler);
utils_1.doms.off(window, 'resize', this.onResizeHandler);
this.resizeObserver.unobserve(targetEle === window ? document.body : targetEle);
this.resizeObserver.disconnect();
}
componentDidUpdate(preProps) {
if (preProps.offsetTop !== this.props.offsetTop || preProps.offsetBottom !== this.props.offsetBottom) {
this.updatePosition();
}
}
onScrollHandler() {
this.updatePosition();
if (this.props.onTargetScroll) {
this.props.onTargetScroll();
}
}
onResizeHandler() {
this.setState({
affixed: false
}, () => {
const { top, left, width, height } = utils_1.doms.offset(this.node);
const position = utils_1.doms.css(this.node, 'position');
const display = utils_1.doms.css(this.node, 'display');
const isWindow = this.props.target() === window;
let targetTop = 0;
let targetLeft = 0;
let targetHeight = window.innerHeight;
if (!isWindow) {
const { top: realTop, height: realHeight, left: realLeft } = utils_1.doms.offset(this.props.target());
targetTop = realTop;
targetHeight = realHeight;
targetLeft = realLeft;
}
this.setState({
contentTop: top,
contentLeft: left,
contentWidth: width,
contentHeight: height,
targetTop,
targetHeight,
targetLeft,
position: position,
isBlock: display === 'block' || display === 'flex',
isWindow
}, () => {
this.updatePosition();
});
});
if (this.props.onTargetResize) {
this.props.onTargetResize();
}
}
updatePosition() {
const { offsetTop, offsetBottom, target, onChange } = this.props;
const { contentTop, targetTop, isWindow, targetHeight, contentLeft, targetLeft } = this.state;
const targetNode = target();
const scrollTop = utils_1.doms.scrollTop(targetNode);
let affixed = false;
if (offsetBottom > 0) {
if (scrollTop + targetHeight - offsetBottom <= contentTop - targetTop) {
affixed = true;
}
}
else {
if (scrollTop >= contentTop - targetTop - offsetTop) {
affixed = true;
}
}
if (affixed !== this.state.affixed) {
if (onChange) {
onChange(affixed);
}
}
this.setState({
affixed,
affixTop: isWindow ? offsetTop : scrollTop + offsetTop,
affixBottom: isWindow
? offsetBottom
: targetNode.scrollHeight - scrollTop - targetHeight + offsetBottom,
affixLeft: isWindow ? contentLeft : contentLeft - targetLeft
});
}
render() {
const { children, offsetBottom, style, className } = this.props;
const { contentWidth, contentHeight, affixed, isBlock, position, isWindow, affixTop, affixBottom, affixLeft } = this.state;
if (!utils_1.isReactElement(children)) {
throw new Error('Affix elements must be wrapped in an enclosing tag');
}
const wrapperStyle = Object.assign({
position,
display: isBlock ? 'block' : 'inline-block'
}, position !== 'absolute' && position !== 'fixed' ? {
contentWidth,
contentHeight
} : {});
const offsetStyle = Object.assign({
position: isWindow ? 'fixed' : 'absolute',
left: affixLeft,
width: contentWidth,
height: contentHeight,
zIndex: 999
}, utils_1.isExist(offsetBottom) ? { bottom: affixBottom } : { top: affixTop });
const renderChild = React.cloneElement(children, {
style: Object.assign({}, children.props.style, affixed ? offsetStyle : {})
});
const componentClassName = classNames({
[utils_1.preClass('affix')]: true,
[className]: !!className
});
return (React.createElement("div", { style: Object.assign({}, style, affixed ? wrapperStyle : {}), className: componentClassName }, renderChild));
}
}
Affix.propTypes = {
children: PropTypes.node,
offsetTop: PropTypes.number,
offsetBottom: PropTypes.number,
target: PropTypes.func,
onTargetScroll: PropTypes.func,
onTargetResize: PropTypes.func,
onChange: PropTypes.func,
style: PropTypes.object,
className: PropTypes.string
};
Affix.defaultProps = {
offsetTop: 0,
target: () => window
};
exports.default = Affix;