@versini/ui-datagrid 0.3.8 → 0.4.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/README.md +106 -29
- package/dist/DataGrid/DataGrid.js +1 -1
- package/dist/DataGrid/DataGridContext.js +1 -1
- package/dist/DataGrid/DataGridTypes.d.ts +280 -0
- package/dist/DataGrid/DataGridTypes.js +9 -0
- 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 +10 -0
- package/dist/DataGridBody/getBodyClass.js +24 -0
- package/dist/DataGridBody/index.js +1 -1
- package/dist/DataGridBody/useColumnMeasurement.d.ts +11 -0
- package/dist/DataGridBody/useColumnMeasurement.js +68 -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 +335 -0
- package/dist/DataGridInfinite/index.d.ts +2 -4
- package/dist/DataGridInfinite/index.js +4 -8
- package/dist/DataGridRow/DataGridRow.js +5 -3
- 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.d.ts +6 -2
- package/dist/utilities/classes.js +35 -8
- 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
|
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
type CSSLength = `${number}px` | `${number}rem` | `${number}em` | `${number}ch` | `${number}vw` | `${number}vh` | `${number}cm` | `${number}mm` | `${number}in` | `${number}pt` | `${number}pc`;
|
|
3
|
+
type CSSPercentage = `${number}%`;
|
|
4
|
+
type CSSFraction = `${number}fr`;
|
|
5
|
+
type CSSVar = `var(${string})`;
|
|
6
|
+
type CSSCalc = `calc(${string})`;
|
|
7
|
+
type CSSClamp = `clamp(${string})`;
|
|
8
|
+
type CSSFitContent = `fit-content(${CSSLength | CSSPercentage})`;
|
|
9
|
+
type GridTrackPrimitive = CSSLength | CSSPercentage | CSSFraction | "auto" | "min-content" | "max-content" | "subgrid" | CSSFitContent | CSSVar | CSSCalc | CSSClamp;
|
|
10
|
+
type CSSMinMax = `minmax(${GridTrackPrimitive}, ${GridTrackPrimitive})`;
|
|
11
|
+
type CSSRepeat = `repeat(${number | "auto-fill" | "auto-fit"}, ${GridTrackPrimitive})`;
|
|
12
|
+
export type DataGridColumnSize = GridTrackPrimitive | CSSMinMax | CSSRepeat;
|
|
13
|
+
import type { BlurEffect, CellWrapperType, SortDirection, ThemeMode } from "../DataGridConstants/DataGridConstants";
|
|
14
|
+
export type DataGridProps = {
|
|
15
|
+
/**
|
|
16
|
+
* The content of the DataGrid (DataGridHeader, DataGridBody, DataGridFooter).
|
|
17
|
+
*/
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* CSS class to apply to the data grid.
|
|
21
|
+
*/
|
|
22
|
+
className?: string;
|
|
23
|
+
/**
|
|
24
|
+
* If true, the data grid will have reduced padding.
|
|
25
|
+
*/
|
|
26
|
+
compact?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* If true, a frosty overlay will be placed above the data grid, blocking all
|
|
29
|
+
* interactions (sorting, scrolling, etc.). The content remains visible but
|
|
30
|
+
* slightly blurred with a spinner.
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The max height of the data grid. Required for sticky header/footer to work.
|
|
36
|
+
* Accepts any valid CSS max-height value.
|
|
37
|
+
*/
|
|
38
|
+
maxHeight?: string;
|
|
39
|
+
/**
|
|
40
|
+
* The theme mode of the data grid. Controls colors and styling.
|
|
41
|
+
* @default "system"
|
|
42
|
+
*/
|
|
43
|
+
mode?: ThemeMode;
|
|
44
|
+
/**
|
|
45
|
+
* If true, the data grid header will be sticky (stays visible when scrolling).
|
|
46
|
+
* Requires maxHeight to be set or wrapperClassName to be set with a
|
|
47
|
+
* max-height.
|
|
48
|
+
*/
|
|
49
|
+
stickyHeader?: boolean;
|
|
50
|
+
/**
|
|
51
|
+
* If true, the data grid footer will be sticky (stays visible when scrolling).
|
|
52
|
+
* Requires maxHeight to be set or wrapperClassName to be set with a
|
|
53
|
+
* max-height.
|
|
54
|
+
*/
|
|
55
|
+
stickyFooter?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* The blur effect intensity for sticky header/footer backgrounds.
|
|
58
|
+
* @default "none"
|
|
59
|
+
*/
|
|
60
|
+
blurEffect?: BlurEffect;
|
|
61
|
+
/**
|
|
62
|
+
* This attribute defines an alternative text that summarizes the content of
|
|
63
|
+
* the data grid. Not visible but read by screen readers.
|
|
64
|
+
*/
|
|
65
|
+
summary?: string;
|
|
66
|
+
/**
|
|
67
|
+
* CSS class to apply to the data grid wrapper container.
|
|
68
|
+
*/
|
|
69
|
+
wrapperClassName?: string;
|
|
70
|
+
/**
|
|
71
|
+
* An array of CSS Grid track sizes that define the width of each column.
|
|
72
|
+
* Template-literal union covers common grid values (fr, %, px/em/rem/etc.,
|
|
73
|
+
* minmax(), fit-content(), repeat(auto-fill/fit, ...), var(), calc()) so
|
|
74
|
+
* obviously invalid strings are caught at compile time.
|
|
75
|
+
* @example ["1fr", "1fr", "auto"] - Two equal columns and one auto-sized column
|
|
76
|
+
* @example ["200px", "1fr", "min-content"] - Fixed, flexible, and content-sized columns
|
|
77
|
+
*/
|
|
78
|
+
columns?: ReadonlyArray<DataGridColumnSize>;
|
|
79
|
+
};
|
|
80
|
+
export type DataGridHeaderProps = {
|
|
81
|
+
/**
|
|
82
|
+
* The content of the header (DataGridRow elements).
|
|
83
|
+
*/
|
|
84
|
+
children: React.ReactNode;
|
|
85
|
+
/**
|
|
86
|
+
* This attribute defines the caption (or title) of the data grid. When
|
|
87
|
+
* provided, it will be rendered above the header row as part of the same
|
|
88
|
+
* block, so they stay together whether sticky or not.
|
|
89
|
+
*/
|
|
90
|
+
caption?: React.ReactNode;
|
|
91
|
+
/**
|
|
92
|
+
* CSS class to apply to the caption element.
|
|
93
|
+
*/
|
|
94
|
+
captionClassName?: string;
|
|
95
|
+
/**
|
|
96
|
+
* CSS class to apply to the header.
|
|
97
|
+
*/
|
|
98
|
+
className?: string;
|
|
99
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
100
|
+
export type DataGridBodyProps = {
|
|
101
|
+
/**
|
|
102
|
+
* The content of the body (DataGridRow elements).
|
|
103
|
+
*/
|
|
104
|
+
children: React.ReactNode;
|
|
105
|
+
/**
|
|
106
|
+
* CSS class to apply to the body.
|
|
107
|
+
*/
|
|
108
|
+
className?: string;
|
|
109
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
110
|
+
export type DataGridFooterProps = {
|
|
111
|
+
/**
|
|
112
|
+
* The content of the footer (DataGridRow elements).
|
|
113
|
+
*/
|
|
114
|
+
children: React.ReactNode;
|
|
115
|
+
/**
|
|
116
|
+
* CSS class to apply to the footer.
|
|
117
|
+
*/
|
|
118
|
+
className?: string;
|
|
119
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
120
|
+
export type DataGridRowProps = {
|
|
121
|
+
/**
|
|
122
|
+
* The content of the row (DataGridCell elements).
|
|
123
|
+
*/
|
|
124
|
+
children: React.ReactNode;
|
|
125
|
+
/**
|
|
126
|
+
* CSS class to apply to the row.
|
|
127
|
+
*/
|
|
128
|
+
className?: string;
|
|
129
|
+
/**
|
|
130
|
+
* If true, the row will display a left border indicator to show it's active.
|
|
131
|
+
* @default false
|
|
132
|
+
*/
|
|
133
|
+
active?: boolean;
|
|
134
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
135
|
+
export type DataGridCellProps = {
|
|
136
|
+
/**
|
|
137
|
+
* The content of the cell.
|
|
138
|
+
*/
|
|
139
|
+
children?: React.ReactNode;
|
|
140
|
+
/**
|
|
141
|
+
* CSS class to apply to the cell.
|
|
142
|
+
*/
|
|
143
|
+
className?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Horizontal alignment of the cell content.
|
|
146
|
+
*/
|
|
147
|
+
align?: "left" | "center" | "right";
|
|
148
|
+
/**
|
|
149
|
+
* If true, adds a vertical border on the left side of the cell.
|
|
150
|
+
*/
|
|
151
|
+
borderLeft?: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* If true, adds a vertical border on the right side of the cell.
|
|
154
|
+
*/
|
|
155
|
+
borderRight?: boolean;
|
|
156
|
+
/**
|
|
157
|
+
* Number of columns the cell should span. Defaults to 1.
|
|
158
|
+
*/
|
|
159
|
+
colSpan?: number;
|
|
160
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
161
|
+
export type DataGridCellSortProps = {
|
|
162
|
+
/**
|
|
163
|
+
* Unique identifier for the cell. Used to determine which column is sorted.
|
|
164
|
+
*/
|
|
165
|
+
cellId: string;
|
|
166
|
+
/**
|
|
167
|
+
* The label text for the sortable column header.
|
|
168
|
+
*/
|
|
169
|
+
children: string;
|
|
170
|
+
/**
|
|
171
|
+
* Handler called when the sort button is clicked. Receives the cellId and
|
|
172
|
+
* current direction (if sorted).
|
|
173
|
+
*/
|
|
174
|
+
onSort?: (cellId: string, currentDirection?: SortDirection) => void;
|
|
175
|
+
/**
|
|
176
|
+
* The current sort direction.
|
|
177
|
+
*/
|
|
178
|
+
sortDirection: SortDirection | false;
|
|
179
|
+
/**
|
|
180
|
+
* The cellId of the currently sorted column.
|
|
181
|
+
*/
|
|
182
|
+
sortedCell: string;
|
|
183
|
+
/**
|
|
184
|
+
* Horizontal alignment of the cell content.
|
|
185
|
+
*/
|
|
186
|
+
align?: "left" | "center" | "right";
|
|
187
|
+
/**
|
|
188
|
+
* CSS class to apply to the sort button.
|
|
189
|
+
*/
|
|
190
|
+
buttonClassName?: string;
|
|
191
|
+
/**
|
|
192
|
+
* CSS class to apply to the cell.
|
|
193
|
+
*/
|
|
194
|
+
className?: string;
|
|
195
|
+
/**
|
|
196
|
+
* The focus mode for the sort button.
|
|
197
|
+
* @default "alt-system"
|
|
198
|
+
*/
|
|
199
|
+
focusMode?: ThemeMode;
|
|
200
|
+
/**
|
|
201
|
+
* The theme mode for the sort button.
|
|
202
|
+
* @default "alt-system"
|
|
203
|
+
*/
|
|
204
|
+
mode?: ThemeMode;
|
|
205
|
+
/**
|
|
206
|
+
* Content to display on the left side of the label.
|
|
207
|
+
*/
|
|
208
|
+
slotLeft?: React.ReactNode;
|
|
209
|
+
/**
|
|
210
|
+
* Content to display on the right side of the label.
|
|
211
|
+
*/
|
|
212
|
+
slotRight?: React.ReactNode;
|
|
213
|
+
} & React.HTMLAttributes<HTMLDivElement>;
|
|
214
|
+
export type DataGridContextValue = {
|
|
215
|
+
mode: ThemeMode;
|
|
216
|
+
stickyHeader?: boolean;
|
|
217
|
+
stickyFooter?: boolean;
|
|
218
|
+
compact?: boolean;
|
|
219
|
+
blurEffect?: BlurEffect;
|
|
220
|
+
cellWrapper?: CellWrapperType;
|
|
221
|
+
/**
|
|
222
|
+
* CSS grid column sizes passed from DataGrid to rows.
|
|
223
|
+
*/
|
|
224
|
+
columns?: ReadonlyArray<DataGridColumnSize>;
|
|
225
|
+
/**
|
|
226
|
+
* Measured column widths in pixels, reported by DataGridBody. Used by sticky
|
|
227
|
+
* header/footer to sync column widths since they can't use subgrid (being
|
|
228
|
+
* absolutely positioned).
|
|
229
|
+
*/
|
|
230
|
+
measuredColumnWidths?: number[];
|
|
231
|
+
/**
|
|
232
|
+
* Callback for DataGridHeader to register its caption ID for accessibility.
|
|
233
|
+
* DataGrid uses this to set aria-labelledby on the grid element.
|
|
234
|
+
*/
|
|
235
|
+
setCaptionId?: (id: string | undefined) => void;
|
|
236
|
+
/**
|
|
237
|
+
* Callback for DataGridHeader to register itself with the parent DataGrid.
|
|
238
|
+
* Used to enable sticky header behavior regardless of nesting depth.
|
|
239
|
+
*/
|
|
240
|
+
registerHeader?: () => void;
|
|
241
|
+
/**
|
|
242
|
+
* Callback for DataGridHeader to unregister itself when unmounting.
|
|
243
|
+
*/
|
|
244
|
+
unregisterHeader?: () => void;
|
|
245
|
+
/**
|
|
246
|
+
* Callback for DataGridFooter to register itself with the parent DataGrid.
|
|
247
|
+
* Used to enable sticky footer behavior regardless of nesting depth.
|
|
248
|
+
*/
|
|
249
|
+
registerFooter?: () => void;
|
|
250
|
+
/**
|
|
251
|
+
* Callback for DataGridFooter to unregister itself when unmounting.
|
|
252
|
+
*/
|
|
253
|
+
unregisterFooter?: () => void;
|
|
254
|
+
/**
|
|
255
|
+
* Callback for DataGridHeader to report its measured height. Used for dynamic
|
|
256
|
+
* padding calculation instead of hard-coded values.
|
|
257
|
+
*/
|
|
258
|
+
setHeaderHeight?: (height: number) => void;
|
|
259
|
+
/**
|
|
260
|
+
* Callback for DataGridFooter to report its measured height. Used for dynamic
|
|
261
|
+
* padding calculation instead of hard-coded values.
|
|
262
|
+
*/
|
|
263
|
+
setFooterHeight?: (height: number) => void;
|
|
264
|
+
/**
|
|
265
|
+
* Callback for DataGridBody to report measured column widths. Used to sync
|
|
266
|
+
* sticky header/footer columns with body columns.
|
|
267
|
+
*/
|
|
268
|
+
setMeasuredColumnWidths?: (widths: number[]) => void;
|
|
269
|
+
/**
|
|
270
|
+
* Row index for explicit odd/even styling. Used by DataGridInfiniteBody where
|
|
271
|
+
* CSS :nth-child selectors don't work due to wrapper elements.
|
|
272
|
+
*/
|
|
273
|
+
rowIndex?: number;
|
|
274
|
+
/**
|
|
275
|
+
* Whether this is the last row (for explicit border removal). Used by
|
|
276
|
+
* DataGridInfiniteBody where CSS :last-child doesn't work.
|
|
277
|
+
*/
|
|
278
|
+
isLastRow?: boolean;
|
|
279
|
+
};
|
|
280
|
+
export {};
|
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.1
|
|
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.1
|
|
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,
|