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