@linzjs/step-ag-grid 29.12.0 → 29.14.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 (103) hide show
  1. package/dist/index.css +26 -2
  2. package/dist/src/components/Grid.d.ts +37 -67
  3. package/dist/src/components/GridCell.d.ts +2 -19
  4. package/dist/src/components/GridCellFiller.d.ts +1 -1
  5. package/dist/src/components/GridCellMultiEditor.d.ts +1 -2
  6. package/dist/src/components/GridPopoverHook.d.ts +1 -1
  7. package/dist/src/components/GridRangeSelectContextMenu.d.ts +10 -0
  8. package/dist/src/components/gridFilter/GridFilterButtons.d.ts +1 -1
  9. package/dist/src/components/gridFilter/useGridFilter.d.ts +1 -1
  10. package/dist/src/components/gridForm/GridFormDropDown.d.ts +2 -2
  11. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +1 -1
  12. package/dist/src/components/gridForm/GridFormMessage.d.ts +1 -1
  13. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
  14. package/dist/src/components/gridForm/GridFormMultiSelectGrid.d.ts +1 -1
  15. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +3 -2
  16. package/dist/src/components/gridForm/GridFormSubComponentTextArea.d.ts +1 -1
  17. package/dist/src/components/gridForm/GridFormSubComponentTextInput.d.ts +1 -1
  18. package/dist/src/components/gridForm/GridFormTextArea.d.ts +1 -1
  19. package/dist/src/components/gridForm/GridFormTextInput.d.ts +1 -1
  20. package/dist/src/components/gridHook/useGridContextMenu.d.ts +13 -8
  21. package/dist/src/components/gridHook/useGridCopy.d.ts +15 -0
  22. package/dist/src/components/gridHook/useGridCopySettings.d.ts +11 -0
  23. package/dist/src/components/gridHook/useGridRangeSelection.d.ts +25 -0
  24. package/dist/src/components/gridPopoverEdit/GridButton.d.ts +1 -2
  25. package/dist/src/components/gridPopoverEdit/GridEditBoolean.d.ts +2 -2
  26. package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +2 -2
  27. package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelectGrid.d.ts +2 -2
  28. package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +4 -2
  29. package/dist/src/components/gridPopoverEdit/GridPopoverEditDropDown.d.ts +2 -2
  30. package/dist/src/components/gridPopoverEdit/GridPopoverMenu.d.ts +2 -2
  31. package/dist/src/components/gridPopoverEdit/GridPopoverMessage.d.ts +2 -2
  32. package/dist/src/components/gridPopoverEdit/GridPopoverTextArea.d.ts +2 -2
  33. package/dist/src/components/gridPopoverEdit/GridPopoverTextInput.d.ts +2 -2
  34. package/dist/src/components/gridRender/GridRenderGenericCell.d.ts +1 -2
  35. package/dist/src/components/index.d.ts +1 -0
  36. package/dist/src/components/types.d.ts +27 -0
  37. package/dist/src/contexts/GridContext.d.ts +13 -12
  38. package/dist/src/contexts/GridPopoverContext.d.ts +1 -1
  39. package/dist/src/lui/timeoutHook.d.ts +0 -6
  40. package/dist/src/lui/tsUtils.d.ts +5 -0
  41. package/dist/src/utils/__tests__/random.ts +19 -0
  42. package/dist/src/utils/textValidator.d.ts +1 -1
  43. package/dist/src/utils/util.d.ts +1 -0
  44. package/dist/step-ag-grid.cjs +650 -242
  45. package/dist/step-ag-grid.cjs.map +1 -1
  46. package/dist/step-ag-grid.esm.js +651 -245
  47. package/dist/step-ag-grid.esm.js.map +1 -1
  48. package/package.json +4 -2
  49. package/src/components/Grid.tsx +312 -221
  50. package/src/components/GridCell.tsx +1 -25
  51. package/src/components/GridCellFiller.tsx +1 -1
  52. package/src/components/GridCellMultiEditor.tsx +2 -2
  53. package/src/components/GridPopoverHook.tsx +1 -1
  54. package/src/components/GridRangeSelectContextMenu.tsx +73 -0
  55. package/src/components/gridFilter/GridFilterButtons.tsx +1 -1
  56. package/src/components/gridFilter/useGridFilter.ts +1 -1
  57. package/src/components/gridForm/GridFormDropDown.tsx +2 -4
  58. package/src/components/gridForm/GridFormEditBearing.tsx +1 -1
  59. package/src/components/gridForm/GridFormMessage.tsx +1 -1
  60. package/src/components/gridForm/GridFormMultiSelect.tsx +1 -1
  61. package/src/components/gridForm/GridFormMultiSelectGrid.tsx +1 -1
  62. package/src/components/gridForm/GridFormPopoverMenu.tsx +3 -2
  63. package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -1
  64. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -1
  65. package/src/components/gridForm/GridFormTextArea.tsx +1 -1
  66. package/src/components/gridForm/GridFormTextInput.tsx +1 -1
  67. package/src/components/gridHook/useGridContextMenu.tsx +23 -10
  68. package/src/components/gridHook/useGridCopy.ts +279 -0
  69. package/src/components/gridHook/useGridCopySettings.ts +28 -0
  70. package/src/components/gridHook/useGridRangeSelection.ts +235 -0
  71. package/src/components/gridPopoverEdit/GridButton.tsx +2 -2
  72. package/src/components/gridPopoverEdit/GridEditBoolean.tsx +2 -2
  73. package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +2 -2
  74. package/src/components/gridPopoverEdit/GridPopoutEditMultiSelectGrid.ts +2 -2
  75. package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +5 -2
  76. package/src/components/gridPopoverEdit/GridPopoverEditDropDown.ts +2 -2
  77. package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +2 -2
  78. package/src/components/gridPopoverEdit/GridPopoverMessage.ts +2 -2
  79. package/src/components/gridPopoverEdit/GridPopoverTextArea.ts +2 -2
  80. package/src/components/gridPopoverEdit/GridPopoverTextInput.ts +2 -2
  81. package/src/components/gridRender/GridRenderGenericCell.tsx +1 -2
  82. package/src/components/index.ts +1 -0
  83. package/src/components/types.ts +34 -0
  84. package/src/contexts/GridContext.tsx +57 -148
  85. package/src/contexts/GridContextProvider.tsx +16 -13
  86. package/src/contexts/GridPopoverContext.tsx +1 -1
  87. package/src/contexts/GridPopoverContextProvider.tsx +1 -1
  88. package/src/lui/timeoutHook.tsx +0 -19
  89. package/src/lui/tsUtils.ts +8 -0
  90. package/src/react-menu3/components/MenuButton.tsx +2 -2
  91. package/src/react-menu3/hooks/useItemState.ts +1 -1
  92. package/src/stories/grid/GridCopy.stories.tsx +175 -0
  93. package/src/stories/grid/GridPopoverEditBearing.stories.tsx +4 -4
  94. package/src/stories/grid/GridPopoverEditDropDown.stories.tsx +3 -3
  95. package/src/stories/grid/GridReadOnly.stories.tsx +4 -3
  96. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +2 -2
  97. package/src/styles/ContextMenu.scss +61 -0
  98. package/src/styles/Grid.scss +26 -2
  99. package/src/utils/__tests__/random.ts +19 -0
  100. package/src/utils/bearing.ts +2 -2
  101. package/src/utils/textValidator.test.ts +1 -1
  102. package/src/utils/textValidator.ts +1 -1
  103. package/src/utils/util.ts +2 -0
@@ -8,6 +8,7 @@ import {
8
8
  CellKeyDownEvent,
9
9
  ColDef,
10
10
  ColGroupDef,
11
+ ColumnMovedEvent,
11
12
  ColumnResizedEvent,
12
13
  EditableCallback,
13
14
  EditableCallbackParams,
@@ -15,6 +16,7 @@ import {
15
16
  GridOptions,
16
17
  GridReadyEvent,
17
18
  GridSizeChangedEvent,
19
+ IRowNode,
18
20
  ModelUpdatedEvent,
19
21
  ModuleRegistry,
20
22
  RowClickedEvent,
@@ -37,148 +39,161 @@ import { compareNaturalInsensitive, fnOrVar, isNotEmpty } from '../utils/util';
37
39
  import { clickInputWhenContainingCellClicked } from './clickInputWhenContainingCellClicked';
38
40
  import { GridHeaderSelect } from './gridHeader';
39
41
  import { GridContextMenuComponent, useGridContextMenu } from './gridHook';
42
+ import { useGridCopy } from './gridHook/useGridCopy';
43
+ import { CellLocation, useGridRangeSelection } from './gridHook/useGridRangeSelection';
40
44
  import { GridNoRowsOverlay } from './GridNoRowsOverlay';
41
45
  import { usePostSortRowsHook } from './PostSortRowsHook';
46
+ import { GridBaseRow, GridOnRowDragEndProps } from './types';
42
47
 
43
48
  ModuleRegistry.registerModules([AllCommunityModule]);
44
49
 
45
- export interface GridBaseRow {
46
- id: string | number;
47
- }
48
-
49
- export interface GridOnRowDragEndProps<TData extends GridBaseRow> {
50
- movedRow: TData;
51
- targetRow: TData;
52
- direction: -1 | 1;
53
- }
54
-
55
50
  export interface GridProps<TData extends GridBaseRow = GridBaseRow> {
56
- readOnly?: boolean; // set all editables to false when read only, make all styles black, otherwise style is gray for not editable
57
- defaultPostSort?: boolean; // Retain sort order after edit, Defaults to true.
58
- selectable?: boolean;
59
- enableClickSelection?: boolean;
60
- enableSelectionWithoutKeys?: boolean;
61
- hideSelectColumn?: boolean;
62
- theme?: string; // should have prefix ag-theme-
63
51
  ['data-testid']?: string;
64
- domLayout?: GridOptions['domLayout'];
65
- externalSelectedItems?: any[];
66
- setExternalSelectedItems?: (items: any[]) => void;
67
- defaultColDef?: GridOptions['defaultColDef'];
52
+ theme?: string;
53
+
54
+ // ─── Grid State ────────────────────────────────────────────────
55
+ loading?: boolean;
56
+ readOnly?: boolean;
57
+ suppressReadOnlyStyle?: boolean;
58
+
59
+ // ─── Data & Columns ────────────────────────────────────────────
68
60
  columnDefs: ColDef<TData>[] | ColGroupDef<TData>[];
61
+ defaultColDef?: GridOptions['defaultColDef'];
62
+ defaultPostSort?: boolean;
63
+ domLayout?: GridOptions['domLayout'];
64
+ pinnedBottomRowData?: GridOptions['pinnedBottomRowData'];
65
+ pinnedTopRowData?: GridOptions['pinnedTopRowData'];
69
66
  rowData: GridOptions['rowData'];
70
- selectColumnPinned?: ColDef['pinned'];
71
- noRowsOverlayText?: string;
72
- noRowsMatchingOverlayText?: string;
73
- animateRows?: boolean;
74
- rowHeight?: number;
75
67
  rowClassRules?: GridOptions['rowClassRules'];
68
+ rowHeight?: number;
76
69
  rowSelection?: 'single' | 'multiple';
70
+ sizeColumns?: 'fit' | 'auto' | 'auto-skip-headers' | 'none';
71
+
72
+ // ─── Selection ────────────────────────────────────────────────
77
73
  autoSelectFirstRow?: boolean;
74
+ enableClickSelection?: boolean;
75
+ enableRangeSelection?: boolean;
76
+ enableSelectionWithoutKeys?: boolean;
77
+ externalSelectedIds?: TData['id'][];
78
+ externalSelectedItems?: TData[];
79
+ hideSelectColumn?: boolean;
80
+ selectColumnPinned?: ColDef['pinned'];
81
+ selectable?: boolean;
82
+ setExternalSelectedIds?: (ids: TData['id'][]) => void;
83
+ setExternalSelectedItems?: (items: TData[]) => void;
84
+
85
+ // ─── Editing ──────────────────────────────────────────────────
86
+ onBulkEditingComplete?: () => Promise<void> | void;
87
+ singleClickEdit?: boolean;
88
+
89
+ // ─── Context Menu ─────────────────────────────────────────────
90
+ contextMenu?: GridContextMenuComponent<TData>;
91
+ contextMenuSelectRow?: boolean;
92
+
93
+ // ─── Callbacks / Events ───────────────────────────────────────
78
94
  onCellFocused?: (props: { colDef: ColDef<TData>; data: TData }) => void;
79
95
  onColumnMoved?: GridOptions['onColumnMoved'];
80
- rowDragText?: GridOptions['rowDragText'];
81
- onRowDragEnd?: (props: GridOnRowDragEndProps<TData>) => Promise<void> | void;
82
- alwaysShowVerticalScroll?: boolean;
83
- suppressColumnVirtualization?: GridOptions['suppressColumnVirtualisation'];
84
- suppressReadOnlyStyle?: boolean;
85
- /**
86
- * When the grid is rendered using sizeColumns=="auto" this is called initially with the required container size to fit all content.
87
- * This allows you set the size of the panel to fit perfectly.
88
- */
89
96
  onContentSize?: (props: { width: number }) => void;
90
- /**
91
- * <ul>
92
- * <li>"none" to use aggrid defaults.</li>
93
- * <li>"fit" will adjust columns to fit within panel via min/max/initial sizing.
94
- * <b>Note:</b> This is only really needed if you have auto-height columns which prevents "auto" from working.
95
- * </li>
96
- * <li>"auto" will size columns based on their content but still obeying min/max sizing.</li>
97
- * <li>"auto-skip-headers" (default) same as auto but does not take headers into account.</li>
98
- * </ul>
99
- *
100
- * If you want to stretch to container width if width is greater than the container add a flex column.
101
- */
102
- sizeColumns?: 'fit' | 'auto' | 'auto-skip-headers' | 'none';
103
- /**
104
- * On first don't return a content size larger than this.
105
- */
106
- maxInitialWidth?: number;
107
- /**
108
- * When pressing tab whilst editing the grid will select and edit the next cell if available.
109
- * Once the last cell to edit closes this callback is called.
110
- */
111
- onBulkEditingComplete?: () => Promise<void> | void;
112
97
 
113
98
  /**
114
- * Context menu definition if required.
99
+ * @deprecated You should drive your app off selection states. This will be deleted.
115
100
  */
116
- contextMenu?: GridContextMenuComponent<any>;
117
-
101
+ onRowClicked?: (event: RowClickedEvent) => void;
118
102
  /**
119
- * Whether to select row on context menu.
103
+ * @deprecated You should drive your app off selection states. This will be deleted.
120
104
  */
121
- contextMenuSelectRow?: boolean;
105
+ onRowDoubleClicked?: (event: RowDoubleClickedEvent) => void;
106
+ onRowDragEnd?: (props: GridOnRowDragEndProps<TData>) => Promise<void> | void;
122
107
 
123
- /**
124
- * Defaults to false.
125
- */
126
- singleClickEdit?: boolean;
108
+ // ─── Row Behavior ─────────────────────────────────────────────
109
+ animateRows?: boolean;
110
+ alwaysShowVerticalScroll?: boolean;
111
+ rowDragText?: GridOptions['rowDragText'];
127
112
 
128
- loading?: boolean;
113
+ // ─── Overlays / Messages ──────────────────────────────────────
114
+ noRowsOverlayText?: string;
115
+ noRowsMatchingOverlayText?: string;
116
+
117
+ // ─── Miscellaneous ────────────────────────────────────────────
118
+ maxInitialWidth?: number;
129
119
  suppressCellFocus?: boolean;
130
- pinnedTopRowData?: GridOptions['pinnedTopRowData'];
131
- pinnedBottomRowData?: GridOptions['pinnedBottomRowData'];
132
- onRowClicked?: (event: RowClickedEvent) => void;
133
- onRowDoubleClicked?: (event: RowDoubleClickedEvent) => void;
120
+ suppressColumnVirtualization?: GridOptions['suppressColumnVirtualisation'];
134
121
  }
135
122
 
136
123
  /**
137
124
  * Wrapper for AgGrid to add commonly used functionality.
138
125
  */
139
126
  export const Grid = <TData extends GridBaseRow = GridBaseRow>({
127
+ theme = 'ag-theme-step-default',
140
128
  'data-testid': dataTestId,
129
+
130
+ // ─── Grid State ───────────────────────────────
131
+ suppressReadOnlyStyle = false,
132
+
133
+ // ─── Data & Columns ───────────────────────────
141
134
  defaultPostSort = true,
135
+ rowData,
136
+ rowHeight = theme === 'ag-theme-step-default' ? 40 : theme === 'ag-theme-step-compact' ? 36 : 40,
142
137
  rowSelection = 'multiple',
143
- suppressColumnVirtualization = true,
144
- theme = 'ag-theme-step-default',
145
138
  sizeColumns = 'auto',
139
+
140
+ // ─── Selection ────────────────────────────────
141
+ autoSelectFirstRow,
142
+ enableRangeSelection = true,
143
+ externalSelectedIds,
144
+ externalSelectedItems,
146
145
  selectColumnPinned = 'left',
147
- contextMenuSelectRow = false,
148
- singleClickEdit = false,
149
- rowData,
150
- rowHeight = theme === 'ag-theme-step-default' ? 40 : theme === 'ag-theme-step-compact' ? 36 : 40,
151
146
  selectable,
147
+ setExternalSelectedIds,
148
+ setExternalSelectedItems,
149
+
150
+ // ─── Editing ──────────────────────────────────
151
+ singleClickEdit = false,
152
+
153
+ // ─── Context Menu ─────────────────────────────
154
+ contextMenuSelectRow = false,
155
+ contextMenu,
156
+
157
+ // ─── Callbacks / Events ───────────────────────
152
158
  onCellFocused: paramsOnCellFocused,
159
+ onColumnMoved,
160
+
161
+ // ─── Row Behavior ─────────────────────────────
162
+ suppressColumnVirtualization = true,
163
+
164
+ // ─── Miscellaneous ────────────────────────────
153
165
  maxInitialWidth,
154
- suppressReadOnlyStyle = false,
166
+
167
+ // ─── Spread Remaining Params ──────────────────
155
168
  ...params
156
169
  }: GridProps<TData>): ReactElement => {
157
170
  const {
171
+ setApis,
172
+ setExternallySelectedItemsAreInSync,
173
+ setOnBulkEditingComplete,
158
174
  gridReady,
159
175
  gridRenderState,
160
- setApis,
161
- ensureRowVisible,
162
- getFirstRowId,
163
- selectRowsById,
164
- focusByRowById,
165
- ensureSelectedRowIsVisible,
176
+ externallySelectedItemsAreInSync,
177
+ showNoRowsOverlay,
166
178
  autoSizeColumns,
167
179
  sizeColumnsToFit,
168
- externallySelectedItemsAreInSync,
169
- setExternallySelectedItemsAreInSync,
170
- isExternalFilterPresent,
171
180
  doesExternalFilterPass,
172
- setOnBulkEditingComplete,
181
+ ensureRowVisible,
182
+ ensureSelectedRowIsVisible,
183
+ focusByRowById,
173
184
  getColDef,
174
- showNoRowsOverlay,
185
+ getFirstRowId,
186
+ isExternalFilterPresent,
175
187
  prePopupOps,
188
+ selectRowsById,
189
+
176
190
  startCellEditing: propStartCellEditing,
177
191
  } = useGridContext<TData>();
192
+
178
193
  // CellEditingStop event happens too much for one edit
179
194
  const startedEditRef = useRef(false);
180
195
  const startCellEditing = useCallback(
181
- (props: StartCellEditingProps) => {
196
+ (props: StartCellEditingProps<TData>) => {
182
197
  startedEditRef.current = true;
183
198
  return propStartCellEditing(props);
184
199
  },
@@ -188,7 +203,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
188
203
  const { updatedDep, anyUpdating, updatingCols } = useContext(GridUpdatingContext);
189
204
 
190
205
  const gridDivRef = useRef<HTMLDivElement>(null);
191
- const lastSelectedIds = useRef<number[]>([]);
206
+ const lastSelectedIds = useRef<TData['id'][] | undefined>();
192
207
 
193
208
  const [staleGrid, setStaleGrid] = useState(false);
194
209
  const [autoSized, setAutoSized] = useState(false);
@@ -326,11 +341,13 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
326
341
  */
327
342
  const hasSelectedFirstItem = useRef(false);
328
343
  useEffect(() => {
329
- if (!gridReady || hasSelectedFirstItem.current || !rowData || !externallySelectedItemsAreInSync) return;
344
+ if (!gridReady || hasSelectedFirstItem.current || !rowData || !externallySelectedItemsAreInSync) {
345
+ return;
346
+ }
330
347
  hasSelectedFirstItem.current = true;
331
- if (isNotEmpty(rowData) && isEmpty(params.externalSelectedItems)) {
348
+ if (isNotEmpty(rowData) && isEmpty(externalSelectedItems) && isEmpty(externalSelectedIds)) {
332
349
  const firstRowId = getFirstRowId();
333
- if (params.autoSelectFirstRow) {
350
+ if (autoSelectFirstRow && selectable) {
334
351
  selectRowsById([firstRowId]);
335
352
  } else {
336
353
  focusByRowById(firstRowId, true);
@@ -340,11 +357,13 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
340
357
  externallySelectedItemsAreInSync,
341
358
  focusByRowById,
342
359
  gridReady,
343
- params.externalSelectedItems,
344
- params.autoSelectFirstRow,
360
+ externalSelectedItems,
361
+ autoSelectFirstRow,
345
362
  rowData,
346
363
  selectRowsById,
347
364
  getFirstRowId,
365
+ selectable,
366
+ externalSelectedIds,
348
367
  ]);
349
368
 
350
369
  /**
@@ -352,24 +371,43 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
352
371
  */
353
372
  const synchroniseExternalStateToGridSelection = useCallback(
354
373
  ({ api }: SelectionChangedEvent) => {
355
- if (!params.externalSelectedItems || !params.setExternalSelectedItems) {
356
- setExternallySelectedItemsAreInSync(true);
357
- return;
358
- }
359
-
360
- const selectedRows = api.getSelectedRows();
361
- // We don't want to update selected Items if it hasn't changed to prevent excess renders
362
- if (
363
- params.externalSelectedItems.length !== selectedRows.length ||
364
- isNotEmpty(xorBy(selectedRows, params.externalSelectedItems, (row) => row.id))
365
- ) {
366
- setExternallySelectedItemsAreInSync(false);
367
- params.setExternalSelectedItems([...selectedRows]);
374
+ if (externalSelectedIds && setExternalSelectedIds) {
375
+ const selectedRowsIds = api.getSelectedRows().map((row: TData) => row.id);
376
+
377
+ // We don't want to update selected Items if it hasn't changed to prevent excess renders
378
+ if (
379
+ externalSelectedIds.length !== selectedRowsIds.length ||
380
+ isNotEmpty(xorBy(selectedRowsIds, externalSelectedIds))
381
+ ) {
382
+ setExternallySelectedItemsAreInSync(false);
383
+ setExternalSelectedIds([...selectedRowsIds]);
384
+ } else {
385
+ setExternallySelectedItemsAreInSync(true);
386
+ }
387
+ } else if (externalSelectedItems && setExternalSelectedItems) {
388
+ const selectedRows = api.getSelectedRows();
389
+
390
+ // We don't want to update selected Items if it hasn't changed to prevent excess renders
391
+ if (
392
+ externalSelectedItems.length !== selectedRows.length ||
393
+ isNotEmpty(xorBy(selectedRows, externalSelectedItems, (row) => row.id))
394
+ ) {
395
+ setExternallySelectedItemsAreInSync(false);
396
+ setExternalSelectedItems([...selectedRows]);
397
+ } else {
398
+ setExternallySelectedItemsAreInSync(true);
399
+ }
368
400
  } else {
369
401
  setExternallySelectedItemsAreInSync(true);
370
402
  }
371
403
  },
372
- [params, setExternallySelectedItemsAreInSync],
404
+ [
405
+ externalSelectedIds,
406
+ externalSelectedItems,
407
+ setExternalSelectedIds,
408
+ setExternalSelectedItems,
409
+ setExternallySelectedItemsAreInSync,
410
+ ],
373
411
  );
374
412
 
375
413
  /**
@@ -378,20 +416,27 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
378
416
  */
379
417
  const synchroniseExternallySelectedItemsToGrid = useCallback(() => {
380
418
  if (!gridReady) return;
381
- if (!params.externalSelectedItems) {
419
+ if (!externalSelectedItems && !externalSelectedIds) {
382
420
  setExternallySelectedItemsAreInSync(true);
383
421
  return;
384
422
  }
385
423
 
386
- const selectedIds = params.externalSelectedItems.map((row) => row.id) as number[];
387
- const lastNewId = last(difference(selectedIds, lastSelectedIds.current));
424
+ const selectedIds = externalSelectedIds ?? externalSelectedItems?.map((row) => row.id);
425
+ const lastNewId = last(difference(selectedIds, lastSelectedIds.current ?? []));
388
426
  if (lastNewId != null) {
389
427
  ensureRowVisible(lastNewId);
390
428
  }
391
429
  lastSelectedIds.current = selectedIds;
392
430
  selectRowsById(selectedIds);
393
431
  setExternallySelectedItemsAreInSync(true);
394
- }, [gridReady, params.externalSelectedItems, ensureRowVisible, selectRowsById, setExternallySelectedItemsAreInSync]);
432
+ }, [
433
+ gridReady,
434
+ externalSelectedItems,
435
+ externalSelectedIds,
436
+ selectRowsById,
437
+ setExternallySelectedItemsAreInSync,
438
+ ensureRowVisible,
439
+ ]);
395
440
 
396
441
  /**
397
442
  * Combine grid and cell editable into one function
@@ -431,7 +476,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
431
476
  cellClassRules: {
432
477
  ...colDef.cellClassRules,
433
478
  'GridCell-readonly': (ccp: CellClassParams<TData>) =>
434
- !suppressReadOnlyStyle && !editable(ccp as any as EditableCallbackParams<TData>),
479
+ !suppressReadOnlyStyle && !editable(ccp as unknown as EditableCallbackParams<TData>),
435
480
  },
436
481
  };
437
482
  }
@@ -446,7 +491,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
446
491
  return params.columnDefs.map(mapColDef);
447
492
  }, [params.columnDefs, mapColDef]);
448
493
 
449
- const hasExternallySelectedItems = !!params.setExternalSelectedItems;
494
+ const hasExternallySelectedItems = !!setExternalSelectedItems || !!setExternalSelectedIds;
450
495
 
451
496
  /**
452
497
  * When grid is ready set the apis to the grid context and sync selected items to grid.
@@ -517,19 +562,6 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
517
562
  [startCellEditing],
518
563
  );
519
564
 
520
- /**
521
- * Handle single click edits
522
- */
523
- const onCellClicked = useCallback(
524
- (event: CellClickedEvent) => {
525
- const editable = fnOrVar(event.colDef?.editable, event);
526
- if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
527
- void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
528
- }
529
- },
530
- [singleClickEdit, startCellEditing],
531
- );
532
-
533
565
  const onCellEditingStopped = useCallback(
534
566
  (event: AgGridEvent<TData>) => {
535
567
  if (!startedEditRef.current) {
@@ -586,6 +618,93 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
586
618
  [prePopupOps, startCellEditing],
587
619
  );
588
620
 
621
+ const { cellContextMenu, contextMenuComponent } = useGridContextMenu({
622
+ contextMenu,
623
+ contextMenuSelectRow,
624
+ });
625
+
626
+ const hasSelectedMoreThanOneCellRef = useRef(false);
627
+ const rangeStartRef = useRef<CellLocation | null>(null);
628
+ const rangeEndRef = useRef<CellLocation | null>(null);
629
+ const rangeSortedNodesRef = useRef<IRowNode<TData>[] | null>(null);
630
+
631
+ const { clearRangeSelection, ranges, onCellMouseDown, onCellMouseOver } = useGridRangeSelection({
632
+ enableRangeSelection,
633
+ gridDivRef,
634
+ rangeStartRef,
635
+ rangeEndRef,
636
+ hasSelectedMoreThanOneCellRef,
637
+ rangeSortedNodesRef,
638
+ });
639
+
640
+ const { onCopyEvent, rangeSelectInterceptContextMenu, rangeSelectContextMenuComponent } = useGridCopy({
641
+ ranges,
642
+ rangeStartRef,
643
+ rangeEndRef,
644
+ hasSelectedMoreThanOneCellRef,
645
+ cellContextMenu,
646
+ });
647
+
648
+ const onSortChanged = useCallback(() => {
649
+ clearRangeSelection();
650
+ ensureSelectedRowIsVisible();
651
+ }, [clearRangeSelection, ensureSelectedRowIsVisible]);
652
+
653
+ /**
654
+ * Handle single click edits
655
+ */
656
+ const onCellClicked = useCallback(
657
+ (event: CellClickedEvent) => {
658
+ if (rangeStartRef.current && rangeEndRef.current) {
659
+ // This is to detect difference between a single click and a click drag return to cell.
660
+ if (rangeEndRef.current.timestamp - rangeStartRef.current.timestamp < 100) {
661
+ clearRangeSelection();
662
+ }
663
+ return;
664
+ }
665
+ const editable = fnOrVar(event.colDef?.editable, event);
666
+ if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
667
+ void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
668
+ }
669
+ },
670
+ [clearRangeSelection, singleClickEdit, startCellEditing],
671
+ );
672
+
673
+ /**
674
+ * Set of column I'd's that are prevented from auto-sizing as they are user set
675
+ */
676
+ const userSizedColIds = useRef(new Map<string, number>());
677
+
678
+ /**
679
+ * Lock/unlock column width on user edit/reset.
680
+ */
681
+ const onColumnResized = useCallback((e: ColumnResizedEvent) => {
682
+ const colId = e.column?.getColId();
683
+ if (colId == null) {
684
+ return;
685
+ }
686
+ const width = e.column?.getActualWidth();
687
+ if (width == null) {
688
+ return;
689
+ }
690
+ switch (e.source) {
691
+ case 'uiColumnResized':
692
+ userSizedColIds.current.set(colId, width);
693
+ break;
694
+ case 'autosizeColumns':
695
+ userSizedColIds.current.delete(colId);
696
+ break;
697
+ }
698
+ }, []);
699
+
700
+ const columnMoved = useCallback(
701
+ (e: ColumnMovedEvent<TData>) => {
702
+ clearRangeSelection();
703
+ onColumnMoved?.(e);
704
+ },
705
+ [clearRangeSelection, onColumnMoved],
706
+ );
707
+
589
708
  /**
590
709
  * Once the grid has auto-sized we want to run fit to fit the grid in its container,
591
710
  * but we don't want the non-flex auto-sized columns to "fit" size, so suppressSizeToFit is set to true.
@@ -606,7 +725,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
606
725
  let comparator = colDef.comparator;
607
726
  if (sortable && !comparator) {
608
727
  comparator = (value1, value2, node1, node2) => {
609
- let r = 0;
728
+ let r;
610
729
  if (typeof valueFormatter === 'function') {
611
730
  r = compareNaturalInsensitive(
612
731
  valueFormatter({
@@ -703,40 +822,6 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
703
822
  prevLoading.current = newLoading;
704
823
  }, [params.loading, rowData, showNoRowsOverlay]);
705
824
 
706
- /**
707
- * Set of column I'd's that are prevented from auto-sizing as they are user set
708
- */
709
- const userSizedColIds = useRef(new Map<string, number>());
710
-
711
- /**
712
- * Lock/unlock column width on user edit/reset.
713
- */
714
- const onColumnResized = useCallback((e: ColumnResizedEvent) => {
715
- const colId = e.column?.getColId();
716
- if (colId == null) {
717
- return;
718
- }
719
- const width = e.column?.getActualWidth();
720
- if (width == null) {
721
- return;
722
- }
723
- switch (e.source) {
724
- case 'uiColumnResized':
725
- userSizedColIds.current.set(colId, width);
726
- /*const colDef = e.column?.getColDef();
727
- if (!colDef?.flex) {
728
- onGridResize(e);
729
- }*/
730
- break;
731
- case 'autosizeColumns':
732
- userSizedColIds.current.delete(colId);
733
- //onGridResize(e);
734
- break;
735
- }
736
- }, []);
737
-
738
- const gridContextMenu = useGridContextMenu({ contextMenu: params.contextMenu, contextMenuSelectRow });
739
-
740
825
  const startDragYRef = useRef<number | null>(null);
741
826
 
742
827
  const clearHighlightRowClasses = useCallback(() => {
@@ -828,14 +913,19 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
828
913
  [params, clearHighlightRowClasses],
829
914
  );
830
915
 
831
- useEffect(() => {
832
- if (params.setExternalSelectedItems && selectable == null) {
833
- console.warn(
834
- '<Grid/> has setExternalSelectedItems parameter, but is missing selectable parameter,' +
835
- 'this will cause weird delays in editing.\nIf you need to hide selection column use hideSelectColumn=true',
836
- );
837
- }
838
- }, [params.setExternalSelectedItems, selectable]);
916
+ useEffect(
917
+ () => {
918
+ if ((setExternalSelectedItems || setExternalSelectedIds) && selectable == null) {
919
+ console.warn(
920
+ '<Grid/> has setExternalSelectedItems/setExternalSelectedIds parameter, but is missing selectable parameter,' +
921
+ 'this will cause weird delays in editing.\nIf you need to hide selection column use hideSelectColumn=true',
922
+ );
923
+ }
924
+ },
925
+ // Only needs to run on startup
926
+ // eslint-disable-next-line react-hooks/exhaustive-deps
927
+ [selectable],
928
+ );
839
929
 
840
930
  // This is setting a ref in the GridContext so won't be triggering an update loop
841
931
  setOnBulkEditingComplete(params.onBulkEditingComplete);
@@ -897,19 +987,17 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
897
987
  }
898
988
  return false;
899
989
  },
900
- onCellClicked:
901
- params.enableSelectionWithoutKeys || params.enableClickSelection
902
- ? undefined
903
- : clickInputWhenContainingCellClicked,
990
+ // enableClickSelection = true means ag-grid auto selects row if you click outside the checkbox
991
+ // if it's false we have to do it.
992
+ onCellClicked: params.enableClickSelection ? undefined : clickInputWhenContainingCellClicked,
904
993
  };
905
994
  }, [
906
- params.enableClickSelection,
907
- params.enableSelectionWithoutKeys,
908
995
  params.hideSelectColumn,
909
996
  params.onRowDragEnd,
910
997
  rowSelection,
911
998
  selectColumnPinned,
912
999
  selectable,
1000
+ params.enableClickSelection,
913
1001
  ]);
914
1002
 
915
1003
  const onGridSizeChanged = useCallback(
@@ -932,10 +1020,52 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
932
1020
  gridReady && rowData && autoSized && 'Grid-ready',
933
1021
  )}
934
1022
  >
935
- {gridContextMenu.component}
936
- <div style={{ flex: 1 }} ref={gridDivRef}>
1023
+ {contextMenuComponent}
1024
+ {rangeSelectContextMenuComponent}
1025
+ <div style={{ flex: 1 }} ref={gridDivRef} onCopy={onCopyEvent}>
937
1026
  <AgGridReact
938
1027
  theme={'legacy'}
1028
+ domLayout={params.domLayout}
1029
+ defaultColDef={defaultColDef}
1030
+ columnDefs={columnDefsAdjusted}
1031
+ getRowId={getRowId}
1032
+ rowData={rowData}
1033
+ alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
1034
+ animateRows={params.animateRows ?? false}
1035
+ doesExternalFilterPass={doesExternalFilterPass}
1036
+ isExternalFilterPresent={isExternalFilterPresent}
1037
+ maintainColumnOrder={true}
1038
+ noRowsOverlayComponent={noRowsOverlayComponent}
1039
+ onCellClicked={onCellClicked}
1040
+ onCellContextMenu={rangeSelectInterceptContextMenu}
1041
+ onCellDoubleClicked={onCellDoubleClick}
1042
+ onCellEditingStopped={onCellEditingStopped}
1043
+ onCellFocused={onCellFocused}
1044
+ onCellKeyDown={onCellKeyPress}
1045
+ onCellMouseDown={onCellMouseDown}
1046
+ onCellMouseOver={onCellMouseOver}
1047
+ onColumnMoved={columnMoved}
1048
+ onColumnResized={onColumnResized}
1049
+ onColumnVisible={() => void setInitialContentSize()}
1050
+ onGridReady={onGridReady}
1051
+ onGridSizeChanged={onGridSizeChanged}
1052
+ onModelUpdated={onModelUpdated}
1053
+ onRowClicked={params.onRowClicked}
1054
+ onRowDataUpdated={onRowDataUpdated}
1055
+ onRowDoubleClicked={params.onRowDoubleClicked}
1056
+ onRowDragCancel={clearHighlightRowClasses}
1057
+ onRowDragEnd={onRowDragEnd}
1058
+ onRowDragMove={onRowDragMove}
1059
+ onSelectionChanged={synchroniseExternalStateToGridSelection}
1060
+ onSortChanged={onSortChanged}
1061
+ pinnedBottomRowData={params.pinnedBottomRowData}
1062
+ pinnedTopRowData={params.pinnedTopRowData}
1063
+ postSortRows={params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows}
1064
+ preventDefaultOnContextMenu={true}
1065
+ quickFilterParser={quickFilterParser}
1066
+ rowClassRules={params.rowClassRules}
1067
+ rowDragText={params.rowDragText}
1068
+ rowHeight={rowHeight}
939
1069
  rowSelection={
940
1070
  selectable
941
1071
  ? {
@@ -950,48 +1080,9 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
950
1080
  : undefined
951
1081
  }
952
1082
  selectionColumnDef={selectionColumnDef}
953
- rowHeight={rowHeight}
954
- animateRows={params.animateRows ?? false}
955
- rowClassRules={params.rowClassRules}
956
- getRowId={getRowId}
957
- suppressColumnVirtualisation={suppressColumnVirtualization}
958
- suppressClickEdit={true}
959
- onGridSizeChanged={onGridSizeChanged}
960
- onColumnVisible={() => void setInitialContentSize()}
961
- onRowDataUpdated={onRowDataUpdated}
962
- onCellFocused={onCellFocused}
963
- onCellKeyDown={onCellKeyPress}
964
- onCellClicked={onCellClicked}
965
- onCellDoubleClicked={onCellDoubleClick}
966
- onCellEditingStopped={onCellEditingStopped}
967
- domLayout={params.domLayout}
968
- onColumnResized={onColumnResized}
969
- defaultColDef={defaultColDef}
970
- columnDefs={columnDefsAdjusted}
971
- rowData={rowData}
972
- postSortRows={params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows}
973
- onModelUpdated={onModelUpdated}
974
- onGridReady={onGridReady}
975
- onSortChanged={ensureSelectedRowIsVisible}
976
- quickFilterParser={quickFilterParser}
977
- onSelectionChanged={synchroniseExternalStateToGridSelection}
978
- onColumnMoved={params.onColumnMoved}
979
- noRowsOverlayComponent={noRowsOverlayComponent}
980
- alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
981
- isExternalFilterPresent={isExternalFilterPresent}
982
- doesExternalFilterPass={doesExternalFilterPass}
983
- maintainColumnOrder={true}
984
- preventDefaultOnContextMenu={true}
985
- onCellContextMenu={gridContextMenu.cellContextMenu}
986
- rowDragText={params.rowDragText}
987
- onRowDragCancel={clearHighlightRowClasses}
988
- onRowDragMove={onRowDragMove}
989
- onRowDragEnd={onRowDragEnd}
990
1083
  suppressCellFocus={params.suppressCellFocus}
991
- pinnedTopRowData={params.pinnedTopRowData}
992
- pinnedBottomRowData={params.pinnedBottomRowData}
993
- onRowClicked={params.onRowClicked}
994
- onRowDoubleClicked={params.onRowDoubleClicked}
1084
+ suppressClickEdit={true}
1085
+ suppressColumnVirtualisation={suppressColumnVirtualization}
995
1086
  suppressStartEditOnTab={true}
996
1087
  />
997
1088
  </div>