@tanstack/table-core 9.0.0-beta.51 → 9.0.0-beta.52

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.
Files changed (54) hide show
  1. package/dist/features/column-grouping/createGroupedRowModel.cjs +19 -11
  2. package/dist/features/column-grouping/createGroupedRowModel.cjs.map +1 -1
  3. package/dist/features/column-grouping/createGroupedRowModel.js +20 -12
  4. package/dist/features/column-grouping/createGroupedRowModel.js.map +1 -1
  5. package/dist/features/row-aggregation/aggregationFns.cjs +80 -35
  6. package/dist/features/row-aggregation/aggregationFns.cjs.map +1 -1
  7. package/dist/features/row-aggregation/aggregationFns.js +80 -35
  8. package/dist/features/row-aggregation/aggregationFns.js.map +1 -1
  9. package/dist/features/row-aggregation/rowAggregationFeature.cjs +0 -3
  10. package/dist/features/row-aggregation/rowAggregationFeature.cjs.map +1 -1
  11. package/dist/features/row-aggregation/rowAggregationFeature.js +1 -4
  12. package/dist/features/row-aggregation/rowAggregationFeature.js.map +1 -1
  13. package/dist/features/row-aggregation/rowAggregationFeature.types.cjs.map +1 -1
  14. package/dist/features/row-aggregation/rowAggregationFeature.types.d.cts +2 -2
  15. package/dist/features/row-aggregation/rowAggregationFeature.types.d.ts +2 -2
  16. package/dist/features/row-aggregation/rowAggregationFeature.types.js.map +1 -1
  17. package/dist/features/row-aggregation/rowAggregationFeature.utils.cjs +35 -4
  18. package/dist/features/row-aggregation/rowAggregationFeature.utils.cjs.map +1 -1
  19. package/dist/features/row-aggregation/rowAggregationFeature.utils.d.cts +15 -1
  20. package/dist/features/row-aggregation/rowAggregationFeature.utils.d.ts +15 -1
  21. package/dist/features/row-aggregation/rowAggregationFeature.utils.js +35 -5
  22. package/dist/features/row-aggregation/rowAggregationFeature.utils.js.map +1 -1
  23. package/dist/static-functions.cjs +1 -0
  24. package/dist/static-functions.d.cts +2 -2
  25. package/dist/static-functions.d.ts +2 -2
  26. package/dist/static-functions.js +2 -2
  27. package/package.json +1 -1
  28. package/skills/aggregation/SKILL.md +1 -1
  29. package/skills/api-not-found/SKILL.md +1 -1
  30. package/skills/client-vs-server/SKILL.md +1 -1
  31. package/skills/column-faceting/SKILL.md +1 -1
  32. package/skills/column-filtering/SKILL.md +1 -1
  33. package/skills/column-ordering/SKILL.md +1 -1
  34. package/skills/column-pinning/SKILL.md +1 -1
  35. package/skills/column-resizing/SKILL.md +1 -1
  36. package/skills/column-sizing/SKILL.md +1 -1
  37. package/skills/column-visibility/SKILL.md +1 -1
  38. package/skills/core/SKILL.md +1 -1
  39. package/skills/custom-features/SKILL.md +1 -1
  40. package/skills/expanding/SKILL.md +1 -1
  41. package/skills/global-filtering/SKILL.md +1 -1
  42. package/skills/grouping/SKILL.md +1 -1
  43. package/skills/migrate-v8-to-v9/SKILL.md +1 -1
  44. package/skills/pagination/SKILL.md +1 -1
  45. package/skills/row-pinning/SKILL.md +1 -1
  46. package/skills/row-selection/SKILL.md +1 -1
  47. package/skills/sorting/SKILL.md +1 -1
  48. package/skills/table-features/SKILL.md +1 -1
  49. package/skills/typescript/SKILL.md +1 -1
  50. package/src/features/column-grouping/createGroupedRowModel.ts +47 -10
  51. package/src/features/row-aggregation/aggregationFns.ts +80 -42
  52. package/src/features/row-aggregation/rowAggregationFeature.ts +1 -5
  53. package/src/features/row-aggregation/rowAggregationFeature.types.ts +2 -2
  54. package/src/features/row-aggregation/rowAggregationFeature.utils.ts +60 -3
@@ -5,7 +5,7 @@ description: >
5
5
  metadata:
6
6
  type: core
7
7
  library: '@tanstack/table-core'
8
- library_version: '9.0.0-beta.51'
8
+ library_version: '9.0.0-beta.52'
9
9
  sources:
10
10
  - 'TanStack/table:docs/overview.md'
11
11
  - 'TanStack/table:docs/guide/tables.md'
@@ -5,7 +5,7 @@ description: >
5
5
  metadata:
6
6
  type: sub-skill
7
7
  library: '@tanstack/table-core'
8
- library_version: '9.0.0-beta.51'
8
+ library_version: '9.0.0-beta.52'
9
9
  requires: ['core', 'table-features', 'typescript']
10
10
  sources:
11
11
  - 'TanStack/table:docs/framework/react/guide/custom-features.md'
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features', 'client-vs-server']
12
12
  sources:
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features', 'client-vs-server', 'column-filtering']
12
12
  sources:
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features', 'client-vs-server']
12
12
  sources:
@@ -5,7 +5,7 @@ description: >
5
5
  metadata:
6
6
  type: lifecycle
7
7
  library: '@tanstack/table-core'
8
- library_version: '9.0.0-beta.51'
8
+ library_version: '9.0.0-beta.52'
9
9
  requires: ['core', 'table-features', 'typescript']
10
10
  sources:
11
11
  - 'TanStack/table:docs/framework/react/guide/migrating.md'
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features', 'client-vs-server']
12
12
  sources:
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features']
12
12
  sources:
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features']
12
12
  sources:
@@ -6,7 +6,7 @@ metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.51',
9
+ library_version: '9.0.0-beta.52',
10
10
  }
11
11
  requires: ['core', 'table-features', 'client-vs-server']
12
12
  sources:
@@ -5,7 +5,7 @@ description: >
5
5
  metadata:
6
6
  type: sub-skill
7
7
  library: '@tanstack/table-core'
8
- library_version: '9.0.0-beta.51'
8
+ library_version: '9.0.0-beta.52'
9
9
  requires: ['core']
10
10
  sources:
11
11
  - 'TanStack/table:docs/guide/row-models.md'
@@ -5,7 +5,7 @@ description: >
5
5
  metadata:
6
6
  type: sub-skill
7
7
  library: '@tanstack/table-core'
8
- library_version: '9.0.0-beta.51'
8
+ library_version: '9.0.0-beta.52'
9
9
  requires: ['core', 'table-features']
10
10
  sources:
11
11
  - 'TanStack/table:docs/guide/helpers.md'
@@ -5,10 +5,10 @@ import { table_autoResetExpanded } from '../row-expanding/rowExpandingFeature.ut
5
5
  import { table_autoResetPageIndex } from '../row-pagination/rowPaginationFeature.utils'
6
6
  import {
7
7
  aggregateColumnValue,
8
- normalizeAggregationRows,
8
+ normalizeUniqueAggregationRows,
9
9
  } from '../row-aggregation/rowAggregationFeature.utils'
10
- import { row_getGroupingValue } from './columnGroupingFeature.utils'
11
10
  import type { Row_ColumnGrouping } from './columnGroupingFeature.types'
11
+ import type { Column_Internal } from '../../types/Column'
12
12
  import type { TableFeatures } from '../../types/TableFeatures'
13
13
  import type { RowModel } from '../../core/row-models/coreRowModelsFeature.types'
14
14
  import type { Table, Table_Internal } from '../../types/Table'
@@ -103,7 +103,7 @@ function _createGroupedRowModel<
103
103
  const columnId = existingGrouping[depth] as string
104
104
 
105
105
  // Group the rows together for this level
106
- const rowGroupsMap = groupBy(rows, columnId)
106
+ const rowGroupsMap = groupBy(table, rows, columnId)
107
107
 
108
108
  // Perform aggregations for each group
109
109
  const aggregatedGroupedRows = Array.from(rowGroupsMap.entries()).map(
@@ -118,7 +118,13 @@ function _createGroupedRowModel<
118
118
  subRow.parentId = id
119
119
  })
120
120
 
121
- const leafRows = normalizeAggregationRows(groupedRows, Infinity)
121
+ // Rows produced by groupBy are disjoint members of the pre-grouped
122
+ // row tree, so the duplicate-id guard is unnecessary; flat groups
123
+ // reuse the partition array as-is.
124
+ const leafRows = normalizeUniqueAggregationRows(
125
+ groupedRows,
126
+ Infinity,
127
+ ) as Array<Row<TFeatures, TData>>
122
128
 
123
129
  const row = constructRow(
124
130
  table,
@@ -171,6 +177,7 @@ function _createGroupedRowModel<
171
177
  column,
172
178
  groupingRow: row,
173
179
  rows: groupedRows,
180
+ uniqueRows: true,
174
181
  })
175
182
  return cache[colId]
176
183
  },
@@ -203,19 +210,49 @@ function _createGroupedRowModel<
203
210
  }
204
211
 
205
212
  function groupBy<TFeatures extends TableFeatures, TData extends RowData = any>(
213
+ table: Table_Internal<TFeatures, TData>,
206
214
  rows: Array<Row<TFeatures, TData>>,
207
215
  columnId: string,
208
216
  ) {
209
217
  const groupMap = new Map<any, Array<Row<TFeatures, TData>>>()
210
218
 
211
- return rows.reduce((map, row) => {
212
- const resKey = `${row_getGroupingValue(row, columnId)}`
213
- const previous = map.get(resKey)
219
+ // Resolve the column once instead of per row: `table.getColumn` goes
220
+ // through the memoized-API dispatcher, which is far too expensive to sit
221
+ // inside this per-row loop. The branches below mirror
222
+ // `row_getGroupingValue`'s caching contract exactly.
223
+ const column = table_getColumn(table, columnId) as
224
+ | Column_Internal<TFeatures, TData, unknown>
225
+ | undefined
226
+ const getGroupingValue = column?.columnDef.getGroupingValue
227
+
228
+ for (let i = 0; i < rows.length; i++) {
229
+ const row = rows[i]!
230
+ let groupingValue
231
+ if (getGroupingValue) {
232
+ const cache = (row as any)._groupingValuesCache as
233
+ | Record<string, unknown>
234
+ | undefined
235
+ if (cache && hasOwn(cache, columnId)) {
236
+ groupingValue = cache[columnId]
237
+ } else if (cache) {
238
+ groupingValue = cache[columnId] = getGroupingValue(
239
+ row.original,
240
+ row.index,
241
+ row,
242
+ )
243
+ }
244
+ } else {
245
+ groupingValue = row.getValue(columnId)
246
+ }
247
+
248
+ const resKey = `${groupingValue}`
249
+ const previous = groupMap.get(resKey)
214
250
  if (!previous) {
215
- map.set(resKey, [row])
251
+ groupMap.set(resKey, [row])
216
252
  } else {
217
253
  previous.push(row)
218
254
  }
219
- return map
220
- }, groupMap)
255
+ }
256
+
257
+ return groupMap
221
258
  }
@@ -1,5 +1,4 @@
1
1
  import { constructAggregationFn } from './rowAggregationFeature.types'
2
- import type { AggregationContext } from './rowAggregationFeature.types'
3
2
 
4
3
  type RangeValue = Date | number
5
4
 
@@ -23,19 +22,9 @@ function compareRangeValues(left: RangeValue, right: RangeValue) {
23
22
  return leftValue - rightValue
24
23
  }
25
24
 
26
- function collectRangeValues(context: AggregationContext<any, any, unknown>) {
27
- const values: Array<RangeValue> = []
28
- let kind: 'date' | 'number' | undefined
29
-
30
- for (let i = 0; i < context.rows.length; i++) {
31
- const value = context.getValue(context.rows[i]!)
32
- const valueKind = getRangeKind(value)
33
- if (!valueKind) continue
34
- kind ??= valueKind
35
- if (valueKind === kind) values.push(value as RangeValue)
36
- }
37
-
38
- return values
25
+ /** Comparable representation of a range value; `Date`s compare by time. */
26
+ function toRangeNumber(value: RangeValue): number {
27
+ return value instanceof Date ? value.getTime() : value
39
28
  }
40
29
 
41
30
  /**
@@ -49,9 +38,10 @@ export const aggregationFn_sum = constructAggregationFn<
49
38
  number
50
39
  >({
51
40
  aggregate: (context) => {
41
+ const rows = context.rows
52
42
  let sum = 0
53
- for (let i = 0; i < context.rows.length; i++) {
54
- const value = context.getValue(context.rows[i]!)
43
+ for (let i = 0; i < rows.length; i++) {
44
+ const value = context.getValue(rows[i]!)
55
45
  sum += typeof value === 'number' ? value : 0
56
46
  }
57
47
  return sum
@@ -77,10 +67,23 @@ export const aggregationFn_min = constructAggregationFn<
77
67
  RangeValue | undefined
78
68
  >({
79
69
  aggregate: (context) => {
80
- const values = collectRangeValues(context)
81
- let result = values[0]
82
- for (let i = 1; i < values.length; i++) {
83
- if (compareRangeValues(values[i]!, result!) < 0) result = values[i]
70
+ const rows = context.rows
71
+ let kind: 'date' | 'number' | undefined
72
+ let result: RangeValue | undefined
73
+ let resultNumber = 0
74
+ for (let i = 0; i < rows.length; i++) {
75
+ const value = context.getValue(rows[i]!)
76
+ const valueKind = getRangeKind(value)
77
+ if (!valueKind || (kind !== undefined && valueKind !== kind)) continue
78
+ const valueNumber = toRangeNumber(value as RangeValue)
79
+ if (kind === undefined) {
80
+ kind = valueKind
81
+ result = value as RangeValue
82
+ resultNumber = valueNumber
83
+ } else if (valueNumber - resultNumber < 0) {
84
+ result = value as RangeValue
85
+ resultNumber = valueNumber
86
+ }
84
87
  }
85
88
  return result
86
89
  },
@@ -113,10 +116,23 @@ export const aggregationFn_max = constructAggregationFn<
113
116
  RangeValue | undefined
114
117
  >({
115
118
  aggregate: (context) => {
116
- const values = collectRangeValues(context)
117
- let result = values[0]
118
- for (let i = 1; i < values.length; i++) {
119
- if (compareRangeValues(values[i]!, result!) > 0) result = values[i]
119
+ const rows = context.rows
120
+ let kind: 'date' | 'number' | undefined
121
+ let result: RangeValue | undefined
122
+ let resultNumber = 0
123
+ for (let i = 0; i < rows.length; i++) {
124
+ const value = context.getValue(rows[i]!)
125
+ const valueKind = getRangeKind(value)
126
+ if (!valueKind || (kind !== undefined && valueKind !== kind)) continue
127
+ const valueNumber = toRangeNumber(value as RangeValue)
128
+ if (kind === undefined) {
129
+ kind = valueKind
130
+ result = value as RangeValue
131
+ resultNumber = valueNumber
132
+ } else if (valueNumber - resultNumber > 0) {
133
+ result = value as RangeValue
134
+ resultNumber = valueNumber
135
+ }
120
136
  }
121
137
  return result
122
138
  },
@@ -150,15 +166,33 @@ export const aggregationFn_extent = constructAggregationFn<
150
166
  [RangeValue | undefined, RangeValue | undefined]
151
167
  >({
152
168
  aggregate: (context) => {
153
- const values = collectRangeValues(context)
154
- if (!values.length) return [undefined, undefined]
155
- let min = values[0]!
156
- let max = values[0]!
157
- for (let i = 1; i < values.length; i++) {
158
- const value = values[i]!
159
- if (compareRangeValues(value, min) < 0) min = value
160
- if (compareRangeValues(value, max) > 0) max = value
169
+ const rows = context.rows
170
+ let kind: 'date' | 'number' | undefined
171
+ let min: RangeValue | undefined
172
+ let max: RangeValue | undefined
173
+ let minNumber = 0
174
+ let maxNumber = 0
175
+ for (let i = 0; i < rows.length; i++) {
176
+ const value = context.getValue(rows[i]!)
177
+ const valueKind = getRangeKind(value)
178
+ if (!valueKind || (kind !== undefined && valueKind !== kind)) continue
179
+ const valueNumber = toRangeNumber(value as RangeValue)
180
+ if (kind === undefined) {
181
+ kind = valueKind
182
+ min = max = value as RangeValue
183
+ minNumber = maxNumber = valueNumber
184
+ } else {
185
+ if (valueNumber - minNumber < 0) {
186
+ min = value as RangeValue
187
+ minNumber = valueNumber
188
+ }
189
+ if (valueNumber - maxNumber > 0) {
190
+ max = value as RangeValue
191
+ maxNumber = valueNumber
192
+ }
193
+ }
161
194
  }
195
+ if (kind === undefined) return [undefined, undefined]
162
196
  return [min, max]
163
197
  },
164
198
  merge: ({ subRowResults }) => {
@@ -205,10 +239,11 @@ export const aggregationFn_mean = constructAggregationFn<
205
239
  number | undefined
206
240
  >({
207
241
  aggregate: (context) => {
242
+ const rows = context.rows
208
243
  let count = 0
209
244
  let sum = 0
210
- for (let i = 0; i < context.rows.length; i++) {
211
- const value = context.getValue(context.rows[i]!)
245
+ for (let i = 0; i < rows.length; i++) {
246
+ const value = context.getValue(rows[i]!)
212
247
  if (value == null) continue
213
248
  const numberValue = typeof value === 'number' ? value : +value
214
249
  if (!Number.isNaN(numberValue)) {
@@ -231,10 +266,11 @@ export const aggregationFn_median = constructAggregationFn<
231
266
  number | undefined
232
267
  >({
233
268
  aggregate: (context) => {
234
- if (!context.rows.length) return undefined
235
- const values = new Array<number>(context.rows.length)
236
- for (let i = 0; i < context.rows.length; i++) {
237
- const value = context.getValue(context.rows[i]!)
269
+ const rows = context.rows
270
+ if (!rows.length) return undefined
271
+ const values = new Array<number>(rows.length)
272
+ for (let i = 0; i < rows.length; i++) {
273
+ const value = context.getValue(rows[i]!)
238
274
  if (typeof value !== 'number') return undefined
239
275
  values[i] = value
240
276
  }
@@ -254,9 +290,10 @@ export const aggregationFn_unique = constructAggregationFn<
254
290
  Array<unknown>
255
291
  >({
256
292
  aggregate: (context) => {
293
+ const rows = context.rows
257
294
  const values = new Set<unknown>()
258
- for (let i = 0; i < context.rows.length; i++) {
259
- values.add(context.getValue(context.rows[i]!))
295
+ for (let i = 0; i < rows.length; i++) {
296
+ values.add(context.getValue(rows[i]!))
260
297
  }
261
298
  return Array.from(values)
262
299
  },
@@ -270,9 +307,10 @@ export const aggregationFn_uniqueCount = constructAggregationFn<
270
307
  number
271
308
  >({
272
309
  aggregate: (context) => {
310
+ const rows = context.rows
273
311
  const values = new Set<unknown>()
274
- for (let i = 0; i < context.rows.length; i++) {
275
- values.add(context.getValue(context.rows[i]!))
312
+ for (let i = 0; i < rows.length; i++) {
313
+ values.add(context.getValue(rows[i]!))
276
314
  }
277
315
  return values.size
278
316
  },
@@ -1,4 +1,4 @@
1
- import { assignPrototypeAPIs, makeObjectMap } from '../../utils'
1
+ import { assignPrototypeAPIs } from '../../utils'
2
2
  import {
3
3
  cell_getIsAggregated,
4
4
  column_getAggregationFns,
@@ -53,8 +53,4 @@ export const rowAggregationFeature: TableFeature = {
53
53
  ;(column as any)._aggregationValueCache = undefined
54
54
  ;(column as any)._resolvedAggregationFnsCache = undefined
55
55
  },
56
-
57
- initRowInstanceData: (row) => {
58
- ;(row as any)._aggregationValuesCache = makeObjectMap()
59
- },
60
56
  }
@@ -302,8 +302,8 @@ export interface Cell_Aggregation {
302
302
 
303
303
  /** Internal per-row cache used while grouped aggregates are evaluated. */
304
304
  export interface Row_Aggregation {
305
- /** Cached aggregate results keyed by column id. */
306
- _aggregationValuesCache: Record<string, unknown>
305
+ /** Cached aggregate results keyed by column id; created lazily on grouped rows. */
306
+ _aggregationValuesCache?: Record<string, unknown>
307
307
  }
308
308
 
309
309
  /** Values passed to a column-level aggregation-value provider. */
@@ -95,6 +95,52 @@ export function normalizeAggregationRows<
95
95
  return result
96
96
  }
97
97
 
98
+ /**
99
+ * Frontier selection for rows that are distinct nodes of a single row tree —
100
+ * the row models the table builds itself. Skips `normalizeAggregationRows`'
101
+ * duplicate-id guard (disjoint subtrees cannot revisit a row) and returns
102
+ * `rows` unchanged when no row descends, so the default `maxDepth: 0` case
103
+ * costs nothing per aggregation.
104
+ */
105
+ export function normalizeUniqueAggregationRows<
106
+ TFeatures extends TableFeatures,
107
+ TData extends RowData,
108
+ >(
109
+ rows: ReadonlyArray<Row<TFeatures, TData>>,
110
+ maxDepth = 0,
111
+ ): ReadonlyArray<Row<TFeatures, TData>> {
112
+ const normalizedMaxDepth = resolveMaxAggregationDepth(maxDepth)
113
+
114
+ let needsDescent = false
115
+ if (normalizedMaxDepth > 0) {
116
+ for (let i = 0; i < rows.length; i++) {
117
+ if (rows[i]!.subRows.length) {
118
+ needsDescent = true
119
+ break
120
+ }
121
+ }
122
+ }
123
+ if (!needsDescent) return rows
124
+
125
+ const result: Array<Row<TFeatures, TData>> = []
126
+
127
+ const visit = (row: Row<TFeatures, TData>, depth: number) => {
128
+ if (row.subRows.length && depth < normalizedMaxDepth) {
129
+ for (let i = 0; i < row.subRows.length; i++) {
130
+ visit(row.subRows[i]!, depth + 1)
131
+ }
132
+ return
133
+ }
134
+ result.push(row)
135
+ }
136
+
137
+ for (let i = 0; i < rows.length; i++) {
138
+ visit(rows[i]!, 0)
139
+ }
140
+
141
+ return result
142
+ }
143
+
98
144
  function getAutoAggregationFnName(
99
145
  value: unknown,
100
146
  ): 'extent' | 'sum' | undefined {
@@ -267,13 +313,21 @@ export function aggregateColumnValue<
267
313
  column: Column<TFeatures, TData, unknown>
268
314
  groupingRow?: Row<TFeatures, TData>
269
315
  rows: ReadonlyArray<Row<TFeatures, TData>>
316
+ /**
317
+ * Marks `rows` as distinct nodes of a single row tree (rows the table's own
318
+ * row models produced), enabling frontier selection without the
319
+ * duplicate-id guard. Caller-supplied row arrays must omit this.
320
+ */
321
+ uniqueRows?: boolean
270
322
  }): unknown {
271
- const { subRows, column, groupingRow, rows } = args
323
+ const { subRows, column, groupingRow, rows, uniqueRows } = args
272
324
  const internalColumn = column as Column_Internal<TFeatures, TData, unknown>
273
325
  const maxDepth = resolveMaxAggregationDepth(
274
326
  args.maxDepth ?? internalColumn.columnDef.maxAggregationDepth,
275
327
  )
276
- const aggregationRows = normalizeAggregationRows(rows, maxDepth)
328
+ const aggregationRows = uniqueRows
329
+ ? normalizeUniqueAggregationRows(rows, maxDepth)
330
+ : normalizeAggregationRows(rows, maxDepth)
277
331
  const entries = column_getAggregationFns(internalColumn)
278
332
  const isMultiple = Array.isArray(internalColumn.columnDef.aggregationFn)
279
333
  const canMerge =
@@ -284,6 +338,8 @@ export function aggregateColumnValue<
284
338
  (row as any).groupingColumnId !== column.id,
285
339
  )
286
340
 
341
+ const getValue = (row: Row<TFeatures, TData>) => row.getValue(column.id)
342
+
287
343
  const execute = (entry: ResolvedAggregationFn<TFeatures, TData>) => {
288
344
  const definition = entry.aggregationFn
289
345
  if (!definition) return undefined
@@ -292,7 +348,7 @@ export function aggregateColumnValue<
292
348
  ...(subRows ? { subRows } : {}),
293
349
  column,
294
350
  columnId: column.id,
295
- getValue: (row: Row<TFeatures, TData>) => row.getValue(column.id),
351
+ getValue,
296
352
  ...(groupingRow ? { groupingRow } : {}),
297
353
  maxDepth,
298
354
  rows: aggregationRows,
@@ -378,6 +434,7 @@ export function column_getAggregationValue<
378
434
  column: column as any,
379
435
  maxDepth: resolvedMaxDepth,
380
436
  rows: model.rows,
437
+ uniqueRows: true,
381
438
  })
382
439
  ;(column as any)._aggregationValueCache = {
383
440
  aggregationFnOption,