@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.
Files changed (38) hide show
  1. package/README.md +106 -29
  2. package/dist/DataGrid/DataGrid.js +1 -1
  3. package/dist/DataGrid/DataGridContext.js +1 -1
  4. package/dist/DataGrid/index.js +1 -1
  5. package/dist/DataGridAnimated/AnimatedWrapper.d.ts +11 -7
  6. package/dist/DataGridAnimated/AnimatedWrapper.js +12 -8
  7. package/dist/DataGridAnimated/index.js +1 -1
  8. package/dist/DataGridAnimated/useAnimatedHeight.js +1 -1
  9. package/dist/DataGridBody/DataGridBody.js +12 -56
  10. package/dist/DataGridBody/getBodyClass.d.ts +9 -0
  11. package/dist/DataGridBody/getBodyClass.js +23 -0
  12. package/dist/DataGridBody/index.js +1 -1
  13. package/dist/DataGridBody/useColumnMeasurement.d.ts +10 -0
  14. package/dist/DataGridBody/useColumnMeasurement.js +67 -0
  15. package/dist/DataGridCell/DataGridCell.js +1 -1
  16. package/dist/DataGridCell/index.js +1 -1
  17. package/dist/DataGridCellSort/DataGridCellSort.js +1 -1
  18. package/dist/DataGridCellSort/index.js +1 -1
  19. package/dist/DataGridConstants/DataGridConstants.js +1 -1
  20. package/dist/DataGridConstants/index.js +1 -1
  21. package/dist/DataGridFooter/DataGridFooter.js +1 -1
  22. package/dist/DataGridFooter/index.js +1 -1
  23. package/dist/DataGridHeader/DataGridHeader.js +1 -1
  24. package/dist/DataGridHeader/index.js +1 -1
  25. package/dist/DataGridInfinite/DataGridInfiniteBody.d.ts +52 -0
  26. package/dist/DataGridInfinite/DataGridInfiniteBody.js +309 -0
  27. package/dist/DataGridInfinite/index.d.ts +2 -4
  28. package/dist/DataGridInfinite/index.js +4 -8
  29. package/dist/DataGridRow/DataGridRow.js +1 -1
  30. package/dist/DataGridRow/index.js +1 -1
  31. package/dist/DataGridSorting/index.js +1 -1
  32. package/dist/DataGridSorting/sortingUtils.js +1 -1
  33. package/dist/utilities/classes.js +1 -1
  34. package/package.json +2 -2
  35. package/dist/DataGridInfinite/InfiniteScrollMarker.d.ts +0 -31
  36. package/dist/DataGridInfinite/InfiniteScrollMarker.js +0 -54
  37. package/dist/DataGridInfinite/useInfiniteScroll.d.ts +0 -92
  38. 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 { visibleCount, hasMore, markerRef } = useInfiniteScroll({
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
- <DataGridBody>
173
- {visibleData.map((item) => (
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
- {hasMore && <InfiniteScrollMarker ref={markerRef} colSpan={2} />}
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
- ### useInfiniteScroll
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
- | Option | Type | Default | Description |
334
- | ------------ | -------- | ------------ | -------------------------------- |
335
- | `totalItems` | `number` | **required** | Total number of items |
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
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -28,16 +28,20 @@ export type AnimatedWrapperProps = {
28
28
  *
29
29
  * @example
30
30
  * ```tsx
31
- * const { visibleCount } = useInfiniteScroll({ totalItems: data.length });
31
+ * const [visibleCount, setVisibleCount] = useState(0);
32
32
  *
33
33
  * return (
34
34
  * <AnimatedWrapper dependency={visibleCount}>
35
- * <DataGrid>
36
- * <DataGridBody>
37
- * {data.slice(0, visibleCount).map((item) => (
38
- * <DataGridRow key={item.id}>...</DataGridRow>
39
- * ))}
40
- * </DataGridBody>
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.3.8
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 { visibleCount } = useInfiniteScroll({ totalItems: data.length });
21
+ * const [visibleCount, setVisibleCount] = useState(0);
22
22
  *
23
23
  * return (
24
24
  * <AnimatedWrapper dependency={visibleCount}>
25
- * <DataGrid>
26
- * <DataGridBody>
27
- * {data.slice(0, visibleCount).map((item) => (
28
- * <DataGridRow key={item.id}>...</DataGridRow>
29
- * ))}
30
- * </DataGridBody>
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,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,86 +1,42 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
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 clsx from "clsx";
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
- * Measure column widths from the first body row's cells. This is needed
35
- * because sticky header/footer are absolutely positioned and can't use CSS
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 };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -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 };
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v0.3.8
2
+ @versini/ui-datagrid v0.4.0
3
3
  © 2026 gizmette.com
4
4
  */
5
5
 
@@ -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;