-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Expand file tree
/
Copy pathuseViewportRows.ts
More file actions
128 lines (110 loc) · 3.92 KB
/
useViewportRows.ts
File metadata and controls
128 lines (110 loc) · 3.92 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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import { useMemo } from 'react';
import { floor, max, min } from '../utils';
import type { VirtualizationOptions } from '../types';
interface ViewportRowsArgs<R> {
rows: readonly R[];
rowHeight: number | ((row: R) => number);
clientHeight: number;
scrollTop: number;
enableVirtualization: VirtualizationOptions
}
export function useViewportRows<R>({
rows,
rowHeight,
clientHeight,
scrollTop,
enableVirtualization
}: ViewportRowsArgs<R>) {
const { totalRowHeight, gridTemplateRows, getRowTop, getRowHeight, findRowIdx } = useMemo(() => {
if (typeof rowHeight === 'number') {
return {
totalRowHeight: rowHeight * rows.length,
gridTemplateRows: ` repeat(${rows.length}, ${rowHeight}px)`,
getRowTop: (rowIdx: number) => rowIdx * rowHeight,
getRowHeight: () => rowHeight,
findRowIdx: (offset: number) => floor(offset / rowHeight)
};
}
// Calcule the height of all the rows upfront. This can cause performance issues
// and we can consider using a similar approach as react-window
// https://github.com/bvaughn/react-window/blob/b0a470cc264e9100afcaa1b78ed59d88f7914ad4/src/VariableSizeList.js#L68
let totalRowHeight = 0;
let gridTemplateRows = '';
let currentHeight: number | null = null;
let repeatCount = 0;
const rowPositions = rows.map((row, index) => {
const currentRowHeight = rowHeight(row);
const position = {
top: totalRowHeight,
height: currentRowHeight
};
totalRowHeight += currentRowHeight;
if (currentHeight === null) {
currentHeight = currentRowHeight;
repeatCount = 1;
} else if (currentHeight === currentRowHeight) {
// If the current row height is the same as the previous one, increment the repeat count
repeatCount++;
} else {
if (repeatCount > 1) {
gridTemplateRows += `repeat(${repeatCount}, ${currentHeight}px) `;
} else {
gridTemplateRows += `${currentHeight}px `;
}
currentHeight = currentRowHeight;
repeatCount = 1;
}
if (index === rows.length - 1) {
if (repeatCount > 1) {
gridTemplateRows += `repeat(${repeatCount}, ${currentHeight}px)`;
} else {
gridTemplateRows += `${currentHeight}px`;
}
}
return position;
});
const validateRowIdx = (rowIdx: number) => {
return max(0, min(rows.length - 1, rowIdx));
};
return {
totalRowHeight,
gridTemplateRows,
getRowTop: (rowIdx: number) => rowPositions[validateRowIdx(rowIdx)].top,
getRowHeight: (rowIdx: number) => rowPositions[validateRowIdx(rowIdx)].height,
findRowIdx(offset: number) {
let start = 0;
let end = rowPositions.length - 1;
while (start <= end) {
const middle = start + floor((end - start) / 2);
const currentOffset = rowPositions[middle].top;
if (currentOffset === offset) return middle;
if (currentOffset < offset) {
start = middle + 1;
} else if (currentOffset > offset) {
end = middle - 1;
}
if (start > end) return end;
}
return 0;
}
};
}, [rowHeight, rows]);
let rowOverscanStartIdx = 0;
let rowOverscanEndIdx = rows.length - 1;
if (enableVirtualization.rows !== false) {
const overscanThreshold = (enableVirtualization.rows === true || enableVirtualization.rows === undefined) ? 4 : enableVirtualization.rows.overscanThreshold;
const rowVisibleStartIdx = findRowIdx(scrollTop);
const rowVisibleEndIdx = findRowIdx(scrollTop + clientHeight);
rowOverscanStartIdx = max(0, rowVisibleStartIdx - overscanThreshold);
rowOverscanEndIdx = min(rows.length - 1, rowVisibleEndIdx + overscanThreshold);
}
return {
rowOverscanStartIdx,
rowOverscanEndIdx,
totalRowHeight,
gridTemplateRows,
getRowTop,
getRowHeight,
findRowIdx
};
}