@tanstack/table-core 9.0.0-beta.26 → 9.0.0-beta.28

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 (70) hide show
  1. package/dist/features/column-faceting/columnFacetingFeature.types.d.cts +6 -3
  2. package/dist/features/column-faceting/columnFacetingFeature.types.d.ts +6 -3
  3. package/dist/features/column-faceting/columnFacetingFeature.utils.cjs +41 -12
  4. package/dist/features/column-faceting/columnFacetingFeature.utils.cjs.map +1 -1
  5. package/dist/features/column-faceting/columnFacetingFeature.utils.js +42 -12
  6. package/dist/features/column-faceting/columnFacetingFeature.utils.js.map +1 -1
  7. package/dist/features/column-faceting/createFacetedRowModel.cjs +1 -0
  8. package/dist/features/column-faceting/createFacetedRowModel.cjs.map +1 -1
  9. package/dist/features/column-faceting/createFacetedRowModel.js +1 -0
  10. package/dist/features/column-faceting/createFacetedRowModel.js.map +1 -1
  11. package/dist/features/column-faceting/createFacetedUniqueValues.cjs +2 -2
  12. package/dist/features/column-faceting/createFacetedUniqueValues.cjs.map +1 -1
  13. package/dist/features/column-faceting/createFacetedUniqueValues.js +2 -2
  14. package/dist/features/column-faceting/createFacetedUniqueValues.js.map +1 -1
  15. package/dist/features/column-ordering/columnOrderingFeature.cjs +2 -1
  16. package/dist/features/column-ordering/columnOrderingFeature.cjs.map +1 -1
  17. package/dist/features/column-ordering/columnOrderingFeature.js +2 -1
  18. package/dist/features/column-ordering/columnOrderingFeature.js.map +1 -1
  19. package/dist/features/column-pinning/columnPinningFeature.cjs +24 -12
  20. package/dist/features/column-pinning/columnPinningFeature.cjs.map +1 -1
  21. package/dist/features/column-pinning/columnPinningFeature.js +24 -12
  22. package/dist/features/column-pinning/columnPinningFeature.js.map +1 -1
  23. package/dist/features/column-resizing/columnResizingFeature.utils.cjs +62 -24
  24. package/dist/features/column-resizing/columnResizingFeature.utils.cjs.map +1 -1
  25. package/dist/features/column-resizing/columnResizingFeature.utils.js +62 -24
  26. package/dist/features/column-resizing/columnResizingFeature.utils.js.map +1 -1
  27. package/dist/features/column-sizing/columnSizingFeature.cjs +35 -45
  28. package/dist/features/column-sizing/columnSizingFeature.cjs.map +1 -1
  29. package/dist/features/column-sizing/columnSizingFeature.js +36 -46
  30. package/dist/features/column-sizing/columnSizingFeature.js.map +1 -1
  31. package/dist/features/column-sizing/columnSizingFeature.types.d.cts +38 -1
  32. package/dist/features/column-sizing/columnSizingFeature.types.d.ts +38 -1
  33. package/dist/features/column-sizing/columnSizingFeature.utils.cjs +49 -10
  34. package/dist/features/column-sizing/columnSizingFeature.utils.cjs.map +1 -1
  35. package/dist/features/column-sizing/columnSizingFeature.utils.d.cts +18 -4
  36. package/dist/features/column-sizing/columnSizingFeature.utils.d.ts +18 -4
  37. package/dist/features/column-sizing/columnSizingFeature.utils.js +49 -11
  38. package/dist/features/column-sizing/columnSizingFeature.utils.js.map +1 -1
  39. package/dist/features/column-visibility/columnVisibilityFeature.cjs +8 -4
  40. package/dist/features/column-visibility/columnVisibilityFeature.cjs.map +1 -1
  41. package/dist/features/column-visibility/columnVisibilityFeature.js +8 -4
  42. package/dist/features/column-visibility/columnVisibilityFeature.js.map +1 -1
  43. package/dist/features/column-visibility/columnVisibilityFeature.utils.cjs +3 -2
  44. package/dist/features/column-visibility/columnVisibilityFeature.utils.cjs.map +1 -1
  45. package/dist/features/column-visibility/columnVisibilityFeature.utils.js +3 -2
  46. package/dist/features/column-visibility/columnVisibilityFeature.utils.js.map +1 -1
  47. package/dist/features/row-selection/rowSelectionFeature.utils.cjs +1 -1
  48. package/dist/features/row-selection/rowSelectionFeature.utils.cjs.map +1 -1
  49. package/dist/features/row-selection/rowSelectionFeature.utils.js +1 -1
  50. package/dist/features/row-selection/rowSelectionFeature.utils.js.map +1 -1
  51. package/dist/index.d.cts +2 -2
  52. package/dist/index.d.ts +2 -2
  53. package/dist/static-functions.cjs +1 -0
  54. package/dist/static-functions.d.cts +2 -2
  55. package/dist/static-functions.d.ts +2 -2
  56. package/dist/static-functions.js +2 -2
  57. package/package.json +1 -1
  58. package/src/features/column-faceting/columnFacetingFeature.types.ts +6 -3
  59. package/src/features/column-faceting/columnFacetingFeature.utils.ts +53 -18
  60. package/src/features/column-faceting/createFacetedRowModel.ts +4 -0
  61. package/src/features/column-faceting/createFacetedUniqueValues.ts +5 -8
  62. package/src/features/column-ordering/columnOrderingFeature.ts +1 -0
  63. package/src/features/column-pinning/columnPinningFeature.ts +12 -0
  64. package/src/features/column-resizing/columnResizingFeature.utils.ts +96 -44
  65. package/src/features/column-sizing/columnSizingFeature.ts +18 -18
  66. package/src/features/column-sizing/columnSizingFeature.types.ts +39 -0
  67. package/src/features/column-sizing/columnSizingFeature.utils.ts +94 -39
  68. package/src/features/column-visibility/columnVisibilityFeature.ts +4 -0
  69. package/src/features/column-visibility/columnVisibilityFeature.utils.ts +4 -2
  70. package/src/features/row-selection/rowSelectionFeature.utils.ts +5 -1
@@ -1,3 +1,4 @@
1
+ import { makeObjectMap } from '../../utils'
1
2
  import type { CellData, RowData } from '../../types/type-utils'
2
3
  import type { TableFeatures } from '../../types/TableFeatures'
3
4
  import type { RowModel } from '../../core/row-models/coreRowModelsFeature.types'
@@ -23,9 +24,16 @@ export function column_getFacetedMinMaxValues<
23
24
  column: Column_Internal<TFeatures, TData, TValue>,
24
25
  table: Table_Internal<TFeatures, TData>,
25
26
  ): [number, number] | undefined {
26
- const facetedMinMaxValuesFn =
27
- table.options.features.facetedMinMaxValues?.(table, column.id) ??
28
- (() => undefined)
27
+ const facetedMinMaxValues = (table._rowModels.facetedMinMaxValues ??=
28
+ makeObjectMap())
29
+ let facetedMinMaxValuesFn = facetedMinMaxValues[column.id]
30
+
31
+ if (!facetedMinMaxValuesFn) {
32
+ facetedMinMaxValuesFn = facetedMinMaxValues[column.id] =
33
+ table.options.features.facetedMinMaxValues?.(table, column.id) ??
34
+ (() => undefined)
35
+ }
36
+
29
37
  return facetedMinMaxValuesFn()
30
38
  }
31
39
 
@@ -49,9 +57,17 @@ export function column_getFacetedRowModel<
49
57
  column: Column_Internal<TFeatures, TData, TValue> | undefined,
50
58
  table: Table_Internal<TFeatures, TData>,
51
59
  ): RowModel<TFeatures, TData> {
52
- const facetedRowModelFn =
53
- table.options.features.facetedRowModel?.(table, column?.id ?? '') ??
54
- (() => table.getPreFilteredRowModel())
60
+ const columnId = column?.id ?? ''
61
+ const facetedRowModels = (table._rowModels.facetedRowModels ??=
62
+ makeObjectMap())
63
+ let facetedRowModelFn = facetedRowModels[columnId]
64
+
65
+ if (!facetedRowModelFn) {
66
+ facetedRowModelFn = facetedRowModels[columnId] =
67
+ table.options.features.facetedRowModel?.(table, columnId) ??
68
+ (() => table.getPreFilteredRowModel())
69
+ }
70
+
55
71
  return facetedRowModelFn()
56
72
  }
57
73
 
@@ -74,9 +90,16 @@ export function column_getFacetedUniqueValues<
74
90
  column: Column_Internal<TFeatures, TData, TValue>,
75
91
  table: Table_Internal<TFeatures, TData>,
76
92
  ): Map<any, number> {
77
- const facetedUniqueValuesFn =
78
- table.options.features.facetedUniqueValues?.(table, column.id) ??
79
- (() => new Map<any, number>())
93
+ const facetedUniqueValues = (table._rowModels.facetedUniqueValues ??=
94
+ makeObjectMap())
95
+ let facetedUniqueValuesFn = facetedUniqueValues[column.id]
96
+
97
+ if (!facetedUniqueValuesFn) {
98
+ facetedUniqueValuesFn = facetedUniqueValues[column.id] =
99
+ table.options.features.facetedUniqueValues?.(table, column.id) ??
100
+ (() => new Map<any, number>())
101
+ }
102
+
80
103
  return facetedUniqueValuesFn()
81
104
  }
82
105
 
@@ -95,9 +118,13 @@ export function table_getGlobalFacetedMinMaxValues<
95
118
  TFeatures extends TableFeatures,
96
119
  TData extends RowData,
97
120
  >(table: Table_Internal<TFeatures, TData>): undefined | [number, number] {
98
- const facetedMinMaxValuesFn =
99
- table.options.features.facetedMinMaxValues?.(table, '__global__') ??
100
- (() => undefined)
121
+ if (!table._rowModels.globalFacetedMinMaxValues) {
122
+ table._rowModels.globalFacetedMinMaxValues =
123
+ table.options.features.facetedMinMaxValues?.(table, '__global__') ??
124
+ (() => undefined)
125
+ }
126
+
127
+ const facetedMinMaxValuesFn = table._rowModels.globalFacetedMinMaxValues
101
128
  return facetedMinMaxValuesFn()
102
129
  }
103
130
 
@@ -117,9 +144,13 @@ export function table_getGlobalFacetedRowModel<
117
144
  TFeatures extends TableFeatures,
118
145
  TData extends RowData,
119
146
  >(table: Table_Internal<TFeatures, TData>): RowModel<TFeatures, TData> {
120
- const facetedRowModelFn =
121
- table.options.features.facetedRowModel?.(table, '__global__') ??
122
- (() => table.getPreFilteredRowModel())
147
+ if (!table._rowModels.globalFacetedRowModel) {
148
+ table._rowModels.globalFacetedRowModel =
149
+ table.options.features.facetedRowModel?.(table, '__global__') ??
150
+ (() => table.getPreFilteredRowModel())
151
+ }
152
+
153
+ const facetedRowModelFn = table._rowModels.globalFacetedRowModel
123
154
  return facetedRowModelFn()
124
155
  }
125
156
 
@@ -138,8 +169,12 @@ export function table_getGlobalFacetedUniqueValues<
138
169
  TFeatures extends TableFeatures,
139
170
  TData extends RowData,
140
171
  >(table: Table_Internal<TFeatures, TData>): Map<any, number> {
141
- const facetedUniqueValuesFn =
142
- table.options.features.facetedUniqueValues?.(table, '__global__') ??
143
- (() => new Map())
172
+ if (!table._rowModels.globalFacetedUniqueValues) {
173
+ table._rowModels.globalFacetedUniqueValues =
174
+ table.options.features.facetedUniqueValues?.(table, '__global__') ??
175
+ (() => new Map())
176
+ }
177
+
178
+ const facetedUniqueValuesFn = table._rowModels.globalFacetedUniqueValues
144
179
  return facetedUniqueValuesFn()
145
180
  }
@@ -69,6 +69,10 @@ function _createFacetedRowModel<
69
69
  }
70
70
  if (globalFilter) filterableIds.push('__global__')
71
71
 
72
+ if (!filterableIds.length) {
73
+ return preRowModel
74
+ }
75
+
72
76
  const filterRowsImpl = (
73
77
  row: Row<TFeatures, TData> & Partial<Row_ColumnFiltering<TFeatures, TData>>,
74
78
  ) => {
@@ -51,14 +51,11 @@ function _createFacetedUniqueValues<
51
51
 
52
52
  for (let j = 0; j < values.length; j++) {
53
53
  const value = values[j]
54
- if (facetedUniqueValues.has(value)) {
55
- facetedUniqueValues.set(
56
- value,
57
- (facetedUniqueValues.get(value) ?? 0) + 1,
58
- )
59
- } else {
60
- facetedUniqueValues.set(value, 1)
61
- }
54
+ const previousValue = facetedUniqueValues.get(value)
55
+ facetedUniqueValues.set(
56
+ value,
57
+ previousValue === undefined ? 1 : previousValue + 1,
58
+ )
62
59
  }
63
60
  }
64
61
 
@@ -41,6 +41,7 @@ export const columnOrderingFeature: TableFeature = {
41
41
  column.table.atoms.columnPinning?.get(),
42
42
  column.table.atoms.grouping?.get(),
43
43
  column.table.atoms.columnVisibility?.get(),
44
+ column.table.options.groupedColumnMode,
44
45
  ],
45
46
  },
46
47
  column_getIsFirstColumn: {
@@ -257,6 +257,8 @@ export const columnPinningFeature: TableFeature = {
257
257
  table.options.columns,
258
258
  table.atoms.columnPinning?.get(),
259
259
  table.atoms.columnOrder?.get(),
260
+ table.atoms.grouping?.get(),
261
+ table.options.groupedColumnMode,
260
262
  ],
261
263
  },
262
264
  table_getRightLeafColumns: {
@@ -265,6 +267,8 @@ export const columnPinningFeature: TableFeature = {
265
267
  table.options.columns,
266
268
  table.atoms.columnPinning?.get(),
267
269
  table.atoms.columnOrder?.get(),
270
+ table.atoms.grouping?.get(),
271
+ table.options.groupedColumnMode,
268
272
  ],
269
273
  },
270
274
  table_getCenterLeafColumns: {
@@ -273,6 +277,8 @@ export const columnPinningFeature: TableFeature = {
273
277
  table.options.columns,
274
278
  table.atoms.columnPinning?.get(),
275
279
  table.atoms.columnOrder?.get(),
280
+ table.atoms.grouping?.get(),
281
+ table.options.groupedColumnMode,
276
282
  ],
277
283
  },
278
284
  table_getPinnedLeafColumns: {
@@ -287,6 +293,8 @@ export const columnPinningFeature: TableFeature = {
287
293
  table.atoms.columnPinning?.get(),
288
294
  table.atoms.columnVisibility?.get(),
289
295
  table.atoms.columnOrder?.get(),
296
+ table.atoms.grouping?.get(),
297
+ table.options.groupedColumnMode,
290
298
  ],
291
299
  },
292
300
  table_getCenterVisibleLeafColumns: {
@@ -296,6 +304,8 @@ export const columnPinningFeature: TableFeature = {
296
304
  table.atoms.columnPinning?.get(),
297
305
  table.atoms.columnVisibility?.get(),
298
306
  table.atoms.columnOrder?.get(),
307
+ table.atoms.grouping?.get(),
308
+ table.options.groupedColumnMode,
299
309
  ],
300
310
  },
301
311
  table_getRightVisibleLeafColumns: {
@@ -305,6 +315,8 @@ export const columnPinningFeature: TableFeature = {
305
315
  table.atoms.columnPinning?.get(),
306
316
  table.atoms.columnVisibility?.get(),
307
317
  table.atoms.columnOrder?.get(),
318
+ table.atoms.grouping?.get(),
319
+ table.options.groupedColumnMode,
308
320
  ],
309
321
  },
310
322
  table_getPinnedVisibleLeafColumns: {
@@ -130,60 +130,112 @@ export function header_getResizeHandler<
130
130
  return
131
131
  }
132
132
 
133
- table_setColumnResizing(column.table, (old) => {
134
- const deltaDirection =
135
- column.table.options.columnResizeDirection === 'rtl' ? -1 : 1
136
- const deltaOffset =
137
- (clientXPos - (old.startOffset ?? 0)) * deltaDirection
138
- const startSize = old.startSize ?? 0
139
- const deltaPercentage = Math.max(
140
- startSize > 0 ? deltaOffset / startSize : 0,
141
- -0.999999,
142
- )
143
-
144
- old.columnSizingStart.forEach(([columnId, headerSize]) => {
145
- newColumnSizing[columnId] =
146
- Math.round(
147
- Math.max(
148
- headerSize > 0
149
- ? headerSize + headerSize * deltaPercentage
150
- : deltaOffset / old.columnSizingStart.length,
151
- 0,
152
- ) * 100,
153
- ) / 100
133
+ const table = column.table
134
+ const isCommit =
135
+ table.options.columnResizeMode === 'onChange' || eventType === 'end'
136
+
137
+ // one notification flush per tick instead of two
138
+ table._reactivity.batch(() => {
139
+ table_setColumnResizing(table, (old) => {
140
+ const deltaDirection =
141
+ table.options.columnResizeDirection === 'rtl' ? -1 : 1
142
+ const deltaOffset =
143
+ (clientXPos - (old.startOffset ?? 0)) * deltaDirection
144
+ const startSize = old.startSize ?? 0
145
+ const deltaPercentage = Math.max(
146
+ startSize > 0 ? deltaOffset / startSize : 0,
147
+ -0.999999,
148
+ )
149
+
150
+ // new sizes are only read at commit and are recomputed from
151
+ // absolute positions each time, so skip them on onEnd-mode moves
152
+ if (isCommit) {
153
+ const columnSizingStart = old.columnSizingStart
154
+ for (let i = 0; i < columnSizingStart.length; i++) {
155
+ const entry = columnSizingStart[i]!
156
+ const headerSize = entry[1]
157
+ newColumnSizing[entry[0]] =
158
+ Math.round(
159
+ Math.max(
160
+ headerSize > 0
161
+ ? headerSize + headerSize * deltaPercentage
162
+ : deltaOffset / columnSizingStart.length,
163
+ 0,
164
+ ) * 100,
165
+ ) / 100
166
+ }
167
+ }
168
+
169
+ return {
170
+ ...old,
171
+ deltaOffset,
172
+ deltaPercentage,
173
+ }
154
174
  })
155
175
 
156
- return {
157
- ...old,
158
- deltaOffset,
159
- deltaPercentage,
176
+ if (isCommit) {
177
+ table_setColumnSizing(table, (old) =>
178
+ Object.assign(makeObjectMap<number>(), old, newColumnSizing),
179
+ )
160
180
  }
161
181
  })
182
+ }
162
183
 
163
- if (
164
- column.table.options.columnResizeMode === 'onChange' ||
165
- eventType === 'end'
166
- ) {
167
- table_setColumnSizing(column.table, (old) =>
168
- Object.assign(makeObjectMap<number>(), old, newColumnSizing),
169
- )
184
+ // Pointer devices can report at several times the display refresh rate,
185
+ // and every update between frames is overwritten by the next one. Apply
186
+ // the first move of a frame immediately, then coalesce the rest into one
187
+ // update per animation frame.
188
+ let moveRafId: number | null = null
189
+ let hasPendingMove = false
190
+ let latestMoveX: number | undefined
191
+
192
+ const flushMove = () => {
193
+ if (hasPendingMove) {
194
+ hasPendingMove = false
195
+ updateOffset('move', latestMoveX)
196
+ moveRafId = requestAnimationFrame(flushMove)
197
+ } else {
198
+ moveRafId = null
170
199
  }
171
200
  }
172
201
 
173
- const onMove = (clientXPos?: number) => updateOffset('move', clientXPos)
202
+ const onMove = (clientXPos?: number) => {
203
+ latestMoveX = clientXPos
204
+ if (typeof requestAnimationFrame !== 'function') {
205
+ updateOffset('move', clientXPos)
206
+ return
207
+ }
208
+ if (moveRafId !== null) {
209
+ hasPendingMove = true
210
+ return
211
+ }
212
+ updateOffset('move', clientXPos)
213
+ moveRafId = requestAnimationFrame(flushMove)
214
+ }
174
215
 
175
216
  const onEnd = (clientXPos?: number) => {
176
- updateOffset('end', clientXPos)
177
-
178
- table_setColumnResizing(column.table, (old) => ({
179
- ...old,
180
- isResizingColumn: false,
181
- startOffset: null,
182
- startSize: null,
183
- deltaOffset: null,
184
- deltaPercentage: null,
185
- columnSizingStart: [],
186
- }))
217
+ if (moveRafId !== null) {
218
+ cancelAnimationFrame(moveRafId)
219
+ moveRafId = null
220
+ hasPendingMove = false
221
+ }
222
+
223
+ // batch the 'end' commit and the reset into one notification flush
224
+ column.table._reactivity.batch(() => {
225
+ // touchend events carry no touch position, so fall back to the last
226
+ // observed move position for the final commit
227
+ updateOffset('end', clientXPos ?? latestMoveX)
228
+
229
+ table_setColumnResizing(column.table, (old) => ({
230
+ ...old,
231
+ isResizingColumn: false,
232
+ startOffset: null,
233
+ startSize: null,
234
+ deltaOffset: null,
235
+ deltaPercentage: null,
236
+ columnSizingStart: [],
237
+ }))
238
+ })
187
239
  }
188
240
 
189
241
  const contextDocument =
@@ -13,6 +13,7 @@ import {
13
13
  header_getSize,
14
14
  header_getStart,
15
15
  table_getCenterTotalSize,
16
+ table_getColumnOffsets,
16
17
  table_getLeftTotalSize,
17
18
  table_getRightTotalSize,
18
19
  table_getTotalSize,
@@ -51,27 +52,13 @@ export const columnSizingFeature: TableFeature = {
51
52
  table.atoms.columnSizing?.get()?.[column.id], // just this column's size state
52
53
  ],
53
54
  },
55
+ // O(1) lookups into the memoized table-level offsets, so no per-column
56
+ // memos here
54
57
  column_getStart: {
55
58
  fn: (column, position) => column_getStart(column, position),
56
- memoDeps: (column, position) => [
57
- position,
58
- table.options.columns,
59
- table.atoms.columnSizing?.get(),
60
- table.atoms.columnOrder?.get(),
61
- table.atoms.columnPinning?.get(),
62
- table.atoms.columnVisibility?.get(),
63
- ],
64
59
  },
65
60
  column_getAfter: {
66
61
  fn: (column, position) => column_getAfter(column, position),
67
- memoDeps: (column, position) => [
68
- position,
69
- table.options.columns,
70
- table.atoms.columnSizing?.get(),
71
- table.atoms.columnOrder?.get(),
72
- table.atoms.columnPinning?.get(),
73
- table.atoms.columnVisibility?.get(),
74
- ],
75
62
  },
76
63
  column_resetSize: {
77
64
  fn: (column) => column_resetSize(column),
@@ -92,13 +79,14 @@ export const columnSizingFeature: TableFeature = {
92
79
  },
93
80
  header_getStart: {
94
81
  fn: (header) => header_getStart(header),
95
- memoDeps: (header, position) => [
96
- position,
82
+ memoDeps: () => [
97
83
  table.options.columns,
98
84
  table.atoms.columnSizing?.get(),
99
85
  table.atoms.columnOrder?.get(),
100
86
  table.atoms.columnPinning?.get(),
101
87
  table.atoms.columnVisibility?.get(),
88
+ table.atoms.grouping?.get(),
89
+ table.options.groupedColumnMode,
102
90
  ],
103
91
  },
104
92
  })
@@ -106,6 +94,18 @@ export const columnSizingFeature: TableFeature = {
106
94
 
107
95
  constructTableAPIs: (table) => {
108
96
  assignTableAPIs('columnSizingFeature', table, {
97
+ table_getColumnOffsets: {
98
+ fn: () => table_getColumnOffsets(table),
99
+ memoDeps: () => [
100
+ table.options.columns,
101
+ table.atoms.columnSizing?.get(),
102
+ table.atoms.columnOrder?.get(),
103
+ table.atoms.columnPinning?.get(),
104
+ table.atoms.columnVisibility?.get(),
105
+ table.atoms.grouping?.get(),
106
+ table.options.groupedColumnMode,
107
+ ],
108
+ },
109
109
  table_setColumnSizing: {
110
110
  fn: (updater) => table_setColumnSizing(table, updater),
111
111
  },
@@ -7,6 +7,38 @@ export interface TableState_ColumnSizing {
7
7
 
8
8
  export type ColumnSizingState = Record<string, number>
9
9
 
10
+ export interface ColumnOffsets {
11
+ /**
12
+ * Offset from the start edge of the region to each column's start edge,
13
+ * keyed by column id.
14
+ */
15
+ starts: Record<string, number>
16
+ /**
17
+ * Offset from the end edge of the region to each column's end edge, keyed by
18
+ * column id.
19
+ */
20
+ afters: Record<string, number>
21
+ }
22
+
23
+ export interface ColumnOffsetsByPosition {
24
+ /**
25
+ * Offsets across all visible leaf columns, ignoring pinning regions.
26
+ */
27
+ all: ColumnOffsets
28
+ /**
29
+ * Offsets within the center (unpinned) region.
30
+ */
31
+ center: ColumnOffsets
32
+ /**
33
+ * Offsets within the left pinned region.
34
+ */
35
+ left: ColumnOffsets
36
+ /**
37
+ * Offsets within the right pinned region.
38
+ */
39
+ right: ColumnOffsets
40
+ }
41
+
10
42
  export interface TableOptions_ColumnSizing {
11
43
  /**
12
44
  * Called with an updater when committed column sizing state changes. Pair
@@ -26,6 +58,13 @@ export interface Table_ColumnSizing {
26
58
  * Sums the current sizes of visible center-region leaf columns.
27
59
  */
28
60
  getCenterTotalSize: () => number
61
+ /**
62
+ * Returns memoized column offset maps (start and after offsets keyed by
63
+ * column id) for each pinning region plus the full visible leaf column list.
64
+ *
65
+ * Backs `column.getStart()` and `column.getAfter()` with O(1) lookups.
66
+ */
67
+ getColumnOffsets: () => ColumnOffsetsByPosition
29
68
  /**
30
69
  * Sums the current sizes of visible left-pinned leaf columns.
31
70
  */
@@ -4,7 +4,6 @@ import {
4
4
  table_getPinnedVisibleLeafColumns,
5
5
  table_getRightHeaderGroups,
6
6
  } from '../column-pinning/columnPinningFeature.utils'
7
- import { column_getIndex } from '../column-ordering/columnOrderingFeature.utils'
8
7
  import {
9
8
  callMemoOrStaticFn,
10
9
  cloneState,
@@ -17,7 +16,11 @@ import type { TableFeatures } from '../../types/TableFeatures'
17
16
  import type { Table_Internal } from '../../types/Table'
18
17
  import type { Header } from '../../types/Header'
19
18
  import type { Column_Internal } from '../../types/Column'
20
- import type { ColumnSizingState } from './columnSizingFeature.types'
19
+ import type {
20
+ ColumnOffsets,
21
+ ColumnOffsetsByPosition,
22
+ ColumnSizingState,
23
+ } from './columnSizingFeature.types'
21
24
 
22
25
  /**
23
26
  * Creates the default committed column sizing state.
@@ -86,6 +89,85 @@ export function column_getSize<
86
89
  )
87
90
  }
88
91
 
92
+ function buildColumnOffsets<
93
+ TFeatures extends TableFeatures,
94
+ TData extends RowData,
95
+ >(columns: Array<Column_Internal<TFeatures, TData, unknown>>): ColumnOffsets {
96
+ const starts = makeObjectMap<number>()
97
+ const afters = makeObjectMap<number>()
98
+ const sizes = new Array<number>(columns.length)
99
+
100
+ let start = 0
101
+ for (let i = 0; i < columns.length; i++) {
102
+ const column = columns[i]!
103
+ const size = callMemoOrStaticFn(column, 'getSize', column_getSize)
104
+ sizes[i] = size
105
+ starts[column.id] = start
106
+ start += size
107
+ }
108
+
109
+ let after = 0
110
+ for (let i = columns.length - 1; i >= 0; i--) {
111
+ afters[columns[i]!.id] = after
112
+ after += sizes[i]!
113
+ }
114
+
115
+ return { starts, afters }
116
+ }
117
+
118
+ /**
119
+ * Builds start and after offset maps for every visible leaf column, computed
120
+ * once per pinning region plus the full visible list.
121
+ *
122
+ * A single table-level memo of this result backs all `column.getStart()` and
123
+ * `column.getAfter()` calls with O(1) lookups.
124
+ *
125
+ * @example
126
+ * ```ts
127
+ * const offsets = table_getColumnOffsets(table)
128
+ * const leftOffset = offsets.left.starts[column.id]
129
+ * ```
130
+ */
131
+ export function table_getColumnOffsets<
132
+ TFeatures extends TableFeatures,
133
+ TData extends RowData,
134
+ >(table: Table_Internal<TFeatures, TData>): ColumnOffsetsByPosition {
135
+ return {
136
+ all: buildColumnOffsets(
137
+ table_getPinnedVisibleLeafColumns(table) as Array<
138
+ Column_Internal<TFeatures, TData, unknown>
139
+ >,
140
+ ),
141
+ center: buildColumnOffsets(
142
+ table_getPinnedVisibleLeafColumns(table, 'center') as Array<
143
+ Column_Internal<TFeatures, TData, unknown>
144
+ >,
145
+ ),
146
+ left: buildColumnOffsets(
147
+ table_getPinnedVisibleLeafColumns(table, 'left') as Array<
148
+ Column_Internal<TFeatures, TData, unknown>
149
+ >,
150
+ ),
151
+ right: buildColumnOffsets(
152
+ table_getPinnedVisibleLeafColumns(table, 'right') as Array<
153
+ Column_Internal<TFeatures, TData, unknown>
154
+ >,
155
+ ),
156
+ }
157
+ }
158
+
159
+ function toOffsetsKey(
160
+ position: ColumnPinningPosition | 'center' | undefined,
161
+ ): keyof ColumnOffsetsByPosition {
162
+ return position === 'left'
163
+ ? 'left'
164
+ : position === 'right'
165
+ ? 'right'
166
+ : position === 'center'
167
+ ? 'center'
168
+ : 'all' // undefined | false use the full visible list
169
+ }
170
+
89
171
  /**
90
172
  * Computes the offset from the start edge of a pinning region to this column.
91
173
  *
@@ -103,28 +185,14 @@ export function column_getStart<
103
185
  TValue extends CellData = CellData,
104
186
  >(
105
187
  column: Column_Internal<TFeatures, TData, TValue>,
106
- position: ColumnPinningPosition | 'center',
188
+ position?: ColumnPinningPosition | 'center',
107
189
  ): number {
108
- const index = callMemoOrStaticFn(
109
- column,
110
- 'getIndex',
111
- column_getIndex,
112
- position,
113
- )
114
- if (index <= 0) return 0
115
-
116
- const visibleLeafColumns = callMemoOrStaticFn(
190
+ const offsets = callMemoOrStaticFn(
117
191
  column.table,
118
- 'getPinnedVisibleLeafColumns',
119
- table_getPinnedVisibleLeafColumns,
120
- position,
121
- )
122
-
123
- const prevColumn = visibleLeafColumns[index - 1]!
124
- return (
125
- callMemoOrStaticFn(prevColumn, 'getStart', column_getStart, position) +
126
- callMemoOrStaticFn(prevColumn, 'getSize', column_getSize)
192
+ 'getColumnOffsets',
193
+ table_getColumnOffsets,
127
194
  )
195
+ return offsets[toOffsetsKey(position)].starts[column.id] ?? 0
128
196
  }
129
197
 
130
198
  /**
@@ -144,27 +212,14 @@ export function column_getAfter<
144
212
  TValue extends CellData = CellData,
145
213
  >(
146
214
  column: Column_Internal<TFeatures, TData, TValue>,
147
- position: ColumnPinningPosition | 'center',
215
+ position?: ColumnPinningPosition | 'center',
148
216
  ): number {
149
- const visibleLeafColumns = callMemoOrStaticFn(
217
+ const offsets = callMemoOrStaticFn(
150
218
  column.table,
151
- 'getPinnedVisibleLeafColumns',
152
- table_getPinnedVisibleLeafColumns,
153
- position,
154
- )
155
- const index = callMemoOrStaticFn(
156
- column,
157
- 'getIndex',
158
- column_getIndex,
159
- position,
160
- )
161
- if (index < 0 || index >= visibleLeafColumns.length - 1) return 0
162
-
163
- const nextColumn = visibleLeafColumns[index + 1]!
164
- return (
165
- callMemoOrStaticFn(nextColumn, 'getSize', column_getSize) +
166
- callMemoOrStaticFn(nextColumn, 'getAfter', column_getAfter, position)
219
+ 'getColumnOffsets',
220
+ table_getColumnOffsets,
167
221
  )
222
+ return offsets[toOffsetsKey(position)].afters[column.id] ?? 0
168
223
  }
169
224
 
170
225
  /**