@linzjs/step-ag-grid 29.1.2 → 29.1.4

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 (29) hide show
  1. package/dist/src/components/Grid.d.ts +1 -1
  2. package/dist/src/components/GridPopoverHook.d.ts +2 -0
  3. package/dist/src/contexts/GridContext.d.ts +2 -2
  4. package/dist/src/utils/waitForCondition.d.ts +1 -1
  5. package/dist/step-ag-grid.cjs +147 -114
  6. package/dist/step-ag-grid.cjs.map +1 -1
  7. package/dist/step-ag-grid.esm.js +147 -115
  8. package/dist/step-ag-grid.esm.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/components/Grid.tsx +1 -1
  11. package/src/components/GridCell.tsx +34 -14
  12. package/src/components/GridPopoverHook.tsx +10 -15
  13. package/src/components/gridForm/GridFormDropDown.tsx +73 -49
  14. package/src/components/gridForm/GridFormMultiSelect.tsx +12 -4
  15. package/src/components/gridPopoverEdit/GridPopoverMessage.ts +1 -0
  16. package/src/contexts/GridContext.tsx +2 -2
  17. package/src/contexts/GridContextProvider.tsx +31 -23
  18. package/src/stories/grid/GridPopoutEditGeneric.stories.tsx +1 -1
  19. package/src/stories/grid/GridPopoverEditMultiSelect.stories.tsx +1 -1
  20. package/src/stories/grid/GridPopoverEditMultiSelectGrid.stories.tsx +1 -1
  21. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +1 -1
  22. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +2 -1
  23. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +1 -1
  24. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +2 -1
  25. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +2 -2
  26. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +1 -1
  27. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +1 -1
  28. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +56 -13
  29. package/src/utils/waitForCondition.tsx +6 -2
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.1.2",
5
+ "version": "29.1.4",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -101,7 +101,7 @@ export interface GridProps<TData extends GridBaseRow = GridBaseRow> {
101
101
  * When pressing tab whilst editing the grid will select and edit the next cell if available.
102
102
  * Once the last cell to edit closes this callback is called.
103
103
  */
104
- onBulkEditingComplete?: () => void;
104
+ onBulkEditingComplete?: () => Promise<void> | void;
105
105
 
106
106
  /**
107
107
  * Context menu definition if required.
@@ -11,7 +11,8 @@ import {
11
11
  ValueFormatterParams,
12
12
  ValueGetterFunc,
13
13
  } from 'ag-grid-community';
14
- import { forwardRef, ReactElement, useContext } from 'react';
14
+ import { defer } from 'lodash-es';
15
+ import { forwardRef, ReactElement, useContext, useEffect } from 'react';
15
16
 
16
17
  import { GridPopoverContextProvider } from '../contexts/GridPopoverContextProvider';
17
18
  import { GridUpdatingContext } from '../contexts/GridUpdatingContext';
@@ -153,20 +154,25 @@ export const GridCell = <TData extends GridBaseRow, TValue = any, Props extends
153
154
  sortable: true,
154
155
  resizable: true,
155
156
  valueSetter: custom?.editor ? blockValueSetter : undefined,
156
- editable: props.editable ?? false,
157
- ...(custom?.editor && {
158
- cellClassRules: GridCellMultiSelectClassRules,
159
- editable: props.editable ?? true,
160
- cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
161
- }),
157
+ editable: props.editable ?? !!custom?.editor,
158
+ ...(custom?.editor
159
+ ? {
160
+ cellClassRules: GridCellMultiSelectClassRules,
161
+ cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
162
+ }
163
+ : {
164
+ cellEditor: CellEditorToBlockEditing,
165
+ }),
162
166
  suppressKeyboardEvent: suppressCellKeyboardEvents,
163
- ...(custom?.editorParams && {
164
- cellEditorParams: {
165
- ...custom.editorParams,
166
- multiEdit: custom.multiEdit,
167
- preventAutoEdit: custom.preventAutoEdit ?? false,
168
- },
169
- }),
167
+ ...(custom?.editorParams
168
+ ? {
169
+ cellEditorParams: {
170
+ ...custom.editorParams,
171
+ multiEdit: custom.multiEdit,
172
+ preventAutoEdit: custom.preventAutoEdit ?? !custom?.editor,
173
+ },
174
+ }
175
+ : { cellEditorParams: { preventAutoEdit: !custom?.editor } }),
170
176
  // If there's a valueFormatter and no filterValueGetter then create a filterValueGetter
171
177
  getQuickFilterText: filterValueGetter as any,
172
178
  // Default value formatter, otherwise react freaks out on objects
@@ -184,6 +190,20 @@ export const GridCell = <TData extends GridBaseRow, TValue = any, Props extends
184
190
  };
185
191
  };
186
192
 
193
+ /**
194
+ * Ag-grid will start its own editor if editable is true and there is no cell editor
195
+ * like in the case of a cell that is editable because it triggers a modal.
196
+ * This will block that editor.
197
+ */
198
+ const CellEditorToBlockEditing = ({ stopEditing }: { stopEditing: () => void }) => {
199
+ useEffect(() => {
200
+ defer(() => {
201
+ stopEditing();
202
+ });
203
+ }, [stopEditing]);
204
+ return <></>;
205
+ };
206
+
187
207
  export interface CellEditorCommon {
188
208
  className?: string | undefined;
189
209
  }
@@ -1,6 +1,5 @@
1
1
  import { ReactElement, useCallback, useEffect, useRef, useState } from 'react';
2
2
 
3
- import { useGridContext } from '../contexts/GridContext';
4
3
  import { useGridPopoverContext } from '../contexts/GridPopoverContext';
5
4
  import { ControlledMenu } from '../react-menu3';
6
5
  import { MenuCloseEvent } from '../react-menu3/types';
@@ -20,13 +19,14 @@ export interface GridPopoverHookProps<TData> {
20
19
  dontSaveOnExternalClick?: boolean;
21
20
  }
22
21
 
22
+ export const CancelPromise = () => Promise.resolve(true);
23
+
23
24
  export const useGridPopoverHook = <TData extends GridBaseRow>({
24
25
  className,
25
26
  save,
26
27
  invalid,
27
28
  dontSaveOnExternalClick,
28
29
  }: GridPopoverHookProps<TData>) => {
29
- const { onBulkEditingComplete, redrawRows } = useGridContext<TData>();
30
30
  const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext<TData>();
31
31
  const saveButtonRef = useRef<HTMLButtonElement>(null);
32
32
  const [isOpen, setOpen] = useState(false);
@@ -38,9 +38,9 @@ export const useGridPopoverHook = <TData extends GridBaseRow>({
38
38
  const triggerSave = useCallback(
39
39
  async (reason?: string) => {
40
40
  if (reason == CloseReason.CANCEL) {
41
+ await updateValue(CancelPromise, 0);
41
42
  stopEditing();
42
- onBulkEditingComplete();
43
- redrawRows();
43
+
44
44
  return;
45
45
  }
46
46
  if (invalid?.()) {
@@ -48,22 +48,16 @@ export const useGridPopoverHook = <TData extends GridBaseRow>({
48
48
  return;
49
49
  }
50
50
 
51
- if (!save) {
52
- // No save method so just close
53
- stopEditing();
54
- onBulkEditingComplete();
55
- redrawRows();
56
- return;
57
- }
58
-
59
51
  if (
60
- await updateValue(save, reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)
52
+ await updateValue(
53
+ save ?? (() => Promise.resolve(true)),
54
+ reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0,
55
+ )
61
56
  ) {
62
57
  stopEditing();
63
- redrawRows();
64
58
  }
65
59
  },
66
- [invalid, onBulkEditingComplete, redrawRows, save, stopEditing, updateValue],
60
+ [invalid, save, stopEditing, updateValue],
67
61
  );
68
62
 
69
63
  const popoverWrapper = useCallback(
@@ -116,5 +110,6 @@ export const useGridPopoverHook = <TData extends GridBaseRow>({
116
110
  return {
117
111
  popoverWrapper,
118
112
  triggerSave,
113
+ gridPopoverOpen: isOpen,
119
114
  };
120
115
  };
@@ -5,6 +5,7 @@ import { Fragment, ReactElement, useCallback, useEffect, useMemo, useRef, useSta
5
5
  import { useGridPopoverContext } from '../../contexts/GridPopoverContext';
6
6
  import { GridSubComponentContext } from '../../contexts/GridSubComponentContext';
7
7
  import { FormError } from '../../lui/FormError';
8
+ import { usePrevious } from '../../lui/reactUtils';
8
9
  import { FocusableItem, MenuDivider, MenuHeader, MenuItem } from '../../react-menu3';
9
10
  import { ClickEvent } from '../../react-menu3/types';
10
11
  import { textMatch } from '../../utils/textMatcher';
@@ -32,7 +33,7 @@ interface FinalSelectOption<TOptionValue> {
32
33
  export const primitiveToSelectOption = <T,>(value: T): SelectOption<T> => {
33
34
  return {
34
35
  value: value,
35
- label: value ? String(value) : '',
36
+ label: value ? String(value) : '-',
36
37
  };
37
38
  };
38
39
 
@@ -84,6 +85,7 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
84
85
  props: GridFormDropDownProps<TData, TOptionValue>,
85
86
  ) => {
86
87
  const { selectedRows, field, data } = useGridPopoverContext<TData>();
88
+ const { onSelectFilter, options: propOptions, onSelectedItem } = props;
87
89
 
88
90
  // Save triggers during async action processing which triggers another selectItem(), this ref blocks that
89
91
  const [filter, setFilter] = useState(props.filterDefaultValue ?? '');
@@ -101,8 +103,8 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
101
103
  selectedRows.some((row) => row[field] !== value) ||
102
104
  (subComponentValue !== undefined && subComponentInitialValue.current !== JSON.stringify(subComponentValue));
103
105
  if (hasChanged) {
104
- if (props.onSelectedItem) {
105
- await props.onSelectedItem({
106
+ if (onSelectedItem) {
107
+ await onSelectedItem({
106
108
  selectedRows,
107
109
  selectedRowIds: selectedRows.map((row) => row.id),
108
110
  value,
@@ -114,23 +116,78 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
114
116
  }
115
117
  return true;
116
118
  },
117
- [field, props, selectedRows],
119
+ [field, onSelectedItem, selectedRows],
118
120
  );
119
121
 
122
+ /**
123
+ * Saves are wrapped in updateValue and triggered by blur events
124
+ */
125
+ const save = useCallback(async () => {
126
+ if (!options) {
127
+ return true;
128
+ }
129
+
130
+ // Filter saved
131
+ if (selectedItem === null) {
132
+ if (onSelectFilter) {
133
+ if (!isEmpty(filter)) {
134
+ await onSelectFilter({
135
+ selectedRows,
136
+ selectedRowIds: selectedRows.map((row) => row.id),
137
+ value: filter as any,
138
+ });
139
+ }
140
+
141
+ return true;
142
+ } else {
143
+ if (filteredValues && filteredValues.length === 1) {
144
+ if (filteredValues[0].subComponent) return false;
145
+ return await selectItemHandler(filteredValues[0].value, null);
146
+ }
147
+ }
148
+ return false;
149
+ }
150
+ if (selectedItem.subComponent && !subComponentIsValid.current) {
151
+ return false;
152
+ }
153
+ await selectItemHandler(selectedItem.value, subSelectedValue);
154
+
155
+ return true;
156
+ }, [
157
+ filter,
158
+ filteredValues,
159
+ onSelectFilter,
160
+ options,
161
+ selectItemHandler,
162
+ selectedItem,
163
+ selectedRows,
164
+ subSelectedValue,
165
+ ]);
166
+
167
+ const { popoverWrapper, gridPopoverOpen } = useGridPopoverHook({
168
+ className: props.className,
169
+ invalid: () => !options || !!(selectedItem && !subComponentIsValid.current),
170
+ save,
171
+ dontSaveOnExternalClick: true,
172
+ });
173
+
120
174
  // Load up options list if it's async function
175
+ const prevIsOpen = usePrevious(gridPopoverOpen);
176
+ const prevFilter = usePrevious(filter);
121
177
  useEffect(() => {
122
- if (options) return;
123
- let optionsConf = props.options;
178
+ if ((gridPopoverOpen !== prevIsOpen || filter !== prevFilter) && gridPopoverOpen) {
179
+ let optionsConf = propOptions;
124
180
 
125
- void (async () => {
126
- if (typeof optionsConf === 'function') {
127
- optionsConf = await optionsConf(selectedRows, filter);
128
- }
129
- if (optionsConf !== undefined) {
130
- setOptions(optionsConf);
131
- }
132
- })();
133
- }, [filter, options, props, selectedRows]);
181
+ void (async () => {
182
+ if (typeof optionsConf === 'function') {
183
+ optionsConf = await optionsConf(selectedRows, filter);
184
+ }
185
+ if (optionsConf !== undefined) {
186
+ setOptions(optionsConf);
187
+ }
188
+ })();
189
+ }
190
+ }, [filter, gridPopoverOpen, options, prevFilter, prevIsOpen, propOptions, selectedRows]);
134
191
 
135
192
  // Local filtering.
136
193
  useEffect(() => {
@@ -168,39 +225,6 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
168
225
  }
169
226
  }, [filter, props, reSearchOnFilterChange]);
170
227
 
171
- /**
172
- * Saves are wrapped in updateValue and triggered by blur events
173
- */
174
- const save = useCallback(async () => {
175
- if (!options) return true;
176
-
177
- // Filter saved
178
- if (selectedItem === null) {
179
- if (props.onSelectFilter) {
180
- const { onSelectFilter } = props;
181
- await onSelectFilter({ selectedRows, selectedRowIds: selectedRows.map((row) => row.id), value: filter as any });
182
- return true;
183
- } else {
184
- if (filteredValues && filteredValues.length === 1) {
185
- if (filteredValues[0].subComponent) return false;
186
- return await selectItemHandler(filteredValues[0].value, null);
187
- }
188
- }
189
- return false;
190
- }
191
- if (selectedItem.subComponent && !subComponentIsValid.current) return false;
192
- await selectItemHandler(selectedItem.value, subSelectedValue);
193
-
194
- return true;
195
- }, [filter, filteredValues, options, props, selectItemHandler, selectedItem, selectedRows, subSelectedValue]);
196
-
197
- const { popoverWrapper } = useGridPopoverHook({
198
- className: props.className,
199
- invalid: () => !options || !!(selectedItem && !subComponentIsValid.current),
200
- save,
201
- dontSaveOnExternalClick: true,
202
- });
203
-
204
228
  let lastHeader: ReactElement | null = null;
205
229
  let showHeader: ReactElement | null = null;
206
230
 
@@ -230,7 +254,7 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
230
254
  !props.onSelectFilter &&
231
255
  !(filteredValues && filteredValues.length === 1 && !filteredValues[0].subComponent)
232
256
  }
233
- onChange={(e) => setFilter(e.target.value)}
257
+ onChange={(e) => setFilter(e.target.value.trim())}
234
258
  />
235
259
  {props.filterHelpText && isNotEmpty(filter) && (
236
260
  <FormError error={null} helpText={props.filterHelpText} />
@@ -98,10 +98,14 @@ export const GridFormMultiSelect = <TData extends GridBaseRow>(props: GridFormMu
98
98
 
99
99
  const save = useCallback(
100
100
  async (selectedRows: TData[]): Promise<boolean> => {
101
- if (!options || !props.onSave) return true;
101
+ if (!options || !props.onSave) {
102
+ return true;
103
+ }
102
104
 
103
105
  // Any changes to save?
104
- if (initialValues === JSON.stringify(options)) return true;
106
+ if (initialValues === JSON.stringify(options)) {
107
+ return true;
108
+ }
105
109
 
106
110
  return props.onSave({
107
111
  selectedRows,
@@ -278,7 +282,9 @@ const FilterInput = (props: {
278
282
  }, [filter, headerGroups, options, setOptions]);
279
283
 
280
284
  const addCustomFilterValue = useCallback(() => {
281
- if (!options || !onSelectFilter) return;
285
+ if (!options || !onSelectFilter) {
286
+ return;
287
+ }
282
288
 
283
289
  const filterTrimmed = filter.trim();
284
290
  if (isEmpty(filterTrimmed)) {
@@ -289,7 +295,9 @@ const FilterInput = (props: {
289
295
  const preFilterOptions = JSON.stringify(options);
290
296
  onSelectFilter({ filter: filterTrimmed, options });
291
297
  // Detect if options list changed and update
292
- if (preFilterOptions === JSON.stringify(options)) return;
298
+ if (preFilterOptions === JSON.stringify(options)) {
299
+ return;
300
+ }
293
301
 
294
302
  setOptions([...options]);
295
303
  setFilter('');
@@ -15,6 +15,7 @@ export const GridPopoverMessage = <TData extends GridBaseRow, TValue = any>(
15
15
  },
16
16
  {
17
17
  editor: GridFormMessage,
18
+ preventAutoEdit: true,
18
19
  ...props,
19
20
  },
20
21
  );
@@ -63,8 +63,8 @@ export interface GridContextType<TData extends GridBaseRow> {
63
63
  invisibleColumnIds: string[] | undefined;
64
64
  setInvisibleColumnIds: (colIds: string[]) => void;
65
65
  downloadCsv: (csvExportParams?: CsvExportParams) => void;
66
- onBulkEditingComplete: () => void;
67
- setOnBulkEditingComplete: (callback: (() => void) | undefined) => void;
66
+ onBulkEditingComplete: () => Promise<void> | void;
67
+ setOnBulkEditingComplete: (callback: (() => Promise<void> | void) | undefined) => void;
68
68
  showNoRowsOverlay: () => void;
69
69
  }
70
70
 
@@ -502,8 +502,13 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
502
502
 
503
503
  try {
504
504
  // Edit in progress so don't edit until finished, timeout waiting after 5s
505
- if (!(await waitForCondition(() => !anyUpdating(), 5000))) {
506
- console.error("Could not start edit because previous edit hasn't finished after 5 seconds");
505
+ if (
506
+ !(await waitForCondition(
507
+ 'startCellEditing failed as update still in progress, waited for 15 seconds',
508
+ () => !anyUpdating(),
509
+ 15000,
510
+ ))
511
+ ) {
507
512
  return;
508
513
  }
509
514
 
@@ -529,11 +534,12 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
529
534
  const rowIndex = rowNode.rowIndex;
530
535
  if (rowIndex != null) {
531
536
  defer(() => {
532
- !gridApi.isDestroyed() &&
537
+ if (!gridApi.isDestroyed()) {
533
538
  gridApi.startEditingCell({
534
539
  rowIndex,
535
540
  colKey: colId,
536
541
  });
542
+ }
537
543
  });
538
544
  }
539
545
  } finally {
@@ -543,15 +549,18 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
543
549
  [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync],
544
550
  );
545
551
 
546
- const bulkEditingCompleteCallbackRef = useRef<() => void>();
547
- const onBulkEditingComplete = useCallback(() => {
552
+ const bulkEditingCompleteCallbackRef = useRef<() => Promise<void> | void>();
553
+ const onBulkEditingComplete = useCallback(async () => {
548
554
  resetFocusedCellAfterCellEditing();
549
- bulkEditingCompleteCallbackRef.current?.();
555
+ await bulkEditingCompleteCallbackRef.current?.();
550
556
  }, [resetFocusedCellAfterCellEditing]);
551
557
 
552
- const setOnBulkEditingComplete = useCallback((cellEditingCompleteCallback: (() => void) | undefined) => {
553
- bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
554
- }, []);
558
+ const setOnBulkEditingComplete = useCallback(
559
+ (cellEditingCompleteCallback: (() => Promise<void> | void) | undefined) => {
560
+ bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
561
+ },
562
+ [],
563
+ );
555
564
 
556
565
  /**
557
566
  * Returns true if an editable cell on same row was selected, else false.
@@ -585,8 +594,10 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
585
594
  prePopupFocusedCell.current = undefined;
586
595
 
587
596
  const preRow = gridApi.getFocusedCell();
597
+ // If we don't do this ag-grid will do its own continuation of an edit on tab, we don't want that as
598
+ // we are managing it ourselves
599
+ gridApi.stopEditing();
588
600
  if (tabDirection === 1) {
589
- gridApi.stopEditing();
590
601
  gridApi.tabToNextCell();
591
602
  } else {
592
603
  gridApi.tabToPreviousCell();
@@ -629,6 +640,7 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
629
640
  ): Promise<boolean> => {
630
641
  try {
631
642
  setSaving?.(true);
643
+
632
644
  return await gridApiOp(async (gridApi) => {
633
645
  const selectedRows = props.selectedRows;
634
646
 
@@ -638,10 +650,8 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
638
650
  props.field ?? '',
639
651
  selectedRows.map((data) => data.id),
640
652
  async () => {
641
- // MATT Disabled I don't believe these are needed anymore
642
- // I've left them here just in case they are
643
653
  // Need to refresh to get spinners to work on all rows
644
- // gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
654
+ gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
645
655
  ok = await fnUpdate(selectedRows).catch((ex) => {
646
656
  console.error('Exception during modifyUpdating', ex);
647
657
  return false;
@@ -649,23 +659,21 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
649
659
  },
650
660
  );
651
661
 
652
- // MATT Disabled I don't believe these are needed anymore
653
- // I've left them here just in case they are
654
662
  // async processes need to refresh their own rows
655
- // gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
663
+ gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
656
664
 
657
665
  if (gridApi.isDestroyed()) {
658
666
  return ok;
659
667
  }
660
668
 
661
669
  if (ok) {
662
- const cell = gridApi.getFocusedCell();
663
- if (cell && gridApi.getFocusedCell() == null) {
664
- !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
665
- }
670
+ //const cell = gridApi.getFocusedCell();
671
+ //if (cell && gridApi.getFocusedCell() == null) {
672
+ // !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
673
+ // }
666
674
 
667
675
  // This is needed to trigger postSortRowsHook
668
- gridApi.refreshClientSideRowModel();
676
+ !gridApi.isDestroyed && gridApi.refreshClientSideRowModel();
669
677
  }
670
678
 
671
679
  void (async () => {
@@ -678,10 +686,10 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
678
686
  prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()
679
687
  ) {
680
688
  if (!tabDirection || !(await selectNextEditableCell(tabDirection))) {
681
- onBulkEditingComplete();
689
+ await onBulkEditingComplete();
682
690
  }
683
691
  } else {
684
- onBulkEditingComplete();
692
+ await onBulkEditingComplete();
685
693
  }
686
694
  })();
687
695
 
@@ -30,7 +30,7 @@ export default {
30
30
  ],
31
31
  } as Meta<typeof Grid>;
32
32
 
33
- const GridPopoutEditGenericTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
33
+ const GridPopoutEditGenericTemplate: StoryFn<typeof Grid<IFormTestRow>> = (props: GridProps<IFormTestRow>) => {
34
34
  const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
35
35
  const columnDefs: ColDefT<IFormTestRow>[] = useMemo(
36
36
  () => [
@@ -49,7 +49,7 @@ interface ITestRow {
49
49
  position3: string | null;
50
50
  }
51
51
 
52
- const GridEditMultiSelectTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
52
+ const GridEditMultiSelectTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<ITestRow>) => {
53
53
  const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
54
54
 
55
55
  const columnDefs: ColDefT<ITestRow>[] = useMemo(() => {
@@ -39,7 +39,7 @@ interface ITestRow {
39
39
  position2: string | null;
40
40
  }
41
41
 
42
- const GridEditMultiSelectGridTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
42
+ const GridEditMultiSelectGridTemplate: StoryFn<typeof Grid<ITestRow>> = (props: GridProps<ITestRow>) => {
43
43
  const [externalSelectedItems, setExternalSelectedItems] = useState<any[]>([]);
44
44
 
45
45
  const columnDefs: ColDefT<ITestRow>[] = useMemo(() => {
@@ -123,7 +123,7 @@ GridFormDropDownInteractions_.play = async ({ canvasElement }) => {
123
123
  // Test escape to not save
124
124
  updateValue.mockClear();
125
125
  await userEvent.type(textInput, '{Escape}');
126
- expect(updateValue).not.toHaveBeenCalled();
126
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
127
127
 
128
128
  // Test invalid value doesn't save
129
129
  updateValue.mockClear();
@@ -9,6 +9,7 @@ import { useRef } from 'react';
9
9
  import { expect, fn, userEvent, within } from 'storybook/test';
10
10
 
11
11
  import {
12
+ CancelPromise,
12
13
  GridContextProvider,
13
14
  GridFormEditBearing,
14
15
  GridFormEditBearingProps,
@@ -86,7 +87,7 @@ GridFormEditBearingCorrectionInteractions_.play = async ({ canvasElement }) => {
86
87
  // Test escape not to save
87
88
  updateValue.mockClear();
88
89
  await userEvent.type(inputField, '{Escape}');
89
- expect(updateValue).not.toHaveBeenCalled();
90
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
90
91
 
91
92
  // Test invalid value doesn't save
92
93
  updateValue.mockClear();
@@ -86,7 +86,7 @@ GridFormEditBearingInteractions_.play = async ({ canvasElement }) => {
86
86
  // Test escape not to save
87
87
  updateValue.mockClear();
88
88
  await userEvent.type(inputField, '{Escape}');
89
- expect(updateValue).not.toHaveBeenCalled();
89
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
90
90
 
91
91
  // Test invalid value doesn't save
92
92
  updateValue.mockClear();
@@ -135,7 +135,8 @@ GridFormMultiSelectInteractions_.play = async ({ canvasElement }) => {
135
135
  updateValue.mockClear();
136
136
  onSave.mockClear();
137
137
  await userEvent.type(textInput, '{Escape}');
138
- expect(updateValue).not.toHaveBeenCalled();
138
+ // expect(updateValue).not.toHaveBeenCalled();
139
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
139
140
  expect(onSave).not.toHaveBeenCalled();
140
141
 
141
142
  // Test invalid value doesn't save
@@ -131,7 +131,7 @@ GridFormPopoverMenuInteractions_.play = async ({ canvasElement }) => {
131
131
  // Test escape to not save
132
132
  updateValue.mockClear();
133
133
  await userEvent.type(textInput, '{Escape}');
134
- expect(updateValue).not.toHaveBeenCalled();
134
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
135
135
 
136
136
  // Test invalid value doesn't save
137
137
  updateValue.mockClear();
@@ -163,7 +163,7 @@ GridFormPopoverMenuInteractions_.play = async ({ canvasElement }) => {
163
163
  // Test escape to not save
164
164
  updateValue.mockClear();
165
165
  await userEvent.type(textArea, '{Escape}');
166
- expect(updateValue).not.toHaveBeenCalled();
166
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
167
167
 
168
168
  // Test invalid value doesn't save
169
169
  updateValue.mockClear();
@@ -71,7 +71,7 @@ GridFormTextAreaInteractions_.play = async ({ canvasElement }) => {
71
71
  // Test escape not to save
72
72
  updateValue.mockClear();
73
73
  await userEvent.type(inputField, '{Escape}');
74
- expect(updateValue).not.toHaveBeenCalled();
74
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
75
75
 
76
76
  // Test invalid value doesn't save
77
77
  updateValue.mockClear();
@@ -76,7 +76,7 @@ GridFormTextInputInteractions_.play = async ({ canvasElement }) => {
76
76
  // Test escape not to save
77
77
  updateValue.mockClear();
78
78
  await userEvent.type(inputField, '{Escape}');
79
- expect(updateValue).not.toHaveBeenCalled();
79
+ expect(updateValue).toHaveBeenCalledWith(expect.anything(), 0);
80
80
 
81
81
  // Test invalid value doesn't save
82
82
  updateValue.mockClear();