Skip to content

Commit 7f57dec

Browse files
authored
feat: export useResizeObserver hooks (#224)
* chore: init * chore: refactor * chore: export * chore: fix lint
1 parent 709f18d commit 7f57dec

File tree

4 files changed

+99
-84
lines changed

4 files changed

+99
-84
lines changed

package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
"@types/node": "^24.5.2",
5252
"@types/react": "^18.0.28",
5353
"@types/react-dom": "^18.0.11",
54-
"@umijs/fabric": "^2.0.9",
54+
"@umijs/fabric": "^4.0.0",
5555
"cheerio": "1.0.0-rc.12",
5656
"coveralls": "^3.0.6",
5757
"cross-env": "^7.0.2",
@@ -68,19 +68,20 @@
6868
"rc-test": "^7.0.15",
6969
"react": "^16.0.0",
7070
"react-dom": "^16.0.0",
71-
"regenerator-runtime": "^0.14.0"
71+
"regenerator-runtime": "^0.14.0",
72+
"eslint": "8.x"
7273
},
7374
"peerDependencies": {
7475
"react": ">=16.9.0",
7576
"react-dom": ">=16.9.0"
7677
},
7778
"resolutions": {
78-
"@types/minimatch": "5.1.2"
79-
},
79+
"@types/minimatch": "5.1.2"
80+
},
8081
"cnpm": {
8182
"mode": "npm"
8283
},
8384
"tnpm": {
8485
"mode": "npm"
8586
}
86-
}
87+
}

src/SingleObserver/index.tsx

Lines changed: 12 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import { supportRef, useComposeRef, getNodeRef } from '@rc-component/util/lib/re
33
import * as React from 'react';
44
import type { ResizeObserverProps } from '..';
55
import { CollectionContext } from '../Collection';
6-
import { observe, unobserve } from '../utils/observerUtil';
6+
import useResizeObserver from '../useResizeObserver';
77

88
export interface SingleObserverProps extends ResizeObserverProps {
99
children: React.ReactElement | ((ref: React.RefObject<Element>) => React.ReactElement);
1010
}
1111

1212
function SingleObserver(props: SingleObserverProps, ref: React.Ref<HTMLElement>) {
13-
const { children, disabled } = props;
13+
const { children, disabled, onResize, data } = props;
1414
const elementRef = React.useRef<Element>(null);
1515

1616
const onCollectionResize = React.useContext(CollectionContext);
@@ -19,14 +19,6 @@ function SingleObserver(props: SingleObserverProps, ref: React.Ref<HTMLElement>)
1919
const isRenderProps = typeof children === 'function';
2020
const mergedChildren = isRenderProps ? children(elementRef) : children;
2121

22-
// ============================= Size =============================
23-
const sizeRef = React.useRef({
24-
width: -1,
25-
height: -1,
26-
offsetWidth: -1,
27-
offsetHeight: -1,
28-
});
29-
3022
// ============================= Ref ==============================
3123
const canRef =
3224
!isRenderProps && React.isValidElement(mergedChildren) && supportRef(mergedChildren);
@@ -35,81 +27,22 @@ function SingleObserver(props: SingleObserverProps, ref: React.Ref<HTMLElement>)
3527
const mergedRef = useComposeRef(originRef, elementRef);
3628

3729
const getDomElement = () => {
38-
return getDOM( elementRef.current ) as HTMLElement
39-
}
40-
30+
return getDOM(elementRef.current) as HTMLElement;
31+
};
4132

4233
React.useImperativeHandle(ref, () => getDomElement());
4334

4435
// =========================== Observe ============================
45-
const propsRef = React.useRef<SingleObserverProps>(props);
46-
propsRef.current = props;
47-
48-
// Handler
49-
const onInternalResize = React.useCallback((target: HTMLElement) => {
50-
const { onResize, data } = propsRef.current;
51-
52-
const { width, height } = target.getBoundingClientRect();
53-
const { offsetWidth, offsetHeight } = target;
54-
55-
/**
56-
* Resize observer trigger when content size changed.
57-
* In most case we just care about element size,
58-
* let's use `boundary` instead of `contentRect` here to avoid shaking.
59-
*/
60-
const fixedWidth = Math.floor(width);
61-
const fixedHeight = Math.floor(height);
62-
63-
if (
64-
sizeRef.current.width !== fixedWidth ||
65-
sizeRef.current.height !== fixedHeight ||
66-
sizeRef.current.offsetWidth !== offsetWidth ||
67-
sizeRef.current.offsetHeight !== offsetHeight
68-
) {
69-
const size = { width: fixedWidth, height: fixedHeight, offsetWidth, offsetHeight };
70-
sizeRef.current = size;
71-
72-
// IE is strange, right?
73-
const mergedOffsetWidth = offsetWidth === Math.round(width) ? width : offsetWidth;
74-
const mergedOffsetHeight = offsetHeight === Math.round(height) ? height : offsetHeight;
75-
76-
const sizeInfo = {
77-
...size,
78-
offsetWidth: mergedOffsetWidth,
79-
offsetHeight: mergedOffsetHeight,
80-
};
81-
82-
// Let collection know what happened
83-
onCollectionResize?.(sizeInfo, target, data);
84-
85-
if (onResize) {
86-
// defer the callback but not defer to next frame
87-
Promise.resolve().then(() => {
88-
onResize(sizeInfo, target);
89-
});
90-
}
91-
}
92-
}, []);
93-
94-
// Dynamic observe
95-
React.useEffect(() => {
96-
const currentElement: HTMLElement = getDomElement();
97-
98-
if (currentElement && !disabled) {
99-
observe(currentElement, onInternalResize);
100-
}
101-
102-
return () => unobserve(currentElement, onInternalResize);
103-
}, [elementRef.current, disabled]);
36+
useResizeObserver(!disabled, getDomElement, onResize, (sizeInfo, target) => {
37+
onCollectionResize?.(sizeInfo, target, data);
38+
});
10439

10540
// ============================ Render ============================
106-
return (
107-
canRef
108-
? React.cloneElement(mergedChildren as any, {
109-
ref: mergedRef,
110-
})
111-
: mergedChildren
112-
);
41+
return canRef
42+
? React.cloneElement(mergedChildren as any, {
43+
ref: mergedRef,
44+
})
45+
: mergedChildren;
11346
}
11447

11548
const RefSingleObserver = React.forwardRef(SingleObserver);

src/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ export {
1212
_rs,
1313
};
1414

15+
export { default as useResizeObserver } from './useResizeObserver';
16+
1517
export interface SizeInfo {
1618
width: number;
1719
height: number;

src/useResizeObserver.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as React from 'react';
2+
import type { OnResize, SizeInfo } from '.';
3+
import { observe, unobserve } from './utils/observerUtil';
4+
import { useEvent } from '@rc-component/util';
5+
6+
export default function useResizeObserver(
7+
enabled: boolean,
8+
getTarget: () => HTMLElement,
9+
onDelayResize?: OnResize,
10+
onSyncResize?: OnResize,
11+
) {
12+
// ============================= Size =============================
13+
const sizeRef = React.useRef<SizeInfo>({
14+
width: -1,
15+
height: -1,
16+
offsetWidth: -1,
17+
offsetHeight: -1,
18+
});
19+
20+
// =========================== Observe ============================
21+
22+
// Handler
23+
const onInternalResize = useEvent((target: HTMLElement) => {
24+
const { width, height } = target.getBoundingClientRect();
25+
const { offsetWidth, offsetHeight } = target;
26+
27+
/**
28+
* Resize observer trigger when content size changed.
29+
* In most case we just care about element size,
30+
* let's use `boundary` instead of `contentRect` here to avoid shaking.
31+
*/
32+
const fixedWidth = Math.floor(width);
33+
const fixedHeight = Math.floor(height);
34+
35+
if (
36+
sizeRef.current.width !== fixedWidth ||
37+
sizeRef.current.height !== fixedHeight ||
38+
sizeRef.current.offsetWidth !== offsetWidth ||
39+
sizeRef.current.offsetHeight !== offsetHeight
40+
) {
41+
const size = { width: fixedWidth, height: fixedHeight, offsetWidth, offsetHeight };
42+
sizeRef.current = size;
43+
44+
// IE is strange, right?
45+
const mergedOffsetWidth = offsetWidth === Math.round(width) ? width : offsetWidth;
46+
const mergedOffsetHeight = offsetHeight === Math.round(height) ? height : offsetHeight;
47+
48+
const sizeInfo = {
49+
...size,
50+
offsetWidth: mergedOffsetWidth,
51+
offsetHeight: mergedOffsetHeight,
52+
};
53+
54+
// Call the callback immediately, let the caller decide whether to defer
55+
// onResize(sizeInfo, target);
56+
onSyncResize?.(sizeInfo, target);
57+
58+
// defer the callback but not defer to next frame
59+
Promise.resolve().then(() => {
60+
onDelayResize?.(sizeInfo, target);
61+
});
62+
}
63+
});
64+
65+
// Dynamic observe
66+
React.useEffect(() => {
67+
const target = getTarget();
68+
69+
if (target && enabled) {
70+
observe(target, onInternalResize);
71+
}
72+
73+
return () => {
74+
if (target) {
75+
unobserve(target, onInternalResize);
76+
}
77+
};
78+
}, [enabled]);
79+
}

0 commit comments

Comments
 (0)