1 | "use client";
|
2 |
|
3 | import React from 'react';
|
4 | import classNames from 'classnames';
|
5 | import ResizeObserver from 'rc-resize-observer';
|
6 | import omit from "rc-util/es/omit";
|
7 | import throttleByAnimationFrame from '../_util/throttleByAnimationFrame';
|
8 | import { ConfigContext } from '../config-provider';
|
9 | import useStyle from './style';
|
10 | import { getFixedBottom, getFixedTop, getTargetRect } from './utils';
|
11 | const TRIGGER_EVENTS = ['resize', 'scroll', 'touchstart', 'touchmove', 'touchend', 'pageshow', 'load'];
|
12 | function getDefaultTarget() {
|
13 | return typeof window !== 'undefined' ? window : null;
|
14 | }
|
15 | var AffixStatus;
|
16 | (function (AffixStatus) {
|
17 | AffixStatus[AffixStatus["None"] = 0] = "None";
|
18 | AffixStatus[AffixStatus["Prepare"] = 1] = "Prepare";
|
19 | })(AffixStatus || (AffixStatus = {}));
|
20 | const Affix = React.forwardRef((props, ref) => {
|
21 | var _a;
|
22 | const {
|
23 | style,
|
24 | offsetTop,
|
25 | offsetBottom,
|
26 | prefixCls,
|
27 | className,
|
28 | rootClassName,
|
29 | children,
|
30 | target,
|
31 | onChange
|
32 | } = props;
|
33 | const {
|
34 | getPrefixCls,
|
35 | getTargetContainer
|
36 | } = React.useContext(ConfigContext);
|
37 | const affixPrefixCls = getPrefixCls('affix', prefixCls);
|
38 | const [lastAffix, setLastAffix] = React.useState(false);
|
39 | const [affixStyle, setAffixStyle] = React.useState();
|
40 | const [placeholderStyle, setPlaceholderStyle] = React.useState();
|
41 | const status = React.useRef(AffixStatus.None);
|
42 | const prevTarget = React.useRef(null);
|
43 | const prevListener = React.useRef();
|
44 | const placeholderNodeRef = React.useRef(null);
|
45 | const fixedNodeRef = React.useRef(null);
|
46 | const timer = React.useRef(null);
|
47 | const targetFunc = (_a = target !== null && target !== void 0 ? target : getTargetContainer) !== null && _a !== void 0 ? _a : getDefaultTarget;
|
48 | const internalOffsetTop = offsetBottom === undefined && offsetTop === undefined ? 0 : offsetTop;
|
49 |
|
50 | const measure = () => {
|
51 | if (status.current !== AffixStatus.Prepare || !fixedNodeRef.current || !placeholderNodeRef.current || !targetFunc) {
|
52 | return;
|
53 | }
|
54 | const targetNode = targetFunc();
|
55 | if (targetNode) {
|
56 | const newState = {
|
57 | status: AffixStatus.None
|
58 | };
|
59 | const placeholderRect = getTargetRect(placeholderNodeRef.current);
|
60 | if (placeholderRect.top === 0 && placeholderRect.left === 0 && placeholderRect.width === 0 && placeholderRect.height === 0) {
|
61 | return;
|
62 | }
|
63 | const targetRect = getTargetRect(targetNode);
|
64 | const fixedTop = getFixedTop(placeholderRect, targetRect, internalOffsetTop);
|
65 | const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom);
|
66 | if (fixedTop !== undefined) {
|
67 | newState.affixStyle = {
|
68 | position: 'fixed',
|
69 | top: fixedTop,
|
70 | width: placeholderRect.width,
|
71 | height: placeholderRect.height
|
72 | };
|
73 | newState.placeholderStyle = {
|
74 | width: placeholderRect.width,
|
75 | height: placeholderRect.height
|
76 | };
|
77 | } else if (fixedBottom !== undefined) {
|
78 | newState.affixStyle = {
|
79 | position: 'fixed',
|
80 | bottom: fixedBottom,
|
81 | width: placeholderRect.width,
|
82 | height: placeholderRect.height
|
83 | };
|
84 | newState.placeholderStyle = {
|
85 | width: placeholderRect.width,
|
86 | height: placeholderRect.height
|
87 | };
|
88 | }
|
89 | newState.lastAffix = !!newState.affixStyle;
|
90 | if (lastAffix !== newState.lastAffix) {
|
91 | onChange === null || onChange === void 0 ? void 0 : onChange(newState.lastAffix);
|
92 | }
|
93 | status.current = newState.status;
|
94 | setAffixStyle(newState.affixStyle);
|
95 | setPlaceholderStyle(newState.placeholderStyle);
|
96 | setLastAffix(newState.lastAffix);
|
97 | }
|
98 | };
|
99 | const prepareMeasure = () => {
|
100 | var _a;
|
101 | status.current = AffixStatus.Prepare;
|
102 | measure();
|
103 | if (process.env.NODE_ENV === 'test') {
|
104 | (_a = props === null || props === void 0 ? void 0 : props.onTestUpdatePosition) === null || _a === void 0 ? void 0 : _a.call(props);
|
105 | }
|
106 | };
|
107 | const updatePosition = throttleByAnimationFrame(() => {
|
108 | prepareMeasure();
|
109 | });
|
110 | const lazyUpdatePosition = throttleByAnimationFrame(() => {
|
111 |
|
112 | if (targetFunc && affixStyle) {
|
113 | const targetNode = targetFunc();
|
114 | if (targetNode && placeholderNodeRef.current) {
|
115 | const targetRect = getTargetRect(targetNode);
|
116 | const placeholderRect = getTargetRect(placeholderNodeRef.current);
|
117 | const fixedTop = getFixedTop(placeholderRect, targetRect, internalOffsetTop);
|
118 | const fixedBottom = getFixedBottom(placeholderRect, targetRect, offsetBottom);
|
119 | if (fixedTop !== undefined && affixStyle.top === fixedTop || fixedBottom !== undefined && affixStyle.bottom === fixedBottom) {
|
120 | return;
|
121 | }
|
122 | }
|
123 | }
|
124 |
|
125 | prepareMeasure();
|
126 | });
|
127 | const addListeners = () => {
|
128 | const listenerTarget = targetFunc === null || targetFunc === void 0 ? void 0 : targetFunc();
|
129 | if (!listenerTarget) {
|
130 | return;
|
131 | }
|
132 | TRIGGER_EVENTS.forEach(eventName => {
|
133 | var _a;
|
134 | if (prevListener.current) {
|
135 | (_a = prevTarget.current) === null || _a === void 0 ? void 0 : _a.removeEventListener(eventName, prevListener.current);
|
136 | }
|
137 | listenerTarget === null || listenerTarget === void 0 ? void 0 : listenerTarget.addEventListener(eventName, lazyUpdatePosition);
|
138 | });
|
139 | prevTarget.current = listenerTarget;
|
140 | prevListener.current = lazyUpdatePosition;
|
141 | };
|
142 | const removeListeners = () => {
|
143 | if (timer.current) {
|
144 | clearTimeout(timer.current);
|
145 | timer.current = null;
|
146 | }
|
147 | const newTarget = targetFunc === null || targetFunc === void 0 ? void 0 : targetFunc();
|
148 | TRIGGER_EVENTS.forEach(eventName => {
|
149 | var _a;
|
150 | newTarget === null || newTarget === void 0 ? void 0 : newTarget.removeEventListener(eventName, lazyUpdatePosition);
|
151 | if (prevListener.current) {
|
152 | (_a = prevTarget.current) === null || _a === void 0 ? void 0 : _a.removeEventListener(eventName, prevListener.current);
|
153 | }
|
154 | });
|
155 | updatePosition.cancel();
|
156 | lazyUpdatePosition.cancel();
|
157 | };
|
158 | React.useImperativeHandle(ref, () => ({
|
159 | updatePosition
|
160 | }));
|
161 |
|
162 | React.useEffect(() => {
|
163 |
|
164 |
|
165 | timer.current = setTimeout(addListeners);
|
166 | return () => removeListeners();
|
167 | }, []);
|
168 | React.useEffect(() => {
|
169 | addListeners();
|
170 | }, [target, affixStyle]);
|
171 | React.useEffect(() => {
|
172 | updatePosition();
|
173 | }, [target, offsetTop, offsetBottom]);
|
174 | const [wrapCSSVar, hashId, cssVarCls] = useStyle(affixPrefixCls);
|
175 | const rootCls = classNames(rootClassName, hashId, affixPrefixCls, cssVarCls);
|
176 | const mergedCls = classNames({
|
177 | [rootCls]: affixStyle
|
178 | });
|
179 | let otherProps = omit(props, ['prefixCls', 'offsetTop', 'offsetBottom', 'target', 'onChange', 'rootClassName']);
|
180 | if (process.env.NODE_ENV === 'test') {
|
181 | otherProps = omit(otherProps, ['onTestUpdatePosition']);
|
182 | }
|
183 | return wrapCSSVar( React.createElement(ResizeObserver, {
|
184 | onResize: updatePosition
|
185 | }, React.createElement("div", Object.assign({
|
186 | style: style,
|
187 | className: className,
|
188 | ref: placeholderNodeRef
|
189 | }, otherProps), affixStyle && React.createElement("div", {
|
190 | style: placeholderStyle,
|
191 | "aria-hidden": "true"
|
192 | }), React.createElement("div", {
|
193 | className: mergedCls,
|
194 | ref: fixedNodeRef,
|
195 | style: affixStyle
|
196 | }, React.createElement(ResizeObserver, {
|
197 | onResize: updatePosition
|
198 | }, children)))));
|
199 | });
|
200 | if (process.env.NODE_ENV !== 'production') {
|
201 | Affix.displayName = 'Affix';
|
202 | }
|
203 | export default Affix; |
\ | No newline at end of file |