@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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- @versini/ui-datagrid v4.2.12
2
+ @versini/ui-datagrid v4.2.13
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
- * Fallback used to restore the normal window after a smooth jump when the
28
- * browser does not fire `scrollend` (e.g. Safari < 17.4) or the target was
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 || scrollEl !== null;
121
- /**
122
- * Measure the list's offset within the scroller (= sticky-header padding /
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
- * Keep the element scroller's `scroll-padding-top` equal to the header height
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
- * Dev-only: warn about scroll-container setups that silently break
215
- * virtualization — windowing against the page while a bounded ancestor is
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 ((overflowY === "auto" || overflowY === "scroll") && el.scrollHeight > el.clientHeight) {
232
- 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.");
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 (behavior !== "smooth") {
268
- /**
269
- * If a widened smooth jump is still in flight, cancel it so its (possibly
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
- * Perform a pending jump once the widened window has mounted the target row
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 !== "undefined" ? window : null : scrollEl;
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
- * Report the total row count to the grid for aria-rowcount (header + body +
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?.(undefined);
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 !== null && (active === document.body || active === null) && body && !body.querySelector(`[data-index="${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 /*#__PURE__*/ jsx(DataGridContext.Provider, {
214
+ return jsx(DataGridContext.Provider, {
445
215
  value: {
446
216
  ...ctx,
447
217
  cellWrapper: CellWrapper.BODY
448
218
  },
449
- children: /*#__PURE__*/ jsx("div", {
219
+ children: jsx("div", {
450
220
  ref: bodyRef,
451
221
  role: "rowgroup",
452
222
  className: bodyClass,
453
- children: noData ? /*#__PURE__*/ jsx("div", {
223
+ children: noData ? jsx("div", {
454
224
  role: "row",
455
225
  style: {
456
226
  gridColumn: "1 / -1"
457
227
  },
458
- children: /*#__PURE__*/ jsx("div", {
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
- }) : /*#__PURE__*/ jsxs(Fragment, {
236
+ }) : jsxs(Fragment, {
467
237
  children: [
468
- /*#__PURE__*/ jsx("div", {
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 && /*#__PURE__*/ jsx("div", {
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)=>/*#__PURE__*/ jsx(DataGridContext.Provider, {
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 && /*#__PURE__*/ jsx("div", {
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.12
2
+ @versini/ui-datagrid v4.2.13
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 /*#__PURE__*/ jsx(DataGridContext.Consumer, {
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
- * When columns are provided AND the row is inside the grid flow (not in a
35
- * sticky header/footer), use CSS subgrid to inherit the column sizing from
36
- * the parent grid.
37
- *
38
- * For sticky headers/footers, use measured pixel widths from the body if
39
- * available. This syncs the header/footer columns with the body columns
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
- return /*#__PURE__*/ jsx("div", {
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 || undefined,
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 };