@linzjs/step-ag-grid 7.11.13 → 7.13.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/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": "7.11.13",
5
+ "version": "7.13.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -35,6 +35,7 @@ export interface GridProps {
35
35
  rowClassRules?: GridOptions["rowClassRules"];
36
36
  rowSelection?: "single" | "multiple";
37
37
  autoSelectFirstRow?: boolean;
38
+ onColumnMoved?: GridOptions["onColumnMoved"];
38
39
  }
39
40
 
40
41
  /**
@@ -47,8 +48,10 @@ export const Grid = (params: GridProps): JSX.Element => {
47
48
  setQuickFilter,
48
49
  ensureRowVisible,
49
50
  selectRowsById,
51
+ focusByRowById,
50
52
  ensureSelectedRowIsVisible,
51
53
  sizeColumnsToFit,
54
+ externallySelectedItemsAreInSync,
52
55
  setExternallySelectedItemsAreInSync,
53
56
  } = useContext(GridContext);
54
57
  const { checkUpdating } = useContext(GridUpdatingContext);
@@ -63,12 +66,25 @@ export const Grid = (params: GridProps): JSX.Element => {
63
66
  */
64
67
  const hasSelectedFirstItem = useRef(false);
65
68
  useEffect(() => {
66
- if (!gridReady || !params.autoSelectFirstRow || hasSelectedFirstItem.current || !params.rowData) return;
69
+ if (!gridReady || hasSelectedFirstItem.current || !params.rowData || !externallySelectedItemsAreInSync) return;
67
70
  hasSelectedFirstItem.current = true;
68
71
  if (isNotEmpty(params.rowData) && isEmpty(params.externalSelectedItems)) {
69
- selectRowsById([params.rowData[0].id]);
72
+ const firstRowId = params.rowData[0].id;
73
+ if (params.autoSelectFirstRow) {
74
+ selectRowsById([firstRowId]);
75
+ } else {
76
+ focusByRowById(firstRowId);
77
+ }
70
78
  }
71
- }, [gridReady, params, params.autoSelectFirstRow, params.rowData, selectRowsById]);
79
+ }, [
80
+ externallySelectedItemsAreInSync,
81
+ focusByRowById,
82
+ gridReady,
83
+ params,
84
+ params.autoSelectFirstRow,
85
+ params.rowData,
86
+ selectRowsById,
87
+ ]);
72
88
 
73
89
  /**
74
90
  * AgGrid checkbox select does not pass clicks within cell but not on the checkbox to checkbox.
@@ -175,7 +191,7 @@ export const Grid = (params: GridProps): JSX.Element => {
175
191
  checkboxSelection: true,
176
192
  headerComponent: GridHeaderSelect,
177
193
  suppressHeaderKeyboardEvent: (e) => {
178
- if (e.event.key === "Enter" && !e.event.repeat) {
194
+ if ((e.event.key === "Enter" || e.event.key === " ") && !e.event.repeat) {
179
195
  const selectedNodeCount = e.api.getSelectedRows().length;
180
196
  if (selectedNodeCount == 0) {
181
197
  e.api.selectAllFiltered();
@@ -339,6 +355,7 @@ export const Grid = (params: GridProps): JSX.Element => {
339
355
  onSortChanged={ensureSelectedRowIsVisible}
340
356
  postSortRows={params.postSortRows ?? postSortRows}
341
357
  onSelectionChanged={synchroniseExternalStateToGridSelection}
358
+ onColumnMoved={params.onColumnMoved}
342
359
  />
343
360
  </div>
344
361
  );
@@ -6,7 +6,7 @@ import { GenericCellColDef, GenericCellRendererParams } from "./gridRender/GridR
6
6
  import { ColDef, ICellEditorParams, ICellRendererParams } from "ag-grid-community";
7
7
  import { GridLoadableCell } from "./GridLoadableCell";
8
8
  import { GridIcon } from "./GridIcon";
9
- import { ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
9
+ import { SuppressKeyboardEventParams, ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
10
10
  import { GridPopoverContextProvider } from "../contexts/GridPopoverContextProvider";
11
11
  import { fnOrVar } from "../utils/util";
12
12
 
@@ -52,6 +52,20 @@ export interface ColDefT<RowType extends GridBaseRow> extends ColDef {
52
52
  editor?: (editorProps: any) => JSX.Element;
53
53
  }
54
54
 
55
+ export const suppressCellKeyboardEvents = (e: SuppressKeyboardEventParams) => {
56
+ const shortcutKeys = e.colDef.cellRendererParams?.shortcutKeys ?? {};
57
+ const exec = shortcutKeys[e.event.key];
58
+ if (!e.editing && !e.event.repeat && e.event.type === "keypress" && exec) {
59
+ const editable = fnOrVar(e.colDef?.editable, e);
60
+ return editable ? exec(e) ?? true : true;
61
+ }
62
+ // It's important that aggrid doesn't trigger edit on enter
63
+ // as the incorrect selected rows will be returned
64
+ return !["ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp", "Tab", " ", "Home", "End", "PageUp", "PageDown"].includes(
65
+ e.event.key,
66
+ );
67
+ };
68
+
55
69
  /*
56
70
  * All cells should use this
57
71
  */
@@ -68,21 +82,11 @@ export const GridCell = <RowType extends GridBaseRow, Props extends CellEditorCo
68
82
  sortable: !!(props?.field || props?.valueGetter),
69
83
  resizable: true,
70
84
  ...(custom?.editor && {
71
- cellClassRules: custom.multiEdit ? GridCellMultiSelectClassRules : undefined,
85
+ cellClassRules: GridCellMultiSelectClassRules,
72
86
  editable: props.editable ?? true,
73
- cellEditor: GenericCellEditorComponentWrapper(custom),
87
+ cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
74
88
  }),
75
- suppressKeyboardEvent: (e) => {
76
- const shortcutKeys = e.colDef.cellRendererParams?.shortcutKeys ?? {};
77
- const exec = shortcutKeys[e.event.key];
78
- if (!e.editing && !e.event.repeat && e.event.type === "keypress" && exec) {
79
- const editable = fnOrVar(e.colDef?.editable, e);
80
- return editable ? exec(e) ?? true : true;
81
- }
82
- // It's important that aggrid doesn't trigger edit on enter
83
- // as the incorrect selected rows will be returned
84
- return !["ArrowLeft", "ArrowRight", "ArrowDown", "ArrowUp", "Tab", " "].includes(e.event.key);
85
- },
89
+ suppressKeyboardEvent: suppressCellKeyboardEvents,
86
90
  ...(custom?.editorParams && {
87
91
  cellEditorParams: { ...custom.editorParams, multiEdit: custom.multiEdit },
88
92
  }),
@@ -106,7 +110,8 @@ export interface CellEditorCommon {
106
110
  className?: string | undefined;
107
111
  }
108
112
 
109
- export const GenericCellEditorComponentWrapper = (custom?: { editor?: (props: any) => JSX.Element }) => {
113
+ export const GenericCellEditorComponentWrapper = (editor?: (props: any) => JSX.Element) => {
114
+ const obj = { editor };
110
115
  return forwardRef(function GenericCellEditorComponentFr(cellEditorParams: ICellEditorParams, _) {
111
116
  const valueFormatted = cellEditorParams.formatValue
112
117
  ? cellEditorParams.formatValue(cellEditorParams.value)
@@ -120,7 +125,7 @@ export const GenericCellEditorComponentWrapper = (custom?: { editor?: (props: an
120
125
  {...cellEditorParams.colDef.cellRendererParams}
121
126
  />
122
127
  }
123
- {custom?.editor && <custom.editor {...cellEditorParams} />}
128
+ {obj.editor && <obj.editor {...cellEditorParams} />}
124
129
  </GridPopoverContextProvider>
125
130
  );
126
131
  });
@@ -0,0 +1,48 @@
1
+ import { GridBaseRow } from "./Grid";
2
+ import { GridCellMultiSelectClassRules } from "./GridCellMultiSelectClassRules";
3
+ import { GenericCellColDef } from "./gridRender/GridRenderGenericCell";
4
+ import { CellEditorSelectorResult, ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
5
+ import { ColDefT, GenericCellEditorComponentWrapper, GridCellRenderer, suppressCellKeyboardEvents } from "./GridCell";
6
+ import { ComponentProps } from "react";
7
+ import { ICellEditorParams } from "ag-grid-community/dist/lib/interfaces/iCellEditor";
8
+
9
+ export const Editor = <FN extends (param: any) => JSX.Element>(props: {
10
+ multiEdit: boolean;
11
+ editor: FN;
12
+ editorParams: ComponentProps<FN>;
13
+ }): CellEditorSelectorResult => ({
14
+ component: GenericCellEditorComponentWrapper(props.editor),
15
+ params: { ...props.editorParams, multiEdit: props.multiEdit },
16
+ });
17
+
18
+ /*
19
+ * All cells should use this
20
+ */
21
+ export const GridCellMultiEditor = <RowType extends GridBaseRow>(
22
+ props: GenericCellColDef<RowType>,
23
+ cellEditorSelector: (params: ICellEditorParams) => CellEditorSelectorResult,
24
+ ): ColDefT<RowType> => {
25
+ return {
26
+ colId: props.colId ?? props.field,
27
+ field: props.field,
28
+ sortable: !!(props?.field || props?.valueGetter),
29
+ resizable: true,
30
+ editable: props.editable ?? true,
31
+ cellClassRules: GridCellMultiSelectClassRules,
32
+ cellEditorSelector,
33
+ suppressKeyboardEvent: suppressCellKeyboardEvents,
34
+ // Default value formatter, otherwise react freaks out on objects
35
+ valueFormatter: (params: ValueFormatterParams) => {
36
+ if (params.value == null) return "–";
37
+ const types = ["number", "boolean", "string"];
38
+ if (types.includes(typeof params.value)) return `${params.value}`;
39
+ else return JSON.stringify(params.value);
40
+ },
41
+ ...props,
42
+ cellRenderer: GridCellRenderer,
43
+ cellRendererParams: {
44
+ originalCellRenderer: props.cellRenderer,
45
+ ...props.cellRendererParams,
46
+ },
47
+ };
48
+ };
@@ -1,9 +1,20 @@
1
1
  import { CellClassParams, CellClassRules } from "ag-grid-community/dist/lib/entities/colDef";
2
+ import { ICellEditorParams } from "ag-grid-community/dist/lib/interfaces/iCellEditor";
2
3
 
3
4
  export const GridCellMultiSelectClassRules: CellClassRules = {
4
- "ag-selected-for-edit": ({ api, node, colDef }: CellClassParams) =>
5
- api
6
- .getSelectedNodes()
7
- .map((row) => row.id)
8
- .includes(node.id) && api.getEditingCells().some((cell) => cell.column.getColDef() === colDef),
5
+ "ag-selected-for-edit": (params: CellClassParams) => {
6
+ const { api, node, colDef } = params;
7
+ const cep = colDef.cellEditorSelector
8
+ ? colDef.cellEditorSelector(params as ICellEditorParams)?.params
9
+ : colDef.cellEditorParams;
10
+
11
+ return (
12
+ cep?.multiEdit &&
13
+ api
14
+ .getSelectedNodes()
15
+ .map((row) => row.id)
16
+ .includes(node.id) &&
17
+ api.getEditingCells().some((cell) => cell.column.getColDef() === colDef)
18
+ );
19
+ },
9
20
  };
@@ -15,7 +15,7 @@ export interface GridFormTextAreaProps<RowType extends GridBaseRow>
15
15
  helpText?: string;
16
16
  }
17
17
 
18
- export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTextAreaProps<RowType>) => {
18
+ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTextAreaProps<RowType>): JSX.Element => {
19
19
  const { field, value: initialVale, data } = useGridPopoverContext<RowType>();
20
20
 
21
21
  const initValue = useMemo(() => (initialVale == null ? "" : `${initialVale}`), [initialVale]);
@@ -15,6 +15,7 @@ export interface GridContextType {
15
15
  selectRowsByIdWithFlash: (rowIds?: number[]) => void;
16
16
  flashRows: (rowIds?: number[]) => void;
17
17
  flashRowsDiff: (updateFn: () => Promise<any>) => Promise<void>;
18
+ focusByRowById: (rowId: number) => void;
18
19
  ensureRowVisible: (id: number | string) => boolean;
19
20
  ensureSelectedRowIsVisible: () => void;
20
21
  sizeColumnsToFit: () => void;
@@ -26,12 +27,14 @@ export interface GridContextType {
26
27
  tabDirection?: 1 | 0 | -1,
27
28
  ) => Promise<boolean>;
28
29
  redrawRows: (rowNodes?: RowNode[]) => void;
30
+ externallySelectedItemsAreInSync: boolean;
29
31
  setExternallySelectedItemsAreInSync: (inSync: boolean) => void;
30
32
  waitForExternallySelectedItemsToBeInSync: () => Promise<void>;
31
33
  }
32
34
 
33
35
  export const GridContext = createContext<GridContextType>({
34
36
  gridReady: false,
37
+ externallySelectedItemsAreInSync: false,
35
38
  setGridApi: () => {
36
39
  console.error("no context provider for setGridApi");
37
40
  },
@@ -64,6 +67,9 @@ export const GridContext = createContext<GridContextType>({
64
67
  flashRowsDiff: async () => {
65
68
  console.error("no context provider for flashRows");
66
69
  },
70
+ focusByRowById: async () => {
71
+ console.error("no context provider for focusByRowById");
72
+ },
67
73
  ensureRowVisible: () => {
68
74
  console.error("no context provider for ensureRowVisible");
69
75
  return true;
@@ -20,7 +20,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
20
20
  const [gridApi, _setGridApi] = useState<GridApi>();
21
21
  const [gridReady, setGridReady] = useState(false);
22
22
  const idsBeforeUpdate = useRef<number[]>([]);
23
- const externallySelectedItemsAreInSync = useRef(false);
23
+ const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
24
24
 
25
25
  const setGridApi = useCallback((gridApi: GridApi | undefined) => {
26
26
  _setGridApi(gridApi);
@@ -181,10 +181,12 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
181
181
  (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, false),
182
182
  [_selectRowsWithOptionalFlash],
183
183
  );
184
+
184
185
  const selectRowsByIdWithFlash = useCallback(
185
186
  (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, true, true),
186
187
  [_selectRowsWithOptionalFlash],
187
188
  );
189
+
188
190
  const flashRows = useCallback(
189
191
  (rowIds?: number[]) => _selectRowsWithOptionalFlash(rowIds, false, true),
190
192
  [_selectRowsWithOptionalFlash],
@@ -217,6 +219,11 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
217
219
  [_selectRowsWithOptionalFlash, beforeUpdate],
218
220
  );
219
221
 
222
+ const focusByRowById = useCallback(
223
+ (rowId: number) => _selectRowsWithOptionalFlash([rowId], false, false),
224
+ [_selectRowsWithOptionalFlash],
225
+ );
226
+
220
227
  const getSelectedRows = useCallback(<T extends unknown>(): T[] => {
221
228
  return gridApiOp(
222
229
  (gridApi) => gridApi.getSelectedRows(),
@@ -350,16 +357,12 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
350
357
  [gridApiOp],
351
358
  );
352
359
 
353
- const setExternallySelectedItemsAreInSync = useCallback((inSync: boolean) => {
354
- externallySelectedItemsAreInSync.current = inSync;
355
- }, []);
356
-
357
360
  const waitForExternallySelectedItemsToBeInSync = useCallback(async () => {
358
361
  // Wait for up to 5 seconds
359
- for (let i = 0; i < 5000 / 200 && !externallySelectedItemsAreInSync.current; i++) {
362
+ for (let i = 0; i < 5000 / 200 && !externallySelectedItemsAreInSync; i++) {
360
363
  await wait(200);
361
364
  }
362
- }, []);
365
+ }, [externallySelectedItemsAreInSync]);
363
366
 
364
367
  return (
365
368
  <GridContext.Provider
@@ -373,6 +376,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
373
376
  selectRowsWithFlashDiff,
374
377
  flashRows,
375
378
  flashRowsDiff,
379
+ focusByRowById,
376
380
  getSelectedRows,
377
381
  getSelectedRowIds,
378
382
  editingCells,
@@ -382,6 +386,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
382
386
  stopEditing,
383
387
  updatingCells,
384
388
  redrawRows,
389
+ externallySelectedItemsAreInSync,
385
390
  setExternallySelectedItemsAreInSync,
386
391
  waitForExternallySelectedItemsToBeInSync,
387
392
  }}
@@ -16,6 +16,10 @@ import { GridPopoverTextInput } from "../../components/gridPopoverEdit/GridPopov
16
16
  import { ActionButton } from "../../lui/ActionButton";
17
17
  import { GridPopoverMenu } from "../../components/gridPopoverEdit/GridPopoverMenu";
18
18
  import { GridContext } from "../../contexts/GridContext";
19
+ import { Editor, GridCellMultiEditor } from "../../components/GridCellMultiEditor";
20
+ import { GridFormTextArea } from "../../components/gridForm/GridFormTextArea";
21
+ import { ICellEditorParams } from "ag-grid-community/dist/lib/interfaces/iCellEditor";
22
+ import { GridFormDropDown } from "../../components/gridForm/GridFormDropDown";
19
23
 
20
24
  export default {
21
25
  title: "Components / Grids",
@@ -88,7 +92,7 @@ const GridPopoutEditGenericTemplate: ComponentStory<typeof Grid> = (props: GridP
88
92
  },
89
93
  },
90
94
  {
91
- multiEdit: true,
95
+ multiEdit: false,
92
96
  editorParams: {
93
97
  maxLength: 12,
94
98
  placeholder: "Enter distance...",
@@ -119,7 +123,6 @@ const GridPopoutEditGenericTemplate: ComponentStory<typeof Grid> = (props: GridP
119
123
  required: true,
120
124
  maxLength: 32,
121
125
  placeholder: "Enter some text...",
122
-
123
126
  invalid: (value: string) => {
124
127
  if (value === "never") return "The value 'never' is not allowed";
125
128
  return null;
@@ -132,6 +135,32 @@ const GridPopoutEditGenericTemplate: ComponentStory<typeof Grid> = (props: GridP
132
135
  },
133
136
  },
134
137
  ),
138
+ GridCellMultiEditor(
139
+ {
140
+ colId: "plan2",
141
+ field: "plan",
142
+ headerName: "Multi-editor",
143
+ maxWidth: 140,
144
+ },
145
+ (_params: ICellEditorParams) =>
146
+ _params.rowIndex == 0
147
+ ? Editor({
148
+ multiEdit: true,
149
+ editor: GridFormTextArea,
150
+ editorParams: {
151
+ required: true,
152
+ maxLength: 32,
153
+ placeholder: "Enter some text...",
154
+ },
155
+ })
156
+ : Editor({
157
+ multiEdit: false,
158
+ editor: GridFormDropDown,
159
+ editorParams: {
160
+ options: [{ label: "One", value: 1 }],
161
+ },
162
+ }),
163
+ ),
135
164
  GridPopoverMenu(
136
165
  {
137
166
  headerName: "Delete menu",
@@ -160,7 +189,17 @@ const GridPopoutEditGenericTemplate: ComponentStory<typeof Grid> = (props: GridP
160
189
 
161
190
  const lastRow = rowData[rowData.length - 1];
162
191
  await selectRowsWithFlashDiff(async () => {
163
- setRowData([...rowData, { id: lastRow.id + 1, name: "?", nameType: "?", numba: "?", plan: "", distance: null }]);
192
+ setRowData([
193
+ ...rowData,
194
+ {
195
+ id: lastRow.id + 1,
196
+ name: "?",
197
+ nameType: "?",
198
+ numba: "?",
199
+ plan: "",
200
+ distance: null,
201
+ },
202
+ ]);
164
203
  });
165
204
  }, [rowData, selectRowsWithFlashDiff]);
166
205
 
@@ -74,10 +74,6 @@
74
74
  align-items: center;
75
75
  }
76
76
 
77
- .ag-cell.ag-cell-inline-editing:not(.ag-cell-auto-height) {
78
- bottom: 0;
79
- }
80
-
81
77
  .ag-cell-wrap-text {
82
78
  word-break: break-word;
83
79
  }