@linzjs/step-ag-grid 29.13.0 → 29.14.1

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 (44) hide show
  1. package/dist/index.css +26 -2
  2. package/dist/src/components/Grid.d.ts +36 -61
  3. package/dist/src/components/GridRangeSelectContextMenu.d.ts +10 -0
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +2 -1
  6. package/dist/src/components/gridHook/useGridContextMenu.d.ts +12 -7
  7. package/dist/src/components/gridHook/useGridCopy.d.ts +15 -0
  8. package/dist/src/components/gridHook/useGridCopySettings.d.ts +11 -0
  9. package/dist/src/components/gridHook/useGridRangeSelection.d.ts +25 -0
  10. package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +2 -0
  11. package/dist/src/components/types.d.ts +1 -0
  12. package/dist/src/contexts/GridContext.d.ts +1 -0
  13. package/dist/src/lui/timeoutHook.d.ts +0 -6
  14. package/dist/src/lui/tsUtils.d.ts +5 -0
  15. package/dist/src/utils/__tests__/random.ts +19 -0
  16. package/dist/src/utils/util.d.ts +1 -0
  17. package/dist/step-ag-grid.cjs +560 -79
  18. package/dist/step-ag-grid.cjs.map +1 -1
  19. package/dist/step-ag-grid.esm.js +561 -82
  20. package/dist/step-ag-grid.esm.js.map +1 -1
  21. package/package.json +1 -1
  22. package/src/components/Grid.tsx +249 -179
  23. package/src/components/GridRangeSelectContextMenu.tsx +73 -0
  24. package/src/components/gridForm/GridFormDropDown.tsx +1 -3
  25. package/src/components/gridForm/GridFormPopoverMenu.tsx +2 -1
  26. package/src/components/gridHook/useGridContextMenu.tsx +22 -9
  27. package/src/components/gridHook/useGridCopy.ts +279 -0
  28. package/src/components/gridHook/useGridCopySettings.ts +28 -0
  29. package/src/components/gridHook/useGridRangeSelection.ts +235 -0
  30. package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +3 -0
  31. package/src/components/types.ts +2 -0
  32. package/src/contexts/GridContext.tsx +2 -0
  33. package/src/contexts/GridContextProvider.tsx +5 -2
  34. package/src/lui/timeoutHook.tsx +0 -19
  35. package/src/lui/tsUtils.ts +8 -0
  36. package/src/stories/grid/GridCopy.stories.tsx +175 -0
  37. package/src/stories/grid/GridPopoverEditBearing.stories.tsx +4 -4
  38. package/src/stories/grid/GridReadOnly.stories.tsx +1 -0
  39. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +2 -2
  40. package/src/styles/ContextMenu.scss +61 -0
  41. package/src/styles/Grid.scss +26 -2
  42. package/src/utils/__tests__/random.ts +19 -0
  43. package/src/utils/bearing.ts +2 -2
  44. package/src/utils/util.ts +2 -0
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "29.13.0",
5
+ "version": "29.14.1",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -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,6 +39,8 @@ 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';
42
46
  import { GridBaseRow, GridOnRowDragEndProps } from './types';
@@ -44,135 +48,148 @@ import { GridBaseRow, GridOnRowDragEndProps } from './types';
44
48
  ModuleRegistry.registerModules([AllCommunityModule]);
45
49
 
46
50
  export interface GridProps<TData extends GridBaseRow = GridBaseRow> {
47
- readOnly?: boolean; // set all editables to false when read only, make all styles black, otherwise style is gray for not editable
48
- defaultPostSort?: boolean; // Retain sort order after edit, Defaults to true.
49
- selectable?: boolean;
50
- enableClickSelection?: boolean;
51
- enableSelectionWithoutKeys?: boolean;
52
- hideSelectColumn?: boolean;
53
- theme?: string; // should have prefix ag-theme-
54
51
  ['data-testid']?: string;
55
- domLayout?: GridOptions['domLayout'];
56
- externalSelectedItems?: TData[];
57
- externalSelectedIds?: TData['id'][];
58
- setExternalSelectedItems?: (items: TData[]) => void;
59
- setExternalSelectedIds?: (ids: TData['id'][]) => void;
60
- defaultColDef?: GridOptions['defaultColDef'];
52
+ theme?: string;
53
+
54
+ // ─── Grid State ────────────────────────────────────────────────
55
+ loading?: boolean;
56
+ readOnly?: boolean;
57
+ suppressReadOnlyStyle?: boolean;
58
+
59
+ // ─── Data & Columns ────────────────────────────────────────────
61
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'];
62
66
  rowData: GridOptions['rowData'];
63
- selectColumnPinned?: ColDef['pinned'];
64
- noRowsOverlayText?: string;
65
- noRowsMatchingOverlayText?: string;
66
- animateRows?: boolean;
67
- rowHeight?: number;
68
67
  rowClassRules?: GridOptions['rowClassRules'];
68
+ rowHeight?: number;
69
69
  rowSelection?: 'single' | 'multiple';
70
+ sizeColumns?: 'fit' | 'auto' | 'auto-skip-headers' | 'none';
71
+
72
+ // ─── Selection ────────────────────────────────────────────────
70
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 ───────────────────────────────────────
71
94
  onCellFocused?: (props: { colDef: ColDef<TData>; data: TData }) => void;
72
95
  onColumnMoved?: GridOptions['onColumnMoved'];
73
- rowDragText?: GridOptions['rowDragText'];
74
- onRowDragEnd?: (props: GridOnRowDragEndProps<TData>) => Promise<void> | void;
75
- alwaysShowVerticalScroll?: boolean;
76
- suppressColumnVirtualization?: GridOptions['suppressColumnVirtualisation'];
77
- suppressReadOnlyStyle?: boolean;
78
- /**
79
- * When the grid is rendered using sizeColumns=="auto" this is called initially with the required container size to fit all content.
80
- * This allows you set the size of the panel to fit perfectly.
81
- */
82
96
  onContentSize?: (props: { width: number }) => void;
83
- /**
84
- * <ul>
85
- * <li>"none" to use aggrid defaults.</li>
86
- * <li>"fit" will adjust columns to fit within panel via min/max/initial sizing.
87
- * <b>Note:</b> This is only really needed if you have auto-height columns which prevents "auto" from working.
88
- * </li>
89
- * <li>"auto" will size columns based on their content but still obeying min/max sizing.</li>
90
- * <li>"auto-skip-headers" (default) same as auto but does not take headers into account.</li>
91
- * </ul>
92
- *
93
- * If you want to stretch to container width if width is greater than the container add a flex column.
94
- */
95
- sizeColumns?: 'fit' | 'auto' | 'auto-skip-headers' | 'none';
96
- /**
97
- * On first don't return a content size larger than this.
98
- */
99
- maxInitialWidth?: number;
100
- /**
101
- * When pressing tab whilst editing the grid will select and edit the next cell if available.
102
- * Once the last cell to edit closes this callback is called.
103
- */
104
- onBulkEditingComplete?: () => Promise<void> | void;
105
97
 
106
98
  /**
107
- * Context menu definition if required.
99
+ * @deprecated You should drive your app off selection states. This will be deleted.
108
100
  */
109
- contextMenu?: GridContextMenuComponent<TData>;
110
-
101
+ onRowClicked?: (event: RowClickedEvent) => void;
111
102
  /**
112
- * Whether to select row on context menu.
103
+ * @deprecated You should drive your app off selection states. This will be deleted.
113
104
  */
114
- contextMenuSelectRow?: boolean;
105
+ onRowDoubleClicked?: (event: RowDoubleClickedEvent) => void;
106
+ onRowDragEnd?: (props: GridOnRowDragEndProps<TData>) => Promise<void> | void;
115
107
 
116
- /**
117
- * Defaults to false.
118
- */
119
- singleClickEdit?: boolean;
108
+ // ─── Row Behavior ─────────────────────────────────────────────
109
+ animateRows?: boolean;
110
+ alwaysShowVerticalScroll?: boolean;
111
+ rowDragText?: GridOptions['rowDragText'];
120
112
 
121
- loading?: boolean;
113
+ // ─── Overlays / Messages ──────────────────────────────────────
114
+ noRowsOverlayText?: string;
115
+ noRowsMatchingOverlayText?: string;
116
+
117
+ // ─── Miscellaneous ────────────────────────────────────────────
118
+ maxInitialWidth?: number;
122
119
  suppressCellFocus?: boolean;
123
- pinnedTopRowData?: GridOptions['pinnedTopRowData'];
124
- pinnedBottomRowData?: GridOptions['pinnedBottomRowData'];
125
- onRowClicked?: (event: RowClickedEvent) => void;
126
- onRowDoubleClicked?: (event: RowDoubleClickedEvent) => void;
120
+ suppressColumnVirtualization?: GridOptions['suppressColumnVirtualisation'];
127
121
  }
128
122
 
129
123
  /**
130
124
  * Wrapper for AgGrid to add commonly used functionality.
131
125
  */
132
126
  export const Grid = <TData extends GridBaseRow = GridBaseRow>({
127
+ theme = 'ag-theme-step-default',
133
128
  'data-testid': dataTestId,
129
+
130
+ // ─── Grid State ───────────────────────────────
131
+ suppressReadOnlyStyle = false,
132
+
133
+ // ─── Data & Columns ───────────────────────────
134
134
  defaultPostSort = true,
135
+ rowData,
136
+ rowHeight = theme === 'ag-theme-step-default' ? 40 : theme === 'ag-theme-step-compact' ? 36 : 40,
135
137
  rowSelection = 'multiple',
136
- suppressColumnVirtualization = true,
137
- theme = 'ag-theme-step-default',
138
138
  sizeColumns = 'auto',
139
+
140
+ // ─── Selection ────────────────────────────────
141
+ autoSelectFirstRow,
142
+ enableRangeSelection = false,
143
+ externalSelectedIds,
144
+ externalSelectedItems,
139
145
  selectColumnPinned = 'left',
140
- contextMenuSelectRow = false,
141
- singleClickEdit = false,
142
- rowData,
143
- rowHeight = theme === 'ag-theme-step-default' ? 40 : theme === 'ag-theme-step-compact' ? 36 : 40,
144
146
  selectable,
145
- autoSelectFirstRow,
147
+ setExternalSelectedIds,
148
+ setExternalSelectedItems,
149
+
150
+ // ─── Editing ──────────────────────────────────
151
+ singleClickEdit = false,
152
+
153
+ // ─── Context Menu ─────────────────────────────
154
+ contextMenuSelectRow = false,
155
+ contextMenu,
156
+
157
+ // ─── Callbacks / Events ───────────────────────
146
158
  onCellFocused: paramsOnCellFocused,
159
+ onColumnMoved,
160
+
161
+ // ─── Row Behavior ─────────────────────────────
162
+ suppressColumnVirtualization = true,
163
+
164
+ // ─── Miscellaneous ────────────────────────────
147
165
  maxInitialWidth,
148
- suppressReadOnlyStyle = false,
149
- externalSelectedItems,
150
- setExternalSelectedItems,
151
- externalSelectedIds,
152
- setExternalSelectedIds,
166
+
167
+ // ─── Spread Remaining Params ──────────────────
153
168
  ...params
154
169
  }: GridProps<TData>): ReactElement => {
155
170
  const {
171
+ setApis,
172
+ setExternallySelectedItemsAreInSync,
173
+ setOnBulkEditingComplete,
156
174
  gridReady,
157
175
  gridRenderState,
158
- setApis,
159
- ensureRowVisible,
160
- getFirstRowId,
161
- selectRowsById,
162
- focusByRowById,
163
- ensureSelectedRowIsVisible,
176
+ externallySelectedItemsAreInSync,
177
+ showNoRowsOverlay,
164
178
  autoSizeColumns,
165
179
  sizeColumnsToFit,
166
- externallySelectedItemsAreInSync,
167
- setExternallySelectedItemsAreInSync,
168
- isExternalFilterPresent,
169
180
  doesExternalFilterPass,
170
- setOnBulkEditingComplete,
181
+ ensureRowVisible,
182
+ ensureSelectedRowIsVisible,
183
+ focusByRowById,
171
184
  getColDef,
172
- showNoRowsOverlay,
185
+ getFirstRowId,
186
+ isExternalFilterPresent,
173
187
  prePopupOps,
188
+ selectRowsById,
189
+
174
190
  startCellEditing: propStartCellEditing,
175
191
  } = useGridContext<TData>();
192
+
176
193
  // CellEditingStop event happens too much for one edit
177
194
  const startedEditRef = useRef(false);
178
195
  const startCellEditing = useCallback(
@@ -328,7 +345,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
328
345
  return;
329
346
  }
330
347
  hasSelectedFirstItem.current = true;
331
- if (isNotEmpty(rowData) && isEmpty(externalSelectedItems)) {
348
+ if (isNotEmpty(rowData) && isEmpty(externalSelectedItems) && isEmpty(externalSelectedIds)) {
332
349
  const firstRowId = getFirstRowId();
333
350
  if (autoSelectFirstRow && selectable) {
334
351
  selectRowsById([firstRowId]);
@@ -346,6 +363,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
346
363
  selectRowsById,
347
364
  getFirstRowId,
348
365
  selectable,
366
+ externalSelectedIds,
349
367
  ]);
350
368
 
351
369
  /**
@@ -544,19 +562,6 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
544
562
  [startCellEditing],
545
563
  );
546
564
 
547
- /**
548
- * Handle single click edits
549
- */
550
- const onCellClicked = useCallback(
551
- (event: CellClickedEvent) => {
552
- const editable = fnOrVar(event.colDef?.editable, event);
553
- if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
554
- void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
555
- }
556
- },
557
- [singleClickEdit, startCellEditing],
558
- );
559
-
560
565
  const onCellEditingStopped = useCallback(
561
566
  (event: AgGridEvent<TData>) => {
562
567
  if (!startedEditRef.current) {
@@ -613,6 +618,93 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
613
618
  [prePopupOps, startCellEditing],
614
619
  );
615
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
+
616
708
  /**
617
709
  * Once the grid has auto-sized we want to run fit to fit the grid in its container,
618
710
  * but we don't want the non-flex auto-sized columns to "fit" size, so suppressSizeToFit is set to true.
@@ -730,40 +822,6 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
730
822
  prevLoading.current = newLoading;
731
823
  }, [params.loading, rowData, showNoRowsOverlay]);
732
824
 
733
- /**
734
- * Set of column I'd's that are prevented from auto-sizing as they are user set
735
- */
736
- const userSizedColIds = useRef(new Map<string, number>());
737
-
738
- /**
739
- * Lock/unlock column width on user edit/reset.
740
- */
741
- const onColumnResized = useCallback((e: ColumnResizedEvent) => {
742
- const colId = e.column?.getColId();
743
- if (colId == null) {
744
- return;
745
- }
746
- const width = e.column?.getActualWidth();
747
- if (width == null) {
748
- return;
749
- }
750
- switch (e.source) {
751
- case 'uiColumnResized':
752
- userSizedColIds.current.set(colId, width);
753
- /*const colDef = e.column?.getColDef();
754
- if (!colDef?.flex) {
755
- onGridResize(e);
756
- }*/
757
- break;
758
- case 'autosizeColumns':
759
- userSizedColIds.current.delete(colId);
760
- //onGridResize(e);
761
- break;
762
- }
763
- }, []);
764
-
765
- const gridContextMenu = useGridContextMenu({ contextMenu: params.contextMenu, contextMenuSelectRow });
766
-
767
825
  const startDragYRef = useRef<number | null>(null);
768
826
 
769
827
  const clearHighlightRowClasses = useCallback(() => {
@@ -929,9 +987,18 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
929
987
  }
930
988
  return false;
931
989
  },
932
- onCellClicked: 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,
933
993
  };
934
- }, [params.hideSelectColumn, params.onRowDragEnd, rowSelection, selectColumnPinned, selectable]);
994
+ }, [
995
+ params.hideSelectColumn,
996
+ params.onRowDragEnd,
997
+ rowSelection,
998
+ selectColumnPinned,
999
+ selectable,
1000
+ params.enableClickSelection,
1001
+ ]);
935
1002
 
936
1003
  const onGridSizeChanged = useCallback(
937
1004
  (event: GridSizeChangedEvent<TData>) => {
@@ -953,10 +1020,52 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
953
1020
  gridReady && rowData && autoSized && 'Grid-ready',
954
1021
  )}
955
1022
  >
956
- {gridContextMenu.component}
957
- <div style={{ flex: 1 }} ref={gridDivRef}>
1023
+ {contextMenuComponent}
1024
+ {rangeSelectContextMenuComponent}
1025
+ <div style={{ flex: 1 }} ref={gridDivRef} onCopy={onCopyEvent}>
958
1026
  <AgGridReact
959
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}
960
1069
  rowSelection={
961
1070
  selectable
962
1071
  ? {
@@ -971,48 +1080,9 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
971
1080
  : undefined
972
1081
  }
973
1082
  selectionColumnDef={selectionColumnDef}
974
- rowHeight={rowHeight}
975
- animateRows={params.animateRows ?? false}
976
- rowClassRules={params.rowClassRules}
977
- getRowId={getRowId}
978
- suppressColumnVirtualisation={suppressColumnVirtualization}
979
- suppressClickEdit={true}
980
- onGridSizeChanged={onGridSizeChanged}
981
- onColumnVisible={() => void setInitialContentSize()}
982
- onRowDataUpdated={onRowDataUpdated}
983
- onCellFocused={onCellFocused}
984
- onCellKeyDown={onCellKeyPress}
985
- onCellClicked={onCellClicked}
986
- onCellDoubleClicked={onCellDoubleClick}
987
- onCellEditingStopped={onCellEditingStopped}
988
- domLayout={params.domLayout}
989
- onColumnResized={onColumnResized}
990
- defaultColDef={defaultColDef}
991
- columnDefs={columnDefsAdjusted}
992
- rowData={rowData}
993
- postSortRows={params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows}
994
- onModelUpdated={onModelUpdated}
995
- onGridReady={onGridReady}
996
- onSortChanged={ensureSelectedRowIsVisible}
997
- quickFilterParser={quickFilterParser}
998
- onSelectionChanged={synchroniseExternalStateToGridSelection}
999
- onColumnMoved={params.onColumnMoved}
1000
- noRowsOverlayComponent={noRowsOverlayComponent}
1001
- alwaysShowVerticalScroll={params.alwaysShowVerticalScroll}
1002
- isExternalFilterPresent={isExternalFilterPresent}
1003
- doesExternalFilterPass={doesExternalFilterPass}
1004
- maintainColumnOrder={true}
1005
- preventDefaultOnContextMenu={true}
1006
- onCellContextMenu={gridContextMenu.cellContextMenu}
1007
- rowDragText={params.rowDragText}
1008
- onRowDragCancel={clearHighlightRowClasses}
1009
- onRowDragMove={onRowDragMove}
1010
- onRowDragEnd={onRowDragEnd}
1011
1083
  suppressCellFocus={params.suppressCellFocus}
1012
- pinnedTopRowData={params.pinnedTopRowData}
1013
- pinnedBottomRowData={params.pinnedBottomRowData}
1014
- onRowClicked={params.onRowClicked}
1015
- onRowDoubleClicked={params.onRowDoubleClicked}
1084
+ suppressClickEdit={true}
1085
+ suppressColumnVirtualisation={suppressColumnVirtualization}
1016
1086
  suppressStartEditOnTab={true}
1017
1087
  />
1018
1088
  </div>
@@ -0,0 +1,73 @@
1
+ import { LuiIcon } from '@linzjs/lui';
2
+ import clsx from 'clsx';
3
+ import { ReactElement, useCallback, useMemo } from 'react';
4
+
5
+ import { typedEntries } from '../lui/tsUtils';
6
+ import { MenuDivider, MenuHeader, MenuItem } from '../react-menu3';
7
+ import { GridContextMenuComponentProps } from './gridHook';
8
+ import { CopyOptionsKey, gridCopyOptions } from './gridHook/useGridCopySettings';
9
+ import { GridBaseRow } from './types';
10
+
11
+ export interface CopyOptionsContext {
12
+ onCopy: (type?: CopyOptionsKey) => void;
13
+ copyType: CopyOptionsKey;
14
+ setCopyType: (copyType: CopyOptionsKey) => void;
15
+ }
16
+
17
+ export const GridRangeSelectContextMenu = <TData extends GridBaseRow>({
18
+ event,
19
+ context,
20
+ }: GridContextMenuComponentProps<TData, CopyOptionsContext>): ReactElement => {
21
+ const developerContextMenu: boolean =
22
+ !!(event?.event as { ctrlKey?: boolean })?.ctrlKey && !!(event?.event as { shiftKey?: boolean })?.shiftKey;
23
+ const onCopy = useMemo(() => context?.onCopy, [context?.onCopy]);
24
+ const onClick = useCallback(
25
+ (type: CopyOptionsKey) => {
26
+ onCopy?.(type);
27
+ },
28
+ [onCopy],
29
+ );
30
+
31
+ return (
32
+ <>
33
+ <MenuHeader>Copy as</MenuHeader>
34
+ {typedEntries(gridCopyOptions).map(([key, { icon, text, developer }]) => {
35
+ return (
36
+ (!developer || developerContextMenu) && (
37
+ <MenuItem key={key} onClick={() => void onClick(key)}>
38
+ <div className={'copyMenuMenuItem'}>
39
+ <LuiIcon name={icon} alt={text} size={'md'} />
40
+ <div className={'copyMenuMenuItem__text'}>{text}</div>
41
+ </div>
42
+ </MenuItem>
43
+ )
44
+ );
45
+ })}
46
+ <MenuDivider />
47
+ <MenuHeader>Set copy default</MenuHeader>
48
+ {typedEntries(gridCopyOptions).map(([key, { text, developer }]) => {
49
+ return (
50
+ !developer && (
51
+ <MenuItem
52
+ key={'default_' + key}
53
+ onClick={(e) => {
54
+ context?.setCopyType(key);
55
+ e.keepOpen = true;
56
+ }}
57
+ >
58
+ <div
59
+ className={clsx(
60
+ 'copyMenuMenuItem',
61
+ context?.copyType === key ? '' : 'copyMenuMenuItem__buttonDefault--hidden',
62
+ )}
63
+ >
64
+ <LuiIcon name={'ic_tick'} alt={'CSV'} size={'sm'} />
65
+ <div className={'copyMenuMenuItem__text'}>{text}</div>
66
+ </div>
67
+ </MenuItem>
68
+ )
69
+ );
70
+ })}
71
+ </>
72
+ );
73
+ };
@@ -9,7 +9,7 @@ import { usePrevious } from '../../lui/reactUtils';
9
9
  import { FocusableItem, MenuDivider, MenuHeader, MenuItem } from '../../react-menu3';
10
10
  import { ClickEvent } from '../../react-menu3/types';
11
11
  import { textMatch } from '../../utils/textMatcher';
12
- import { isNotEmpty } from '../../utils/util';
12
+ import { isNotEmpty, MaybePromise } from '../../utils/util';
13
13
  import { ComponentLoadingWrapper } from '../ComponentLoadingWrapper';
14
14
  import { CellEditorCommon } from '../GridCell';
15
15
  import { useGridPopoverHook } from '../GridPopoverHook';
@@ -47,8 +47,6 @@ export const MenuHeaderItem = (title: string) => {
47
47
 
48
48
  export type SelectOption<TOptionValue = any> = FinalSelectOption<TOptionValue>;
49
49
 
50
- export type MaybePromise<T> = T | Promise<T>;
51
-
52
50
  export interface GridFormDropDownProps<TData extends GridBaseRow, TOptionValue> extends CellEditorCommon {
53
51
  // This overrides CellEditorCommon to provide some common class options
54
52
  className?: // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
@@ -5,6 +5,7 @@ import { useGridPopoverContext } from '../../contexts/GridPopoverContext';
5
5
  import { GridSubComponentContext } from '../../contexts/GridSubComponentContext';
6
6
  import { FocusableItem, MenuDivider, MenuItem, SubMenu } from '../../react-menu3';
7
7
  import { ClickEvent } from '../../react-menu3/types';
8
+ import { MaybePromise } from '../../utils/util';
8
9
  import { ComponentLoadingWrapper } from '../ComponentLoadingWrapper';
9
10
  import { CellEditorCommon } from '../GridCell';
10
11
  import { useGridPopoverHook } from '../GridPopoverHook';
@@ -33,7 +34,7 @@ export interface SelectedMenuOptionResult<TData extends GridBaseRow> extends Men
33
34
  export interface MenuOption<TData extends GridBaseRow> {
34
35
  label: ReactElement | string | MenuSeparatorType;
35
36
  subMenu?: () => ReactElement;
36
- action?: (props: { selectedRows: TData[]; menuOption: SelectedMenuOptionResult<TData> }) => Promise<void>;
37
+ action?: (props: { selectedRows: TData[]; menuOption: SelectedMenuOptionResult<TData> }) => MaybePromise<void>;
37
38
  disabled?: string | boolean;
38
39
  hidden?: boolean;
39
40
  subComponent?: (props: any) => ReactElement;