|
|
|
import * as React from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
|
|
import omit from 'omit.js';
|
|
|
|
import ResizeObserver from 'rc-resize-observer';
|
|
|
|
import { ConfigContext, ConfigConsumerProps } from '../config-provider';
|
|
|
|
import { throttleByAnimationFrameDecorator } from '../_util/throttleByAnimationFrame';
|
|
|
|
|
|
|
|
import {
|
|
|
|
addObserveTarget,
|
|
|
|
removeObserveTarget,
|
|
|
|
getTargetRect,
|
|
|
|
getFixedTop,
|
|
|
|
getFixedBottom,
|
|
|
|
} from './utils';
|
|
|
|
|
|
|
|
function getDefaultTarget() {
|
|
|
|
return typeof window !== 'undefined' ? window : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Affix
|
|
|
|
export interface AffixProps {
|
|
|
|
/**
|
|
|
|
* 距离窗口顶部达到指定偏移量后触发
|
|
|
|
*/
|
|
|
|
offsetTop?: number;
|
|
|
|
/** 距离窗口底部达到指定偏移量后触发 */
|
|
|
|
offsetBottom?: number;
|
|
|
|
style?: React.CSSProperties;
|
|
|
|
/** 固定状态改变时触发的回调函数 */
|
|
|
|
onChange?: (affixed?: boolean) => void;
|
|
|
|
/** 设置 Affix 需要监听其滚动事件的元素,值为一个返回对应 DOM 元素的函数 */
|
|
|
|
target?: () => Window | HTMLElement | null;
|
|
|
|
prefixCls?: string;
|
|
|
|
className?: string;
|
|
|
|
children: React.ReactNode;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum AffixStatus {
|
|
|
|
None,
|
|
|
|
Prepare,
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface AffixState {
|
|
|
|
affixStyle?: React.CSSProperties;
|
|
|
|
placeholderStyle?: React.CSSProperties;
|
|
|
|
status: AffixStatus;
|
|
|
|
lastAffix: boolean;
|
|
|
|
|
|
|
|
prevTarget: Window | HTMLElement | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
class Affix extends React.Component<AffixProps, AffixState> {
|
|
|
|
static contextType = ConfigContext;
|
|
|
|
|
|
|
|
state: AffixState = {
|
|
|
|
status: AffixStatus.None,
|
|
|
|
lastAffix: false,
|
|
|
|
prevTarget: null,
|
|
|
|
};
|
|
|
|
|
|
|
|
placeholderNode: HTMLDivElement;
|
|
|
|
|
|
|
|
fixedNode: HTMLDivElement;
|
|
|
|
|
|
|
|
private timeout: number;
|
|
|
|
|
|
|
|
context: ConfigConsumerProps;
|
|
|
|
|
|
|
|
private getTargetFunc() {
|
|
|
|
const { getTargetContainer } = this.context;
|
|
|
|
const { target } = this.props;
|
|
|
|
|
|
|
|
if (target !== undefined) {
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
|
|
|
|
return getTargetContainer || getDefaultTarget;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Event handler
|
|
|
|
componentDidMount() {
|
|
|
|
const targetFunc = this.getTargetFunc();
|
|
|
|
if (targetFunc) {
|
|
|
|
// [Legacy] Wait for parent component ref has its value.
|
|
|
|
// We should use target as directly element instead of function which makes element check hard.
|
|
|
|
this.timeout = setTimeout(() => {
|
|
|
|
addObserveTarget(targetFunc(), this);
|
|
|
|
// Mock Event object.
|
|
|
|
this.updatePosition();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps: AffixProps) {
|
|
|
|
const { prevTarget } = this.state;
|
|
|
|
const targetFunc = this.getTargetFunc();
|