@reforgium/data-grid 3.2.2 → 3.2.3

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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [3.2.3]: 13.05.2026
2
+
3
+ ### Fix:
4
+ - `DataGrid`: source/infinity — `totalRowCount` no longer collapses to `0` (empty state) after the internal page buffer evicts early pages on long scrolls when the source does not return `totalElements`; row-count knowledge is now tracked via a monotonic high-water mark (`knownRowCount`) plus end-of-data state (`endReached` / `confirmedTotal`) updated when each page lands, instead of walking the eviction-bound `sourcePages` map
5
+ - `DataGrid`: source/infinity — buffer eviction now anchors `pageBase` and never evicts it, so `localToAbsolutePage` and contiguous-loaded-row accounting stay correct after scrolling past `MAX_BUFFERED_PAGES`
6
+
7
+ ### Test:
8
+ - added regression coverage: monotonic `totalRowCount` without `totalElements` (optimistic probe page, fixed end after short page, post-end full page does not unwind the total), 120-page scroll past `MAX_BUFFERED_PAGES` keeps `totalRowCount` stable, and anchor page survives eviction
9
+
10
+ ### Docs:
11
+ - `GridPagedDataSource.version`: tightened JSDoc — without `totalElements` the grid pins end-of-data on the first short/empty page, so the source must bump `version` on dataset growth/replacement to clear the pin; mutating `items()` in place without a bump leaves the grid with a stale row count
12
+
13
+ ---
14
+
1
15
  ## [3.2.1]: 17.04.2026
2
16
 
3
17
  ### Fix:
@@ -1,4 +1,4 @@
1
- import { c as computeScrollbarState, a as clampThumbTop, m as mapThumbTopToScrollTop } from './reforgium-data-grid-reforgium-data-grid-MQb2oHWO.mjs';
1
+ import { c as computeScrollbarState, a as clampThumbTop, m as mapThumbTopToScrollTop } from './reforgium-data-grid-reforgium-data-grid-DUpCJ9P0.mjs';
2
2
 
3
3
  function createGridOverlayScrollFeature(ctx) {
4
4
  const showScrollbar = () => {
@@ -76,4 +76,4 @@ function createGridOverlayScrollFeature(ctx) {
76
76
  }
77
77
 
78
78
  export { createGridOverlayScrollFeature };
79
- //# sourceMappingURL=reforgium-data-grid-grid-overlay-scroll.feature-CaO2mnI-.mjs.map
79
+ //# sourceMappingURL=reforgium-data-grid-grid-overlay-scroll.feature-DQp-sU6c.mjs.map
@@ -2162,6 +2162,12 @@ function createGridSourceDataFeature(ctx) {
2162
2162
  let requestGeneration = 0;
2163
2163
  let hasReceivedData = false;
2164
2164
  let pageBase = 0;
2165
+ // Row knowledge accounting. Decoupled from `sourcePages` so that buffer eviction
2166
+ // (MAX_BUFFERED_PAGES) cannot collapse totalRowCount back to 0 once a source
2167
+ // without `totalElements` has scrolled past the buffer window.
2168
+ let knownRowCount = 0;
2169
+ let endReached = false;
2170
+ let confirmedTotal = null;
2165
2171
  const hasSessionState = () => sourcePages.size > 0 || pendingPages.size > 0 || queuedPages.length > 0 || activeRequests > 0 || hasReceivedData;
2166
2172
  const resolvedLoading = () => ctx.getSource()?.loading() ?? ctx.getFallbackLoading();
2167
2173
  const totalRowCount = () => {
@@ -2177,14 +2183,13 @@ function createGridSourceDataFeature(ctx) {
2177
2183
  const shiftedTotal = Math.max(0, knownTotal - pageBase * pageSize);
2178
2184
  return shiftedTotal > 0 ? shiftedTotal : (source.items()?.length ?? 0);
2179
2185
  }
2180
- const loadedRows = contiguousLoadedRowCount(source);
2181
- if (loadedRows <= 0) {
2182
- return 0;
2186
+ if (endReached) {
2187
+ return confirmedTotal ?? 0;
2183
2188
  }
2184
- if (hasReachedInfinityEnd(source)) {
2185
- return loadedRows;
2189
+ if (knownRowCount <= 0) {
2190
+ return 0;
2186
2191
  }
2187
- return loadedRows + Math.max(1, source.pageSize || ctx.getFallbackPageSize());
2192
+ return knownRowCount + Math.max(1, source.pageSize || ctx.getFallbackPageSize());
2188
2193
  }
2189
2194
  const knownTotal = resolvedTotalElements(source);
2190
2195
  if (knownTotal !== null) {
@@ -2341,7 +2346,10 @@ function createGridSourceDataFeature(ctx) {
2341
2346
  }
2342
2347
  pageBase = sourcePages.size === 0 ? Math.max(0, source.page || 0) : pageBase;
2343
2348
  const pageItems = source.items() ?? [];
2344
- sourcePages.set(Math.max(0, source.page || 0), pageItems);
2349
+ const sourcePage = Math.max(0, source.page || 0);
2350
+ const pageSize = Math.max(1, source.pageSize || ctx.getFallbackPageSize());
2351
+ sourcePages.set(sourcePage, pageItems);
2352
+ recordPageKnowledge(sourcePage, pageItems, pageSize);
2345
2353
  if (!hasReceivedData && pageItems.length > 0) {
2346
2354
  hasReceivedData = true;
2347
2355
  }
@@ -2363,8 +2371,37 @@ function createGridSourceDataFeature(ctx) {
2363
2371
  pendingPages.clear();
2364
2372
  activeRequests = 0;
2365
2373
  sourcePages.clear();
2374
+ knownRowCount = 0;
2375
+ endReached = false;
2376
+ confirmedTotal = null;
2366
2377
  bufferVersion.update((current) => current + 1);
2367
2378
  };
2379
+ const recordPageKnowledge = (sourcePage, pageItems, pageSize) => {
2380
+ if (sourcePage < pageBase) {
2381
+ return;
2382
+ }
2383
+ const relPage = sourcePage - pageBase;
2384
+ if (pageItems.length === 0) {
2385
+ // Empty page at or beyond the first known page implies the dataset ends here.
2386
+ // For the initial empty sync (relPage === 0) we stay non-committal so that a
2387
+ // later in-place items() update can still surface real rows.
2388
+ if (relPage > 0) {
2389
+ endReached = true;
2390
+ const total = relPage * pageSize;
2391
+ confirmedTotal = confirmedTotal === null ? total : Math.min(confirmedTotal, total);
2392
+ }
2393
+ return;
2394
+ }
2395
+ if (pageItems.length < pageSize) {
2396
+ endReached = true;
2397
+ const total = relPage * pageSize + pageItems.length;
2398
+ confirmedTotal = confirmedTotal === null ? total : Math.min(confirmedTotal, total);
2399
+ return;
2400
+ }
2401
+ if (!endReached) {
2402
+ knownRowCount = Math.max(knownRowCount, (relPage + 1) * pageSize);
2403
+ }
2404
+ };
2368
2405
  const pushPage = (pages, page, maxPage) => {
2369
2406
  if (page < 0 || page > maxPage || pages.includes(page)) {
2370
2407
  return;
@@ -2404,7 +2441,10 @@ function createGridSourceDataFeature(ctx) {
2404
2441
  // только последнюю из страниц, если несколько запросов завершились до
2405
2442
  // того, как эффект успел выполниться, и все промежуточные страницы теряются.
2406
2443
  if (ctx.getMode() === 'infinity' && !sourcePages.has(nextPage)) {
2407
- sourcePages.set(nextPage, activeSource.items() ?? []);
2444
+ const capturedItems = activeSource.items() ?? [];
2445
+ const pageSize = Math.max(1, activeSource.pageSize || ctx.getFallbackPageSize());
2446
+ sourcePages.set(nextPage, capturedItems);
2447
+ recordPageKnowledge(nextPage, capturedItems, pageSize);
2408
2448
  }
2409
2449
  })
2410
2450
  .finally(() => {
@@ -2431,18 +2471,6 @@ function createGridSourceDataFeature(ctx) {
2431
2471
  }
2432
2472
  return loaded;
2433
2473
  };
2434
- const hasReachedInfinityEnd = (source) => {
2435
- const pageSize = Math.max(1, source.pageSize || ctx.getFallbackPageSize());
2436
- let page = pageBase;
2437
- while (sourcePages.has(page)) {
2438
- const items = sourcePages.get(page) ?? [];
2439
- if (items.length < pageSize) {
2440
- return true;
2441
- }
2442
- page++;
2443
- }
2444
- return false;
2445
- };
2446
2474
  const resolvedTotalElements = (source) => {
2447
2475
  if (typeof source.totalElements !== 'number' || !Number.isFinite(source.totalElements)) {
2448
2476
  return null;
@@ -2465,6 +2493,12 @@ function createGridSourceDataFeature(ctx) {
2465
2493
  if (sourcePages.size <= MAX_BUFFERED_PAGES) {
2466
2494
  break;
2467
2495
  }
2496
+ // Never evict the anchor page: `localToAbsolutePage` and the contiguous-loaded
2497
+ // walk both start from `pageBase`, so dropping it would cascade into wrong
2498
+ // request-progress numbers even though totalRowCount is now buffer-independent.
2499
+ if (page === pageBase) {
2500
+ continue;
2501
+ }
2468
2502
  if (!keepPages.has(page)) {
2469
2503
  sourcePages.delete(page);
2470
2504
  }
@@ -4087,7 +4121,7 @@ class DataGrid {
4087
4121
  if (this.overlayScrollFeaturePromise) {
4088
4122
  return this.overlayScrollFeaturePromise;
4089
4123
  }
4090
- this.overlayScrollFeaturePromise = import('./reforgium-data-grid-grid-overlay-scroll.feature-CaO2mnI-.mjs').then(({ createGridOverlayScrollFeature }) => {
4124
+ this.overlayScrollFeaturePromise = import('./reforgium-data-grid-grid-overlay-scroll.feature-DQp-sU6c.mjs').then(({ createGridOverlayScrollFeature }) => {
4091
4125
  const feature = createGridOverlayScrollFeature({
4092
4126
  getScrollElement: () => this.scrollEl()?.nativeElement ?? null,
4093
4127
  getThumbTop: () => this.vm.thumbTopPx(),
@@ -4212,4 +4246,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
4212
4246
  */
4213
4247
 
4214
4248
  export { DataGridTypeCellTemplateDirective as D, clampThumbTop as a, DataGridCellTemplateDirective as b, computeScrollbarState as c, DataGridHeaderTemplateDirective as d, DataGridRowDirective as e, DataGridDeclarativeColumn as f, DataGridDeclarativeHeaderDirective as g, DataGridDeclarativeCellDirective as h, DataGridCellEmptyDirective as i, DataGridCellLoadingDirective as j, DataGridStickyRowDirective as k, DataGridSortIconDirective as l, mapThumbTopToScrollTop as m, DataGridExpanderIconDirective as n, DATA_GRID_CONFIG as o, DATA_GRID_HEADER_TEXT_RESOLVER as p, DATA_GRID_TYPE_RENDERERS as q, DATA_GRID_TYPE_TRANSFORMERS as r, DEFAULT_DATA_GRID_DEFAULTS as s, provideDataGridDefaults as t, provideDataGridHeaderTextResolver as u, provideDataGridHeaderTextResolverWithParent as v, provideDataGridTypeRenderers as w, provideDataGridTypeTransformers as x, DataGrid as y };
4215
- //# sourceMappingURL=reforgium-data-grid-reforgium-data-grid-MQb2oHWO.mjs.map
4249
+ //# sourceMappingURL=reforgium-data-grid-reforgium-data-grid-DUpCJ9P0.mjs.map
@@ -1,2 +1,2 @@
1
- export { o as DATA_GRID_CONFIG, p as DATA_GRID_HEADER_TEXT_RESOLVER, q as DATA_GRID_TYPE_RENDERERS, r as DATA_GRID_TYPE_TRANSFORMERS, s as DEFAULT_DATA_GRID_DEFAULTS, y as DataGrid, i as DataGridCellEmptyDirective, j as DataGridCellLoadingDirective, b as DataGridCellTemplateDirective, h as DataGridDeclarativeCellDirective, f as DataGridDeclarativeColumn, g as DataGridDeclarativeHeaderDirective, n as DataGridExpanderIconDirective, d as DataGridHeaderTemplateDirective, e as DataGridRowDirective, l as DataGridSortIconDirective, k as DataGridStickyRowDirective, D as DataGridTypeCellTemplateDirective, t as provideDataGridDefaults, u as provideDataGridHeaderTextResolver, v as provideDataGridHeaderTextResolverWithParent, w as provideDataGridTypeRenderers, x as provideDataGridTypeTransformers } from './reforgium-data-grid-reforgium-data-grid-MQb2oHWO.mjs';
1
+ export { o as DATA_GRID_CONFIG, p as DATA_GRID_HEADER_TEXT_RESOLVER, q as DATA_GRID_TYPE_RENDERERS, r as DATA_GRID_TYPE_TRANSFORMERS, s as DEFAULT_DATA_GRID_DEFAULTS, y as DataGrid, i as DataGridCellEmptyDirective, j as DataGridCellLoadingDirective, b as DataGridCellTemplateDirective, h as DataGridDeclarativeCellDirective, f as DataGridDeclarativeColumn, g as DataGridDeclarativeHeaderDirective, n as DataGridExpanderIconDirective, d as DataGridHeaderTemplateDirective, e as DataGridRowDirective, l as DataGridSortIconDirective, k as DataGridStickyRowDirective, D as DataGridTypeCellTemplateDirective, t as provideDataGridDefaults, u as provideDataGridHeaderTextResolver, v as provideDataGridHeaderTextResolverWithParent, w as provideDataGridTypeRenderers, x as provideDataGridTypeTransformers } from './reforgium-data-grid-reforgium-data-grid-DUpCJ9P0.mjs';
2
2
  //# sourceMappingURL=reforgium-data-grid.mjs.map
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.2.2",
2
+ "version": "3.2.3",
3
3
  "name": "@reforgium/data-grid",
4
4
  "description": "reforgium DataGrid component",
5
5
  "author": "rtommievich",
@@ -768,7 +768,14 @@ interface GridPagedDataSource<Data extends AnyDict = AnyDict> {
768
768
  * after the next request returns a short page.
769
769
  */
770
770
  totalElements?: number;
771
- /** Optional dataset revision signal. Increment to force the grid to clear its local page buffer. */
771
+ /**
772
+ * Optional dataset revision signal. Increment to force the grid to clear its local page buffer.
773
+ *
774
+ * Required (when present) to be incremented on any dataset growth, replacement, or filter/sort change:
775
+ * in `infinity` mode without `totalElements` the grid pins end-of-data state on the first short/empty
776
+ * page it sees, and only a version bump clears that pin. Mutating `items()` in place without bumping
777
+ * `version` will leave the grid with a stale row count.
778
+ */
772
779
  version?: Signal<number>;
773
780
  /** Current source-side sort state. */
774
781
  sort?: ReadonlyArray<GridSortItem<Data>>;