@linzjs/step-ag-grid 28.5.0 → 29.0.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 (42) hide show
  1. package/dist/index.css +11 -7
  2. package/dist/src/components/Grid.d.ts +5 -1
  3. package/dist/src/contexts/GridContext.d.ts +3 -4
  4. package/dist/src/contexts/GridPopoverContext.d.ts +1 -0
  5. package/dist/src/contexts/GridUpdatingContext.d.ts +1 -0
  6. package/dist/src/lui/reactUtils.d.ts +4 -0
  7. package/dist/step-ag-grid.cjs +265 -247
  8. package/dist/step-ag-grid.cjs.map +1 -1
  9. package/dist/step-ag-grid.esm.js +266 -248
  10. package/dist/step-ag-grid.esm.js.map +1 -1
  11. package/package.json +21 -22
  12. package/src/components/Grid.tsx +118 -113
  13. package/src/components/GridPopoverHook.tsx +25 -20
  14. package/src/components/gridFilter/useGridFilter.ts +3 -3
  15. package/src/components/gridHook/useGridContextMenu.tsx +3 -3
  16. package/src/contexts/GridContext.tsx +11 -14
  17. package/src/contexts/GridContextProvider.tsx +164 -137
  18. package/src/contexts/GridPopoverContext.tsx +2 -0
  19. package/src/contexts/GridPopoverContextProvider.tsx +9 -5
  20. package/src/contexts/GridUpdatingContext.tsx +5 -0
  21. package/src/contexts/GridUpdatingContextProvider.tsx +35 -28
  22. package/src/lui/reactUtils.tsx +24 -0
  23. package/src/react-menu3/components/MenuList.tsx +3 -0
  24. package/src/stories/grid/GridFilterButtons.stories.tsx +1 -1
  25. package/src/stories/grid/GridNonEditableRow.stories.tsx +1 -1
  26. package/src/stories/grid/GridPinnedRow.stories.tsx +1 -1
  27. package/src/stories/grid/GridPopoutContextMenu.stories.tsx +3 -3
  28. package/src/stories/grid/GridPopoutEditGenericTextArea.stories.tsx +2 -2
  29. package/src/stories/grid/GridPopoverEditDropDown.stories.tsx +8 -4
  30. package/src/stories/grid/GridPopoverEditMultiSelect.stories.tsx +1 -0
  31. package/src/stories/grid/GridReadOnly.stories.tsx +1 -1
  32. package/src/stories/grid/GridViewList.stories.tsx +1 -1
  33. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +5 -3
  34. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +3 -2
  35. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +3 -2
  36. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectGridInteraction.stories.tsx +3 -2
  37. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +3 -2
  38. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +3 -2
  39. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +3 -2
  40. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +3 -2
  41. package/src/stories/grid/gridFormStatic/GridFormDropDown.stories.tsx +2 -2
  42. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +2 -2
@@ -449,9 +449,6 @@ const GridContext = React.createContext({
449
449
  prePopupOps: () => {
450
450
  console.error('no context provider for prePopupOps');
451
451
  },
452
- postPopupOps: () => {
453
- console.error('no context provider for prePopupOps');
454
- },
455
452
  externallySelectedItemsAreInSync: false,
456
453
  setApis: () => {
457
454
  console.error('no context provider for setApis');
@@ -523,15 +520,12 @@ const GridContext = React.createContext({
523
520
  console.error('no context provider for editingCells');
524
521
  return false;
525
522
  },
526
- cancelEdit: () => {
527
- console.error('no context provider for cancelEdit');
528
- },
529
523
  // eslint-disable-next-line @typescript-eslint/require-await
530
524
  startCellEditing: async () => {
531
525
  console.error('no context provider for startCellEditing');
532
526
  },
533
- stopEditing: () => {
534
- console.error('no context provider for stopEditing');
527
+ resetFocusedCellAfterCellEditing: () => {
528
+ console.error('no context provider for resetFocusedCellAfterCellEditing');
535
529
  },
536
530
  // eslint-disable-next-line @typescript-eslint/require-await
537
531
  updatingCells: async () => {
@@ -565,8 +559,11 @@ const GridContext = React.createContext({
565
559
  downloadCsv: () => {
566
560
  console.error('no context provider for downloadCsv');
567
561
  },
568
- setOnCellEditingComplete: () => {
569
- console.error('no context provider for setOnCellEditingComplete');
562
+ onBulkEditingComplete: () => {
563
+ console.error('no context provider for onBulkEditingComplete');
564
+ },
565
+ setOnBulkEditingComplete: () => {
566
+ console.error('no context provider for setOnBulkEditingComplete');
570
567
  },
571
568
  showNoRowsOverlay: () => {
572
569
  console.error('no context provider for showLoadingOverlay');
@@ -575,6 +572,10 @@ const GridContext = React.createContext({
575
572
  const useGridContext = () => React.useContext(GridContext);
576
573
 
577
574
  const GridUpdatingContext = React.createContext({
575
+ anyUpdating: () => {
576
+ console.error('Missing GridUpdatingContext');
577
+ return false;
578
+ },
578
579
  checkUpdating: () => {
579
580
  console.error('Missing GridUpdatingContext');
580
581
  return false;
@@ -1796,6 +1797,9 @@ const MenuList = ({ ariaLabel, menuClassName, menuStyle, arrowClassName, arrowSt
1796
1797
  return;
1797
1798
  }
1798
1799
  const callback = lodashEs.debounce(() => {
1800
+ if (!menuRef.current) {
1801
+ return;
1802
+ }
1799
1803
  const { width, height } = menuRef.current.ownerDocument.body.getBoundingClientRect();
1800
1804
  if (width === 0 || height === 0)
1801
1805
  return;
@@ -2541,7 +2545,7 @@ const SubMenuFr = ({ 'aria-label': ariaLabel, className, disabled, direction, la
2541
2545
  const SubMenu = withHovering('SubMenu', SubMenuFr);
2542
2546
 
2543
2547
  const useGridContextMenu = ({ contextMenuSelectRow, contextMenu: ContextMenu, }) => {
2544
- const { redrawRows, prePopupOps, postPopupOps, getSelectedRows } = React.useContext(GridContext);
2548
+ const { redrawRows, prePopupOps, resetFocusedCellAfterCellEditing, getSelectedRows } = React.useContext(GridContext);
2545
2549
  const [isOpen, setOpen] = React.useState(false);
2546
2550
  const [anchorPoint, setAnchorPoint] = React.useState({ x: 0, y: 0 });
2547
2551
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
@@ -2558,8 +2562,8 @@ const useGridContextMenu = ({ contextMenuSelectRow, contextMenu: ContextMenu, })
2558
2562
  const closeMenu = React.useCallback(() => {
2559
2563
  setOpen(false);
2560
2564
  redrawRows();
2561
- postPopupOps();
2562
- }, [postPopupOps, redrawRows]);
2565
+ resetFocusedCellAfterCellEditing();
2566
+ }, [resetFocusedCellAfterCellEditing, redrawRows]);
2563
2567
  const cellContextMenu = React.useCallback((event) => {
2564
2568
  if (contextMenuSelectRow && !event.node.isSelected()) {
2565
2569
  event.node.setSelected(true, true);
@@ -2755,36 +2759,11 @@ const usePostSortRowsHook = ({ setStaleGrid }) => {
2755
2759
  };
2756
2760
 
2757
2761
  agGridCommunity.ModuleRegistry.registerModules([agGridCommunity.AllCommunityModule]);
2758
- let timeOfLastSingleClick = 0;
2759
- let lastClickColId;
2760
- let lastClickRowIndex;
2761
- const resetClickDebounce = () => {
2762
- timeOfLastSingleClick = 0;
2763
- lastClickColId = '';
2764
- lastClickRowIndex = -1;
2765
- };
2766
- /**
2767
- * If click is more than 200ms since last click return true.
2768
- */
2769
- const clickDebounceSkipClick = (colId, rowIndex) => {
2770
- const doubleClickMs = 200;
2771
- if (Date.now() - timeOfLastSingleClick < doubleClickMs &&
2772
- lastClickColId === colId &&
2773
- lastClickRowIndex === rowIndex) {
2774
- // Skipping double click due to single click edit
2775
- return true;
2776
- }
2777
- timeOfLastSingleClick = Date.now();
2778
- lastClickColId = colId;
2779
- lastClickRowIndex = rowIndex;
2780
- return false;
2781
- };
2782
2762
  /**
2783
2763
  * Wrapper for AgGrid to add commonly used functionality.
2784
2764
  */
2785
2765
  const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection = 'multiple', suppressColumnVirtualization = true, theme = 'ag-theme-step-default', sizeColumns = 'auto', selectColumnPinned = 'left', contextMenuSelectRow = false, singleClickEdit = false, rowData, rowHeight = theme === 'ag-theme-step-default' ? 40 : theme === 'ag-theme-step-compact' ? 36 : 40, selectable, ...params }) => {
2786
- const { gridReady, gridRenderState, setApis, ensureRowVisible, getFirstRowId, selectRowsById, focusByRowById, ensureSelectedRowIsVisible, autoSizeColumns, sizeColumnsToFit, externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync, isExternalFilterPresent, doesExternalFilterPass, setOnCellEditingComplete, getColDef, showNoRowsOverlay, prePopupOps, stopEditing, } = React.useContext(GridContext);
2787
- const { startCellEditing } = useGridContext();
2766
+ const { gridReady, gridRenderState, setApis, ensureRowVisible, getFirstRowId, selectRowsById, focusByRowById, ensureSelectedRowIsVisible, autoSizeColumns, sizeColumnsToFit, externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync, isExternalFilterPresent, doesExternalFilterPass, setOnBulkEditingComplete, getColDef, showNoRowsOverlay, prePopupOps, startCellEditing, } = useGridContext();
2788
2767
  const { updatedDep, updatingCols } = React.useContext(GridUpdatingContext);
2789
2768
  const gridDivRef = React.useRef(null);
2790
2769
  const lastSelectedIds = React.useRef([]);
@@ -2861,7 +2840,8 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
2861
2840
  needsAutoSize.current = true;
2862
2841
  }
2863
2842
  }
2864
- if (needsAutoSize.current || (!hasSetContentSize.current && sizeColumns === 'auto')) {
2843
+ if (needsAutoSize.current ||
2844
+ (!hasSetContentSize.current && (sizeColumns === 'auto' || sizeColumns === 'auto-skip-headers'))) {
2865
2845
  needsAutoSize.current = false;
2866
2846
  setInitialContentSize();
2867
2847
  }
@@ -3002,26 +2982,10 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3002
2982
  const onModelUpdated = React.useCallback((event) => {
3003
2983
  event.api.showNoRowsOverlay();
3004
2984
  }, []);
3005
- /**
3006
- * Force-refresh all selected rows to re-run class function, to update selection highlighting
3007
- */
3008
- const refreshSelectedRows = React.useCallback((_event) => {
3009
- // MATT Disabled I don't believe these are needed anymore
3010
- // I've left them here just in case they are
3011
- /*event.api.refreshCells({
3012
- force: true,
3013
- rowNodes: event.api.getSelectedNodes(),
3014
- });*/
3015
- }, []);
3016
2985
  /**
3017
2986
  * Handle double click edit
3018
2987
  */
3019
2988
  const onCellDoubleClick = React.useCallback((event) => {
3020
- if (clickDebounceSkipClick(event.colDef.colId, event.rowIndex)) {
3021
- // the next click will be a single click, we want it to pass
3022
- resetClickDebounce();
3023
- return;
3024
- }
3025
2989
  const editable = fnOrVar(event.colDef?.editable, event);
3026
2990
  if (editable && !invokeEditAction(event)) {
3027
2991
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
@@ -3033,9 +2997,6 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3033
2997
  const onCellClicked = React.useCallback((event) => {
3034
2998
  const editable = fnOrVar(event.colDef?.editable, event);
3035
2999
  if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
3036
- if (clickDebounceSkipClick(event.colDef.colId, event.rowIndex)) {
3037
- return;
3038
- }
3039
3000
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
3040
3001
  }
3041
3002
  }, [singleClickEdit, startCellEditing]);
@@ -3131,11 +3092,10 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3131
3092
  React.useEffect(() => {
3132
3093
  const newLoading = !rowData || params.loading === true;
3133
3094
  if (newLoading && !prevLoading.current) {
3134
- stopEditing();
3135
3095
  showNoRowsOverlay();
3136
3096
  }
3137
3097
  prevLoading.current = newLoading;
3138
- }, [params.loading, rowData, showNoRowsOverlay, stopEditing]);
3098
+ }, [params.loading, rowData, showNoRowsOverlay]);
3139
3099
  /**
3140
3100
  * Resize columns to fit if required on window/container resize
3141
3101
  */
@@ -3197,6 +3157,23 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3197
3157
  });
3198
3158
  }
3199
3159
  }, [clearHighlightRowClasses]);
3160
+ const onCellFocused = React.useCallback((event) => {
3161
+ if (!params.onCellFocused || event.rowIndex == null) {
3162
+ return;
3163
+ }
3164
+ const api = event.api;
3165
+ const rowNode = api.getDisplayedRowAtIndex(event.rowIndex);
3166
+ const data = rowNode?.data;
3167
+ const column = event.column;
3168
+ if (!data || !column || typeof column === 'string') {
3169
+ return;
3170
+ }
3171
+ const colDef = column.getColDef();
3172
+ if (!colDef || typeof colDef === 'string') {
3173
+ return;
3174
+ }
3175
+ params.onCellFocused({ colDef, data });
3176
+ }, [params]);
3200
3177
  const onRowDragEnd = React.useCallback((event) => {
3201
3178
  clearHighlightRowClasses();
3202
3179
  gridElementRef.current = undefined;
@@ -3221,57 +3198,76 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3221
3198
  }
3222
3199
  }, [params.setExternalSelectedItems, selectable]);
3223
3200
  // This is setting a ref in the GridContext so won't be triggering an update loop
3224
- setOnCellEditingComplete(params.onCellEditingComplete);
3225
- const selectWidth = params.hideSelectColumn ? 0 : selectable && params.onRowDragEnd ? 76 : 48;
3226
- const headerRowCount = columnDefs.some((c) => c.children) ? 2 : 1;
3201
+ setOnBulkEditingComplete(params.onBulkEditingComplete);
3202
+ const getRowId = React.useCallback((params) => `${params.data.id}`, []);
3203
+ const defaultColDef = React.useMemo(() => ({
3204
+ minWidth: 48,
3205
+ ...lodashEs.omit(params.defaultColDef, ['editable', 'field', 'colId', 'tooltipField']),
3206
+ }), [params.defaultColDef]);
3207
+ const noRowsOverlayComponent = React.useCallback((event) => {
3208
+ const headerRowCount = columnDefs.some((c) => c.children) ? 2 : 1;
3209
+ let rowCount = 0;
3210
+ event.api.forEachNode(() => rowCount++);
3211
+ return (jsxRuntime.jsx(GridNoRowsOverlay, { loading: !rowData || params.loading === true, rowCount: rowCount, headerRowHeight: headerRowCount * rowHeight, filteredRowCount: event.api.getDisplayedRowCount(), noRowsOverlayText: params.noRowsOverlayText, noRowsMatchingOverlayText: params.noRowsMatchingOverlayText }));
3212
+ }, [columnDefs, params.loading, params.noRowsMatchingOverlayText, params.noRowsOverlayText, rowData, rowHeight]);
3213
+ const selectionColumnDef = React.useMemo(() => {
3214
+ const selectWidth = params.hideSelectColumn ? 0 : selectable && params.onRowDragEnd ? 76 : 48;
3215
+ return {
3216
+ suppressNavigable: params.hideSelectColumn,
3217
+ rowDrag: !!params.onRowDragEnd,
3218
+ minWidth: selectWidth,
3219
+ maxWidth: selectWidth,
3220
+ pinned: selectColumnPinned,
3221
+ headerComponentParams: {
3222
+ exportable: false,
3223
+ },
3224
+ headerClass: clsx('ag-header-hide-default-select', params.onRowDragEnd && 'ag-header-select-draggable'),
3225
+ headerComponent: rowSelection == 'multiple' ? GridHeaderSelect : undefined,
3226
+ suppressHeaderKeyboardEvent: (e) => {
3227
+ if (!selectable)
3228
+ return false;
3229
+ if ((e.event.key === 'Enter' || e.event.key === ' ') && !e.event.repeat) {
3230
+ if (lodashEs.isEmpty(e.api.getSelectedRows())) {
3231
+ e.api.selectAll('filtered');
3232
+ }
3233
+ else {
3234
+ e.api.deselectAll();
3235
+ }
3236
+ return true;
3237
+ }
3238
+ return false;
3239
+ },
3240
+ onCellClicked: params.enableSelectionWithoutKeys || params.enableClickSelection
3241
+ ? undefined
3242
+ : clickInputWhenContainingCellClicked,
3243
+ };
3244
+ }, [
3245
+ params.enableClickSelection,
3246
+ params.enableSelectionWithoutKeys,
3247
+ params.hideSelectColumn,
3248
+ params.onRowDragEnd,
3249
+ rowSelection,
3250
+ selectColumnPinned,
3251
+ selectable,
3252
+ ]);
3227
3253
  return (jsxRuntime.jsxs("div", { "data-testid": dataTestId, className: clsx('Grid-container', theme, 'theme-specific', staleGrid && 'Grid-sortIsStale', gridReady && rowData && autoSized && 'Grid-ready'), children: [gridContextMenu.component, jsxRuntime.jsx("div", { style: { flex: 1 }, ref: gridDivRef, children: jsxRuntime.jsx(agGridReact.AgGridReact, { theme: 'legacy', rowSelection: selectable
3228
3254
  ? {
3229
3255
  enableSelectionWithoutKeys: params.enableSelectionWithoutKeys ?? false,
3230
3256
  enableClickSelection: params.enableClickSelection ?? false,
3231
3257
  mode: rowSelection == 'single' ? 'singleRow' : 'multiRow',
3232
3258
  }
3233
- : undefined, rowHeight: rowHeight, animateRows: params.animateRows ?? false, rowClassRules: params.rowClassRules, getRowId: (params) => `${params.data.id}`, onGridSizeChanged: onGridSizeChanged, suppressColumnVirtualisation: suppressColumnVirtualization, suppressClickEdit: true, onColumnVisible: setInitialContentSize, onRowDataUpdated: onRowDataChanged, onCellKeyDown: onCellKeyPress, onCellClicked: onCellClicked, onCellDoubleClicked: onCellDoubleClick, onCellEditingStarted: refreshSelectedRows, domLayout: params.domLayout, onColumnResized: onColumnResized, defaultColDef: { minWidth: 48, ...lodashEs.omit(params.defaultColDef, ['editable']) }, columnDefs: columnDefsAdjusted, rowData: rowData, noRowsOverlayComponent: (event) => {
3234
- let rowCount = 0;
3235
- event.api.forEachNode(() => rowCount++);
3236
- return (jsxRuntime.jsx(GridNoRowsOverlay, { loading: !rowData || params.loading === true, rowCount: rowCount, headerRowHeight: headerRowCount * rowHeight, filteredRowCount: event.api.getDisplayedRowCount(), noRowsOverlayText: params.noRowsOverlayText, noRowsMatchingOverlayText: params.noRowsMatchingOverlayText }));
3237
- }, quickFilterParser: (filterStr) => {
3238
- // filter is exact matches exactly groups separated by commas
3239
- return filterStr.split(',').map((str) => str.trim());
3240
- }, onModelUpdated: onModelUpdated, onGridReady: onGridReady, onSortChanged: ensureSelectedRowIsVisible, postSortRows: params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows, onSelectionChanged: synchroniseExternalStateToGridSelection, onColumnMoved: params.onColumnMoved, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, isExternalFilterPresent: isExternalFilterPresent, doesExternalFilterPass: doesExternalFilterPass, maintainColumnOrder: true, preventDefaultOnContextMenu: true, onCellContextMenu: gridContextMenu.cellContextMenu, rowDragText: params.rowDragText, onRowDragCancel: clearHighlightRowClasses, onRowDragMove: onRowDragMove, onRowDragEnd: onRowDragEnd, suppressCellFocus: params.suppressCellFocus, pinnedTopRowData: params.pinnedTopRowData, pinnedBottomRowData: params.pinnedBottomRowData, selectionColumnDef: {
3241
- suppressNavigable: params.hideSelectColumn,
3242
- rowDrag: !!params.onRowDragEnd,
3243
- minWidth: selectWidth,
3244
- maxWidth: selectWidth,
3245
- pinned: selectColumnPinned,
3246
- headerComponentParams: {
3247
- exportable: false,
3248
- },
3249
- headerClass: clsx('ag-header-hide-default-select', params.onRowDragEnd && 'ag-header-select-draggable'),
3250
- headerComponent: rowSelection == 'multiple' ? GridHeaderSelect : undefined,
3251
- suppressHeaderKeyboardEvent: (e) => {
3252
- if (!selectable)
3253
- return false;
3254
- if ((e.event.key === 'Enter' || e.event.key === ' ') && !e.event.repeat) {
3255
- if (lodashEs.isEmpty(e.api.getSelectedRows())) {
3256
- e.api.selectAll('filtered');
3257
- }
3258
- else {
3259
- e.api.deselectAll();
3260
- }
3261
- return true;
3262
- }
3263
- return false;
3264
- },
3265
- onCellClicked: params.enableSelectionWithoutKeys || params.enableClickSelection
3266
- ? undefined
3267
- : clickInputWhenContainingCellClicked,
3268
- } }) })] }));
3259
+ : undefined, rowHeight: rowHeight, animateRows: params.animateRows ?? false, rowClassRules: params.rowClassRules, getRowId: getRowId, onGridSizeChanged: onGridSizeChanged, suppressColumnVirtualisation: suppressColumnVirtualization, suppressClickEdit: true, onColumnVisible: setInitialContentSize, onRowDataUpdated: onRowDataChanged, onCellFocused: onCellFocused, onCellKeyDown: onCellKeyPress, onCellClicked: onCellClicked, onCellDoubleClicked: onCellDoubleClick, domLayout: params.domLayout, onColumnResized: onColumnResized, defaultColDef: defaultColDef, columnDefs: columnDefsAdjusted, selectionColumnDef: selectionColumnDef, rowData: rowData, postSortRows: params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows, onModelUpdated: onModelUpdated, onGridReady: onGridReady, onSortChanged: ensureSelectedRowIsVisible, quickFilterParser: quickFilterParser, onSelectionChanged: synchroniseExternalStateToGridSelection, onColumnMoved: params.onColumnMoved, noRowsOverlayComponent: noRowsOverlayComponent, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, isExternalFilterPresent: isExternalFilterPresent, doesExternalFilterPass: doesExternalFilterPass, maintainColumnOrder: true, preventDefaultOnContextMenu: true, onCellContextMenu: gridContextMenu.cellContextMenu, rowDragText: params.rowDragText, onRowDragCancel: clearHighlightRowClasses, onRowDragMove: onRowDragMove, onRowDragEnd: onRowDragEnd, suppressCellFocus: params.suppressCellFocus, pinnedTopRowData: params.pinnedTopRowData, pinnedBottomRowData: params.pinnedBottomRowData }) })] }));
3260
+ };
3261
+ const quickFilterParser = (filterStr) => {
3262
+ // filter is exact matches exactly groups separated by commas
3263
+ return filterStr.split(',').map((str) => str.trim());
3269
3264
  };
3270
3265
 
3271
3266
  const GridPopoverContext = React.createContext({
3272
3267
  anchorRef: { current: null },
3273
3268
  saving: false,
3274
3269
  setSaving: () => { },
3270
+ stopEditing: () => { },
3275
3271
  colId: '',
3276
3272
  field: '',
3277
3273
  value: null,
@@ -3289,7 +3285,7 @@ const GridPopoverContextProvider = (props2) => {
3289
3285
  const anchorRef = React.useRef(props.eGridCell);
3290
3286
  const hasSaved = React.useRef(false);
3291
3287
  const [saving, setSaving] = React.useState(false);
3292
- const { colDef } = props;
3288
+ const { colDef, stopEditing, formatValue, data, value } = props;
3293
3289
  const { cellEditorParams } = colDef;
3294
3290
  const multiEdit = cellEditorParams?.multiEdit ?? false;
3295
3291
  // Then item that is clicked on will always be first in the list
@@ -3297,8 +3293,10 @@ const GridPopoverContextProvider = (props2) => {
3297
3293
  const field = props.colDef?.field ?? '';
3298
3294
  const colId = props.colDef?.colId ?? field ?? '';
3299
3295
  const updateValue = React.useCallback(async (saveFn, tabDirection) => {
3300
- if (hasSaved.current)
3296
+ if (hasSaved.current) {
3297
+ // already called save so ignore
3301
3298
  return true;
3299
+ }
3302
3300
  hasSaved.current = true;
3303
3301
  return saving ? false : await updatingCells({ selectedRows, field }, saveFn, setSaving, tabDirection);
3304
3302
  }, [field, saving, selectedRows, updatingCells]);
@@ -3309,10 +3307,11 @@ const GridPopoverContextProvider = (props2) => {
3309
3307
  selectedRows,
3310
3308
  colId,
3311
3309
  field,
3312
- data: props.data,
3313
- value: props.value,
3310
+ data,
3311
+ value,
3314
3312
  updateValue,
3315
- formatValue: props.formatValue,
3313
+ formatValue,
3314
+ stopEditing,
3316
3315
  }, children: children }));
3317
3316
  };
3318
3317
 
@@ -3462,7 +3461,7 @@ const GridCellMultiEditor = (props, cellEditorSelector) => GridCell({
3462
3461
  });
3463
3462
 
3464
3463
  const useGridFilter = (filter) => {
3465
- const { addExternalFilter, removeExternalFilter } = React.useContext(GridContext);
3464
+ const { addExternalFilter, removeExternalFilter } = useGridContext();
3466
3465
  React.useEffect(() => {
3467
3466
  const thisFilter = filter;
3468
3467
  thisFilter && addExternalFilter(thisFilter);
@@ -3937,8 +3936,8 @@ const textMatch = (text, filter) => {
3937
3936
  };
3938
3937
 
3939
3938
  const useGridPopoverHook = (props) => {
3940
- const { stopEditing, cancelEdit } = React.useContext(GridContext);
3941
- const { anchorRef, saving, updateValue } = useGridPopoverContext();
3939
+ const { onBulkEditingComplete } = useGridContext();
3940
+ const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext();
3942
3941
  const saveButtonRef = React.useRef(null);
3943
3942
  const [isOpen, setOpen] = React.useState(false);
3944
3943
  React.useEffect(() => {
@@ -3946,30 +3945,30 @@ const useGridPopoverHook = (props) => {
3946
3945
  }, []);
3947
3946
  const triggerSave = React.useCallback(async (reason) => {
3948
3947
  if (reason == CloseReason.CANCEL) {
3949
- cancelEdit();
3948
+ stopEditing();
3949
+ onBulkEditingComplete();
3950
3950
  return;
3951
3951
  }
3952
- if (props.invalid && props.invalid()) {
3952
+ if (props?.invalid?.()) {
3953
+ // Don't close, don't do anything it's invalid
3953
3954
  return;
3954
3955
  }
3955
3956
  if (!props.save) {
3956
- cancelEdit();
3957
- }
3958
- else if (props.save) {
3959
- // forms that don't provide an invalid fn must wait until they have saved to close
3960
- if (props.invalid)
3961
- stopEditing();
3962
- if (await updateValue(props.save, reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
3963
- if (!props.invalid)
3964
- stopEditing();
3965
- }
3957
+ // No save method so just close
3958
+ stopEditing();
3959
+ onBulkEditingComplete();
3960
+ return;
3966
3961
  }
3967
- }, [cancelEdit, props, stopEditing, updateValue]);
3962
+ if (await updateValue(props.save, reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
3963
+ stopEditing();
3964
+ }
3965
+ }, [onBulkEditingComplete, props, stopEditing, updateValue]);
3968
3966
  const popoverWrapper = React.useCallback((children) => {
3969
3967
  return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: anchorRef.current && (jsxRuntime.jsxs(ControlledMenu, { state: isOpen ? 'open' : 'closed', portal: true, unmountOnClose: true, anchorRef: anchorRef, saveButtonRef: saveButtonRef, menuClassName: 'step-ag-grid-react-menu', onClose: (event) => {
3970
3968
  // Prevent menu from closing when modals are invoked
3971
- if (event.reason === CloseReason.BLUR)
3969
+ if (event.reason === CloseReason.BLUR) {
3972
3970
  return;
3971
+ }
3973
3972
  void triggerSave(event.reason);
3974
3973
  }, viewScroll: 'auto', dontShrinkIfDirectionIsTop: true, className: props.className, children: [saving && ( // This is the overlay that prevents editing when the editor is saving
3975
3974
  jsxRuntime.jsx("div", { className: 'ComponentLoadingWrapper-saveOverlay' })), children, jsxRuntime.jsx("button", { ref: saveButtonRef, "data-reason": '', onClick: (e) => {
@@ -5063,7 +5062,7 @@ const isFlexColumn = (colDef) => !!colDef.flex && !isGridCellFiller(colDef);
5063
5062
  * Also, make sure the provider is created in a separate component, otherwise it won't be found.
5064
5063
  */
5065
5064
  const GridContextProvider = (props) => {
5066
- const { modifyUpdating, checkUpdating } = React.useContext(GridUpdatingContext);
5065
+ const { modifyUpdating, anyUpdating } = React.useContext(GridUpdatingContext);
5067
5066
  const [gridApi, setGridApi] = React.useState();
5068
5067
  const [gridReady, setGridReady] = React.useState(false);
5069
5068
  const [quickFilter, _setQuickFilter] = React.useState('');
@@ -5082,7 +5081,7 @@ const GridContextProvider = (props) => {
5082
5081
  /**
5083
5082
  * Make extra sure the GridCellFillerColId never gets added to invisibleColumnIds as it's dynamically determined
5084
5083
  */
5085
- const setInvisibleColumnIds = (invisibleColumnIds) => _setInvisibleColumnIds(lodashEs.pull(invisibleColumnIds, GridCellFillerColId));
5084
+ const setInvisibleColumnIds = React.useCallback((invisibleColumnIds) => _setInvisibleColumnIds(lodashEs.pull(invisibleColumnIds, GridCellFillerColId)), []);
5086
5085
  /**
5087
5086
  * Set quick filter directly on grid, based on previously save quickFilter state.
5088
5087
  */
@@ -5177,17 +5176,6 @@ const GridContextProvider = (props) => {
5177
5176
  }
5178
5177
  prePopupFocusedCell.current = gridApi.getFocusedCell() ?? undefined;
5179
5178
  }, [gridApi]);
5180
- /**
5181
- * After a popup refocus the cell.
5182
- */
5183
- const postPopupOps = React.useCallback(() => {
5184
- if (!gridApi || gridApi.isDestroyed()) {
5185
- return;
5186
- }
5187
- if (prePopupFocusedCell.current) {
5188
- gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
5189
- }
5190
- }, [gridApi]);
5191
5179
  /**
5192
5180
  * Get all row id's in grid.
5193
5181
  */
@@ -5375,13 +5363,16 @@ const GridContextProvider = (props) => {
5375
5363
  gridApi.sizeColumnsToFit();
5376
5364
  }
5377
5365
  }, [gridApi]);
5378
- const stopEditing = React.useCallback(() => {
5379
- if (!gridApi || gridApi.isDestroyed()) {
5366
+ /**
5367
+ *
5368
+ */
5369
+ const resetFocusedCellAfterCellEditing = React.useCallback(() => {
5370
+ if (!gridApi || gridApi.isDestroyed() || startCellEditingInProgressRef.current) {
5380
5371
  return;
5381
5372
  }
5382
- gridApi.stopEditing();
5383
5373
  if (prePopupFocusedCell.current) {
5384
5374
  gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
5375
+ prePopupFocusedCell.current = undefined;
5385
5376
  }
5386
5377
  }, [gridApi]);
5387
5378
  // waitForExternallySelectedItemsToBeInSync can't use the state as it won't be updated during function execution
@@ -5402,58 +5393,65 @@ const GridContextProvider = (props) => {
5402
5393
  console.error('externallySelectedItems did not sync with ag-grid selection');
5403
5394
  }
5404
5395
  }, []);
5396
+ const startCellEditingInProgressRef = React.useRef(false);
5405
5397
  const startCellEditing = React.useCallback(async ({ rowId, colId }) => {
5406
- if (!gridApi)
5407
- return;
5408
- const colDef = gridApi.getColumnDef(colId);
5409
- if (!colDef) {
5410
- return;
5411
- }
5412
- const rowNode = gridApi.getRowNode(`${rowId}`);
5413
- if (!rowNode) {
5414
- return;
5415
- }
5416
- prePopupOps();
5417
- const shouldSelectNode = !rowNode.isSelected();
5418
- if (shouldSelectNode) {
5419
- externallySelectedItemsAreInSyncRef.current = false;
5420
- setExternallySelectedItemsAreInSync(false);
5421
- rowNode.setSelected(true, true);
5422
- await waitForExternallySelectedItemsToBeInSync();
5423
- }
5424
- // Cell already being edited, so don't re-edit until finished
5425
- if (checkUpdating([colDef.field ?? colDef.colId ?? ''], rowId)) {
5398
+ if (!gridApi || startCellEditingInProgressRef.current) {
5426
5399
  return;
5427
5400
  }
5428
- const rowIndex = rowNode.rowIndex;
5429
- if (rowIndex != null) {
5430
- lodashEs.defer(() => {
5431
- !gridApi.isDestroyed() &&
5432
- gridApi.startEditingCell({
5433
- rowIndex,
5434
- colKey: colId,
5435
- });
5436
- });
5401
+ startCellEditingInProgressRef.current = true;
5402
+ try {
5403
+ // Edit in progress so don't edit until finished, timeout waiting after 5s
5404
+ if (!(await waitForCondition(() => !anyUpdating(), 5000))) {
5405
+ console.error("Could not start edit because previous edit hasn't finished after 5 seconds");
5406
+ return;
5407
+ }
5408
+ const colDef = gridApi.getColumnDef(colId);
5409
+ if (!colDef) {
5410
+ return;
5411
+ }
5412
+ const rowNode = gridApi.getRowNode(`${rowId}`);
5413
+ if (!rowNode) {
5414
+ return;
5415
+ }
5416
+ prePopupOps();
5417
+ const shouldSelectNode = !rowNode.isSelected();
5418
+ if (shouldSelectNode) {
5419
+ externallySelectedItemsAreInSyncRef.current = false;
5420
+ setExternallySelectedItemsAreInSync(false);
5421
+ rowNode.setSelected(true, true);
5422
+ await waitForExternallySelectedItemsToBeInSync();
5423
+ }
5424
+ const rowIndex = rowNode.rowIndex;
5425
+ if (rowIndex != null) {
5426
+ lodashEs.defer(() => {
5427
+ !gridApi.isDestroyed() &&
5428
+ gridApi.startEditingCell({
5429
+ rowIndex,
5430
+ colKey: colId,
5431
+ });
5432
+ });
5433
+ }
5437
5434
  }
5438
- }, [checkUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
5439
- /**
5440
- * This differs from stopEdit in that it will also invoke cellEditingCompleteCallback
5441
- */
5442
- const cancelEdit = React.useCallback(() => {
5443
- stopEditing();
5444
- cellEditingCompleteCallbackRef.current?.();
5445
- }, [stopEditing]);
5446
- const cellEditingCompleteCallbackRef = React.useRef();
5447
- const setOnCellEditingComplete = React.useCallback((cellEditingCompleteCallback) => {
5448
- cellEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
5435
+ finally {
5436
+ startCellEditingInProgressRef.current = false;
5437
+ }
5438
+ }, [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
5439
+ const bulkEditingCompleteCallbackRef = React.useRef();
5440
+ const onBulkEditingComplete = React.useCallback(() => {
5441
+ resetFocusedCellAfterCellEditing();
5442
+ bulkEditingCompleteCallbackRef.current?.();
5443
+ }, [resetFocusedCellAfterCellEditing]);
5444
+ const setOnBulkEditingComplete = React.useCallback((cellEditingCompleteCallback) => {
5445
+ bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
5449
5446
  }, []);
5450
5447
  /**
5451
5448
  * Returns true if an editable cell on same row was selected, else false.
5452
5449
  */
5453
5450
  const selectNextEditableCell = React.useCallback(async (tabDirection) => {
5454
5451
  // Pretend it succeeded to prevent unwanted cellEditingCompleteCallback
5455
- if (!gridApi)
5452
+ if (!gridApi) {
5456
5453
  return true;
5454
+ }
5457
5455
  const focusedCellIsEditable = () => {
5458
5456
  const focusedCell = gridApi.isDestroyed() ? null : gridApi.getFocusedCell();
5459
5457
  const nextColumn = focusedCell?.column;
@@ -5469,16 +5467,16 @@ const GridContextProvider = (props) => {
5469
5467
  if (gridApi.isDestroyed()) {
5470
5468
  return true;
5471
5469
  }
5470
+ // Prevent resetting focus to original editing cell
5471
+ prePopupFocusedCell.current = undefined;
5472
5472
  const preRow = gridApi.getFocusedCell();
5473
5473
  if (tabDirection === 1) {
5474
+ gridApi.stopEditing();
5474
5475
  gridApi.tabToNextCell();
5475
5476
  }
5476
5477
  else {
5477
5478
  gridApi.tabToPreviousCell();
5478
5479
  }
5479
- // If we don't wait the tab to next element won't work
5480
- // I think this is due to the grid resizing
5481
- await wait(150);
5482
5480
  if (gridApi.isDestroyed()) {
5483
5481
  return true;
5484
5482
  }
@@ -5491,64 +5489,71 @@ const GridContextProvider = (props) => {
5491
5489
  if (focusedCellIsEditable()) {
5492
5490
  const focusedCell = gridApi.getFocusedCell();
5493
5491
  if (focusedCell) {
5494
- prePopupOps();
5495
- gridApi.startEditingCell({
5496
- rowIndex: focusedCell.rowIndex,
5497
- colKey: focusedCell.column.getColId(),
5498
- });
5492
+ const rowNode = gridApi.getDisplayedRowAtIndex(focusedCell.rowIndex);
5493
+ const rowId = rowNode?.data?.id;
5494
+ if (rowId == null) {
5495
+ return false;
5496
+ }
5497
+ await startCellEditing({ rowId, colId: focusedCell.column.getColId() });
5499
5498
  return true;
5500
5499
  }
5501
5500
  }
5502
5501
  }
5503
5502
  return false;
5504
- }, [gridApi, prePopupOps]);
5503
+ }, [gridApi, startCellEditing]);
5505
5504
  const updatingCells = React.useCallback(async (props, fnUpdate, setSaving, tabDirection) => {
5506
- setSaving && setSaving(true);
5507
- return await gridApiOp(async (gridApi) => {
5508
- const selectedRows = props.selectedRows;
5509
- let ok = false;
5510
- await modifyUpdating(props.field ?? '', selectedRows.map((data) => data.id), async () => {
5505
+ try {
5506
+ setSaving?.(true);
5507
+ return await gridApiOp(async (gridApi) => {
5508
+ const selectedRows = props.selectedRows;
5509
+ let ok = false;
5510
+ await modifyUpdating(props.field ?? '', selectedRows.map((data) => data.id), async () => {
5511
+ // MATT Disabled I don't believe these are needed anymore
5512
+ // I've left them here just in case they are
5513
+ // Need to refresh to get spinners to work on all rows
5514
+ // gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
5515
+ ok = await fnUpdate(selectedRows).catch((ex) => {
5516
+ console.error('Exception during modifyUpdating', ex);
5517
+ return false;
5518
+ });
5519
+ });
5511
5520
  // MATT Disabled I don't believe these are needed anymore
5512
5521
  // I've left them here just in case they are
5513
- // Need to refresh to get spinners to work on all rows
5514
- // gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
5515
- ok = await fnUpdate(selectedRows).catch((ex) => {
5516
- console.error('Exception during modifyUpdating', ex);
5517
- return false;
5518
- });
5519
- });
5520
- // MATT Disabled I don't believe these are needed anymore
5521
- // I've left them here just in case they are
5522
- // async processes need to refresh their own rows
5523
- // gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
5524
- if (gridApi.isDestroyed()) {
5525
- return ok;
5526
- }
5527
- if (ok) {
5528
- const cell = gridApi.getFocusedCell();
5529
- if (cell && gridApi.getFocusedCell() == null) {
5530
- !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
5522
+ // async processes need to refresh their own rows
5523
+ // gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
5524
+ if (gridApi.isDestroyed()) {
5525
+ return ok;
5531
5526
  }
5532
- // This is needed to trigger postSortRowsHook
5533
- gridApi.refreshClientSideRowModel();
5534
- }
5535
- else {
5536
- // Don't set saving if ok as the form has already closed
5537
- setSaving?.(false);
5538
- }
5539
- // Only focus next cell if user hasn't already manually changed focus
5540
- const postPopupFocusedCell = gridApi.getFocusedCell();
5541
- if (prePopupFocusedCell.current &&
5542
- postPopupFocusedCell &&
5543
- prePopupFocusedCell.current.rowIndex == postPopupFocusedCell.rowIndex &&
5544
- prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()) {
5545
- if (!tabDirection || !(await selectNextEditableCell(tabDirection))) {
5546
- cellEditingCompleteCallbackRef.current && cellEditingCompleteCallbackRef.current();
5527
+ if (ok) {
5528
+ const cell = gridApi.getFocusedCell();
5529
+ if (cell && gridApi.getFocusedCell() == null) {
5530
+ !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
5531
+ }
5532
+ // This is needed to trigger postSortRowsHook
5533
+ gridApi.refreshClientSideRowModel();
5547
5534
  }
5548
- }
5549
- return ok;
5550
- });
5551
- }, [gridApiOp, modifyUpdating, selectNextEditableCell]);
5535
+ void (async () => {
5536
+ // Only focus next cell if user hasn't already manually changed focus
5537
+ const postPopupFocusedCell = gridApi.getFocusedCell();
5538
+ if (prePopupFocusedCell.current &&
5539
+ postPopupFocusedCell &&
5540
+ prePopupFocusedCell.current.rowIndex == postPopupFocusedCell.rowIndex &&
5541
+ prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()) {
5542
+ if (!tabDirection || !(await selectNextEditableCell(tabDirection))) {
5543
+ onBulkEditingComplete();
5544
+ }
5545
+ }
5546
+ else {
5547
+ onBulkEditingComplete();
5548
+ }
5549
+ })();
5550
+ return ok;
5551
+ });
5552
+ }
5553
+ finally {
5554
+ setSaving?.(false);
5555
+ }
5556
+ }, [gridApiOp, modifyUpdating, onBulkEditingComplete, selectNextEditableCell]);
5552
5557
  const redrawRows = React.useMemo(() => debounce((rowNodes) => {
5553
5558
  try {
5554
5559
  gridApi && gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
@@ -5579,16 +5584,16 @@ const GridContextProvider = (props) => {
5579
5584
  preHash !== postHash && gridApi.clearFocusedCell();
5580
5585
  }
5581
5586
  }, 200), [gridApi]);
5582
- const addExternalFilter = (filter) => {
5587
+ const addExternalFilter = React.useCallback((filter) => {
5583
5588
  externalFilters.current.push(filter);
5584
5589
  void onFilterChanged();
5585
- };
5586
- const removeExternalFilter = (filter) => {
5590
+ }, [onFilterChanged]);
5591
+ const removeExternalFilter = React.useCallback((filter) => {
5587
5592
  lodashEs.remove(externalFilters.current, (v) => v === filter);
5588
5593
  void onFilterChanged();
5589
- };
5590
- const isExternalFilterPresent = () => !lodashEs.isEmpty(externalFilters.current);
5591
- const doesExternalFilterPass = (node) => externalFilters.current.every((filter) => filter(node.data, node));
5594
+ }, [onFilterChanged]);
5595
+ const isExternalFilterPresent = React.useCallback(() => !lodashEs.isEmpty(externalFilters.current), []);
5596
+ const doesExternalFilterPass = React.useCallback((node) => externalFilters.current.every((filter) => filter(node.data, node)), []);
5592
5597
  const getColDef = React.useCallback((colId) => (!!colId && gridApi?.getColumnDef(colId)) || undefined, [gridApi]);
5593
5598
  const showNoRowsOverlay = React.useCallback(() => {
5594
5599
  gridApi?.showNoRowsOverlay();
@@ -5644,7 +5649,6 @@ const GridContextProvider = (props) => {
5644
5649
  setInvisibleColumnIds,
5645
5650
  gridReady,
5646
5651
  prePopupOps,
5647
- postPopupOps,
5648
5652
  setApis,
5649
5653
  setQuickFilter,
5650
5654
  selectRowsById,
@@ -5665,8 +5669,7 @@ const GridContextProvider = (props) => {
5665
5669
  sizeColumnsToFit,
5666
5670
  autoSizeColumns,
5667
5671
  startCellEditing,
5668
- stopEditing,
5669
- cancelEdit,
5672
+ resetFocusedCellAfterCellEditing,
5670
5673
  updatingCells,
5671
5674
  redrawRows,
5672
5675
  externallySelectedItemsAreInSync,
@@ -5677,7 +5680,8 @@ const GridContextProvider = (props) => {
5677
5680
  isExternalFilterPresent,
5678
5681
  doesExternalFilterPass,
5679
5682
  downloadCsv,
5680
- setOnCellEditingComplete,
5683
+ onBulkEditingComplete,
5684
+ setOnBulkEditingComplete,
5681
5685
  showNoRowsOverlay,
5682
5686
  }, children: props.children }));
5683
5687
  };
@@ -5724,12 +5728,23 @@ const downloadCsvUseValueFormattersProcessCellCallback = (params) => {
5724
5728
  // We add an extra encodeToString here just in case valueFormatter is returning non string values
5725
5729
  return encodeToString(result);
5726
5730
  };
5731
+ const waitForCondition = async (condition, timeoutMs) => {
5732
+ const endTime = Date.now() + timeoutMs;
5733
+ while (Date.now() < endTime) {
5734
+ if (condition()) {
5735
+ return true;
5736
+ }
5737
+ await wait(100);
5738
+ }
5739
+ console.warn('waitForCondition failed');
5740
+ return false;
5741
+ };
5727
5742
 
5728
5743
  const GridUpdatingContextProvider = (props) => {
5729
5744
  const updatingBlocks = React.useRef({});
5730
5745
  const updating = React.useRef({});
5731
5746
  const [updatedDep, setUpdatedDep] = React.useState(0);
5732
- const resetUpdating = () => {
5747
+ const resetUpdating = React.useCallback(() => {
5733
5748
  const mergedUpdatingBlocks = {};
5734
5749
  for (const key in updatingBlocks.current) {
5735
5750
  const arr = lodashEs.flatten(updatingBlocks.current[key]);
@@ -5739,9 +5754,9 @@ const GridUpdatingContextProvider = (props) => {
5739
5754
  }
5740
5755
  updating.current = mergedUpdatingBlocks;
5741
5756
  setUpdatedDep((updatedDep) => updatedDep + 1);
5742
- };
5743
- const updatingCols = () => Object.keys(updating.current);
5744
- const modifyUpdating = async (fields, ids, fn) => {
5757
+ }, []);
5758
+ const updatingCols = React.useCallback(() => Object.keys(updating.current), []);
5759
+ const modifyUpdating = React.useCallback(async (fields, ids, fn) => {
5745
5760
  const idRef = [...ids];
5746
5761
  lodashEs.castArray(fields).forEach((field) => {
5747
5762
  const fieldUpdatingIds = updatingBlocks.current[field] ?? (updatingBlocks.current[field] = []);
@@ -5754,9 +5769,12 @@ const GridUpdatingContextProvider = (props) => {
5754
5769
  lodashEs.remove(fieldUpdatingIds, (idList) => idList === idRef);
5755
5770
  });
5756
5771
  resetUpdating();
5757
- };
5758
- const checkUpdating = (fields, id) => lodashEs.castArray(fields).some((f) => updating.current[f]?.includes(id));
5759
- return (jsxRuntime.jsx(GridUpdatingContext.Provider, { value: { modifyUpdating, checkUpdating, updatingCols, updatedDep }, children: props.children }));
5772
+ }, [resetUpdating]);
5773
+ const anyUpdating = React.useCallback(() => {
5774
+ return !lodashEs.isEmpty(updating.current);
5775
+ }, []);
5776
+ const checkUpdating = React.useCallback((fields, id) => lodashEs.castArray(fields).some((f) => updating.current[f]?.includes(id)), []);
5777
+ return (jsxRuntime.jsx(GridUpdatingContext.Provider, { value: { modifyUpdating, anyUpdating, checkUpdating, updatingCols, updatedDep }, children: props.children }));
5760
5778
  };
5761
5779
 
5762
5780
  var css_248z = ".ActionButton{align-items:center;display:flex}.ActionButton-inProgress .LuiIcon{visibility:hidden}.ActionButton-minimal.lui-button-lg.lui-button-icon-right{padding-right:38px}.ActionButton.lui-button-lg.lui-button-icon-right:not(.ActionButton-tight) .LuiIcon{margin:0 4px}.ActionButton.ActionButton-tight.lui-button-lg.lui-button-icon-right .LuiIcon{margin:0}.ActionButton-iconOnly.lui-button-lg.lui-button-icon-right:not(.ActionButton-tight){padding:8px 5px}.ActionButton-iconOnly.lui-button-lg.lui-button-icon-right.ActionButton-tight{padding:0}.ActionButton-minimalArea{position:relative}.ActionButton-minimalAreaDisplay{position:absolute}.ActionButton-minimalAreaExpand{visibility:hidden}.ActionButton-inProgress.lui-button-lg.lui-button-icon-right{background-color:#e2f3f7;color:#007198;cursor:progress}.ActionButton-fill{justify-content:center;width:100%}";