@hexclave/dashboard-ui-components 1.0.49 → 1.0.51
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/dist/components/button.d.ts +4 -4
- package/dist/components/data-grid/data-grid.js +7 -5
- package/dist/components/data-grid/data-grid.js.map +1 -1
- package/dist/components/data-grid/data-grid.test.js +36 -3
- package/dist/components/data-grid/data-grid.test.js.map +1 -1
- package/dist/dashboard-ui-components.global.js +49 -47
- package/dist/dashboard-ui-components.global.js.map +4 -4
- package/dist/data-grid-AJi1TNDa.d.ts.map +1 -1
- package/dist/esm/components/button.d.ts +4 -4
- package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -1
- package/dist/esm/components/data-grid/data-grid.js +7 -5
- package/dist/esm/components/data-grid/data-grid.js.map +1 -1
- package/dist/esm/components/data-grid/data-grid.test.js +36 -3
- package/dist/esm/components/data-grid/data-grid.test.js.map +1 -1
- package/package.json +3 -3
- package/src/components/data-grid/data-grid.test.tsx +46 -3
- package/src/components/data-grid/data-grid.tsx +19 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hexclave/dashboard-ui-components",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.51",
|
|
4
4
|
"repository": "https://github.com/hexclave/hexclave",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -47,8 +47,8 @@
|
|
|
47
47
|
"@tanstack/react-table": "^8.21.3",
|
|
48
48
|
"@tanstack/react-virtual": "^3.13.0",
|
|
49
49
|
"class-variance-authority": "^0.7.0",
|
|
50
|
-
"@hexclave/shared": "1.0.
|
|
51
|
-
"@hexclave/ui": "1.0.
|
|
50
|
+
"@hexclave/shared": "1.0.51",
|
|
51
|
+
"@hexclave/ui": "1.0.51"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/react": "^19.0.0",
|
|
@@ -166,6 +166,24 @@ function DataGridHarness(props: { fillHeight?: boolean }) {
|
|
|
166
166
|
);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function PaginatedDataGridHarness() {
|
|
170
|
+
const [state, setState] = useState(() => createDefaultDataGridState(columns));
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div style={{ height: 400 }}>
|
|
174
|
+
<DataGrid<Row>
|
|
175
|
+
columns={columns}
|
|
176
|
+
rows={[{ id: "row-1", name: "Row 1" }]}
|
|
177
|
+
getRowId={(row) => row.id}
|
|
178
|
+
state={state}
|
|
179
|
+
onChange={setState}
|
|
180
|
+
paginationMode="paginated"
|
|
181
|
+
fillHeight={false}
|
|
182
|
+
/>
|
|
183
|
+
</div>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
169
187
|
function InteractiveDataGridHarness(props: {
|
|
170
188
|
onSortChange?: DataGridProps<Row>["onSortChange"],
|
|
171
189
|
onSelectionChange?: DataGridProps<Row>["onSelectionChange"],
|
|
@@ -259,14 +277,39 @@ describe("DataGrid infinite scroll observer", () => {
|
|
|
259
277
|
expect(intersectionObserverRecords.at(-1)?.options?.root).toBe(scrollContainer);
|
|
260
278
|
});
|
|
261
279
|
|
|
262
|
-
|
|
263
|
-
|
|
280
|
+
// Regression: an infinite grid left unbounded (`fillHeight={false}`, no `maxHeight`) used to
|
|
281
|
+
// grow its scroll container to fit every loaded row, which defeats virtualization (the
|
|
282
|
+
// virtualizer measures the container as fully visible and mounts every row) and OOMs the tab on
|
|
283
|
+
// large datasets. Such grids now fall back to a default `maxHeight`, so the grid owns its own
|
|
284
|
+
// bounded scroll container and observes against it rather than the viewport.
|
|
285
|
+
it("bounds an unbounded infinite grid and observes against its own scroll container", async () => {
|
|
286
|
+
const { container } = render(<DataGridHarness fillHeight={false} />);
|
|
264
287
|
|
|
265
288
|
await waitFor(() => {
|
|
266
289
|
expect(intersectionObserverRecords.length).toBeGreaterThan(0);
|
|
267
290
|
});
|
|
268
291
|
|
|
269
|
-
|
|
292
|
+
const grid = container.querySelector('[role="grid"]');
|
|
293
|
+
expect(grid).not.toBeNull();
|
|
294
|
+
const scrollContainer = grid?.children.item(1);
|
|
295
|
+
|
|
296
|
+
expect(intersectionObserverRecords.at(-1)?.options?.root).toBe(scrollContainer);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("applies a default maxHeight to an otherwise-unbounded infinite grid", () => {
|
|
300
|
+
const { container } = render(<DataGridHarness fillHeight={false} />);
|
|
301
|
+
|
|
302
|
+
const grid = container.querySelector<HTMLElement>('[role="grid"]');
|
|
303
|
+
expect(grid).not.toBeNull();
|
|
304
|
+
expect(grid?.style.maxHeight).toBe("calc(100dvh - 16rem)");
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
it("does not force a maxHeight onto a paginated grid", () => {
|
|
308
|
+
const { container } = render(<PaginatedDataGridHarness />);
|
|
309
|
+
|
|
310
|
+
const grid = container.querySelector<HTMLElement>('[role="grid"]');
|
|
311
|
+
expect(grid).not.toBeNull();
|
|
312
|
+
expect(grid?.style.maxHeight).toBe("");
|
|
270
313
|
});
|
|
271
314
|
});
|
|
272
315
|
|
|
@@ -58,6 +58,11 @@ import type {
|
|
|
58
58
|
RowId,
|
|
59
59
|
} from "./types";
|
|
60
60
|
|
|
61
|
+
// Viewport-relative fallback height for infinite-scroll grids that the caller left unbounded
|
|
62
|
+
// (no `fillHeight`, no `maxHeight`). Leaves ~16rem of room for the top bar, page header, and grid
|
|
63
|
+
// toolbar. See the `effectiveMaxHeight` comment in DataGrid for why an infinite grid must be bounded.
|
|
64
|
+
const DEFAULT_INFINITE_MAX_HEIGHT = "calc(100dvh - 16rem)";
|
|
65
|
+
|
|
61
66
|
// ─── Row click target ────────────────────────────────────────────────
|
|
62
67
|
|
|
63
68
|
function getEventTargetElement(target: EventTarget | null): Element | null {
|
|
@@ -997,8 +1002,18 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
|
|
|
997
1002
|
|
|
998
1003
|
const allSelected = rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id));
|
|
999
1004
|
const someSelected = !allSelected && rowIds.some((id) => state.selection.selectedIds.has(id));
|
|
1005
|
+
|
|
1006
|
+
// An infinite-scroll grid MUST have a height-bounded scroll container, otherwise the container
|
|
1007
|
+
// grows to fit every loaded row, the virtualizer measures it as fully visible, and it renders
|
|
1008
|
+
// all rows into the DOM — unbounded memory growth that eventually OOMs the tab (rows never stop
|
|
1009
|
+
// accumulating as the user scrolls). When the caller didn't bound it (no fillHeight, no maxHeight),
|
|
1010
|
+
// fall back to a viewport-relative cap so virtualization can actually window. Only applied to
|
|
1011
|
+
// infinite mode; paginated grids cap rows at the page size and are safe to render unbounded.
|
|
1012
|
+
const effectiveMaxHeight =
|
|
1013
|
+
maxHeight ?? (paginationMode === "infinite" && !fillHeight ? DEFAULT_INFINITE_MAX_HEIGHT : undefined);
|
|
1014
|
+
|
|
1000
1015
|
const infiniteScrollRootRef =
|
|
1001
|
-
paginationMode === "infinite" && (fillHeight ||
|
|
1016
|
+
paginationMode === "infinite" && (fillHeight || effectiveMaxHeight != null)
|
|
1002
1017
|
? scrollContainerRef
|
|
1003
1018
|
: undefined;
|
|
1004
1019
|
|
|
@@ -1016,7 +1031,7 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
|
|
|
1016
1031
|
return m;
|
|
1017
1032
|
}, [headers]);
|
|
1018
1033
|
|
|
1019
|
-
const isBounded = fillHeight ||
|
|
1034
|
+
const isBounded = fillHeight || effectiveMaxHeight != null;
|
|
1020
1035
|
|
|
1021
1036
|
return (
|
|
1022
1037
|
<>
|
|
@@ -1036,7 +1051,7 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
|
|
|
1036
1051
|
isBounded && "overflow-hidden",
|
|
1037
1052
|
className,
|
|
1038
1053
|
)}
|
|
1039
|
-
style={
|
|
1054
|
+
style={effectiveMaxHeight != null ? { ...cssVars, maxHeight: effectiveMaxHeight } : cssVars}
|
|
1040
1055
|
role="grid"
|
|
1041
1056
|
aria-rowcount={totalRowCount ?? rows.length}
|
|
1042
1057
|
aria-colcount={visibleColumns.length}
|
|
@@ -1044,7 +1059,7 @@ export function DataGrid<TRow>(props: DataGridProps<TRow>) {
|
|
|
1044
1059
|
<div
|
|
1045
1060
|
ref={stickyChromeRef}
|
|
1046
1061
|
className="sticky z-30 w-full min-w-0 shrink-0 overflow-visible rounded-t-[calc(var(--radius)*2)] bg-white/90 dark:bg-background backdrop-blur-xl"
|
|
1047
|
-
style={{ top: stickyTop ?? (
|
|
1062
|
+
style={{ top: stickyTop ?? (effectiveMaxHeight != null ? 0 : "var(--data-grid-sticky-top, 0px)") }}
|
|
1048
1063
|
>
|
|
1049
1064
|
{toolbar !== false && (
|
|
1050
1065
|
<div className="relative bg-transparent">
|