@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
@@ -0,0 +1,279 @@
1
+ import { useShowLUIMessage } from '@linzjs/lui';
2
+ import { CellContextMenuEvent, IRowNode } from 'ag-grid-community';
3
+ import { compact } from 'lodash-es';
4
+ import { ClipboardEvent as ReactClipboardEvent, MutableRefObject, useCallback } from 'react';
5
+
6
+ import { useGridContext } from '../../contexts/GridContext';
7
+ import { CopyOptionsContext, GridRangeSelectContextMenu } from '../GridRangeSelectContextMenu';
8
+ import { agGridSelectRowColId, GridBaseRow } from '../types';
9
+ import { useGridContextMenu } from './useGridContextMenu';
10
+ import { CopyOptionsKey, gridCopyOptions, useGridCopySettings } from './useGridCopySettings';
11
+ import { CellLocation, GridRanges } from './useGridRangeSelection';
12
+
13
+ export const useGridCopy = <TData extends GridBaseRow>({
14
+ ranges,
15
+ rangeStartRef,
16
+ rangeEndRef,
17
+ hasSelectedMoreThanOneCellRef,
18
+ cellContextMenu,
19
+ }: {
20
+ ranges: () => GridRanges<TData>;
21
+ rangeStartRef: MutableRefObject<CellLocation | null>;
22
+ rangeEndRef: MutableRefObject<CellLocation | null>;
23
+ hasSelectedMoreThanOneCellRef: MutableRefObject<boolean>;
24
+ cellContextMenu: (event: CellContextMenuEvent) => void;
25
+ }) => {
26
+ const showToast = useShowLUIMessage();
27
+ const { getSelectedRowIds } = useGridContext<TData>();
28
+ const { getColDef, getCellValue } = useGridContext<TData>();
29
+ const { copyType, setCopyType } = useGridCopySettings();
30
+
31
+ const onCopy = useCallback(
32
+ (type: CopyOptionsKey = copyType) => {
33
+ const rangeStart = rangeStartRef.current;
34
+ const rangeEnd = rangeEndRef.current;
35
+ if (rangeStart === null || rangeEnd === null || !hasSelectedMoreThanOneCellRef.current) {
36
+ return;
37
+ }
38
+
39
+ if (rangeStart.rowId === rangeEnd.rowId && rangeStart.colId === rangeEnd.colId) {
40
+ type = 'html';
41
+ }
42
+
43
+ const json = type === 'json';
44
+ const { selectedColIds, selectedNodes } = ranges();
45
+ const filteredSelectedColIds = selectedColIds.filter(
46
+ (colId) =>
47
+ colId === agGridSelectRowColId ||
48
+ (colId !== 'gridCellFiller' && getColDef(colId)?.headerComponentParams?.exportable !== false),
49
+ );
50
+
51
+ const selectedRowIds = getSelectedRowIds();
52
+ const formatters = compact(
53
+ filteredSelectedColIds.map((colKey) => {
54
+ return (rowNode: IRowNode): string | number | boolean | null | undefined => {
55
+ if (colKey === agGridSelectRowColId) {
56
+ return selectedRowIds.includes(rowNode.data.id);
57
+ } else {
58
+ const v = getCellValue({ rowNode, colKey });
59
+ if (json && v !== undefined) {
60
+ return v;
61
+ }
62
+ const f = getCellValue({ rowNode, colKey, useFormatter: true });
63
+ // If it's a number, and it matches value return the original type
64
+ return v == f ? v : f;
65
+ }
66
+ };
67
+ }),
68
+ );
69
+
70
+ // Get and apply headers
71
+ const headers = filteredSelectedColIds.map((colId) => {
72
+ if (colId === agGridSelectRowColId) return type === 'json' ? 'selected' : 'Selected';
73
+ if (json) {
74
+ return colId;
75
+ }
76
+ return getColDef(colId)?.headerName ?? '?';
77
+ });
78
+ const maxCellLength: Record<string, number> = {};
79
+ headers.forEach((headerName, i) => {
80
+ const colId = filteredSelectedColIds[i];
81
+ maxCellLength[colId] = headerName.length;
82
+ });
83
+
84
+ const rows: string[][] = [];
85
+ if (type === 'csv' || type === 'html') {
86
+ rows.push(headers);
87
+ }
88
+ selectedNodes.forEach((node) => {
89
+ const row: string[] = [];
90
+ rows.push(row);
91
+ formatters.forEach((formatter, i) => {
92
+ const colId = filteredSelectedColIds[i];
93
+ let value = formatter(node);
94
+ if (typeof value === 'string') {
95
+ value = value.replaceAll('\xa0', ' ');
96
+ }
97
+ if (!json) {
98
+ if (value === '-' || value === '–' || value == null) {
99
+ value = '';
100
+ }
101
+ value = String(value);
102
+ if (value === '-' || value === '–') {
103
+ value = '';
104
+ }
105
+ maxCellLength[colId] = Math.max(maxCellLength[colId], value.length);
106
+ }
107
+
108
+ switch (type) {
109
+ case 'json':
110
+ value =
111
+ value === 'undefined' || (value !== null && typeof value === 'object') || typeof value === 'string'
112
+ ? JSON.stringify(value)
113
+ : value;
114
+ break;
115
+ case 'markdown':
116
+ value = encodeMarkdownValue(value as string);
117
+ break;
118
+ case 'csv':
119
+ value = encodeCSVValue(value as string);
120
+ break;
121
+ }
122
+ row.push(String(value));
123
+ });
124
+ });
125
+
126
+ let result = '';
127
+ let html = `<table style="
128
+ font-family: 'Open Sans', system-ui, sans-serif;
129
+ font-size: 10px;
130
+ line-height: 10px;
131
+ border: 1px solid #d1d9e0;
132
+ border-collapse: collapse;
133
+ ">`;
134
+ if (json) {
135
+ result += '[\n';
136
+ }
137
+ rows.forEach((row, rowIndex) => {
138
+ if (json) {
139
+ result += ' { ';
140
+ }
141
+ if (rowIndex == 1) {
142
+ html += '<tbody style="font-weight: 400;">';
143
+ }
144
+ if (rowIndex == 0) {
145
+ html += '<thead style="font-weight: 600;">';
146
+ }
147
+ if (rowIndex === 0 || (rowIndex & 1) === 1) {
148
+ html += '<tr>';
149
+ } else {
150
+ html += '<tr style="background-color: #f6f8fa;">';
151
+ }
152
+ if (rowIndex === 1 && type === 'html') {
153
+ Object.values(maxCellLength).forEach((maxLength) => {
154
+ result += '|' + '-'.repeat(maxLength + 2);
155
+ });
156
+ result += '|\n';
157
+ }
158
+ row.forEach((cell, i) => {
159
+ switch (type) {
160
+ case 'csv':
161
+ if (i !== 0) {
162
+ result += ', ';
163
+ }
164
+ result += cell;
165
+ break;
166
+ case 'html':
167
+ if (i === 0) {
168
+ result += '|';
169
+ }
170
+ const colId = filteredSelectedColIds[i];
171
+ result += ' ' + cell.padEnd(maxCellLength[colId], ' ') + ' ';
172
+ result += '|';
173
+ html +=
174
+ rowIndex === 0
175
+ ? '<th style="border: 1px solid #d1d9e0; padding:6px 13px; text-align: left;">'
176
+ : '<td style="border: 1px solid #d1d9e0; padding:6px 13px;">';
177
+ html += cell;
178
+ html += rowIndex === 0 ? '</th>' : '</td>';
179
+ break;
180
+ case 'json':
181
+ if (i !== 0) {
182
+ result += ', ';
183
+ }
184
+ result += JSON.stringify(headers[i]) + ': ' + cell;
185
+ break;
186
+ }
187
+ });
188
+ html += '</tr>';
189
+ if (rowIndex == 0) {
190
+ html += '</thead>';
191
+ }
192
+ if (rowIndex == rows.length - 1) {
193
+ html += '</tbody>';
194
+ }
195
+ if (json) {
196
+ result += ' }';
197
+ }
198
+ result += '\n';
199
+ });
200
+ if (json) {
201
+ result += ']\n';
202
+ }
203
+ html += '</table>';
204
+
205
+ showToast({
206
+ message: `${gridCopyOptions[type].text} copied`,
207
+ messageType: 'toast',
208
+ messageLevel: 'info',
209
+ });
210
+
211
+ if (type === 'html') {
212
+ const clipboardItem = new ClipboardItem({
213
+ 'text/html': new Blob([html], { type: 'text/html' }),
214
+ 'text/plain': new Blob([result], { type: 'text/plain' }), // fallback
215
+ });
216
+
217
+ void navigator.clipboard.write([clipboardItem]).catch((err) => console.error('Failed to copy: ', err));
218
+ } else {
219
+ void navigator.clipboard.writeText(result).catch((err) => console.error('Failed to copy: ', err));
220
+ }
221
+ },
222
+ [getCellValue, ranges, copyType],
223
+ );
224
+
225
+ const onCopyEvent = useCallback(
226
+ (e: ReactClipboardEvent<HTMLDivElement>) => {
227
+ const rangeStart = rangeStartRef.current;
228
+ const rangeEnd = rangeEndRef.current;
229
+ if (
230
+ rangeStart === null ||
231
+ rangeEnd === null ||
232
+ (rangeStart.colId === rangeEnd.colId &&
233
+ rangeStart.rowId === rangeEnd.rowId &&
234
+ !hasSelectedMoreThanOneCellRef.current)
235
+ ) {
236
+ return;
237
+ }
238
+
239
+ e.preventDefault();
240
+
241
+ onCopy();
242
+ },
243
+ [onCopy],
244
+ );
245
+
246
+ const { cellContextMenu: rangeSelectContextMenu, contextMenuComponent: rangeSelectContextMenuComponent } =
247
+ useGridContextMenu<TData, CopyOptionsContext>({
248
+ contextMenu: GridRangeSelectContextMenu<TData>,
249
+ context: { onCopy, copyType, setCopyType },
250
+ });
251
+
252
+ const rangeSelectInterceptContextMenu = useCallback(
253
+ (event: CellContextMenuEvent<TData>) =>
254
+ rangeStartRef.current == null ? cellContextMenu(event) : rangeSelectContextMenu(event),
255
+ [cellContextMenu, rangeSelectContextMenu],
256
+ );
257
+
258
+ return {
259
+ onCopyEvent,
260
+ rangeSelectInterceptContextMenu,
261
+ rangeSelectContextMenuComponent,
262
+ };
263
+ };
264
+
265
+ const encodeMarkdownValue = (value: string): string => {
266
+ return value.replaceAll('|', '\\|');
267
+ };
268
+
269
+ const encodeCSVValue = (value: string): string => {
270
+ let str = String(value);
271
+
272
+ // Check if it needs quoting
273
+ if (/[",\n]/.test(str)) {
274
+ // Escape double quotes by doubling them
275
+ str = '"' + str.replace(/"/g, '""') + '"';
276
+ }
277
+
278
+ return str;
279
+ };
@@ -0,0 +1,28 @@
1
+ import { IconName } from '@linzjs/lui/dist/components/LuiIcon/LuiIcon';
2
+ import { useCallback, useState } from 'react';
3
+
4
+ export const gridCopyOptions: Record<string, { text: string; icon: IconName; developer?: boolean }> = {
5
+ html: { text: 'Text\xa0/\xa0HTML', icon: 'ic_product_list' },
6
+ csv: { text: 'CSV', icon: 'ic_csv_file' },
7
+ json: { text: 'Json', icon: 'ic_file_attached_outline', developer: true },
8
+ };
9
+ export type CopyOptionsKey = keyof typeof gridCopyOptions;
10
+
11
+ const CopyOptionsStorageKey = 'stepAgGrid_defaultCopy';
12
+
13
+ export const useGridCopySettings = () => {
14
+ const [copyType, _setCopyType] = useState<CopyOptionsKey>(() => {
15
+ const defaultCopy = window.localStorage.getItem(CopyOptionsStorageKey) ?? '';
16
+ return Object.keys(gridCopyOptions).includes(defaultCopy) ? defaultCopy : 'html';
17
+ });
18
+
19
+ const setCopyType = useCallback((key: CopyOptionsKey) => {
20
+ window.localStorage.setItem(CopyOptionsStorageKey, key);
21
+ _setCopyType(key);
22
+ }, []);
23
+
24
+ return {
25
+ copyType,
26
+ setCopyType,
27
+ };
28
+ };
@@ -0,0 +1,235 @@
1
+ import { CellMouseDownEvent, CellMouseOverEvent, IRowNode } from 'ag-grid-community';
2
+ import { MutableRefObject, RefObject, useCallback, useState } from 'react';
3
+ import { useInterval } from 'usehooks-ts';
4
+
5
+ import { GridBaseRow } from '../types';
6
+
7
+ export interface CellLocation {
8
+ rowId: string;
9
+ colId: string;
10
+ timestamp: number;
11
+ }
12
+
13
+ export interface GridRanges<TData extends GridBaseRow> {
14
+ selectedColIds: string[];
15
+ selectedNodes: IRowNode<TData>[];
16
+ }
17
+
18
+ export const useGridRangeSelection = <TData extends GridBaseRow>({
19
+ enableRangeSelection,
20
+ gridDivRef,
21
+ rangeStartRef,
22
+ rangeEndRef,
23
+ hasSelectedMoreThanOneCellRef,
24
+ rangeSortedNodesRef,
25
+ }: {
26
+ enableRangeSelection: boolean;
27
+ gridDivRef: RefObject<HTMLDivElement>;
28
+ rangeStartRef: MutableRefObject<CellLocation | null>;
29
+ rangeEndRef: MutableRefObject<CellLocation | null>;
30
+ hasSelectedMoreThanOneCellRef: MutableRefObject<boolean>;
31
+ rangeSortedNodesRef: MutableRefObject<IRowNode<TData>[] | null>;
32
+ }) => {
33
+ const [refreshIntervalEnabled, setRefreshIntervalEnabled] = useState(false);
34
+
35
+ const ranges = useCallback((): GridRanges<TData> => {
36
+ const gridElement = gridDivRef.current!;
37
+ const rangeStart = rangeStartRef.current!;
38
+ const rangeEnd = rangeEndRef.current!;
39
+ const rangeSortedNodes = rangeSortedNodesRef.current!;
40
+ if (!gridElement || !rangeStart || !rangeEnd || !rangeSortedNodes) {
41
+ return {
42
+ selectedColIds: [],
43
+ selectedNodes: [],
44
+ };
45
+ }
46
+
47
+ const elStyleLeftComparator = (el1: Element, el2: Element) => elStyleLeft(el1) - elStyleLeft(el2);
48
+ const elStyleLeft = (el: Element): number => parseFloat((el as HTMLElement).style.left) ?? 0;
49
+
50
+ const getSortedColIds = (): string[] => {
51
+ //
52
+ const leftHeaders = [...gridElement.querySelectorAll('.ag-pinned-left-header .ag-header-cell')].sort(
53
+ elStyleLeftComparator,
54
+ );
55
+ const centerHeaders = [...gridElement.querySelectorAll('.ag-header-viewport .ag-header-cell')].sort(
56
+ elStyleLeftComparator,
57
+ );
58
+ const rightHeaders = [...gridElement.querySelectorAll('.ag-pinned-right-header .ag-header-cell')].sort(
59
+ elStyleLeftComparator,
60
+ );
61
+
62
+ return [...leftHeaders, ...centerHeaders, ...rightHeaders].map((el, i) => el.getAttribute('col-id') ?? String(i));
63
+ };
64
+
65
+ const sortedColIds = getSortedColIds();
66
+
67
+ const startColIndex = sortedColIds.indexOf(rangeStart.colId);
68
+ const endColIndex = sortedColIds.indexOf(rangeEnd.colId);
69
+ const selectedColIds = sortedColIds.slice(
70
+ Math.min(startColIndex, endColIndex),
71
+ Math.max(startColIndex, endColIndex) + 1,
72
+ );
73
+
74
+ const startRowIndex = rangeSortedNodes.findIndex((node) => node.data!.id === rangeStart.rowId);
75
+ const endRowIndex = rangeSortedNodes.findIndex((node) => node.data!.id === rangeEnd.rowId);
76
+ const selectedNodes = rangeSortedNodes.slice(
77
+ Math.min(startRowIndex, endRowIndex),
78
+ Math.max(startRowIndex, endRowIndex) + 1,
79
+ );
80
+
81
+ return { selectedColIds, selectedNodes };
82
+ }, []);
83
+
84
+ const redrawSelectedRanges = useCallback(() => {
85
+ const gridElement = gridDivRef.current!;
86
+ const { selectedColIds, selectedNodes } = ranges();
87
+
88
+ selectedColIds.forEach((colId, colIndex) => {
89
+ selectedNodes.forEach((node, rowIndex) => {
90
+ const rowId = node.data!.id;
91
+ const cell = gridElement.querySelector(
92
+ `.ag-row[row-id=${JSON.stringify(String(rowId))}] .ag-cell[col-id=${JSON.stringify(colId)}`,
93
+ );
94
+ cell?.classList.add('rangeSelect');
95
+ if (colIndex === 0) {
96
+ cell?.classList.add('rangeSelectLeft');
97
+ }
98
+ if (colIndex === selectedColIds.length - 1) {
99
+ cell?.classList.add('rangeSelectRight');
100
+ }
101
+ if (rowIndex === 0) {
102
+ cell?.classList.add('rangeSelectTop');
103
+ }
104
+ if (rowIndex === selectedNodes.length - 1) {
105
+ cell?.classList.add('rangeSelectBottom');
106
+ }
107
+ });
108
+ });
109
+ }, []);
110
+
111
+ const updateRangeSelectionCellClasses = useCallback(
112
+ (justRefresh?: boolean) => {
113
+ //
114
+ // Get all grid cols, sort by pinned, then style: left
115
+ const gridElement = gridDivRef.current;
116
+ if (!gridElement) {
117
+ return;
118
+ }
119
+
120
+ // Clear all selections
121
+ if (!justRefresh) {
122
+ gridElement
123
+ .querySelectorAll('.rangeSelect,.rangeSelectLeft,.rangeSelectTop,.rangeSelectRight,.rangeSelectBottom')
124
+ .forEach((el) => {
125
+ el.classList.remove(
126
+ 'rangeSelect',
127
+ 'rangeSelectLeft',
128
+ 'rangeSelectTop',
129
+ 'rangeSelectRight',
130
+ 'rangeSelectBottom',
131
+ );
132
+ });
133
+ }
134
+
135
+ // if range selection multiple add .Grid-container.rangeSelectingMultiple
136
+ const rangeStart = rangeStartRef.current;
137
+ const rangeEnd = rangeEndRef.current;
138
+
139
+ if (
140
+ rangeStart !== null &&
141
+ rangeEnd !== null &&
142
+ (hasSelectedMoreThanOneCellRef.current ||
143
+ rangeStart.colId !== rangeEnd.colId ||
144
+ rangeStart.rowId !== rangeEnd.rowId)
145
+ ) {
146
+ gridElement.classList.add('rangeSelectingMultiple');
147
+ gridElement.querySelectorAll('.ag-cell-focus').forEach((el) => {
148
+ el.classList.remove('ag-cell-focus');
149
+ });
150
+ } else {
151
+ gridElement.classList.remove('rangeSelectingMultiple');
152
+ return;
153
+ }
154
+ const rangeSortedNodes = rangeSortedNodesRef.current;
155
+ if (!rangeSortedNodes) {
156
+ return;
157
+ }
158
+
159
+ redrawSelectedRanges();
160
+ },
161
+ [ranges, redrawSelectedRanges],
162
+ );
163
+
164
+ // Handle updates after scroll / grid refresh
165
+ useInterval(
166
+ () => {
167
+ updateRangeSelectionCellClasses(true);
168
+ },
169
+ refreshIntervalEnabled ? 150 : null,
170
+ );
171
+
172
+ const clearRangeSelection = useCallback(() => {
173
+ hasSelectedMoreThanOneCellRef.current = false;
174
+ setRefreshIntervalEnabled(false);
175
+
176
+ hasSelectedMoreThanOneCellRef.current = false;
177
+ rangeStartRef.current = null;
178
+ rangeEndRef.current = null;
179
+
180
+ updateRangeSelectionCellClasses();
181
+ }, [updateRangeSelectionCellClasses]);
182
+
183
+ const onCellMouseOver = useCallback(
184
+ (e: CellMouseOverEvent, mouseDown?: boolean) => {
185
+ if (!enableRangeSelection) {
186
+ return;
187
+ }
188
+ const button = (e.event as { buttons?: number }).buttons;
189
+ if (button !== 1) {
190
+ // TODO fix this?
191
+ //rangeSortedNodesRef.current = null;
192
+ return;
193
+ }
194
+ rangeEndRef.current = {
195
+ rowId: e.node.data.id,
196
+ colId: e.column.getColId(),
197
+ timestamp: Date.now(),
198
+ };
199
+
200
+ if (mouseDown) {
201
+ const sortedNodes: IRowNode<TData>[] = [];
202
+ e.api.forEachNodeAfterFilterAndSort((node: IRowNode<TData>) => sortedNodes.push(node));
203
+ rangeSortedNodesRef.current = sortedNodes;
204
+ setRefreshIntervalEnabled(true);
205
+ rangeStartRef.current = { ...rangeEndRef.current };
206
+ }
207
+
208
+ if (
209
+ rangeStartRef.current &&
210
+ rangeEndRef.current &&
211
+ (rangeStartRef.current.rowId !== rangeEndRef.current.rowId ||
212
+ rangeStartRef.current.colId !== rangeEndRef.current.colId)
213
+ ) {
214
+ hasSelectedMoreThanOneCellRef.current = true;
215
+ window.getSelection()?.removeAllRanges();
216
+ }
217
+
218
+ updateRangeSelectionCellClasses();
219
+ },
220
+ [enableRangeSelection, updateRangeSelectionCellClasses],
221
+ );
222
+
223
+ const onCellMouseDown = useCallback(
224
+ (e: CellMouseDownEvent) => {
225
+ const button = (e.event as { buttons?: number }).buttons;
226
+ if (button === 1) {
227
+ clearRangeSelection();
228
+ }
229
+ onCellMouseOver(e as unknown as CellMouseOverEvent, true);
230
+ },
231
+ [onCellMouseOver],
232
+ );
233
+
234
+ return { clearRangeSelection, ranges, onCellMouseDown, onCellMouseOver };
235
+ };
@@ -2,9 +2,9 @@ import { LuiButton, LuiIcon } from '@linzjs/lui';
2
2
  import { CellFocusedEvent, ICellEditorParams, ICellRendererParams } from 'ag-grid-community';
3
3
  import { useEffect, useRef } from 'react';
4
4
 
5
- import { GridBaseRow } from '../Grid';
6
- import { ColDefT, GridCell } from '../GridCell';
5
+ import { GridCell } from '../GridCell';
7
6
  import { GenericCellColDef } from '../gridRender';
7
+ import { ColDefT, GridBaseRow } from '../types';
8
8
 
9
9
  const ButtonCellRenderer = <TData extends GridBaseRow>(props: ICellRendererParams<TData>) => {
10
10
  const { data, node, column, colDef, api } = props;
@@ -5,9 +5,9 @@ import { useEffect, useRef } from 'react';
5
5
 
6
6
  import { fnOrVar } from '../../utils/util';
7
7
  import { clickInputWhenContainingCellClicked } from '../clickInputWhenContainingCellClicked';
8
- import { GridBaseRow } from '../Grid';
9
- import { CellEditorCommon, ColDefT, GridCell } from '../GridCell';
8
+ import { CellEditorCommon, GridCell } from '../GridCell';
10
9
  import { GenericCellColDef } from '../gridRender';
10
+ import { ColDefT, GridBaseRow } from '../types';
11
11
 
12
12
  const BooleanCellRenderer = (props: CustomCellEditorProps) => {
13
13
  const { onValueChange, value, api, node, column, colDef, data } = props;
@@ -1,9 +1,9 @@
1
1
  import clsx from 'clsx';
2
2
 
3
- import { GridBaseRow } from '../Grid';
4
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
3
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
5
4
  import { GridFormMultiSelect, GridFormMultiSelectProps } from '../gridForm/GridFormMultiSelect';
6
5
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
6
+ import { ColDefT, GridBaseRow } from '../types';
7
7
 
8
8
  export const GridPopoutEditMultiSelect = <TData extends GridBaseRow, TValue = any>(
9
9
  colDef: GenericCellColDef<TData, TValue>,
@@ -1,9 +1,9 @@
1
1
  import clsx from 'clsx';
2
2
 
3
- import { GridBaseRow } from '../Grid';
4
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
3
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
5
4
  import { GridFormMultiSelectGrid, GridFormMultiSelectGridProps } from '../gridForm/GridFormMultiSelectGrid';
6
5
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
6
+ import { ColDefT, GridBaseRow } from '../types';
7
7
 
8
8
  export const GridPopoutEditMultiSelectGrid = <TData extends GridBaseRow, TValue = any>(
9
9
  colDef: GenericCellColDef<TData, TValue>,
@@ -6,10 +6,13 @@ import {
6
6
  bearingRangeValidator,
7
7
  bearingValueFormatter,
8
8
  } from '../../utils/bearing';
9
- import { GridBaseRow } from '../Grid';
10
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
9
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
11
10
  import { GridFormEditBearing, GridFormEditBearingProps } from '../gridForm/GridFormEditBearing';
12
11
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
12
+ import { ColDefT, GridBaseRow } from '../types';
13
+
14
+ export const GridCellBearingValueFormatter = (params: ValueFormatterParams) =>
15
+ bearingValueFormatter(params.value) ?? '';
13
16
 
14
17
  const GridPopoverEditBearingLike = <TData extends GridBaseRow, TValue = any>(
15
18
  colDef: GenericCellColDef<TData, TValue>,
@@ -1,9 +1,9 @@
1
1
  import clsx from 'clsx';
2
2
 
3
- import { GridBaseRow } from '../Grid';
4
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
3
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
5
4
  import { GridFormDropDown, GridFormDropDownProps } from '../gridForm/GridFormDropDown';
6
5
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
6
+ import { ColDefT, GridBaseRow } from '../types';
7
7
 
8
8
  export const GridPopoverEditDropDown = <TData extends GridBaseRow, TValue = any, TOptionValue = any>(
9
9
  colDef: GenericCellColDef<TData, TValue>,
@@ -1,8 +1,8 @@
1
- import { GridBaseRow } from '../Grid';
2
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
1
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
3
2
  import { GridFormPopoverMenu, GridFormPopoverMenuProps } from '../gridForm/GridFormPopoverMenu';
4
3
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
5
4
  import { GridRenderPopoutMenuCell } from '../gridRender/GridRenderPopoutMenuCell';
5
+ import { ColDefT, GridBaseRow } from '../types';
6
6
 
7
7
  /**
8
8
  * Popout burger menu
@@ -1,7 +1,7 @@
1
- import { GridBaseRow } from '../Grid';
2
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
1
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
3
2
  import { GridFormMessage, GridFormMessageProps } from '../gridForm/GridFormMessage';
4
3
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
4
+ import { ColDefT, GridBaseRow } from '../types';
5
5
 
6
6
  export const GridPopoverMessage = <TData extends GridBaseRow, TValue = any>(
7
7
  colDef: GenericCellColDef<TData, TValue>,
@@ -1,7 +1,7 @@
1
- import { GridBaseRow } from '../Grid';
2
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
1
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
3
2
  import { GridFormTextArea, GridFormTextAreaProps } from '../gridForm/GridFormTextArea';
4
3
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
4
+ import { ColDefT, GridBaseRow } from '../types';
5
5
 
6
6
  export const GridPopoverTextArea = <TData extends GridBaseRow, TValue = any>(
7
7
  colDef: GenericCellColDef<TData, TValue>,
@@ -1,7 +1,7 @@
1
- import { GridBaseRow } from '../Grid';
2
- import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
1
+ import { GenericCellEditorProps, GridCell } from '../GridCell';
3
2
  import { GridFormTextInput, GridFormTextInputProps } from '../gridForm/GridFormTextInput';
4
3
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
4
+ import { ColDefT, GridBaseRow } from '../types';
5
5
 
6
6
  export const GridPopoverTextInput = <TData extends GridBaseRow, TValue = any>(
7
7
  colDef: GenericCellColDef<TData, TValue>,
@@ -2,8 +2,7 @@ import { ICellRendererParams } from 'ag-grid-community';
2
2
  import { SuppressKeyboardEventParams } from 'ag-grid-community';
3
3
  import { ReactElement } from 'react';
4
4
 
5
- import { GridBaseRow } from '../Grid';
6
- import { ColDefT } from '../GridCell';
5
+ import { ColDefT, GridBaseRow } from '../types';
7
6
 
8
7
  export interface GenericCellColDef<TData extends GridBaseRow, TValue = any> extends ColDefT<TData, TValue> {
9
8
  exportable?: boolean;