@versini/ui-datagrid 4.2.12 → 4.2.13
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/7.js +5 -14
- package/dist/8.js +1 -5
- package/dist/DataGrid/index.js +32 -93
- package/dist/DataGridAnimated/index.js +22 -116
- package/dist/DataGridBody/index.js +6 -21
- package/dist/DataGridCell/index.js +3 -16
- package/dist/DataGridCellSort/index.js +29 -78
- package/dist/DataGridConstants/index.js +1 -4
- package/dist/DataGridFooter/index.js +12 -60
- package/dist/DataGridHeader/index.js +17 -70
- package/dist/DataGridInfinite/index.js +55 -288
- package/dist/DataGridRow/index.js +20 -67
- package/dist/DataGridSorting/index.js +40 -189
- package/dist/u.js +17 -89
- package/dist/y.js +34 -107
- package/package.json +3 -3
|
@@ -1,224 +1,80 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v4.2.
|
|
2
|
+
@versini/ui-datagrid v4.2.13
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { SortDirections } from "../7.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Gets the opposite sort direction.
|
|
11
|
-
*/ function getOppositeSortDirection(direction) {
|
|
7
|
+
function getOppositeSortDirection(direction) {
|
|
12
8
|
return direction === SortDirections.ASC ? SortDirections.DESC : SortDirections.ASC;
|
|
13
9
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
*/ function toggleSortDirection(currentDirection, defaultDirection = SortDirections.ASC) {
|
|
17
|
-
if (!currentDirection) {
|
|
18
|
-
return defaultDirection;
|
|
19
|
-
}
|
|
10
|
+
function toggleSortDirection(currentDirection, defaultDirection = SortDirections.ASC) {
|
|
11
|
+
if (!currentDirection) return defaultDirection;
|
|
20
12
|
return getOppositeSortDirection(currentDirection);
|
|
21
13
|
}
|
|
22
|
-
|
|
23
|
-
* Creates a sort comparator for dates. Handles Date objects, ISO strings, and
|
|
24
|
-
* timestamps.
|
|
25
|
-
*
|
|
26
|
-
* @param direction - Sort direction (asc or desc)
|
|
27
|
-
* @param nullPosition - Where to place null/undefined values ('start' or 'end')
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```ts
|
|
31
|
-
* const items = [{ date: '2024-01-15' }, { date: '2024-01-10' }];
|
|
32
|
-
* const sorted = items.sort((a, b) =>
|
|
33
|
-
* sortByDate('asc')(a.date, b.date)
|
|
34
|
-
* );
|
|
35
|
-
* ```
|
|
36
|
-
*
|
|
37
|
-
*/ function sortByDate(direction, nullPosition = "end") {
|
|
14
|
+
function sortByDate(direction, nullPosition = "end") {
|
|
38
15
|
return (a, b)=>{
|
|
39
|
-
const aIsNull =
|
|
40
|
-
const bIsNull =
|
|
41
|
-
|
|
42
|
-
if (aIsNull
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
if (aIsNull) {
|
|
46
|
-
return nullPosition === "start" ? -1 : 1;
|
|
47
|
-
}
|
|
48
|
-
if (bIsNull) {
|
|
49
|
-
return nullPosition === "start" ? 1 : -1;
|
|
50
|
-
}
|
|
16
|
+
const aIsNull = null == a;
|
|
17
|
+
const bIsNull = null == b;
|
|
18
|
+
if (aIsNull && bIsNull) return 0;
|
|
19
|
+
if (aIsNull) return "start" === nullPosition ? -1 : 1;
|
|
20
|
+
if (bIsNull) return "start" === nullPosition ? 1 : -1;
|
|
51
21
|
const aTime = a instanceof Date ? a.getTime() : new Date(a).getTime();
|
|
52
22
|
const bTime = b instanceof Date ? b.getTime() : new Date(b).getTime();
|
|
53
|
-
|
|
54
|
-
if (Number.isNaN(aTime)
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
if (Number.isNaN(aTime)) {
|
|
58
|
-
return nullPosition === "start" ? -1 : 1;
|
|
59
|
-
}
|
|
60
|
-
if (Number.isNaN(bTime)) {
|
|
61
|
-
return nullPosition === "start" ? 1 : -1;
|
|
62
|
-
}
|
|
23
|
+
if (Number.isNaN(aTime) && Number.isNaN(bTime)) return 0;
|
|
24
|
+
if (Number.isNaN(aTime)) return "start" === nullPosition ? -1 : 1;
|
|
25
|
+
if (Number.isNaN(bTime)) return "start" === nullPosition ? 1 : -1;
|
|
63
26
|
return direction === SortDirections.ASC ? aTime - bTime : bTime - aTime;
|
|
64
27
|
};
|
|
65
28
|
}
|
|
66
|
-
|
|
67
|
-
* Creates a sort comparator for numbers.
|
|
68
|
-
*
|
|
69
|
-
* @param direction - Sort direction (asc or desc)
|
|
70
|
-
* @param nullPosition - Where to place null/undefined values ('start' or 'end')
|
|
71
|
-
*
|
|
72
|
-
* @example
|
|
73
|
-
* ```ts
|
|
74
|
-
* const items = [{ count: 5 }, { count: 2 }, { count: 10 }];
|
|
75
|
-
* const sorted = items.sort((a, b) =>
|
|
76
|
-
* sortByNumber('desc')(a.count, b.count)
|
|
77
|
-
* );
|
|
78
|
-
* ```
|
|
79
|
-
*
|
|
80
|
-
*/ function sortByNumber(direction, nullPosition = "end") {
|
|
29
|
+
function sortByNumber(direction, nullPosition = "end") {
|
|
81
30
|
return (a, b)=>{
|
|
82
|
-
const aIsNull =
|
|
83
|
-
const bIsNull =
|
|
84
|
-
|
|
85
|
-
if (aIsNull
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
if (aIsNull) {
|
|
89
|
-
return nullPosition === "start" ? -1 : 1;
|
|
90
|
-
}
|
|
91
|
-
if (bIsNull) {
|
|
92
|
-
return nullPosition === "start" ? 1 : -1;
|
|
93
|
-
}
|
|
94
|
-
// Handle NaN.
|
|
31
|
+
const aIsNull = null == a;
|
|
32
|
+
const bIsNull = null == b;
|
|
33
|
+
if (aIsNull && bIsNull) return 0;
|
|
34
|
+
if (aIsNull) return "start" === nullPosition ? -1 : 1;
|
|
35
|
+
if (bIsNull) return "start" === nullPosition ? 1 : -1;
|
|
95
36
|
const aIsNaN = Number.isNaN(a);
|
|
96
37
|
const bIsNaN = Number.isNaN(b);
|
|
97
|
-
if (aIsNaN && bIsNaN)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (aIsNaN) {
|
|
101
|
-
return nullPosition === "start" ? -1 : 1;
|
|
102
|
-
}
|
|
103
|
-
if (bIsNaN) {
|
|
104
|
-
return nullPosition === "start" ? 1 : -1;
|
|
105
|
-
}
|
|
38
|
+
if (aIsNaN && bIsNaN) return 0;
|
|
39
|
+
if (aIsNaN) return "start" === nullPosition ? -1 : 1;
|
|
40
|
+
if (bIsNaN) return "start" === nullPosition ? 1 : -1;
|
|
106
41
|
return direction === SortDirections.ASC ? a - b : b - a;
|
|
107
42
|
};
|
|
108
43
|
}
|
|
109
|
-
|
|
110
|
-
* Creates a sort comparator for strings. Uses localeCompare for proper Unicode
|
|
111
|
-
* sorting.
|
|
112
|
-
*
|
|
113
|
-
* @param direction - Sort direction (asc or desc)
|
|
114
|
-
* @param options - Options for string comparison
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* const items = [{ name: 'Zebra' }, { name: 'apple' }];
|
|
119
|
-
* const sorted = items.sort((a, b) =>
|
|
120
|
-
* sortByString('asc', { caseInsensitive: true })(a.name, b.name)
|
|
121
|
-
* );
|
|
122
|
-
* ```
|
|
123
|
-
*
|
|
124
|
-
*/ function sortByString(direction, options) {
|
|
44
|
+
function sortByString(direction, options) {
|
|
125
45
|
const { nullPosition = "end", caseInsensitive = true, locale } = options ?? {};
|
|
126
46
|
return (a, b)=>{
|
|
127
|
-
const aIsNull =
|
|
128
|
-
const bIsNull =
|
|
129
|
-
|
|
130
|
-
if (aIsNull
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
if (aIsNull) {
|
|
134
|
-
return nullPosition === "start" ? -1 : 1;
|
|
135
|
-
}
|
|
136
|
-
if (bIsNull) {
|
|
137
|
-
return nullPosition === "start" ? 1 : -1;
|
|
138
|
-
}
|
|
47
|
+
const aIsNull = null == a || "" === a;
|
|
48
|
+
const bIsNull = null == b || "" === b;
|
|
49
|
+
if (aIsNull && bIsNull) return 0;
|
|
50
|
+
if (aIsNull) return "start" === nullPosition ? -1 : 1;
|
|
51
|
+
if (bIsNull) return "start" === nullPosition ? 1 : -1;
|
|
139
52
|
const compareOptions = {
|
|
140
53
|
sensitivity: caseInsensitive ? "base" : "variant"
|
|
141
54
|
};
|
|
142
|
-
const comparison = locale ? a.localeCompare(b, locale, compareOptions) : a.localeCompare(b,
|
|
55
|
+
const comparison = locale ? a.localeCompare(b, locale, compareOptions) : a.localeCompare(b, void 0, compareOptions);
|
|
143
56
|
return direction === SortDirections.ASC ? comparison : -comparison;
|
|
144
57
|
};
|
|
145
58
|
}
|
|
146
|
-
|
|
147
|
-
* Creates a sort comparator for booleans.
|
|
148
|
-
*
|
|
149
|
-
* @param direction - Sort direction (asc = false first, desc = true first)
|
|
150
|
-
*
|
|
151
|
-
* @example
|
|
152
|
-
* ```ts
|
|
153
|
-
* const items = [{ active: true }, { active: false }];
|
|
154
|
-
* const sorted = items.sort((a, b) =>
|
|
155
|
-
* sortByBoolean('desc')(a.active, b.active)
|
|
156
|
-
* );
|
|
157
|
-
* // Result: [{ active: true }, { active: false }]
|
|
158
|
-
* ```
|
|
159
|
-
*
|
|
160
|
-
*/ function sortByBoolean(direction) {
|
|
59
|
+
function sortByBoolean(direction) {
|
|
161
60
|
return (a, b)=>{
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
const bVal = b === true ? 1 : 0;
|
|
61
|
+
const aVal = true === a ? 1 : 0;
|
|
62
|
+
const bVal = true === b ? 1 : 0;
|
|
165
63
|
return direction === SortDirections.ASC ? aVal - bVal : bVal - aVal;
|
|
166
64
|
};
|
|
167
65
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
* @param cellId - ID of the clicked cell
|
|
174
|
-
* @param defaultDirection - Default direction when clicking a new column
|
|
175
|
-
*
|
|
176
|
-
* @example
|
|
177
|
-
* ```tsx
|
|
178
|
-
* const [sortConfig, setSortConfig] = useState<SortConfig>({
|
|
179
|
-
* sortedCell: 'date',
|
|
180
|
-
* sortDirection: 'desc',
|
|
181
|
-
* });
|
|
182
|
-
*
|
|
183
|
-
* const handleSort = (cellId: string) => {
|
|
184
|
-
* setSortConfig(getNextSortConfig(sortConfig, cellId));
|
|
185
|
-
* };
|
|
186
|
-
* ```
|
|
187
|
-
*
|
|
188
|
-
*/ function getNextSortConfig(currentConfig, cellId, defaultDirection = SortDirections.DESC) {
|
|
189
|
-
if (currentConfig.sortedCell === cellId) {
|
|
190
|
-
// Toggle direction on same column.
|
|
191
|
-
return {
|
|
192
|
-
sortedCell: cellId,
|
|
193
|
-
sortDirection: getOppositeSortDirection(currentConfig.sortDirection)
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
// New column, use default direction.
|
|
66
|
+
function getNextSortConfig(currentConfig, cellId, defaultDirection = SortDirections.DESC) {
|
|
67
|
+
if (currentConfig.sortedCell === cellId) return {
|
|
68
|
+
sortedCell: cellId,
|
|
69
|
+
sortDirection: getOppositeSortDirection(currentConfig.sortDirection)
|
|
70
|
+
};
|
|
197
71
|
return {
|
|
198
72
|
sortedCell: cellId,
|
|
199
73
|
sortDirection: defaultDirection
|
|
200
74
|
};
|
|
201
75
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
*
|
|
205
|
-
* @param items - Array to sort
|
|
206
|
-
* @param key - Key to sort by
|
|
207
|
-
* @param direction - Sort direction
|
|
208
|
-
* @param sortType - Type of sorting ('date', 'number', 'string', 'boolean')
|
|
209
|
-
*
|
|
210
|
-
* @example
|
|
211
|
-
* ```ts
|
|
212
|
-
* const items = [
|
|
213
|
-
* { id: 1, name: 'Alice', createdAt: '2024-01-01' },
|
|
214
|
-
* { id: 2, name: 'Bob', createdAt: '2024-01-15' },
|
|
215
|
-
* ];
|
|
216
|
-
*
|
|
217
|
-
* const sorted = sortItems(items, 'createdAt', 'desc', 'date');
|
|
218
|
-
* ```
|
|
219
|
-
*
|
|
220
|
-
*/ function sortItems(items, key, direction, sortType = "string") {
|
|
221
|
-
const comparator = sortType === "date" ? sortByDate(direction) : sortType === "number" ? sortByNumber(direction) : sortType === "boolean" ? sortByBoolean(direction) : sortByString(direction);
|
|
76
|
+
function sortItems(items, key, direction, sortType = "string") {
|
|
77
|
+
const comparator = "date" === sortType ? sortByDate(direction) : "number" === sortType ? sortByNumber(direction) : "boolean" === sortType ? sortByBoolean(direction) : sortByString(direction);
|
|
222
78
|
return [
|
|
223
79
|
...items
|
|
224
80
|
].sort((a, b)=>{
|
|
@@ -227,10 +83,5 @@ import { SortDirections } from "../7.js";
|
|
|
227
83
|
return comparator(aVal, bVal);
|
|
228
84
|
});
|
|
229
85
|
}
|
|
230
|
-
|
|
231
|
-
// Re-export sort directions from constants for convenience.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
export { getNextSortConfig, getOppositeSortDirection, sortByBoolean, sortByDate, sortByNumber, sortByString, sortItems, toggleSortDirection };
|
|
236
86
|
export { SortDirections } from "../7.js";
|
|
87
|
+
export { getNextSortConfig, getOppositeSortDirection, sortByBoolean, sortByDate, sortByNumber, sortByString, sortItems, toggleSortDirection };
|
package/dist/u.js
CHANGED
|
@@ -1,88 +1,35 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v4.2.
|
|
2
|
+
@versini/ui-datagrid v4.2.13
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import { useContext, useEffect, useLayoutEffect, useRef } from "react";
|
|
8
8
|
import { DataGridContext } from "./8.js";
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Get the CSS class for a DataGrid body element. Uses display:contents so the
|
|
14
|
-
* body doesn't interfere with the grid flow. Rows will use subgrid when columns
|
|
15
|
-
* are provided, or define their own grid otherwise.
|
|
16
|
-
*
|
|
17
|
-
* @param className - Additional class name to merge
|
|
18
|
-
*
|
|
19
|
-
*/ function getBodyClass(className) {
|
|
9
|
+
function getBodyClass(className) {
|
|
20
10
|
return clsx("contents", className);
|
|
21
11
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Snap a CSS pixel value to the nearest device pixel.
|
|
28
|
-
*
|
|
29
|
-
* `getBoundingClientRect()` can produce sub-pixel widths that fluctuate
|
|
30
|
-
* slightly across renders (font rasterization, fractional transforms, zoom).
|
|
31
|
-
* For sticky header/footer alignment we don't need infinite precision — we need
|
|
32
|
-
* stability.
|
|
33
|
-
*
|
|
34
|
-
* By quantizing to the device pixel grid we reduce "width jitter" that would
|
|
35
|
-
* otherwise cause repeated state updates and re-renders.
|
|
36
|
-
*
|
|
37
|
-
* @internal
|
|
38
|
-
*
|
|
39
|
-
*/ function quantizeWidthToDevicePixels(width, devicePixelRatio = 1) {
|
|
40
|
-
// Guard against invalid DPR values.
|
|
12
|
+
function quantizeWidthToDevicePixels(width, devicePixelRatio = 1) {
|
|
41
13
|
const dpr = Number.isFinite(devicePixelRatio) && devicePixelRatio > 0 ? devicePixelRatio : 1;
|
|
42
|
-
|
|
43
|
-
* NOTE: `window.devicePixelRatio` can be < 1 in some zoom/scaling scenarios.
|
|
44
|
-
* We intentionally accept any DPR > 0 rather than clamping to 1 so that we
|
|
45
|
-
* still snap to the real device pixel grid.
|
|
46
|
-
*/ const quantum = 1 / dpr;
|
|
14
|
+
const quantum = 1 / dpr;
|
|
47
15
|
const snapped = Math.round(width / quantum) * quantum;
|
|
48
|
-
// Clamp floating error (e.g. 33.50000000004).
|
|
49
16
|
return Number(snapped.toFixed(4));
|
|
50
17
|
}
|
|
51
|
-
|
|
52
|
-
* Hook to measure column widths from the first body row's cells. This is needed
|
|
53
|
-
* because sticky header/footer are absolutely positioned and can't use CSS
|
|
54
|
-
* subgrid. We measure the body cells (which ARE in the grid flow) and report
|
|
55
|
-
* the widths so header/footer can use the same pixel values.
|
|
56
|
-
*
|
|
57
|
-
* @param bodyRef - Ref to the body element containing the rows
|
|
58
|
-
* @param contentDependency - Dependency that changes when content changes (children or renderedContent)
|
|
59
|
-
*
|
|
60
|
-
*/ function useColumnMeasurement(bodyRef, contentDependency, noData = false) {
|
|
18
|
+
function useColumnMeasurement(bodyRef, contentDependency, noData = false) {
|
|
61
19
|
const ctx = useContext(DataGridContext);
|
|
62
20
|
const prevWidthsRef = useRef([]);
|
|
63
21
|
const observerRef = useRef(null);
|
|
64
22
|
const needsMeasurement = Boolean(ctx.columns && (ctx.stickyHeader || ctx.stickyFooter));
|
|
65
|
-
|
|
66
|
-
* Re-query the current first row's cells, report any changed widths, and
|
|
67
|
-
* return the cells. Held in a ref so the persistent ResizeObserver always runs
|
|
68
|
-
* the latest logic without being recreated as the window slides.
|
|
69
|
-
*/ const measureRef = useRef(()=>[]);
|
|
23
|
+
const measureRef = useRef(()=>[]);
|
|
70
24
|
measureRef.current = ()=>{
|
|
71
25
|
const element = bodyRef.current;
|
|
72
|
-
if (noData || !element || !needsMeasurement || !ctx.setMeasuredColumnWidths)
|
|
73
|
-
return [];
|
|
74
|
-
}
|
|
26
|
+
if (noData || !element || !needsMeasurement || !ctx.setMeasuredColumnWidths) return [];
|
|
75
27
|
const firstRow = element.querySelector('[role="row"]');
|
|
76
28
|
const cells = firstRow ? Array.from(firstRow.querySelectorAll('[role="cell"], [role="columnheader"], [role="gridcell"]')) : [];
|
|
77
|
-
if (cells.length
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
const dpr = typeof window !== "undefined" ? window.devicePixelRatio ?? 1 : 1;
|
|
29
|
+
if (0 === cells.length) return [];
|
|
30
|
+
const dpr = "u" > typeof window ? window.devicePixelRatio ?? 1 : 1;
|
|
81
31
|
const widths = cells.map((cell)=>quantizeWidthToDevicePixels(cell.getBoundingClientRect().width, dpr));
|
|
82
|
-
|
|
83
|
-
* Only update state if widths have actually changed to prevent infinite
|
|
84
|
-
* loops.
|
|
85
|
-
*/ const prevWidths = prevWidthsRef.current;
|
|
32
|
+
const prevWidths = prevWidthsRef.current;
|
|
86
33
|
const hasChanged = widths.length !== prevWidths.length || widths.some((w, i)=>w !== prevWidths[i]);
|
|
87
34
|
if (hasChanged) {
|
|
88
35
|
prevWidthsRef.current = widths;
|
|
@@ -90,30 +37,15 @@ import { DataGridContext } from "./8.js";
|
|
|
90
37
|
}
|
|
91
38
|
return cells;
|
|
92
39
|
};
|
|
93
|
-
/**
|
|
94
|
-
* Keep a single ResizeObserver for the hook's lifetime and re-point it at the
|
|
95
|
-
* current first row's cells whenever the rendered content changes (e.g. a
|
|
96
|
-
* virtualized window sliding). The cells are the observed targets because the
|
|
97
|
-
* body is `display: contents` (it has no box of its own); re-pointing the same
|
|
98
|
-
* observer avoids tearing one down and allocating a new one on every scroll
|
|
99
|
-
* shift.
|
|
100
|
-
*/ // biome-ignore lint/correctness/useExhaustiveDependencies: measureRef is stable; contentDependency re-points the observer when the rendered rows change
|
|
101
40
|
useLayoutEffect(()=>{
|
|
102
|
-
if (!needsMeasurement)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
if (!observerRef.current) {
|
|
107
|
-
observerRef.current = new ResizeObserver(()=>{
|
|
108
|
-
measureRef.current();
|
|
109
|
-
});
|
|
110
|
-
}
|
|
41
|
+
if (!needsMeasurement) return void observerRef.current?.disconnect();
|
|
42
|
+
if (!observerRef.current) observerRef.current = new ResizeObserver(()=>{
|
|
43
|
+
measureRef.current();
|
|
44
|
+
});
|
|
111
45
|
const observer = observerRef.current;
|
|
112
46
|
observer.disconnect();
|
|
113
47
|
const cells = measureRef.current();
|
|
114
|
-
for (const cell of cells)
|
|
115
|
-
observer.observe(cell);
|
|
116
|
-
}
|
|
48
|
+
for (const cell of cells)observer.observe(cell);
|
|
117
49
|
}, [
|
|
118
50
|
ctx.columns,
|
|
119
51
|
ctx.stickyHeader,
|
|
@@ -123,13 +55,9 @@ import { DataGridContext } from "./8.js";
|
|
|
123
55
|
needsMeasurement,
|
|
124
56
|
noData
|
|
125
57
|
]);
|
|
126
|
-
|
|
127
|
-
useEffect(()=>{
|
|
128
|
-
return ()=>{
|
|
58
|
+
useEffect(()=>()=>{
|
|
129
59
|
observerRef.current?.disconnect();
|
|
130
60
|
observerRef.current = null;
|
|
131
|
-
};
|
|
132
|
-
}, []);
|
|
61
|
+
}, []);
|
|
133
62
|
}
|
|
134
|
-
|
|
135
63
|
export { getBodyClass, useColumnMeasurement };
|
package/dist/y.js
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v4.2.
|
|
2
|
+
@versini/ui-datagrid v4.2.13
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import { BlurEffects, CellWrapper } from "./7.js";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Theme mode helpers - reused across many functions.
|
|
14
|
-
*/ const isDark = (m)=>m === "dark";
|
|
15
|
-
const isLight = (m)=>m === "light";
|
|
16
|
-
const isSys = (m)=>m === "system";
|
|
17
|
-
const isAlt = (m)=>m === "alt-system";
|
|
8
|
+
const isDark = (m)=>"dark" === m;
|
|
9
|
+
const isLight = (m)=>"light" === m;
|
|
10
|
+
const isSys = (m)=>"system" === m;
|
|
11
|
+
const isAlt = (m)=>"alt-system" === m;
|
|
18
12
|
const isDarkish = (m)=>isDark(m) || isSys(m);
|
|
19
13
|
const isLightish = (m)=>isLight(m) || isAlt(m);
|
|
20
14
|
const hasBlurEffect = (b)=>Boolean(b && b !== BlurEffects.NONE);
|
|
21
15
|
const isHeaderCell = (c)=>c === CellWrapper.HEADER;
|
|
22
16
|
const isFooterCell = (c)=>c === CellWrapper.FOOTER;
|
|
23
|
-
|
|
24
|
-
* Text color classes - reused in multiple functions.
|
|
25
|
-
*/ const TXT_LIGHT = "text-copy-light";
|
|
17
|
+
const TXT_LIGHT = "text-copy-light";
|
|
26
18
|
const TXT_DARK = "text-copy-dark";
|
|
27
19
|
const TXT_SYS = "text-copy-light dark:text-copy-dark";
|
|
28
20
|
const TXT_ALT = "text-copy-dark dark:text-copy-light";
|
|
29
|
-
|
|
30
|
-
* Text color classes based on theme mode.
|
|
31
|
-
*/ const getTextColorClasses = ({ mode })=>clsx({
|
|
21
|
+
const getTextColorClasses = ({ mode })=>clsx({
|
|
32
22
|
[TXT_LIGHT]: isDark(mode),
|
|
33
23
|
[TXT_DARK]: isLight(mode),
|
|
34
24
|
[TXT_SYS]: isSys(mode),
|
|
@@ -39,82 +29,41 @@ const getTextColorClassesForHeaderFooter = ({ mode, hasBlur })=>clsx({
|
|
|
39
29
|
[TXT_SYS]: isSys(mode) && hasBlur,
|
|
40
30
|
"text-copy-dark dark:text-copy-dark": (isSys(mode) || isAlt(mode)) && !hasBlur
|
|
41
31
|
});
|
|
42
|
-
|
|
43
|
-
* Surface background classes (main container backgrounds).
|
|
44
|
-
*/ const getSurfaceBackgroundClasses = ({ mode })=>clsx({
|
|
32
|
+
const getSurfaceBackgroundClasses = ({ mode })=>clsx({
|
|
45
33
|
"bg-surface-darker": isDarkish(mode),
|
|
46
34
|
"bg-surface-light": isLightish(mode),
|
|
47
35
|
"dark:bg-surface-light": isSys(mode),
|
|
48
36
|
"dark:bg-surface-darker": isAlt(mode)
|
|
49
37
|
});
|
|
50
|
-
|
|
51
|
-
* Header/footer background classes (with optional blur transparency).
|
|
52
|
-
*/ const getHeaderFooterBackgroundClasses = ({ hasBlur, sticky })=>clsx({
|
|
38
|
+
const getHeaderFooterBackgroundClasses = ({ hasBlur, sticky })=>clsx({
|
|
53
39
|
"bg-surface-medium/50 dark:bg-surface-medium/50": hasBlur && sticky,
|
|
54
40
|
"bg-surface-medium-light dark:bg-surface-medium-light": !hasBlur
|
|
55
41
|
});
|
|
56
|
-
|
|
57
|
-
* Border color classes for rows.
|
|
58
|
-
*/ const getBorderClasses = ({ mode })=>clsx({
|
|
42
|
+
const getBorderClasses = ({ mode })=>clsx({
|
|
59
43
|
"border-table-dark": isDarkish(mode),
|
|
60
44
|
"border-table-light": isLightish(mode),
|
|
61
45
|
"dark:border-table-light": isSys(mode),
|
|
62
46
|
"dark:border-table-dark": isAlt(mode)
|
|
63
47
|
});
|
|
64
|
-
|
|
65
|
-
* Hover classes for body rows.
|
|
66
|
-
*
|
|
67
|
-
* The hover lifts both the background and the text color so the highlighted row
|
|
68
|
-
* reads as a single, cohesive change (mirroring the light-mode behavior where
|
|
69
|
-
* the font color shifts too).
|
|
70
|
-
*
|
|
71
|
-
* The shade is tuned per surface so the lift stays proportional:
|
|
72
|
-
* - Dark surfaces (slate-900 base) only step up to `table-dark-hover`
|
|
73
|
-
* (slate-700) instead of the much brighter `surface-medium` (slate-500),
|
|
74
|
-
* which felt harsh against very dark tables, and brighten the text to
|
|
75
|
-
* `copy-lighter` (white).
|
|
76
|
-
* - Light surfaces (slate-300 base) keep the established `surface-medium`
|
|
77
|
-
* (slate-500) background with `copy-light` (slate-200) text.
|
|
78
|
-
*
|
|
79
|
-
* Pairing matches `getBorderClasses`/`getSurfaceBackgroundClasses` so the hover
|
|
80
|
-
* always tracks the active surface, including the OS-driven `dark:` flips that
|
|
81
|
-
* `system` and `alt-system` perform.
|
|
82
|
-
*
|
|
83
|
-
*/ const getRowHoverClasses = ({ mode })=>clsx(/**
|
|
84
|
-
* Smooth the hover so quick mouse sweeps fade rather than flash. Animating
|
|
85
|
-
* the row's background- and (inherited) text-color is enough; cells stay
|
|
86
|
-
* transparent and follow the row's transitioning color.
|
|
87
|
-
*/ "transition-colors duration-300 ease-in-out motion-reduce:transition-none", {
|
|
48
|
+
const getRowHoverClasses = ({ mode })=>clsx("transition-colors duration-300 ease-in-out motion-reduce:transition-none", {
|
|
88
49
|
"hover:bg-table-dark-hover hover:text-copy-lighter": isDarkish(mode),
|
|
89
50
|
"hover:bg-surface-medium hover:text-copy-light": isLightish(mode),
|
|
90
51
|
"dark:hover:bg-surface-medium dark:hover:text-copy-light": isSys(mode),
|
|
91
52
|
"dark:hover:bg-table-dark-hover dark:hover:text-copy-lighter": isAlt(mode)
|
|
92
53
|
});
|
|
93
|
-
|
|
94
|
-
* Blur effect classes for sticky elements.
|
|
95
|
-
*/ const getBlurClasses = ({ blurEffect })=>clsx({
|
|
54
|
+
const getBlurClasses = ({ blurEffect })=>clsx({
|
|
96
55
|
"backdrop-blur-sm": blurEffect === BlurEffects.SMALL,
|
|
97
56
|
"backdrop-blur-md": blurEffect === BlurEffects.MEDIUM,
|
|
98
57
|
"backdrop-blur-lg": blurEffect === BlurEffects.LARGE
|
|
99
58
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* a fixed dark background and light text so the message stays legible against
|
|
103
|
-
* the blurred content behind it regardless of the grid mode.
|
|
104
|
-
*/ const getLoadingTextClasses = ()=>"px-5 py-2 rounded-full shadow-lg text-sm font-semibold bg-surface-darker text-copy-light";
|
|
105
|
-
/**
|
|
106
|
-
* Overlay classes for loading state. Uses z-30/z-40 to layer above the sticky
|
|
107
|
-
* header/footer (z-20), which sit later in the DOM within the same stacking
|
|
108
|
-
* context and would otherwise paint on top of an equal-z overlay.
|
|
109
|
-
*/ const getOverlayClasses = ()=>({
|
|
59
|
+
const getLoadingTextClasses = ()=>"px-5 py-2 rounded-full shadow-lg text-sm font-semibold bg-surface-darker text-copy-light";
|
|
60
|
+
const getOverlayClasses = ()=>({
|
|
110
61
|
inner: "relative",
|
|
111
62
|
overlay: clsx("absolute inset-0 z-30 cursor-not-allowed rounded-lg", "backdrop-blur-xs bg-white/30 dark:bg-black/30"),
|
|
112
63
|
loadingWrapper: clsx("absolute z-40 top-0 left-0 right-0 h-[min(100%,100vh)]", "flex items-center justify-center", "pointer-events-none"),
|
|
113
64
|
loadingText: getLoadingTextClasses()
|
|
114
65
|
});
|
|
115
|
-
|
|
116
|
-
* Generates classes for the main DataGrid wrapper and grid.
|
|
117
|
-
*/ const getDataGridClasses = ({ mode, className, wrapperClassName, stickyHeader, stickyFooter, loading })=>{
|
|
66
|
+
const getDataGridClasses = ({ mode, className, wrapperClassName, stickyHeader, stickyFooter, loading })=>{
|
|
118
67
|
const overlayClasses = loading ? getOverlayClasses() : null;
|
|
119
68
|
const hasSticky = stickyHeader || stickyFooter;
|
|
120
69
|
return {
|
|
@@ -136,20 +85,16 @@ const getTextColorClassesForHeaderFooter = ({ mode, hasBlur })=>clsx({
|
|
|
136
85
|
}))
|
|
137
86
|
};
|
|
138
87
|
};
|
|
139
|
-
|
|
140
|
-
* Generates classes for the DataGridHeader wrapper.
|
|
141
|
-
*/ const getHeaderClasses = ({ className, stickyHeader, mode, blurEffect })=>{
|
|
88
|
+
const getHeaderClasses = ({ className, stickyHeader, mode, blurEffect })=>{
|
|
142
89
|
const hasBlur = hasBlurEffect(blurEffect);
|
|
143
90
|
const textClasses = getTextColorClassesForHeaderFooter({
|
|
144
91
|
mode,
|
|
145
92
|
hasBlur
|
|
146
93
|
});
|
|
147
|
-
if (!stickyHeader) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}), textClasses, className);
|
|
152
|
-
}
|
|
94
|
+
if (!stickyHeader) return clsx("contents", getHeaderFooterBackgroundClasses({
|
|
95
|
+
hasBlur,
|
|
96
|
+
sticky: false
|
|
97
|
+
}), textClasses, className);
|
|
153
98
|
return clsx("flex flex-col border-b border-table-medium shadow-md", "absolute left-0 right-0 z-20 top-0 rounded-t-lg", getHeaderFooterBackgroundClasses({
|
|
154
99
|
hasBlur,
|
|
155
100
|
sticky: true
|
|
@@ -157,26 +102,20 @@ const getTextColorClassesForHeaderFooter = ({ mode, hasBlur })=>clsx({
|
|
|
157
102
|
blurEffect
|
|
158
103
|
}), className);
|
|
159
104
|
};
|
|
160
|
-
|
|
161
|
-
* Generates classes for the caption element inside DataGridHeader.
|
|
162
|
-
*/ const getCaptionClasses = ({ captionClassName, mode, hasBlur, stickyHeader })=>clsx("py-2 text-sm text-center font-bold col-span-full", getTextColorClassesForHeaderFooter({
|
|
105
|
+
const getCaptionClasses = ({ captionClassName, mode, hasBlur, stickyHeader })=>clsx("py-2 text-sm text-center font-bold col-span-full", getTextColorClassesForHeaderFooter({
|
|
163
106
|
mode,
|
|
164
107
|
hasBlur
|
|
165
108
|
}), !stickyHeader && getHeaderFooterBackgroundClasses({
|
|
166
109
|
hasBlur,
|
|
167
110
|
sticky: false
|
|
168
111
|
}), captionClassName);
|
|
169
|
-
|
|
170
|
-
* Generates classes for the DataGridFooter.
|
|
171
|
-
*/ const getFooterClasses = ({ className, stickyFooter, mode, blurEffect })=>{
|
|
112
|
+
const getFooterClasses = ({ className, stickyFooter, mode, blurEffect })=>{
|
|
172
113
|
const hasBlur = hasBlurEffect(blurEffect);
|
|
173
114
|
const textClasses = getTextColorClassesForHeaderFooter({
|
|
174
115
|
mode,
|
|
175
116
|
hasBlur
|
|
176
117
|
});
|
|
177
|
-
if (!stickyFooter)
|
|
178
|
-
return clsx("contents", textClasses, className);
|
|
179
|
-
}
|
|
118
|
+
if (!stickyFooter) return clsx("contents", textClasses, className);
|
|
180
119
|
return clsx("flex flex-col border-t border-table-medium", "absolute left-0 right-0 z-20 bottom-0 rounded-b-lg", "shadow-[0_-10px_15px_-3px_rgba(0,0,0,0.1)]", getHeaderFooterBackgroundClasses({
|
|
181
120
|
hasBlur,
|
|
182
121
|
sticky: true
|
|
@@ -185,37 +124,30 @@ const getTextColorClassesForHeaderFooter = ({ mode, hasBlur })=>clsx({
|
|
|
185
124
|
}), textClasses, className);
|
|
186
125
|
};
|
|
187
126
|
const ROW_LAYOUT = "group grid items-center";
|
|
188
|
-
|
|
189
|
-
* Generates classes for DataGridRow.
|
|
190
|
-
*/ const getRowClasses = ({ mode, className, cellWrapper, isLastRow, stickyHeader, stickyFooter })=>{
|
|
127
|
+
const getRowClasses = ({ mode, className, cellWrapper, isLastRow, stickyHeader, stickyFooter })=>{
|
|
191
128
|
if (isHeaderCell(cellWrapper) || isFooterCell(cellWrapper)) {
|
|
192
129
|
const isHeader = isHeaderCell(cellWrapper);
|
|
193
130
|
const parentUsesContents = isHeader ? !stickyHeader : !stickyFooter;
|
|
194
|
-
if (parentUsesContents) {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}), isHeader ? "border-b border-table-medium shadow-md" : "border-t border-table-medium", className);
|
|
199
|
-
}
|
|
131
|
+
if (parentUsesContents) return clsx(ROW_LAYOUT, getHeaderFooterBackgroundClasses({
|
|
132
|
+
hasBlur: false,
|
|
133
|
+
sticky: false
|
|
134
|
+
}), isHeader ? "border-b border-table-medium shadow-md" : "border-t border-table-medium", className);
|
|
200
135
|
return clsx(ROW_LAYOUT, className);
|
|
201
136
|
}
|
|
202
|
-
const borderClasses =
|
|
137
|
+
const borderClasses = void 0 !== isLastRow ? isLastRow ? "border-b border-b-transparent dark:border-b-transparent" : "border-b" : "border-b last:border-0";
|
|
203
138
|
return clsx(ROW_LAYOUT, borderClasses, getRowHoverClasses({
|
|
204
139
|
mode
|
|
205
140
|
}), getBorderClasses({
|
|
206
141
|
mode
|
|
207
142
|
}), className);
|
|
208
143
|
};
|
|
209
|
-
|
|
210
|
-
* Generates classes for DataGridCell.
|
|
211
|
-
*/ const getCellClasses = ({ cellWrapper, className, compact, align, mode, borderLeft, borderRight })=>{
|
|
212
|
-
return clsx({
|
|
144
|
+
const getCellClasses = ({ cellWrapper, className, compact, align, mode, borderLeft, borderRight })=>clsx({
|
|
213
145
|
"px-2 py-1": compact,
|
|
214
146
|
"px-4 py-3": !compact
|
|
215
147
|
}, {
|
|
216
|
-
"text-left justify-start":
|
|
217
|
-
"text-center justify-center":
|
|
218
|
-
"text-right justify-end":
|
|
148
|
+
"text-left justify-start": "left" === align || !align,
|
|
149
|
+
"text-center justify-center": "center" === align,
|
|
150
|
+
"text-right justify-end": "right" === align
|
|
219
151
|
}, {
|
|
220
152
|
"font-bold": isHeaderCell(cellWrapper) || isFooterCell(cellWrapper)
|
|
221
153
|
}, "first:group-data-active:relative", "first:group-data-active:before:absolute first:group-data-active:before:left-0 first:group-data-active:before:top-0 first:group-data-active:before:bottom-0 first:group-data-active:before:w-1", "first:group-data-active:self-stretch first:group-data-active:flex first:group-data-active:items-center", {
|
|
@@ -234,10 +166,5 @@ const ROW_LAYOUT = "group grid items-center";
|
|
|
234
166
|
"border-r border-r-table-dark dark:border-r-table-light": borderRight && isSys(mode),
|
|
235
167
|
"border-r border-r-table-light dark:border-r-table-dark": borderRight && isAlt(mode)
|
|
236
168
|
}, className);
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Returns the appropriate ARIA role for the cell based on the cell wrapper
|
|
240
|
-
* type.
|
|
241
|
-
*/ const getCellRole = (cellWrapper)=>isHeaderCell(cellWrapper) ? "columnheader" : "gridcell";
|
|
242
|
-
|
|
169
|
+
const getCellRole = (cellWrapper)=>isHeaderCell(cellWrapper) ? "columnheader" : "gridcell";
|
|
243
170
|
export { getCaptionClasses, getCellClasses, getCellRole, getDataGridClasses, getFooterClasses, getHeaderClasses, getRowClasses, getTextColorClassesForHeaderFooter };
|