-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expand file tree
/
Copy pathuseGridDimensions.ts
More file actions
86 lines (68 loc) · 2.69 KB
/
useGridDimensions.ts
File metadata and controls
86 lines (68 loc) · 2.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import { useCallback, useLayoutEffect, useRef, useSyncExternalStore, type RefObject } from 'react';
const initialSize: ResizeObserverSize = {
inlineSize: 1,
blockSize: 1
};
// use an unmanaged WeakMap so we preserve the cache even when
// the component partially unmounts via Suspense or Activity
const sizeMap = new WeakMap<RefObject<HTMLDivElement | null>, ResizeObserverSize>();
const targetToRefMap = new WeakMap<HTMLDivElement, RefObject<HTMLDivElement | null>>();
const subscribers = new Map<RefObject<HTMLDivElement | null>, () => void>();
// don't break in Node.js (SSR), jsdom, and environments that don't support ResizeObserver
const resizeObserver =
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
globalThis.ResizeObserver == null ? null : new ResizeObserver(resizeObserverCallback);
function resizeObserverCallback(entries: ResizeObserverEntry[]) {
for (const entry of entries) {
const target = entry.target as HTMLDivElement;
if (targetToRefMap.has(target)) {
const ref = targetToRefMap.get(target)!;
updateSize(ref, entry.contentBoxSize[0]);
}
}
}
function updateSize(ref: RefObject<HTMLDivElement | null>, size: ResizeObserverSize) {
if (sizeMap.has(ref)) {
const prevSize = sizeMap.get(ref)!;
if (prevSize.inlineSize === size.inlineSize && prevSize.blockSize === size.blockSize) {
return;
}
}
sizeMap.set(ref, size);
subscribers.get(ref)?.();
}
function getServerSnapshot(): ResizeObserverSize {
return initialSize;
}
export function useGridDimensions() {
const ref = useRef<HTMLDivElement>(null);
const subscribe = useCallback((onStoreChange: () => void) => {
subscribers.set(ref, onStoreChange);
return () => {
subscribers.delete(ref);
};
}, []);
const getSnapshot = useCallback((): ResizeObserverSize => {
// ref.current is null during the initial render, when suspending, or in <Activity mode="hidden">.
// We use ref as key instead to access stable values regardless of rendering state.
return sizeMap.has(ref) ? sizeMap.get(ref)! : initialSize;
}, []);
// We use `useSyncExternalStore` instead of `useState` to avoid tearing,
// which can lead to flashing scrollbars.
const { inlineSize, blockSize } = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
useLayoutEffect(() => {
const target = ref.current!;
targetToRefMap.set(target, ref);
resizeObserver?.observe(target);
if (!sizeMap.has(ref)) {
updateSize(ref, {
inlineSize: target.clientWidth,
blockSize: target.clientHeight
});
}
return () => {
resizeObserver?.unobserve(target);
};
}, []);
return [ref, inlineSize, blockSize] as const;
}