@versini/ui-datagrid 0.1.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/LICENSE +21 -0
- package/README.md +294 -0
- package/dist/DataGrid/DataGrid.d.ts +2 -0
- package/dist/DataGrid/DataGrid.js +132 -0
- package/dist/DataGrid/DataGridContext.d.ts +2 -0
- package/dist/DataGrid/DataGridContext.js +16 -0
- package/dist/DataGrid/index.d.ts +2 -0
- package/dist/DataGrid/index.js +17 -0
- package/dist/DataGrid/utilities.d.ts +36 -0
- package/dist/DataGrid/utilities.js +99 -0
- package/dist/DataGridAnimated/AnimatedWrapper.d.ts +47 -0
- package/dist/DataGridAnimated/AnimatedWrapper.js +49 -0
- package/dist/DataGridAnimated/index.d.ts +4 -0
- package/dist/DataGridAnimated/index.js +17 -0
- package/dist/DataGridAnimated/useAnimatedHeight.d.ts +49 -0
- package/dist/DataGridAnimated/useAnimatedHeight.js +131 -0
- package/dist/DataGridBody/DataGridBody.d.ts +2 -0
- package/dist/DataGridBody/DataGridBody.js +38 -0
- package/dist/DataGridBody/index.d.ts +1 -0
- package/dist/DataGridBody/index.js +13 -0
- package/dist/DataGridCell/DataGridCell.d.ts +14 -0
- package/dist/DataGridCell/DataGridCell.js +77 -0
- package/dist/DataGridCell/index.d.ts +1 -0
- package/dist/DataGridCell/index.js +13 -0
- package/dist/DataGridCellSort/DataGridCellSort.d.ts +2 -0
- package/dist/DataGridCellSort/DataGridCellSort.js +107 -0
- package/dist/DataGridCellSort/index.d.ts +1 -0
- package/dist/DataGridCellSort/index.js +13 -0
- package/dist/DataGridConstants/DataGridConstants.d.ts +37 -0
- package/dist/DataGridConstants/DataGridConstants.js +38 -0
- package/dist/DataGridConstants/index.d.ts +1 -0
- package/dist/DataGridConstants/index.js +13 -0
- package/dist/DataGridFooter/DataGridFooter.d.ts +12 -0
- package/dist/DataGridFooter/DataGridFooter.js +81 -0
- package/dist/DataGridFooter/index.d.ts +1 -0
- package/dist/DataGridFooter/index.js +13 -0
- package/dist/DataGridHeader/DataGridHeader.d.ts +13 -0
- package/dist/DataGridHeader/DataGridHeader.js +80 -0
- package/dist/DataGridHeader/index.d.ts +1 -0
- package/dist/DataGridHeader/index.js +13 -0
- package/dist/DataGridInfinite/InfiniteScrollMarker.d.ts +35 -0
- package/dist/DataGridInfinite/InfiniteScrollMarker.js +53 -0
- package/dist/DataGridInfinite/index.d.ts +4 -0
- package/dist/DataGridInfinite/index.js +17 -0
- package/dist/DataGridInfinite/useInfiniteScroll.d.ts +81 -0
- package/dist/DataGridInfinite/useInfiniteScroll.js +117 -0
- package/dist/DataGridRow/DataGridRow.d.ts +2 -0
- package/dist/DataGridRow/DataGridRow.js +75 -0
- package/dist/DataGridRow/index.d.ts +1 -0
- package/dist/DataGridRow/index.js +13 -0
- package/dist/DataGridSorting/index.d.ts +2 -0
- package/dist/DataGridSorting/index.js +18 -0
- package/dist/DataGridSorting/sortingUtils.d.ts +138 -0
- package/dist/DataGridSorting/sortingUtils.js +234 -0
- package/dist/DataGridVirtual/VirtualDataGrid.d.ts +114 -0
- package/dist/DataGridVirtual/VirtualDataGrid.js +181 -0
- package/dist/DataGridVirtual/index.d.ts +6 -0
- package/dist/DataGridVirtual/index.js +22 -0
- package/dist/DataGridVirtual/useVirtualDataGrid.d.ts +112 -0
- package/dist/DataGridVirtual/useVirtualDataGrid.js +89 -0
- package/package.json +103 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { type SortDirection } from "../DataGridConstants/DataGridConstants";
|
|
2
|
+
/**
|
|
3
|
+
* Gets the opposite sort direction.
|
|
4
|
+
*/
|
|
5
|
+
export declare function getOppositeSortDirection(direction: SortDirection): SortDirection;
|
|
6
|
+
/**
|
|
7
|
+
* Toggles the sort direction or returns default if no current direction.
|
|
8
|
+
*/
|
|
9
|
+
export declare function toggleSortDirection(currentDirection?: SortDirection, defaultDirection?: SortDirection): SortDirection;
|
|
10
|
+
/**
|
|
11
|
+
* Comparator function type for sorting.
|
|
12
|
+
*/
|
|
13
|
+
export type SortComparator<T> = (a: T, b: T) => number;
|
|
14
|
+
/**
|
|
15
|
+
* Creates a sort comparator for dates. Handles Date objects, ISO strings, and
|
|
16
|
+
* timestamps.
|
|
17
|
+
*
|
|
18
|
+
* @param direction - Sort direction (asc or desc)
|
|
19
|
+
* @param nullPosition - Where to place null/undefined values ('start' or 'end')
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const items = [{ date: '2024-01-15' }, { date: '2024-01-10' }];
|
|
24
|
+
* const sorted = items.sort((a, b) =>
|
|
25
|
+
* sortByDate('asc')(a.date, b.date)
|
|
26
|
+
* );
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
*/
|
|
30
|
+
export declare function sortByDate(direction: SortDirection, nullPosition?: "start" | "end"): SortComparator<Date | string | number | null | undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Creates a sort comparator for numbers.
|
|
33
|
+
*
|
|
34
|
+
* @param direction - Sort direction (asc or desc)
|
|
35
|
+
* @param nullPosition - Where to place null/undefined values ('start' or 'end')
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* const items = [{ count: 5 }, { count: 2 }, { count: 10 }];
|
|
40
|
+
* const sorted = items.sort((a, b) =>
|
|
41
|
+
* sortByNumber('desc')(a.count, b.count)
|
|
42
|
+
* );
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
*/
|
|
46
|
+
export declare function sortByNumber(direction: SortDirection, nullPosition?: "start" | "end"): SortComparator<number | null | undefined>;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a sort comparator for strings. Uses localeCompare for proper Unicode
|
|
49
|
+
* sorting.
|
|
50
|
+
*
|
|
51
|
+
* @param direction - Sort direction (asc or desc)
|
|
52
|
+
* @param options - Options for string comparison
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const items = [{ name: 'Zebra' }, { name: 'apple' }];
|
|
57
|
+
* const sorted = items.sort((a, b) =>
|
|
58
|
+
* sortByString('asc', { caseInsensitive: true })(a.name, b.name)
|
|
59
|
+
* );
|
|
60
|
+
* ```
|
|
61
|
+
*
|
|
62
|
+
*/
|
|
63
|
+
export declare function sortByString(direction: SortDirection, options?: {
|
|
64
|
+
nullPosition?: "start" | "end";
|
|
65
|
+
caseInsensitive?: boolean;
|
|
66
|
+
locale?: string;
|
|
67
|
+
}): SortComparator<string | null | undefined>;
|
|
68
|
+
/**
|
|
69
|
+
* Creates a sort comparator for booleans.
|
|
70
|
+
*
|
|
71
|
+
* @param direction - Sort direction (asc = false first, desc = true first)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* const items = [{ active: true }, { active: false }];
|
|
76
|
+
* const sorted = items.sort((a, b) =>
|
|
77
|
+
* sortByBoolean('desc')(a.active, b.active)
|
|
78
|
+
* );
|
|
79
|
+
* // Result: [{ active: true }, { active: false }]
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
export declare function sortByBoolean(direction: SortDirection): SortComparator<boolean | null | undefined>;
|
|
84
|
+
/**
|
|
85
|
+
* Type for sort configuration.
|
|
86
|
+
*/
|
|
87
|
+
export type SortConfig<T extends string = string> = {
|
|
88
|
+
/**
|
|
89
|
+
* The column/field currently being sorted.
|
|
90
|
+
*/
|
|
91
|
+
sortedCell: T;
|
|
92
|
+
/**
|
|
93
|
+
* Current sort direction.
|
|
94
|
+
*/
|
|
95
|
+
sortDirection: SortDirection;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Helper to create a sort handler function. Returns the new sort config when a
|
|
99
|
+
* column header is clicked.
|
|
100
|
+
*
|
|
101
|
+
* @param currentConfig - Current sort configuration
|
|
102
|
+
* @param cellId - ID of the clicked cell
|
|
103
|
+
* @param defaultDirection - Default direction when clicking a new column
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```tsx
|
|
107
|
+
* const [sortConfig, setSortConfig] = useState<SortConfig>({
|
|
108
|
+
* sortedCell: 'date',
|
|
109
|
+
* sortDirection: 'desc',
|
|
110
|
+
* });
|
|
111
|
+
*
|
|
112
|
+
* const handleSort = (cellId: string) => {
|
|
113
|
+
* setSortConfig(getNextSortConfig(sortConfig, cellId));
|
|
114
|
+
* };
|
|
115
|
+
* ```
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
118
|
+
export declare function getNextSortConfig<T extends string = string>(currentConfig: SortConfig<T>, cellId: T, defaultDirection?: SortDirection): SortConfig<T>;
|
|
119
|
+
/**
|
|
120
|
+
* Generic sort function that sorts an array by a specified key.
|
|
121
|
+
*
|
|
122
|
+
* @param items - Array to sort
|
|
123
|
+
* @param key - Key to sort by
|
|
124
|
+
* @param direction - Sort direction
|
|
125
|
+
* @param sortType - Type of sorting ('date', 'number', 'string', 'boolean')
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```ts
|
|
129
|
+
* const items = [
|
|
130
|
+
* { id: 1, name: 'Alice', createdAt: '2024-01-01' },
|
|
131
|
+
* { id: 2, name: 'Bob', createdAt: '2024-01-15' },
|
|
132
|
+
* ];
|
|
133
|
+
*
|
|
134
|
+
* const sorted = sortItems(items, 'createdAt', 'desc', 'date');
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
*/
|
|
138
|
+
export declare function sortItems<T extends Record<string, unknown>>(items: T[], key: keyof T, direction: SortDirection, sortType?: "date" | "number" | "string" | "boolean"): T[];
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-datagrid v0.1.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { SortDirections } from "../DataGridConstants/DataGridConstants.js";
|
|
7
|
+
|
|
8
|
+
;// CONCATENATED MODULE: external "../DataGridConstants/DataGridConstants.js"
|
|
9
|
+
|
|
10
|
+
;// CONCATENATED MODULE: ./src/DataGridSorting/sortingUtils.ts
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Gets the opposite sort direction.
|
|
14
|
+
*/ function getOppositeSortDirection(direction) {
|
|
15
|
+
return direction === SortDirections.ASC ? SortDirections.DESC : SortDirections.ASC;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Toggles the sort direction or returns default if no current direction.
|
|
19
|
+
*/ function toggleSortDirection(currentDirection, defaultDirection = SortDirections.ASC) {
|
|
20
|
+
if (!currentDirection) {
|
|
21
|
+
return defaultDirection;
|
|
22
|
+
}
|
|
23
|
+
return getOppositeSortDirection(currentDirection);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Creates a sort comparator for dates. Handles Date objects, ISO strings, and
|
|
27
|
+
* timestamps.
|
|
28
|
+
*
|
|
29
|
+
* @param direction - Sort direction (asc or desc)
|
|
30
|
+
* @param nullPosition - Where to place null/undefined values ('start' or 'end')
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const items = [{ date: '2024-01-15' }, { date: '2024-01-10' }];
|
|
35
|
+
* const sorted = items.sort((a, b) =>
|
|
36
|
+
* sortByDate('asc')(a.date, b.date)
|
|
37
|
+
* );
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
*/ function sortByDate(direction, nullPosition = "end") {
|
|
41
|
+
return (a, b)=>{
|
|
42
|
+
const aIsNull = a === null || a === undefined;
|
|
43
|
+
const bIsNull = b === null || b === undefined;
|
|
44
|
+
// Handle null values.
|
|
45
|
+
if (aIsNull && bIsNull) {
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
if (aIsNull) {
|
|
49
|
+
return nullPosition === "start" ? -1 : 1;
|
|
50
|
+
}
|
|
51
|
+
if (bIsNull) {
|
|
52
|
+
return nullPosition === "start" ? 1 : -1;
|
|
53
|
+
}
|
|
54
|
+
const aTime = a instanceof Date ? a.getTime() : new Date(a).getTime();
|
|
55
|
+
const bTime = b instanceof Date ? b.getTime() : new Date(b).getTime();
|
|
56
|
+
// Handle invalid dates.
|
|
57
|
+
if (Number.isNaN(aTime) && Number.isNaN(bTime)) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (Number.isNaN(aTime)) {
|
|
61
|
+
return nullPosition === "start" ? -1 : 1;
|
|
62
|
+
}
|
|
63
|
+
if (Number.isNaN(bTime)) {
|
|
64
|
+
return nullPosition === "start" ? 1 : -1;
|
|
65
|
+
}
|
|
66
|
+
return direction === SortDirections.ASC ? aTime - bTime : bTime - aTime;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Creates a sort comparator for numbers.
|
|
71
|
+
*
|
|
72
|
+
* @param direction - Sort direction (asc or desc)
|
|
73
|
+
* @param nullPosition - Where to place null/undefined values ('start' or 'end')
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* const items = [{ count: 5 }, { count: 2 }, { count: 10 }];
|
|
78
|
+
* const sorted = items.sort((a, b) =>
|
|
79
|
+
* sortByNumber('desc')(a.count, b.count)
|
|
80
|
+
* );
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
*/ function sortByNumber(direction, nullPosition = "end") {
|
|
84
|
+
return (a, b)=>{
|
|
85
|
+
const aIsNull = a === null || a === undefined;
|
|
86
|
+
const bIsNull = b === null || b === undefined;
|
|
87
|
+
// Handle null values.
|
|
88
|
+
if (aIsNull && bIsNull) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
if (aIsNull) {
|
|
92
|
+
return nullPosition === "start" ? -1 : 1;
|
|
93
|
+
}
|
|
94
|
+
if (bIsNull) {
|
|
95
|
+
return nullPosition === "start" ? 1 : -1;
|
|
96
|
+
}
|
|
97
|
+
// Handle NaN.
|
|
98
|
+
const aIsNaN = Number.isNaN(a);
|
|
99
|
+
const bIsNaN = Number.isNaN(b);
|
|
100
|
+
if (aIsNaN && bIsNaN) {
|
|
101
|
+
return 0;
|
|
102
|
+
}
|
|
103
|
+
if (aIsNaN) {
|
|
104
|
+
return nullPosition === "start" ? -1 : 1;
|
|
105
|
+
}
|
|
106
|
+
if (bIsNaN) {
|
|
107
|
+
return nullPosition === "start" ? 1 : -1;
|
|
108
|
+
}
|
|
109
|
+
return direction === SortDirections.ASC ? a - b : b - a;
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Creates a sort comparator for strings. Uses localeCompare for proper Unicode
|
|
114
|
+
* sorting.
|
|
115
|
+
*
|
|
116
|
+
* @param direction - Sort direction (asc or desc)
|
|
117
|
+
* @param options - Options for string comparison
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* ```ts
|
|
121
|
+
* const items = [{ name: 'Zebra' }, { name: 'apple' }];
|
|
122
|
+
* const sorted = items.sort((a, b) =>
|
|
123
|
+
* sortByString('asc', { caseInsensitive: true })(a.name, b.name)
|
|
124
|
+
* );
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
*/ function sortByString(direction, options) {
|
|
128
|
+
const { nullPosition = "end", caseInsensitive = true, locale } = options ?? {};
|
|
129
|
+
return (a, b)=>{
|
|
130
|
+
const aIsNull = a === null || a === undefined || a === "";
|
|
131
|
+
const bIsNull = b === null || b === undefined || b === "";
|
|
132
|
+
// Handle null/empty values.
|
|
133
|
+
if (aIsNull && bIsNull) {
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
if (aIsNull) {
|
|
137
|
+
return nullPosition === "start" ? -1 : 1;
|
|
138
|
+
}
|
|
139
|
+
if (bIsNull) {
|
|
140
|
+
return nullPosition === "start" ? 1 : -1;
|
|
141
|
+
}
|
|
142
|
+
const compareOptions = {
|
|
143
|
+
sensitivity: caseInsensitive ? "base" : "variant"
|
|
144
|
+
};
|
|
145
|
+
const comparison = locale ? a.localeCompare(b, locale, compareOptions) : a.localeCompare(b, undefined, compareOptions);
|
|
146
|
+
return direction === SortDirections.ASC ? comparison : -comparison;
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates a sort comparator for booleans.
|
|
151
|
+
*
|
|
152
|
+
* @param direction - Sort direction (asc = false first, desc = true first)
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* ```ts
|
|
156
|
+
* const items = [{ active: true }, { active: false }];
|
|
157
|
+
* const sorted = items.sort((a, b) =>
|
|
158
|
+
* sortByBoolean('desc')(a.active, b.active)
|
|
159
|
+
* );
|
|
160
|
+
* // Result: [{ active: true }, { active: false }]
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
*/ function sortByBoolean(direction) {
|
|
164
|
+
return (a, b)=>{
|
|
165
|
+
// Treat null/undefined as false for comparison.
|
|
166
|
+
const aVal = a === true ? 1 : 0;
|
|
167
|
+
const bVal = b === true ? 1 : 0;
|
|
168
|
+
return direction === SortDirections.ASC ? aVal - bVal : bVal - aVal;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Helper to create a sort handler function. Returns the new sort config when a
|
|
173
|
+
* column header is clicked.
|
|
174
|
+
*
|
|
175
|
+
* @param currentConfig - Current sort configuration
|
|
176
|
+
* @param cellId - ID of the clicked cell
|
|
177
|
+
* @param defaultDirection - Default direction when clicking a new column
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```tsx
|
|
181
|
+
* const [sortConfig, setSortConfig] = useState<SortConfig>({
|
|
182
|
+
* sortedCell: 'date',
|
|
183
|
+
* sortDirection: 'desc',
|
|
184
|
+
* });
|
|
185
|
+
*
|
|
186
|
+
* const handleSort = (cellId: string) => {
|
|
187
|
+
* setSortConfig(getNextSortConfig(sortConfig, cellId));
|
|
188
|
+
* };
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
*/ function getNextSortConfig(currentConfig, cellId, defaultDirection = SortDirections.DESC) {
|
|
192
|
+
if (currentConfig.sortedCell === cellId) {
|
|
193
|
+
// Toggle direction on same column.
|
|
194
|
+
return {
|
|
195
|
+
sortedCell: cellId,
|
|
196
|
+
sortDirection: getOppositeSortDirection(currentConfig.sortDirection)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
// New column, use default direction.
|
|
200
|
+
return {
|
|
201
|
+
sortedCell: cellId,
|
|
202
|
+
sortDirection: defaultDirection
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Generic sort function that sorts an array by a specified key.
|
|
207
|
+
*
|
|
208
|
+
* @param items - Array to sort
|
|
209
|
+
* @param key - Key to sort by
|
|
210
|
+
* @param direction - Sort direction
|
|
211
|
+
* @param sortType - Type of sorting ('date', 'number', 'string', 'boolean')
|
|
212
|
+
*
|
|
213
|
+
* @example
|
|
214
|
+
* ```ts
|
|
215
|
+
* const items = [
|
|
216
|
+
* { id: 1, name: 'Alice', createdAt: '2024-01-01' },
|
|
217
|
+
* { id: 2, name: 'Bob', createdAt: '2024-01-15' },
|
|
218
|
+
* ];
|
|
219
|
+
*
|
|
220
|
+
* const sorted = sortItems(items, 'createdAt', 'desc', 'date');
|
|
221
|
+
* ```
|
|
222
|
+
*
|
|
223
|
+
*/ function sortItems(items, key, direction, sortType = "string") {
|
|
224
|
+
const comparator = sortType === "date" ? sortByDate(direction) : sortType === "number" ? sortByNumber(direction) : sortType === "boolean" ? sortByBoolean(direction) : sortByString(direction);
|
|
225
|
+
return [
|
|
226
|
+
...items
|
|
227
|
+
].sort((a, b)=>{
|
|
228
|
+
const aVal = a[key];
|
|
229
|
+
const bVal = b[key];
|
|
230
|
+
return comparator(aVal, bVal);
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export { getNextSortConfig, getOppositeSortDirection, sortByBoolean, sortByDate, sortByNumber, sortByString, sortItems, toggleSortDirection };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type BlurEffect, type ThemeMode } from "../DataGridConstants/DataGridConstants";
|
|
2
|
+
export type VirtualDataGridColumn<T> = {
|
|
3
|
+
/**
|
|
4
|
+
* Unique identifier for the column.
|
|
5
|
+
*/
|
|
6
|
+
id: string;
|
|
7
|
+
/**
|
|
8
|
+
* Header content.
|
|
9
|
+
*/
|
|
10
|
+
header: React.ReactNode;
|
|
11
|
+
/**
|
|
12
|
+
* Cell renderer function.
|
|
13
|
+
*/
|
|
14
|
+
cell: (item: T, index: number) => React.ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* Column width (CSS value).
|
|
17
|
+
*/
|
|
18
|
+
width?: string | number;
|
|
19
|
+
/**
|
|
20
|
+
* Additional class name for cells in this column.
|
|
21
|
+
*/
|
|
22
|
+
className?: string;
|
|
23
|
+
};
|
|
24
|
+
export type VirtualDataGridProps<T> = {
|
|
25
|
+
/**
|
|
26
|
+
* Array of data items.
|
|
27
|
+
*/
|
|
28
|
+
data: T[];
|
|
29
|
+
/**
|
|
30
|
+
* Column definitions.
|
|
31
|
+
*/
|
|
32
|
+
columns: VirtualDataGridColumn<T>[];
|
|
33
|
+
/**
|
|
34
|
+
* Height of the container (required for virtualization).
|
|
35
|
+
*/
|
|
36
|
+
height: string | number;
|
|
37
|
+
/**
|
|
38
|
+
* Theme mode.
|
|
39
|
+
* @default "system"
|
|
40
|
+
*/
|
|
41
|
+
mode?: ThemeMode;
|
|
42
|
+
/**
|
|
43
|
+
* Use compact row height.
|
|
44
|
+
* @default false
|
|
45
|
+
*/
|
|
46
|
+
compact?: boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Keep header fixed while scrolling.
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
stickyHeader?: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Blur effect for sticky header.
|
|
54
|
+
* @default BlurEffects.NONE
|
|
55
|
+
*/
|
|
56
|
+
blurEffect?: BlurEffect;
|
|
57
|
+
/**
|
|
58
|
+
* Caption text for accessibility.
|
|
59
|
+
*/
|
|
60
|
+
caption?: string;
|
|
61
|
+
/**
|
|
62
|
+
* Additional class name for the wrapper.
|
|
63
|
+
*/
|
|
64
|
+
wrapperClassName?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Additional class name for the table.
|
|
67
|
+
*/
|
|
68
|
+
className?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Function to get a unique key for each row.
|
|
71
|
+
*/
|
|
72
|
+
getRowKey?: (item: T, index: number) => string | number;
|
|
73
|
+
/**
|
|
74
|
+
* Callback when a row is clicked.
|
|
75
|
+
*/
|
|
76
|
+
onRowClick?: (item: T, index: number) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Index of the active row (for highlighting).
|
|
79
|
+
*/
|
|
80
|
+
activeRowIndex?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Estimated row height for virtualization.
|
|
83
|
+
* @default 40
|
|
84
|
+
*/
|
|
85
|
+
estimateSize?: number | ((index: number) => number);
|
|
86
|
+
/**
|
|
87
|
+
* Number of rows to render above/below visible area.
|
|
88
|
+
* @default 5
|
|
89
|
+
*/
|
|
90
|
+
overscan?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Whether to use React's flushSync. Set to false for React 19.
|
|
93
|
+
* @default true
|
|
94
|
+
*/
|
|
95
|
+
useFlushSync?: boolean;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* A virtualized data grid component for rendering large datasets.
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```tsx
|
|
102
|
+
* <VirtualDataGrid
|
|
103
|
+
* data={items}
|
|
104
|
+
* height="500px"
|
|
105
|
+
* columns={[
|
|
106
|
+
* { id: 'name', header: 'Name', cell: (item) => item.name },
|
|
107
|
+
* { id: 'date', header: 'Date', cell: (item) => formatDate(item.date) },
|
|
108
|
+
* ]}
|
|
109
|
+
* getRowKey={(item) => item.id}
|
|
110
|
+
* />
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
*/
|
|
114
|
+
export declare function VirtualDataGrid<T>({ data, columns, height, mode, compact, stickyHeader, blurEffect, caption, wrapperClassName, className, getRowKey, onRowClick, activeRowIndex, estimateSize, overscan, useFlushSync, }: VirtualDataGridProps<T>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-datagrid v0.1.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import clsx from "clsx";
|
|
8
|
+
import { useCallback, useMemo } from "react";
|
|
9
|
+
import { DataGridContext } from "../DataGrid/DataGridContext.js";
|
|
10
|
+
import { getDataGridClasses } from "../DataGrid/utilities.js";
|
|
11
|
+
import { BlurEffects } from "../DataGridConstants/DataGridConstants.js";
|
|
12
|
+
import { getHeaderClasses } from "../DataGridHeader/DataGridHeader.js";
|
|
13
|
+
import { useVirtualDataGrid } from "./useVirtualDataGrid.js";
|
|
14
|
+
|
|
15
|
+
;// CONCATENATED MODULE: external "react/jsx-runtime"
|
|
16
|
+
|
|
17
|
+
;// CONCATENATED MODULE: external "clsx"
|
|
18
|
+
|
|
19
|
+
;// CONCATENATED MODULE: external "react"
|
|
20
|
+
|
|
21
|
+
;// CONCATENATED MODULE: external "../DataGrid/DataGridContext.js"
|
|
22
|
+
|
|
23
|
+
;// CONCATENATED MODULE: external "../DataGrid/utilities.js"
|
|
24
|
+
|
|
25
|
+
;// CONCATENATED MODULE: external "../DataGridConstants/DataGridConstants.js"
|
|
26
|
+
|
|
27
|
+
;// CONCATENATED MODULE: external "../DataGridHeader/DataGridHeader.js"
|
|
28
|
+
|
|
29
|
+
;// CONCATENATED MODULE: external "./useVirtualDataGrid.js"
|
|
30
|
+
|
|
31
|
+
;// CONCATENATED MODULE: ./src/DataGridVirtual/VirtualDataGrid.tsx
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A virtualized data grid component for rendering large datasets.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <VirtualDataGrid
|
|
46
|
+
* data={items}
|
|
47
|
+
* height="500px"
|
|
48
|
+
* columns={[
|
|
49
|
+
* { id: 'name', header: 'Name', cell: (item) => item.name },
|
|
50
|
+
* { id: 'date', header: 'Date', cell: (item) => formatDate(item.date) },
|
|
51
|
+
* ]}
|
|
52
|
+
* getRowKey={(item) => item.id}
|
|
53
|
+
* />
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
*/ function VirtualDataGrid({ data, columns, height, mode = "system", compact = false, stickyHeader = true, blurEffect = BlurEffects.NONE, caption, wrapperClassName, className, getRowKey, onRowClick, activeRowIndex, estimateSize = 40, overscan = 5, useFlushSync = true }) {
|
|
57
|
+
// Memoize the getItemKey function to prevent infinite re-renders.
|
|
58
|
+
const getItemKey = useCallback((index)=>getRowKey ? getRowKey(data[index], index) : index, [
|
|
59
|
+
getRowKey,
|
|
60
|
+
data
|
|
61
|
+
]);
|
|
62
|
+
const { scrollContainerRef, totalSize, virtualItems, measureElement } = useVirtualDataGrid({
|
|
63
|
+
data,
|
|
64
|
+
estimateSize,
|
|
65
|
+
overscan,
|
|
66
|
+
getItemKey,
|
|
67
|
+
useFlushSync
|
|
68
|
+
});
|
|
69
|
+
const classes = useMemo(()=>getDataGridClasses({
|
|
70
|
+
mode,
|
|
71
|
+
className,
|
|
72
|
+
wrapperClassName,
|
|
73
|
+
stickyHeader,
|
|
74
|
+
stickyFooter: false,
|
|
75
|
+
disabled: false
|
|
76
|
+
}), [
|
|
77
|
+
mode,
|
|
78
|
+
className,
|
|
79
|
+
wrapperClassName,
|
|
80
|
+
stickyHeader
|
|
81
|
+
]);
|
|
82
|
+
const headerClasses = useMemo(()=>getHeaderClasses({
|
|
83
|
+
stickyHeader,
|
|
84
|
+
mode,
|
|
85
|
+
blurEffect
|
|
86
|
+
}), [
|
|
87
|
+
stickyHeader,
|
|
88
|
+
mode,
|
|
89
|
+
blurEffect
|
|
90
|
+
]);
|
|
91
|
+
const contextValue = useMemo(()=>({
|
|
92
|
+
mode,
|
|
93
|
+
compact
|
|
94
|
+
}), [
|
|
95
|
+
mode,
|
|
96
|
+
compact
|
|
97
|
+
]);
|
|
98
|
+
const containerStyle = useMemo(()=>({
|
|
99
|
+
height: typeof height === "number" ? `${height}px` : height,
|
|
100
|
+
overflow: "auto"
|
|
101
|
+
}), [
|
|
102
|
+
height
|
|
103
|
+
]);
|
|
104
|
+
/**
|
|
105
|
+
* The tbody needs relative positioning for absolute row children and a height
|
|
106
|
+
* equal to the total virtualized size.
|
|
107
|
+
*/ const tbodyStyle = useMemo(()=>({
|
|
108
|
+
display: "block",
|
|
109
|
+
height: `${totalSize}px`,
|
|
110
|
+
position: "relative"
|
|
111
|
+
}), [
|
|
112
|
+
totalSize
|
|
113
|
+
]);
|
|
114
|
+
// Header row needs to be display: table to maintain column widths.
|
|
115
|
+
const theadStyle = useMemo(()=>({
|
|
116
|
+
display: "table",
|
|
117
|
+
width: "100%",
|
|
118
|
+
tableLayout: "fixed"
|
|
119
|
+
}), []);
|
|
120
|
+
return /*#__PURE__*/ jsx(DataGridContext.Provider, {
|
|
121
|
+
value: contextValue,
|
|
122
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
123
|
+
className: classes.wrapper,
|
|
124
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
125
|
+
ref: scrollContainerRef,
|
|
126
|
+
style: containerStyle,
|
|
127
|
+
children: /*#__PURE__*/ jsxs("table", {
|
|
128
|
+
className: classes.table,
|
|
129
|
+
"aria-label": caption,
|
|
130
|
+
children: [
|
|
131
|
+
caption && /*#__PURE__*/ jsx("caption", {
|
|
132
|
+
className: classes.caption,
|
|
133
|
+
children: caption
|
|
134
|
+
}),
|
|
135
|
+
/*#__PURE__*/ jsx("thead", {
|
|
136
|
+
className: headerClasses,
|
|
137
|
+
style: theadStyle,
|
|
138
|
+
children: /*#__PURE__*/ jsx("tr", {
|
|
139
|
+
children: columns.map((column)=>/*#__PURE__*/ jsx("th", {
|
|
140
|
+
style: {
|
|
141
|
+
width: column.width
|
|
142
|
+
},
|
|
143
|
+
className: clsx("px-4 py-3 font-semibold", column.className),
|
|
144
|
+
children: column.header
|
|
145
|
+
}, column.id))
|
|
146
|
+
})
|
|
147
|
+
}),
|
|
148
|
+
/*#__PURE__*/ jsx("tbody", {
|
|
149
|
+
style: tbodyStyle,
|
|
150
|
+
children: virtualItems.map((virtualRow)=>{
|
|
151
|
+
const isActive = activeRowIndex === virtualRow.index;
|
|
152
|
+
return /*#__PURE__*/ jsx("tr", {
|
|
153
|
+
ref: measureElement,
|
|
154
|
+
"data-index": virtualRow.index,
|
|
155
|
+
onClick: onRowClick ? ()=>onRowClick(virtualRow.data, virtualRow.index) : undefined,
|
|
156
|
+
className: clsx("absolute flex w-full", {
|
|
157
|
+
"cursor-pointer": !!onRowClick,
|
|
158
|
+
"bg-action-dark/10 dark:bg-action-light/10": isActive
|
|
159
|
+
}),
|
|
160
|
+
style: {
|
|
161
|
+
height: `${virtualRow.size}px`,
|
|
162
|
+
transform: `translateY(${virtualRow.start}px)`
|
|
163
|
+
},
|
|
164
|
+
children: columns.map((column)=>/*#__PURE__*/ jsx("td", {
|
|
165
|
+
className: clsx(compact ? "px-2 py-1" : "px-4 py-3", column.className),
|
|
166
|
+
style: {
|
|
167
|
+
width: column.width
|
|
168
|
+
},
|
|
169
|
+
children: column.cell(virtualRow.data, virtualRow.index)
|
|
170
|
+
}, column.id))
|
|
171
|
+
}, virtualRow.key);
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
]
|
|
175
|
+
})
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export { VirtualDataGrid };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { VirtualItem, Virtualizer } from "@tanstack/react-virtual";
|
|
2
|
+
export { useVirtualizer, useWindowVirtualizer } from "@tanstack/react-virtual";
|
|
3
|
+
export type { UseVirtualDataGridOptions, UseVirtualDataGridReturn, } from "./useVirtualDataGrid";
|
|
4
|
+
export { useVirtualDataGrid } from "./useVirtualDataGrid";
|
|
5
|
+
export type { VirtualDataGridColumn, VirtualDataGridProps, } from "./VirtualDataGrid";
|
|
6
|
+
export { VirtualDataGrid } from "./VirtualDataGrid";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
@versini/ui-datagrid v0.1.0
|
|
3
|
+
© 2026 gizmette.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useVirtualizer, useWindowVirtualizer } from "@tanstack/react-virtual";
|
|
7
|
+
import { useVirtualDataGrid } from "./useVirtualDataGrid.js";
|
|
8
|
+
import { VirtualDataGrid } from "./VirtualDataGrid.js";
|
|
9
|
+
|
|
10
|
+
;// CONCATENATED MODULE: external "@tanstack/react-virtual"
|
|
11
|
+
|
|
12
|
+
;// CONCATENATED MODULE: external "./useVirtualDataGrid.js"
|
|
13
|
+
|
|
14
|
+
;// CONCATENATED MODULE: external "./VirtualDataGrid.js"
|
|
15
|
+
|
|
16
|
+
;// CONCATENATED MODULE: ./src/DataGridVirtual/index.ts
|
|
17
|
+
// Re-export from @tanstack/react-virtual for convenience.
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export { VirtualDataGrid, useVirtualDataGrid, useVirtualizer, useWindowVirtualizer };
|