@opencorestack/opengridx 2.0.3 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/index.d.ts +7 -0
- package/dist/opengridx.es.js +2457 -2432
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +16 -16
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +1 -0
- package/docs/components/datagrid.md +1 -0
- package/docs/features/virtualization.md +30 -9
- package/lib/components/DataGrid/DataGrid.tsx +3 -1
- package/lib/hooks/core/useGridScrollSync.ts +62 -15
- package/lib/hooks/core/useGridVirtualization.ts +4 -1
- package/lib/types/index.ts +7 -0
- package/llms.txt +1 -1
- package/package.json +1 -1
package/docs/API_REFERENCE.md
CHANGED
|
@@ -18,6 +18,7 @@ The main component for displaying and interacting with data.
|
|
|
18
18
|
| `rowHeight` | `number` | `52` | Height of each row in pixels. |
|
|
19
19
|
| `headerHeight` | `number` | `56` | Height of the header row. |
|
|
20
20
|
| `autoHeight` | `boolean` | `false` | Adjust grid height to match row total. |
|
|
21
|
+
| `overscanRowCount` | `number` | `3` | Minimum rows rendered outside the visible viewport. The grid adapts this upward automatically based on scroll velocity — this prop sets the floor. |
|
|
21
22
|
| `loading` | `boolean` | `false` | Shows a loading skeleton overlay. |
|
|
22
23
|
| `checkboxSelection` | `boolean` | `false` | Enable row selection via checkboxes. |
|
|
23
24
|
| `pagination` | `boolean` | `false` | Enable the bottom pagination bar. |
|
|
@@ -35,6 +35,7 @@ function MyGrid() {
|
|
|
35
35
|
| `paginationModel` | `GridPaginationModel` | — | Controlled pagination state (`{ page, pageSize }`). Omit for uncontrolled; use `initialState` to set the initial page/pageSize. |
|
|
36
36
|
| `pageSizeOptions` | `number[]` | `[10, 25, 50, 100]` | Available page size options. |
|
|
37
37
|
| `initialState` | `GridInitialState` | `undefined` | Initial configuration (sorting, columns, pagination, etc). |
|
|
38
|
+
| `overscanRowCount` | `number` | `3` | Minimum rows rendered outside the visible viewport. The grid adapts this upward automatically based on scroll velocity — this prop sets the floor. |
|
|
38
39
|
|
|
39
40
|
## 🔃 Sorting
|
|
40
41
|
|
|
@@ -84,16 +84,37 @@ Disable virtualization for small datasets:
|
|
|
84
84
|
|
|
85
85
|
### Overscan Buffer
|
|
86
86
|
|
|
87
|
-
The overscan buffer determines how many extra rows are rendered outside the viewport.
|
|
87
|
+
The overscan buffer determines how many extra rows are rendered outside the viewport. The grid adapts this buffer automatically based on scroll velocity so fast scrolls never produce blank flashes.
|
|
88
88
|
|
|
89
|
-
**
|
|
90
|
-
-
|
|
91
|
-
-
|
|
89
|
+
**Adaptive algorithm (`useGridScrollSync`):**
|
|
90
|
+
- Scroll velocity (px/ms) is measured on every scroll event
|
|
91
|
+
- The overscan tier is chosen from the table below and applied immediately
|
|
92
|
+
- 200 ms after scrolling stops the buffer decays back to `overscanRowCount`
|
|
93
|
+
|
|
94
|
+
| Velocity (px/ms) | Overscan rows |
|
|
95
|
+
|---|---|
|
|
96
|
+
| < 0.5 | 3 |
|
|
97
|
+
| 0.5 – 3 | 5 |
|
|
98
|
+
| 3 – 15 | 12 |
|
|
99
|
+
| 15 – 40 | 20 |
|
|
100
|
+
| > 40 | 30 |
|
|
101
|
+
|
|
102
|
+
**Configuring the floor:**
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// Never render fewer than 5 rows outside the viewport
|
|
106
|
+
<DataGrid
|
|
107
|
+
rows={rows}
|
|
108
|
+
columns={columns}
|
|
109
|
+
overscanRowCount={5}
|
|
110
|
+
/>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`overscanRowCount` (default `3`) sets the minimum — the adaptive algorithm always produces a value ≥ this floor.
|
|
92
114
|
|
|
93
115
|
**Trade-offs:**
|
|
94
|
-
- **Smaller
|
|
95
|
-
- **Larger
|
|
96
|
-
- **Very large overscan (10+)**: Diminishing returns, wasted rendering
|
|
116
|
+
- **Smaller floor (1-3)**: Less idle memory, relies on adaptive headroom for fast scrolls
|
|
117
|
+
- **Larger floor (5-8)**: Pre-renders more rows at rest, trades memory for smoother slow scrolls
|
|
97
118
|
|
|
98
119
|
### Scroll Performance
|
|
99
120
|
|
|
@@ -178,7 +199,7 @@ You should see console logs only for visible rows + overscan buffer.
|
|
|
178
199
|
## Future Improvements
|
|
179
200
|
|
|
180
201
|
- [ ] Column virtualization for grids with 50+ columns
|
|
181
|
-
- [
|
|
202
|
+
- [x] Adaptive overscan based on scroll velocity (`overscanRowCount` prop, v2.0.4)
|
|
182
203
|
- [ ] Virtual scrollbar for extremely large datasets (millions of rows)
|
|
183
204
|
- [ ] Intersection Observer API for better scroll detection
|
|
184
205
|
|
|
@@ -189,7 +210,7 @@ You should see console logs only for visible rows + overscan buffer.
|
|
|
189
210
|
| Row virtualization | ✅ Yes | ✅ Yes |
|
|
190
211
|
| Column virtualization | ❌ Planned | ✅ Yes (Pro) |
|
|
191
212
|
| Variable row heights | ✅ Yes | ✅ Yes |
|
|
192
|
-
| Overscan buffer |
|
|
213
|
+
| Overscan buffer | 3–30 rows (adaptive, velocity-based) | 3-8 rows (adaptive) |
|
|
193
214
|
| Max recommended rows | 100,000+ | 100,000+ |
|
|
194
215
|
|
|
195
216
|
## Related Documentation
|
|
@@ -118,6 +118,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
118
118
|
noRowsLabel = 'No Data',
|
|
119
119
|
localeText,
|
|
120
120
|
apiRef: propApiRef,
|
|
121
|
+
overscanRowCount = 3,
|
|
121
122
|
} = props;
|
|
122
123
|
|
|
123
124
|
const effectiveNoRowsLabel = localeText?.noRowsLabel ?? noRowsLabel;
|
|
@@ -301,7 +302,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
301
302
|
setRowCount
|
|
302
303
|
} = gridData;
|
|
303
304
|
|
|
304
|
-
const { scrollTop, scrollLeft, handleScroll } = useGridScrollSync({ onRowsScrollEnd });
|
|
305
|
+
const { scrollTop, scrollLeft, overscanRows, handleScroll } = useGridScrollSync({ onRowsScrollEnd, overscanRowCount });
|
|
305
306
|
|
|
306
307
|
useEffect(() => {
|
|
307
308
|
if (propApiRef) {
|
|
@@ -693,6 +694,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
693
694
|
checkboxSelection,
|
|
694
695
|
pinCheckboxColumn,
|
|
695
696
|
pinExpandColumn,
|
|
697
|
+
overscanRows,
|
|
696
698
|
});
|
|
697
699
|
|
|
698
700
|
|
|
@@ -3,42 +3,84 @@ import type { GridRowScrollEndParams } from '../../types';
|
|
|
3
3
|
|
|
4
4
|
export interface UseGridScrollSyncParams {
|
|
5
5
|
onRowsScrollEnd?: (params: GridRowScrollEndParams) => void;
|
|
6
|
+
/** Minimum overscan rows — the adaptive algorithm never goes below this. Defaults to 3. */
|
|
7
|
+
overscanRowCount?: number;
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export interface UseGridScrollSyncResult {
|
|
9
11
|
scrollTop: number;
|
|
10
12
|
scrollLeft: number;
|
|
13
|
+
/** Dynamically computed overscan row count based on current scroll velocity. */
|
|
14
|
+
overscanRows: number;
|
|
11
15
|
handleScroll: (event: React.UIEvent<HTMLDivElement>) => void;
|
|
12
16
|
}
|
|
13
17
|
|
|
18
|
+
/** Maps scroll velocity (px/ms) to an overscan row count. */
|
|
19
|
+
function velocityToOverscan(velocity: number, minOverscan: number): number {
|
|
20
|
+
let base: number;
|
|
21
|
+
if (velocity < 0.5) base = 3;
|
|
22
|
+
else if (velocity < 3) base = 5;
|
|
23
|
+
else if (velocity < 15) base = 12;
|
|
24
|
+
else if (velocity < 40) base = 20;
|
|
25
|
+
else base = 30;
|
|
26
|
+
return Math.max(base, minOverscan);
|
|
27
|
+
}
|
|
28
|
+
|
|
14
29
|
export function useGridScrollSync(params: UseGridScrollSyncParams): UseGridScrollSyncResult {
|
|
15
|
-
const { onRowsScrollEnd } = params;
|
|
30
|
+
const { onRowsScrollEnd, overscanRowCount = 3 } = params;
|
|
16
31
|
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
const
|
|
32
|
+
const pendingRef = useRef({ scrollTop: 0, scrollLeft: 0, velocity: 0 });
|
|
33
|
+
const prevRef = useRef({ scrollTop: 0, scrollLeft: 0, time: 0 });
|
|
34
|
+
const rafRef = useRef<number | null>(null);
|
|
35
|
+
const decayRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
36
|
+
|
|
37
|
+
const [scrollState, setScrollState] = useState({
|
|
38
|
+
scrollTop: 0,
|
|
39
|
+
scrollLeft: 0,
|
|
40
|
+
overscanRows: overscanRowCount,
|
|
41
|
+
});
|
|
20
42
|
|
|
21
43
|
useEffect(() => {
|
|
22
44
|
return () => {
|
|
23
|
-
if (
|
|
45
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
46
|
+
if (decayRef.current !== null) clearTimeout(decayRef.current);
|
|
24
47
|
};
|
|
25
48
|
}, []);
|
|
26
49
|
|
|
27
50
|
const handleScroll = useCallback((event: React.UIEvent<HTMLDivElement>) => {
|
|
28
51
|
const target = event.currentTarget;
|
|
29
|
-
|
|
52
|
+
const now = performance.now();
|
|
53
|
+
|
|
54
|
+
// Velocity: total positional delta / elapsed time (px/ms)
|
|
55
|
+
const deltaPos = Math.abs(target.scrollTop - prevRef.current.scrollTop)
|
|
56
|
+
+ Math.abs(target.scrollLeft - prevRef.current.scrollLeft);
|
|
57
|
+
const deltaTime = now - prevRef.current.time;
|
|
58
|
+
const velocity = deltaTime > 0 ? deltaPos / deltaTime : 0;
|
|
30
59
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
60
|
+
prevRef.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft, time: now };
|
|
61
|
+
pendingRef.current = { scrollTop: target.scrollTop, scrollLeft: target.scrollLeft, velocity };
|
|
62
|
+
|
|
63
|
+
if (rafRef.current !== null) cancelAnimationFrame(rafRef.current);
|
|
64
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
65
|
+
rafRef.current = null;
|
|
66
|
+
const { scrollTop, scrollLeft, velocity: v } = pendingRef.current;
|
|
67
|
+
setScrollState({
|
|
68
|
+
scrollTop,
|
|
69
|
+
scrollLeft,
|
|
70
|
+
overscanRows: velocityToOverscan(v, overscanRowCount),
|
|
71
|
+
});
|
|
35
72
|
});
|
|
36
73
|
|
|
74
|
+
// Decay back to the minimum overscan 200ms after scrolling stops
|
|
75
|
+
if (decayRef.current !== null) clearTimeout(decayRef.current);
|
|
76
|
+
decayRef.current = setTimeout(() => {
|
|
77
|
+
decayRef.current = null;
|
|
78
|
+
setScrollState(prev => ({ ...prev, overscanRows: overscanRowCount }));
|
|
79
|
+
}, 200);
|
|
80
|
+
|
|
37
81
|
if (onRowsScrollEnd) {
|
|
38
82
|
const { scrollTop, scrollHeight, clientHeight } = target;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (scrollHeight - scrollTop - clientHeight < scrollThreshold) {
|
|
83
|
+
if (scrollHeight - scrollTop - clientHeight < 100) {
|
|
42
84
|
onRowsScrollEnd({
|
|
43
85
|
visibleTop: scrollTop,
|
|
44
86
|
visibleBottom: scrollTop + clientHeight,
|
|
@@ -46,7 +88,12 @@ export function useGridScrollSync(params: UseGridScrollSyncParams): UseGridScrol
|
|
|
46
88
|
});
|
|
47
89
|
}
|
|
48
90
|
}
|
|
49
|
-
}, [onRowsScrollEnd]);
|
|
91
|
+
}, [onRowsScrollEnd, overscanRowCount]);
|
|
50
92
|
|
|
51
|
-
return {
|
|
93
|
+
return {
|
|
94
|
+
scrollTop: scrollState.scrollTop,
|
|
95
|
+
scrollLeft: scrollState.scrollLeft,
|
|
96
|
+
overscanRows: scrollState.overscanRows,
|
|
97
|
+
handleScroll,
|
|
98
|
+
};
|
|
52
99
|
}
|
|
@@ -14,6 +14,8 @@ export interface UseGridVirtualizationParams<R extends GridRowModel> {
|
|
|
14
14
|
checkboxSelection: boolean;
|
|
15
15
|
pinCheckboxColumn: boolean;
|
|
16
16
|
pinExpandColumn: boolean;
|
|
17
|
+
/** Adaptive overscan row count from useGridScrollSync. Defaults to 5. */
|
|
18
|
+
overscanRows?: number;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export interface GridVirtualizationResult<R extends GridRowModel = GridRowModel> {
|
|
@@ -57,6 +59,7 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
57
59
|
checkboxSelection,
|
|
58
60
|
pinCheckboxColumn,
|
|
59
61
|
pinExpandColumn,
|
|
62
|
+
overscanRows = 5,
|
|
60
63
|
} = params;
|
|
61
64
|
|
|
62
65
|
return useMemo<GridVirtualizationResult<R>>(() => {
|
|
@@ -120,7 +123,6 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
120
123
|
const scrollbarBuffer = hasHorizontalScroll ? 16 : 0;
|
|
121
124
|
const totalHeight = pinnedTopHeight + unpinnedRowsHeight + pinnedBottomHeight + scrollbarBuffer + 2;
|
|
122
125
|
|
|
123
|
-
const overscanRows = 5;
|
|
124
126
|
let firstRowIndex = 0;
|
|
125
127
|
let lastRowIndex = unpinnedRowsLength - 1;
|
|
126
128
|
let offsetTop = 0;
|
|
@@ -182,5 +184,6 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
182
184
|
checkboxSelection,
|
|
183
185
|
pinCheckboxColumn,
|
|
184
186
|
pinExpandColumn,
|
|
187
|
+
overscanRows,
|
|
185
188
|
]);
|
|
186
189
|
}
|
package/lib/types/index.ts
CHANGED
|
@@ -608,6 +608,13 @@ export interface DataGridProps<R extends GridRowModel = GridRowModel> {
|
|
|
608
608
|
headerHeight?: number;
|
|
609
609
|
/** If true, the grid height will adjust to match the total height of its rows. */
|
|
610
610
|
autoHeight?: boolean;
|
|
611
|
+
/**
|
|
612
|
+
* Minimum number of rows to render outside the visible viewport (overscan buffer).
|
|
613
|
+
* The grid adapts this value upward automatically based on scroll velocity to prevent
|
|
614
|
+
* blank flashes during fast scrolling. This prop sets the floor — the buffer never
|
|
615
|
+
* drops below this value regardless of scroll speed. Defaults to `3`.
|
|
616
|
+
*/
|
|
617
|
+
overscanRowCount?: number;
|
|
611
618
|
|
|
612
619
|
/** Sorting mode: 'client' (default) or 'server'. */
|
|
613
620
|
sortingMode?: 'client' | 'server';
|
package/llms.txt
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencorestack/opengridx",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/opengridx.umd.js",
|