@coreui/data-grid 0.1.0-rc.0 → 0.3.0-rc.0

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 (166) hide show
  1. package/README.md +9 -6
  2. package/dist/css/data-grid.css +156 -38
  3. package/dist/css/data-grid.css.map +1 -1
  4. package/dist/css/data-grid.min.css +2 -2
  5. package/dist/css/data-grid.min.css.map +1 -1
  6. package/dist/css/data-grid.standalone.css +1066 -0
  7. package/dist/css/data-grid.standalone.css.map +1 -0
  8. package/dist/css/data-grid.standalone.min.css +5 -0
  9. package/dist/css/data-grid.standalone.min.css.map +1 -0
  10. package/dist/js/clipboard.cjs +56 -0
  11. package/dist/js/clipboard.cjs.map +1 -0
  12. package/dist/js/clipboard.mjs +54 -0
  13. package/dist/js/clipboard.mjs.map +1 -0
  14. package/dist/js/csv.cjs +36 -59
  15. package/dist/js/csv.cjs.map +1 -1
  16. package/dist/js/csv.mjs +35 -55
  17. package/dist/js/csv.mjs.map +1 -1
  18. package/dist/js/data-grid.esm.js +14108 -9331
  19. package/dist/js/data-grid.esm.js.map +1 -1
  20. package/dist/js/data-grid.esm.min.js +6 -6
  21. package/dist/js/data-grid.esm.min.js.map +1 -1
  22. package/dist/js/data-grid.js +14408 -9634
  23. package/dist/js/data-grid.js.map +1 -1
  24. package/dist/js/data-grid.lite.esm.js +10507 -0
  25. package/dist/js/data-grid.lite.esm.js.map +1 -0
  26. package/dist/js/data-grid.lite.esm.min.js +8 -0
  27. package/dist/js/data-grid.lite.esm.min.js.map +1 -0
  28. package/dist/js/data-grid.lite.js +10520 -0
  29. package/dist/js/data-grid.lite.js.map +1 -0
  30. package/dist/js/data-grid.lite.min.js +8 -0
  31. package/dist/js/data-grid.lite.min.js.map +1 -0
  32. package/dist/js/data-grid.lite.mjs +10507 -0
  33. package/dist/js/data-grid.lite.mjs.map +1 -0
  34. package/dist/js/data-grid.min.js +6 -6
  35. package/dist/js/data-grid.min.js.map +1 -1
  36. package/dist/js/data-grid.mjs +14108 -9331
  37. package/dist/js/data-grid.mjs.map +1 -1
  38. package/dist/js/direction.cjs +26 -0
  39. package/dist/js/direction.cjs.map +1 -0
  40. package/dist/js/direction.mjs +24 -0
  41. package/dist/js/direction.mjs.map +1 -0
  42. package/dist/js/drag.cjs +467 -0
  43. package/dist/js/drag.cjs.map +1 -0
  44. package/dist/js/drag.mjs +464 -0
  45. package/dist/js/drag.mjs.map +1 -0
  46. package/dist/js/export-table-BdabEbJG.mjs +26 -0
  47. package/dist/js/export-table-BdabEbJG.mjs.map +1 -0
  48. package/dist/js/export-table-D762wrZ1.cjs +55 -0
  49. package/dist/js/export-table-D762wrZ1.cjs.map +1 -0
  50. package/dist/js/filters.cjs +89 -132
  51. package/dist/js/filters.cjs.map +1 -1
  52. package/dist/js/filters.mjs +88 -128
  53. package/dist/js/filters.mjs.map +1 -1
  54. package/dist/js/labels.cjs +65 -70
  55. package/dist/js/labels.cjs.map +1 -1
  56. package/dist/js/labels.mjs +64 -66
  57. package/dist/js/labels.mjs.map +1 -1
  58. package/dist/js/print.cjs +66 -0
  59. package/dist/js/print.cjs.map +1 -0
  60. package/dist/js/print.mjs +64 -0
  61. package/dist/js/print.mjs.map +1 -0
  62. package/dist/js/validate.cjs +43 -0
  63. package/dist/js/validate.cjs.map +1 -0
  64. package/dist/js/validate.mjs +42 -0
  65. package/dist/js/validate.mjs.map +1 -0
  66. package/dist/js/xlsx.cjs +240 -0
  67. package/dist/js/xlsx.cjs.map +1 -0
  68. package/dist/js/xlsx.mjs +238 -0
  69. package/dist/js/xlsx.mjs.map +1 -0
  70. package/dist/types/base-component.d.ts +28 -18
  71. package/dist/types/data-grid.d.ts +26 -13
  72. package/dist/types/dom/data.d.ts +15 -0
  73. package/dist/types/dom/event-handler.d.ts +21 -10
  74. package/dist/types/dom/manipulator.d.ts +15 -8
  75. package/dist/types/engine/active-cell.d.ts +1 -0
  76. package/dist/types/engine/data-source.d.ts +1 -1
  77. package/dist/types/engine/features-lite.d.ts +25 -0
  78. package/dist/types/engine/features.d.ts +35 -0
  79. package/dist/types/engine/table.d.ts +3 -3
  80. package/dist/types/engine/validate.d.mts +1 -0
  81. package/dist/types/engine/validate.d.ts +20 -0
  82. package/dist/types/engine/virtualizer.d.ts +2 -0
  83. package/dist/types/icons.d.ts +6 -4
  84. package/dist/types/index-lite.d.mts +1 -0
  85. package/dist/types/index-lite.d.ts +19 -0
  86. package/dist/types/index.d.ts +11 -2
  87. package/dist/types/types.d.ts +53 -31
  88. package/dist/types/ui/body.d.ts +6 -3
  89. package/dist/types/ui/header.d.ts +3 -2
  90. package/dist/types/ui/loading.d.ts +1 -1
  91. package/dist/types/ui/pinning.d.ts +1 -1
  92. package/dist/types/ui/toolbar.d.ts +10 -2
  93. package/dist/types/util/clipboard.d.mts +1 -0
  94. package/dist/types/util/clipboard.d.ts +29 -0
  95. package/dist/types/util/config.d.ts +27 -10
  96. package/dist/types/util/csv.d.ts +5 -5
  97. package/dist/types/util/direction.d.mts +1 -0
  98. package/dist/types/util/direction.d.ts +17 -0
  99. package/dist/types/util/drag.d.mts +1 -0
  100. package/dist/types/util/drag.d.ts +48 -0
  101. package/dist/types/util/export-table.d.ts +47 -0
  102. package/dist/types/util/index.d.ts +66 -10
  103. package/dist/types/util/print.d.mts +1 -0
  104. package/dist/types/util/print.d.ts +32 -0
  105. package/dist/types/util/sanitizer.d.ts +15 -11
  106. package/dist/types/util/xlsx.d.mts +1 -0
  107. package/dist/types/util/xlsx.d.ts +17 -0
  108. package/dist/types/util/zip.d.ts +11 -0
  109. package/package.json +101 -10
  110. package/scss/_base.scss +13 -0
  111. package/scss/_config.scss +14 -0
  112. package/scss/_functions.scss +1 -0
  113. package/scss/_mixins.scss +1 -0
  114. package/scss/base/_buttons.scss +113 -0
  115. package/scss/base/_forms.scss +157 -0
  116. package/scss/base/_helpers.scss +24 -0
  117. package/scss/base/_pagination.scss +100 -0
  118. package/scss/base/_reboot.scss +93 -0
  119. package/scss/base/_root.scss +85 -0
  120. package/scss/base/_spinner.scss +45 -0
  121. package/scss/base/_table.scss +78 -0
  122. package/scss/components/_data-grid.scss +807 -0
  123. package/scss/data-grid.scss +7 -555
  124. package/scss/data-grid.standalone.scss +16 -0
  125. package/scss/functions/_defaults.scss +25 -0
  126. package/scss/mixins/_tokens.scss +12 -0
  127. package/src/{base-component.js → base-component.ts} +21 -17
  128. package/src/data-grid.ts +523 -201
  129. package/src/dom/{data.js → data.ts} +13 -10
  130. package/src/dom/event-handler.ts +380 -0
  131. package/src/dom/{manipulator.js → manipulator.ts} +11 -11
  132. package/src/engine/active-cell.ts +10 -3
  133. package/src/engine/data-source.ts +7 -3
  134. package/src/engine/features-lite.ts +47 -0
  135. package/src/engine/features.ts +68 -0
  136. package/src/engine/table.ts +86 -45
  137. package/src/engine/validate.ts +64 -0
  138. package/src/engine/virtualizer.ts +17 -1
  139. package/src/icons.ts +11 -5
  140. package/src/index-lite.ts +85 -0
  141. package/src/index.ts +30 -2
  142. package/src/labels.ts +6 -4
  143. package/src/types.ts +87 -32
  144. package/src/ui/body.ts +73 -17
  145. package/src/ui/header.ts +51 -26
  146. package/src/ui/loading.ts +2 -2
  147. package/src/ui/pinning.ts +16 -16
  148. package/src/ui/toolbar.ts +27 -3
  149. package/src/util/clipboard.ts +112 -0
  150. package/src/util/{config.js → config.ts} +25 -11
  151. package/src/util/csv.ts +11 -30
  152. package/src/util/direction.ts +37 -0
  153. package/src/util/drag.ts +832 -0
  154. package/src/util/export-table.ts +86 -0
  155. package/src/util/{index.js → index.ts} +55 -35
  156. package/src/util/print.ts +125 -0
  157. package/src/util/{sanitizer.js → sanitizer.ts} +17 -13
  158. package/src/util/xlsx.ts +183 -0
  159. package/src/util/zip.ts +170 -0
  160. package/src/base-component.d.ts +0 -19
  161. package/src/dom/event-handler.d.ts +0 -11
  162. package/src/dom/event-handler.js +0 -331
  163. package/src/dom/manipulator.d.ts +0 -9
  164. package/src/util/config.d.ts +0 -11
  165. package/src/util/index.d.ts +0 -10
  166. package/src/util/sanitizer.d.ts +0 -11
package/src/data-grid.ts CHANGED
@@ -5,26 +5,35 @@
5
5
  * --------------------------------------------------------------------------
6
6
  */
7
7
 
8
- import type { Table, TableState } from '@tanstack/table-core'
8
+ import { batch } from '@tanstack/store'
9
9
  import BaseComponent from './base-component.js'
10
+ import type { ComponentConfig } from './util/config.js'
10
11
  import EventHandler from './dom/event-handler.js'
11
12
  import {
12
13
  createActiveCellManager, resolveNavigation, type ActiveCell, type ActiveCellContext, type ActiveCellManager
13
14
  } from './engine/active-cell'
14
15
  import { createDataSource, type GridDataSource } from './engine/data-source'
16
+ import { copyCellSelection, type ClipboardColumn } from './util/clipboard'
17
+ import { printGrid, type PrintColumn } from './util/print'
18
+ import {
19
+ createColumnDragController, createRowDragController, type DragController
20
+ } from './util/drag'
15
21
  import { createScheduler, type Scheduler } from './engine/scheduler'
16
- import { createGridTable } from './engine/table'
22
+ import { createGridTable, subscribeToTableState } from './engine/table'
23
+ import { isElementRTL, syncResizeDirection } from './util/direction'
17
24
  import { createGridVirtualizer, type GridVirtualizer } from './engine/virtualizer'
18
25
  import {
19
- DEFAULT_COLUMN_MENU_ICON, DEFAULT_FILTER_ICON, DEFAULT_HIDE_COLUMN_ICON, DEFAULT_MOVE_LEFT_ICON, DEFAULT_MOVE_RIGHT_ICON,
20
- DEFAULT_PIN_LEFT_ICON, DEFAULT_PIN_RIGHT_ICON, DEFAULT_SORT_ASCENDING_ICON, DEFAULT_SORT_DESCENDING_ICON,
26
+ DEFAULT_COLUMN_MENU_ICON, DEFAULT_FILTER_ICON, DEFAULT_HIDE_COLUMN_ICON, DEFAULT_MOVE_END_ICON, DEFAULT_MOVE_START_ICON,
27
+ DEFAULT_PIN_END_ICON, DEFAULT_PIN_START_ICON, DEFAULT_ROW_HANDLE_ICON, DEFAULT_SORT_ASCENDING_ICON,
28
+ DEFAULT_SORT_DESCENDING_ICON,
21
29
  DEFAULT_SORT_NEUTRAL_ICON, DEFAULT_TOOLBAR_COLUMNS_ICON, DEFAULT_TOOLBAR_EXPORT_ICON,
22
- DEFAULT_TOOLBAR_REDO_ICON, DEFAULT_TOOLBAR_UNDO_ICON, DEFAULT_UNPIN_ICON
30
+ DEFAULT_TOOLBAR_PRINT_ICON, DEFAULT_TOOLBAR_REDO_ICON, DEFAULT_TOOLBAR_UNDO_ICON, DEFAULT_UNPIN_ICON
23
31
  } from './icons'
24
32
  import { DEFAULT_LABELS, interpolate } from './labels'
25
33
  import type {
26
34
  DataGridColumn, DataGridColumnFilterValue, DataGridEditableOptions, DataGridEditor, DataGridEditorContext,
27
- DataGridItem, DataGridLabels, DataGridMenuAction, DataGridOptions, DataGridSlot, DataGridSlotContext, DataGridState
35
+ DataGridFeatures, DataGridItem, DataGridLabels, DataGridMenuAction, DataGridOptions, DataGridSlot,
36
+ DataGridSlotContext, DataGridState, DataGridTable, DataGridTableState, TableFeatures
28
37
  } from './types'
29
38
  import { createBody, type GridBody } from './ui/body'
30
39
  import { createBuiltInEditor } from './ui/editor'
@@ -41,7 +50,6 @@ import {
41
50
  downloadCsv, exportCsv, type CsvDownloadOptions, type CsvOptions
42
51
  } from './util/csv'
43
52
  import { debounce } from './util/debounce'
44
- import { defineJQueryPlugin } from './util/index.js'
45
53
  import { DefaultAllowlist, sanitizeHtml, SVGAllowlist } from './util/sanitizer.js'
46
54
 
47
55
  /**
@@ -62,7 +70,9 @@ const EVENT_EDIT_COMMIT = `editCommit${EVENT_KEY}`
62
70
  const EVENT_EDIT_START = `editStart${EVENT_KEY}`
63
71
  const EVENT_FILTER_CHANGE = `filterChange${EVENT_KEY}`
64
72
  const EVENT_INPUT = `input${EVENT_KEY}`
73
+ const EVENT_CELL_COPY = `cellCopy${EVENT_KEY}`
65
74
  const EVENT_ORDER_CHANGE = `orderChange${EVENT_KEY}`
75
+ const EVENT_ROW_ORDER_CHANGE = `rowOrderChange${EVENT_KEY}`
66
76
  const EVENT_PAGINATION_CHANGE = `paginationChange${EVENT_KEY}`
67
77
  const EVENT_PINNING_CHANGE = `pinningChange${EVENT_KEY}`
68
78
  const EVENT_SELECTION_CHANGE = `selectionChange${EVENT_KEY}`
@@ -74,27 +84,23 @@ const EVENT_FOCUSIN = `focusin${EVENT_KEY}`
74
84
  const EVENT_FOCUSOUT = `focusout${EVENT_KEY}`
75
85
  const EVENT_KEYDOWN = `keydown${EVENT_KEY}`
76
86
 
77
- const EVENT_DRAGEND = `dragend${EVENT_KEY}`
78
- const EVENT_DRAGLEAVE = `dragleave${EVENT_KEY}`
79
- const EVENT_DRAGOVER = `dragover${EVENT_KEY}`
80
- const EVENT_DRAGSTART = `dragstart${EVENT_KEY}`
81
- const EVENT_DROP = `drop${EVENT_KEY}`
82
87
  const EVENT_MOUSEDOWN = `mousedown${EVENT_KEY}`
88
+ const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY}`
83
89
  const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY}`
84
90
  const EVENT_TOUCHSTART = `touchstart${EVENT_KEY}`
85
91
 
92
+ const CLASS_NAME_AUTO_ROW_HEIGHT = 'data-grid-auto-row-height'
86
93
  const CLASS_NAME_DATA_GRID = 'data-grid'
87
- const CLASS_NAME_DRAGGING = 'data-grid-dragging'
88
94
  const CLASS_NAME_EDITOR_POPUP = 'data-grid-editor-popup'
89
- const CLASS_NAME_DROP_TARGET = 'data-grid-drop-target'
95
+ const CLASS_NAME_LOADING_MORE = 'data-grid-loading-more'
90
96
  const CLASS_NAME_PAGINATION_TOP = 'data-grid-pagination-top'
91
97
  const CLASS_NAME_PINNED = 'data-grid-pinned'
92
98
  const CLASS_NAME_RESIZABLE = 'data-grid-resizable'
93
99
  const CLASS_NAME_TABLE = 'table'
94
100
  const CLASS_NAME_VIEWPORT = 'data-grid-viewport'
95
101
 
102
+ const SELECTOR_BODY_CELL = 'tbody td[data-coreui-column]'
96
103
  const SELECTOR_COLUMN_FILTER = 'input[data-coreui-column-filter]'
97
- const SELECTOR_DRAGGABLE = 'th[data-coreui-draggable]'
98
104
  const SELECTOR_MENU_BUTTON = 'button[data-coreui-menu]'
99
105
  const SELECTOR_FILTER_BUTTON = 'button[data-coreui-filter]'
100
106
  const SELECTOR_GLOBAL_FILTER = 'input[data-coreui-global-filter]'
@@ -106,6 +112,7 @@ const SELECTOR_SELECT_ALL = 'input[data-coreui-select-all]'
106
112
  const SELECTOR_SORT_BUTTON = '.data-grid-sort-button'
107
113
  const SELECTOR_TOOLBAR_COLUMNS = 'button[data-coreui-toolbar-columns]'
108
114
  const SELECTOR_TOOLBAR_EXPORT = 'button[data-coreui-toolbar-export]'
115
+ const SELECTOR_TOOLBAR_PRINT = 'button[data-coreui-toolbar-print]'
109
116
  const SELECTOR_TOOLBAR_REDO = 'button[data-coreui-toolbar-redo]'
110
117
  const SELECTOR_TOOLBAR_UNDO = 'button[data-coreui-toolbar-undo]'
111
118
 
@@ -128,6 +135,19 @@ const STATE_STORAGE_PREFIX = 'coreui-data-grid:'
128
135
  const STATE_AUTOSAVE_DEBOUNCE_MS = 250
129
136
  const HISTORY_LIMIT = 100
130
137
 
138
+ // Under RTL the two horizontal keys point at the opposite logical neighbour.
139
+ const INVERTED_ARROWS: Record<string, string | undefined> = {
140
+ ArrowLeft: 'ArrowRight',
141
+ ArrowRight: 'ArrowLeft'
142
+ }
143
+
144
+ const CELL_SELECTION_DIRECTIONS: Record<string, 'up' | 'down' | 'left' | 'right' | undefined> = {
145
+ ArrowDown: 'down',
146
+ ArrowLeft: 'left',
147
+ ArrowRight: 'right',
148
+ ArrowUp: 'up'
149
+ }
150
+
131
151
  const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]'
132
152
 
133
153
  const applyTabIndex = (cellElement: HTMLElement, isTabbable: boolean) => {
@@ -138,7 +158,9 @@ const applyTabIndex = (cellElement: HTMLElement, isTabbable: boolean) => {
138
158
  }
139
159
 
140
160
  const Default: DataGridOptions = {
161
+ autoRowHeight: false,
141
162
  cellNavigation: false,
163
+ cellSelection: false,
142
164
  columnFilters: false,
143
165
  columnMenu: false,
144
166
  columnMenuIcon: DEFAULT_COLUMN_MENU_ICON,
@@ -150,20 +172,25 @@ const Default: DataGridOptions = {
150
172
  dataProvider: null,
151
173
  editing: false,
152
174
  empty: 'No items found',
175
+ features: null,
153
176
  filterIcon: DEFAULT_FILTER_ICON,
154
177
  globalFilter: false,
155
178
  hideColumnIcon: DEFAULT_HIDE_COLUMN_ICON,
156
179
  history: false,
180
+ infiniteScroll: false,
157
181
  itemKey: null,
158
182
  items: [],
159
183
  labels: {},
160
- moveLeftIcon: DEFAULT_MOVE_LEFT_ICON,
161
- moveRightIcon: DEFAULT_MOVE_RIGHT_ICON,
184
+ moveEndIcon: DEFAULT_MOVE_END_ICON,
185
+ moveStartIcon: DEFAULT_MOVE_START_ICON,
162
186
  overscan: 10,
163
187
  pagination: false,
164
- pinLeftIcon: DEFAULT_PIN_LEFT_ICON,
165
- pinRightIcon: DEFAULT_PIN_RIGHT_ICON,
188
+ pinEndIcon: DEFAULT_PIN_END_ICON,
189
+ pinStartIcon: DEFAULT_PIN_START_ICON,
190
+ printTitle: null,
191
+ rowHandleIcon: DEFAULT_ROW_HANDLE_ICON,
166
192
  rowHeight: 44,
193
+ rowOrder: false,
167
194
  rowSelection: false,
168
195
  sanitize: true,
169
196
  sanitizeFn: null,
@@ -177,6 +204,7 @@ const Default: DataGridOptions = {
177
204
  toolbar: false,
178
205
  toolbarColumnsIcon: DEFAULT_TOOLBAR_COLUMNS_ICON,
179
206
  toolbarExportIcon: DEFAULT_TOOLBAR_EXPORT_ICON,
207
+ toolbarPrintIcon: DEFAULT_TOOLBAR_PRINT_ICON,
180
208
  toolbarRedoIcon: DEFAULT_TOOLBAR_REDO_ICON,
181
209
  toolbarUndoIcon: DEFAULT_TOOLBAR_UNDO_ICON,
182
210
  unpinIcon: DEFAULT_UNPIN_ICON,
@@ -184,7 +212,9 @@ const Default: DataGridOptions = {
184
212
  }
185
213
 
186
214
  const DefaultType = {
215
+ autoRowHeight: 'boolean',
187
216
  cellNavigation: 'boolean',
217
+ cellSelection: 'boolean',
188
218
  columnFilters: 'boolean',
189
219
  columnMenu: '(boolean|function)',
190
220
  columnMenuIcon: 'string',
@@ -196,20 +226,25 @@ const DefaultType = {
196
226
  dataProvider: '(function|null)',
197
227
  editing: 'boolean',
198
228
  empty: '(string|function)',
229
+ features: '(object|null)',
199
230
  filterIcon: 'string',
200
231
  globalFilter: 'boolean',
201
232
  hideColumnIcon: 'string',
202
233
  history: 'boolean',
234
+ infiniteScroll: '(boolean|object)',
203
235
  itemKey: '(function|null)',
204
236
  items: 'array',
205
237
  labels: 'object',
206
- moveLeftIcon: 'string',
207
- moveRightIcon: 'string',
238
+ moveEndIcon: 'string',
239
+ moveStartIcon: 'string',
208
240
  overscan: 'number',
209
241
  pagination: '(boolean|object)',
210
- pinLeftIcon: 'string',
211
- pinRightIcon: 'string',
242
+ pinEndIcon: 'string',
243
+ pinStartIcon: 'string',
244
+ printTitle: '(string|null)',
245
+ rowHandleIcon: 'string',
212
246
  rowHeight: 'number',
247
+ rowOrder: 'boolean',
213
248
  rowSelection: '(boolean|object)',
214
249
  sanitize: 'boolean',
215
250
  sanitizeFn: '(function|null)',
@@ -223,6 +258,7 @@ const DefaultType = {
223
258
  toolbar: '(boolean|object)',
224
259
  toolbarColumnsIcon: 'string',
225
260
  toolbarExportIcon: 'string',
261
+ toolbarPrintIcon: 'string',
226
262
  toolbarRedoIcon: 'string',
227
263
  toolbarUndoIcon: 'string',
228
264
  unpinIcon: 'string',
@@ -233,9 +269,13 @@ const DefaultType = {
233
269
  * Class definition
234
270
  */
235
271
 
236
- class DataGrid extends BaseComponent {
272
+ class DataGrid<TFeatures extends TableFeatures = DataGridFeatures> extends BaseComponent {
237
273
  declare _config: DataGridOptions
238
- private _table!: Table<DataGridItem>
274
+ // Typed on the full set even when the runtime set is smaller - internals
275
+ // capability-guard every optional-feature call, and the public `table`
276
+ // getter narrows back to the instance's TFeatures.
277
+ private _table!: DataGridTable
278
+ private _unsubscribeState: (() => void) | null = null
239
279
  private _scheduler!: Scheduler
240
280
  private _virtualizer: GridVirtualizer | null = null
241
281
  private _header!: GridHeader
@@ -245,7 +285,13 @@ class DataGrid extends BaseComponent {
245
285
  private _emptySlot: DataGridSlot | null = null
246
286
  private _dataSource: GridDataSource | null = null
247
287
  private _loading: GridLoading | null = null
288
+ private _loadingMore: GridLoading | null = null
248
289
  private _loadFailed = false
290
+ // Infinite scroll accumulates every loaded page here; the table's data is
291
+ // this buffer, not the last response.
292
+ private _loadedItems: DataGridItem[] = []
293
+ private _loadMorePending = false
294
+ private _pageResetPending = false
249
295
  private _dataVersion = 0
250
296
  private _viewportElement!: HTMLDivElement
251
297
  private _tableElement!: HTMLTableElement
@@ -272,14 +318,15 @@ private _undoStack: DataGridEditCommitEntry[] = []
272
318
  private _editorErrorElement: HTMLElement | null = null
273
319
  private _editorFocusPending = false
274
320
  private _columnsByKey = new Map<string, DataGridColumn>()
275
- private _dragColumn: string | null = null
321
+ private _columnDrag: DragController | null = null
322
+ private _rowDrag: DragController | null = null
276
323
  private _menu: GridMenu | null = null
277
324
  private _tooltip: GridTooltip | null = null
278
325
  private _filterMenu: GridFilterMenu | null = null
279
326
  private _columnChooser: GridColumnChooser | null = null
280
327
  private _toolbarConfig: ResolvedToolbar | null = null
281
328
 
282
- constructor(element: Element | string, config?: Partial<DataGridOptions>) {
329
+ constructor(element: Element | string, config?: Partial<DataGridOptions<TFeatures>>) {
283
330
  super(element, config)
284
331
 
285
332
  if (!this._element) {
@@ -305,8 +352,10 @@ private _undoStack: DataGridEditCommitEntry[] = []
305
352
  }
306
353
 
307
354
  // Getters
308
- get table(): Table<DataGridItem> {
309
- return this._table
355
+ get table(): DataGridTable<TFeatures> {
356
+ // TFeatures is invariant, so the internal full-set alias needs the double
357
+ // cast back to the instance's actual set.
358
+ return this._table as unknown as DataGridTable<TFeatures>
310
359
  }
311
360
 
312
361
  static get Default(): DataGridOptions {
@@ -321,19 +370,22 @@ private _undoStack: DataGridEditCommitEntry[] = []
321
370
  return NAME
322
371
  }
323
372
 
324
- static getInstance(element: Element | string | null): DataGrid | null {
325
- return super.getInstance(element) as DataGrid | null
326
- }
327
-
328
- static getOrCreateInstance(element: Element | string, config?: Partial<DataGridOptions>): DataGrid {
329
- return super.getOrCreateInstance(element, config) as DataGrid
330
- }
331
-
332
373
  // Public
333
374
  getSelectedItems(): DataGridItem[] {
375
+ if (typeof this._table.getSelectedRowModel !== 'function') {
376
+ return []
377
+ }
378
+
334
379
  return this._table.getSelectedRowModel().rows.map(row => row.original)
335
380
  }
336
381
 
382
+ // Prints every sorted and filtered row, not the virtual window or the
383
+ // current page: the grid renders a plain complete table, prints it, and
384
+ // throws it away.
385
+ print(): void {
386
+ printGrid(this._table, this._printColumns(), { title: this._config.printTitle ?? undefined })
387
+ }
388
+
337
389
  getCsv(options?: CsvOptions): string {
338
390
  return exportCsv(this._table, options)
339
391
  }
@@ -345,22 +397,66 @@ private _undoStack: DataGridEditCommitEntry[] = []
345
397
  // A serializable snapshot of every user-adjustable state slice. Feed it
346
398
  // back through restoreState() - scroll position and edit drafts excluded.
347
399
  getState(): DataGridState {
348
- const state = this._table.getState()
400
+ // A reduced feature set has no atom for its absent slices - the snapshot
401
+ // keeps its full shape with neutral values so persisted state stays
402
+ // portable between feature sets.
403
+ const { state } = this._table.store
349
404
  return {
350
- columnFilters: state.columnFilters,
351
- columnOrder: state.columnOrder,
352
- columnPinning: state.columnPinning,
353
- columnSizing: state.columnSizing,
354
- columnVisibility: state.columnVisibility,
405
+ columnFilters: state.columnFilters ?? [],
406
+ columnOrder: state.columnOrder ?? [],
407
+ columnPinning: state.columnPinning ?? { start: [], end: [] },
408
+ columnSizing: state.columnSizing ?? {},
409
+ columnVisibility: state.columnVisibility ?? {},
355
410
  globalFilter: (state.globalFilter as string | undefined) ?? '',
356
- pagination: state.pagination,
357
- rowSelection: state.rowSelection,
358
- sorting: state.sorting
411
+ pagination: state.pagination ?? { pageIndex: 0, pageSize: Number.MAX_SAFE_INTEGER },
412
+ rowSelection: state.rowSelection ?? {},
413
+ sorting: state.sorting ?? []
359
414
  }
360
415
  }
361
416
 
362
417
  restoreState(state: Partial<DataGridState>): void {
363
- this._table.setState(previous => ({ ...previous, ...state }))
418
+ // v9 owns one atom per slice and has no bulk state setter; batching keeps
419
+ // the writes to a single store emission, so the change events still fire
420
+ // once for the whole restore.
421
+ // The setter-presence checks let a snapshot taken on one feature set
422
+ // restore into a grid running a smaller one - absent slices are skipped.
423
+ batch(() => {
424
+ if (state.columnFilters !== undefined && typeof this._table.setColumnFilters === 'function') {
425
+ this._table.setColumnFilters(state.columnFilters)
426
+ }
427
+
428
+ if (state.columnOrder !== undefined && typeof this._table.setColumnOrder === 'function') {
429
+ this._table.setColumnOrder(state.columnOrder)
430
+ }
431
+
432
+ if (state.columnPinning !== undefined && typeof this._table.setColumnPinning === 'function') {
433
+ this._table.setColumnPinning(state.columnPinning)
434
+ }
435
+
436
+ if (state.columnSizing !== undefined && typeof this._table.setColumnSizing === 'function') {
437
+ this._table.setColumnSizing(state.columnSizing)
438
+ }
439
+
440
+ if (state.columnVisibility !== undefined && typeof this._table.setColumnVisibility === 'function') {
441
+ this._table.setColumnVisibility(state.columnVisibility)
442
+ }
443
+
444
+ if (state.globalFilter !== undefined && typeof this._table.setGlobalFilter === 'function') {
445
+ this._table.setGlobalFilter(state.globalFilter)
446
+ }
447
+
448
+ if (state.pagination !== undefined && typeof this._table.setPagination === 'function') {
449
+ this._table.setPagination(state.pagination)
450
+ }
451
+
452
+ if (state.rowSelection !== undefined && typeof this._table.setRowSelection === 'function') {
453
+ this._table.setRowSelection(state.rowSelection)
454
+ }
455
+
456
+ if (state.sorting !== undefined && typeof this._table.setSorting === 'function') {
457
+ this._table.setSorting(state.sorting)
458
+ }
459
+ })
364
460
 
365
461
  // The built-in search input is uncontrolled - sync it to the restored
366
462
  // filter so the UI matches the state.
@@ -424,19 +520,23 @@ private _undoStack: DataGridEditCommitEntry[] = []
424
520
  this._stopEditing({ commit: false })
425
521
  this._config.items = items
426
522
  this._dataVersion++
523
+ this._virtualizer?.remeasure()
427
524
  this._table.setOptions(previous => ({ ...previous, data: items }))
428
525
  this._scheduler.schedule()
429
526
  }
430
527
 
431
528
  // Full rebuild: recreates the table, wiping sorting/filters/selection/scroll.
432
529
  // Use setItems() to swap data while keeping user state.
433
- update(config: Partial<DataGridOptions>): void {
434
- this._config = this._getConfig({ ...this._config, ...config })
530
+ update(config: Partial<DataGridOptions<TFeatures>>): void {
531
+ this._config = this._getConfig({ ...this._config, ...config }) as DataGridOptions
435
532
  this._labels = { ...DEFAULT_LABELS, ...this._config.labels }
436
533
 
437
534
  this._teardownUi()
438
535
  this._selectionAnchorIndex = null
439
536
  this._loadFailed = false
537
+ this._pageResetPending = false
538
+ this._loadedItems = []
539
+ this._loadMorePending = false
440
540
  this._dataVersion++
441
541
  this._table = this._createTable()
442
542
  this._render()
@@ -445,6 +545,8 @@ private _undoStack: DataGridEditCommitEntry[] = []
445
545
 
446
546
  dispose(): void {
447
547
  this._teardownUi()
548
+ this._unsubscribeState?.()
549
+ this._unsubscribeState = null
448
550
  this._scheduler.dispose()
449
551
  this._tooltip?.dispose()
450
552
  this._tooltip = null
@@ -460,19 +562,25 @@ private _undoStack: DataGridEditCommitEntry[] = []
460
562
 
461
563
  // Private
462
564
  // Editing needs the active-cell substrate (Enter/F2 target the active cell).
463
- _configAfterMerge(config: DataGridOptions): DataGridOptions {
464
- if (config.editing) {
565
+ _configAfterMerge(config: ComponentConfig): ComponentConfig {
566
+ // Both need the active-cell substrate: editing targets it with Enter/F2,
567
+ // cell selection anchors ranges on it.
568
+ if (config.editing || config.cellSelection) {
465
569
  config.cellNavigation = true
466
570
  }
467
571
 
468
572
  return config
469
573
  }
470
574
 
471
- private _createTable(): Table<DataGridItem> {
472
- return createGridTable(this._config, (previous, next) => {
575
+ private _createTable(): DataGridTable {
576
+ this._unsubscribeState?.()
577
+ const table = createGridTable(this._config, () => this._element)
578
+ this._unsubscribeState = subscribeToTableState(table, (previous, next) => {
473
579
  this._notifyStateChange(previous, next)
474
580
  this._scheduler.schedule()
475
581
  })
582
+
583
+ return table
476
584
  }
477
585
 
478
586
  private _teardownUi(): void {
@@ -505,6 +613,21 @@ private _undoStack: DataGridEditCommitEntry[] = []
505
613
  this._loading = null
506
614
  }
507
615
 
616
+ if (this._loadingMore) {
617
+ this._loadingMore.element.remove()
618
+ this._loadingMore = null
619
+ }
620
+
621
+ if (this._columnDrag) {
622
+ this._columnDrag.dispose()
623
+ this._columnDrag = null
624
+ }
625
+
626
+ if (this._rowDrag) {
627
+ this._rowDrag.dispose()
628
+ this._rowDrag = null
629
+ }
630
+
508
631
  if (this._virtualizer) {
509
632
  this._virtualizer.destroy()
510
633
  this._virtualizer = null
@@ -581,7 +704,10 @@ private _undoStack: DataGridEditCommitEntry[] = []
581
704
  return
582
705
  }
583
706
 
584
- if (!toolbarConfig.columns && !toolbarConfig.export && !toolbarConfig.search && !toolbarConfig.history) {
707
+ if (
708
+ !toolbarConfig.columns && !toolbarConfig.export && !toolbarConfig.print &&
709
+ !toolbarConfig.search && !toolbarConfig.history
710
+ ) {
585
711
  return
586
712
  }
587
713
 
@@ -591,11 +717,13 @@ private _undoStack: DataGridEditCommitEntry[] = []
591
717
  icons: {
592
718
  columns: this._sanitizeIcon(this._config.toolbarColumnsIcon),
593
719
  export: this._sanitizeIcon(this._config.toolbarExportIcon),
720
+ print: this._sanitizeIcon(this._config.toolbarPrintIcon),
594
721
  redo: this._sanitizeIcon(this._config.toolbarRedoIcon),
595
722
  undo: this._sanitizeIcon(this._config.toolbarUndoIcon)
596
723
  },
597
724
  history: toolbarConfig.history,
598
725
  labels: this._labels,
726
+ print: toolbarConfig.print,
599
727
  search: toolbarConfig.search
600
728
  })
601
729
  this._element.append(this._toolbar.element)
@@ -620,6 +748,7 @@ private _undoStack: DataGridEditCommitEntry[] = []
620
748
  createActiveCellManager(() => this._scheduler.schedule()) :
621
749
  null
622
750
  this._element.classList.add(CLASS_NAME_DATA_GRID)
751
+ this._element.classList.toggle(CLASS_NAME_AUTO_ROW_HEIGHT, Boolean(this._config.autoRowHeight))
623
752
  this._element.classList.toggle(CLASS_NAME_RESIZABLE, Boolean(this._config.columnSizing))
624
753
  this._element.classList.toggle(CLASS_NAME_PINNED, Boolean(this._config.columnPinning))
625
754
  if (this._config.sorterVisibility) {
@@ -637,9 +766,11 @@ private _undoStack: DataGridEditCommitEntry[] = []
637
766
 
638
767
  const selectionEnabled = Boolean(this._config.rowSelection)
639
768
  const selectionOptions = typeof this._config.rowSelection === 'object' ? this._config.rowSelection : {}
640
- // Server-side mode implies pagination — virtualized infinite scroll over a
641
- // dataProvider is out of scope until 0.3.0.
642
- const paginationEnabled = Boolean(this._config.pagination) || Boolean(this._config.dataProvider)
769
+ // Server-side mode implies pagination unless it scrolls infinitely, where
770
+ // the pages accumulate behind the virtualizer instead of replacing.
771
+ const paginationEnabled =
772
+ Boolean(this._config.pagination) ||
773
+ (Boolean(this._config.dataProvider) && !this._config.infiniteScroll)
643
774
  const paginationOptions = typeof this._config.pagination === 'object' ? this._config.pagination : {}
644
775
 
645
776
  if (this._config.columnMenu) {
@@ -654,8 +785,15 @@ private _undoStack: DataGridEditCommitEntry[] = []
654
785
  root: this._element as HTMLElement,
655
786
  labels: this._labels,
656
787
  getColumn: columnKey => this._columnsByKey.get(columnKey) ?? null,
657
- getFacetedValues: columnKey =>
658
- [...(this._table.getColumn(columnKey)?.getFacetedUniqueValues().keys() ?? [])],
788
+ getFacetedValues: columnKey => {
789
+ const column = this._table.getColumn(columnKey)
790
+ // Only filterType 'select' menus ask for values, and the coverage
791
+ // assertion ties that filter type to the faceting features - this
792
+ // guard covers select columns added later through update().
793
+ return typeof column?.getFacetedUniqueValues === 'function' ?
794
+ [...column.getFacetedUniqueValues().keys()] :
795
+ []
796
+ },
659
797
  getFilterValue: columnKey =>
660
798
  this._table.getColumn(columnKey)?.getFilterValue() as DataGridColumnFilterValue | undefined,
661
799
  applyFilter: (columnKey, value) => {
@@ -686,26 +824,22 @@ private _undoStack: DataGridEditCommitEntry[] = []
686
824
  isSortable: column => this._isColumnSortable(column),
687
825
  labels: this._labels,
688
826
  resizable: Boolean(this._config.columnSizing),
827
+ rowOrder: this._config.rowOrder,
689
828
  selection: selectionEnabled,
690
829
  selectAll: selectionEnabled && selectionOptions.selectAll !== false,
691
830
  table: this._table
692
831
  })
693
832
  this._body = createBody({
833
+ autoRowHeight: this._config.autoRowHeight && this._config.virtualization,
694
834
  cellNavigation: this._config.cellNavigation,
835
+ cellSelection: this._config.cellSelection,
695
836
  getDataVersion: () => this._dataVersion,
696
837
  getEditingCell: () =>
697
838
  (this._editingCell ? { rowId: this._editingCell.rowId, columnId: this._editingCell.columnId } : null),
698
839
  // Popup editors overlay the cell; its content stays in place.
699
840
  getEditorElement: () => (this._editor && !this._editor.popup ? this._editor.element : null),
700
841
  getPinning: () => (this._config.columnPinning ? this._getPinning() : null),
701
- getRowIndexOffset: () => {
702
- if (!this._config.pagination && !this._config.dataProvider) {
703
- return 0
704
- }
705
-
706
- const { pageIndex, pageSize } = this._table.getState().pagination
707
- return pageIndex * pageSize
708
- },
842
+ getRowIndexOffset: () => this._rowIndexOffset(),
709
843
  getRows: () => this._table.getRowModel().rows,
710
844
  getVisibleColumns: () => this._visibleColumns(),
711
845
  getVirtualizer: () => this._virtualizer?.instance ?? null,
@@ -722,6 +856,8 @@ private _undoStack: DataGridEditCommitEntry[] = []
722
856
 
723
857
  return typeof this._config.empty === 'function' ? this._config.empty() : this._config.empty
724
858
  },
859
+ rowHandleIcon: this._sanitizeIcon(this._config.rowHandleIcon),
860
+ rowOrder: this._config.rowOrder,
725
861
  sanitizeContent: html =>
726
862
  (this._config.sanitize ? sanitizeHtml(html, DefaultAllowlist, this._config.sanitizeFn) : html),
727
863
  selection: selectionEnabled
@@ -750,6 +886,13 @@ private _undoStack: DataGridEditCommitEntry[] = []
750
886
  this._viewportElement.append(this._loading.element)
751
887
  }
752
888
 
889
+ if (this._config.infiniteScroll) {
890
+ // A second indicator: the overlay covers the grid, which is wrong once
891
+ // rows are already on screen and only the next page is arriving.
892
+ this._loadingMore = createLoading(this._labels, CLASS_NAME_LOADING_MORE)
893
+ this._viewportElement.append(this._loadingMore.element)
894
+ }
895
+
753
896
  this._element.append(this._viewportElement)
754
897
 
755
898
  if (paginationEnabled) {
@@ -788,6 +931,7 @@ private _undoStack: DataGridEditCommitEntry[] = []
788
931
  }
789
932
  } else if (this._config.virtualization) {
790
933
  this._virtualizer = createGridVirtualizer({
934
+ autoRowHeight: this._config.autoRowHeight,
791
935
  count: this._table.getRowModel().rows.length,
792
936
  getScrollElement: () => this._viewportElement,
793
937
  onChange: () => this._scheduler.schedule(),
@@ -796,6 +940,8 @@ private _undoStack: DataGridEditCommitEntry[] = []
796
940
  })
797
941
  }
798
942
 
943
+ this._initColumnDrag()
944
+ this._initRowDrag()
799
945
  this._update()
800
946
  }
801
947
 
@@ -818,6 +964,18 @@ private _undoStack: DataGridEditCommitEntry[] = []
818
964
  }
819
965
  })
820
966
 
967
+ // Drag-select: table-core owns the session (it attaches its own document
968
+ // mouseup), so the grid only routes the two events at the right cell.
969
+ EventHandler.on(this._element, EVENT_MOUSEDOWN, SELECTOR_BODY_CELL, event => {
970
+ const cell = this._tableCellFromTarget(event)
971
+ cell?.getSelectionStartHandler()(event)
972
+ })
973
+
974
+ EventHandler.on(this._element, EVENT_MOUSEENTER, SELECTOR_BODY_CELL, event => {
975
+ const cell = this._tableCellFromTarget(event)
976
+ cell?.getSelectionExtendHandler()(event)
977
+ })
978
+
821
979
  EventHandler.on(this._element, EVENT_DBLCLICK, event => {
822
980
  if (!this._config.editing) {
823
981
  return
@@ -872,7 +1030,12 @@ private _undoStack: DataGridEditCommitEntry[] = []
872
1030
  })
873
1031
 
874
1032
  EventHandler.on(this._element, EVENT_CLICK, SELECTOR_TOOLBAR_EXPORT, () => {
875
- this.downloadCsv({ scope: 'filtered', ...this._toolbarConfig?.exportOptions })
1033
+ const { exporter, ...exportOptions } = this._toolbarConfig?.exportOptions ?? {};
1034
+ (exporter ?? downloadCsv)(this._table, { scope: 'filtered', ...exportOptions })
1035
+ })
1036
+
1037
+ EventHandler.on(this._element, EVENT_CLICK, SELECTOR_TOOLBAR_PRINT, () => {
1038
+ this.print()
876
1039
  })
877
1040
 
878
1041
  EventHandler.on(this._element, EVENT_CLICK, SELECTOR_TOOLBAR_UNDO, () => {
@@ -883,42 +1046,6 @@ private _undoStack: DataGridEditCommitEntry[] = []
883
1046
  this.redo()
884
1047
  })
885
1048
 
886
- EventHandler.on(this._element, EVENT_DRAGSTART, SELECTOR_DRAGGABLE, event => {
887
- this._onColumnDragStart(event)
888
- })
889
-
890
- EventHandler.on(this._element, EVENT_DRAGOVER, SELECTOR_DRAGGABLE, event => {
891
- if (!this._dragColumn) {
892
- return
893
- }
894
-
895
- const target = (event.delegateTarget ?? event.target) as HTMLElement
896
- const targetKey = target.dataset.coreuiColumn
897
- // A drop is only valid within the drag source's pinned region; leaving
898
- // other targets un-prevented shows the browser's no-drop cursor there.
899
- const source = this._table.getColumn(this._dragColumn)
900
- const targetColumn = targetKey ? this._table.getColumn(targetKey) : undefined
901
- if (!source || !targetColumn || targetKey === this._dragColumn || source.getIsPinned() !== targetColumn.getIsPinned()) {
902
- return
903
- }
904
-
905
- event.preventDefault()
906
- target.classList.add(CLASS_NAME_DROP_TARGET)
907
- })
908
-
909
- EventHandler.on(this._element, EVENT_DRAGLEAVE, SELECTOR_DRAGGABLE, event => {
910
- const target = (event.delegateTarget ?? event.target) as HTMLElement
911
- target.classList.remove(CLASS_NAME_DROP_TARGET)
912
- })
913
-
914
- EventHandler.on(this._element, EVENT_DROP, SELECTOR_DRAGGABLE, event => {
915
- this._onColumnDrop(event)
916
- })
917
-
918
- EventHandler.on(this._element, EVENT_DRAGEND, SELECTOR_DRAGGABLE, () => {
919
- this._endColumnDrag()
920
- })
921
-
922
1049
  EventHandler.on(this._element, EVENT_MOUSEDOWN, SELECTOR_RESIZER, event => {
923
1050
  this._startColumnResize(event)
924
1051
  })
@@ -1147,6 +1274,22 @@ private _undoStack: DataGridEditCommitEntry[] = []
1147
1274
  return
1148
1275
  }
1149
1276
 
1277
+ if (this._config.cellSelection && (event.ctrlKey || event.metaKey) && found.cell.rowIndex >= 0) {
1278
+ const key = event.key.toLowerCase()
1279
+ if (key === 'a') {
1280
+ event.preventDefault()
1281
+ this._table.selectAllCells()
1282
+ this._scheduler.schedule()
1283
+ return
1284
+ }
1285
+
1286
+ if (key === 'c') {
1287
+ event.preventDefault()
1288
+ this._copyCellSelection()
1289
+ return
1290
+ }
1291
+ }
1292
+
1150
1293
  const result = resolveNavigation(event.key, event.ctrlKey || event.metaKey, found.cell, this._activeCellContext())
1151
1294
  if (!result) {
1152
1295
  return
@@ -1154,6 +1297,19 @@ private _undoStack: DataGridEditCommitEntry[] = []
1154
1297
 
1155
1298
  event.preventDefault()
1156
1299
 
1300
+ // Shift+arrow grows the range from its anchor and leaves focus put; a
1301
+ // plain arrow moves focus and collapses the range to the new cell.
1302
+ if (this._config.cellSelection && found.cell.rowIndex >= 0 && result.cell.rowIndex >= 0) {
1303
+ const direction = CELL_SELECTION_DIRECTIONS[
1304
+ isElementRTL(this._element) ? (INVERTED_ARROWS[event.key] ?? event.key) : event.key
1305
+ ]
1306
+ if (event.shiftKey && direction) {
1307
+ this._table.extendCellSelection(direction)
1308
+ this._scheduler.schedule()
1309
+ return
1310
+ }
1311
+ }
1312
+
1157
1313
  if (result.pageDelta === 1 && this._table.getCanNextPage()) {
1158
1314
  this._table.nextPage()
1159
1315
  } else if (result.pageDelta === -1 && this._table.getCanPreviousPage()) {
@@ -1163,6 +1319,24 @@ private _undoStack: DataGridEditCommitEntry[] = []
1163
1319
  this._setActiveCell(result.cell)
1164
1320
  }
1165
1321
 
1322
+ // The table-core Cell behind a DOM cell, or null when cell selection is off
1323
+ // or the target is not a data cell.
1324
+ private _tableCellFromTarget(event: Event & Record<string, unknown>) {
1325
+ if (!this._config.cellSelection) {
1326
+ return null
1327
+ }
1328
+
1329
+ const element = (event.delegateTarget ?? event.target) as HTMLElement
1330
+ const columnId = element.dataset.coreuiColumn
1331
+ const rowIndex = Number(element.closest<HTMLElement>('tr')?.dataset.coreuiIndex ?? -1)
1332
+ if (!columnId || rowIndex < 0) {
1333
+ return null
1334
+ }
1335
+
1336
+ const row = this._table.getRowModel().rows[rowIndex]
1337
+ return row?.getAllCells().find(cell => cell.column.id === columnId) ?? null
1338
+ }
1339
+
1166
1340
  private _descendIntoCell(cellElement: HTMLElement): void {
1167
1341
  cellElement.querySelector<HTMLElement>(FOCUSABLE_SELECTOR)?.focus({ preventScroll: true })
1168
1342
  }
@@ -1429,6 +1603,15 @@ private _undoStack: DataGridEditCommitEntry[] = []
1429
1603
 
1430
1604
  private _setActiveCell(cell: ActiveCell): void {
1431
1605
  this._activeCell?.set(cell, { focus: true })
1606
+ // The active cell is the grid's single focus owner; cell selection follows
1607
+ // it rather than keeping a second anchor of its own.
1608
+ if (this._config.cellSelection && cell.rowIndex >= 0) {
1609
+ const row = this._table.getRowModel().rows[cell.rowIndex]
1610
+ if (row) {
1611
+ this._table.setFocusedCell(row.id, cell.columnId)
1612
+ }
1613
+ }
1614
+
1432
1615
  if (this._virtualizer && cell.rowIndex >= 0) {
1433
1616
  // No-op when the row is already in view (align: auto); otherwise the
1434
1617
  // focus request stays pending until the row renders on the next flush.
@@ -1436,6 +1619,28 @@ private _undoStack: DataGridEditCommitEntry[] = []
1436
1619
  }
1437
1620
  }
1438
1621
 
1622
+ // The columns the selection's column indexes address, with the formatter
1623
+ // each cell displays through — a copied date pastes as the date on screen.
1624
+ private _printColumns(): PrintColumn[] {
1625
+ return this._visibleColumns().map(column => ({
1626
+ id: column.key,
1627
+ label: column.label ?? column.key,
1628
+ formatter: column.formatter
1629
+ }))
1630
+ }
1631
+
1632
+ private _clipboardColumns(): ClipboardColumn[] {
1633
+ return this._visibleColumns().map(column => ({ id: column.key, formatter: column.formatter }))
1634
+ }
1635
+
1636
+ private _copyCellSelection(): void {
1637
+ copyCellSelection(this._table, this._clipboardColumns()).then(text => {
1638
+ if (text !== '') {
1639
+ EventHandler.trigger(this._element, EVENT_CELL_COPY, { text })
1640
+ }
1641
+ }, () => {})
1642
+ }
1643
+
1439
1644
  private _activeCellContext(): ActiveCellContext {
1440
1645
  const rowCount = this._table.getRowModel().rows.length
1441
1646
  const paginated = Boolean(this._config.pagination) || Boolean(this._config.dataProvider)
@@ -1445,9 +1650,10 @@ private _undoStack: DataGridEditCommitEntry[] = []
1445
1650
  columnIds: this._visibleLeafColumns().map(column => column.id),
1446
1651
  rowCount,
1447
1652
  pageStep: paginated ?
1448
- this._table.getState().pagination.pageSize :
1653
+ this._table.store.state.pagination.pageSize :
1449
1654
  Math.max(1, viewportRows || rowCount),
1450
- paginated
1655
+ paginated,
1656
+ rtl: isElementRTL(this._element)
1451
1657
  }
1452
1658
  }
1453
1659
 
@@ -1563,23 +1769,23 @@ private _undoStack: DataGridEditCommitEntry[] = []
1563
1769
 
1564
1770
  if (this._config.columnPinning) {
1565
1771
  const pinned = column.getIsPinned()
1566
- if (pinned !== 'left') {
1772
+ if (pinned !== 'start') {
1567
1773
  actions.push({
1568
- key: 'pin-left',
1569
- label: this._labels.pinLeft,
1774
+ key: 'pin-start',
1775
+ label: this._labels.pinStart,
1570
1776
  group: 'pinning',
1571
- icon: this._sanitizeIcon(this._config.pinLeftIcon),
1572
- run: () => column.pin('left')
1777
+ icon: this._sanitizeIcon(this._config.pinStartIcon),
1778
+ run: () => column.pin('start')
1573
1779
  })
1574
1780
  }
1575
1781
 
1576
- if (pinned !== 'right') {
1782
+ if (pinned !== 'end') {
1577
1783
  actions.push({
1578
- key: 'pin-right',
1579
- label: this._labels.pinRight,
1784
+ key: 'pin-end',
1785
+ label: this._labels.pinEnd,
1580
1786
  group: 'pinning',
1581
- icon: this._sanitizeIcon(this._config.pinRightIcon),
1582
- run: () => column.pin('right')
1787
+ icon: this._sanitizeIcon(this._config.pinEndIcon),
1788
+ run: () => column.pin('end')
1583
1789
  })
1584
1790
  }
1585
1791
 
@@ -1595,23 +1801,23 @@ private _undoStack: DataGridEditCommitEntry[] = []
1595
1801
  }
1596
1802
 
1597
1803
  if (this._config.columnOrder && config.movable !== false) {
1598
- const region = this._visibleLeafColumns().filter(item => item.getIsPinned() === column.getIsPinned())
1804
+ const region = this._visibleLeafColumns().filter(item => this._pinnedRegion(item) === this._pinnedRegion(column))
1599
1805
  const index = region.findIndex(item => item.id === columnKey)
1600
1806
  actions.push(
1601
1807
  {
1602
- key: 'move-left',
1603
- label: this._labels.moveLeft,
1808
+ key: 'move-start',
1809
+ label: this._labels.moveStart,
1604
1810
  disabled: index <= 0,
1605
1811
  group: 'ordering',
1606
- icon: this._sanitizeIcon(this._config.moveLeftIcon),
1812
+ icon: this._sanitizeIcon(this._config.moveStartIcon),
1607
1813
  run: () => this._moveColumn(columnKey, -1)
1608
1814
  },
1609
1815
  {
1610
- key: 'move-right',
1611
- label: this._labels.moveRight,
1816
+ key: 'move-end',
1817
+ label: this._labels.moveEnd,
1612
1818
  disabled: index === region.length - 1,
1613
1819
  group: 'ordering',
1614
- icon: this._sanitizeIcon(this._config.moveRightIcon),
1820
+ icon: this._sanitizeIcon(this._config.moveEndIcon),
1615
1821
  run: () => this._moveColumn(columnKey, 1)
1616
1822
  }
1617
1823
  )
@@ -1644,7 +1850,7 @@ private _undoStack: DataGridEditCommitEntry[] = []
1644
1850
  // missing ids at the end when they are shown again.
1645
1851
  private _orderedLeafIds(): string[] {
1646
1852
  const allIds = this._table.getAllLeafColumns().map(column => column.id)
1647
- const order = this._table.getState().columnOrder
1853
+ const order = this._table.store.state.columnOrder ?? []
1648
1854
  if (order.length === 0) {
1649
1855
  return allIds
1650
1856
  }
@@ -1661,10 +1867,10 @@ private _undoStack: DataGridEditCommitEntry[] = []
1661
1867
  return
1662
1868
  }
1663
1869
 
1664
- const region = column.getIsPinned()
1870
+ const region = this._pinnedRegion(column)
1665
1871
  const ids = this._orderedLeafIds()
1666
1872
  const regionIds = this._visibleLeafColumns()
1667
- .filter(item => item.getIsPinned() === region)
1873
+ .filter(item => this._pinnedRegion(item) === region)
1668
1874
  .map(item => item.id)
1669
1875
  const neighbor = regionIds[regionIds.indexOf(columnKey) + direction]
1670
1876
  if (!neighbor) {
@@ -1678,64 +1884,92 @@ private _undoStack: DataGridEditCommitEntry[] = []
1678
1884
  this._table.setColumnOrder(ids)
1679
1885
  }
1680
1886
 
1681
- private _onColumnDragStart(event: Event & Record<string, unknown>): void {
1682
- // A drag started from the resize handle is a resize, not a reorder. The
1683
- // handle's mousedown is preventDefault'd so the browser never starts a drag
1684
- // there; this ancestry check is a secondary guard.
1685
- if ((event.target as HTMLElement).closest?.(SELECTOR_RESIZER)) {
1686
- event.preventDefault()
1887
+ // Pointer-based, not HTML5 drag events: the controller owns the ghost, the
1888
+ // drop indicator and the edge auto-scroll, and behaves the same on touch.
1889
+ private _initColumnDrag(): void {
1890
+ if (!this._config.columnOrder) {
1687
1891
  return
1688
1892
  }
1689
1893
 
1690
- const target = (event.delegateTarget ?? event.target) as HTMLElement
1691
- this._dragColumn = target.dataset.coreuiColumn ?? null
1692
- target.classList.add(CLASS_NAME_DRAGGING)
1894
+ this._columnDrag = createColumnDragController({
1895
+ root: this._element as HTMLElement,
1896
+ getScrollElement: () => this._viewportElement,
1897
+ canDrop: (sourceKey, targetKey) => {
1898
+ const source = this._table.getColumn(sourceKey)
1899
+ const target = this._table.getColumn(targetKey)
1900
+ return Boolean(source && target) &&
1901
+ sourceKey !== targetKey &&
1902
+ this._pinnedRegion(source!) === this._pinnedRegion(target!)
1903
+ },
1904
+ onReorder: (sourceKey, targetKey, after) => this._moveColumnTo(sourceKey, targetKey, after)
1905
+ })
1906
+ }
1693
1907
 
1694
- const { dataTransfer } = event as unknown as DragEvent
1695
- if (dataTransfer) {
1696
- dataTransfer.effectAllowed = 'move'
1697
- dataTransfer.setData('text/plain', this._dragColumn ?? '')
1908
+ // Rows are indexed within the current row model; the offset turns that into
1909
+ // a position in `items`.
1910
+ private _rowIndexOffset(): number {
1911
+ // The infinite buffer always starts at the first row, so its indices are
1912
+ // already absolute.
1913
+ if (this._config.infiniteScroll || (!this._config.pagination && !this._config.dataProvider)) {
1914
+ return 0
1698
1915
  }
1699
- }
1700
1916
 
1701
- private _onColumnDrop(event: Event & Record<string, unknown>): void {
1702
- event.preventDefault()
1703
- const target = (event.delegateTarget ?? event.target) as HTMLElement
1704
- const targetKey = target.dataset.coreuiColumn
1705
- const sourceKey = this._dragColumn
1706
- this._endColumnDrag()
1917
+ const { pageIndex, pageSize } = this._table.store.state.pagination
1918
+ return pageIndex * pageSize
1919
+ }
1707
1920
 
1708
- if (!sourceKey || !targetKey || sourceKey === targetKey) {
1921
+ private _initRowDrag(): void {
1922
+ if (!this._config.rowOrder) {
1709
1923
  return
1710
1924
  }
1711
1925
 
1712
- const source = this._table.getColumn(sourceKey)
1713
- const targetColumn = this._table.getColumn(targetKey)
1714
- // Reordering never crosses a pinning boundary — pin membership is state,
1715
- // not position.
1716
- if (!source || !targetColumn || source.getIsPinned() !== targetColumn.getIsPinned()) {
1926
+ this._rowDrag = createRowDragController({
1927
+ root: this._element as HTMLElement,
1928
+ getScrollElement: () => this._viewportElement,
1929
+ onReorder: (sourceIndex, targetIndex, after) => this._moveRowTo(sourceIndex, targetIndex, after)
1930
+ })
1931
+ }
1932
+
1933
+ // The grid never mutates `items` — it hands the reordered array to the app
1934
+ // through rowOrderChange, the same contract inline editing uses.
1935
+ private _moveRowTo(sourceIndex: number, targetIndex: number, after: boolean): void {
1936
+ const offset = this._rowIndexOffset()
1937
+ const items = [...this._config.items]
1938
+ const from = offset + sourceIndex
1939
+ const [item] = items.splice(from, 1)
1940
+ if (item === undefined) {
1717
1941
  return
1718
1942
  }
1719
1943
 
1944
+ // Resolved AFTER the removal, so the index still points at the target
1945
+ // whichever direction the row travelled.
1946
+ const target = offset + targetIndex
1947
+ const to = target > from ? target - 1 : target
1948
+ const newIndex = after ? to + 1 : to
1949
+ items.splice(newIndex, 0, item)
1950
+
1951
+ EventHandler.trigger(this._element, EVENT_ROW_ORDER_CHANGE, {
1952
+ items, item, oldIndex: from, newIndex
1953
+ })
1954
+ }
1955
+
1956
+ private _moveColumnTo(sourceKey: string, targetKey: string, after: boolean): void {
1720
1957
  const ids = this._orderedLeafIds()
1721
1958
  const from = ids.indexOf(sourceKey)
1722
- const to = ids.indexOf(targetKey)
1723
- if (from === -1 || to === -1) {
1959
+ if (from === -1) {
1724
1960
  return
1725
1961
  }
1726
1962
 
1727
- // Removing the source first makes inserting at the target's original index
1728
- // land after it when dragging right and before it when dragging left.
1729
1963
  ids.splice(from, 1)
1730
- ids.splice(to, 0, sourceKey)
1731
- this._table.setColumnOrder(ids)
1732
- }
1733
-
1734
- private _endColumnDrag(): void {
1735
- this._dragColumn = null
1736
- for (const cell of this._element.querySelectorAll(`.${CLASS_NAME_DRAGGING}, .${CLASS_NAME_DROP_TARGET}`)) {
1737
- cell.classList.remove(CLASS_NAME_DRAGGING, CLASS_NAME_DROP_TARGET)
1964
+ // Resolved AFTER the removal, so the index still points at the target
1965
+ // whichever direction the column travelled.
1966
+ const to = ids.indexOf(targetKey)
1967
+ if (to === -1) {
1968
+ return
1738
1969
  }
1970
+
1971
+ ids.splice(after ? to + 1 : to, 0, sourceKey)
1972
+ this._table.setColumnOrder(ids)
1739
1973
  }
1740
1974
 
1741
1975
  private _startColumnResize(event: Event & Record<string, unknown>): void {
@@ -1751,6 +1985,11 @@ private _undoStack: DataGridEditCommitEntry[] = []
1751
1985
  event.preventDefault()
1752
1986
  }
1753
1987
 
1988
+ // The direction table-core snapshotted at construction may be stale — an
1989
+ // app that swaps language flips dir on a live grid. A resize is the only
1990
+ // moment it matters, and it is rare enough to re-read here.
1991
+ syncResizeDirection(this._table, this._element)
1992
+
1754
1993
  const header = this._table.getHeaderGroups()[0]?.headers.find(item => item.column.id === key)
1755
1994
  header?.getResizeHandler()(event)
1756
1995
  }
@@ -1797,7 +2036,7 @@ private _undoStack: DataGridEditCommitEntry[] = []
1797
2036
  this._dataSource = createDataSource({
1798
2037
  provider: request => dataProvider(request),
1799
2038
  getRequest: () => {
1800
- const state = this._table.getState()
2039
+ const { state } = this._table.store
1801
2040
  return {
1802
2041
  columnFilters: state.columnFilters,
1803
2042
  globalFilter: (state.globalFilter as string | undefined) ?? '',
@@ -1805,13 +2044,29 @@ private _undoStack: DataGridEditCommitEntry[] = []
1805
2044
  sorting: state.sorting
1806
2045
  }
1807
2046
  },
1808
- onLoad: response => {
2047
+ onLoad: (response, request) => {
1809
2048
  this._stopEditing({ commit: false })
1810
2049
  this._loadFailed = false
2050
+ this._loadMorePending = false
1811
2051
  this._dataVersion++
2052
+
2053
+ let { items } = response
2054
+ if (this._config.infiniteScroll) {
2055
+ const firstPage = request.pagination.pageIndex === 0
2056
+ items = firstPage ? response.items : [...this._loadedItems, ...response.items]
2057
+ this._loadedItems = items
2058
+ // Appended rows land at fresh indices; a first page puts different
2059
+ // data at indices the virtualizer has already measured — and shrinks
2060
+ // the buffer back under a viewport that may be scrolled far past it.
2061
+ if (firstPage) {
2062
+ this._viewportElement.scrollTop = 0
2063
+ this._virtualizer?.remeasure()
2064
+ }
2065
+ }
2066
+
1812
2067
  this._table.setOptions(previous => ({
1813
2068
  ...previous,
1814
- data: response.items,
2069
+ data: items,
1815
2070
  rowCount: response.totalRows
1816
2071
  }))
1817
2072
  EventHandler.trigger(this._element, EVENT_DATA_LOAD, {
@@ -1824,16 +2079,17 @@ private _undoStack: DataGridEditCommitEntry[] = []
1824
2079
  onError: error => {
1825
2080
  this._stopEditing({ commit: false })
1826
2081
  this._loadFailed = true
2082
+ this._loadMorePending = false
2083
+ this._loadedItems = []
1827
2084
  this._dataVersion++
1828
2085
  this._table.setOptions(previous => ({ ...previous, data: [], rowCount: 0 }))
1829
2086
  EventHandler.trigger(this._element, EVENT_DATA_ERROR, { error })
1830
2087
  this._scheduler.schedule()
1831
2088
  },
1832
2089
  onLoadingChange: loading => {
1833
- if (this._loading) {
1834
- this._loading.setVisible(loading)
1835
- }
1836
-
2090
+ const appending = Boolean(this._config.infiniteScroll) && this._loadedItems.length > 0
2091
+ this._loading?.setVisible(loading && !appending)
2092
+ this._loadingMore?.setVisible(loading && appending)
1837
2093
  this._viewportElement.setAttribute('aria-busy', String(loading))
1838
2094
  }
1839
2095
  })
@@ -1841,14 +2097,55 @@ private _undoStack: DataGridEditCommitEntry[] = []
1841
2097
  this._dataSource.load()
1842
2098
  }
1843
2099
 
2100
+ // The trigger is the last RENDERED row index, not a scroll ratio: measured
2101
+ // row heights make the ratio meaningless (the total size keeps moving as
2102
+ // rows are measured, so a ratio either fires early or never).
2103
+ private _maybeLoadMore(): void {
2104
+ if (!this._config.infiniteScroll || !this._dataSource || !this._virtualizer || this._loadMorePending) {
2105
+ return
2106
+ }
2107
+
2108
+ const loaded = this._table.getRowModel().rows.length
2109
+ if (loaded === 0 || loaded >= this._getTotalRows()) {
2110
+ return
2111
+ }
2112
+
2113
+ const options = typeof this._config.infiniteScroll === 'object' ? this._config.infiniteScroll : {}
2114
+ const threshold = options.threshold ?? this._config.overscan
2115
+ const rendered = this._virtualizer.instance.getVirtualItems()
2116
+ const last = rendered[rendered.length - 1]
2117
+ if (!last || last.index < loaded - 1 - threshold) {
2118
+ return
2119
+ }
2120
+
2121
+ this._loadMorePending = true
2122
+ this._table.setPageIndex(this._table.store.state.pagination.pageIndex + 1)
2123
+ }
2124
+
1844
2125
  // Visible leaf columns in VISUAL order: left-pinned, center (columnOrder),
1845
2126
  // right-pinned — getVisibleLeafColumns() alone does not regroup pinning.
2127
+ // Without the pinning feature there are no regions to regroup, and without
2128
+ // visibility every leaf column is visible.
1846
2129
  private _visibleLeafColumns() {
1847
- return [
1848
- ...this._table.getLeftVisibleLeafColumns(),
1849
- ...this._table.getCenterVisibleLeafColumns(),
1850
- ...this._table.getRightVisibleLeafColumns()
1851
- ]
2130
+ if (typeof this._table.getCenterVisibleLeafColumns === 'function') {
2131
+ return [
2132
+ ...this._table.getStartVisibleLeafColumns(),
2133
+ ...this._table.getCenterVisibleLeafColumns(),
2134
+ ...this._table.getEndVisibleLeafColumns()
2135
+ ]
2136
+ }
2137
+
2138
+ if (typeof this._table.getVisibleLeafColumns === 'function') {
2139
+ return this._table.getVisibleLeafColumns()
2140
+ }
2141
+
2142
+ return this._table.getAllLeafColumns()
2143
+ }
2144
+
2145
+ // Ordering paths compare pinned regions even on grids that never enabled
2146
+ // pinning - without the feature every column sits in the unpinned region.
2147
+ private _pinnedRegion(column: { getIsPinned?: () => 'start' | 'end' | false }): 'start' | 'end' | false {
2148
+ return typeof column.getIsPinned === 'function' ? column.getIsPinned() : false
1852
2149
  }
1853
2150
 
1854
2151
  private _chooserColumns(): ColumnChooserColumn[] {
@@ -1893,14 +2190,14 @@ private _undoStack: DataGridEditCommitEntry[] = []
1893
2190
  }
1894
2191
 
1895
2192
  private _getFilteredColumns(): string[] {
1896
- return this._table.getState().columnFilters.map(filter => filter.id)
2193
+ return this._table.store.state.columnFilters.map(filter => filter.id)
1897
2194
  }
1898
2195
 
1899
2196
  private _getPinning(): GridPinning {
1900
- // Visible columns only - getLeftLeafColumns/getPinnedIndex include hidden
2197
+ // Visible columns only - getStartLeafColumns/getPinnedIndex include hidden
1901
2198
  // columns, which would leave the edge class on a column nobody sees.
1902
- const leftColumns = this._table.getLeftLeafColumns().filter(column => column.getIsVisible())
1903
- const rightColumns = this._table.getRightLeafColumns().filter(column => column.getIsVisible())
2199
+ const leftColumns = this._table.getStartLeafColumns().filter(column => column.getIsVisible())
2200
+ const rightColumns = this._table.getEndLeafColumns().filter(column => column.getIsVisible())
1904
2201
  // The selection cell is not a TanStack column; anchor it to the left as
1905
2202
  // soon as any data column is pinned there so the checkbox travels with them.
1906
2203
  const selectPinned = Boolean(this._config.rowSelection) && leftColumns.length > 0
@@ -1908,8 +2205,8 @@ private _undoStack: DataGridEditCommitEntry[] = []
1908
2205
 
1909
2206
  for (const [index, column] of leftColumns.entries()) {
1910
2207
  columns[column.id] = {
1911
- side: 'left',
1912
- offset: column.getStart('left'),
2208
+ side: 'start',
2209
+ offset: column.getStart('start'),
1913
2210
  edge: index === leftColumns.length - 1,
1914
2211
  afterSelect: selectPinned
1915
2212
  }
@@ -1917,8 +2214,8 @@ private _undoStack: DataGridEditCommitEntry[] = []
1917
2214
 
1918
2215
  for (const [index, column] of rightColumns.entries()) {
1919
2216
  columns[column.id] = {
1920
- side: 'right',
1921
- offset: column.getAfter('right'),
2217
+ side: 'end',
2218
+ offset: column.getAfter('end'),
1922
2219
  edge: index === 0,
1923
2220
  afterSelect: false
1924
2221
  }
@@ -1928,7 +2225,7 @@ private _undoStack: DataGridEditCommitEntry[] = []
1928
2225
  columns,
1929
2226
  select: selectPinned ?
1930
2227
  {
1931
- side: 'left', offset: 0, edge: false, afterSelect: false
2228
+ side: 'start', offset: 0, edge: false, afterSelect: false
1932
2229
  } :
1933
2230
  null
1934
2231
  }
@@ -1986,10 +2283,14 @@ null
1986
2283
  return this._table.options.rowCount ?? 0
1987
2284
  }
1988
2285
 
2286
+ if (typeof this._table.getFilteredRowModel !== 'function') {
2287
+ return this._table.getCoreRowModel().rows.length
2288
+ }
2289
+
1989
2290
  return this._table.getFilteredRowModel().rows.length
1990
2291
  }
1991
2292
 
1992
- private _notifyStateChange(previous: TableState, next: TableState): void {
2293
+ private _notifyStateChange(previous: DataGridTableState, next: DataGridTableState): void {
1993
2294
  // TanStack's resize handler keeps document-level listeners until mouseup;
1994
2295
  // a dispose mid-drag would re-enter here against a torn-down instance.
1995
2296
  if (!this._element) {
@@ -2025,16 +2326,22 @@ null
2025
2326
  // auto-reset the page index, which replaces the state object without
2026
2327
  // changing it - identity alone would kill an edit right after its
2027
2328
  // editCommit handler applied the change.
2329
+ // Infinite scroll is the exception: its page bumps only append, so every
2330
+ // row already on screen keeps its index, its measurement and its focus.
2028
2331
  if (
2029
2332
  previous.sorting !== next.sorting ||
2030
2333
  previous.columnFilters !== next.columnFilters ||
2031
2334
  previous.globalFilter !== next.globalFilter ||
2032
- previous.pagination.pageIndex !== next.pagination.pageIndex ||
2033
- previous.pagination.pageSize !== next.pagination.pageSize
2335
+ (!this._config.infiniteScroll &&
2336
+ (previous.pagination?.pageIndex !== next.pagination?.pageIndex ||
2337
+ previous.pagination?.pageSize !== next.pagination?.pageSize))
2034
2338
  ) {
2035
2339
  this._stopEditing({ commit: true, force: true })
2036
2340
  this._selectionAnchorIndex = null
2037
2341
  this._activeCell?.reset()
2342
+ // Row measurements are cached by index, so the same index now holding a
2343
+ // different row makes them stale.
2344
+ this._virtualizer?.remeasure()
2038
2345
  }
2039
2346
 
2040
2347
  if (previous.sorting !== next.sorting) {
@@ -2064,7 +2371,9 @@ null
2064
2371
  })
2065
2372
  }
2066
2373
 
2067
- if (previous.pagination !== next.pagination) {
2374
+ // No paginationChange under infinite scroll — pagination is internal
2375
+ // there (no widget, not user-adjustable); dataLoad is the page signal.
2376
+ if (previous.pagination !== next.pagination && !this._config.infiniteScroll) {
2068
2377
  EventHandler.trigger(this._element, EVENT_PAGINATION_CHANGE, {
2069
2378
  pagination: next.pagination
2070
2379
  })
@@ -2097,15 +2406,26 @@ null
2097
2406
  if (this._dataSource) {
2098
2407
  const refetchNeeded = filtersChanged || previous.sorting !== next.sorting
2099
2408
 
2100
- if (refetchNeeded && next.pagination.pageIndex !== 0) {
2101
- // Back to page one first; the resulting pagination change re-enters
2102
- // this handler and triggers the (debounced) fetch exactly once.
2103
- this._table.setPageIndex(0)
2409
+ if (refetchNeeded) {
2410
+ if (next.pagination.pageIndex !== 0) {
2411
+ // Back to page one. The write lands synchronously, so the request the
2412
+ // debounced fetch reads already carries index 0 - but it also echoes
2413
+ // back here as a pagination change, which must not fetch again.
2414
+ this._pageResetPending = true
2415
+ this._table.setPageIndex(0)
2416
+ }
2417
+
2418
+ this._dataSource.load()
2104
2419
  return
2105
2420
  }
2106
2421
 
2107
- if (refetchNeeded || previous.pagination !== next.pagination) {
2108
- this._dataSource.load()
2422
+ if (previous.pagination !== next.pagination) {
2423
+ const echoedPageReset = this._pageResetPending && next.pagination.pageIndex === 0
2424
+ this._pageResetPending = false
2425
+
2426
+ if (!echoedPageReset) {
2427
+ this._dataSource.load()
2428
+ }
2109
2429
  }
2110
2430
  }
2111
2431
  }
@@ -2136,14 +2456,16 @@ null
2136
2456
  order: this._visibleLeafColumns().map(column => column.id),
2137
2457
  pinning: this._config.columnPinning ? this._getPinning() : null,
2138
2458
  resizingColumn: this._config.columnSizing ?
2139
- this._table.getState().columnSizingInfo.isResizingColumn :
2459
+ this._table.store.state.columnResizing.isResizingColumn :
2140
2460
  false,
2141
2461
  sizes: (this._config.columnSizing || this._config.columnPinning) ? this._getColumnSizes() : null,
2142
- sorting: this._table.getState().sorting,
2462
+ sorting: this._table.store.state.sorting ?? [],
2143
2463
  selectAll: this._config.rowSelection ?
2144
2464
  {
2145
2465
  checked: this._table.getIsAllRowsSelected(),
2146
- indeterminate: this._table.getIsSomeRowsSelected()
2466
+ // getIsSomeRowsSelected() means "at least one" - the all-selected
2467
+ // case has to be excluded for the tri-state checkbox.
2468
+ indeterminate: this._table.getIsSomeRowsSelected() && !this._table.getIsAllRowsSelected()
2147
2469
  } :
2148
2470
  null
2149
2471
  })
@@ -2169,10 +2491,12 @@ null
2169
2491
  for (const instance of this._paginations) {
2170
2492
  instance.refresh()
2171
2493
  }
2494
+
2495
+ this._maybeLoadMore()
2172
2496
  }
2173
2497
 
2174
2498
  private _paginationState() {
2175
- const { pagination } = this._table.getState()
2499
+ const { pagination } = this._table.store.state
2176
2500
  return {
2177
2501
  pageCount: this._table.getPageCount(),
2178
2502
  pageIndex: pagination.pageIndex,
@@ -2203,6 +2527,4 @@ null
2203
2527
  }
2204
2528
  }
2205
2529
 
2206
- defineJQueryPlugin(DataGrid)
2207
-
2208
2530
  export default DataGrid