@opencorestack/opengridx 3.5.0 → 3.5.1
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 +12 -0
- package/README.md +4 -3
- package/dist/opengridx.es.js +3707 -3657
- 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/architecture/datagrid-orchestration.md +2 -1
- package/docs/features/virtualization.md +5 -5
- package/docs/performance.md +212 -1
- package/docs/roadmap.md +1 -1
- package/lib/components/DataGrid/DataGrid.tsx +8 -1
- package/lib/hooks/core/useGridDevWarnings.ts +21 -6
- package/lib/hooks/core/useGridMaxContentHeight.ts +16 -0
- package/lib/hooks/core/useGridVirtualization.ts +19 -0
- package/lib/utils/maxElementHeight.ts +53 -0
- package/llms.txt +1 -1
- package/package.json +3 -2
|
@@ -71,7 +71,8 @@ in an effect, so **keep the call order when you move or add a hook**. In particu
|
|
|
71
71
|
| 31 | `useGridScrollToIndexesApi` | `hooks/core/useGridScrollToIndexesApi.ts` | `scrollToIndexes` |
|
|
72
72
|
| 31a | `useGridColumnAutosize` | `hooks/core/useGridColumnAutosize.ts` | `autosizeColumn` / `autosizeColumns` on the API, and the resize handle's auto-size handler (v3.1) |
|
|
73
73
|
| 32 | `useGridViewport` | `hooks/core/useGridViewport.ts` | Scroll sync, viewport measurement, viewport callback ref |
|
|
74
|
-
|
|
|
74
|
+
| 32a | `useGridMaxContentHeight` | `hooks/core/useGridMaxContentHeight.ts` | The tallest element this browser lays out (`utils/maxElementHeight.ts`, measured once per document after mount; `undefined` until then, `Infinity` without a layout engine) |
|
|
75
|
+
| 33 | `useGridVirtualization` | `hooks/core/useGridVirtualization.ts` | Render window; `virtualContainerHeight` caps the rows' container to `maxContentHeight` (Firefox collapses taller elements) |
|
|
75
76
|
| 34 | `useGridSortHandlers` | `hooks/core/useGridHeaderHandlers.ts` | Header sort / shift-sort |
|
|
76
77
|
| 35 | `useGridKeyboardNavigation` | `hooks/core/useGridKeyboardNavigation.ts` | Focus and keyboard handling; takes `cellSelection.keyboard` (Shift+navigation, Escape, Ctrl/Cmd+A) |
|
|
77
78
|
| 36 | `useGridPointerFocusHandlers` | `hooks/core/useGridFocusHandlers.ts` | Cell and header click focus |
|
|
@@ -29,11 +29,11 @@ The grid calculates which rows are visible based on:
|
|
|
29
29
|
/>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
**Performance characteristics** (when the grid has a bounded height — see below):
|
|
33
|
-
- ✅
|
|
34
|
-
- ✅
|
|
35
|
-
- ✅
|
|
36
|
-
- ⚠️ Not millions of rows. Browsers cap an element's height
|
|
32
|
+
**Performance characteristics** (when the grid has a bounded height — see below), measured by the [benchmark](../performance.md#benchmarks):
|
|
33
|
+
- ✅ About 19 row elements at rest in an 800 px page at 52 px rows, at most about 86 while scrolling fast, whatever the row count (10k to 1M)
|
|
34
|
+
- ✅ Scrolling keeps up with a 60 Hz display at 1M rows in Chromium (no missed frames) and Firefox; WebKit misses a frame or two on each long jump of the scroll position
|
|
35
|
+
- ✅ The grid itself adds about 10 MB of JS heap at 100k rows and 79 MB at 1M (Chromium), on top of your row data
|
|
36
|
+
- ⚠️ Not millions of rows at the default height. Browsers cap an element's height and the grid does no scroll scaling: it lays every row out at its real height. Chromium and WebKit stop at 33,554,428 px, so rows past about `33,554,428 / rowHeight` cannot be scrolled to (645,277 at the default 52 px; 1M rows fit at 32 px). Firefox's limit is about 17.9M px (about 344,000 rows at 52 px); since 3.5.1 the grid caps its content to the limit it measures, so Firefox scrolls normally up to there (on 3.5.0 and earlier, content past the limit collapsed and the grid did not scroll at all). Beyond that, use pagination, or server-side paging / infinite scroll. In development the grid warns when rows are cut off and names how many can be scrolled to. Measured reach per browser: [Performance → Reach](../performance.md#reach-the-browser-height-ceiling).
|
|
37
37
|
|
|
38
38
|
### Column Virtualization
|
|
39
39
|
|
package/docs/performance.md
CHANGED
|
@@ -87,7 +87,12 @@ import '@opencorestack/opengridx/styles';
|
|
|
87
87
|
|
|
88
88
|
## Know the browser's height limit
|
|
89
89
|
|
|
90
|
-
The grid sizes its scroll content to `rowCount × rowHeight` pixels and does not scale scroll positions. Browsers cap an element's height
|
|
90
|
+
The grid sizes its scroll content to `rowCount × rowHeight` pixels and does not scale scroll positions. Browsers cap an element's height, and the [benchmark](#reach-the-browser-height-ceiling) measures what happens past the cap:
|
|
91
|
+
|
|
92
|
+
- **Chromium and WebKit** clamp the content at 33,554,428 px. Rows below that cannot be scrolled to: at the default 52 px row height that is every row past 645,277.
|
|
93
|
+
- **Firefox** has a lower limit, about 17.9M px, and collapses a taller element instead of clamping it. Up to 3.5.0 that made a grid with more content unscrollable in Firefox (only the first screen of rows was reachable). **Since 3.5.1 the grid measures the browser's limit once and caps its scroll content to it**, so Firefox scrolls normally and reaches the rows above the cap: about 344,000 rows at 52 px, 559,000 at 32 px.
|
|
94
|
+
|
|
95
|
+
In development the grid warns when rows are cut off, naming how many can be scrolled to in that browser. For more rows, use pagination, `paginationMode="server"` or infinite scroll with a `dataSource`, or a smaller `rowHeight` (`density="compact"`, 32 px, keeps 1M rows under the Chromium and WebKit cap).
|
|
91
96
|
|
|
92
97
|
## Use server-side operations for very large datasets
|
|
93
98
|
|
|
@@ -116,9 +121,215 @@ Without a `dataSource`, fetch the rows yourself from the change callbacks and co
|
|
|
116
121
|
In server modes the grid shows `rows` as given: it does not re-filter, re-sort or slice them (v3.0+; earlier versions did unless a `dataSource` was set).
|
|
117
122
|
|
|
118
123
|
|
|
124
|
+
## Benchmarks
|
|
125
|
+
|
|
126
|
+
Measured with `npm run bench` (`bench/`), which installs the packed npm package into a production Vite + React 19 app and drives it with Playwright in Chromium, Firefox and WebKit. The numbers below are from one machine (Apple M1, 8 GB RAM): compare them with each other and with your own runs, not with other machines.
|
|
127
|
+
|
|
128
|
+
**In short** (Chromium, median):
|
|
129
|
+
|
|
130
|
+
| Rows | Mount | Sort (number / string) | Filter | Quick filter | Group + sum | Scroll frames over 16.7 ms | JS heap |
|
|
131
|
+
| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
|
|
132
|
+
| 10,000 | 79 ms | 34 / 42 ms | 19 ms | 52 ms | 30 ms | 0 % | 2.9 MB |
|
|
133
|
+
| 100,000 | 89 ms | 102 / 160 ms | 28 ms | 205 ms | 87 ms | 0 % | 10.4 MB |
|
|
134
|
+
| 500,000 | 295 ms | 493 / 641 ms | 59 ms | 958 ms | 331 ms | 0 % | 39.4 MB |
|
|
135
|
+
| 1,000,000 | 688 ms | 1,105 / 1,316 ms | 95 ms | 1,845 ms | 681 ms | 0 % | 79.1 MB |
|
|
136
|
+
|
|
137
|
+
- **Scrolling keeps up with the display at every size.** Chromium missed no frames; Firefox missed 0–2.4 %. The DOM holds about 19 row elements at rest (28 at 32 px) and at most 86 while scrolling fast, whatever the row count. WebKit scrolls steadily without missed frames too, but misses one or two frames each time the scroll position jumps far (the *during the jumps* rows below). Firefox's scroll numbers at 500k and 1M rows are not comparable: the grid cannot scroll there (see Reach).
|
|
138
|
+
- **Sorting, the quick filter and grouping run on the main thread in one piece.** Up to 100k rows they finish in a few frames; at 1M a sort blocks the page for about 1.1–1.3 s in Chromium (up to 2.9 s for a string sort in WebKit), and the first quick-filter keystroke for about 2–3 s. For sorting or searching a million rows interactively, use server-side sorting and filtering.
|
|
139
|
+
- **Reach:** past the browser's height cap rows cannot be scrolled to, and in Firefox the grid stops scrolling altogether. See [Reach](#reach-the-browser-height-ceiling).
|
|
140
|
+
|
|
141
|
+
### Method
|
|
142
|
+
|
|
143
|
+
- **The app is the built package.** `bench/app` installs the tarball from `npm pack`, like a consumer, and is a production build (no React development checks).
|
|
144
|
+
- **Data:** 12 columns (`id`, two strings, two `singleSelect`, four numbers of which two have a `valueFormatter`, a date, a boolean, a short code), `id` and `name` pinned left. Rows come from a seeded random generator in the page and are generated before any timer starts.
|
|
145
|
+
- **Page:** 1280×800, the grid fills it, headless browsers launched by Playwright, one new browser context per run.
|
|
146
|
+
- **Timing:** each operation is timed from the call (`root.render`, `apiRef.setSortModel`, `setFilterModel`, `setRowGroupingModel` + `setAggregationModel`) to the first painted frame that shows its result: the page waits on `requestAnimationFrame` until the grid's DOM shows the result, then one more frame so that frame has been painted. The time therefore includes React's render and commit, style, layout and paint, and up to one frame of waiting for the next refresh. The method is described in detail at the top of `bench/app/src/harness.tsx`.
|
|
147
|
+
- **Operations:** sort ascending on `amount` (number) and on `name` (string); filter `name contains "nova"` (6 % of rows) and `amount > 5000` (50 %); one quick-filter term over all columns (the first search reads and formats every cell, so this is the first keystroke's cost); group by `region` (8 groups) with `sum` of `amount`. Each is undone before the next.
|
|
148
|
+
- **Scroll:** 5 s driven from `requestAnimationFrame`: 3 s at a constant 2,400 px/s, then a jump to a random position anywhere in the scrollable range every 100 ms. Frame times are the gaps between consecutive animation frames. Headless browsers do not pace frames by a display, so idle frames already vary between about 16.6 and 18 ms (17–21 ms in WebKit); a frame therefore counts as *over 16.7 ms* when it took more than 1.5 refresh intervals (25 ms at 60 Hz, a missed refresh) and as *over 33 ms* above 2.5 intervals (41.7 ms, two or more missed).
|
|
149
|
+
- **Chromium only:** long tasks (`PerformanceObserver('longtask')`; Firefox and WebKit have no such entries) and the JS heap (Chrome DevTools Protocol `Runtime.getHeapUsage` after a forced garbage collection, after mount minus the same page with the rows generated and no grid, so it is what the grid adds on top of your data).
|
|
150
|
+
- **DOM size:** row elements and cells at the end of the scroll test, and row elements after scrolling back to the top and letting the grid settle.
|
|
151
|
+
- **Repeats:** 6 runs per row count and engine; the first is discarded as warm-up, the tables show the median of the other 5 and the min–max range.
|
|
152
|
+
- **Machine load:** the runner records the load average before and after. This machine had a steady load of about 3.5–4 from desktop apps (an IDE, a browser) during the run; nothing else heavy ran.
|
|
153
|
+
|
|
154
|
+
### Results
|
|
155
|
+
|
|
156
|
+
<!-- bench:start -->
|
|
157
|
+
<!-- Generated by `node bench/report.mjs --write docs/performance.md` from bench/results/2026-10-09-apple-m1-darwin.json. Do not edit by hand. -->
|
|
158
|
+
|
|
159
|
+
- **Machine:** Apple M1, 8 cores, 8 GB RAM, macOS 26.5.1 (arm64)
|
|
160
|
+
- **Browsers:** Chromium 145.0.7632.6, Firefox 146.0.1, WebKit 26.0 (Playwright, headless, 1280×800 page)
|
|
161
|
+
- **Package:** @opencorestack/opengridx 3.5.0 (commit `f2ad5cb`), production build
|
|
162
|
+
- **Date:** 2026-10-09; load average at start 3.88 / 3.73 / 4.04, at end 3.46 / 3.71 / 3.77
|
|
163
|
+
- **Runs:** median of 5 runs after one discarded warm-up run, with the min–max range in brackets
|
|
164
|
+
|
|
165
|
+
¹ Chromium only: Firefox and WebKit have no long-task entries, and the heap is read through the Chrome DevTools Protocol.
|
|
166
|
+
|
|
167
|
+
#### 10,000 rows, 52 px rows
|
|
168
|
+
|
|
169
|
+
| Metric | Chromium | Firefox | WebKit |
|
|
170
|
+
| :--- | ---: | ---: | ---: |
|
|
171
|
+
| Mount | 79 ms (59–88) | 84 ms (82–85) | 116 ms (110–128) |
|
|
172
|
+
| Sort, number column | 34 ms (28–38) | 44 ms (43–46) | 81 ms (72–85) |
|
|
173
|
+
| Sort, string column | 42 ms (22–45) | 56 ms (36–58) | 83 ms (76–108) |
|
|
174
|
+
| Filter, string `contains` | 19 ms (14–31) | 31 ms (26–40) | 51 ms (47–57) |
|
|
175
|
+
| Filter, number `>` | 18 ms (13–19) | 25 ms (24–28) | 37 ms (34–39) |
|
|
176
|
+
| Quick filter, one term | 52 ms (33–60) | 76 ms (68–83) | 96 ms (84–110) |
|
|
177
|
+
| Group by region + `sum` | 30 ms (22–37) | 41 ms (31–47) | 77 ms (72–126) |
|
|
178
|
+
| Scroll frame time p50 | 16.7 ms | 16.7 ms | 17.0 ms |
|
|
179
|
+
| Scroll frame time p95 | 17.6 ms (17.5–17.6) | 17.6 ms (17.6–18.6) | 45.0 ms (43.0–46.0) |
|
|
180
|
+
| Scroll frame time p99 | 17.6 ms (17.6–17.7) | 33.3 ms (33.3–34.1) | 49.0 ms (47.0–58.0) |
|
|
181
|
+
| Scroll frames over 16.7 ms | 0.0 % (0.0–0.3) | 1.7 % (1.4–4.2) | 8.6 % (7.6–9.7) |
|
|
182
|
+
| Scroll frames over 33 ms | 0.0 % | 0.0 % (0.0–0.7) | 6.5 % (5.4–7.1) |
|
|
183
|
+
| … over 16.7 ms while scrolling steadily | 0.0 % | 0.6 % (0.0–4.7) | 0.0 % |
|
|
184
|
+
| … over 16.7 ms during the jumps | 0.0 % (0.0–0.8) | 3.5 % (2.6–4.3) | 26.1 % (21.9–29.9) |
|
|
185
|
+
| Long tasks during scroll ¹ | 0 | – | – |
|
|
186
|
+
| Longest task during a sort ¹ | none | – | – |
|
|
187
|
+
| JS heap for the grid ¹ | 2.9 MB | – | – |
|
|
188
|
+
| Row elements in the DOM, end of scroll | 76 | 76 | 76 |
|
|
189
|
+
| Cells in the DOM, end of scroll | 912 | 912 | 912 |
|
|
190
|
+
| Row elements in the DOM, at rest | 19 | 19 | 19 |
|
|
191
|
+
| Reach: last row on screen at the bottom | all rows | all rows | all rows |
|
|
192
|
+
|
|
193
|
+
#### 100,000 rows, 52 px rows
|
|
194
|
+
|
|
195
|
+
| Metric | Chromium | Firefox | WebKit |
|
|
196
|
+
| :--- | ---: | ---: | ---: |
|
|
197
|
+
| Mount | 89 ms (86–100) | 138 ms (133–155) | 157 ms (138–204) |
|
|
198
|
+
| Sort, number column | 102 ms (85–119) | 129 ms (120–136) | 159 ms (126–237) |
|
|
199
|
+
| Sort, string column | 160 ms (152–165) | 213 ms (180–224) | 241 ms (211–259) |
|
|
200
|
+
| Filter, string `contains` | 28 ms (20–34) | 42 ms (28–46) | 56 ms (47–109) |
|
|
201
|
+
| Filter, number `>` | 33 ms (27–39) | 39 ms (24–57) | 48 ms (31–60) |
|
|
202
|
+
| Quick filter, one term | 205 ms (193–232) | 348 ms (322–359) | 323 ms (269–340) |
|
|
203
|
+
| Group by region + `sum` | 87 ms (81–91) | 107 ms (99–109) | 118 ms (105–143) |
|
|
204
|
+
| Scroll frame time p50 | 16.7 ms | 16.7 ms | 17.0 ms |
|
|
205
|
+
| Scroll frame time p95 | 17.4 ms (17.3–17.5) | 17.5 ms (17.4–17.6) | 44.0 ms (43.0–45.0) |
|
|
206
|
+
| Scroll frame time p99 | 17.6 ms (17.6–17.7) | 33.3 ms | 48.0 ms (47.0–55.0) |
|
|
207
|
+
| Scroll frames over 16.7 ms | 0.0 % (0.0–0.3) | 2.4 % (1.0–2.7) | 7.9 % (7.1–9.3) |
|
|
208
|
+
| Scroll frames over 33 ms | 0.0 % | 0.0 % (0.0–0.3) | 6.4 % (5.4–7.0) |
|
|
209
|
+
| … over 16.7 ms while scrolling steadily | 0.0 % | 0.6 % (0.0–1.7) | 0.0 % (0.0–0.6) |
|
|
210
|
+
| … over 16.7 ms during the jumps | 0.0 % (0.0–0.8) | 4.3 % (1.7–6.2) | 22.7 % (20.2–26.1) |
|
|
211
|
+
| Long tasks during scroll ¹ | 0 | – | – |
|
|
212
|
+
| Longest task during a sort ¹ | 155 ms (144–160) | – | – |
|
|
213
|
+
| JS heap for the grid ¹ | 10.4 MB | – | – |
|
|
214
|
+
| Row elements in the DOM, end of scroll | 76 | 76 | 76 |
|
|
215
|
+
| Cells in the DOM, end of scroll | 912 | 912 | 912 |
|
|
216
|
+
| Row elements in the DOM, at rest | 19 | 19 | 19 |
|
|
217
|
+
| Reach: last row on screen at the bottom | all rows | all rows | all rows |
|
|
218
|
+
|
|
219
|
+
#### 500,000 rows, 52 px rows
|
|
220
|
+
|
|
221
|
+
| Metric | Chromium | Firefox | WebKit |
|
|
222
|
+
| :--- | ---: | ---: | ---: |
|
|
223
|
+
| Mount | 295 ms (287–307) | 482 ms (473–505) | 402 ms (338–463) |
|
|
224
|
+
| Sort, number column | 493 ms (485–567) | 660 ms (629–936) | 483 ms (413–564) |
|
|
225
|
+
| Sort, string column | 641 ms (600–807) | 1,105 ms (1,045–1,188) | 1,407 ms (1,203–1,568) |
|
|
226
|
+
| Filter, string `contains` | 59 ms (51–67) | 63 ms (56–68) | 75 ms (72–87) |
|
|
227
|
+
| Filter, number `>` | 80 ms (70–111) | 100 ms (92–111) | 89 ms (77–101) |
|
|
228
|
+
| Quick filter, one term | 958 ms (836–976) | 1,565 ms (1,503–1,828) | 1,442 ms (1,275–1,619) |
|
|
229
|
+
| Group by region + `sum` | 331 ms (307–338) | 641 ms (419–675) | 386 ms (351–1,244) |
|
|
230
|
+
| Scroll frame time p50 | 16.7 ms | 16.7 ms | 17.0 ms |
|
|
231
|
+
| Scroll frame time p95 | 17.6 ms (17.5–17.6) | 17.5 ms (17.5–17.6) | 45.0 ms (44.0–46.0) |
|
|
232
|
+
| Scroll frame time p99 | 17.7 ms (17.6–17.7) | 17.6 ms (17.6–17.7) | 49.0 ms (46.0–50.0) |
|
|
233
|
+
| Scroll frames over 16.7 ms | 0.0 % (0.0–0.7) | 0.0 % | 7.7 % (7.6–10.1) |
|
|
234
|
+
| Scroll frames over 33 ms | 0.0 % (0.0–0.3) | 0.0 % | 7.3 % (5.8–7.8) |
|
|
235
|
+
| … over 16.7 ms while scrolling steadily | 0.0 % | 0.0 % | 0.0 % (0.0–1.1) |
|
|
236
|
+
| … over 16.7 ms during the jumps | 0.0 % (0.0–1.8) | 0.0 % | 23.1 % (20.6–28.4) |
|
|
237
|
+
| Long tasks during scroll ¹ | 0 | – | – |
|
|
238
|
+
| Longest task during a sort ¹ | 635 ms (595–799) | – | – |
|
|
239
|
+
| JS heap for the grid ¹ | 39.4 MB | – | – |
|
|
240
|
+
| Row elements in the DOM, end of scroll | 76 | 22 | 76 |
|
|
241
|
+
| Cells in the DOM, end of scroll | 912 | 264 | 912 |
|
|
242
|
+
| Row elements in the DOM, at rest | 19 | 19 | 19 |
|
|
243
|
+
| Reach: last row on screen at the bottom | all rows | 24 (<0.1 %) | all rows |
|
|
244
|
+
|
|
245
|
+
#### 1,000,000 rows, 52 px rows
|
|
246
|
+
|
|
247
|
+
| Metric | Chromium | Firefox | WebKit |
|
|
248
|
+
| :--- | ---: | ---: | ---: |
|
|
249
|
+
| Mount | 688 ms (673–715) | 1,097 ms (1,011–1,503) | 589 ms (576–735) |
|
|
250
|
+
| Sort, number column | 1,105 ms (1,094–1,140) | 1,540 ms (1,477–1,795) | 1,023 ms (956–1,075) |
|
|
251
|
+
| Sort, string column | 1,316 ms (1,227–1,463) | 2,434 ms (2,371–2,716) | 2,913 ms (2,715–3,277) |
|
|
252
|
+
| Filter, string `contains` | 95 ms (87–152) | 100 ms (90–108) | 102 ms (102–112) |
|
|
253
|
+
| Filter, number `>` | 144 ms (129–149) | 192 ms (175–237) | 136 ms (123–242) |
|
|
254
|
+
| Quick filter, one term | 1,845 ms (1,761–1,977) | 3,161 ms (2,920–3,327) | 2,914 ms (2,762–3,272) |
|
|
255
|
+
| Group by region + `sum` | 681 ms (664–703) | 1,754 ms (1,695–2,059) | 764 ms (720–901) |
|
|
256
|
+
| Scroll frame time p50 | 16.7 ms | 16.7 ms | 17.0 ms |
|
|
257
|
+
| Scroll frame time p95 | 17.4 ms (17.4–17.6) | 17.6 ms (17.4–17.6) | 44.0 ms (44.0–46.0) |
|
|
258
|
+
| Scroll frame time p99 | 17.7 ms (17.6–17.7) | 17.6 ms (17.6–17.7) | 46.0 ms (45.0–48.0) |
|
|
259
|
+
| Scroll frames over 16.7 ms | 0.0 % (0.0–0.7) | 0.0 % (0.0–0.3) | 7.2 % (7.1–9.3) |
|
|
260
|
+
| Scroll frames over 33 ms | 0.0 % | 0.0 % | 7.0 % (6.5–7.1) |
|
|
261
|
+
| … over 16.7 ms while scrolling steadily | 0.0 % | 0.0 % (0.0–0.6) | 0.0 % |
|
|
262
|
+
| … over 16.7 ms during the jumps | 0.0 % (0.0–1.7) | 0.0 % | 20.8 % (20.0–28.1) |
|
|
263
|
+
| Long tasks during scroll ¹ | 0 | – | – |
|
|
264
|
+
| Longest task during a sort ¹ | 1,307 ms (1,220–1,455) | – | – |
|
|
265
|
+
| JS heap for the grid ¹ | 79.1 MB | – | – |
|
|
266
|
+
| Row elements in the DOM, end of scroll | 77 | 22 | 77 |
|
|
267
|
+
| Cells in the DOM, end of scroll | 924 | 264 | 924 |
|
|
268
|
+
| Row elements in the DOM, at rest | 19 | 19 | 19 |
|
|
269
|
+
| Reach: last row on screen at the bottom | 645,277 (64.5 %) | 24 (<0.1 %) | 645,277 (64.5 %) |
|
|
270
|
+
|
|
271
|
+
#### 1,000,000 rows, 32 px rows
|
|
272
|
+
|
|
273
|
+
| Metric | Chromium | Firefox | WebKit |
|
|
274
|
+
| :--- | ---: | ---: | ---: |
|
|
275
|
+
| Mount | 674 ms (668–680) | 1,106 ms (1,032–1,329) | 604 ms (585–621) |
|
|
276
|
+
| Sort, number column | 1,143 ms (1,090–1,254) | 1,475 ms (1,462–1,837) | 1,077 ms (986–1,137) |
|
|
277
|
+
| Sort, string column | 1,295 ms (1,236–1,456) | 2,517 ms (2,268–2,675) | 2,758 ms (2,720–2,947) |
|
|
278
|
+
| Filter, string `contains` | 92 ms (91–109) | 99 ms (99–109) | 107 ms (106–121) |
|
|
279
|
+
| Filter, number `>` | 137 ms (133–140) | 206 ms (172–232) | 140 ms (126–161) |
|
|
280
|
+
| Quick filter, one term | 1,818 ms (1,651–1,855) | 2,960 ms (2,910–3,200) | 2,750 ms (2,677–2,776) |
|
|
281
|
+
| Group by region + `sum` | 694 ms (673–758) | 2,071 ms (1,224–2,745) | 749 ms (689–798) |
|
|
282
|
+
| Scroll frame time p50 | 16.7 ms | 16.7 ms | 17.0 ms |
|
|
283
|
+
| Scroll frame time p95 | 17.5 ms (17.5–18.5) | 17.5 ms (17.3–17.5) | 49.0 ms (48.0–49.0) |
|
|
284
|
+
| Scroll frame time p99 | 17.6 ms (17.6–18.7) | 17.6 ms (17.6–17.7) | 52.0 ms (51.0–52.0) |
|
|
285
|
+
| Scroll frames over 16.7 ms | 0.0 % (0.0–0.3) | 0.0 % (0.0–0.3) | 15.5 % (15.4–15.8) |
|
|
286
|
+
| Scroll frames over 33 ms | 0.0 % | 0.0 % | 7.7 % (7.7–7.8) |
|
|
287
|
+
| … over 16.7 ms while scrolling steadily | 0.0 % (0.0–0.6) | 0.0 % (0.0–0.6) | 0.0 % (0.0–0.6) |
|
|
288
|
+
| … over 16.7 ms during the jumps | 0.0 % | 0.0 % | 51.3 % (50.6–53.3) |
|
|
289
|
+
| Long tasks during scroll ¹ | 0 | – | – |
|
|
290
|
+
| Longest task during a sort ¹ | 1,286 ms (1,229–1,436) | – | – |
|
|
291
|
+
| JS heap for the grid ¹ | 79.5 MB | – | – |
|
|
292
|
+
| Row elements in the DOM, end of scroll | 86 | 32 | 86 |
|
|
293
|
+
| Cells in the DOM, end of scroll | 1,032 | 384 | 1,032 |
|
|
294
|
+
| Row elements in the DOM, at rest | 28 | 28 | 28 |
|
|
295
|
+
| Reach: last row on screen at the bottom | all rows | 33 (<0.1 %) | all rows |
|
|
296
|
+
|
|
297
|
+
#### Reach: rows that can be scrolled into view
|
|
298
|
+
|
|
299
|
+
| Rows × row height | Content height | Chromium | Firefox | WebKit |
|
|
300
|
+
| :--- | ---: | ---: | ---: | ---: |
|
|
301
|
+
| 10,000 × 52 px | 520,000 px | all | all | all |
|
|
302
|
+
| 100,000 × 52 px | 5,200,000 px | all | all | all |
|
|
303
|
+
| 500,000 × 52 px | 26,000,000 px | all | 24 (<0.1 %) | all |
|
|
304
|
+
| 1,000,000 × 52 px | 52,000,000 px | 645,277 (64.5 %) | 24 (<0.1 %) | 645,277 (64.5 %) |
|
|
305
|
+
| 1,000,000 × 32 px | 32,000,000 px | all | 33 (<0.1 %) | all |
|
|
306
|
+
<!-- bench:end -->
|
|
307
|
+
|
|
308
|
+
### Reach: the browser height ceiling
|
|
309
|
+
|
|
310
|
+
The grid makes its scroll area as tall as all the rows: `rowCount × rowHeight` pixels. It does not compress scroll positions, so every row keeps its real offset. Browsers limit how tall an element can be, and the *Reach* rows above report the last row that actually appears on screen after scrolling to the very bottom:
|
|
311
|
+
|
|
312
|
+
- **Chromium and WebKit** stop the element at 33,554,428 px. Everything above that is reachable, everything below is not. At 52 px that is 645,277 rows, so 1M rows at 52 px reach 64.5 %. At 32 px (`density="compact"`), 1M rows need 32,000,000 px and are all reachable.
|
|
313
|
+
- **Firefox** has a lower limit, about 17.9M px, and handles it differently: an element taller than that collapses instead of stopping at the limit. On 3.5.0, which these tables were measured on, the grid then had nothing to scroll: 500k rows at 52 px (26M px) and 1M rows at either height showed only their first screen of rows (24 or 33 rows). **3.5.1 fixes this**: the grid caps its scroll content to the limit it measures, so Firefox reaches about 344,000 rows at 52 px and 559,000 at 32 px, like Chromium and WebKit reach theirs.
|
|
314
|
+
|
|
315
|
+
If you need more rows than this in one scrolling grid, page them (`pagination`), load them from the server (`paginationMode="server"`, or infinite scroll with a `dataSource`), or reduce the row height.
|
|
316
|
+
|
|
317
|
+
### Rerun
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
npx playwright install chromium firefox webkit # once
|
|
321
|
+
npm run bench # all engines, 10k/100k/500k/1M, about 30 min
|
|
322
|
+
npm run bench -- --quick # 1 run per size, no 500k, a few minutes
|
|
323
|
+
npm run bench -- --rows 100000 --engine firefox # one size, one engine
|
|
324
|
+
node bench/report.mjs # Markdown tables of the newest results file
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Results are written to `bench/results/<date>-<machine>.json` with the machine, browser versions and package version. Close other heavy programs first and keep a laptop on power. More options: [Testing → The benchmark](contributing/testing.md#the-benchmark). A weekly CI job runs a quick Chromium pass and keeps the JSON as a build artifact; CI machines are shared and noisy, so it is for spotting trends, not for comparing with these numbers.
|
|
328
|
+
|
|
119
329
|
## Related
|
|
120
330
|
|
|
121
331
|
- [Virtualization](./features/virtualization.md)
|
|
122
332
|
- [Server-Side Data](./features/data-source.md)
|
|
123
333
|
- [Sorting & Pagination](./features/sorting-pagination.md)
|
|
124
334
|
- [Infinite Scroll](./features/infinite-scroll.md)
|
|
335
|
+
- [Testing → The benchmark](./contributing/testing.md#the-benchmark)
|
package/docs/roadmap.md
CHANGED
|
@@ -89,7 +89,7 @@ This document tracks the current status of features in `OpenGridX` and outlines
|
|
|
89
89
|
|
|
90
90
|
### Across the quarter
|
|
91
91
|
|
|
92
|
-
* **Published benchmark
|
|
92
|
+
* ✅ **Published benchmark** (done 9 Oct): reproducible 10k / 100k / 500k / 1M-row numbers (scroll frame times, sort, filter and grouping time, memory, the reachable row) in [`docs/performance.md`](performance.md#benchmarks); rerun with `npm run bench`.
|
|
93
93
|
* **Next.js App Router guide**: using the grid from a `'use client'` component, with the stylesheet in `app/layout.tsx`.
|
|
94
94
|
* **RTL check**: verify right-to-left layouts and document the result.
|
|
95
95
|
* **Demo site**: source viewer on all examples (`DocsLayout` with Vite `?raw`) and syntax highlighting, done alongside each release's demo page.
|
|
@@ -4,6 +4,8 @@ import { useGridKeyboardNavigation } from '../../hooks/core/useGridKeyboardNavig
|
|
|
4
4
|
import { useGridControlledState } from '../../hooks/core/useGridControlledState';
|
|
5
5
|
import { useGridThemeDimensions } from '../../hooks/core/useGridThemeDimensions';
|
|
6
6
|
import { useGridRowPipeline, type GridRowPipelineResult } from '../../hooks/core/useGridRowPipeline';
|
|
7
|
+
import { useGridMaxContentHeight } from '../../hooks/core/useGridMaxContentHeight';
|
|
8
|
+
import { countReachableRows } from '../../utils/maxElementHeight';
|
|
7
9
|
import { useGridVirtualization } from '../../hooks/core/useGridVirtualization';
|
|
8
10
|
import { useGridColumns } from '../../hooks/core/useGridColumns';
|
|
9
11
|
import { useGridVisibleRows } from '../../hooks/core/useGridVisibleRows';
|
|
@@ -585,12 +587,14 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
585
587
|
onRowsScrollEnd, overscanRowCount, rowCount: layout.unpinnedRowsLength, autoHeight, setDimensions, viewportRef,
|
|
586
588
|
});
|
|
587
589
|
|
|
590
|
+
const maxContentHeight = useGridMaxContentHeight(containerRef);
|
|
588
591
|
const virtualization = useGridVirtualization({
|
|
589
592
|
layout, scrollTop, scrollLeft,
|
|
590
593
|
viewportWidth: state.dimensions.viewportWidth,
|
|
591
594
|
viewportHeight: state.dimensions.viewportHeight,
|
|
592
595
|
autoHeight, rowReordering, hasDetailPanel, checkboxSelection, pinCheckboxColumn, pinExpandColumn,
|
|
593
596
|
overscanRows,
|
|
597
|
+
maxContentHeight: maxContentHeight ?? Infinity,
|
|
594
598
|
});
|
|
595
599
|
|
|
596
600
|
|
|
@@ -743,6 +747,9 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
743
747
|
totalRowCount: pinnedTopRows.length + centerRows.length + pinnedBottomRows.length,
|
|
744
748
|
pinnedRowsIgnored: isHierarchyEnabled && Boolean(pinnedRows?.top?.length || pinnedRows?.bottom?.length),
|
|
745
749
|
scrollHeight: virtualization.totalHeight,
|
|
750
|
+
maxContentHeight,
|
|
751
|
+
reachableRowCount: countReachableRows(virtualization.cumulativeHeights, virtualization.virtualContainerHeight),
|
|
752
|
+
unpinnedRowCount: virtualization.cumulativeHeights.length,
|
|
746
753
|
listViewWithoutColumn: listViewRequested && !listViewColumn,
|
|
747
754
|
listViewRowCount: listView && listViewColumn ? allRenderableRows.length : 0,
|
|
748
755
|
});
|
|
@@ -1008,7 +1015,7 @@ export function DataGrid<R extends GridRowModel = GridRowModel>(props: DataGridP
|
|
|
1008
1015
|
|
|
1009
1016
|
<GridVirtualRows<R>
|
|
1010
1017
|
{...rowRenderProps}
|
|
1011
|
-
virtualContainerHeight={virtualization.
|
|
1018
|
+
virtualContainerHeight={virtualization.virtualContainerHeight}
|
|
1012
1019
|
offsetTop={spanRowWindow.offsetTop}
|
|
1013
1020
|
effectiveLoading={effectiveLoading}
|
|
1014
1021
|
visibleRows={visibleRows}
|
|
@@ -10,8 +10,16 @@ export interface UseGridDevWarningsParams {
|
|
|
10
10
|
totalRowCount: number;
|
|
11
11
|
/** `pinnedRows` is set while tree data or row grouping is active, where it has no effect. */
|
|
12
12
|
pinnedRowsIgnored?: boolean;
|
|
13
|
-
/** Pixel height of the scrollable content. */
|
|
13
|
+
/** Pixel height of the scrollable content, uncapped. */
|
|
14
14
|
scrollHeight?: number;
|
|
15
|
+
/**
|
|
16
|
+
* The tallest element the browser lays out (`getMaxElementHeight`): `undefined` until measured (the warning
|
|
17
|
+
* waits), `Infinity` where there is no layout engine (the lowest known cap is used).
|
|
18
|
+
*/
|
|
19
|
+
maxContentHeight?: number;
|
|
20
|
+
/** Unpinned rows the user can scroll to, and how many unpinned rows there are. */
|
|
21
|
+
reachableRowCount?: number;
|
|
22
|
+
unpinnedRowCount?: number;
|
|
15
23
|
/** `listView` is set but `listViewColumn` is not, so the grid view is shown. */
|
|
16
24
|
listViewWithoutColumn?: boolean;
|
|
17
25
|
/** Number of items the list view renders (0 when the list view is not shown). It does not virtualize. */
|
|
@@ -31,7 +39,7 @@ export const LIST_VIEW_WARN_THRESHOLD = 2000;
|
|
|
31
39
|
export const MAX_SCROLLABLE_HEIGHT = 17_800_000;
|
|
32
40
|
|
|
33
41
|
export function useGridDevWarnings(params: UseGridDevWarningsParams): void {
|
|
34
|
-
const { paginationRequested, isRowGrouping, autoHeight, viewportHeight, renderedRowCount, totalRowCount, pinnedRowsIgnored = false, scrollHeight = 0, listViewWithoutColumn = false, listViewRowCount = 0 } = params;
|
|
42
|
+
const { paginationRequested, isRowGrouping, autoHeight, viewportHeight, renderedRowCount, totalRowCount, pinnedRowsIgnored = false, scrollHeight = 0, maxContentHeight, reachableRowCount, unpinnedRowCount, listViewWithoutColumn = false, listViewRowCount = 0 } = params;
|
|
35
43
|
const warnedPaginationRef = useRef(false);
|
|
36
44
|
const warnedUnboundedRef = useRef(false);
|
|
37
45
|
const warnedPinnedRowsRef = useRef(false);
|
|
@@ -67,14 +75,21 @@ export function useGridDevWarnings(params: UseGridDevWarningsParams): void {
|
|
|
67
75
|
}, [pinnedRowsIgnored]);
|
|
68
76
|
|
|
69
77
|
useEffect(() => {
|
|
70
|
-
if (process.env.NODE_ENV === 'production' || warnedHeightRef.current ||
|
|
78
|
+
if (process.env.NODE_ENV === 'production' || warnedHeightRef.current || maxContentHeight === undefined) return;
|
|
79
|
+
const measured = Number.isFinite(maxContentHeight);
|
|
80
|
+
// Once the browser's cap is measured, warn only when rows are actually cut off; before that (and
|
|
81
|
+
// where there is no layout engine) fall back to the lowest known cap.
|
|
82
|
+
if (measured ? scrollHeight <= maxContentHeight : scrollHeight <= MAX_SCROLLABLE_HEIGHT) return;
|
|
71
83
|
warnedHeightRef.current = true;
|
|
84
|
+
const reach = measured && reachableRowCount !== undefined && unpinnedRowCount !== undefined
|
|
85
|
+
? `Only the first ${reachableRowCount.toLocaleString('en-US')} of ${unpinnedRowCount.toLocaleString('en-US')} rows can be scrolled to in this browser. `
|
|
86
|
+
: 'The last rows may not be reachable by scrolling. ';
|
|
72
87
|
console.warn(
|
|
73
|
-
`[OpenGridX] The grid content is ${Math.round(scrollHeight)}px tall
|
|
74
|
-
'(about 17.9M px in Firefox, 33.5M px in Chrome and Safari)
|
|
88
|
+
`[OpenGridX] The grid content is ${Math.round(scrollHeight)}px tall, more than this browser lays out ` +
|
|
89
|
+
'(about 17.9M px in Firefox, 33.5M px in Chrome and Safari). ' + reach +
|
|
75
90
|
'Use pagination, server-side or infinite loading, or a smaller rowHeight. See docs/features/virtualization.md.'
|
|
76
91
|
);
|
|
77
|
-
}, [scrollHeight]);
|
|
92
|
+
}, [scrollHeight, maxContentHeight, reachableRowCount, unpinnedRowCount]);
|
|
78
93
|
|
|
79
94
|
useEffect(() => {
|
|
80
95
|
if (process.env.NODE_ENV === 'production' || warnedPaginationRef.current) return;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import type { RefObject } from 'react';
|
|
3
|
+
import { getMaxElementHeight } from '../../utils/maxElementHeight';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The tallest element this browser lays out (see `getMaxElementHeight`), measured once per document after
|
|
7
|
+
* mount: `undefined` until measured, `Infinity` where there is no layout engine (jsdom, SSR).
|
|
8
|
+
*/
|
|
9
|
+
export function useGridMaxContentHeight(containerRef: RefObject<HTMLElement | null>): number | undefined {
|
|
10
|
+
const [maxHeight, setMaxHeight] = useState<number | undefined>(undefined);
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
const measured = getMaxElementHeight(containerRef.current?.ownerDocument);
|
|
13
|
+
setMaxHeight(prev => (prev === measured ? prev : measured));
|
|
14
|
+
}, [containerRef]);
|
|
15
|
+
return maxHeight;
|
|
16
|
+
}
|
|
@@ -16,6 +16,11 @@ export interface UseGridVirtualizationParams<R extends GridRowModel> {
|
|
|
16
16
|
pinExpandColumn: boolean;
|
|
17
17
|
/** Adaptive overscan row count from useGridScrollSync. Defaults to 5. */
|
|
18
18
|
overscanRows?: number;
|
|
19
|
+
/**
|
|
20
|
+
* The tallest element the browser lays out (`getMaxElementHeight`); the scrollable content is capped to
|
|
21
|
+
* it. Defaults to no cap.
|
|
22
|
+
*/
|
|
23
|
+
maxContentHeight?: number;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
26
|
export interface GridVirtualizationResult<R extends GridRowModel = GridRowModel> {
|
|
@@ -28,6 +33,11 @@ export interface GridVirtualizationResult<R extends GridRowModel = GridRowModel>
|
|
|
28
33
|
offsetTop: number;
|
|
29
34
|
offsetLeft: number;
|
|
30
35
|
totalHeight: number;
|
|
36
|
+
/**
|
|
37
|
+
* Height of the unpinned rows' container: their full height, capped so the whole content stays under
|
|
38
|
+
* `maxContentHeight`. Rows below the cap cannot be scrolled to (the dev warning names how many can).
|
|
39
|
+
*/
|
|
40
|
+
virtualContainerHeight: number;
|
|
31
41
|
pinnedTopHeight: number;
|
|
32
42
|
pinnedBottomHeight: number;
|
|
33
43
|
totalWidth: number;
|
|
@@ -60,6 +70,7 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
60
70
|
pinCheckboxColumn,
|
|
61
71
|
pinExpandColumn,
|
|
62
72
|
overscanRows = 5,
|
|
73
|
+
maxContentHeight = Infinity,
|
|
63
74
|
} = params;
|
|
64
75
|
|
|
65
76
|
return useMemo<GridVirtualizationResult<R>>(() => {
|
|
@@ -122,6 +133,12 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
122
133
|
const hasHorizontalScroll = viewportWidth > 0 && totalWidth > viewportWidth;
|
|
123
134
|
const scrollbarBuffer = hasHorizontalScroll ? 16 : 0;
|
|
124
135
|
const totalHeight = pinnedTopHeight + unpinnedRowsHeight + pinnedBottomHeight + scrollbarBuffer + 2;
|
|
136
|
+
// Firefox collapses an element taller than its cap (about 17.9M px) instead of clamping it, which
|
|
137
|
+
// made a large grid unscrollable there; keep the rows' container under the browser's cap.
|
|
138
|
+
const fullContainerHeight = totalHeight - pinnedTopHeight - pinnedBottomHeight;
|
|
139
|
+
const virtualContainerHeight = Number.isFinite(maxContentHeight)
|
|
140
|
+
? Math.max(0, Math.min(fullContainerHeight, maxContentHeight - pinnedTopHeight - pinnedBottomHeight))
|
|
141
|
+
: fullContainerHeight;
|
|
125
142
|
|
|
126
143
|
let firstRowIndex = 0;
|
|
127
144
|
let lastRowIndex = unpinnedRowsLength - 1;
|
|
@@ -157,6 +174,7 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
157
174
|
offsetTop,
|
|
158
175
|
offsetLeft: 0,
|
|
159
176
|
totalHeight,
|
|
177
|
+
virtualContainerHeight,
|
|
160
178
|
pinnedTopHeight,
|
|
161
179
|
pinnedBottomHeight,
|
|
162
180
|
totalWidth,
|
|
@@ -185,5 +203,6 @@ export function useGridVirtualization<R extends GridRowModel>(
|
|
|
185
203
|
pinCheckboxColumn,
|
|
186
204
|
pinExpandColumn,
|
|
187
205
|
overscanRows,
|
|
206
|
+
maxContentHeight,
|
|
188
207
|
]);
|
|
189
208
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tallest element this browser lays out. Browsers cap element heights: Chromium and Safari at
|
|
3
|
+
* about 33.5M px, Firefox at about 17.9M px. Firefox does not clamp an element that is taller — it
|
|
4
|
+
* collapses it, so a grid whose content is taller than the cap cannot be scrolled at all.
|
|
5
|
+
*
|
|
6
|
+
* Measured once per document with an invisible probe (one layout), from the largest candidate down.
|
|
7
|
+
* Returns `Infinity` where there is no layout engine (jsdom, SSR), so nothing is capped there.
|
|
8
|
+
*/
|
|
9
|
+
const CANDIDATES = [33_554_400, 17_895_000, 10_000_000, 5_000_000] as const;
|
|
10
|
+
|
|
11
|
+
const cache = new WeakMap<Document, number>();
|
|
12
|
+
|
|
13
|
+
function measure(doc: Document): number {
|
|
14
|
+
const body = doc.body;
|
|
15
|
+
if (!body) return Infinity;
|
|
16
|
+
const probe = doc.createElement('div');
|
|
17
|
+
probe.setAttribute('aria-hidden', 'true');
|
|
18
|
+
probe.style.cssText = 'position:absolute;top:0;left:-10000px;width:1px;visibility:hidden;pointer-events:none;';
|
|
19
|
+
body.appendChild(probe);
|
|
20
|
+
try {
|
|
21
|
+
probe.style.height = '100px';
|
|
22
|
+
// No layout engine: every element reads 0 tall.
|
|
23
|
+
if (Math.abs(probe.getBoundingClientRect().height - 100) > 1) return Infinity;
|
|
24
|
+
for (const height of CANDIDATES) {
|
|
25
|
+
probe.style.height = `${height}px`;
|
|
26
|
+
if (Math.abs(probe.getBoundingClientRect().height - height) < 2) return height;
|
|
27
|
+
}
|
|
28
|
+
return CANDIDATES[CANDIDATES.length - 1];
|
|
29
|
+
} finally {
|
|
30
|
+
probe.remove();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function getMaxElementHeight(doc: Document | null | undefined): number {
|
|
35
|
+
if (!doc) return Infinity;
|
|
36
|
+
const known = cache.get(doc);
|
|
37
|
+
if (known !== undefined) return known;
|
|
38
|
+
const height = measure(doc);
|
|
39
|
+
cache.set(doc, height);
|
|
40
|
+
return height;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** How many of the unpinned rows end at or above `limit` px (the rows a user can scroll to). */
|
|
44
|
+
export function countReachableRows(cumulativeHeights: readonly number[], limit: number): number {
|
|
45
|
+
if (!Number.isFinite(limit)) return cumulativeHeights.length;
|
|
46
|
+
let lo = 0;
|
|
47
|
+
let hi = cumulativeHeights.length;
|
|
48
|
+
while (lo < hi) {
|
|
49
|
+
const mid = (lo + hi) >> 1;
|
|
50
|
+
if (cumulativeHeights[mid] <= limit) lo = mid + 1; else hi = mid;
|
|
51
|
+
}
|
|
52
|
+
return lo;
|
|
53
|
+
}
|
package/llms.txt
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opencorestack/opengridx",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.1",
|
|
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",
|
|
@@ -53,12 +53,13 @@
|
|
|
53
53
|
"build": "tsc --project tsconfig.build.json && vite build",
|
|
54
54
|
"build:lib": "tsc --project tsconfig.build.json && vite build --config vite.config.lib.js && vite build --config vite.config.lib.js --mode ai && cp lib/styles/opengridx.css.d.ts dist/opengridx.css.d.ts && node scripts/check-bundle.mjs",
|
|
55
55
|
"lint": "eslint .",
|
|
56
|
-
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p e2e/tsconfig.json",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p e2e/tsconfig.json && tsc -p bench/tsconfig.json",
|
|
57
57
|
"preview": "vite preview",
|
|
58
58
|
"test": "vitest run --project unit",
|
|
59
59
|
"test:browser": "vitest run --project browser",
|
|
60
60
|
"test:all": "vitest run",
|
|
61
61
|
"test:smoke": "node scripts/smoke.mjs",
|
|
62
|
+
"bench": "node bench/run.mjs",
|
|
62
63
|
"test:watch": "vitest --project unit",
|
|
63
64
|
"coverage": "vitest run --coverage",
|
|
64
65
|
"prepublishOnly": "npm run build:lib",
|