@versini/ui-datagrid 0.4.0 → 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/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.js +1 -1
- package/dist/DataGridAnimated/index.js +1 -1
- package/dist/DataGridAnimated/useAnimatedHeight.js +1 -1
- package/dist/DataGridBody/DataGridBody.js +1 -1
- package/dist/DataGridBody/getBodyClass.d.ts +4 -3
- package/dist/DataGridBody/getBodyClass.js +5 -4
- package/dist/DataGridBody/index.js +1 -1
- package/dist/DataGridBody/useColumnMeasurement.d.ts +5 -4
- package/dist/DataGridBody/useColumnMeasurement.js +6 -5
- 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 +6 -6
- package/dist/DataGridInfinite/DataGridInfiniteBody.js +55 -29
- package/dist/DataGridInfinite/index.js +1 -1
- 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
|
@@ -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
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Get the CSS class for a DataGrid body element.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Get the CSS class for a DataGrid body element. When columns are provided, use
|
|
3
|
+
* display:contents so the body doesn't interfere with the grid flow. Rows will
|
|
4
|
+
* use subgrid.
|
|
5
5
|
*
|
|
6
6
|
* @param hasColumns - Whether the DataGrid has columns defined
|
|
7
7
|
* @param className - Additional class name to merge
|
|
8
|
+
*
|
|
8
9
|
*/
|
|
9
10
|
export declare function getBodyClass(hasColumns: boolean | undefined, className?: string): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.4.
|
|
2
|
+
@versini/ui-datagrid v0.4.1
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -10,12 +10,13 @@ import clsx from "clsx";
|
|
|
10
10
|
;// CONCATENATED MODULE: ./src/DataGridBody/getBodyClass.ts
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* Get the CSS class for a DataGrid body element.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Get the CSS class for a DataGrid body element. When columns are provided, use
|
|
14
|
+
* display:contents so the body doesn't interfere with the grid flow. Rows will
|
|
15
|
+
* use subgrid.
|
|
16
16
|
*
|
|
17
17
|
* @param hasColumns - Whether the DataGrid has columns defined
|
|
18
18
|
* @param className - Additional class name to merge
|
|
19
|
+
*
|
|
19
20
|
*/ function getBodyClass(hasColumns, className) {
|
|
20
21
|
return hasColumns ? clsx("contents", className) : clsx("flex flex-col", className);
|
|
21
22
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hook to measure column widths from the first body row's cells.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Hook to measure column widths from the first body row's cells. This is needed
|
|
3
|
+
* because sticky header/footer are absolutely positioned and can't use CSS
|
|
4
|
+
* subgrid. We measure the body cells (which ARE in the grid flow) and report
|
|
5
|
+
* the widths so header/footer can use the same pixel values.
|
|
6
6
|
*
|
|
7
7
|
* @param bodyRef - Ref to the body element containing the rows
|
|
8
8
|
* @param contentDependency - Dependency that changes when content changes (children or renderedContent)
|
|
9
|
+
*
|
|
9
10
|
*/
|
|
10
11
|
export declare function useColumnMeasurement(bodyRef: React.RefObject<HTMLDivElement | null>, contentDependency: unknown): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.4.
|
|
2
|
+
@versini/ui-datagrid v0.4.1
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -14,13 +14,14 @@ import { DataGridContext } from "../DataGrid/DataGridContext.js";
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Hook to measure column widths from the first body row's cells.
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
17
|
+
* Hook to measure column widths from the first body row's cells. This is needed
|
|
18
|
+
* because sticky header/footer are absolutely positioned and can't use CSS
|
|
19
|
+
* subgrid. We measure the body cells (which ARE in the grid flow) and report
|
|
20
|
+
* the widths so header/footer can use the same pixel values.
|
|
21
21
|
*
|
|
22
22
|
* @param bodyRef - Ref to the body element containing the rows
|
|
23
23
|
* @param contentDependency - Dependency that changes when content changes (children or renderedContent)
|
|
24
|
+
*
|
|
24
25
|
*/ function useColumnMeasurement(bodyRef, contentDependency) {
|
|
25
26
|
const ctx = useContext(DataGridContext);
|
|
26
27
|
// biome-ignore lint/correctness/useExhaustiveDependencies: contentDependency triggers remeasurement when rows change
|
|
@@ -4,8 +4,8 @@ export type DataGridInfiniteBodyProps<T> = {
|
|
|
4
4
|
*/
|
|
5
5
|
data: T[];
|
|
6
6
|
/**
|
|
7
|
-
* Render function for each row. Should return a DataGridRow element.
|
|
8
|
-
*
|
|
7
|
+
* Render function for each row. Should return a DataGridRow element. The
|
|
8
|
+
* consumer is responsible for providing the key prop on the returned element.
|
|
9
9
|
*/
|
|
10
10
|
children: (item: T, index: number) => React.ReactNode;
|
|
11
11
|
/**
|
|
@@ -14,8 +14,8 @@ export type DataGridInfiniteBodyProps<T> = {
|
|
|
14
14
|
*/
|
|
15
15
|
batchSize?: number;
|
|
16
16
|
/**
|
|
17
|
-
* How many items to keep below the marker for seamless scrolling.
|
|
18
|
-
*
|
|
17
|
+
* How many items to keep below the marker for seamless scrolling. The marker
|
|
18
|
+
* is placed `threshold` items before the end of visible items.
|
|
19
19
|
* @default 5
|
|
20
20
|
*/
|
|
21
21
|
threshold?: number;
|
|
@@ -38,8 +38,8 @@ export type DataGridInfiniteBodyProps<T> = {
|
|
|
38
38
|
*/
|
|
39
39
|
export type DataGridInfiniteBodyRef = {
|
|
40
40
|
/**
|
|
41
|
-
* Scroll to a specific row index with smooth animation.
|
|
42
|
-
*
|
|
41
|
+
* Scroll to a specific row index with smooth animation. If the row is not yet
|
|
42
|
+
* visible, it will expand the visible count first.
|
|
43
43
|
* @param index - The index of the row to scroll to
|
|
44
44
|
*/
|
|
45
45
|
scrollToIndex: (index: number) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.4.
|
|
2
|
+
@versini/ui-datagrid v0.4.1
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -34,8 +34,8 @@ const DEFAULT_THRESHOLD = 5;
|
|
|
34
34
|
const DEFAULT_ROOT_MARGIN = "20px";
|
|
35
35
|
const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
36
36
|
/**
|
|
37
|
-
* Finds the nearest scrollable ancestor of an element.
|
|
38
|
-
*
|
|
37
|
+
* Finds the nearest scrollable ancestor of an element. Returns null if no
|
|
38
|
+
* scrollable ancestor is found (uses viewport).
|
|
39
39
|
*/ function findScrollableAncestor(element) {
|
|
40
40
|
let parent = element.parentElement;
|
|
41
41
|
while(parent){
|
|
@@ -89,6 +89,7 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
89
89
|
* </DataGridInfiniteBody>
|
|
90
90
|
* </DataGrid>
|
|
91
91
|
* ```
|
|
92
|
+
*
|
|
92
93
|
*/ function DataGridInfiniteBodyInner({ data, children: renderRow, batchSize = DEFAULT_BATCH_SIZE, threshold = DEFAULT_THRESHOLD, rootMargin = DEFAULT_ROOT_MARGIN, onVisibleCountChange, className }, ref) {
|
|
93
94
|
const ctx = useContext(DataGridContext);
|
|
94
95
|
const bodyRef = useRef(null);
|
|
@@ -100,9 +101,9 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
100
101
|
const hasMore = visibleCount < totalItems;
|
|
101
102
|
/**
|
|
102
103
|
* Scroll to a row by its index. Called after visibleCount updates.
|
|
103
|
-
*
|
|
104
|
-
* actual row element inside it (since the wrapper has display:contents
|
|
105
|
-
*
|
|
104
|
+
* NOTE: We query for the wrapper with data-row-index, then scroll to the
|
|
105
|
+
* actual row element inside it (since the wrapper has display:contents and
|
|
106
|
+
* doesn't have a bounding box).
|
|
106
107
|
*/ const scrollToRowElement = useCallback((index)=>{
|
|
107
108
|
const body = bodyRef.current;
|
|
108
109
|
if (!body) {
|
|
@@ -151,9 +152,10 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
151
152
|
scrollToRowElement(index);
|
|
152
153
|
return;
|
|
153
154
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Otherwise, expand visible count to include the target row. Add some
|
|
157
|
+
* buffer so the row isn't at the very edge.
|
|
158
|
+
*/ const targetCount = Math.min(index + threshold + 1, totalItems);
|
|
157
159
|
pendingScrollRef.current = index;
|
|
158
160
|
setVisibleCount(targetCount);
|
|
159
161
|
}
|
|
@@ -180,8 +182,8 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
180
182
|
onVisibleCountChange
|
|
181
183
|
]);
|
|
182
184
|
/**
|
|
183
|
-
* IntersectionObserver callback - triggered when marker becomes visible.
|
|
184
|
-
*
|
|
185
|
+
* IntersectionObserver callback - triggered when marker becomes visible. Loads
|
|
186
|
+
* the next batch of items.
|
|
185
187
|
*/ const handleIntersection = useCallback((entries)=>{
|
|
186
188
|
const target = entries[0];
|
|
187
189
|
if (target?.isIntersecting) {
|
|
@@ -192,10 +194,10 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
192
194
|
totalItems
|
|
193
195
|
]);
|
|
194
196
|
/**
|
|
195
|
-
* Callback ref for the marker element.
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
197
|
+
* Callback ref for the marker element. Sets up IntersectionObserver when
|
|
198
|
+
* marker mounts, cleans up when it unmounts. This pattern ensures the observer
|
|
199
|
+
* always watches the current marker element, even when visibleCount changes
|
|
200
|
+
* and a new marker is created at a different position.
|
|
199
201
|
*/ const markerRefCallback = useCallback((node)=>{
|
|
200
202
|
// Clean up previous observer.
|
|
201
203
|
if (observerRef.current) {
|
|
@@ -223,10 +225,9 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
223
225
|
};
|
|
224
226
|
}, []);
|
|
225
227
|
/**
|
|
226
|
-
* Calculate marker position.
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
* the remaining `threshold` items.
|
|
228
|
+
* Calculate marker position. The marker should be placed `threshold` items
|
|
229
|
+
* from the end of visible items. This allows seamless scrolling - new items
|
|
230
|
+
* load while user scrolls through the remaining `threshold` items.
|
|
230
231
|
*/ const markerPosition = useMemo(()=>{
|
|
231
232
|
if (!hasMore) {
|
|
232
233
|
return -1; // No marker needed when all items are loaded.
|
|
@@ -239,10 +240,22 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
239
240
|
threshold
|
|
240
241
|
]);
|
|
241
242
|
/**
|
|
242
|
-
*
|
|
243
|
-
|
|
243
|
+
* Context value for body rows (shared base, rowIndex added per-row).
|
|
244
|
+
*/ const bodyContextBase = useMemo(()=>({
|
|
245
|
+
...ctx,
|
|
246
|
+
cellWrapper: CellWrapper.BODY
|
|
247
|
+
}), [
|
|
248
|
+
ctx
|
|
249
|
+
]);
|
|
250
|
+
/**
|
|
251
|
+
* Render items with marker at the correct position. Each row gets a
|
|
252
|
+
* data-row-index attribute for scrollToIndex functionality. Each row is
|
|
253
|
+
* wrapped with a context provider that includes the row index for proper
|
|
254
|
+
* odd/even styling (CSS :nth-child doesn't work with wrappers).
|
|
244
255
|
*/ const renderedContent = useMemo(()=>{
|
|
245
256
|
const result = [];
|
|
257
|
+
// Determine the actual last visible index (for border styling).
|
|
258
|
+
const lastVisibleIndex = Math.min(visibleCount, totalItems) - 1;
|
|
246
259
|
for(let i = 0; i < visibleCount && i < totalItems; i++){
|
|
247
260
|
// Insert marker at the calculated position.
|
|
248
261
|
if (i === markerPosition) {
|
|
@@ -255,14 +268,26 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
255
268
|
}
|
|
256
269
|
}, "__infinite-scroll-marker-inline__"));
|
|
257
270
|
}
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
271
|
+
/**
|
|
272
|
+
* Determine if this is the last row (only when all data is loaded). If
|
|
273
|
+
* hasMore is true, no row is "last" since more will be loaded.
|
|
274
|
+
*/ const isLastRow = !hasMore && i === lastVisibleIndex;
|
|
275
|
+
/**
|
|
276
|
+
* Wrap row with context provider that includes rowIndex for proper odd/even
|
|
277
|
+
* styling. Using display:contents so the wrapper doesn't affect grid layout.
|
|
278
|
+
*/ result.push(/*#__PURE__*/ jsx(DataGridContext.Provider, {
|
|
279
|
+
value: {
|
|
280
|
+
...bodyContextBase,
|
|
281
|
+
rowIndex: i,
|
|
282
|
+
isLastRow
|
|
264
283
|
},
|
|
265
|
-
children:
|
|
284
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
285
|
+
[ROW_INDEX_DATA_ATTR]: i,
|
|
286
|
+
style: {
|
|
287
|
+
display: "contents"
|
|
288
|
+
},
|
|
289
|
+
children: renderRow(data[i], i)
|
|
290
|
+
})
|
|
266
291
|
}, i));
|
|
267
292
|
}
|
|
268
293
|
// If marker position is at the end (edge case with small data).
|
|
@@ -284,7 +309,8 @@ const ROW_INDEX_DATA_ATTR = "data-row-index";
|
|
|
284
309
|
markerPosition,
|
|
285
310
|
hasMore,
|
|
286
311
|
renderRow,
|
|
287
|
-
markerRefCallback
|
|
312
|
+
markerRefCallback,
|
|
313
|
+
bodyContextBase
|
|
288
314
|
]);
|
|
289
315
|
// Measure column widths for sticky header/footer sync.
|
|
290
316
|
useColumnMeasurement(bodyRef, renderedContent);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.4.
|
|
2
|
+
@versini/ui-datagrid v0.4.1
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -31,7 +31,7 @@ import { getRowClasses } from "../utilities/classes.js";
|
|
|
31
31
|
// Count the number of direct children to determine column count.
|
|
32
32
|
const columnCount = react.Children.count(children);
|
|
33
33
|
return /*#__PURE__*/ jsx(DataGridContext.Consumer, {
|
|
34
|
-
children: ({ mode, cellWrapper, stickyHeader, stickyFooter, columns, measuredColumnWidths })=>{
|
|
34
|
+
children: ({ mode, cellWrapper, stickyHeader, stickyFooter, columns, measuredColumnWidths, rowIndex, isLastRow })=>{
|
|
35
35
|
/**
|
|
36
36
|
* Determine if this row is inside a sticky header/footer. Sticky elements
|
|
37
37
|
* are absolutely positioned and outside the main grid flow, so they can't
|
|
@@ -88,7 +88,9 @@ import { getRowClasses } from "../utilities/classes.js";
|
|
|
88
88
|
className: getRowClasses({
|
|
89
89
|
mode,
|
|
90
90
|
className,
|
|
91
|
-
cellWrapper
|
|
91
|
+
cellWrapper,
|
|
92
|
+
rowIndex,
|
|
93
|
+
isLastRow
|
|
92
94
|
}),
|
|
93
95
|
style: {
|
|
94
96
|
...rowStyle,
|
|
@@ -124,12 +124,16 @@ export declare const getFooterClasses: ({ className, stickyFooter, mode, blurEff
|
|
|
124
124
|
stickyFooter?: boolean;
|
|
125
125
|
}) => string;
|
|
126
126
|
/**
|
|
127
|
-
* Generates classes for DataGridRow.
|
|
127
|
+
* Generates classes for DataGridRow. When rowIndex is provided (e.g., from
|
|
128
|
+
* DataGridInfiniteBody), explicit odd/even classes are used instead of CSS
|
|
129
|
+
* :nth-child selectors, which don't work with wrapper elements.
|
|
128
130
|
*/
|
|
129
|
-
export declare const getRowClasses: ({ mode, className, cellWrapper, }: {
|
|
131
|
+
export declare const getRowClasses: ({ mode, className, cellWrapper, rowIndex, isLastRow, }: {
|
|
130
132
|
mode: ThemeMode;
|
|
131
133
|
cellWrapper?: CellWrapperType;
|
|
132
134
|
className?: string;
|
|
135
|
+
rowIndex?: number;
|
|
136
|
+
isLastRow?: boolean;
|
|
133
137
|
}) => string;
|
|
134
138
|
/**
|
|
135
139
|
* Generates classes for DataGridCell.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v0.4.
|
|
2
|
+
@versini/ui-datagrid v0.4.1
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -181,22 +181,49 @@ import { BlurEffects, CellWrapper } from "../DataGridConstants/DataGridConstants
|
|
|
181
181
|
}), className);
|
|
182
182
|
};
|
|
183
183
|
/**
|
|
184
|
-
* Generates classes for DataGridRow.
|
|
185
|
-
|
|
184
|
+
* Generates classes for DataGridRow. When rowIndex is provided (e.g., from
|
|
185
|
+
* DataGridInfiniteBody), explicit odd/even classes are used instead of CSS
|
|
186
|
+
* :nth-child selectors, which don't work with wrapper elements.
|
|
187
|
+
*/ const getRowClasses = ({ mode, className, cellWrapper, rowIndex, isLastRow })=>{
|
|
186
188
|
const layoutClass = "group grid items-center";
|
|
187
189
|
if (cellWrapper === CellWrapper.HEADER || cellWrapper === CellWrapper.FOOTER) {
|
|
188
190
|
return clsx(layoutClass, className);
|
|
189
191
|
}
|
|
190
|
-
|
|
192
|
+
/**
|
|
193
|
+
* When rowIndex is provided, use explicit classes instead of CSS :nth-child
|
|
194
|
+
* selectors. CSS :nth-child doesn't work correctly when rows are wrapped
|
|
195
|
+
* (e.g., in DataGridInfiniteBody).
|
|
196
|
+
*/ const hasExplicitIndex = rowIndex !== undefined;
|
|
197
|
+
const isOdd = hasExplicitIndex && rowIndex % 2 === 0; // 0-based index: 0,2,4 are visually "odd" rows (1st, 3rd, 5th)
|
|
198
|
+
const isEven = hasExplicitIndex && rowIndex % 2 === 1;
|
|
199
|
+
/**
|
|
200
|
+
* Border classes: use explicit border-0 for last row when isLastRow is
|
|
201
|
+
* provided, otherwise fall back to CSS :last-child selector.
|
|
202
|
+
*/ const borderClasses = isLastRow !== undefined ? isLastRow ? "border-b border-b-transparent" // Last row: transparent border to maintain spacing
|
|
203
|
+
: "border-b" : "border-b last:border-0"; // Fallback to CSS :last-child
|
|
204
|
+
return clsx(layoutClass, borderClasses, getBorderClasses({
|
|
191
205
|
mode
|
|
192
206
|
}), {
|
|
193
|
-
|
|
207
|
+
// Explicit odd/even when rowIndex is provided.
|
|
208
|
+
"bg-table-dark-odd": hasExplicitIndex && isOdd && mode === "dark",
|
|
209
|
+
"bg-table-dark-even": hasExplicitIndex && isEven && mode === "dark",
|
|
210
|
+
"bg-table-light-odd": hasExplicitIndex && isOdd && mode === "light",
|
|
211
|
+
"bg-table-light-even": hasExplicitIndex && isEven && mode === "light",
|
|
212
|
+
// System mode with explicit index.
|
|
213
|
+
"bg-table-dark-odd dark:bg-table-light-odd": hasExplicitIndex && isOdd && mode === "system",
|
|
214
|
+
"bg-table-dark-even dark:bg-table-light-even": hasExplicitIndex && isEven && mode === "system",
|
|
215
|
+
// Alt-system mode with explicit index.
|
|
216
|
+
"bg-table-light-odd dark:bg-table-dark-odd": hasExplicitIndex && isOdd && mode === "alt-system",
|
|
217
|
+
"bg-table-light-even dark:bg-table-dark-even": hasExplicitIndex && isEven && mode === "alt-system",
|
|
218
|
+
// CSS :nth-child selectors (original behavior when rowIndex not provided).
|
|
219
|
+
"odd:bg-table-dark-odd even:bg-table-dark-even": !hasExplicitIndex && mode === "dark",
|
|
220
|
+
"odd:bg-table-light-odd even:bg-table-light-even": !hasExplicitIndex && mode === "light",
|
|
221
|
+
"odd:bg-table-dark-odd even:bg-table-dark-even dark:odd:bg-table-light-odd dark:even:bg-table-light-even": !hasExplicitIndex && mode === "system",
|
|
222
|
+
"odd:bg-table-light-odd even:bg-table-light-even dark:odd:bg-table-dark-odd dark:even:bg-table-dark-even": !hasExplicitIndex && mode === "alt-system",
|
|
223
|
+
// Hover effects (same for both modes).
|
|
194
224
|
"hover:bg-table-dark-hover": mode === "dark",
|
|
195
|
-
"odd:bg-table-light-odd even:bg-table-light-even": mode === "light",
|
|
196
225
|
"hover:bg-table-light-hover": mode === "light",
|
|
197
|
-
"odd:bg-table-dark-odd even:bg-table-dark-even dark:odd:bg-table-light-odd dark:even:bg-table-light-even": mode === "system",
|
|
198
226
|
"hover:bg-table-dark-hover dark:hover:bg-table-light-hover": mode === "system",
|
|
199
|
-
"odd:bg-table-light-odd even:bg-table-light-even dark:odd:bg-table-dark-odd dark:even:bg-table-dark-even": mode === "alt-system",
|
|
200
227
|
"hover:bg-table-light-hover dark:hover:bg-table-dark-hover": mode === "alt-system"
|
|
201
228
|
}, className);
|
|
202
229
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versini/ui-datagrid",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"author": "Arno Versini",
|
|
6
6
|
"publishConfig": {
|
|
@@ -94,5 +94,5 @@
|
|
|
94
94
|
"sideEffects": [
|
|
95
95
|
"**/*.css"
|
|
96
96
|
],
|
|
97
|
-
"gitHead": "
|
|
97
|
+
"gitHead": "77685390e91706f56ebacec061f047f85e6e2982"
|
|
98
98
|
}
|