@coreui/react-data-grid 0.1.0-rc.0 → 0.2.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 (82) hide show
  1. package/README.md +3 -2
  2. package/dist/cjs/CDataGrid.d.ts +11 -1
  3. package/dist/cjs/CDataGrid.js +228 -43
  4. package/dist/cjs/CDataGrid.js.map +1 -1
  5. package/dist/cjs/CDataGridBody.d.ts +9 -4
  6. package/dist/cjs/CDataGridBody.js +54 -11
  7. package/dist/cjs/CDataGridBody.js.map +1 -1
  8. package/dist/cjs/CDataGridColumnChooser.d.ts +2 -3
  9. package/dist/cjs/CDataGridColumnChooser.js.map +1 -1
  10. package/dist/cjs/CDataGridFilterMenu.d.ts +2 -3
  11. package/dist/cjs/CDataGridFilterMenu.js.map +1 -1
  12. package/dist/cjs/CDataGridHeader.d.ts +4 -4
  13. package/dist/cjs/CDataGridHeader.js +25 -66
  14. package/dist/cjs/CDataGridHeader.js.map +1 -1
  15. package/dist/cjs/CDataGridHeaderMenu.d.ts +2 -3
  16. package/dist/cjs/CDataGridHeaderMenu.js.map +1 -1
  17. package/dist/cjs/CDataGridPagination.d.ts +2 -3
  18. package/dist/cjs/CDataGridPagination.js +1 -1
  19. package/dist/cjs/CDataGridPagination.js.map +1 -1
  20. package/dist/cjs/CDataGridToolbar.d.ts +8 -6
  21. package/dist/cjs/CDataGridToolbar.js +7 -2
  22. package/dist/cjs/CDataGridToolbar.js.map +1 -1
  23. package/dist/cjs/columns.d.ts +38 -12
  24. package/dist/cjs/columns.js +51 -31
  25. package/dist/cjs/columns.js.map +1 -1
  26. package/dist/cjs/features.d.ts +52 -0
  27. package/dist/cjs/features.js +66 -0
  28. package/dist/cjs/features.js.map +1 -0
  29. package/dist/cjs/icons.d.ts +6 -4
  30. package/dist/cjs/icons.js +12 -8
  31. package/dist/cjs/icons.js.map +1 -1
  32. package/dist/cjs/index.d.ts +2 -1
  33. package/dist/cjs/index.js +3 -0
  34. package/dist/cjs/index.js.map +1 -1
  35. package/dist/cjs/pinning.d.ts +4 -5
  36. package/dist/cjs/pinning.js +10 -8
  37. package/dist/cjs/pinning.js.map +1 -1
  38. package/dist/cjs/toolbar.d.ts +3 -2
  39. package/dist/cjs/toolbar.js +5 -2
  40. package/dist/cjs/toolbar.js.map +1 -1
  41. package/dist/cjs/types.d.ts +81 -29
  42. package/dist/esm/CDataGrid.d.ts +11 -1
  43. package/dist/esm/CDataGrid.mjs +231 -46
  44. package/dist/esm/CDataGrid.mjs.map +1 -1
  45. package/dist/esm/CDataGridBody.d.ts +9 -4
  46. package/dist/esm/CDataGridBody.mjs +55 -12
  47. package/dist/esm/CDataGridBody.mjs.map +1 -1
  48. package/dist/esm/CDataGridColumnChooser.d.ts +2 -3
  49. package/dist/esm/CDataGridColumnChooser.mjs.map +1 -1
  50. package/dist/esm/CDataGridFilterMenu.d.ts +2 -3
  51. package/dist/esm/CDataGridFilterMenu.mjs.map +1 -1
  52. package/dist/esm/CDataGridHeader.d.ts +4 -4
  53. package/dist/esm/CDataGridHeader.mjs +27 -68
  54. package/dist/esm/CDataGridHeader.mjs.map +1 -1
  55. package/dist/esm/CDataGridHeaderMenu.d.ts +2 -3
  56. package/dist/esm/CDataGridHeaderMenu.mjs.map +1 -1
  57. package/dist/esm/CDataGridPagination.d.ts +2 -3
  58. package/dist/esm/CDataGridPagination.mjs +1 -1
  59. package/dist/esm/CDataGridPagination.mjs.map +1 -1
  60. package/dist/esm/CDataGridToolbar.d.ts +8 -6
  61. package/dist/esm/CDataGridToolbar.mjs +7 -2
  62. package/dist/esm/CDataGridToolbar.mjs.map +1 -1
  63. package/dist/esm/columns.d.ts +38 -12
  64. package/dist/esm/columns.mjs +51 -32
  65. package/dist/esm/columns.mjs.map +1 -1
  66. package/dist/esm/features.d.ts +52 -0
  67. package/dist/esm/features.mjs +63 -0
  68. package/dist/esm/features.mjs.map +1 -0
  69. package/dist/esm/icons.d.ts +6 -4
  70. package/dist/esm/icons.mjs +7 -5
  71. package/dist/esm/icons.mjs.map +1 -1
  72. package/dist/esm/index.d.ts +2 -1
  73. package/dist/esm/index.mjs +1 -0
  74. package/dist/esm/index.mjs.map +1 -1
  75. package/dist/esm/pinning.d.ts +4 -5
  76. package/dist/esm/pinning.mjs +10 -8
  77. package/dist/esm/pinning.mjs.map +1 -1
  78. package/dist/esm/toolbar.d.ts +3 -2
  79. package/dist/esm/toolbar.mjs +5 -2
  80. package/dist/esm/toolbar.mjs.map +1 -1
  81. package/dist/esm/types.d.ts +81 -29
  82. package/package.json +6 -6
@@ -1,6 +1,10 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
2
  import { evaluateColumnFilter } from '@coreui/data-grid/filters';
3
- import { useReactTable, getPaginationRowModel, getSortedRowModel, getFilteredRowModel, getFacetedUniqueValues, getFacetedRowModel, getCoreRowModel } from '@tanstack/react-table';
3
+ import { copyCellSelection } from '@coreui/data-grid/clipboard';
4
+ import { printGrid } from '@coreui/data-grid/print';
5
+ import { createColumnDragController, createRowDragController } from '@coreui/data-grid/drag';
6
+ import { assertFeatureCoverage } from '@coreui/data-grid/validate';
7
+ import { useTable } from '@tanstack/react-table';
4
8
  import { forwardRef, useMemo, useRef, useState, useCallback, useEffect } from 'react';
5
9
  import { clampActiveCell, resolveNavigation } from './active-cell.mjs';
6
10
  import { CDataGridBody } from './CDataGridBody.mjs';
@@ -8,8 +12,9 @@ import { CDataGridCellEditor } from './CDataGridCellEditor.mjs';
8
12
  import { CDataGridHeader } from './CDataGridHeader.mjs';
9
13
  import { CDataGridPagination } from './CDataGridPagination.mjs';
10
14
  import { CDataGridToolbar } from './CDataGridToolbar.mjs';
11
- import { visibleColumns } from './columns.mjs';
12
- import { DEFAULT_COLUMN_MENU_ICON, DEFAULT_FILTER_ICON, DEFAULT_HIDE_COLUMN_ICON, DEFAULT_MOVE_LEFT_ICON, DEFAULT_MOVE_RIGHT_ICON, DEFAULT_PIN_LEFT_ICON, DEFAULT_PIN_RIGHT_ICON, DEFAULT_SORT_ASCENDING_ICON, DEFAULT_SORT_DESCENDING_ICON, DEFAULT_SORT_NEUTRAL_ICON, DEFAULT_TOOLBAR_COLUMNS_ICON, DEFAULT_TOOLBAR_EXPORT_ICON, DEFAULT_TOOLBAR_REDO_ICON, DEFAULT_TOOLBAR_UNDO_ICON, DEFAULT_UNPIN_ICON } from './icons.mjs';
15
+ import { reorderColumn, canReorderColumn, visibleColumns } from './columns.mjs';
16
+ import { dataGridFeatures } from './features.mjs';
17
+ import { DEFAULT_COLUMN_MENU_ICON, DEFAULT_FILTER_ICON, DEFAULT_HIDE_COLUMN_ICON, DEFAULT_MOVE_START_ICON, DEFAULT_MOVE_END_ICON, DEFAULT_PIN_START_ICON, DEFAULT_PIN_END_ICON, DEFAULT_ROW_HANDLE_ICON, DEFAULT_SORT_ASCENDING_ICON, DEFAULT_SORT_DESCENDING_ICON, DEFAULT_SORT_NEUTRAL_ICON, DEFAULT_TOOLBAR_COLUMNS_ICON, DEFAULT_TOOLBAR_EXPORT_ICON, DEFAULT_TOOLBAR_PRINT_ICON, DEFAULT_TOOLBAR_REDO_ICON, DEFAULT_TOOLBAR_UNDO_ICON, DEFAULT_UNPIN_ICON } from './icons.mjs';
13
18
  import { DEFAULT_LABELS, interpolate } from '@coreui/data-grid/labels';
14
19
  import { resolveToolbar } from './toolbar.mjs';
15
20
  import { createTooltip } from './tooltip.mjs';
@@ -17,6 +22,8 @@ import { createTooltip } from './tooltip.mjs';
17
22
  const DEFAULT_PAGE_SIZE_OPTIONS = [5, 10, 20, 50];
18
23
  const STATE_STORAGE_PREFIX = 'coreui-data-grid:';
19
24
  const HISTORY_LIMIT = 100;
25
+ const INFINITE_SCROLL_PAGE_SIZE = 50;
26
+ const UNPAGINATED_PAGE_SIZE = Number.MAX_SAFE_INTEGER;
20
27
  const readPersistedState = (stateKey) => {
21
28
  if (!stateKey) {
22
29
  return null;
@@ -29,6 +36,12 @@ const readPersistedState = (stateKey) => {
29
36
  return null;
30
37
  }
31
38
  };
39
+ const CELL_SELECTION_DIRECTIONS = {
40
+ ArrowDown: 'down',
41
+ ArrowLeft: 'left',
42
+ ArrowRight: 'right',
43
+ ArrowUp: 'up'
44
+ };
32
45
  const FETCH_DEBOUNCE_MS = 100;
33
46
  // Module-level so an omitted `items` prop keeps a stable identity - a fresh
34
47
  // array per render would force TanStack to recompute row models every time.
@@ -41,17 +54,17 @@ const toTanStackFilterFn = (column) => {
41
54
  return (row, columnId, filterValue) => evaluateColumnFilter(row.getValue(columnId), filterValue, column.filterType);
42
55
  };
43
56
  const CDataGrid = forwardRef((props, ref) => {
44
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
45
- const { cellNavigation = false, className, columnFilters = false, columnMenu = false, columnMenuIcon = DEFAULT_COLUMN_MENU_ICON, columnOrder = false, columnPinning = false, columnSizing = false, columnVisibility = false, columns, dataProvider, editing = false, empty = 'No items found', filterIcon = DEFAULT_FILTER_ICON, globalFilter = false, hideColumnIcon = DEFAULT_HIDE_COLUMN_ICON, history = false, itemKey, items = EMPTY_ITEMS, labels, moveLeftIcon = DEFAULT_MOVE_LEFT_ICON, moveRightIcon = DEFAULT_MOVE_RIGHT_ICON, onDataError, onDataLoad, onEditCancel, onEditCommit, onEditStart, onFilterChange, onOrderChange, onPaginationChange, onPinningChange, onSelectionChange, onSizingChange, onSortingChange, onVisibilityChange, overscan = 10, pagination = false, pinLeftIcon = DEFAULT_PIN_LEFT_ICON, pinRightIcon = DEFAULT_PIN_RIGHT_ICON, rowHeight = 44, rowSelection = false, slots, sortAscendingIcon = DEFAULT_SORT_ASCENDING_ICON, sortDescendingIcon = DEFAULT_SORT_DESCENDING_ICON, sortNeutralIcon = DEFAULT_SORT_NEUTRAL_ICON, sorterVisibility, sorting = true, stateKey, tableRef, toolbar = false, toolbarColumnsIcon = DEFAULT_TOOLBAR_COLUMNS_ICON, toolbarExportIcon = DEFAULT_TOOLBAR_EXPORT_ICON, toolbarRedoIcon = DEFAULT_TOOLBAR_REDO_ICON, toolbarUndoIcon = DEFAULT_TOOLBAR_UNDO_ICON, unpinIcon = DEFAULT_UNPIN_ICON, virtualization = true, ...rest } = props;
57
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
58
+ const { autoRowHeight = false, cellNavigation = false, cellSelection = false, className, columnFilters = false, columnMenu = false, columnMenuIcon = DEFAULT_COLUMN_MENU_ICON, columnOrder = false, columnPinning = false, columnSizing = false, columnVisibility = false, columns, dataProvider, editing = false, empty = 'No items found', features = dataGridFeatures, filterIcon = DEFAULT_FILTER_ICON, globalFilter = false, hideColumnIcon = DEFAULT_HIDE_COLUMN_ICON, history = false, infiniteScroll = false, itemKey, items = EMPTY_ITEMS, labels, moveStartIcon = DEFAULT_MOVE_START_ICON, moveEndIcon = DEFAULT_MOVE_END_ICON, onDataError, onDataLoad, onEditCancel, onEditCommit, onEditStart, onFilterChange, onOrderChange, onPaginationChange, onPinningChange, onSelectionChange, onRowOrderChange, onCellCopy, onSizingChange, onSortingChange, onVisibilityChange, overscan = 10, pagination = false, pinStartIcon = DEFAULT_PIN_START_ICON, pinEndIcon = DEFAULT_PIN_END_ICON, rowHandleIcon = DEFAULT_ROW_HANDLE_ICON, rowHeight = 44, rowOrder = false, rowSelection = false, slots, sortAscendingIcon = DEFAULT_SORT_ASCENDING_ICON, sortDescendingIcon = DEFAULT_SORT_DESCENDING_ICON, sortNeutralIcon = DEFAULT_SORT_NEUTRAL_ICON, sorterVisibility, sorting = true, stateKey, tableRef, toolbar = false, toolbarColumnsIcon = DEFAULT_TOOLBAR_COLUMNS_ICON, printTitle, toolbarExportIcon = DEFAULT_TOOLBAR_EXPORT_ICON, toolbarPrintIcon = DEFAULT_TOOLBAR_PRINT_ICON, toolbarRedoIcon = DEFAULT_TOOLBAR_REDO_ICON, toolbarUndoIcon = DEFAULT_TOOLBAR_UNDO_ICON, unpinIcon = DEFAULT_UNPIN_ICON, virtualization = true, ...rest } = props;
46
59
  const mergedLabels = useMemo(() => ({ ...DEFAULT_LABELS, ...labels }), [labels]);
47
60
  const menuIcons = useMemo(() => ({
48
61
  columnMenu: columnMenuIcon,
49
62
  filter: filterIcon,
50
63
  hideColumn: hideColumnIcon,
51
- moveLeft: moveLeftIcon,
52
- moveRight: moveRightIcon,
53
- pinLeft: pinLeftIcon,
54
- pinRight: pinRightIcon,
64
+ moveStart: moveStartIcon,
65
+ moveEnd: moveEndIcon,
66
+ pinStart: pinStartIcon,
67
+ pinEnd: pinEndIcon,
55
68
  sortAscending: sortAscendingIcon,
56
69
  sortDescending: sortDescendingIcon,
57
70
  sortNeutral: sortNeutralIcon,
@@ -60,18 +73,19 @@ const CDataGrid = forwardRef((props, ref) => {
60
73
  columnMenuIcon,
61
74
  filterIcon,
62
75
  hideColumnIcon,
63
- moveLeftIcon,
64
- moveRightIcon,
65
- pinLeftIcon,
66
- pinRightIcon,
76
+ moveStartIcon,
77
+ moveEndIcon,
78
+ pinStartIcon,
79
+ pinEndIcon,
67
80
  sortAscendingIcon,
68
81
  sortDescendingIcon,
69
82
  sortNeutralIcon,
70
83
  unpinIcon
71
84
  ]);
72
85
  const menuBuilder = typeof columnMenu === 'function' ? columnMenu : null;
73
- // Editing needs the active-cell substrate (Enter/F2 target the active cell).
74
- const cellNavigationEnabled = cellNavigation || editing;
86
+ // Both need the active-cell substrate: editing targets it with Enter/F2,
87
+ // cell selection anchors ranges on it.
88
+ const cellNavigationEnabled = cellNavigation || editing || cellSelection;
75
89
  const sortingOptions = typeof sorting === 'object' ? sorting : {};
76
90
  const sizingEnabled = Boolean(columnSizing);
77
91
  const sizingOptions = typeof columnSizing === 'object' ? columnSizing : {};
@@ -80,9 +94,23 @@ const CDataGrid = forwardRef((props, ref) => {
80
94
  const selectionEnabled = Boolean(rowSelection);
81
95
  const selectionOptions = typeof rowSelection === 'object' ? rowSelection : {};
82
96
  const serverSide = Boolean(dataProvider);
83
- // Server-side mode implies pagination — virtualized infinite scroll over a
84
- // dataProvider is out of scope until 0.3.0.
85
- const paginationEnabled = Boolean(pagination) || serverSide;
97
+ if (infiniteScroll) {
98
+ if (!serverSide) {
99
+ throw new TypeError('DATA-GRID: Option "infiniteScroll" requires "dataProvider".');
100
+ }
101
+ if (pagination) {
102
+ throw new TypeError('DATA-GRID: Option "infiniteScroll" cannot be combined with "pagination".');
103
+ }
104
+ if (!virtualization) {
105
+ throw new TypeError('DATA-GRID: Option "infiniteScroll" requires "virtualization".');
106
+ }
107
+ }
108
+ const infiniteOptions = typeof infiniteScroll === 'object' ? infiniteScroll : {};
109
+ // Server-side mode implies pagination unless it scrolls infinitely, where
110
+ // the pages accumulate behind the virtualizer instead of replacing. The
111
+ // pagination STATE stays either way — infinite scroll pages the requests.
112
+ const paginationEnabled = Boolean(pagination) || (serverSide && !infiniteScroll);
113
+ const paginated = paginationEnabled || Boolean(infiniteScroll);
86
114
  const paginationOptions = typeof pagination === 'object' ? pagination : {};
87
115
  const virtualizationEnabled = virtualization && !paginationEnabled;
88
116
  const resolvedToolbar = resolveToolbar({
@@ -105,7 +133,11 @@ const CDataGrid = forwardRef((props, ref) => {
105
133
  const [globalFilterState, setGlobalFilterState] = useState((persisted === null || persisted === void 0 ? void 0 : persisted.globalFilter) === undefined || persisted.globalFilter === '' ? undefined : persisted.globalFilter);
106
134
  const [rowSelectionState, setRowSelectionState] = useState((_c = persisted === null || persisted === void 0 ? void 0 : persisted.rowSelection) !== null && _c !== void 0 ? _c : {});
107
135
  const [columnSizingState, setColumnSizingState] = useState((_d = persisted === null || persisted === void 0 ? void 0 : persisted.columnSizing) !== null && _d !== void 0 ? _d : {});
108
- const [columnPinningState, setColumnPinningState] = useState((_e = persisted === null || persisted === void 0 ? void 0 : persisted.columnPinning) !== null && _e !== void 0 ? _e : (typeof columnPinning === 'object' ? columnPinning : {}));
136
+ const [columnPinningState, setColumnPinningState] = useState({
137
+ start: [],
138
+ end: [],
139
+ ...((_e = persisted === null || persisted === void 0 ? void 0 : persisted.columnPinning) !== null && _e !== void 0 ? _e : (typeof columnPinning === 'object' ? columnPinning : {}))
140
+ });
109
141
  const [columnOrderState, setColumnOrderState] = useState((_f = persisted === null || persisted === void 0 ? void 0 : persisted.columnOrder) !== null && _f !== void 0 ? _f : (Array.isArray(columnOrder) ? columnOrder : []));
110
142
  const [columnVisibilityState, setColumnVisibilityState] = useState((_g = persisted === null || persisted === void 0 ? void 0 : persisted.columnVisibility) !== null && _g !== void 0 ? _g : (typeof columnVisibility === 'object' ? columnVisibility : {}));
111
143
  // The chooser's Reset restores the initial columnVisibility object, captured
@@ -113,7 +145,13 @@ const CDataGrid = forwardRef((props, ref) => {
113
145
  const initialColumnVisibility = useRef(typeof columnVisibility === 'object' ? columnVisibility : {});
114
146
  const [paginationState, setPaginationState] = useState({
115
147
  pageIndex: (_j = (_h = persisted === null || persisted === void 0 ? void 0 : persisted.pagination) === null || _h === void 0 ? void 0 : _h.pageIndex) !== null && _j !== void 0 ? _j : 0,
116
- pageSize: (_m = (_l = (_k = persisted === null || persisted === void 0 ? void 0 : persisted.pagination) === null || _k === void 0 ? void 0 : _k.pageSize) !== null && _l !== void 0 ? _l : paginationOptions.pageSize) !== null && _m !== void 0 ? _m : 10
148
+ // Pagination is a registered feature on every grid (v9 features are
149
+ // static), so an unpaginated grid is one page holding every row.
150
+ pageSize: infiniteScroll ?
151
+ (_k = infiniteOptions.pageSize) !== null && _k !== void 0 ? _k : INFINITE_SCROLL_PAGE_SIZE :
152
+ (paginationEnabled ?
153
+ (_o = (_m = (_l = persisted === null || persisted === void 0 ? void 0 : persisted.pagination) === null || _l === void 0 ? void 0 : _l.pageSize) !== null && _m !== void 0 ? _m : paginationOptions.pageSize) !== null && _o !== void 0 ? _o : 10 :
154
+ UNPAGINATED_PAGE_SIZE)
117
155
  });
118
156
  const [activeCell, setActiveCell] = useState(null);
119
157
  // A pending focus request survives renders until the target cell exists in
@@ -163,14 +201,28 @@ const CDataGrid = forwardRef((props, ref) => {
163
201
  enableSorting: column.sortable !== false,
164
202
  filterFn: toTanStackFilterFn(column),
165
203
  id: column.key,
166
- meta: { label: column.label, formatter: column.formatter },
204
+ meta: { label: column.label, formatter: column.formatter, width: column.width },
167
205
  ...(widthsEnabled && size !== null && { size })
168
206
  };
169
207
  }), [columns, widthsEnabled]);
170
- const table = useReactTable({
208
+ assertFeatureCoverage('data-grid', features, {
209
+ cellSelection,
210
+ columnFilters,
211
+ columnOrder: Boolean(columnOrder),
212
+ columnPinning: pinningEnabled,
213
+ columnSizing: sizingEnabled,
214
+ columnVisibility: Boolean(columnVisibility),
215
+ globalFilter: resolvedToolbar.search,
216
+ pagination: paginated,
217
+ rowSelection: selectionEnabled,
218
+ selectFilters: columnFilters && columns.some(column => column.filterType === 'select'),
219
+ sorting: Boolean(sorting)
220
+ });
221
+ const table = useTable({
171
222
  columns: columnDefs,
172
- columnResizeMode: (_o = sizingOptions.mode) !== null && _o !== void 0 ? _o : 'onChange',
223
+ columnResizeMode: (_p = sizingOptions.mode) !== null && _p !== void 0 ? _p : 'onChange',
173
224
  data: serverSide ? serverItems : items,
225
+ enableCellSelection: cellSelection,
174
226
  enableColumnFilters: columnFilters,
175
227
  enableColumnPinning: pinningEnabled,
176
228
  enableColumnResizing: sizingEnabled,
@@ -180,12 +232,10 @@ const CDataGrid = forwardRef((props, ref) => {
180
232
  enableRowSelection: selectionEnabled,
181
233
  enableSorting: Boolean(sorting),
182
234
  enableSortingRemoval: Boolean(sortingOptions.resetable),
183
- getCoreRowModel: getCoreRowModel(),
184
- // Faceting powers custom filter UIs (column.getFacetedUniqueValues()).
185
- getFacetedRowModel: getFacetedRowModel(),
186
- getFacetedUniqueValues: getFacetedUniqueValues(),
187
- getFilteredRowModel: getFilteredRowModel(),
188
- getSortedRowModel: getSortedRowModel(),
235
+ // The runtime set may be smaller than the DataGridFeatures the table is
236
+ // typed against - internals stay typed on the full set and every call
237
+ // into an optional feature is capability-guarded.
238
+ features,
189
239
  globalFilterFn: 'includesString',
190
240
  ...(serverSide && {
191
241
  manualFiltering: true,
@@ -193,7 +243,6 @@ const CDataGrid = forwardRef((props, ref) => {
193
243
  manualSorting: true,
194
244
  rowCount: serverTotalRows
195
245
  }),
196
- ...(paginationEnabled && { getPaginationRowModel: getPaginationRowModel() }),
197
246
  ...(itemKey && { getRowId: itemKey }),
198
247
  onColumnFiltersChange: setColumnFiltersState,
199
248
  onColumnOrderChange: setColumnOrderState,
@@ -254,8 +303,68 @@ const CDataGrid = forwardRef((props, ref) => {
254
303
  const tooltip = createTooltip(rootRef.current);
255
304
  return () => tooltip.dispose();
256
305
  }, []);
306
+ // Pointer-based column drag: the shared controller owns the ghost, the drop
307
+ // indicator and the edge auto-scroll, and writes to the DOM directly, so a
308
+ // drag costs no React renders. The table is read through a ref so the
309
+ // controller survives every unrelated re-render.
310
+ const tableRefForDrag = useRef(table);
311
+ tableRefForDrag.current = table;
312
+ useEffect(() => {
313
+ const root = rootRef.current;
314
+ if (!columnOrder || !root) {
315
+ return;
316
+ }
317
+ const controller = createColumnDragController({
318
+ root,
319
+ getScrollElement: () => viewportElement,
320
+ canDrop: (sourceKey, targetKey) => canReorderColumn(tableRefForDrag.current, sourceKey, targetKey),
321
+ onReorder: (sourceKey, targetKey, after) => reorderColumn(tableRefForDrag.current, sourceKey, targetKey, after)
322
+ });
323
+ return () => controller.dispose();
324
+ }, [columnOrder, viewportElement]);
325
+ // The grid never mutates `items` — it hands the reordered array to the app
326
+ // through onRowOrderChange, the same contract inline editing uses.
327
+ const rowOrderRef = useRef({ items, rowIndexOffset: 0, onRowOrderChange });
328
+ rowOrderRef.current = {
329
+ items,
330
+ rowIndexOffset: paginationEnabled ? paginationState.pageIndex * paginationState.pageSize : 0,
331
+ onRowOrderChange
332
+ };
333
+ useEffect(() => {
334
+ const root = rootRef.current;
335
+ if (!rowOrder || !root) {
336
+ return;
337
+ }
338
+ const controller = createRowDragController({
339
+ root,
340
+ getScrollElement: () => viewportElement,
341
+ onReorder(sourceIndex, targetIndex, after) {
342
+ var _a;
343
+ const { current } = rowOrderRef;
344
+ const next = [...current.items];
345
+ const from = current.rowIndexOffset + sourceIndex;
346
+ const [item] = next.splice(from, 1);
347
+ if (item === undefined) {
348
+ return;
349
+ }
350
+ // Resolved AFTER the removal, so the index still points at the target
351
+ // whichever direction the row travelled.
352
+ const target = current.rowIndexOffset + targetIndex;
353
+ const to = target > from ? target - 1 : target;
354
+ const newIndex = after ? to + 1 : to;
355
+ next.splice(newIndex, 0, item);
356
+ (_a = current.onRowOrderChange) === null || _a === void 0 ? void 0 : _a.call(current, {
357
+ items: next, item, oldIndex: from, newIndex
358
+ });
359
+ }
360
+ });
361
+ return () => controller.dispose();
362
+ }, [rowOrder, viewportElement]);
257
363
  const requestIdRef = useRef(0);
258
364
  const lastFilterKeyRef = useRef(null);
365
+ // One page bump per fetch: the body re-renders many times before the next
366
+ // page lands, and each of those passes still sees the end of the buffer.
367
+ const loadMorePendingRef = useRef(false);
259
368
  useEffect(() => {
260
369
  if (!dataProvider) {
261
370
  return;
@@ -282,7 +391,14 @@ const CDataGrid = forwardRef((props, ref) => {
282
391
  if (id !== requestIdRef.current) {
283
392
  return;
284
393
  }
285
- setServerItems(response.items);
394
+ // Infinite scroll accumulates: only a first page replaces what is on
395
+ // screen, every later one appends to it. A replacement also shrinks
396
+ // the buffer back under a viewport scrolled far past it.
397
+ loadMorePendingRef.current = false;
398
+ if (infiniteScroll && paginationState.pageIndex === 0 && viewportElement) {
399
+ viewportElement.scrollTop = 0;
400
+ }
401
+ setServerItems(previous => (infiniteScroll && paginationState.pageIndex > 0 ? [...previous, ...response.items] : response.items));
286
402
  setServerTotalRows(response.totalRows);
287
403
  setLoadFailed(false);
288
404
  setAnnouncement(interpolate(mergedLabels.resultsAnnouncement, { count: response.totalRows }));
@@ -292,7 +408,8 @@ const CDataGrid = forwardRef((props, ref) => {
292
408
  if (id !== requestIdRef.current) {
293
409
  return;
294
410
  }
295
- setServerItems([]);
411
+ loadMorePendingRef.current = false;
412
+ setServerItems(EMPTY_ITEMS);
296
413
  setServerTotalRows(0);
297
414
  setLoadFailed(true);
298
415
  onDataError === null || onDataError === void 0 ? void 0 : onDataError(error);
@@ -307,7 +424,9 @@ const CDataGrid = forwardRef((props, ref) => {
307
424
  // eslint-disable-next-line react-hooks/exhaustive-deps
308
425
  }, [dataProvider, sortingState, columnFiltersState, globalFilterState, paginationState]);
309
426
  const mounted = useRef(false);
310
- const totalRows = serverSide ? serverTotalRows : table.getFilteredRowModel().rows.length;
427
+ const totalRows = serverSide ?
428
+ serverTotalRows :
429
+ (typeof table.getFilteredRowModel === 'function' ? table.getFilteredRowModel() : table.getCoreRowModel()).rows.length;
311
430
  useEffect(() => {
312
431
  if (mounted.current) {
313
432
  onSortingChange === null || onSortingChange === void 0 ? void 0 : onSortingChange(sortingState);
@@ -321,13 +440,15 @@ const CDataGrid = forwardRef((props, ref) => {
321
440
  // eslint-disable-next-line react-hooks/exhaustive-deps
322
441
  }, [columnFiltersState, globalFilterState]);
323
442
  useEffect(() => {
324
- if (mounted.current) {
443
+ if (mounted.current && typeof table.getSelectedRowModel === 'function') {
325
444
  onSelectionChange === null || onSelectionChange === void 0 ? void 0 : onSelectionChange(rowSelectionState, table.getSelectedRowModel().rows.map(row => row.original));
326
445
  }
327
446
  // eslint-disable-next-line react-hooks/exhaustive-deps
328
447
  }, [rowSelectionState]);
448
+ // No onPaginationChange under infinite scroll — pagination is internal
449
+ // there (no widget, not user-adjustable); onDataLoad is the page signal.
329
450
  useEffect(() => {
330
- if (mounted.current) {
451
+ if (mounted.current && !infiniteScroll) {
331
452
  onPaginationChange === null || onPaginationChange === void 0 ? void 0 : onPaginationChange(paginationState);
332
453
  }
333
454
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -372,11 +493,17 @@ const CDataGrid = forwardRef((props, ref) => {
372
493
  // reshuffles rows would make it point at an arbitrary one. An in-flight
373
494
  // edit stops (commit) first. A pending focus request marks an intentional
374
495
  // navigation (PageUp/PageDown page flip) - keep the cell it just set.
375
- const signature = JSON.stringify([
376
- sortingState, columnFiltersState,
377
- globalFilterState !== null && globalFilterState !== void 0 ? globalFilterState : '',
378
- paginationState.pageIndex, paginationState.pageSize
379
- ]);
496
+ // Infinite scroll is the exception: its page bumps only append, so every
497
+ // row already on screen keeps its index and its focus.
498
+ const signature = JSON.stringify(infiniteScroll ?
499
+ [sortingState, columnFiltersState, globalFilterState !== null && globalFilterState !== void 0 ? globalFilterState : ''] :
500
+ [
501
+ sortingState,
502
+ columnFiltersState,
503
+ globalFilterState !== null && globalFilterState !== void 0 ? globalFilterState : '',
504
+ paginationState.pageIndex,
505
+ paginationState.pageSize
506
+ ]);
380
507
  if (rowSetSignatureRef.current === signature) {
381
508
  return;
382
509
  }
@@ -387,6 +514,7 @@ const CDataGrid = forwardRef((props, ref) => {
387
514
  setActiveCell(null);
388
515
  }
389
516
  }
517
+ // eslint-disable-next-line react-hooks/exhaustive-deps
390
518
  }, [sortingState, columnFiltersState, globalFilterState, paginationState]);
391
519
  useEffect(() => {
392
520
  mounted.current = true;
@@ -398,9 +526,31 @@ const CDataGrid = forwardRef((props, ref) => {
398
526
  }, []);
399
527
  const hasCustomFilterRow = columnFilters && columns.some(column => column.filter && column.filterable !== false);
400
528
  const headerRowCount = hasCustomFilterRow ? 2 : 1;
401
- const position = (_p = paginationOptions.position) !== null && _p !== void 0 ? _p : 'bottom';
529
+ const position = (_q = paginationOptions.position) !== null && _q !== void 0 ? _q : 'bottom';
402
530
  const rowCount = table.getRowModel().rows.length;
531
+ const appending = Boolean(infiniteScroll) && serverItems.length > 0;
532
+ const loadMore = () => {
533
+ if (loadMorePendingRef.current || rowCount === 0 || rowCount >= totalRows) {
534
+ return;
535
+ }
536
+ loadMorePendingRef.current = true;
537
+ setPaginationState(previous => ({ ...previous, pageIndex: previous.pageIndex + 1 }));
538
+ };
403
539
  const visibleColumnIds = visibleColumns(table, columns).map(column => column.key);
540
+ // The columns the selection's column indexes address, with the formatter
541
+ // each cell displays through — a copied date pastes as the date on screen.
542
+ const clipboardColumns = () => visibleColumns(table, columns).map(column => ({ id: column.key, formatter: column.formatter }));
543
+ // Prints every sorted and filtered row, not the virtual window or the
544
+ // current page: the grid renders a plain complete table, prints it, and
545
+ // throws it away.
546
+ const printGridTable = () => printGrid(table, visibleColumns(table, columns).map(column => {
547
+ var _a;
548
+ return ({
549
+ id: column.key,
550
+ label: (_a = column.label) !== null && _a !== void 0 ? _a : column.key,
551
+ formatter: column.formatter
552
+ });
553
+ }), { title: printTitle });
404
554
  const activeCellContext = () => {
405
555
  var _a;
406
556
  return ({
@@ -453,6 +603,14 @@ const CDataGrid = forwardRef((props, ref) => {
453
603
  var _a;
454
604
  focusRequestRef.current = cell;
455
605
  setActiveCell(cell);
606
+ // The active cell is the grid's single focus owner; cell selection follows
607
+ // it rather than keeping a second anchor of its own.
608
+ if (cellSelection && cell.rowIndex >= 0) {
609
+ const row = table.getRowModel().rows[cell.rowIndex];
610
+ if (row) {
611
+ table.setFocusedCell(row.id, cell.columnId);
612
+ }
613
+ }
456
614
  if (virtualizationEnabled && cell.rowIndex >= 0) {
457
615
  // No-op when the row is already in view (align: auto); otherwise the
458
616
  // focus request stays pending until the row renders.
@@ -834,11 +992,37 @@ const CDataGrid = forwardRef((props, ref) => {
834
992
  }
835
993
  return;
836
994
  }
995
+ if (cellSelection && (event.ctrlKey || event.metaKey) && found.cell.rowIndex >= 0) {
996
+ const key = event.key.toLowerCase();
997
+ if (key === 'a') {
998
+ event.preventDefault();
999
+ table.selectAllCells();
1000
+ return;
1001
+ }
1002
+ if (key === 'c') {
1003
+ event.preventDefault();
1004
+ copyCellSelection(table, clipboardColumns()).then(text => {
1005
+ if (text !== '') {
1006
+ onCellCopy === null || onCellCopy === void 0 ? void 0 : onCellCopy({ text });
1007
+ }
1008
+ }, () => { });
1009
+ return;
1010
+ }
1011
+ }
837
1012
  const result = resolveNavigation(event.key, event.ctrlKey || event.metaKey, found.cell, activeCellContext());
838
1013
  if (!result) {
839
1014
  return;
840
1015
  }
841
1016
  event.preventDefault();
1017
+ // Shift+arrow grows the range from its anchor and leaves focus put; a
1018
+ // plain arrow moves focus and collapses the range to the new cell.
1019
+ if (cellSelection && found.cell.rowIndex >= 0 && result.cell.rowIndex >= 0) {
1020
+ const direction = CELL_SELECTION_DIRECTIONS[event.key];
1021
+ if (event.shiftKey && direction) {
1022
+ table.extendCellSelection(direction);
1023
+ return;
1024
+ }
1025
+ }
842
1026
  if (result.pageDelta === 1 && table.getCanNextPage()) {
843
1027
  table.nextPage();
844
1028
  }
@@ -860,9 +1044,9 @@ const CDataGrid = forwardRef((props, ref) => {
860
1044
  jsx(EmptySlot, { table: table, labels: mergedLabels }) :
861
1045
  (loadFailed ? mergedLabels.loadError : empty);
862
1046
  const editingConfig = editingCell ?
863
- (_q = columns.find(column => column.key === editingCell.columnId)) !== null && _q !== void 0 ? _q : null :
1047
+ (_r = columns.find(column => column.key === editingCell.columnId)) !== null && _r !== void 0 ? _r : null :
864
1048
  null;
865
- const EditorComponent = (_r = editingConfig === null || editingConfig === void 0 ? void 0 : editingConfig.editor) !== null && _r !== void 0 ? _r : CDataGridCellEditor;
1049
+ const EditorComponent = (_s = editingConfig === null || editingConfig === void 0 ? void 0 : editingConfig.editor) !== null && _s !== void 0 ? _s : CDataGridCellEditor;
866
1050
  const editorSession = editingCell && editingConfig ?
867
1051
  {
868
1052
  rowId: editingCell.rowId,
@@ -881,9 +1065,10 @@ const CDataGrid = forwardRef((props, ref) => {
881
1065
  onHidden: () => stopEditingRef.current({ commit: true, force: true })
882
1066
  } :
883
1067
  null;
884
- return (jsxs("div", { className: `data-grid${sizingEnabled ? ' data-grid-resizable' : ''}${pinningEnabled ? ' data-grid-pinned' : ''}${className ? ` ${className}` : ''}`, "data-coreui-sorter-visibility": sorterVisibility || undefined, ref: setRootRef, ...rest, children: [jsx("div", { className: "visually-hidden", "aria-live": "polite", children: announcement }), ToolbarSlot ?
1068
+ return (jsxs("div", { className: `data-grid${autoRowHeight && virtualizationEnabled ? ' data-grid-auto-row-height' : ''}${sizingEnabled ? ' data-grid-resizable' : ''}${pinningEnabled ? ' data-grid-pinned' : ''}${className ? ` ${className}` : ''}`, "data-coreui-sorter-visibility": sorterVisibility || undefined, ref: setRootRef, ...rest, children: [jsx("div", { className: "visually-hidden", "aria-live": "polite", children: announcement }), ToolbarSlot ?
885
1069
  jsx(ToolbarSlot, { table: table, labels: mergedLabels }) :
886
- (resolvedToolbar.columns || resolvedToolbar.export || resolvedToolbar.search || resolvedToolbar.history) && (jsx(CDataGridToolbar, { columns: columns, columnsIcon: toolbarColumnsIcon, container: rootRef, export: resolvedToolbar.export, exportIcon: toolbarExportIcon, exportOptions: resolvedToolbar.exportOptions, initialVisibility: initialColumnVisibility.current, labels: mergedLabels, canRedo: canRedo, canUndo: canUndo, onRedo: redo, onUndo: undo, redoIcon: toolbarRedoIcon, search: resolvedToolbar.search, searchValue: globalFilterState !== null && globalFilterState !== void 0 ? globalFilterState : '', showColumns: resolvedToolbar.columns, showHistory: resolvedToolbar.history, undoIcon: toolbarUndoIcon, table: table })), paginationEnabled && (position === 'top' || position === 'both') && paginationElement('top'), jsxs("div", { className: "data-grid-viewport", ref: setViewportElement, ...(serverSide && { 'aria-busy': loading }), ...(cellNavigationEnabled && {
1070
+ (resolvedToolbar.columns || resolvedToolbar.export || resolvedToolbar.print ||
1071
+ resolvedToolbar.search || resolvedToolbar.history) && (jsx(CDataGridToolbar, { columns: columns, columnsIcon: toolbarColumnsIcon, container: rootRef, export: resolvedToolbar.export, exportIcon: toolbarExportIcon, exportOptions: resolvedToolbar.exportOptions, initialVisibility: initialColumnVisibility.current, labels: mergedLabels, canRedo: canRedo, canUndo: canUndo, onPrint: printGridTable, onRedo: redo, onUndo: undo, print: resolvedToolbar.print, printIcon: toolbarPrintIcon, redoIcon: toolbarRedoIcon, search: resolvedToolbar.search, searchValue: globalFilterState !== null && globalFilterState !== void 0 ? globalFilterState : '', showColumns: resolvedToolbar.columns, showHistory: resolvedToolbar.history, undoIcon: toolbarUndoIcon, table: table })), paginationEnabled && (position === 'top' || position === 'both') && paginationElement('top'), jsxs("div", { className: "data-grid-viewport", ref: setViewportElement, ...(serverSide && { 'aria-busy': loading }), ...(cellNavigationEnabled && {
887
1072
  // Focus parks here when the focused row is recycled out of the
888
1073
  // window; the keydown fallback then scrolls the active cell back.
889
1074
  tabIndex: -1,
@@ -893,7 +1078,7 @@ const CDataGrid = forwardRef((props, ref) => {
893
1078
  }), ...(editing && { onDoubleClick: handleEditingDoubleClick }), children: [jsxs("table", { className: "table", "aria-rowcount": headerRowCount + totalRows, ...(cellNavigationEnabled && {
894
1079
  role: 'grid',
895
1080
  'aria-colcount': visibleColumnIds.length + (selectionEnabled ? 1 : 0)
896
- }), children: [jsx(CDataGridHeader, { cellNavigation: cellNavigationEnabled, columnFilters: columnFilters, columns: columns, draggable: Boolean(columnOrder), icons: menuIcons, isFilterable: isColumnFilterable, isResizable: isColumnResizable, isSortable: isColumnSortable, labels: mergedLabels, menu: Boolean(columnMenu), menuBuilder: menuBuilder, menuContainer: rootRef, pinning: pinningEnabled, resizable: sizingEnabled, selectAll: selectionEnabled && selectionOptions.selectAll !== false, selection: selectionEnabled, tabStop: tabStop, table: table, visibility: Boolean(columnVisibility) }), jsx(CDataGridBody, { cellNavigation: cellNavigationEnabled, columns: columns, containsFocusRef: containsFocusRef, editorSession: editorSession, empty: emptyContent, focusRequestRef: focusRequestRef, headerRowCount: headerRowCount, labels: mergedLabels, overscan: overscan, pinning: pinningEnabled, rowHeight: rowHeight, rowIndexOffset: paginationEnabled ? paginationState.pageIndex * paginationState.pageSize : 0, scrollElement: viewportElement, scrollToIndexRef: scrollToIndexRef, selection: selectionEnabled, tabStop: tabStop, table: table, virtualization: virtualizationEnabled })] }), serverSide && (jsx("div", { className: "data-grid-loading", hidden: !loading, children: jsx("div", { className: "spinner-border", role: "status", children: jsx("span", { className: "visually-hidden", children: mergedLabels.loading }) }) }))] }), (editorSession === null || editorSession === void 0 ? void 0 : editorSession.popup) && (jsx("div", { className: "data-grid-editor-popup", style: editorPopupStyle !== null && editorPopupStyle !== void 0 ? editorPopupStyle : undefined, onBlur: editorSession.onBlur, onKeyDown: handleNavigationKeydown, children: editorSession.render() })), paginationEnabled && (position === 'bottom' || position === 'both') && paginationElement('bottom')] }));
1081
+ }), children: [jsx(CDataGridHeader, { cellNavigation: cellNavigationEnabled, columnFilters: columnFilters, columns: columns, draggable: Boolean(columnOrder), icons: menuIcons, isFilterable: isColumnFilterable, isResizable: isColumnResizable, isSortable: isColumnSortable, labels: mergedLabels, menu: Boolean(columnMenu), menuBuilder: menuBuilder, menuContainer: rootRef, pinning: pinningEnabled, resizable: sizingEnabled, rowOrder: rowOrder, selectAll: selectionEnabled && selectionOptions.selectAll !== false, selection: selectionEnabled, tabStop: tabStop, table: table, visibility: Boolean(columnVisibility) }), jsx(CDataGridBody, { cellNavigation: cellNavigationEnabled, columns: columns, containsFocusRef: containsFocusRef, editorSession: editorSession, empty: emptyContent, focusRequestRef: focusRequestRef, headerRowCount: headerRowCount, labels: mergedLabels, loadMoreThreshold: infiniteScroll ? (_t = infiniteOptions.threshold) !== null && _t !== void 0 ? _t : overscan : null, onLoadMore: loadMore, overscan: overscan, pinning: pinningEnabled, autoRowHeight: autoRowHeight && virtualizationEnabled, rowHeight: rowHeight, cellSelection: cellSelection, rowHandleIcon: rowHandleIcon, rowIndexOffset: paginationEnabled ? paginationState.pageIndex * paginationState.pageSize : 0, rowOrder: rowOrder, scrollElement: viewportElement, scrollToIndexRef: scrollToIndexRef, selection: selectionEnabled, tabStop: tabStop, table: table, virtualization: virtualizationEnabled })] }), serverSide && (jsx("div", { className: "data-grid-loading", hidden: !loading || appending, children: jsx("div", { className: "spinner-border", role: "status", children: jsx("span", { className: "visually-hidden", children: mergedLabels.loading }) }) })), Boolean(infiniteScroll) && (jsx("div", { className: "data-grid-loading-more", hidden: !loading || !appending, children: jsx("div", { className: "spinner-border", role: "status", children: jsx("span", { className: "visually-hidden", children: mergedLabels.loading }) }) }))] }), (editorSession === null || editorSession === void 0 ? void 0 : editorSession.popup) && (jsx("div", { className: "data-grid-editor-popup", style: editorPopupStyle !== null && editorPopupStyle !== void 0 ? editorPopupStyle : undefined, onBlur: editorSession.onBlur, onKeyDown: handleNavigationKeydown, children: editorSession.render() })), paginationEnabled && (position === 'bottom' || position === 'both') && paginationElement('bottom')] }));
897
1082
  });
898
1083
  CDataGrid.displayName = 'CDataGrid';
899
1084