-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expand file tree
/
Copy pathuseGridDimensions.ts
More file actions
99 lines (79 loc) · 2.77 KB
/
useGridDimensions.ts
File metadata and controls
99 lines (79 loc) · 2.77 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
87
88
89
90
91
92
93
94
95
96
97
98
99
import { useCallback, useId, useLayoutEffect, useRef, useSyncExternalStore } from 'react';
const initialSize: ResizeObserverSize = {
inlineSize: 1,
blockSize: 1
};
const targetToIdMap = new Map<HTMLDivElement, string>();
const idToTargetMap = new Map<string, HTMLDivElement>();
// use an unmanaged WeakMap so we preserve the cache even when
// the component partially unmounts via Suspense or Activity
const sizeMap = new WeakMap<HTMLDivElement, ResizeObserverSize>();
const subscribers = new Map<string, () => 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 (!targetToIdMap.has(target)) continue;
const id = targetToIdMap.get(target)!;
updateSize(target, id, entry.contentBoxSize[0]);
}
}
function updateSize(target: HTMLDivElement, id: string, size: ResizeObserverSize) {
if (sizeMap.has(target)) {
const prevSize = sizeMap.get(target)!;
if (prevSize.inlineSize === size.inlineSize && prevSize.blockSize === size.blockSize) {
return;
}
}
sizeMap.set(target, size);
subscribers.get(id)?.();
}
function getServerSnapshot(): ResizeObserverSize {
return initialSize;
}
export function useGridDimensions() {
const id = useId();
const gridRef = useRef<HTMLDivElement>(null);
const subscribe = useCallback(
(onStoreChange: () => void) => {
subscribers.set(id, onStoreChange);
return () => {
subscribers.delete(id);
};
},
[id]
);
const getSnapshot = useCallback((): ResizeObserverSize => {
if (idToTargetMap.has(id)) {
const target = idToTargetMap.get(id)!;
if (sizeMap.has(target)) {
return sizeMap.get(target)!;
}
}
return initialSize;
}, [id]);
// 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 = gridRef.current!;
targetToIdMap.set(target, id);
idToTargetMap.set(id, target);
resizeObserver?.observe(target);
if (!sizeMap.has(target)) {
updateSize(target, id, {
inlineSize: target.clientWidth,
blockSize: target.clientHeight
});
}
return () => {
targetToIdMap.delete(target);
idToTargetMap.delete(id);
resizeObserver?.unobserve(target);
};
}, [id]);
return [gridRef, inlineSize, blockSize] as const;
}