@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.
- package/dist/index.css +26 -2
- package/dist/src/components/Grid.d.ts +37 -67
- package/dist/src/components/GridCell.d.ts +2 -19
- package/dist/src/components/GridCellFiller.d.ts +1 -1
- package/dist/src/components/GridCellMultiEditor.d.ts +1 -2
- package/dist/src/components/GridPopoverHook.d.ts +1 -1
- package/dist/src/components/GridRangeSelectContextMenu.d.ts +10 -0
- package/dist/src/components/gridFilter/GridFilterButtons.d.ts +1 -1
- package/dist/src/components/gridFilter/useGridFilter.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormDropDown.d.ts +2 -2
- package/dist/src/components/gridForm/GridFormEditBearing.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormMessage.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormMultiSelectGrid.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +3 -2
- package/dist/src/components/gridForm/GridFormSubComponentTextArea.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormSubComponentTextInput.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormTextArea.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormTextInput.d.ts +1 -1
- package/dist/src/components/gridHook/useGridContextMenu.d.ts +13 -8
- package/dist/src/components/gridHook/useGridCopy.d.ts +15 -0
- package/dist/src/components/gridHook/useGridCopySettings.d.ts +11 -0
- package/dist/src/components/gridHook/useGridRangeSelection.d.ts +25 -0
- package/dist/src/components/gridPopoverEdit/GridButton.d.ts +1 -2
- package/dist/src/components/gridPopoverEdit/GridEditBoolean.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoutEditMultiSelectGrid.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoverEditBearing.d.ts +4 -2
- package/dist/src/components/gridPopoverEdit/GridPopoverEditDropDown.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoverMenu.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoverMessage.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoverTextArea.d.ts +2 -2
- package/dist/src/components/gridPopoverEdit/GridPopoverTextInput.d.ts +2 -2
- package/dist/src/components/gridRender/GridRenderGenericCell.d.ts +1 -2
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/types.d.ts +27 -0
- package/dist/src/contexts/GridContext.d.ts +13 -12
- package/dist/src/contexts/GridPopoverContext.d.ts +1 -1
- package/dist/src/lui/timeoutHook.d.ts +0 -6
- package/dist/src/lui/tsUtils.d.ts +5 -0
- package/dist/src/utils/__tests__/random.ts +19 -0
- package/dist/src/utils/textValidator.d.ts +1 -1
- package/dist/src/utils/util.d.ts +1 -0
- package/dist/step-ag-grid.cjs +650 -242
- package/dist/step-ag-grid.cjs.map +1 -1
- package/dist/step-ag-grid.esm.js +651 -245
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +4 -2
- package/src/components/Grid.tsx +312 -221
- package/src/components/GridCell.tsx +1 -25
- package/src/components/GridCellFiller.tsx +1 -1
- package/src/components/GridCellMultiEditor.tsx +2 -2
- package/src/components/GridPopoverHook.tsx +1 -1
- package/src/components/GridRangeSelectContextMenu.tsx +73 -0
- package/src/components/gridFilter/GridFilterButtons.tsx +1 -1
- package/src/components/gridFilter/useGridFilter.ts +1 -1
- package/src/components/gridForm/GridFormDropDown.tsx +2 -4
- package/src/components/gridForm/GridFormEditBearing.tsx +1 -1
- package/src/components/gridForm/GridFormMessage.tsx +1 -1
- package/src/components/gridForm/GridFormMultiSelect.tsx +1 -1
- package/src/components/gridForm/GridFormMultiSelectGrid.tsx +1 -1
- package/src/components/gridForm/GridFormPopoverMenu.tsx +3 -2
- package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -1
- package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -1
- package/src/components/gridForm/GridFormTextArea.tsx +1 -1
- package/src/components/gridForm/GridFormTextInput.tsx +1 -1
- package/src/components/gridHook/useGridContextMenu.tsx +23 -10
- package/src/components/gridHook/useGridCopy.ts +279 -0
- package/src/components/gridHook/useGridCopySettings.ts +28 -0
- package/src/components/gridHook/useGridRangeSelection.ts +235 -0
- package/src/components/gridPopoverEdit/GridButton.tsx +2 -2
- package/src/components/gridPopoverEdit/GridEditBoolean.tsx +2 -2
- package/src/components/gridPopoverEdit/GridPopoutEditMultiSelect.ts +2 -2
- package/src/components/gridPopoverEdit/GridPopoutEditMultiSelectGrid.ts +2 -2
- package/src/components/gridPopoverEdit/GridPopoverEditBearing.ts +5 -2
- package/src/components/gridPopoverEdit/GridPopoverEditDropDown.ts +2 -2
- package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +2 -2
- package/src/components/gridPopoverEdit/GridPopoverMessage.ts +2 -2
- package/src/components/gridPopoverEdit/GridPopoverTextArea.ts +2 -2
- package/src/components/gridPopoverEdit/GridPopoverTextInput.ts +2 -2
- package/src/components/gridRender/GridRenderGenericCell.tsx +1 -2
- package/src/components/index.ts +1 -0
- package/src/components/types.ts +34 -0
- package/src/contexts/GridContext.tsx +57 -148
- package/src/contexts/GridContextProvider.tsx +16 -13
- package/src/contexts/GridPopoverContext.tsx +1 -1
- package/src/contexts/GridPopoverContextProvider.tsx +1 -1
- package/src/lui/timeoutHook.tsx +0 -19
- package/src/lui/tsUtils.ts +8 -0
- package/src/react-menu3/components/MenuButton.tsx +2 -2
- package/src/react-menu3/hooks/useItemState.ts +1 -1
- package/src/stories/grid/GridCopy.stories.tsx +175 -0
- package/src/stories/grid/GridPopoverEditBearing.stories.tsx +4 -4
- package/src/stories/grid/GridPopoverEditDropDown.stories.tsx +3 -3
- package/src/stories/grid/GridReadOnly.stories.tsx +4 -3
- package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +2 -2
- package/src/styles/ContextMenu.scss +61 -0
- package/src/styles/Grid.scss +26 -2
- package/src/utils/__tests__/random.ts +19 -0
- package/src/utils/bearing.ts +2 -2
- package/src/utils/textValidator.test.ts +1 -1
- package/src/utils/textValidator.ts +1 -1
- 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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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 '../
|
|
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;
|