@reforgium/data-grid 3.2.0 → 3.2.1

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.1]: 17.04.2026
2
+
3
+ ### Fix:
4
+ - `DataGrid`: source/pagination now respects exact `totalElements` when it is provided instead of collapsing paginator state to the current page payload length
5
+ - `DataGrid`: source/pagination without `totalElements` now exposes one optimistic extra page when the current page is full (`items.length === pageSize`), so the consumer can perform the final probe request and detect the real end on the first short/empty page
6
+
7
+ ### Docs:
8
+ - `README`: documented `GridPagedDataSource.totalElements` behavior for source-driven pagination without a known total
9
+
10
+ ### Test:
11
+ - added regression coverage for source/pagination with exact `totalElements` and for the full-page-without-total probe flow
12
+
13
+ ---
14
+
1
15
  ## [3.2.0]: 14.04.2026
2
16
 
3
17
  ### Feat:
package/README.md CHANGED
@@ -303,7 +303,9 @@ Data source guidance:
303
303
  - Use `data` when the parent already owns the full rendered collection.
304
304
  - Use `source` for server-driven pagination or infinity flows where the parent should expose only the current page.
305
305
  - In `mode="infinity"` + `source`, the grid uses `totalElements` for scroll height when available and stores loaded page chunks internally instead of forcing the parent to append rows into one large array.
306
- - `GridPagedDataSource.totalElements` is optional for infinity sources. If omitted, the grid keeps requesting pages until it receives a short page (`items.length < pageSize`) and then treats that page as the end of the dataset.
306
+ - `GridPagedDataSource.totalElements` is optional for source-driven infinity and pagination flows.
307
+ - In `mode="infinity"`, if `totalElements` is omitted, the grid keeps requesting pages until it receives a short page (`items.length < pageSize`) and then treats that page as the end of the dataset.
308
+ - In `mode="pagination"`, if `totalElements` is omitted, the grid exposes one optimistic extra page while the current page is full. The final size becomes known only after a short page is returned.
307
309
  - In `source` mode, initialize the source from the parent after all filters / params are ready; the grid does not perform an automatic first fetch on mount.
308
310
  - `GridPagedDataSource.error` is optional and can be used by consumers that want to project source-level error UI near the grid.
309
311
  - `GridPagedDataSource.sort`, `updateSort(...)`, and `updateSorts(...)` let the grid use source-owned sort state instead of forcing a separate parent bridge.
@@ -388,21 +390,21 @@ Notes:
388
390
 
389
391
  The optional `source` input accepts a page-oriented contract:
390
392
 
391
- | Field / method | Type | Description |
392
- |---|---|---|
393
- | `items` | `Signal<T[]>` | Current page items |
394
- | `loading` | `Signal<boolean>` | Source loading state |
395
- | `error` | `Signal<unknown \| null>` | Optional source error state |
396
- | `page` | `number` | Current page index |
397
- | `pageSize` | `number` | Current page size |
398
- | `totalElements` | `number \| undefined` | Optional total size |
399
- | `sort` | `ReadonlyArray<GridSortItem<T>> \| undefined` | Optional source-owned sort state |
400
- | `version` | `Signal<number> \| undefined` | Dataset reset marker for local buffer invalidation |
401
- | `updatePage(page)` | `(page: number) => Promise<unknown>` | Load a page |
402
- | `updatePageSize(size)` | `((size: number) => Promise<unknown>) \| undefined` | Optional page-size handler |
403
- | `updateSort(sort)` | `((sort?: GridSortItem<T> \| null) => Promise<unknown>) \| undefined` | Optional single-sort handler |
404
- | `updateSorts(sort)` | `((sort?: ReadonlyArray<GridSortItem<T>> \| null) => Promise<unknown>) \| undefined` | Optional multi-sort handler |
405
- | `prefetchMode` | `'sequential' \| 'parallel' \| undefined` | Infinity prefetch strategy |
393
+ | Field / method | Type | Description |
394
+ |------------------------|--------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
395
+ | `items` | `Signal<T[]>` | Current page items |
396
+ | `loading` | `Signal<boolean>` | Source loading state |
397
+ | `error` | `Signal<unknown \| null>` | Optional source error state |
398
+ | `page` | `number` | Current page index |
399
+ | `pageSize` | `number` | Current page size |
400
+ | `totalElements` | `number \| undefined` | Optional exact total size; if omitted, `infinity` stops on the first short page and `pagination` exposes one optimistic extra page while the current page is full |
401
+ | `sort` | `ReadonlyArray<GridSortItem<T>> \| undefined` | Optional source-owned sort state |
402
+ | `version` | `Signal<number> \| undefined` | Dataset reset marker for local buffer invalidation |
403
+ | `updatePage(page)` | `(page: number) => Promise<unknown>` | Load a page |
404
+ | `updatePageSize(size)` | `((size: number) => Promise<unknown>) \| undefined` | Optional page-size handler |
405
+ | `updateSort(sort)` | `((sort?: GridSortItem<T> \| null) => Promise<unknown>) \| undefined` | Optional single-sort handler |
406
+ | `updateSorts(sort)` | `((sort?: ReadonlyArray<GridSortItem<T>> \| null) => Promise<unknown>) \| undefined` | Optional multi-sort handler |
407
+ | `prefetchMode` | `'sequential' \| 'parallel' \| undefined` | Infinity prefetch strategy |
406
408
 
407
409
  This contract is intentionally grid-like, not `statum`-specific, but `PagedQueryStore` already matches it well enough to be used directly.
408
410
 
@@ -1,4 +1,4 @@
1
- import { c as computeScrollbarState, a as clampThumbTop, m as mapThumbTopToScrollTop } from './reforgium-data-grid-reforgium-data-grid-Cb2oAQjG.mjs';
1
+ import { c as computeScrollbarState, a as clampThumbTop, m as mapThumbTopToScrollTop } from './reforgium-data-grid-reforgium-data-grid-MQb2oHWO.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-6tkNtIuO.mjs.map
79
+ //# sourceMappingURL=reforgium-data-grid-grid-overlay-scroll.feature-CaO2mnI-.mjs.map
@@ -2186,7 +2186,11 @@ function createGridSourceDataFeature(ctx) {
2186
2186
  }
2187
2187
  return loadedRows + Math.max(1, source.pageSize || ctx.getFallbackPageSize());
2188
2188
  }
2189
- return source.items()?.length ?? 0;
2189
+ const knownTotal = resolvedTotalElements(source);
2190
+ if (knownTotal !== null) {
2191
+ return knownTotal;
2192
+ }
2193
+ return estimateOpenEndedPaginationTotal(source);
2190
2194
  };
2191
2195
  const selectionRows = () => {
2192
2196
  const source = ctx.getSource();
@@ -2445,6 +2449,14 @@ function createGridSourceDataFeature(ctx) {
2445
2449
  }
2446
2450
  return Math.max(0, source.totalElements);
2447
2451
  };
2452
+ const estimateOpenEndedPaginationTotal = (source) => {
2453
+ const pageSize = Math.max(1, source.pageSize || ctx.getFallbackPageSize());
2454
+ const page = Math.max(0, source.page || 0);
2455
+ const itemsCount = source.items()?.length ?? 0;
2456
+ const loadedBefore = page * pageSize;
2457
+ const hasUnknownNextPage = itemsCount === pageSize;
2458
+ return loadedBefore + itemsCount + (hasUnknownNextPage ? pageSize : 0);
2459
+ };
2448
2460
  const evictExcessPages = (keepPages) => {
2449
2461
  if (sourcePages.size <= MAX_BUFFERED_PAGES) {
2450
2462
  return;
@@ -4075,7 +4087,7 @@ class DataGrid {
4075
4087
  if (this.overlayScrollFeaturePromise) {
4076
4088
  return this.overlayScrollFeaturePromise;
4077
4089
  }
4078
- this.overlayScrollFeaturePromise = import('./reforgium-data-grid-grid-overlay-scroll.feature-6tkNtIuO.mjs').then(({ createGridOverlayScrollFeature }) => {
4090
+ this.overlayScrollFeaturePromise = import('./reforgium-data-grid-grid-overlay-scroll.feature-CaO2mnI-.mjs').then(({ createGridOverlayScrollFeature }) => {
4079
4091
  const feature = createGridOverlayScrollFeature({
4080
4092
  getScrollElement: () => this.scrollEl()?.nativeElement ?? null,
4081
4093
  getThumbTop: () => this.vm.thumbTopPx(),
@@ -4200,4 +4212,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.4", ngImpor
4200
4212
  */
4201
4213
 
4202
4214
  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 };
4203
- //# sourceMappingURL=reforgium-data-grid-reforgium-data-grid-Cb2oAQjG.mjs.map
4215
+ //# sourceMappingURL=reforgium-data-grid-reforgium-data-grid-MQb2oHWO.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-Cb2oAQjG.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-MQb2oHWO.mjs';
2
2
  //# sourceMappingURL=reforgium-data-grid.mjs.map
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "3.2.0",
2
+ "version": "3.2.1",
3
3
  "name": "@reforgium/data-grid",
4
4
  "description": "reforgium DataGrid component",
5
5
  "author": "rtommievich",
@@ -759,8 +759,13 @@ interface GridPagedDataSource<Data extends AnyDict = AnyDict> {
759
759
  /**
760
760
  * Total number of rows available in the dataset.
761
761
  *
762
- * Optional in infinity mode. When omitted, the grid treats the dataset as open-ended
763
- * until it receives a short page (`items.length < pageSize`) and then fixes the final size.
762
+ * Optional in source-driven pagination/infinity mode.
763
+ *
764
+ * - In `infinity`, the grid treats the dataset as open-ended until it receives
765
+ * a short page (`items.length < pageSize`) and then fixes the final size.
766
+ * - In `pagination`, the grid exposes one optimistic extra page while the current
767
+ * page is full, because without `totalElements` the end can only be detected
768
+ * after the next request returns a short page.
764
769
  */
765
770
  totalElements?: number;
766
771
  /** Optional dataset revision signal. Increment to force the grid to clear its local page buffer. */