@versini/ui-datagrid 3.1.2 → 4.0.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 +18 -18
- package/dist/197.js +56 -34
- package/dist/430.js +1 -1
- package/dist/799.js +1 -1
- package/dist/91.js +1 -1
- package/dist/DataGrid/DataGridTypes.d.ts +52 -0
- package/dist/DataGrid/index.js +53 -9
- package/dist/DataGridAnimated/AnimatedWrapper.d.ts +0 -1
- package/dist/DataGridAnimated/index.js +16 -3
- package/dist/DataGridBody/index.js +1 -1
- package/dist/DataGridCell/index.js +1 -1
- package/dist/DataGridCellSort/index.js +1 -1
- package/dist/DataGridConstants/index.js +1 -1
- package/dist/DataGridFooter/index.js +4 -2
- package/dist/DataGridHeader/index.js +4 -2
- package/dist/DataGridInfinite/DataGridInfiniteBody.d.ts +32 -43
- package/dist/DataGridInfinite/index.js +220 -225
- package/dist/DataGridRow/index.js +5 -2
- package/dist/DataGridSorting/index.js +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ A data grid component library for React built with div-based ARIA grid layout fo
|
|
|
5
5
|
- **Accessible**: Uses ARIA grid roles for screen reader support
|
|
6
6
|
- **Flexible Layout**: Div-based structure allows for complex styling
|
|
7
7
|
- **CSS Grid Columns**: Define column widths with CSS Grid track sizes
|
|
8
|
-
- **
|
|
8
|
+
- **Row Virtualization**: Windowing for large datasets — only viewport rows stay in the DOM
|
|
9
9
|
- **Animated Height**: Smooth height transitions when content changes
|
|
10
10
|
- **Sorting**: Built-in sorting utilities and sortable column headers
|
|
11
11
|
- **Sticky Header/Footer**: With optional blur effects
|
|
@@ -35,7 +35,7 @@ import { DataGridRow } from "@versini/ui-datagrid/row";
|
|
|
35
35
|
import { DataGridCell } from "@versini/ui-datagrid/cell";
|
|
36
36
|
import { DataGridCellSort } from "@versini/ui-datagrid/cell-sort";
|
|
37
37
|
|
|
38
|
-
//
|
|
38
|
+
// Virtualized body for large datasets
|
|
39
39
|
import { DataGridInfiniteBody } from "@versini/ui-datagrid/infinite";
|
|
40
40
|
|
|
41
41
|
// Animated height wrapper
|
|
@@ -135,7 +135,7 @@ Supported column values include:
|
|
|
135
135
|
|
|
136
136
|
## Infinite Scroll
|
|
137
137
|
|
|
138
|
-
For datasets
|
|
138
|
+
For large datasets, use `DataGridInfiniteBody`. It virtualizes the rows (windowing via [`@tanstack/react-virtual`](https://tanstack.com/virtual/latest)): only the rows near the viewport stay mounted, so the DOM stays small and scrolling stays smooth no matter how many rows there are.
|
|
139
139
|
|
|
140
140
|
```tsx
|
|
141
141
|
import { useState } from "react";
|
|
@@ -161,8 +161,7 @@ function MyInfiniteTable({ data }) {
|
|
|
161
161
|
|
|
162
162
|
<DataGridInfiniteBody
|
|
163
163
|
data={data}
|
|
164
|
-
|
|
165
|
-
threshold={5}
|
|
164
|
+
estimatedRowHeight={48}
|
|
166
165
|
onVisibleCountChange={(count) => setVisibleCount(count)}
|
|
167
166
|
>
|
|
168
167
|
{(item) => (
|
|
@@ -178,11 +177,13 @@ function MyInfiniteTable({ data }) {
|
|
|
178
177
|
}
|
|
179
178
|
```
|
|
180
179
|
|
|
181
|
-
|
|
180
|
+
`DataGridInfiniteBody` keeps `columns` (including intrinsic `auto` tracks) working because it renders rows in normal grid flow between two full-span spacer elements (not absolutely positioned). It windows against the DataGrid's own scroll container — created when you set `maxHeight` or a sticky header/footer — and otherwise against the page (window scroll). To scroll inside your own bounded container, set `maxHeight` on the `DataGrid` rather than relying on an outer wrapper.
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
-
|
|
182
|
+
Notes:
|
|
183
|
+
|
|
184
|
+
- **Set `estimatedRowHeight` close to your average row height.** It seeds the scrollbar before rows are measured; a good estimate minimizes scrollbar drift and makes `scrollToIndex` to far rows land accurately. Real heights are always measured from the DOM, so variable / wrapping rows work. It defaults to the grid's density (44, or 29 when the `DataGrid` is `compact`), so you usually only need to set it for unusually tall rows.
|
|
185
|
+
- The grid exposes `aria-rowcount` and `aria-rowindex`, and keeps keyboard focus on the grid if a focused row recycles out.
|
|
186
|
+
- **Known limitation:** because off-screen rows are removed from the DOM, browser find-in-page (Ctrl+F) and uncontrolled per-row state (e.g. an open menu, an uncommitted input) don't persist when a row recycles. Lift such state to controlled props. This is inherent to DOM virtualization.
|
|
186
187
|
|
|
187
188
|
### Jump to Row
|
|
188
189
|
|
|
@@ -226,7 +227,7 @@ function MyInfiniteTableWithJump({ data }) {
|
|
|
226
227
|
</DataGridRow>
|
|
227
228
|
</DataGridHeader>
|
|
228
229
|
|
|
229
|
-
<DataGridInfiniteBody ref={infiniteBodyRef} data={data}
|
|
230
|
+
<DataGridInfiniteBody ref={infiniteBodyRef} data={data}>
|
|
230
231
|
{(item) => (
|
|
231
232
|
<DataGridRow
|
|
232
233
|
key={item.id}
|
|
@@ -246,8 +247,8 @@ function MyInfiniteTableWithJump({ data }) {
|
|
|
246
247
|
|
|
247
248
|
The `scrollToIndex` method:
|
|
248
249
|
|
|
249
|
-
-
|
|
250
|
-
-
|
|
250
|
+
- Centers the target row in the scroll viewport, mounting it first if it's off-screen
|
|
251
|
+
- Scrolls smoothly, unless the user prefers reduced motion (then it jumps instantly)
|
|
251
252
|
|
|
252
253
|
## Sorting
|
|
253
254
|
|
|
@@ -404,12 +405,11 @@ function MySortableTable({ data }) {
|
|
|
404
405
|
|
|
405
406
|
| Prop | Type | Default | Description |
|
|
406
407
|
| ---------------------- | ----------------------------------------------- | ------------ | ----------------------------------------------- |
|
|
407
|
-
| `data` | `T[]` | **required** | The full dataset to
|
|
408
|
+
| `data` | `T[]` | **required** | The full dataset to virtualize |
|
|
408
409
|
| `children` | `(item: T, index: number) => ReactNode` | **required** | Render function for each row |
|
|
409
|
-
| `
|
|
410
|
-
| `
|
|
411
|
-
| `
|
|
412
|
-
| `onVisibleCountChange` | `(visibleCount: number, total: number) => void` | - | Callback when visible count changes |
|
|
410
|
+
| `overscan` | `number` | `8` | Rows rendered beyond the viewport on each side |
|
|
411
|
+
| `estimatedRowHeight` | `number` | `44` / `29` | Initial row-height estimate (29 when `compact`) before measurement |
|
|
412
|
+
| `onVisibleCountChange` | `(visibleCount: number, total: number) => void` | - | Monotonic high-water mark — furthest row reached, plus one |
|
|
413
413
|
| `noData` | `boolean` | `false` | Display empty state instead of infinite scroll |
|
|
414
414
|
| `noDataText` | `string` | `'No Data'` | Custom text for the empty state |
|
|
415
415
|
| `className` | `string` | - | CSS class for the body element |
|
|
@@ -419,7 +419,7 @@ function MySortableTable({ data }) {
|
|
|
419
419
|
|
|
420
420
|
| Method | Signature | Description |
|
|
421
421
|
| --------------- | ------------------------- | ------------------------------------------------------------------------------- |
|
|
422
|
-
| `scrollToIndex` | `(index: number) => void` |
|
|
422
|
+
| `scrollToIndex` | `(index: number) => void` | Center a row by index, mounting it if off-screen. Smooth unless reduced motion is preferred. |
|
|
423
423
|
|
|
424
424
|
## License
|
|
425
425
|
|
package/dist/197.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid
|
|
2
|
+
@versini/ui-datagrid v4.0.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import clsx from "clsx";
|
|
7
|
-
import { useContext, useLayoutEffect, useRef } from "react";
|
|
7
|
+
import { useContext, useEffect, useLayoutEffect, useRef } from "react";
|
|
8
8
|
import { DataGridContext } from "./430.js";
|
|
9
9
|
|
|
10
10
|
|
|
@@ -60,54 +60,76 @@ import { DataGridContext } from "./430.js";
|
|
|
60
60
|
*/ function useColumnMeasurement(bodyRef, contentDependency, noData = false) {
|
|
61
61
|
const ctx = useContext(DataGridContext);
|
|
62
62
|
const prevWidthsRef = useRef([]);
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const observerRef = useRef(null);
|
|
64
|
+
const needsMeasurement = Boolean(ctx.columns && (ctx.stickyHeader || ctx.stickyFooter));
|
|
65
|
+
/**
|
|
66
|
+
* Re-query the current first row's cells, report any changed widths, and
|
|
67
|
+
* return the cells. Held in a ref so the persistent ResizeObserver always runs
|
|
68
|
+
* the latest logic without being recreated as the window slides.
|
|
69
|
+
*/ const measureRef = useRef(()=>[]);
|
|
70
|
+
measureRef.current = ()=>{
|
|
65
71
|
const element = bodyRef.current;
|
|
66
|
-
const needsMeasurement = ctx.columns && (ctx.stickyHeader || ctx.stickyFooter);
|
|
67
72
|
if (noData || !element || !needsMeasurement || !ctx.setMeasuredColumnWidths) {
|
|
68
|
-
return;
|
|
73
|
+
return [];
|
|
69
74
|
}
|
|
70
75
|
const firstRow = element.querySelector('[role="row"]');
|
|
71
|
-
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const cells = firstRow.querySelectorAll('[role="cell"], [role="columnheader"], [role="gridcell"]');
|
|
76
|
+
const cells = firstRow ? Array.from(firstRow.querySelectorAll('[role="cell"], [role="columnheader"], [role="gridcell"]')) : [];
|
|
75
77
|
if (cells.length === 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const dpr = typeof window !== "undefined" ? window.devicePixelRatio ?? 1 : 1;
|
|
81
|
+
const widths = cells.map((cell)=>quantizeWidthToDevicePixels(cell.getBoundingClientRect().width, dpr));
|
|
82
|
+
/**
|
|
83
|
+
* Only update state if widths have actually changed to prevent infinite
|
|
84
|
+
* loops.
|
|
85
|
+
*/ const prevWidths = prevWidthsRef.current;
|
|
86
|
+
const hasChanged = widths.length !== prevWidths.length || widths.some((w, i)=>w !== prevWidths[i]);
|
|
87
|
+
if (hasChanged) {
|
|
88
|
+
prevWidthsRef.current = widths;
|
|
89
|
+
ctx.setMeasuredColumnWidths(widths);
|
|
90
|
+
}
|
|
91
|
+
return cells;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Keep a single ResizeObserver for the hook's lifetime and re-point it at the
|
|
95
|
+
* current first row's cells whenever the rendered content changes (e.g. a
|
|
96
|
+
* virtualized window sliding). The cells are the observed targets because the
|
|
97
|
+
* body is `display: contents` (it has no box of its own); re-pointing the same
|
|
98
|
+
* observer avoids tearing one down and allocating a new one on every scroll
|
|
99
|
+
* shift.
|
|
100
|
+
*/ // biome-ignore lint/correctness/useExhaustiveDependencies: measureRef is stable; contentDependency re-points the observer when the rendered rows change
|
|
101
|
+
useLayoutEffect(()=>{
|
|
102
|
+
if (!needsMeasurement) {
|
|
103
|
+
observerRef.current?.disconnect();
|
|
76
104
|
return;
|
|
77
105
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (hasChanged) {
|
|
87
|
-
prevWidthsRef.current = widths;
|
|
88
|
-
ctx.setMeasuredColumnWidths?.(widths);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
// Initial measurement.
|
|
92
|
-
measureColumns();
|
|
93
|
-
// Set up ResizeObserver to re-measure when cells resize.
|
|
94
|
-
const observer = new ResizeObserver(()=>{
|
|
95
|
-
measureColumns();
|
|
96
|
-
});
|
|
97
|
-
// Observe the body element for any size changes.
|
|
98
|
-
observer.observe(element);
|
|
99
|
-
// Also observe the first row's cells directly for more accurate updates.
|
|
106
|
+
if (!observerRef.current) {
|
|
107
|
+
observerRef.current = new ResizeObserver(()=>{
|
|
108
|
+
measureRef.current();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
const observer = observerRef.current;
|
|
112
|
+
observer.disconnect();
|
|
113
|
+
const cells = measureRef.current();
|
|
100
114
|
for (const cell of cells){
|
|
101
115
|
observer.observe(cell);
|
|
102
116
|
}
|
|
103
|
-
return ()=>observer.disconnect();
|
|
104
117
|
}, [
|
|
105
118
|
ctx.columns,
|
|
106
119
|
ctx.stickyHeader,
|
|
107
120
|
ctx.stickyFooter,
|
|
108
121
|
ctx.setMeasuredColumnWidths,
|
|
109
|
-
contentDependency
|
|
122
|
+
contentDependency,
|
|
123
|
+
needsMeasurement,
|
|
124
|
+
noData
|
|
110
125
|
]);
|
|
126
|
+
// Disconnect on unmount.
|
|
127
|
+
useEffect(()=>{
|
|
128
|
+
return ()=>{
|
|
129
|
+
observerRef.current?.disconnect();
|
|
130
|
+
observerRef.current = null;
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
111
133
|
}
|
|
112
134
|
|
|
113
135
|
export { getBodyClass, useColumnMeasurement };
|
package/dist/430.js
CHANGED
package/dist/799.js
CHANGED
package/dist/91.js
CHANGED
|
@@ -290,5 +290,57 @@ export type DataGridContextValue = {
|
|
|
290
290
|
* DataGridInfiniteBody where CSS :last-child doesn't work.
|
|
291
291
|
*/
|
|
292
292
|
isLastRow?: boolean;
|
|
293
|
+
/**
|
|
294
|
+
* Total row count (header + body + footer) for `aria-rowcount` on the grid.
|
|
295
|
+
* Set only when a body virtualizes its rows; `undefined` otherwise, so
|
|
296
|
+
* non-virtualized grids render no `aria-rowcount`.
|
|
297
|
+
*/
|
|
298
|
+
rowCount?: number;
|
|
299
|
+
/**
|
|
300
|
+
* Callback for a virtualizing body to report the total row count to the grid.
|
|
301
|
+
*/
|
|
302
|
+
setRowCount?: (count: number | undefined) => void;
|
|
303
|
+
/**
|
|
304
|
+
* Number of header rows currently registered (0 or 1). Used to offset
|
|
305
|
+
* `aria-rowindex` so body rows are numbered after the header.
|
|
306
|
+
*/
|
|
307
|
+
headerRows?: number;
|
|
308
|
+
/**
|
|
309
|
+
* Number of footer rows currently registered (0 or 1).
|
|
310
|
+
*/
|
|
311
|
+
footerRows?: number;
|
|
312
|
+
/**
|
|
313
|
+
* 1-based ARIA row index for this row (header/body/footer), set per-row by
|
|
314
|
+
* the provider. Applied to the `role="row"` element when present.
|
|
315
|
+
*/
|
|
316
|
+
ariaRowIndex?: number;
|
|
317
|
+
/**
|
|
318
|
+
* 0-based data index for a virtualized body row. Applied as `data-index` on
|
|
319
|
+
* the `role="row"` element so the virtualizer maps measurements to the row.
|
|
320
|
+
*/
|
|
321
|
+
dataIndex?: number;
|
|
322
|
+
/**
|
|
323
|
+
* Virtualizer measure callback. Attached as a ref to the `role="row"` element
|
|
324
|
+
* so the virtualizer measures the real row box (the wrapper is
|
|
325
|
+
* `display:contents` and has no box).
|
|
326
|
+
*/
|
|
327
|
+
measureRowElement?: (el: HTMLElement | null) => void;
|
|
328
|
+
/**
|
|
329
|
+
* Focus the grid element (a `tabIndex={-1}` sentinel). Used by a virtualizing
|
|
330
|
+
* body to keep focus off `document.body` when a focused row recycles out.
|
|
331
|
+
*/
|
|
332
|
+
focusGrid?: () => void;
|
|
333
|
+
/**
|
|
334
|
+
* The internal bounded scroll container (sticky scrollable area, or the
|
|
335
|
+
* maxHeight wrapper) a virtualized body should window against. `null` until
|
|
336
|
+
* mounted, or when DataGrid owns no internal scroller (see `pageScroll`).
|
|
337
|
+
*/
|
|
338
|
+
scrollContainer?: HTMLElement | null;
|
|
339
|
+
/**
|
|
340
|
+
* True when DataGrid has no internal bounded scroller (no sticky, no
|
|
341
|
+
* maxHeight), so a virtualized body should window against the page (window
|
|
342
|
+
* scroll) instead of `scrollContainer`.
|
|
343
|
+
*/
|
|
344
|
+
pageScroll?: boolean;
|
|
293
345
|
};
|
|
294
346
|
export {};
|
package/dist/DataGrid/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid
|
|
2
|
+
@versini/ui-datagrid v4.0.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
-
import { useCallback, useMemo, useState } from "react";
|
|
7
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { BlurEffects } from "../799.js";
|
|
9
9
|
import { getDataGridClasses } from "../91.js";
|
|
10
10
|
import { DataGridContext } from "../430.js";
|
|
@@ -47,12 +47,21 @@ import { DataGridContext } from "../430.js";
|
|
|
47
47
|
const unregisterFooter = useCallback(()=>setFooterCount((c)=>c - 1), []);
|
|
48
48
|
const hasRegisteredHeader = headerCount > 0;
|
|
49
49
|
const hasRegisteredFooter = footerCount > 0;
|
|
50
|
+
const headerRows = hasRegisteredHeader ? 1 : 0;
|
|
51
|
+
const footerRows = hasRegisteredFooter ? 1 : 0;
|
|
50
52
|
/**
|
|
51
53
|
* Only apply sticky behavior if both the prop is true AND the corresponding
|
|
52
54
|
* component exists. This prevents adding padding/styles for non-existent
|
|
53
55
|
* headers/footers.
|
|
54
56
|
*/ const effectiveStickyHeader = stickyHeader && hasRegisteredHeader;
|
|
55
57
|
const effectiveStickyFooter = stickyFooter && hasRegisteredFooter;
|
|
58
|
+
const hasSticky = effectiveStickyHeader || effectiveStickyFooter;
|
|
59
|
+
/**
|
|
60
|
+
* Whether DataGrid owns a bounded internal scroll container (sticky → the
|
|
61
|
+
* scrollableContent area; maxHeight → the wrapper). When it doesn't, a
|
|
62
|
+
* virtualized body windows against the page instead (pageScroll).
|
|
63
|
+
*/ const hasInternalScroller = hasSticky || Boolean(maxHeight);
|
|
64
|
+
const pageScroll = !hasInternalScroller;
|
|
56
65
|
/**
|
|
57
66
|
* State to hold the caption ID registered by DataGridHeader. Used for
|
|
58
67
|
* aria-labelledby on the grid element for accessibility.
|
|
@@ -60,6 +69,28 @@ import { DataGridContext } from "../430.js";
|
|
|
60
69
|
const handleSetCaptionId = useCallback((id)=>{
|
|
61
70
|
setCaptionId(id);
|
|
62
71
|
}, []);
|
|
72
|
+
/**
|
|
73
|
+
* Total row count reported by DataGridInfiniteBody. Drives `aria-rowcount` on
|
|
74
|
+
* the grid. Seeded `undefined` so non-infinite grids render no `aria-rowcount`
|
|
75
|
+
* (markup stays unchanged).
|
|
76
|
+
*/ const [rowCount, setRowCount] = useState(undefined);
|
|
77
|
+
/**
|
|
78
|
+
* Focus sentinel: a virtualizing body moves focus here (the grid element,
|
|
79
|
+
* made focusable via tabIndex={-1}) when a focused row recycles out of the
|
|
80
|
+
* DOM, so focus never drops to document.body.
|
|
81
|
+
*/ const gridRef = useRef(null);
|
|
82
|
+
const focusGrid = useCallback(()=>{
|
|
83
|
+
gridRef.current?.focus();
|
|
84
|
+
}, []);
|
|
85
|
+
/**
|
|
86
|
+
* The internal scroll container a virtualized body should window against,
|
|
87
|
+
* exposed via context. DataGrid owns a bounded scroller only when sticky
|
|
88
|
+
* header/footer or maxHeight is set; otherwise the body windows against the
|
|
89
|
+
* page (pageScroll).
|
|
90
|
+
*/ const [scrollContainer, setScrollContainer] = useState(null);
|
|
91
|
+
const scrollContainerRef = useCallback((el)=>{
|
|
92
|
+
setScrollContainer(el);
|
|
93
|
+
}, []);
|
|
63
94
|
const classes = useMemo(()=>getDataGridClasses({
|
|
64
95
|
mode,
|
|
65
96
|
className,
|
|
@@ -90,7 +121,14 @@ import { DataGridContext } from "../430.js";
|
|
|
90
121
|
unregisterFooter,
|
|
91
122
|
setHeaderHeight,
|
|
92
123
|
setFooterHeight,
|
|
93
|
-
setMeasuredColumnWidths
|
|
124
|
+
setMeasuredColumnWidths,
|
|
125
|
+
rowCount,
|
|
126
|
+
setRowCount,
|
|
127
|
+
headerRows,
|
|
128
|
+
footerRows,
|
|
129
|
+
focusGrid,
|
|
130
|
+
scrollContainer,
|
|
131
|
+
pageScroll
|
|
94
132
|
}), [
|
|
95
133
|
mode,
|
|
96
134
|
compact,
|
|
@@ -103,7 +141,13 @@ import { DataGridContext } from "../430.js";
|
|
|
103
141
|
registerHeader,
|
|
104
142
|
unregisterHeader,
|
|
105
143
|
registerFooter,
|
|
106
|
-
unregisterFooter
|
|
144
|
+
unregisterFooter,
|
|
145
|
+
rowCount,
|
|
146
|
+
headerRows,
|
|
147
|
+
footerRows,
|
|
148
|
+
focusGrid,
|
|
149
|
+
scrollContainer,
|
|
150
|
+
pageScroll
|
|
107
151
|
]);
|
|
108
152
|
const wrapperStyle = maxHeight ? {
|
|
109
153
|
maxHeight: typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight
|
|
@@ -116,11 +160,6 @@ import { DataGridContext } from "../430.js";
|
|
|
116
160
|
paddingTop: effectiveStickyHeader ? headerHeight : undefined,
|
|
117
161
|
paddingBottom: effectiveStickyFooter ? footerHeight : undefined
|
|
118
162
|
};
|
|
119
|
-
/**
|
|
120
|
-
* When sticky header/footer is enabled, use Panel-like structure: - Outer
|
|
121
|
-
* wrapper has overflow-hidden - Scrollable content area in the middle with
|
|
122
|
-
* padding - Header/footer are absolutely positioned.
|
|
123
|
-
*/ const hasSticky = effectiveStickyHeader || effectiveStickyFooter;
|
|
124
163
|
/**
|
|
125
164
|
* When columns are provided, apply grid-template-columns at the grid level so
|
|
126
165
|
* all rows can use subgrid to inherit the same column sizing.
|
|
@@ -128,8 +167,11 @@ import { DataGridContext } from "../430.js";
|
|
|
128
167
|
gridTemplateColumns: columns.join(" ")
|
|
129
168
|
} : undefined;
|
|
130
169
|
const gridContent = /*#__PURE__*/ jsx("div", {
|
|
170
|
+
ref: gridRef,
|
|
131
171
|
role: "grid",
|
|
132
172
|
"aria-labelledby": captionId,
|
|
173
|
+
"aria-rowcount": rowCount,
|
|
174
|
+
tabIndex: rowCount != null ? -1 : undefined,
|
|
133
175
|
className: classes.grid,
|
|
134
176
|
style: gridStyle,
|
|
135
177
|
...rest,
|
|
@@ -158,9 +200,11 @@ import { DataGridContext } from "../430.js";
|
|
|
158
200
|
]
|
|
159
201
|
}),
|
|
160
202
|
/*#__PURE__*/ jsx("div", {
|
|
203
|
+
ref: hasInternalScroller && !hasSticky ? scrollContainerRef : undefined,
|
|
161
204
|
className: classes.wrapper,
|
|
162
205
|
style: wrapperStyle,
|
|
163
206
|
children: hasSticky ? /*#__PURE__*/ jsx("div", {
|
|
207
|
+
ref: scrollContainerRef,
|
|
164
208
|
className: classes.scrollableContent,
|
|
165
209
|
style: scrollableContentStyle,
|
|
166
210
|
children: gridContent
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid
|
|
2
|
+
@versini/ui-datagrid v4.0.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -54,6 +54,12 @@ const DEFAULT_ANIMATION_DURATION = 300;
|
|
|
54
54
|
*/ if (lastHeightRef.current === 0) {
|
|
55
55
|
lastHeightRef.current = ref.current.offsetHeight;
|
|
56
56
|
prevDependencyRef.current = dependency;
|
|
57
|
+
// If a prior animation was canceled and the content collapsed to 0,
|
|
58
|
+
// clear any locked height so the wrapper can't stay frozen.
|
|
59
|
+
setAnimationState((state)=>state.isAnimating ? {
|
|
60
|
+
height: "auto",
|
|
61
|
+
isAnimating: false
|
|
62
|
+
} : state);
|
|
57
63
|
return;
|
|
58
64
|
}
|
|
59
65
|
/**
|
|
@@ -68,8 +74,16 @@ const DEFAULT_ANIMATION_DURATION = 300;
|
|
|
68
74
|
* Update the stored height for next time.
|
|
69
75
|
*/ lastHeightRef.current = newHeight;
|
|
70
76
|
/**
|
|
71
|
-
* If heights are the same or previous was 0, no animation needed.
|
|
77
|
+
* If heights are the same or previous was 0, no animation needed. Reset any
|
|
78
|
+
* height left locked by a just-canceled animation, so the wrapper can't get
|
|
79
|
+
* stuck collapsed with `overflow: hidden` when the dependency keeps changing
|
|
80
|
+
* without a height change — e.g. a virtualized body reporting a scroll-driven
|
|
81
|
+
* count while its panel stays a fixed `maxHeight`.
|
|
72
82
|
*/ if (previousHeight === newHeight || previousHeight === 0) {
|
|
83
|
+
setAnimationState((state)=>state.isAnimating ? {
|
|
84
|
+
height: "auto",
|
|
85
|
+
isAnimating: false
|
|
86
|
+
} : state);
|
|
73
87
|
return;
|
|
74
88
|
}
|
|
75
89
|
/**
|
|
@@ -142,7 +156,6 @@ const DEFAULT_ANIMATION_DURATION = 300;
|
|
|
142
156
|
* <DataGrid maxHeight="400px" stickyHeader>
|
|
143
157
|
* <DataGridInfiniteBody
|
|
144
158
|
* data={largeData}
|
|
145
|
-
* batchSize={25}
|
|
146
159
|
* onVisibleCountChange={(count) => setVisibleCount(count)}
|
|
147
160
|
* >
|
|
148
161
|
* {(row) => (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid
|
|
2
|
+
@versini/ui-datagrid v4.0.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -89,7 +89,9 @@ DataGridFooter.displayName = "DataGridFooter";
|
|
|
89
89
|
return /*#__PURE__*/ jsx(DataGridContext.Provider, {
|
|
90
90
|
value: {
|
|
91
91
|
...ctx,
|
|
92
|
-
cellWrapper: CellWrapper.FOOTER
|
|
92
|
+
cellWrapper: CellWrapper.FOOTER,
|
|
93
|
+
// Footer is the last row (= total rowCount) when virtualized.
|
|
94
|
+
ariaRowIndex: ctx.rowCount != null ? ctx.rowCount : undefined
|
|
93
95
|
},
|
|
94
96
|
children: /*#__PURE__*/ jsx("div", {
|
|
95
97
|
ref: footerRef,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid
|
|
2
|
+
@versini/ui-datagrid v4.0.0
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -110,7 +110,9 @@ DataGridHeader.displayName = "DataGridHeader";
|
|
|
110
110
|
return /*#__PURE__*/ jsx(DataGridContext.Provider, {
|
|
111
111
|
value: {
|
|
112
112
|
...ctx,
|
|
113
|
-
cellWrapper: CellWrapper.HEADER
|
|
113
|
+
cellWrapper: CellWrapper.HEADER,
|
|
114
|
+
// Header is row 1 when the grid is virtualized (rowCount set).
|
|
115
|
+
ariaRowIndex: ctx.rowCount != null ? 1 : undefined
|
|
114
116
|
},
|
|
115
117
|
children: /*#__PURE__*/ jsxs("div", {
|
|
116
118
|
ref: headerRef,
|