@tanstack/table-core 9.0.0-beta.42 → 9.0.0-beta.44

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 (81) hide show
  1. package/dist/core/rows/constructRow.cjs +1 -0
  2. package/dist/core/rows/constructRow.cjs.map +1 -1
  3. package/dist/core/rows/constructRow.js +1 -0
  4. package/dist/core/rows/constructRow.js.map +1 -1
  5. package/dist/core/rows/coreRowsFeature.cjs +9 -0
  6. package/dist/core/rows/coreRowsFeature.cjs.map +1 -1
  7. package/dist/core/rows/coreRowsFeature.js +10 -1
  8. package/dist/core/rows/coreRowsFeature.js.map +1 -1
  9. package/dist/core/rows/coreRowsFeature.types.d.cts +24 -0
  10. package/dist/core/rows/coreRowsFeature.types.d.ts +24 -0
  11. package/dist/core/rows/coreRowsFeature.utils.cjs +34 -0
  12. package/dist/core/rows/coreRowsFeature.utils.cjs.map +1 -1
  13. package/dist/core/rows/coreRowsFeature.utils.d.cts +15 -1
  14. package/dist/core/rows/coreRowsFeature.utils.d.ts +15 -1
  15. package/dist/core/rows/coreRowsFeature.utils.js +33 -1
  16. package/dist/core/rows/coreRowsFeature.utils.js.map +1 -1
  17. package/dist/core/table/constructTable.cjs +1 -0
  18. package/dist/core/table/constructTable.cjs.map +1 -1
  19. package/dist/core/table/constructTable.js +1 -0
  20. package/dist/core/table/constructTable.js.map +1 -1
  21. package/dist/core/table/coreTablesFeature.types.d.cts +2 -1
  22. package/dist/core/table/coreTablesFeature.types.d.ts +2 -1
  23. package/dist/core/table/coreTablesFeature.utils.cjs +4 -1
  24. package/dist/core/table/coreTablesFeature.utils.cjs.map +1 -1
  25. package/dist/core/table/coreTablesFeature.utils.d.cts +2 -1
  26. package/dist/core/table/coreTablesFeature.utils.d.ts +2 -1
  27. package/dist/core/table/coreTablesFeature.utils.js +4 -1
  28. package/dist/core/table/coreTablesFeature.utils.js.map +1 -1
  29. package/dist/features/row-selection/rowSelectionFeature.cjs +13 -2
  30. package/dist/features/row-selection/rowSelectionFeature.cjs.map +1 -1
  31. package/dist/features/row-selection/rowSelectionFeature.js +13 -2
  32. package/dist/features/row-selection/rowSelectionFeature.js.map +1 -1
  33. package/dist/features/row-selection/rowSelectionFeature.types.d.cts +35 -5
  34. package/dist/features/row-selection/rowSelectionFeature.types.d.ts +35 -5
  35. package/dist/features/row-selection/rowSelectionFeature.utils.cjs +50 -3
  36. package/dist/features/row-selection/rowSelectionFeature.utils.cjs.map +1 -1
  37. package/dist/features/row-selection/rowSelectionFeature.utils.d.cts +8 -6
  38. package/dist/features/row-selection/rowSelectionFeature.utils.d.ts +8 -6
  39. package/dist/features/row-selection/rowSelectionFeature.utils.js +50 -3
  40. package/dist/features/row-selection/rowSelectionFeature.utils.js.map +1 -1
  41. package/dist/index.d.cts +2 -2
  42. package/dist/index.d.ts +2 -2
  43. package/dist/static-functions.cjs +2 -0
  44. package/dist/static-functions.d.cts +2 -2
  45. package/dist/static-functions.d.ts +2 -2
  46. package/dist/static-functions.js +2 -2
  47. package/dist/types/TableFeatures.d.cts +22 -2
  48. package/dist/types/TableFeatures.d.ts +22 -2
  49. package/package.json +1 -1
  50. package/skills/api-not-found/SKILL.md +1 -1
  51. package/skills/client-vs-server/SKILL.md +1 -1
  52. package/skills/column-faceting/SKILL.md +1 -1
  53. package/skills/column-filtering/SKILL.md +1 -1
  54. package/skills/column-ordering/SKILL.md +1 -1
  55. package/skills/column-pinning/SKILL.md +1 -1
  56. package/skills/column-resizing/SKILL.md +1 -1
  57. package/skills/column-sizing/SKILL.md +1 -1
  58. package/skills/column-visibility/SKILL.md +1 -1
  59. package/skills/core/SKILL.md +37 -2
  60. package/skills/custom-features/SKILL.md +30 -2
  61. package/skills/expanding/SKILL.md +1 -1
  62. package/skills/global-filtering/SKILL.md +1 -1
  63. package/skills/grouping/SKILL.md +1 -1
  64. package/skills/migrate-v8-to-v9/SKILL.md +1 -1
  65. package/skills/pagination/SKILL.md +1 -1
  66. package/skills/row-pinning/SKILL.md +1 -1
  67. package/skills/row-selection/SKILL.md +91 -3
  68. package/skills/sorting/SKILL.md +1 -1
  69. package/skills/table-features/SKILL.md +1 -1
  70. package/skills/typescript/SKILL.md +1 -1
  71. package/src/core/rows/constructRow.ts +1 -0
  72. package/src/core/rows/coreRowsFeature.ts +15 -0
  73. package/src/core/rows/coreRowsFeature.types.ts +24 -0
  74. package/src/core/rows/coreRowsFeature.utils.ts +58 -0
  75. package/src/core/table/constructTable.ts +4 -0
  76. package/src/core/table/coreTablesFeature.types.ts +2 -1
  77. package/src/core/table/coreTablesFeature.utils.ts +7 -1
  78. package/src/features/row-selection/rowSelectionFeature.ts +19 -1
  79. package/src/features/row-selection/rowSelectionFeature.types.ts +37 -3
  80. package/src/features/row-selection/rowSelectionFeature.utils.ts +114 -14
  81. package/src/types/TableFeatures.ts +32 -2
@@ -1,12 +1,12 @@
1
1
  ---
2
2
  name: row-selection
3
3
  description: >
4
- Maintain rowSelection ID state with stable getRowId, single, multi, and subrow rules, selected row models, and manual-pagination semantics. Load when selected IDs outlive loaded Row objects or data removal.
4
+ Maintain rowSelection ID state with stable getRowId, single, multi, subrow, and Shift-range rules, selected row models, handler anchors, and manual-pagination semantics. Load when implementing getToggleSelectedHandler, enableRowRangeSelection, selectChildren, or selected IDs that outlive loaded Row objects.
5
5
  metadata:
6
6
  {
7
7
  type: sub-skill,
8
8
  library: '@tanstack/table-core',
9
- library_version: '9.0.0-beta.42',
9
+ library_version: '9.0.0-beta.44',
10
10
  }
11
11
  requires: ['core', 'table-features']
12
12
  sources:
@@ -20,7 +20,11 @@ This skill builds on `core` and `table-features`. Selection is independent ID st
20
20
  ## Setup
21
21
 
22
22
  ```ts
23
- import { rowSelectionFeature, tableFeatures } from '@tanstack/table-core'
23
+ import {
24
+ rowSelectionFeature,
25
+ tableFeatures,
26
+ type Row,
27
+ } from '@tanstack/table-core'
24
28
 
25
29
  type Person = { id: string; name: string }
26
30
  export const features = tableFeatures({ rowSelectionFeature })
@@ -39,6 +43,28 @@ const loadedSelectedRows = table.getSelectedRowModel().rows
39
43
 
40
44
  Use IDs for database-wide intent and row models for currently loaded objects.
41
45
 
46
+ ### Inclusive Shift ranges through the row handler
47
+
48
+ ```ts
49
+ export function getSelectionHandler(row: Row<typeof features, Person>) {
50
+ return row.getToggleSelectedHandler()
51
+ }
52
+ ```
53
+
54
+ The handler establishes a table-local anchor on ordinary interactions and applies the checked value to the inclusive current display-order range on Shift interactions. Range behavior is enabled by default; set `enableRowRangeSelection: false` to preserve non-range handler behavior. Direct `row.toggleSelected()` and `table.setRowSelection()` calls do not move that anchor.
55
+
56
+ Pass the original checkbox click event to this handler. DOM `change` events often omit modifier keys, so use the framework's click binding for row checkboxes unless its change event exposes the original click through `nativeEvent` (as React does).
57
+
58
+ ### Limit a range to explicitly displayed rows
59
+
60
+ ```ts
61
+ export function getDisplayedRowsOnlyHandler(row: Row<typeof features, Person>) {
62
+ return row.getToggleSelectedHandler({ selectChildren: false })
63
+ }
64
+ ```
65
+
66
+ The default `selectChildren: true` recursively changes selectable descendants of parents encountered in the range. Set it to `false` when collapsed descendants outside the display-order interval must remain unchanged.
67
+
42
68
  ## Common Mistakes
43
69
 
44
70
  ### [HIGH] Expecting selection to clean itself
@@ -71,6 +97,68 @@ Under manual pagination, unloaded selected IDs have no `Row` object in the curre
71
97
 
72
98
  Source: `docs/framework/react/guide/row-selection.md#note-if-you-are-using-manualpagination`
73
99
 
100
+ ### [HIGH] Bypassing the range-selection handler
101
+
102
+ Wrong:
103
+
104
+ ```ts
105
+ const onChange = (event: { target: { checked: boolean } }) =>
106
+ row.toggleSelected(event.target.checked)
107
+ ```
108
+
109
+ Correct:
110
+
111
+ ```ts
112
+ const onChange = row.getToggleSelectedHandler()
113
+ ```
114
+
115
+ Only successful interactions through `getToggleSelectedHandler()` establish or advance the Shift-range anchor. The handler also supports custom range-event detection through `isRowRangeSelectionEvent`.
116
+
117
+ Source: `docs/framework/react/guide/row-selection.md#shift-range-selection`
118
+
119
+ ### [HIGH] Binding a DOM change event that drops Shift
120
+
121
+ Wrong:
122
+
123
+ ```ts
124
+ checkbox.addEventListener('change', row.getToggleSelectedHandler())
125
+ ```
126
+
127
+ Correct:
128
+
129
+ ```ts
130
+ checkbox.addEventListener('click', row.getToggleSelectedHandler())
131
+ ```
132
+
133
+ The range modifier must be present on the event passed to the handler. Raw DOM `change` events do not reliably expose click modifier keys.
134
+
135
+ Source: `docs/framework/svelte/guide/row-selection.md#shift-range-selection`
136
+
137
+ ### [MEDIUM] Expecting ranges across unloaded server pages
138
+
139
+ Wrong:
140
+
141
+ ```ts
142
+ const options = {
143
+ manualPagination: true,
144
+ enableRowRangeSelection: true,
145
+ }
146
+ // Shift cannot select rows absent from data.
147
+ ```
148
+
149
+ Correct:
150
+
151
+ ```ts
152
+ const options = {
153
+ manualPagination: true,
154
+ getRowId: (row: Person) => row.id,
155
+ }
156
+ ```
157
+
158
+ Client-side ranges can cross pages because display order is pre-pagination. Manual/server pagination cannot include rows absent from the loaded `data`; select database-wide IDs in application state when that behavior is required.
159
+
160
+ Source: `docs/framework/react/guide/row-selection.md#shift-range-selection`, `https://github.com/TanStack/table/issues/4781`
161
+
74
162
  ## API Discovery
75
163
 
76
164
  Inspect `node_modules/@tanstack/table-core/src/features/row-selection/` for state, row-model variants, and selection enablement callbacks.
@@ -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.42',
9
+ library_version: '9.0.0-beta.44',
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.42'
8
+ library_version: '9.0.0-beta.44'
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.42'
8
+ library_version: '9.0.0-beta.44'
9
9
  requires: ['core', 'table-features']
10
10
  sources:
11
11
  - 'TanStack/table:docs/guide/helpers.md'
@@ -48,6 +48,7 @@ export const constructRow = <
48
48
  >
49
49
 
50
50
  // Only assign instance-specific properties
51
+ row._displayIndexCache = -1
51
52
  row._uniqueValuesCache = makeObjectMap()
52
53
  row._valuesCache = makeObjectMap()
53
54
  row.depth = depth
@@ -2,6 +2,7 @@ import { assignPrototypeAPIs, assignTableAPIs } from '../../utils'
2
2
  import {
3
3
  row_getAllCells,
4
4
  row_getAllCellsByColumnId,
5
+ row_getDisplayIndex,
5
6
  row_getLeafRows,
6
7
  row_getParentRow,
7
8
  row_getParentRows,
@@ -10,6 +11,7 @@ import {
10
11
  row_renderValue,
11
12
  table_getRow,
12
13
  table_getRowId,
14
+ table_getRowsInDisplayOrder,
13
15
  } from './coreRowsFeature.utils'
14
16
  import type { TableFeature } from '../../types/TableFeatures'
15
17
 
@@ -19,6 +21,9 @@ import type { TableFeature } from '../../types/TableFeatures'
19
21
  export const coreRowsFeature: TableFeature = {
20
22
  assignRowPrototype: (prototype, table) => {
21
23
  assignPrototypeAPIs('coreRowsFeature', prototype, table, {
24
+ row_getDisplayIndex: {
25
+ fn: (row) => row_getDisplayIndex(row),
26
+ },
22
27
  row_getAllCellsByColumnId: {
23
28
  fn: (row) => row_getAllCellsByColumnId(row),
24
29
  memoDeps: (row) => [row.getAllCells()],
@@ -50,6 +55,16 @@ export const coreRowsFeature: TableFeature = {
50
55
  },
51
56
  constructTableAPIs: (table) => {
52
57
  assignTableAPIs('coreRowsFeature', table, {
58
+ table_getRowsInDisplayOrder: {
59
+ fn: () => table_getRowsInDisplayOrder(table),
60
+ memoDeps: () => [
61
+ table.getPrePaginatedRowModel().rows,
62
+ table.options.paginateExpandedRows,
63
+ table.options.paginateExpandedRows === false
64
+ ? table.atoms.expanded?.get()
65
+ : undefined,
66
+ ],
67
+ },
53
68
  table_getRowId: {
54
69
  fn: (originalRow, index, parent) =>
55
70
  table_getRowId(originalRow, table, index, parent),
@@ -13,6 +13,16 @@ export interface Row_CoreProperties<
13
13
  Column<TFeatures, TData, unknown>,
14
14
  Cell<TFeatures, TData, unknown>
15
15
  >
16
+ /**
17
+ * Internal cache used while resolving the current display order.
18
+ *
19
+ * This value may be stale until display order is recomputed. Use
20
+ * `row.getDisplayIndex()` instead; it refreshes and validates the cached
21
+ * position before returning it.
22
+ *
23
+ * @internal
24
+ */
25
+ _displayIndexCache: number
16
26
  _uniqueValuesCache: Record<string, unknown>
17
27
  _valuesCache: Record<string, unknown>
18
28
  /**
@@ -53,6 +63,13 @@ export interface Row_Row<
53
63
  in out TFeatures extends TableFeatures,
54
64
  in out TData extends RowData,
55
65
  > extends Row_CoreProperties<TFeatures, TData> {
66
+ /**
67
+ * Returns the zero-based index of the row in the current display order
68
+ * before pagination, or `-1` if the row is not in that model. Use this for
69
+ * display row-number columns instead of `row.index` or the internal
70
+ * `_displayIndexCache` field.
71
+ */
72
+ getDisplayIndex: () => number
56
73
  /**
57
74
  * Builds a lookup of this row's cells keyed by leaf column id.
58
75
  */
@@ -114,6 +131,13 @@ export interface Table_Rows<
114
131
  in out TFeatures extends TableFeatures,
115
132
  in out TData extends RowData,
116
133
  > {
134
+ /**
135
+ * Returns the rows in the current display order and assigns their display
136
+ * indexes. When expanded rows bypass pagination, expanded descendants are
137
+ * included in this order. This is the memoized source for
138
+ * `row.getDisplayIndex()`.
139
+ */
140
+ getRowsInDisplayOrder: () => Array<Row<TFeatures, TData>>
117
141
  getRowId: (_: TData, index: number, parent?: Row<TFeatures, TData>) => string
118
142
  /**
119
143
  * Returns the row with the given ID.
@@ -5,6 +5,64 @@ import type { RowData } from '../../types/type-utils'
5
5
  import type { TableFeatures } from '../../types/TableFeatures'
6
6
  import type { Row } from '../../types/Row'
7
7
  import type { Cell } from '../../types/Cell'
8
+ import type { Row_RowExpanding } from '../../features/row-expanding/rowExpandingFeature.types'
9
+
10
+ /**
11
+ * Returns this row's zero-based position in the current pre-pagination row
12
+ * model. Rows outside that model return `-1`.
13
+ */
14
+ export function row_getDisplayIndex<
15
+ TFeatures extends TableFeatures,
16
+ TData extends RowData,
17
+ >(row: Row<TFeatures, TData>) {
18
+ const rows = row.table.getRowsInDisplayOrder()
19
+ const displayIndex = row._displayIndexCache
20
+
21
+ return rows[displayIndex] === row ? displayIndex : -1
22
+ }
23
+
24
+ /**
25
+ * Returns the rows in the current display order after assigning their
26
+ * zero-based display indexes.
27
+ *
28
+ * When expanded rows bypass pagination, expanded descendants are inserted into
29
+ * the returned order even though they are absent from the pre-pagination row
30
+ * model.
31
+ */
32
+ export function table_getRowsInDisplayOrder<
33
+ TFeatures extends TableFeatures,
34
+ TData extends RowData,
35
+ >(table: Table_Internal<TFeatures, TData>) {
36
+ const rows = table.getPrePaginatedRowModel().rows
37
+
38
+ if (table.options.paginateExpandedRows === false) {
39
+ const displayRows: Array<Row<TFeatures, TData>> = []
40
+
41
+ const handleRow = (row: Row<TFeatures, TData>) => {
42
+ row._displayIndexCache = displayRows.length
43
+ displayRows.push(row)
44
+
45
+ if (
46
+ row.subRows.length &&
47
+ (
48
+ row as Row<TFeatures, TData> & Partial<Row_RowExpanding>
49
+ ).getIsExpanded?.()
50
+ ) {
51
+ row.subRows.forEach(handleRow)
52
+ }
53
+ }
54
+
55
+ rows.forEach(handleRow)
56
+
57
+ return displayRows
58
+ }
59
+
60
+ for (let i = 0; i < rows.length; i++) {
61
+ rows[i]!._displayIndexCache = i
62
+ }
63
+
64
+ return rows
65
+ }
8
66
 
9
67
  /**
10
68
  * Reads and caches this row's value for a column.
@@ -166,6 +166,10 @@ export function constructTable<
166
166
  ),
167
167
  )
168
168
 
169
+ for (let i = 0; i < featuresList.length; i++) {
170
+ featuresList[i]!.initTableInstanceData?.(table)
171
+ }
172
+
169
173
  if (
170
174
  process.env.NODE_ENV === 'development' &&
171
175
  (tableOptions.debugAll || tableOptions.debugTable)
@@ -229,7 +229,8 @@ export interface Table_Table<
229
229
  *
230
230
  * Prefer feature-specific reset APIs, such as `resetPagination`, when a state
231
231
  * slice may be owned by an external atom or needs that feature's blank/default
232
- * reset behavior.
232
+ * reset behavior. After resetting internal atoms, this also invokes feature
233
+ * reset hooks for mutable, transient table-instance data.
233
234
  */
234
235
  reset: () => void
235
236
  /**
@@ -40,7 +40,8 @@ export function table_syncExternalStateToBaseAtoms<
40
40
  }
41
41
 
42
42
  /**
43
- * Resets all internal table base atoms to `table.initialState`.
43
+ * Resets all internal table base atoms to `table.initialState`, then clears
44
+ * transient instance data through registered feature reset hooks.
44
45
  *
45
46
  * This resets internally owned state slices in a single reactivity batch. Use
46
47
  * feature-specific reset APIs when a slice may be externally owned.
@@ -62,6 +63,11 @@ export function table_reset<
62
63
  ;(table.baseAtoms as any)[key].set(snap[key])
63
64
  }
64
65
  })
66
+
67
+ const features = Object.values(table._features)
68
+ for (let i = 0; i < features.length; i++) {
69
+ features[i]!.resetTableInstanceData?.(table)
70
+ }
65
71
  }
66
72
 
67
73
  /**
@@ -35,6 +35,16 @@ import type { TableFeature } from '../../types/TableFeatures'
35
35
  * Feature that adds row selection state and APIs for row and page selection.
36
36
  */
37
37
  export const rowSelectionFeature: TableFeature = {
38
+ initTableInstanceData: (table) => {
39
+ // @ts-ignore - _lastSelectedRowId is row selection table instance data
40
+ table._lastSelectedRowId = null
41
+ },
42
+
43
+ resetTableInstanceData: (table) => {
44
+ // @ts-ignore - _lastSelectedRowId is row selection table instance data
45
+ table._lastSelectedRowId = null
46
+ },
47
+
38
48
  getInitialState: (initialState) => {
39
49
  return {
40
50
  rowSelection: getDefaultRowSelectionState(),
@@ -47,7 +57,15 @@ export const rowSelectionFeature: TableFeature = {
47
57
  onRowSelectionChange: makeStateUpdater('rowSelection', table),
48
58
  enableRowSelection: true,
49
59
  enableMultiRowSelection: true,
60
+ enableRowRangeSelection: true,
50
61
  enableSubRowSelection: true,
62
+ isRowRangeSelectionEvent: (event) => {
63
+ const rangeEvent = event as {
64
+ shiftKey?: boolean
65
+ nativeEvent?: { shiftKey?: boolean }
66
+ }
67
+ return Boolean(rangeEvent.shiftKey || rangeEvent.nativeEvent?.shiftKey)
68
+ },
51
69
  }
52
70
  },
53
71
 
@@ -85,7 +103,7 @@ export const rowSelectionFeature: TableFeature = {
85
103
  fn: (row) => row_getCanMultiSelect(row),
86
104
  },
87
105
  row_getToggleSelectedHandler: {
88
- fn: (row) => row_getToggleSelectedHandler(row),
106
+ fn: (row, opts) => row_getToggleSelectedHandler(row, opts),
89
107
  },
90
108
  })
91
109
  },
@@ -5,6 +5,17 @@ import type { Row } from '../../types/Row'
5
5
 
6
6
  export type RowSelectionState = Record<string, true>
7
7
 
8
+ /**
9
+ * Controls how toggling a row affects its descendants.
10
+ */
11
+ export interface ToggleSelectedOptions {
12
+ /**
13
+ * Whether selectable child rows should be toggled recursively. Defaults to
14
+ * `true`.
15
+ */
16
+ selectChildren?: boolean
17
+ }
18
+
8
19
  export interface TableState_RowSelection {
9
20
  rowSelection: RowSelectionState
10
21
  }
@@ -13,6 +24,11 @@ export interface TableOptions_RowSelection<
13
24
  in out TFeatures extends TableFeatures,
14
25
  in out TData extends RowData,
15
26
  > {
27
+ /**
28
+ * Enables inclusive row range selection through
29
+ * `row.getToggleSelectedHandler()`. Defaults to `true`.
30
+ */
31
+ enableRowRangeSelection?: boolean
16
32
  /**
17
33
  * Allows rows to be selected alongside other rows.
18
34
  *
@@ -32,6 +48,14 @@ export interface TableOptions_RowSelection<
32
48
  * grouping features and defaults to `true`.
33
49
  */
34
50
  enableSubRowSelection?: boolean | ((row: Row<TFeatures, TData>) => boolean)
51
+ /**
52
+ * Determines whether a row-selection handler event should select or
53
+ * deselect the inclusive range from the most recent handler interaction.
54
+ *
55
+ * By default, events with `shiftKey` directly on the event or on
56
+ * `event.nativeEvent` are treated as range-selection events.
57
+ */
58
+ isRowRangeSelectionEvent?: (event: unknown) => boolean
35
59
  /**
36
60
  * Called with an updater when row selection state changes. Pair this with
37
61
  * `state.rowSelection` when using external state; external atoms can own the
@@ -44,7 +68,6 @@ export interface TableOptions_RowSelection<
44
68
  // row: Row<TFeatures, TData>
45
69
  // ) => boolean)
46
70
  // isAdditiveSelectEvent?: (e: unknown) => boolean
47
- // isInclusiveSelectEvent?: (e: unknown) => boolean
48
71
  // selectRowsFn?: (
49
72
  // table: Table<TFeatures, TData>,
50
73
  // rowModel: RowModel<TFeatures, TData>
@@ -78,18 +101,29 @@ export interface Row_RowSelection {
78
101
  getIsSomeSelected: () => boolean
79
102
  /**
80
103
  * Creates a checkbox-style handler that toggles this row's selected state.
104
+ * Pass the original checkbox click event, or a framework event whose
105
+ * `nativeEvent` is that click, so Shift range selection can detect the
106
+ * modifier key.
81
107
  */
82
- getToggleSelectedHandler: () => (event: unknown) => void
108
+ getToggleSelectedHandler: (
109
+ opts?: ToggleSelectedOptions,
110
+ ) => (event: unknown) => void
83
111
  /**
84
112
  * Selects/deselects the row.
85
113
  */
86
- toggleSelected: (value?: boolean, opts?: { selectChildren?: boolean }) => void
114
+ toggleSelected: (value?: boolean, opts?: ToggleSelectedOptions) => void
87
115
  }
88
116
 
89
117
  export interface Table_RowSelection<
90
118
  in out TFeatures extends TableFeatures,
91
119
  in out TData extends RowData,
92
120
  > {
121
+ /**
122
+ * The most recent row interacted with through the row selection handler.
123
+ *
124
+ * @internal
125
+ */
126
+ _lastSelectedRowId: string | null
93
127
  /**
94
128
  * Builds a selected-row model from rows after filtering.
95
129
  */
@@ -10,7 +10,10 @@ import type { TableFeatures } from '../../types/TableFeatures'
10
10
  import type { RowModel } from '../../core/row-models/coreRowModelsFeature.types'
11
11
  import type { Table_Internal } from '../../types/Table'
12
12
  import type { Row } from '../../types/Row'
13
- import type { RowSelectionState } from './rowSelectionFeature.types'
13
+ import type {
14
+ RowSelectionState,
15
+ ToggleSelectedOptions,
16
+ } from './rowSelectionFeature.types'
14
17
 
15
18
  // State APIs
16
19
 
@@ -66,6 +69,8 @@ export function table_resetRowSelection<
66
69
  TFeatures extends TableFeatures,
67
70
  TData extends RowData,
68
71
  >(table: Table_Internal<TFeatures, TData>, defaultState?: boolean) {
72
+ // @ts-ignore - _lastSelectedRowId is part of the RowSelection feature
73
+ table._lastSelectedRowId = null
69
74
  table_setRowSelection(
70
75
  table,
71
76
  defaultState
@@ -98,6 +103,8 @@ export function table_toggleAllRowsSelected<
98
103
  value?: boolean,
99
104
  opts?: { deselectAll?: boolean },
100
105
  ) {
106
+ // @ts-ignore - _lastSelectedRowId is part of the RowSelection feature
107
+ table._lastSelectedRowId = null
101
108
  table_setRowSelection(table, (old) => {
102
109
  value =
103
110
  typeof value !== 'undefined'
@@ -153,6 +160,8 @@ export function table_toggleAllPageRowsSelected<
153
160
  value?: boolean,
154
161
  opts?: { deselectAll?: boolean },
155
162
  ) {
163
+ // @ts-ignore - _lastSelectedRowId is part of the RowSelection feature
164
+ table._lastSelectedRowId = null
156
165
  table_setRowSelection(table, (old) => {
157
166
  const resolvedValue =
158
167
  typeof value !== 'undefined'
@@ -492,13 +501,7 @@ export function table_getToggleAllPageRowsSelectedHandler<
492
501
  export function row_toggleSelected<
493
502
  TFeatures extends TableFeatures,
494
503
  TData extends RowData,
495
- >(
496
- row: Row<TFeatures, TData>,
497
- value?: boolean,
498
- opts?: {
499
- selectChildren?: boolean
500
- },
501
- ) {
504
+ >(row: Row<TFeatures, TData>, value?: boolean, opts?: ToggleSelectedOptions) {
502
505
  const isSelected = row_getIsSelected(row)
503
506
 
504
507
  table_setRowSelection(row.table, (old) => {
@@ -643,7 +646,11 @@ export function row_getCanMultiSelect<
643
646
  * Creates a checkbox-style handler that selects or deselects this row.
644
647
  *
645
648
  * The handler is a no-op when the row cannot be selected and reads
646
- * `event.target.checked`.
649
+ * `event.target.checked`. Shift events select or deselect the inclusive range
650
+ * from the most recent selectable row handled by this table. The event's
651
+ * optional `persist()` method is called before it is read. Pass
652
+ * `selectChildren: false` to limit changes to rows explicitly present in the
653
+ * display-order interval.
647
654
  *
648
655
  * @example
649
656
  * ```ts
@@ -653,18 +660,111 @@ export function row_getCanMultiSelect<
653
660
  export function row_getToggleSelectedHandler<
654
661
  TFeatures extends TableFeatures,
655
662
  TData extends RowData,
656
- >(row: Row<TFeatures, TData>) {
663
+ >(row: Row<TFeatures, TData>, opts?: ToggleSelectedOptions) {
657
664
  const canSelect = row_getCanSelect(row)
658
665
 
659
666
  return (e: unknown) => {
660
667
  if (!canSelect) return
661
- row_toggleSelected(
662
- row,
663
- ((e as MouseEvent).target as HTMLInputElement).checked,
664
- )
668
+
669
+ const event = e as {
670
+ persist?: () => void
671
+ target: { checked: boolean }
672
+ }
673
+ event.persist?.()
674
+
675
+ const table = row.table
676
+ const checked = event.target.checked
677
+ // @ts-ignore - _lastSelectedRowId is part of the RowSelection feature
678
+ const anchorId = table._lastSelectedRowId
679
+ const canSelectRange =
680
+ table.options.enableRowRangeSelection !== false &&
681
+ anchorId !== null &&
682
+ row_getCanMultiSelect(row) &&
683
+ (table.options.isRowRangeSelectionEvent?.(e) ?? false)
684
+
685
+ if (
686
+ !canSelectRange ||
687
+ !selectRowRange(row, anchorId, checked, opts?.selectChildren ?? true)
688
+ ) {
689
+ row_toggleSelected(row, checked, opts)
690
+ }
691
+
692
+ // @ts-ignore - _lastSelectedRowId is part of the RowSelection feature
693
+ table._lastSelectedRowId = row.id
665
694
  }
666
695
  }
667
696
 
697
+ /**
698
+ * Resolves and mutates an inclusive interval in the table's latest logical
699
+ * display order.
700
+ *
701
+ * The anchor is resolved without throwing from the pre-pagination row model,
702
+ * then the core row model. Both endpoint display indexes must still identify
703
+ * those rows in the current order and both endpoints must support
704
+ * multi-selection. Eligible interval rows are applied through one row
705
+ * selection updater; non-selectable and non-multi-selectable rows are skipped.
706
+ * Returns `false` when the interaction should fall back to an ordinary toggle.
707
+ */
708
+ function selectRowRange<TFeatures extends TableFeatures, TData extends RowData>(
709
+ row: Row<TFeatures, TData>,
710
+ anchorId: string,
711
+ value: boolean,
712
+ includeChildren: boolean,
713
+ ): boolean {
714
+ const table = row.table
715
+ const rows = table.getRowsInDisplayOrder()
716
+ const anchorRow =
717
+ table.getPrePaginatedRowModel().rowsById[anchorId] ??
718
+ table.getCoreRowModel().rowsById[anchorId]
719
+
720
+ if (!anchorRow) {
721
+ return false
722
+ }
723
+
724
+ const anchorIndex = anchorRow.getDisplayIndex()
725
+ const rowIndex = row.getDisplayIndex()
726
+ const anchorAtIndex = rows[anchorIndex]
727
+ const rowAtIndex = rows[rowIndex]
728
+
729
+ if (
730
+ anchorIndex < 0 ||
731
+ rowIndex < 0 ||
732
+ anchorIndex >= rows.length ||
733
+ rowIndex >= rows.length ||
734
+ anchorAtIndex?.id !== anchorRow.id ||
735
+ rowAtIndex?.id !== row.id ||
736
+ !row_getCanMultiSelect(anchorRow) ||
737
+ !row_getCanMultiSelect(row)
738
+ ) {
739
+ return false
740
+ }
741
+
742
+ const start = Math.min(anchorIndex, rowIndex)
743
+ const end = Math.max(anchorIndex, rowIndex)
744
+
745
+ table_setRowSelection(table, (old) => {
746
+ const rowSelection = Object.assign(makeObjectMap<true>(), old)
747
+
748
+ for (let index = start; index <= end; index++) {
749
+ const rangeRow = rows[index]!
750
+ if (!row_getCanSelect(rangeRow) || !row_getCanMultiSelect(rangeRow)) {
751
+ continue
752
+ }
753
+ mutateRowIsSelected(
754
+ rowSelection,
755
+ rangeRow.id,
756
+ value,
757
+ includeChildren,
758
+ table,
759
+ )
760
+ }
761
+
762
+ return rowSelection
763
+ })
764
+
765
+ return true
766
+ }
767
+
668
768
  const mutateRowIsSelected = <
669
769
  TFeatures extends TableFeatures,
670
770
  TData extends RowData,