@versini/ui-datagrid 0.3.8 → 0.4.0
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/README.md +106 -29
- package/dist/DataGrid/DataGrid.js +1 -1
- package/dist/DataGrid/DataGridContext.js +1 -1
- package/dist/DataGrid/index.js +1 -1
- package/dist/DataGridAnimated/AnimatedWrapper.d.ts +11 -7
- package/dist/DataGridAnimated/AnimatedWrapper.js +12 -8
- package/dist/DataGridAnimated/index.js +1 -1
- package/dist/DataGridAnimated/useAnimatedHeight.js +1 -1
- package/dist/DataGridBody/DataGridBody.js +12 -56
- package/dist/DataGridBody/getBodyClass.d.ts +9 -0
- package/dist/DataGridBody/getBodyClass.js +23 -0
- package/dist/DataGridBody/index.js +1 -1
- package/dist/DataGridBody/useColumnMeasurement.d.ts +10 -0
- package/dist/DataGridBody/useColumnMeasurement.js +67 -0
- package/dist/DataGridCell/DataGridCell.js +1 -1
- package/dist/DataGridCell/index.js +1 -1
- package/dist/DataGridCellSort/DataGridCellSort.js +1 -1
- package/dist/DataGridCellSort/index.js +1 -1
- package/dist/DataGridConstants/DataGridConstants.js +1 -1
- package/dist/DataGridConstants/index.js +1 -1
- package/dist/DataGridFooter/DataGridFooter.js +1 -1
- package/dist/DataGridFooter/index.js +1 -1
- package/dist/DataGridHeader/DataGridHeader.js +1 -1
- package/dist/DataGridHeader/index.js +1 -1
- package/dist/DataGridInfinite/DataGridInfiniteBody.d.ts +52 -0
- package/dist/DataGridInfinite/DataGridInfiniteBody.js +309 -0
- package/dist/DataGridInfinite/index.d.ts +2 -4
- package/dist/DataGridInfinite/index.js +4 -8
- package/dist/DataGridRow/DataGridRow.js +1 -1
- package/dist/DataGridRow/index.js +1 -1
- package/dist/DataGridSorting/index.js +1 -1
- package/dist/DataGridSorting/sortingUtils.js +1 -1
- package/dist/utilities/classes.js +1 -1
- package/package.json +2 -2
- package/dist/DataGridInfinite/InfiniteScrollMarker.d.ts +0 -31
- package/dist/DataGridInfinite/InfiniteScrollMarker.js +0 -54
- package/dist/DataGridInfinite/useInfiniteScroll.d.ts +0 -92
- package/dist/DataGridInfinite/useInfiniteScroll.js +0 -136
package/README.md
CHANGED
|
@@ -36,10 +36,7 @@ import { DataGridCell } from "@versini/ui-datagrid/cell";
|
|
|
36
36
|
import { DataGridCellSort } from "@versini/ui-datagrid/cell-sort";
|
|
37
37
|
|
|
38
38
|
// Infinite scroll (progressive loading)
|
|
39
|
-
import {
|
|
40
|
-
useInfiniteScroll,
|
|
41
|
-
InfiniteScrollMarker
|
|
42
|
-
} from "@versini/ui-datagrid/infinite";
|
|
39
|
+
import { DataGridInfiniteBody } from "@versini/ui-datagrid/infinite";
|
|
43
40
|
|
|
44
41
|
// Animated height wrapper
|
|
45
42
|
import {
|
|
@@ -138,52 +135,122 @@ Supported column values include:
|
|
|
138
135
|
|
|
139
136
|
## Infinite Scroll
|
|
140
137
|
|
|
141
|
-
For datasets with hundreds to thousands of rows, use progressive loading:
|
|
138
|
+
For datasets with hundreds to thousands of rows, use `DataGridInfiniteBody` for progressive loading:
|
|
142
139
|
|
|
143
140
|
```tsx
|
|
141
|
+
import { useState } from "react";
|
|
144
142
|
import { DataGrid } from "@versini/ui-datagrid/datagrid";
|
|
145
143
|
import { DataGridHeader } from "@versini/ui-datagrid/header";
|
|
146
|
-
import { DataGridBody } from "@versini/ui-datagrid/body";
|
|
147
144
|
import { DataGridRow } from "@versini/ui-datagrid/row";
|
|
148
145
|
import { DataGridCell } from "@versini/ui-datagrid/cell";
|
|
149
|
-
import {
|
|
150
|
-
useInfiniteScroll,
|
|
151
|
-
InfiniteScrollMarker
|
|
152
|
-
} from "@versini/ui-datagrid/infinite";
|
|
146
|
+
import { DataGridInfiniteBody } from "@versini/ui-datagrid/infinite";
|
|
153
147
|
import { AnimatedWrapper } from "@versini/ui-datagrid/animated";
|
|
154
148
|
|
|
155
149
|
function MyInfiniteTable({ data }) {
|
|
156
|
-
const
|
|
157
|
-
totalItems: data.length,
|
|
158
|
-
batchSize: 25
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
const visibleData = data.slice(0, visibleCount);
|
|
150
|
+
const [visibleCount, setVisibleCount] = useState(0);
|
|
162
151
|
|
|
163
152
|
return (
|
|
164
153
|
<AnimatedWrapper dependency={visibleCount}>
|
|
165
154
|
<DataGrid maxHeight="500px" stickyHeader>
|
|
166
|
-
<DataGridHeader>
|
|
155
|
+
<DataGridHeader caption={`Showing ${visibleCount} of ${data.length}`}>
|
|
167
156
|
<DataGridRow>
|
|
168
157
|
<DataGridCell>Name</DataGridCell>
|
|
169
158
|
<DataGridCell>Date</DataGridCell>
|
|
170
159
|
</DataGridRow>
|
|
171
160
|
</DataGridHeader>
|
|
172
|
-
|
|
173
|
-
|
|
161
|
+
|
|
162
|
+
<DataGridInfiniteBody
|
|
163
|
+
data={data}
|
|
164
|
+
batchSize={25}
|
|
165
|
+
threshold={5}
|
|
166
|
+
onVisibleCountChange={(count) => setVisibleCount(count)}
|
|
167
|
+
>
|
|
168
|
+
{(item) => (
|
|
174
169
|
<DataGridRow key={item.id}>
|
|
175
170
|
<DataGridCell>{item.name}</DataGridCell>
|
|
176
171
|
<DataGridCell>{item.date}</DataGridCell>
|
|
177
172
|
</DataGridRow>
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
</DataGridBody>
|
|
173
|
+
)}
|
|
174
|
+
</DataGridInfiniteBody>
|
|
181
175
|
</DataGrid>
|
|
182
176
|
</AnimatedWrapper>
|
|
183
177
|
);
|
|
184
178
|
}
|
|
185
179
|
```
|
|
186
180
|
|
|
181
|
+
The `DataGridInfiniteBody` component handles all the complexity internally:
|
|
182
|
+
- Progressive loading with IntersectionObserver
|
|
183
|
+
- Correct marker placement for seamless scrolling (marker is placed `threshold` items before the end)
|
|
184
|
+
- Automatic data slicing and memoization
|
|
185
|
+
|
|
186
|
+
### Jump to Row
|
|
187
|
+
|
|
188
|
+
Use the `scrollToIndex` imperative method to programmatically scroll to any row, even if it's not yet loaded:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { useRef, useState } from "react";
|
|
192
|
+
import { DataGrid } from "@versini/ui-datagrid/datagrid";
|
|
193
|
+
import { DataGridHeader } from "@versini/ui-datagrid/header";
|
|
194
|
+
import { DataGridRow } from "@versini/ui-datagrid/row";
|
|
195
|
+
import { DataGridCell } from "@versini/ui-datagrid/cell";
|
|
196
|
+
import {
|
|
197
|
+
DataGridInfiniteBody,
|
|
198
|
+
type DataGridInfiniteBodyRef
|
|
199
|
+
} from "@versini/ui-datagrid/infinite";
|
|
200
|
+
|
|
201
|
+
function MyInfiniteTableWithJump({ data }) {
|
|
202
|
+
const [activeRowId, setActiveRowId] = useState(null);
|
|
203
|
+
const infiniteBodyRef = useRef<DataGridInfiniteBodyRef>(null);
|
|
204
|
+
|
|
205
|
+
const handleJumpToRow = (targetId) => {
|
|
206
|
+
// Find the index of the row with the target id
|
|
207
|
+
const targetIndex = data.findIndex((row) => row.id === targetId);
|
|
208
|
+
if (targetIndex !== -1) {
|
|
209
|
+
setActiveRowId(targetId);
|
|
210
|
+
infiniteBodyRef.current?.scrollToIndex(targetIndex);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
return (
|
|
215
|
+
<div>
|
|
216
|
+
<button onClick={() => handleJumpToRow(1341)}>
|
|
217
|
+
Jump to row with id=1341
|
|
218
|
+
</button>
|
|
219
|
+
|
|
220
|
+
<DataGrid maxHeight="400px" stickyHeader>
|
|
221
|
+
<DataGridHeader>
|
|
222
|
+
<DataGridRow>
|
|
223
|
+
<DataGridCell>ID</DataGridCell>
|
|
224
|
+
<DataGridCell>Name</DataGridCell>
|
|
225
|
+
</DataGridRow>
|
|
226
|
+
</DataGridHeader>
|
|
227
|
+
|
|
228
|
+
<DataGridInfiniteBody
|
|
229
|
+
ref={infiniteBodyRef}
|
|
230
|
+
data={data}
|
|
231
|
+
batchSize={25}
|
|
232
|
+
>
|
|
233
|
+
{(item) => (
|
|
234
|
+
<DataGridRow
|
|
235
|
+
key={item.id}
|
|
236
|
+
active={activeRowId === item.id}
|
|
237
|
+
onClick={() => setActiveRowId(item.id)}
|
|
238
|
+
>
|
|
239
|
+
<DataGridCell>{item.id}</DataGridCell>
|
|
240
|
+
<DataGridCell>{item.name}</DataGridCell>
|
|
241
|
+
</DataGridRow>
|
|
242
|
+
)}
|
|
243
|
+
</DataGridInfiniteBody>
|
|
244
|
+
</DataGrid>
|
|
245
|
+
</div>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The `scrollToIndex` method:
|
|
251
|
+
- If the row is already visible → smooth scrolls to it immediately
|
|
252
|
+
- If the row is not yet loaded → expands visible count first, then scrolls after render
|
|
253
|
+
|
|
187
254
|
## Sorting
|
|
188
255
|
|
|
189
256
|
```tsx
|
|
@@ -328,14 +395,24 @@ function MySortableTable({ data }) {
|
|
|
328
395
|
| `className` | `string` | - | CSS class for the cell |
|
|
329
396
|
| `buttonClassName` | `string` | - | CSS class for the sort button |
|
|
330
397
|
|
|
331
|
-
###
|
|
398
|
+
### DataGridInfiniteBody
|
|
399
|
+
|
|
400
|
+
| Prop | Type | Default | Description |
|
|
401
|
+
| ---------------------- | ------------------------------------------- | ------- | ------------------------------------------------- |
|
|
402
|
+
| `data` | `T[]` | **required** | The full dataset to render progressively |
|
|
403
|
+
| `children` | `(item: T, index: number) => ReactNode` | **required** | Render function for each row |
|
|
404
|
+
| `batchSize` | `number` | `20` | Items to show initially and add per scroll |
|
|
405
|
+
| `threshold` | `number` | `5` | Items before marker to allow seamless scrolling |
|
|
406
|
+
| `rootMargin` | `string` | `'20px'`| IntersectionObserver margin |
|
|
407
|
+
| `onVisibleCountChange` | `(visibleCount: number, total: number) => void` | - | Callback when visible count changes |
|
|
408
|
+
| `className` | `string` | - | CSS class for the body element |
|
|
409
|
+
| `ref` | `React.Ref<DataGridInfiniteBodyRef>` | - | Ref to access imperative methods |
|
|
410
|
+
|
|
411
|
+
### DataGridInfiniteBodyRef (Imperative Handle)
|
|
332
412
|
|
|
333
|
-
|
|
|
334
|
-
|
|
|
335
|
-
| `
|
|
336
|
-
| `batchSize` | `number` | `20` | Items to load per batch |
|
|
337
|
-
| `threshold` | `number` | `5` | Items before end to trigger load |
|
|
338
|
-
| `rootMargin` | `string` | `'20px'` | IntersectionObserver margin |
|
|
413
|
+
| Method | Signature | Description |
|
|
414
|
+
| --------------- | ---------------------------- | -------------------------------------------------------- |
|
|
415
|
+
| `scrollToIndex` | `(index: number) => void` | Scroll to a row by index. Expands visible count if needed, then smooth scrolls. |
|
|
339
416
|
|
|
340
417
|
## License
|
|
341
418
|
|
package/dist/DataGrid/index.js
CHANGED
|
@@ -28,16 +28,20 @@ export type AnimatedWrapperProps = {
|
|
|
28
28
|
*
|
|
29
29
|
* @example
|
|
30
30
|
* ```tsx
|
|
31
|
-
* const
|
|
31
|
+
* const [visibleCount, setVisibleCount] = useState(0);
|
|
32
32
|
*
|
|
33
33
|
* return (
|
|
34
34
|
* <AnimatedWrapper dependency={visibleCount}>
|
|
35
|
-
* <DataGrid>
|
|
36
|
-
* <
|
|
37
|
-
* {
|
|
38
|
-
*
|
|
39
|
-
* ))}
|
|
40
|
-
*
|
|
35
|
+
* <DataGrid maxHeight="400px" stickyHeader>
|
|
36
|
+
* <DataGridInfiniteBody
|
|
37
|
+
* data={largeData}
|
|
38
|
+
* batchSize={25}
|
|
39
|
+
* onVisibleCountChange={(count) => setVisibleCount(count)}
|
|
40
|
+
* >
|
|
41
|
+
* {(row) => (
|
|
42
|
+
* <DataGridRow key={row.id}>...</DataGridRow>
|
|
43
|
+
* )}
|
|
44
|
+
* </DataGridInfiniteBody>
|
|
41
45
|
* </DataGrid>
|
|
42
46
|
* </AnimatedWrapper>
|
|
43
47
|
* );
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.
|
|
2
|
+
@versini/ui-datagrid v0.4.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -18,16 +18,20 @@ import { useAnimatedHeight } from "./useAnimatedHeight.js";
|
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```tsx
|
|
21
|
-
* const
|
|
21
|
+
* const [visibleCount, setVisibleCount] = useState(0);
|
|
22
22
|
*
|
|
23
23
|
* return (
|
|
24
24
|
* <AnimatedWrapper dependency={visibleCount}>
|
|
25
|
-
* <DataGrid>
|
|
26
|
-
* <
|
|
27
|
-
* {
|
|
28
|
-
*
|
|
29
|
-
* ))}
|
|
30
|
-
*
|
|
25
|
+
* <DataGrid maxHeight="400px" stickyHeader>
|
|
26
|
+
* <DataGridInfiniteBody
|
|
27
|
+
* data={largeData}
|
|
28
|
+
* batchSize={25}
|
|
29
|
+
* onVisibleCountChange={(count) => setVisibleCount(count)}
|
|
30
|
+
* >
|
|
31
|
+
* {(row) => (
|
|
32
|
+
* <DataGridRow key={row.id}>...</DataGridRow>
|
|
33
|
+
* )}
|
|
34
|
+
* </DataGridInfiniteBody>
|
|
31
35
|
* </DataGrid>
|
|
32
36
|
* </AnimatedWrapper>
|
|
33
37
|
* );
|
|
@@ -1,86 +1,42 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.
|
|
2
|
+
@versini/ui-datagrid v0.4.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { jsx } from "react/jsx-runtime";
|
|
7
|
-
import
|
|
8
|
-
import { useContext, useLayoutEffect, useRef } from "react";
|
|
7
|
+
import { useContext, useRef } from "react";
|
|
9
8
|
import { DataGridContext } from "../DataGrid/DataGridContext.js";
|
|
10
9
|
import { CellWrapper } from "../DataGridConstants/index.js";
|
|
10
|
+
import { getBodyClass } from "./getBodyClass.js";
|
|
11
|
+
import { useColumnMeasurement } from "./useColumnMeasurement.js";
|
|
11
12
|
|
|
12
13
|
;// CONCATENATED MODULE: external "react/jsx-runtime"
|
|
13
14
|
|
|
14
|
-
;// CONCATENATED MODULE: external "clsx"
|
|
15
|
-
|
|
16
15
|
;// CONCATENATED MODULE: external "react"
|
|
17
16
|
|
|
18
17
|
;// CONCATENATED MODULE: external "../DataGrid/DataGridContext.js"
|
|
19
18
|
|
|
20
19
|
;// CONCATENATED MODULE: external "../DataGridConstants/index.js"
|
|
21
20
|
|
|
21
|
+
;// CONCATENATED MODULE: external "./getBodyClass.js"
|
|
22
|
+
|
|
23
|
+
;// CONCATENATED MODULE: external "./useColumnMeasurement.js"
|
|
24
|
+
|
|
22
25
|
;// CONCATENATED MODULE: ./src/DataGridBody/DataGridBody.tsx
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
|
|
26
29
|
|
|
27
30
|
|
|
31
|
+
|
|
28
32
|
/* =============================================================================
|
|
29
33
|
* DataGridBody
|
|
30
34
|
* ========================================================================== */ const DataGridBody = ({ className, children, ...rest })=>{
|
|
31
35
|
const ctx = useContext(DataGridContext);
|
|
32
36
|
const bodyRef = useRef(null);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
* subgrid. We measure the body cells (which ARE in the grid flow) and report
|
|
37
|
-
* the widths so header/footer can use the same pixel values.
|
|
38
|
-
*/ useLayoutEffect(()=>{
|
|
39
|
-
const element = bodyRef.current;
|
|
40
|
-
const needsMeasurement = ctx.columns && (ctx.stickyHeader || ctx.stickyFooter);
|
|
41
|
-
if (!element || !needsMeasurement || !ctx.setMeasuredColumnWidths) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Find the first row and its cells once, reuse for both measurement and
|
|
46
|
-
* observation.
|
|
47
|
-
*/ const firstRow = element.querySelector('[role="row"]');
|
|
48
|
-
if (!firstRow) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const cells = firstRow.querySelectorAll('[role="cell"], [role="columnheader"], [role="gridcell"]');
|
|
52
|
-
if (cells.length === 0) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
const measureColumns = ()=>{
|
|
56
|
-
// Measure each cell's width.
|
|
57
|
-
const widths = Array.from(cells).map((cell)=>cell.getBoundingClientRect().width);
|
|
58
|
-
ctx.setMeasuredColumnWidths?.(widths);
|
|
59
|
-
};
|
|
60
|
-
// Initial measurement.
|
|
61
|
-
measureColumns();
|
|
62
|
-
// Set up ResizeObserver to re-measure when cells resize.
|
|
63
|
-
const observer = new ResizeObserver(()=>{
|
|
64
|
-
measureColumns();
|
|
65
|
-
});
|
|
66
|
-
// Observe the body element for any size changes.
|
|
67
|
-
observer.observe(element);
|
|
68
|
-
// Also observe the first row's cells directly for more accurate updates.
|
|
69
|
-
for (const cell of cells){
|
|
70
|
-
observer.observe(cell);
|
|
71
|
-
}
|
|
72
|
-
return ()=>observer.disconnect();
|
|
73
|
-
}, [
|
|
74
|
-
ctx.columns,
|
|
75
|
-
ctx.stickyHeader,
|
|
76
|
-
ctx.stickyFooter,
|
|
77
|
-
ctx.setMeasuredColumnWidths,
|
|
78
|
-
children
|
|
79
|
-
]);
|
|
80
|
-
/**
|
|
81
|
-
* When columns are provided, use display:contents so the body doesn't
|
|
82
|
-
* interfere with the grid flow. Rows will use subgrid.
|
|
83
|
-
*/ const bodyClass = ctx.columns ? clsx("contents", className) : clsx("flex flex-col", className);
|
|
37
|
+
// Measure column widths for sticky header/footer sync.
|
|
38
|
+
useColumnMeasurement(bodyRef, children);
|
|
39
|
+
const bodyClass = getBodyClass(Boolean(ctx.columns), className);
|
|
84
40
|
return /*#__PURE__*/ jsx(DataGridContext.Provider, {
|
|
85
41
|
value: {
|
|
86
42
|
...ctx,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the CSS class for a DataGrid body element.
|
|
3
|
+
* When columns are provided, use display:contents so the body doesn't
|
|
4
|
+
* interfere with the grid flow. Rows will use subgrid.
|
|
5
|
+
*
|
|
6
|
+
* @param hasColumns - Whether the DataGrid has columns defined
|
|
7
|
+
* @param className - Additional class name to merge
|
|
8
|
+
*/
|
|
9
|
+
export declare function getBodyClass(hasColumns: boolean | undefined, className?: string): string;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-datagrid v0.4.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
|
|
8
|
+
;// CONCATENATED MODULE: external "clsx"
|
|
9
|
+
|
|
10
|
+
;// CONCATENATED MODULE: ./src/DataGridBody/getBodyClass.ts
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the CSS class for a DataGrid body element.
|
|
14
|
+
* When columns are provided, use display:contents so the body doesn't
|
|
15
|
+
* interfere with the grid flow. Rows will use subgrid.
|
|
16
|
+
*
|
|
17
|
+
* @param hasColumns - Whether the DataGrid has columns defined
|
|
18
|
+
* @param className - Additional class name to merge
|
|
19
|
+
*/ function getBodyClass(hasColumns, className) {
|
|
20
|
+
return hasColumns ? clsx("contents", className) : clsx("flex flex-col", className);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export { getBodyClass };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to measure column widths from the first body row's cells.
|
|
3
|
+
* This is needed because sticky header/footer are absolutely positioned
|
|
4
|
+
* and can't use CSS subgrid. We measure the body cells (which ARE in the
|
|
5
|
+
* grid flow) and report the widths so header/footer can use the same pixel values.
|
|
6
|
+
*
|
|
7
|
+
* @param bodyRef - Ref to the body element containing the rows
|
|
8
|
+
* @param contentDependency - Dependency that changes when content changes (children or renderedContent)
|
|
9
|
+
*/
|
|
10
|
+
export declare function useColumnMeasurement(bodyRef: React.RefObject<HTMLDivElement | null>, contentDependency: unknown): void;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-datagrid v0.4.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useContext, useLayoutEffect } from "react";
|
|
7
|
+
import { DataGridContext } from "../DataGrid/DataGridContext.js";
|
|
8
|
+
|
|
9
|
+
;// CONCATENATED MODULE: external "react"
|
|
10
|
+
|
|
11
|
+
;// CONCATENATED MODULE: external "../DataGrid/DataGridContext.js"
|
|
12
|
+
|
|
13
|
+
;// CONCATENATED MODULE: ./src/DataGridBody/useColumnMeasurement.ts
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Hook to measure column widths from the first body row's cells.
|
|
18
|
+
* This is needed because sticky header/footer are absolutely positioned
|
|
19
|
+
* and can't use CSS subgrid. We measure the body cells (which ARE in the
|
|
20
|
+
* grid flow) and report the widths so header/footer can use the same pixel values.
|
|
21
|
+
*
|
|
22
|
+
* @param bodyRef - Ref to the body element containing the rows
|
|
23
|
+
* @param contentDependency - Dependency that changes when content changes (children or renderedContent)
|
|
24
|
+
*/ function useColumnMeasurement(bodyRef, contentDependency) {
|
|
25
|
+
const ctx = useContext(DataGridContext);
|
|
26
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: contentDependency triggers remeasurement when rows change
|
|
27
|
+
useLayoutEffect(()=>{
|
|
28
|
+
const element = bodyRef.current;
|
|
29
|
+
const needsMeasurement = ctx.columns && (ctx.stickyHeader || ctx.stickyFooter);
|
|
30
|
+
if (!element || !needsMeasurement || !ctx.setMeasuredColumnWidths) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const firstRow = element.querySelector('[role="row"]');
|
|
34
|
+
if (!firstRow) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const cells = firstRow.querySelectorAll('[role="cell"], [role="columnheader"], [role="gridcell"]');
|
|
38
|
+
if (cells.length === 0) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const measureColumns = ()=>{
|
|
42
|
+
const widths = Array.from(cells).map((cell)=>cell.getBoundingClientRect().width);
|
|
43
|
+
ctx.setMeasuredColumnWidths?.(widths);
|
|
44
|
+
};
|
|
45
|
+
// Initial measurement.
|
|
46
|
+
measureColumns();
|
|
47
|
+
// Set up ResizeObserver to re-measure when cells resize.
|
|
48
|
+
const observer = new ResizeObserver(()=>{
|
|
49
|
+
measureColumns();
|
|
50
|
+
});
|
|
51
|
+
// Observe the body element for any size changes.
|
|
52
|
+
observer.observe(element);
|
|
53
|
+
// Also observe the first row's cells directly for more accurate updates.
|
|
54
|
+
for (const cell of cells){
|
|
55
|
+
observer.observe(cell);
|
|
56
|
+
}
|
|
57
|
+
return ()=>observer.disconnect();
|
|
58
|
+
}, [
|
|
59
|
+
ctx.columns,
|
|
60
|
+
ctx.stickyHeader,
|
|
61
|
+
ctx.stickyFooter,
|
|
62
|
+
ctx.setMeasuredColumnWidths,
|
|
63
|
+
contentDependency
|
|
64
|
+
]);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { useColumnMeasurement };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export type DataGridInfiniteBodyProps<T> = {
|
|
2
|
+
/**
|
|
3
|
+
* The full dataset to render progressively.
|
|
4
|
+
*/
|
|
5
|
+
data: T[];
|
|
6
|
+
/**
|
|
7
|
+
* Render function for each row. Should return a DataGridRow element.
|
|
8
|
+
* The consumer is responsible for providing the key prop on the returned element.
|
|
9
|
+
*/
|
|
10
|
+
children: (item: T, index: number) => React.ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Number of items to show initially and to add on each scroll.
|
|
13
|
+
* @default 20
|
|
14
|
+
*/
|
|
15
|
+
batchSize?: number;
|
|
16
|
+
/**
|
|
17
|
+
* How many items to keep below the marker for seamless scrolling.
|
|
18
|
+
* The marker is placed `threshold` items before the end of visible items.
|
|
19
|
+
* @default 5
|
|
20
|
+
*/
|
|
21
|
+
threshold?: number;
|
|
22
|
+
/**
|
|
23
|
+
* IntersectionObserver root margin.
|
|
24
|
+
* @default "20px"
|
|
25
|
+
*/
|
|
26
|
+
rootMargin?: string;
|
|
27
|
+
/**
|
|
28
|
+
* Callback when visible count changes. Useful for displaying count in header.
|
|
29
|
+
*/
|
|
30
|
+
onVisibleCountChange?: (visibleCount: number, totalItems: number) => void;
|
|
31
|
+
/**
|
|
32
|
+
* CSS class to apply to the body.
|
|
33
|
+
*/
|
|
34
|
+
className?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Ref handle for DataGridInfiniteBody, exposing imperative methods.
|
|
38
|
+
*/
|
|
39
|
+
export type DataGridInfiniteBodyRef = {
|
|
40
|
+
/**
|
|
41
|
+
* Scroll to a specific row index with smooth animation.
|
|
42
|
+
* If the row is not yet visible, it will expand the visible count first.
|
|
43
|
+
* @param index - The index of the row to scroll to
|
|
44
|
+
*/
|
|
45
|
+
scrollToIndex: (index: number) => void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* DataGridInfiniteBody with forwardRef support for imperative methods.
|
|
49
|
+
*/
|
|
50
|
+
export declare const DataGridInfiniteBody: <T>(props: DataGridInfiniteBodyProps<T> & {
|
|
51
|
+
ref?: React.ForwardedRef<DataGridInfiniteBodyRef>;
|
|
52
|
+
}) => React.ReactElement;
|