@versini/ui-datagrid 4.2.12 → 4.2.14
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 +4 -4
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v4.2.
|
|
2
|
+
@versini/ui-datagrid v4.2.14
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -9,136 +9,38 @@ import { useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, u
|
|
|
9
9
|
import { DataGridContext } from "../8.js";
|
|
10
10
|
import { CellWrapper } from "../7.js";
|
|
11
11
|
import { getBodyClass, useColumnMeasurement } from "../u.js";
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
12
|
const DEFAULT_OVERSCAN = 8;
|
|
24
13
|
const DEFAULT_ESTIMATED_ROW_HEIGHT = 44;
|
|
25
14
|
const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* already centered.
|
|
30
|
-
*/ const JUMP_SETTLE_FALLBACK_MS = 1000;
|
|
31
|
-
/**
|
|
32
|
-
* A smooth jump animates reliably over a short hop, but a long one outruns the
|
|
33
|
-
* animation budget and races react-virtual's mid-flight measurement, landing
|
|
34
|
-
* unpredictably. Past this many rows of reach we jump instantly instead — it
|
|
35
|
-
* lands exactly (no animation to race) at the cost of the glide.
|
|
36
|
-
*/ const FAR_JUMP_THRESHOLD = 30;
|
|
37
|
-
/**
|
|
38
|
-
* A virtualized DataGrid body for large datasets.
|
|
39
|
-
*
|
|
40
|
-
* Only the rows near the viewport stay mounted (windowing via
|
|
41
|
-
* `@tanstack/react-virtual`), so the DOM stays small and scrolling stays smooth
|
|
42
|
-
* at any row count. Rows are rendered in normal grid flow between two full-span
|
|
43
|
-
* spacer elements, so `columns`/subgrid (including intrinsic `auto` tracks)
|
|
44
|
-
* keep working without any consumer change. The virtualizer windows against the
|
|
45
|
-
* DataGrid's own scroll container (created by `maxHeight` or a sticky
|
|
46
|
-
* header/footer), or the page (window scroll) when the grid has neither.
|
|
47
|
-
*
|
|
48
|
-
* Because off-screen rows are removed from the DOM, the grid exposes
|
|
49
|
-
* `aria-rowcount`/`aria-rowindex` and keeps keyboard focus on the grid if a
|
|
50
|
-
* focused row recycles out. Browser find-in-page and uncontrolled per-row state
|
|
51
|
-
* do not persist across recycling (inherent to DOM virtualization).
|
|
52
|
-
*
|
|
53
|
-
* @example
|
|
54
|
-
* ```tsx
|
|
55
|
-
* <DataGrid maxHeight="400px" stickyHeader>
|
|
56
|
-
* <DataGridHeader caption={`Showing ${reached} of ${data.length}`}>
|
|
57
|
-
* <DataGridRow>
|
|
58
|
-
* <DataGridCell>Name</DataGridCell>
|
|
59
|
-
* </DataGridRow>
|
|
60
|
-
* </DataGridHeader>
|
|
61
|
-
*
|
|
62
|
-
* <DataGridInfiniteBody data={largeData} estimatedRowHeight={56}>
|
|
63
|
-
* {(row) => (
|
|
64
|
-
* <DataGridRow key={row.id}>
|
|
65
|
-
* <DataGridCell>{row.name}</DataGridCell>
|
|
66
|
-
* </DataGridRow>
|
|
67
|
-
* )}
|
|
68
|
-
* </DataGridInfiniteBody>
|
|
69
|
-
* </DataGrid>
|
|
70
|
-
* ```
|
|
71
|
-
*
|
|
72
|
-
*/ function DataGridInfiniteBody({ data = [], children: renderRow, overscan = DEFAULT_OVERSCAN, estimatedRowHeight, onVisibleCountChange, className, noData = false, noDataText, ref }) {
|
|
15
|
+
const JUMP_SETTLE_FALLBACK_MS = 1000;
|
|
16
|
+
const FAR_JUMP_THRESHOLD = 30;
|
|
17
|
+
function DataGridInfiniteBody({ data = [], children: renderRow, overscan = DEFAULT_OVERSCAN, estimatedRowHeight, onVisibleCountChange, className, noData = false, noDataText, ref }) {
|
|
73
18
|
const ctx = useContext(DataGridContext);
|
|
74
19
|
const bodyRef = useRef(null);
|
|
75
|
-
|
|
76
|
-
* A zero-height grid item at the list start. The display:contents body has no
|
|
77
|
-
* box, so we measure this real element to find the list's scroll-margin.
|
|
78
|
-
*/ const sentinelRef = useRef(null);
|
|
20
|
+
const sentinelRef = useRef(null);
|
|
79
21
|
const totalItems = data.length;
|
|
80
22
|
const headerRows = ctx.headerRows ?? 0;
|
|
81
23
|
const footerRows = ctx.footerRows ?? 0;
|
|
82
|
-
|
|
83
|
-
* Default the row-height estimate from the grid's density when the consumer
|
|
84
|
-
* doesn't set it: `compact` rows are much shorter (~29px vs ~44px), and an
|
|
85
|
-
* estimate far from the real height hurts scrollbar accuracy and
|
|
86
|
-
* scrollToIndex.
|
|
87
|
-
*/ const resolvedEstimatedRowHeight = estimatedRowHeight ?? (ctx.compact ? DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT : DEFAULT_ESTIMATED_ROW_HEIGHT);
|
|
24
|
+
const resolvedEstimatedRowHeight = estimatedRowHeight ?? (ctx.compact ? DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT : DEFAULT_ESTIMATED_ROW_HEIGHT);
|
|
88
25
|
const [scrollMargin, setScrollMargin] = useState(0);
|
|
89
|
-
|
|
90
|
-
* Widen-window jump-to-row state. A default (`"smooth"`) `scrollToIndex` first
|
|
91
|
-
* widens the virtualized window (`jump.overscan`) so every row between the
|
|
92
|
-
* current view and the target is mounted with real, laid-out heights, then the
|
|
93
|
-
* layout effect scrolls to the real target element and restores the window
|
|
94
|
-
* once the scroll settles.
|
|
95
|
-
*
|
|
96
|
-
* `instant` is set for a far jump (reach past `FAR_JUMP_THRESHOLD`); the
|
|
97
|
-
* effect then jumps instantly rather than animating (it also forces instant
|
|
98
|
-
* for a `prefers-reduced-motion` user, but that is read live in the effect,
|
|
99
|
-
* not stored here). Scrolling over real rows always lands exactly; the
|
|
100
|
-
* animation is what raced react-virtual's mid-flight measurement over a long
|
|
101
|
-
* distance, so far jumps drop it. A direct jump (`behavior: "instant"`) skips
|
|
102
|
-
* this state entirely and delegates to the virtualizer (see `scrollToIndex`
|
|
103
|
-
* below).
|
|
104
|
-
*
|
|
105
|
-
* `seq` makes every request a fresh object: two jumps that happen to compute
|
|
106
|
-
* the same `overscan` still re-run the jump effect (a plain number would be a
|
|
107
|
-
* no-op setState and the second jump would be dropped). `handledSeqRef`
|
|
108
|
-
* records the last seq the effect acted on, so it scrolls once per request.
|
|
109
|
-
*
|
|
110
|
-
*/ const [jump, setJump] = useState(null);
|
|
26
|
+
const [jump, setJump] = useState(null);
|
|
111
27
|
const jumpSeqRef = useRef(0);
|
|
112
28
|
const handledSeqRef = useRef(-1);
|
|
113
|
-
|
|
114
|
-
* The scroll target is provided by the parent DataGrid: its internal bounded
|
|
115
|
-
* scroller (sticky / maxHeight), or the page (window scroll) when it owns
|
|
116
|
-
* none. This avoids fragile DOM-walking that can mistake an unbounded
|
|
117
|
-
* `overflow-x:auto` wrapper for a real vertical scroller.
|
|
118
|
-
*/ const useWindow = ctx.pageScroll === true;
|
|
29
|
+
const useWindow = true === ctx.pageScroll;
|
|
119
30
|
const scrollEl = useWindow ? null : ctx.scrollContainer ?? null;
|
|
120
|
-
const resolved = useWindow ||
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
* inline-header height / page offset). Keeps the window origin correct.
|
|
124
|
-
*/ useLayoutEffect(()=>{
|
|
125
|
-
if (!resolved || noData) {
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
31
|
+
const resolved = useWindow || null !== scrollEl;
|
|
32
|
+
useLayoutEffect(()=>{
|
|
33
|
+
if (!resolved || noData) return;
|
|
128
34
|
const compute = ()=>{
|
|
129
35
|
const node = sentinelRef.current;
|
|
130
|
-
if (!node)
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
36
|
+
if (!node) return;
|
|
133
37
|
const top = node.getBoundingClientRect().top;
|
|
134
38
|
const next = useWindow ? Math.max(0, Math.round(top + window.scrollY)) : Math.max(0, Math.round(top - scrollEl.getBoundingClientRect().top + scrollEl.scrollTop));
|
|
135
39
|
setScrollMargin((prev)=>prev === next ? prev : next);
|
|
136
40
|
};
|
|
137
41
|
compute();
|
|
138
42
|
const target = useWindow ? document.documentElement : scrollEl;
|
|
139
|
-
if (!target)
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
43
|
+
if (!target) return;
|
|
142
44
|
const observer = new ResizeObserver(compute);
|
|
143
45
|
observer.observe(target);
|
|
144
46
|
return ()=>observer.disconnect();
|
|
@@ -148,18 +50,8 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
148
50
|
scrollEl,
|
|
149
51
|
noData
|
|
150
52
|
]);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
* (`scrollMargin`). A smooth jump centers via native `scrollIntoView`, which
|
|
154
|
-
* otherwise centers in the full box and lands the row half a header-height too
|
|
155
|
-
* high under a sticky header; the inset shrinks the centering region to the
|
|
156
|
-
* visible area. Declared here (not mutated per-jump) so it is not call-order
|
|
157
|
-
* dependent, and the previous inline value is restored on cleanup so the grid
|
|
158
|
-
* never permanently overrides a consumer's own `scroll-padding-top`.
|
|
159
|
-
*/ useLayoutEffect(()=>{
|
|
160
|
-
if (!scrollEl) {
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
53
|
+
useLayoutEffect(()=>{
|
|
54
|
+
if (!scrollEl) return;
|
|
163
55
|
const previous = scrollEl.style.scrollPaddingTop;
|
|
164
56
|
scrollEl.style.scrollPaddingTop = `${scrollMargin}px`;
|
|
165
57
|
return ()=>{
|
|
@@ -171,112 +63,47 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
171
63
|
]);
|
|
172
64
|
const virtualizer = useVirtualizer({
|
|
173
65
|
count: totalItems,
|
|
174
|
-
|
|
175
|
-
* In window mode the scroll element IS `window` (the window observers read
|
|
176
|
-
* window.innerHeight / window.scrollY off it); cast bridges the type since
|
|
177
|
-
* useVirtualizer's element constraint excludes Window.
|
|
178
|
-
*/ getScrollElement: ()=>useWindow ? typeof window !== "undefined" ? window : null : scrollEl,
|
|
66
|
+
getScrollElement: ()=>useWindow ? "u" > typeof window ? window : null : scrollEl,
|
|
179
67
|
estimateSize: ()=>resolvedEstimatedRowHeight,
|
|
180
|
-
|
|
181
|
-
* While a jump is in flight, `jump.overscan` widens the window to mount the
|
|
182
|
-
* rows along the path; otherwise the consumer's (or default) overscan.
|
|
183
|
-
*/ overscan: jump?.overscan ?? overscan,
|
|
68
|
+
overscan: jump?.overscan ?? overscan,
|
|
184
69
|
scrollMargin,
|
|
185
|
-
|
|
186
|
-
* Direct jumps center via the virtualizer (`scrollToIndex` with align
|
|
187
|
-
* "center"). Like the sticky-header inset on the smooth path, the row must
|
|
188
|
-
* center in the visible area below the header, not the full box.
|
|
189
|
-
* react-virtual shifts the target offset by `scrollPaddingStart` and then
|
|
190
|
-
* centers, so half the header height lands the row at the visible center.
|
|
191
|
-
* (The smooth path uses CSS `scroll-padding-top`, which shrinks the centering
|
|
192
|
-
* box instead, so that one takes the full header height — hence the /2
|
|
193
|
-
* asymmetry.)
|
|
194
|
-
*/ scrollPaddingStart: scrollMargin / 2,
|
|
195
|
-
// React 19: let React batch the scroll-driven updates natively.
|
|
70
|
+
scrollPaddingStart: scrollMargin / 2,
|
|
196
71
|
useFlushSync: false,
|
|
197
|
-
|
|
198
|
-
* Window vs element observers are selected to match getScrollElement; the
|
|
199
|
-
* casts bridge the Element|Window union react-virtual can't narrow here.
|
|
200
|
-
*/ observeElementRect: useWindow ? observeWindowRect : observeElementRect,
|
|
72
|
+
observeElementRect: useWindow ? observeWindowRect : observeElementRect,
|
|
201
73
|
observeElementOffset: useWindow ? observeWindowOffset : observeElementOffset,
|
|
202
74
|
scrollToFn: useWindow ? windowScroll : elementScroll,
|
|
203
|
-
|
|
204
|
-
* Window mode: seed the offset from the live scroll position (mirrors
|
|
205
|
-
* useWindowVirtualizer). Without this, on mount the virtualizer's offset is
|
|
206
|
-
* 0, so it scrolls an already-scrolled page to the top and computes the wrong
|
|
207
|
-
* initial window.
|
|
208
|
-
*/ initialOffset: useWindow ? ()=>typeof window !== "undefined" ? window.scrollY : 0 : undefined
|
|
75
|
+
initialOffset: useWindow ? ()=>"u" > typeof window ? window.scrollY : 0 : void 0
|
|
209
76
|
});
|
|
210
77
|
const virtualItems = virtualizer.getVirtualItems();
|
|
211
78
|
const totalSize = virtualizer.getTotalSize();
|
|
212
79
|
const lastVisibleIndex = virtualItems.length > 0 ? virtualItems[virtualItems.length - 1].index : -1;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
* actually the scroller (only the first screen renders), or an internal
|
|
217
|
-
* scroller that isn't height-bounded (every row mounts). Runs after paint so
|
|
218
|
-
* the measurements are real; stripped from production builds.
|
|
219
|
-
*/ useEffect(()=>{
|
|
220
|
-
/**
|
|
221
|
-
* Treat a missing `process` (a non-Node runtime whose bundler didn't replace
|
|
222
|
-
* this expression) as production, so reading NODE_ENV can never throw.
|
|
223
|
-
*/ const isProduction = typeof process === "undefined" || process.env.NODE_ENV === "production";
|
|
224
|
-
if (isProduction || noData || totalItems === 0 || typeof window === "undefined") {
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
80
|
+
useEffect(()=>{
|
|
81
|
+
const isProduction = "u" < typeof process || "production" === process.env.NODE_ENV;
|
|
82
|
+
if (isProduction || noData || 0 === totalItems || "u" < typeof window) return;
|
|
227
83
|
if (useWindow) {
|
|
228
84
|
let el = bodyRef.current?.parentElement ?? null;
|
|
229
85
|
while(el && el !== document.body && el !== document.documentElement){
|
|
230
86
|
const { overflowY } = getComputedStyle(el);
|
|
231
|
-
if ((
|
|
232
|
-
console.warn("[DataGridInfiniteBody] Virtualizing against the page (window scroll), but
|
|
87
|
+
if (("auto" === overflowY || "scroll" === overflowY) && el.scrollHeight > el.clientHeight) {
|
|
88
|
+
console.warn("[DataGridInfiniteBody] Virtualizing against the page (window scroll), but the grid is inside a scrollable container, so only the first screen of rows will render. Set `maxHeight` on the DataGrid to scroll inside it.");
|
|
233
89
|
break;
|
|
234
90
|
}
|
|
235
91
|
el = el.parentElement;
|
|
236
92
|
}
|
|
237
|
-
} else if (scrollEl && scrollEl.clientHeight > window.innerHeight && scrollEl.scrollHeight <= scrollEl.clientHeight + 1)
|
|
238
|
-
console.warn("[DataGridInfiniteBody] The scroll container is not height-bounded, so every " + "row mounts and virtualization has no effect. Set `maxHeight` on the DataGrid.");
|
|
239
|
-
}
|
|
93
|
+
} else if (scrollEl && scrollEl.clientHeight > window.innerHeight && scrollEl.scrollHeight <= scrollEl.clientHeight + 1) console.warn("[DataGridInfiniteBody] The scroll container is not height-bounded, so every row mounts and virtualization has no effect. Set `maxHeight` on the DataGrid.");
|
|
240
94
|
}, [
|
|
241
95
|
useWindow,
|
|
242
96
|
scrollEl,
|
|
243
97
|
noData,
|
|
244
98
|
totalItems
|
|
245
99
|
]);
|
|
246
|
-
|
|
247
|
-
* Expose scrollToIndex via ref. Works for any index, mounted or not.
|
|
248
|
-
*
|
|
249
|
-
* A direct jump (`behavior: "instant"`/`"auto"`) delegates straight to the
|
|
250
|
-
* virtualizer: it scrolls to the target's estimated offset and mounts only the
|
|
251
|
-
* window around it — lightweight, but the landing is only as accurate as
|
|
252
|
-
* `estimatedRowHeight` (the rows above stay estimated).
|
|
253
|
-
*
|
|
254
|
-
* A default (`"smooth"`) jump instead widens the window to mount every row
|
|
255
|
-
* from the current view to the target with real heights, so it lands exactly.
|
|
256
|
-
* The layout effect then animates a near jump, or — past `FAR_JUMP_THRESHOLD`
|
|
257
|
-
* — jumps instantly (a long animation races the virtualizer's mid-flight
|
|
258
|
-
* measurement and strands the jump short).
|
|
259
|
-
*
|
|
260
|
-
*/ useImperativeHandle(ref, ()=>({
|
|
100
|
+
useImperativeHandle(ref, ()=>({
|
|
261
101
|
scrollToIndex: (index, options)=>{
|
|
262
|
-
if (index < 0 || index >= totalItems) {
|
|
263
|
-
console.warn(`scrollToIndex: index ${index} is out of bounds${totalItems === 0 ? " (the grid is empty)" : ` (0-${totalItems - 1})`}`);
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
102
|
+
if (index < 0 || index >= totalItems) return void console.warn(`scrollToIndex: index ${index} is out of bounds${0 === totalItems ? " (the grid is empty)" : ` (0-${totalItems - 1})`}`);
|
|
266
103
|
const behavior = options?.behavior ?? "smooth";
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
* large) path window doesn't stay mounted until its own settle. Safe
|
|
271
|
-
* unconditionally: `setJump(null)` no-ops when there is no pending jump.
|
|
272
|
-
*/ setJump(null);
|
|
273
|
-
/**
|
|
274
|
-
* Direct jump: delegate to the virtualizer with an explicit "instant".
|
|
275
|
-
* Forwarding "auto" instead would defer to the scroller's CSS
|
|
276
|
-
* `scroll-behavior` — under a global `scroll-behavior: smooth` that
|
|
277
|
-
* animates through the un-widened window (the very stutter the smooth path
|
|
278
|
-
* exists to avoid), breaking the documented "jumps directly" contract.
|
|
279
|
-
*/ virtualizer.scrollToIndex(index, {
|
|
104
|
+
if ("smooth" !== behavior) {
|
|
105
|
+
setJump(null);
|
|
106
|
+
virtualizer.scrollToIndex(index, {
|
|
280
107
|
align: "center",
|
|
281
108
|
behavior: "instant"
|
|
282
109
|
});
|
|
@@ -285,14 +112,7 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
285
112
|
const items = virtualizer.getVirtualItems();
|
|
286
113
|
const first = items.length > 0 ? items[0].index : 0;
|
|
287
114
|
const last = items.length > 0 ? items[items.length - 1].index : 0;
|
|
288
|
-
|
|
289
|
-
* Overscan is symmetric, so widen by the larger gap to either edge of the
|
|
290
|
-
* current window; that guarantees the target (and the rows between) mount.
|
|
291
|
-
* The jump effect scrolls once they render, then restores the normal
|
|
292
|
-
* window. A jump reaching past `FAR_JUMP_THRESHOLD` lands instantly (a long
|
|
293
|
-
* smooth animation races the virtualizer's mid-flight measurement and
|
|
294
|
-
* strands the jump short).
|
|
295
|
-
*/ const reach = Math.max(index - first, last - index, 0);
|
|
115
|
+
const reach = Math.max(index - first, last - index, 0);
|
|
296
116
|
jumpSeqRef.current += 1;
|
|
297
117
|
setJump({
|
|
298
118
|
index,
|
|
@@ -306,46 +126,18 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
306
126
|
virtualizer,
|
|
307
127
|
overscan
|
|
308
128
|
]);
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
* (react-virtual recomputes the window synchronously, so it is mounted in the
|
|
312
|
-
* commit that follows the `jump` change), then restore the normal window.
|
|
313
|
-
*
|
|
314
|
-
* Two flavors:
|
|
315
|
-
* - **Instant** (far jump or reduced motion): scroll instantly, measure the
|
|
316
|
-
* path rows so the window-collapse keeps their real heights (not the
|
|
317
|
-
* estimate, which would shift the target), then collapse — all synchronously.
|
|
318
|
-
* No animation means nothing races the measurement, so it lands exactly.
|
|
319
|
-
* - **Smooth** (near jump): animate, and collapse once the scroll settles
|
|
320
|
-
* (`scrollend`, or the timeout fallback for the no-op / unsupported case). A
|
|
321
|
-
* short hop finishes before measurement can interfere, so it stays exact.
|
|
322
|
-
*
|
|
323
|
-
*/ useLayoutEffect(()=>{
|
|
324
|
-
if (jump === null || handledSeqRef.current === jump.seq) {
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
129
|
+
useLayoutEffect(()=>{
|
|
130
|
+
if (null === jump || handledSeqRef.current === jump.seq) return;
|
|
327
131
|
handledSeqRef.current = jump.seq;
|
|
328
132
|
const rowEl = bodyRef.current?.querySelector(`[data-index="${jump.index}"]`);
|
|
329
|
-
if (!rowEl)
|
|
330
|
-
|
|
331
|
-
* The widened window should always mount the target; if it somehow did not,
|
|
332
|
-
* don't stay stuck in widened mode — restore the normal window so later
|
|
333
|
-
* jumps aren't dropped.
|
|
334
|
-
*/ setJump(null);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
const reduceMotion = typeof window !== "undefined" && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
133
|
+
if (!rowEl) return void setJump(null);
|
|
134
|
+
const reduceMotion = "u" > typeof window && !!window.matchMedia?.("(prefers-reduced-motion: reduce)").matches;
|
|
338
135
|
if (jump.instant || reduceMotion) {
|
|
339
136
|
rowEl.scrollIntoView({
|
|
340
137
|
behavior: "instant",
|
|
341
138
|
block: "center"
|
|
342
139
|
});
|
|
343
|
-
|
|
344
|
-
* Cache the path rows' real heights before the window collapses, so the rows
|
|
345
|
-
* above the target don't fall back to `estimatedRowHeight` and shift it.
|
|
346
|
-
* Safe to do synchronously here: the instant scroll has already landed, so
|
|
347
|
-
* there is no in-flight animation for the measurement to disturb.
|
|
348
|
-
*/ bodyRef.current?.querySelectorAll("[data-index]").forEach((el)=>virtualizer.measureElement(el));
|
|
140
|
+
bodyRef.current?.querySelectorAll("[data-index]").forEach((el)=>virtualizer.measureElement(el));
|
|
349
141
|
setJump(null);
|
|
350
142
|
return;
|
|
351
143
|
}
|
|
@@ -353,7 +145,7 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
353
145
|
behavior: "smooth",
|
|
354
146
|
block: "center"
|
|
355
147
|
});
|
|
356
|
-
const scroller = useWindow ? typeof window
|
|
148
|
+
const scroller = useWindow ? "u" > typeof window ? window : null : scrollEl;
|
|
357
149
|
const settle = ()=>setJump(null);
|
|
358
150
|
scroller?.addEventListener("scrollend", settle, {
|
|
359
151
|
once: true
|
|
@@ -369,10 +161,7 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
369
161
|
scrollEl,
|
|
370
162
|
virtualizer
|
|
371
163
|
]);
|
|
372
|
-
|
|
373
|
-
* Report a monotonic high-water mark via onVisibleCountChange: the furthest
|
|
374
|
-
* row index reached + 1 (resets when the dataset length changes).
|
|
375
|
-
*/ const highWaterRef = useRef(0);
|
|
164
|
+
const highWaterRef = useRef(0);
|
|
376
165
|
const prevTotalRef = useRef(totalItems);
|
|
377
166
|
useEffect(()=>{
|
|
378
167
|
if (prevTotalRef.current !== totalItems) {
|
|
@@ -389,17 +178,10 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
389
178
|
totalItems,
|
|
390
179
|
onVisibleCountChange
|
|
391
180
|
]);
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
* footer rows), and clear it when this body unmounts. Empty/noData grids
|
|
395
|
-
* report nothing, so they render no aria-rowcount and no focus sentinel.
|
|
396
|
-
*/ useEffect(()=>{
|
|
397
|
-
if (noData || totalItems === 0) {
|
|
398
|
-
ctx.setRowCount?.(undefined);
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
181
|
+
useEffect(()=>{
|
|
182
|
+
if (noData || 0 === totalItems) return void ctx.setRowCount?.(void 0);
|
|
401
183
|
ctx.setRowCount?.(totalItems + headerRows + footerRows);
|
|
402
|
-
return ()=>ctx.setRowCount?.(
|
|
184
|
+
return ()=>ctx.setRowCount?.(void 0);
|
|
403
185
|
}, [
|
|
404
186
|
noData,
|
|
405
187
|
totalItems,
|
|
@@ -407,11 +189,7 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
407
189
|
footerRows,
|
|
408
190
|
ctx.setRowCount
|
|
409
191
|
]);
|
|
410
|
-
|
|
411
|
-
* Focus preservation: when a focused per-row control recycles out of the DOM,
|
|
412
|
-
* focus would fall to document.body. Track the focused row index; if it
|
|
413
|
-
* unmounts and focus drops to the body, move focus to the grid sentinel.
|
|
414
|
-
*/ const focusedIndexRef = useRef(null);
|
|
192
|
+
const focusedIndexRef = useRef(null);
|
|
415
193
|
useLayoutEffect(()=>{
|
|
416
194
|
const body = bodyRef.current;
|
|
417
195
|
const active = document.activeElement;
|
|
@@ -420,42 +198,34 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
420
198
|
focusedIndexRef.current = rowEl ? Number(rowEl.getAttribute("data-index")) : null;
|
|
421
199
|
return;
|
|
422
200
|
}
|
|
423
|
-
if (focusedIndexRef.current
|
|
424
|
-
ctx.focusGrid?.();
|
|
425
|
-
}
|
|
201
|
+
if (null !== focusedIndexRef.current && (active === document.body || null === active) && body && !body.querySelector(`[data-index="${focusedIndexRef.current}"]`)) ctx.focusGrid?.();
|
|
426
202
|
focusedIndexRef.current = null;
|
|
427
203
|
});
|
|
428
|
-
|
|
429
|
-
* Context value for body rows (shared base; per-row fields added below).
|
|
430
|
-
*/ const bodyContextBase = useMemo(()=>({
|
|
204
|
+
const bodyContextBase = useMemo(()=>({
|
|
431
205
|
...ctx,
|
|
432
206
|
cellWrapper: CellWrapper.BODY
|
|
433
207
|
}), [
|
|
434
208
|
ctx
|
|
435
209
|
]);
|
|
436
|
-
|
|
437
|
-
* Measure column widths for sticky header/footer sync. Re-key on the first
|
|
438
|
-
* mounted row so the sticky header re-syncs as the window (and the auto-column
|
|
439
|
-
* width) slides.
|
|
440
|
-
*/ useColumnMeasurement(bodyRef, virtualItems[0]?.index, noData);
|
|
210
|
+
useColumnMeasurement(bodyRef, virtualItems[0]?.index, noData);
|
|
441
211
|
const bodyClass = getBodyClass(className);
|
|
442
212
|
const paddingTop = virtualItems.length > 0 ? Math.max(0, virtualItems[0].start - scrollMargin) : 0;
|
|
443
213
|
const paddingBottom = virtualItems.length > 0 ? Math.max(0, totalSize - virtualItems[virtualItems.length - 1].end) : 0;
|
|
444
|
-
return
|
|
214
|
+
return jsx(DataGridContext.Provider, {
|
|
445
215
|
value: {
|
|
446
216
|
...ctx,
|
|
447
217
|
cellWrapper: CellWrapper.BODY
|
|
448
218
|
},
|
|
449
|
-
children:
|
|
219
|
+
children: jsx("div", {
|
|
450
220
|
ref: bodyRef,
|
|
451
221
|
role: "rowgroup",
|
|
452
222
|
className: bodyClass,
|
|
453
|
-
children: noData ?
|
|
223
|
+
children: noData ? jsx("div", {
|
|
454
224
|
role: "row",
|
|
455
225
|
style: {
|
|
456
226
|
gridColumn: "1 / -1"
|
|
457
227
|
},
|
|
458
|
-
children:
|
|
228
|
+
children: jsx("div", {
|
|
459
229
|
role: "gridcell",
|
|
460
230
|
style: {
|
|
461
231
|
textAlign: "center",
|
|
@@ -463,9 +233,9 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
463
233
|
},
|
|
464
234
|
children: noDataText ?? "No Data"
|
|
465
235
|
})
|
|
466
|
-
}) :
|
|
236
|
+
}) : jsxs(Fragment, {
|
|
467
237
|
children: [
|
|
468
|
-
|
|
238
|
+
jsx("div", {
|
|
469
239
|
ref: sentinelRef,
|
|
470
240
|
"aria-hidden": "true",
|
|
471
241
|
style: {
|
|
@@ -473,14 +243,14 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
473
243
|
height: 0
|
|
474
244
|
}
|
|
475
245
|
}),
|
|
476
|
-
paddingTop > 0 &&
|
|
246
|
+
paddingTop > 0 && jsx("div", {
|
|
477
247
|
"aria-hidden": "true",
|
|
478
248
|
style: {
|
|
479
249
|
gridColumn: "1 / -1",
|
|
480
250
|
height: paddingTop
|
|
481
251
|
}
|
|
482
252
|
}),
|
|
483
|
-
virtualItems.map((vItem)
|
|
253
|
+
virtualItems.map((vItem)=>jsx(DataGridContext.Provider, {
|
|
484
254
|
value: {
|
|
485
255
|
...bodyContextBase,
|
|
486
256
|
isLastRow: vItem.index === totalItems - 1,
|
|
@@ -490,7 +260,7 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
490
260
|
},
|
|
491
261
|
children: renderRow(data[vItem.index], vItem.index)
|
|
492
262
|
}, vItem.key)),
|
|
493
|
-
paddingBottom > 0 &&
|
|
263
|
+
paddingBottom > 0 && jsx("div", {
|
|
494
264
|
"aria-hidden": "true",
|
|
495
265
|
style: {
|
|
496
266
|
gridColumn: "1 / -1",
|
|
@@ -502,7 +272,4 @@ const DEFAULT_COMPACT_ESTIMATED_ROW_HEIGHT = 29;
|
|
|
502
272
|
})
|
|
503
273
|
});
|
|
504
274
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
275
|
export { DataGridInfiniteBody };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
@versini/ui-datagrid v4.2.
|
|
2
|
+
@versini/ui-datagrid v4.2.14
|
|
3
3
|
© 2026 gizmette.com
|
|
4
4
|
*/
|
|
5
5
|
|
|
@@ -8,73 +8,29 @@ import react from "react";
|
|
|
8
8
|
import { DataGridContext } from "../8.js";
|
|
9
9
|
import { CellWrapper } from "../7.js";
|
|
10
10
|
import { getRowClasses } from "../y.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
/* =============================================================================
|
|
20
|
-
* DataGridRow
|
|
21
|
-
* ========================================================================== */ const DataGridRow = ({ className, children, active, style: userStyle, ...rest })=>{
|
|
22
|
-
// Count the number of direct children to determine column count.
|
|
11
|
+
const DataGridRow = ({ className, children, active, style: userStyle, ...rest })=>{
|
|
23
12
|
const columnCount = react.Children.count(children);
|
|
24
|
-
return
|
|
13
|
+
return jsx(DataGridContext.Consumer, {
|
|
25
14
|
children: ({ mode, cellWrapper, stickyHeader, stickyFooter, columns, measuredColumnWidths, isLastRow, ariaRowIndex, dataIndex, measureRowElement })=>{
|
|
26
|
-
|
|
27
|
-
* Determine if this row is inside a sticky header/footer. Sticky elements
|
|
28
|
-
* are absolutely positioned and outside the main grid flow, so they can't
|
|
29
|
-
* use subgrid.
|
|
30
|
-
*/ const isInStickyHeader = cellWrapper === CellWrapper.HEADER && stickyHeader;
|
|
15
|
+
const isInStickyHeader = cellWrapper === CellWrapper.HEADER && stickyHeader;
|
|
31
16
|
const isInStickyFooter = cellWrapper === CellWrapper.FOOTER && stickyFooter;
|
|
32
17
|
const isInStickyContext = isInStickyHeader || isInStickyFooter;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
* since absolutely positioned elements can't use subgrid.
|
|
41
|
-
*
|
|
42
|
-
* When columns are not provided, fall back to the original behavior where
|
|
43
|
-
* each row defines its own equal-width columns.
|
|
44
|
-
*
|
|
45
|
-
*/ let rowStyle;
|
|
46
|
-
if (columns) {
|
|
47
|
-
if (isInStickyContext) {
|
|
48
|
-
/**
|
|
49
|
-
* Sticky elements can't use subgrid. Use measured widths from body if
|
|
50
|
-
* available for perfect alignment. Also check that widths are valid
|
|
51
|
-
* (non-zero) - in test environments or before layout, measurements may be
|
|
52
|
-
* zero.
|
|
53
|
-
*/ const hasValidMeasurements = measuredColumnWidths && measuredColumnWidths.length === columns.length && measuredColumnWidths.some((w)=>w > 0);
|
|
54
|
-
if (hasValidMeasurements) {
|
|
55
|
-
rowStyle = {
|
|
56
|
-
gridTemplateColumns: measuredColumnWidths.map((w)=>`${w}px`).join(" ")
|
|
57
|
-
};
|
|
58
|
-
} else {
|
|
59
|
-
// Fallback to explicit template while measurements are pending.
|
|
60
|
-
rowStyle = {
|
|
61
|
-
gridTemplateColumns: columns.join(" ")
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
// Normal flow: use subgrid to inherit from parent grid.
|
|
66
|
-
rowStyle = {
|
|
67
|
-
gridColumn: "1 / -1",
|
|
68
|
-
gridTemplateColumns: "subgrid"
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
// No columns prop: use equal-width columns.
|
|
73
|
-
rowStyle = {
|
|
74
|
-
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`
|
|
18
|
+
let rowStyle;
|
|
19
|
+
if (columns) if (isInStickyContext) {
|
|
20
|
+
const hasValidMeasurements = measuredColumnWidths && measuredColumnWidths.length === columns.length && measuredColumnWidths.some((w)=>w > 0);
|
|
21
|
+
rowStyle = hasValidMeasurements ? {
|
|
22
|
+
gridTemplateColumns: measuredColumnWidths.map((w)=>`${w}px`).join(" ")
|
|
23
|
+
} : {
|
|
24
|
+
gridTemplateColumns: columns.join(" ")
|
|
75
25
|
};
|
|
76
|
-
}
|
|
77
|
-
|
|
26
|
+
} else rowStyle = {
|
|
27
|
+
gridColumn: "1 / -1",
|
|
28
|
+
gridTemplateColumns: "subgrid"
|
|
29
|
+
};
|
|
30
|
+
else rowStyle = {
|
|
31
|
+
gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))`
|
|
32
|
+
};
|
|
33
|
+
return jsx("div", {
|
|
78
34
|
ref: measureRowElement,
|
|
79
35
|
role: "row",
|
|
80
36
|
className: getRowClasses({
|
|
@@ -89,7 +45,7 @@ import { getRowClasses } from "../y.js";
|
|
|
89
45
|
...rowStyle,
|
|
90
46
|
...userStyle
|
|
91
47
|
},
|
|
92
|
-
"data-active": active ||
|
|
48
|
+
"data-active": active || void 0,
|
|
93
49
|
"data-index": dataIndex,
|
|
94
50
|
"aria-rowindex": ariaRowIndex,
|
|
95
51
|
...rest,
|
|
@@ -98,7 +54,4 @@ import { getRowClasses } from "../y.js";
|
|
|
98
54
|
}
|
|
99
55
|
});
|
|
100
56
|
};
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
57
|
export { DataGridRow };
|