@linzjs/step-ag-grid 28.5.1 → 29.0.1

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 (44) 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 +272 -250
  8. package/dist/step-ag-grid.cjs.map +1 -1
  9. package/dist/step-ag-grid.esm.js +273 -251
  10. package/dist/step-ag-grid.esm.js.map +1 -1
  11. package/package.json +21 -22
  12. package/src/components/Grid.tsx +114 -112
  13. package/src/components/GridCell.tsx +6 -2
  14. package/src/components/GridPopoverHook.tsx +25 -20
  15. package/src/components/gridFilter/useGridFilter.ts +3 -3
  16. package/src/components/gridForm/GridFormTextInput.tsx +6 -2
  17. package/src/components/gridHook/useGridContextMenu.tsx +3 -3
  18. package/src/contexts/GridContext.tsx +11 -14
  19. package/src/contexts/GridContextProvider.tsx +164 -137
  20. package/src/contexts/GridPopoverContext.tsx +2 -0
  21. package/src/contexts/GridPopoverContextProvider.tsx +9 -5
  22. package/src/contexts/GridUpdatingContext.tsx +5 -0
  23. package/src/contexts/GridUpdatingContextProvider.tsx +35 -28
  24. package/src/lui/reactUtils.tsx +24 -0
  25. package/src/react-menu3/components/MenuList.tsx +3 -0
  26. package/src/stories/grid/GridFilterButtons.stories.tsx +1 -1
  27. package/src/stories/grid/GridNonEditableRow.stories.tsx +1 -1
  28. package/src/stories/grid/GridPinnedRow.stories.tsx +1 -1
  29. package/src/stories/grid/GridPopoutContextMenu.stories.tsx +3 -3
  30. package/src/stories/grid/GridPopoutEditGenericTextArea.stories.tsx +3 -3
  31. package/src/stories/grid/GridPopoverEditDropDown.stories.tsx +8 -4
  32. package/src/stories/grid/GridPopoverEditMultiSelect.stories.tsx +1 -0
  33. package/src/stories/grid/GridReadOnly.stories.tsx +1 -1
  34. package/src/stories/grid/GridViewList.stories.tsx +1 -1
  35. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +5 -3
  36. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +3 -2
  37. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +3 -2
  38. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectGridInteraction.stories.tsx +3 -2
  39. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +3 -2
  40. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +3 -2
  41. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +3 -2
  42. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +3 -2
  43. package/src/stories/grid/gridFormStatic/GridFormDropDown.stories.tsx +2 -2
  44. 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([]);
@@ -3001,26 +2980,10 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3001
2980
  const onModelUpdated = useCallback((event) => {
3002
2981
  event.api.showNoRowsOverlay();
3003
2982
  }, []);
3004
- /**
3005
- * Force-refresh all selected rows to re-run class function, to update selection highlighting
3006
- */
3007
- const refreshSelectedRows = useCallback((_event) => {
3008
- // MATT Disabled I don't believe these are needed anymore
3009
- // I've left them here just in case they are
3010
- /*event.api.refreshCells({
3011
- force: true,
3012
- rowNodes: event.api.getSelectedNodes(),
3013
- });*/
3014
- }, []);
3015
2983
  /**
3016
2984
  * Handle double click edit
3017
2985
  */
3018
2986
  const onCellDoubleClick = useCallback((event) => {
3019
- if (clickDebounceSkipClick(event.colDef.colId, event.rowIndex)) {
3020
- // the next click will be a single click, we want it to pass
3021
- resetClickDebounce();
3022
- return;
3023
- }
3024
2987
  const editable = fnOrVar(event.colDef?.editable, event);
3025
2988
  if (editable && !invokeEditAction(event)) {
3026
2989
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
@@ -3032,9 +2995,6 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3032
2995
  const onCellClicked = useCallback((event) => {
3033
2996
  const editable = fnOrVar(event.colDef?.editable, event);
3034
2997
  if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
3035
- if (clickDebounceSkipClick(event.colDef.colId, event.rowIndex)) {
3036
- return;
3037
- }
3038
2998
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
3039
2999
  }
3040
3000
  }, [singleClickEdit, startCellEditing]);
@@ -3130,11 +3090,10 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3130
3090
  useEffect(() => {
3131
3091
  const newLoading = !rowData || params.loading === true;
3132
3092
  if (newLoading && !prevLoading.current) {
3133
- stopEditing();
3134
3093
  showNoRowsOverlay();
3135
3094
  }
3136
3095
  prevLoading.current = newLoading;
3137
- }, [params.loading, rowData, showNoRowsOverlay, stopEditing]);
3096
+ }, [params.loading, rowData, showNoRowsOverlay]);
3138
3097
  /**
3139
3098
  * Resize columns to fit if required on window/container resize
3140
3099
  */
@@ -3196,6 +3155,23 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3196
3155
  });
3197
3156
  }
3198
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]);
3199
3175
  const onRowDragEnd = useCallback((event) => {
3200
3176
  clearHighlightRowClasses();
3201
3177
  gridElementRef.current = undefined;
@@ -3220,57 +3196,76 @@ const Grid = ({ 'data-testid': dataTestId, defaultPostSort = true, rowSelection
3220
3196
  }
3221
3197
  }, [params.setExternalSelectedItems, selectable]);
3222
3198
  // This is setting a ref in the GridContext so won't be triggering an update loop
3223
- setOnCellEditingComplete(params.onCellEditingComplete);
3224
- const selectWidth = params.hideSelectColumn ? 0 : selectable && params.onRowDragEnd ? 76 : 48;
3225
- 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
+ ]);
3226
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
3227
3252
  ? {
3228
3253
  enableSelectionWithoutKeys: params.enableSelectionWithoutKeys ?? false,
3229
3254
  enableClickSelection: params.enableClickSelection ?? false,
3230
3255
  mode: rowSelection == 'single' ? 'singleRow' : 'multiRow',
3231
3256
  }
3232
- : 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) => {
3233
- let rowCount = 0;
3234
- event.api.forEachNode(() => rowCount++);
3235
- return (jsx(GridNoRowsOverlay, { loading: !rowData || params.loading === true, rowCount: rowCount, headerRowHeight: headerRowCount * rowHeight, filteredRowCount: event.api.getDisplayedRowCount(), noRowsOverlayText: params.noRowsOverlayText, noRowsMatchingOverlayText: params.noRowsMatchingOverlayText }));
3236
- }, quickFilterParser: (filterStr) => {
3237
- // filter is exact matches exactly groups separated by commas
3238
- return filterStr.split(',').map((str) => str.trim());
3239
- }, 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: {
3240
- suppressNavigable: params.hideSelectColumn,
3241
- rowDrag: !!params.onRowDragEnd,
3242
- minWidth: selectWidth,
3243
- maxWidth: selectWidth,
3244
- pinned: selectColumnPinned,
3245
- headerComponentParams: {
3246
- exportable: false,
3247
- },
3248
- headerClass: clsx('ag-header-hide-default-select', params.onRowDragEnd && 'ag-header-select-draggable'),
3249
- headerComponent: rowSelection == 'multiple' ? GridHeaderSelect : undefined,
3250
- suppressHeaderKeyboardEvent: (e) => {
3251
- if (!selectable)
3252
- return false;
3253
- if ((e.event.key === 'Enter' || e.event.key === ' ') && !e.event.repeat) {
3254
- if (isEmpty(e.api.getSelectedRows())) {
3255
- e.api.selectAll('filtered');
3256
- }
3257
- else {
3258
- e.api.deselectAll();
3259
- }
3260
- return true;
3261
- }
3262
- return false;
3263
- },
3264
- onCellClicked: params.enableSelectionWithoutKeys || params.enableClickSelection
3265
- ? undefined
3266
- : clickInputWhenContainingCellClicked,
3267
- } }) })] }));
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());
3268
3262
  };
3269
3263
 
3270
3264
  const GridPopoverContext = createContext({
3271
3265
  anchorRef: { current: null },
3272
3266
  saving: false,
3273
3267
  setSaving: () => { },
3268
+ stopEditing: () => { },
3274
3269
  colId: '',
3275
3270
  field: '',
3276
3271
  value: null,
@@ -3288,7 +3283,7 @@ const GridPopoverContextProvider = (props2) => {
3288
3283
  const anchorRef = useRef(props.eGridCell);
3289
3284
  const hasSaved = useRef(false);
3290
3285
  const [saving, setSaving] = useState(false);
3291
- const { colDef } = props;
3286
+ const { colDef, stopEditing, formatValue, data, value } = props;
3292
3287
  const { cellEditorParams } = colDef;
3293
3288
  const multiEdit = cellEditorParams?.multiEdit ?? false;
3294
3289
  // Then item that is clicked on will always be first in the list
@@ -3296,8 +3291,10 @@ const GridPopoverContextProvider = (props2) => {
3296
3291
  const field = props.colDef?.field ?? '';
3297
3292
  const colId = props.colDef?.colId ?? field ?? '';
3298
3293
  const updateValue = useCallback(async (saveFn, tabDirection) => {
3299
- if (hasSaved.current)
3294
+ if (hasSaved.current) {
3295
+ // already called save so ignore
3300
3296
  return true;
3297
+ }
3301
3298
  hasSaved.current = true;
3302
3299
  return saving ? false : await updatingCells({ selectedRows, field }, saveFn, setSaving, tabDirection);
3303
3300
  }, [field, saving, selectedRows, updatingCells]);
@@ -3308,10 +3305,11 @@ const GridPopoverContextProvider = (props2) => {
3308
3305
  selectedRows,
3309
3306
  colId,
3310
3307
  field,
3311
- data: props.data,
3312
- value: props.value,
3308
+ data,
3309
+ value,
3313
3310
  updateValue,
3314
- formatValue: props.formatValue,
3311
+ formatValue,
3312
+ stopEditing,
3315
3313
  }, children: children }));
3316
3314
  };
3317
3315
 
@@ -3377,16 +3375,18 @@ const defaultValueFormatter = ({ value }) => {
3377
3375
  ? String(value)
3378
3376
  : JSON.stringify(value);
3379
3377
  };
3378
+ // ag-grid doesn't understand the custom editor, when it stops editing it thinks
3379
+ // that _it_ was editing and overwrites the user updated data
3380
+ // _but_, it only does this if the initial value of the cell was null!?
3381
+ const blockValueSetter = () => true;
3380
3382
  /*
3381
3383
  * All cells should use this.
3382
3384
  */
3383
3385
  const GridCell = (props, custom) => {
3384
- // props.field = ;
3385
3386
  // Generate a default filter value getter which uses the formatted value plus
3386
3387
  // the editable value if it's a string and different from the formatted value.
3387
3388
  // This is so that e.g. bearings can be searched for by DMS or raw number.
3388
3389
  const valueFormatter = props.valueFormatter ?? defaultValueFormatter;
3389
- // FIXME
3390
3390
  const filterValueGetter = props.filterValueGetter ?? generateFilterGetter(valueFormatter);
3391
3391
  const exportable = props.exportable;
3392
3392
  // Can't leave this here ag-grid will complain
@@ -3397,6 +3397,7 @@ const GridCell = (props, custom) => {
3397
3397
  headerTooltip: props.headerName,
3398
3398
  sortable: true,
3399
3399
  resizable: true,
3400
+ valueSetter: custom?.editor ? blockValueSetter : undefined,
3400
3401
  editable: props.editable ?? false,
3401
3402
  ...(custom?.editor && {
3402
3403
  cellClassRules: GridCellMultiSelectClassRules,
@@ -3461,7 +3462,7 @@ const GridCellMultiEditor = (props, cellEditorSelector) => GridCell({
3461
3462
  });
3462
3463
 
3463
3464
  const useGridFilter = (filter) => {
3464
- const { addExternalFilter, removeExternalFilter } = useContext(GridContext);
3465
+ const { addExternalFilter, removeExternalFilter } = useGridContext();
3465
3466
  useEffect(() => {
3466
3467
  const thisFilter = filter;
3467
3468
  thisFilter && addExternalFilter(thisFilter);
@@ -3936,8 +3937,8 @@ const textMatch = (text, filter) => {
3936
3937
  };
3937
3938
 
3938
3939
  const useGridPopoverHook = (props) => {
3939
- const { stopEditing, cancelEdit } = useContext(GridContext);
3940
- const { anchorRef, saving, updateValue } = useGridPopoverContext();
3940
+ const { onBulkEditingComplete } = useGridContext();
3941
+ const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext();
3941
3942
  const saveButtonRef = useRef(null);
3942
3943
  const [isOpen, setOpen] = useState(false);
3943
3944
  useEffect(() => {
@@ -3945,30 +3946,30 @@ const useGridPopoverHook = (props) => {
3945
3946
  }, []);
3946
3947
  const triggerSave = useCallback(async (reason) => {
3947
3948
  if (reason == CloseReason.CANCEL) {
3948
- cancelEdit();
3949
+ stopEditing();
3950
+ onBulkEditingComplete();
3949
3951
  return;
3950
3952
  }
3951
- if (props.invalid && props.invalid()) {
3953
+ if (props?.invalid?.()) {
3954
+ // Don't close, don't do anything it's invalid
3952
3955
  return;
3953
3956
  }
3954
3957
  if (!props.save) {
3955
- cancelEdit();
3956
- }
3957
- else if (props.save) {
3958
- // forms that don't provide an invalid fn must wait until they have saved to close
3959
- if (props.invalid)
3960
- stopEditing();
3961
- if (await updateValue(props.save, reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
3962
- if (!props.invalid)
3963
- stopEditing();
3964
- }
3958
+ // No save method so just close
3959
+ stopEditing();
3960
+ onBulkEditingComplete();
3961
+ return;
3965
3962
  }
3966
- }, [cancelEdit, props, stopEditing, updateValue]);
3963
+ if (await updateValue(props.save, reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
3964
+ stopEditing();
3965
+ }
3966
+ }, [onBulkEditingComplete, props, stopEditing, updateValue]);
3967
3967
  const popoverWrapper = useCallback((children) => {
3968
3968
  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) => {
3969
3969
  // Prevent menu from closing when modals are invoked
3970
- if (event.reason === CloseReason.BLUR)
3970
+ if (event.reason === CloseReason.BLUR) {
3971
3971
  return;
3972
+ }
3972
3973
  void triggerSave(event.reason);
3973
3974
  }, viewScroll: 'auto', dontShrinkIfDirectionIsTop: true, className: props.className, children: [saving && ( // This is the overlay that prevents editing when the editor is saving
3974
3975
  jsx("div", { className: 'ComponentLoadingWrapper-saveOverlay' })), children, jsx("button", { ref: saveButtonRef, "data-reason": '', onClick: (e) => {
@@ -4836,12 +4837,14 @@ const GridFormTextInput = (props) => {
4836
4837
  const [value, setValue] = useState(initValue);
4837
4838
  const invalid = useCallback(() => TextInputValidator(props, value, data, {}), [data, props, value]);
4838
4839
  const save = useCallback(async (selectedRows) => {
4839
- if (invalid())
4840
+ if (invalid()) {
4840
4841
  return false;
4842
+ }
4841
4843
  const trimmedValue = value.trim();
4842
4844
  // No change, so don't save
4843
- if (initValue === trimmedValue)
4845
+ if (initValue === trimmedValue) {
4844
4846
  return true;
4847
+ }
4845
4848
  if (props.onSave) {
4846
4849
  return await props.onSave({ selectedRows, value: trimmedValue });
4847
4850
  }
@@ -5062,7 +5065,7 @@ const isFlexColumn = (colDef) => !!colDef.flex && !isGridCellFiller(colDef);
5062
5065
  * Also, make sure the provider is created in a separate component, otherwise it won't be found.
5063
5066
  */
5064
5067
  const GridContextProvider = (props) => {
5065
- const { modifyUpdating, checkUpdating } = useContext(GridUpdatingContext);
5068
+ const { modifyUpdating, anyUpdating } = useContext(GridUpdatingContext);
5066
5069
  const [gridApi, setGridApi] = useState();
5067
5070
  const [gridReady, setGridReady] = useState(false);
5068
5071
  const [quickFilter, _setQuickFilter] = useState('');
@@ -5081,7 +5084,7 @@ const GridContextProvider = (props) => {
5081
5084
  /**
5082
5085
  * Make extra sure the GridCellFillerColId never gets added to invisibleColumnIds as it's dynamically determined
5083
5086
  */
5084
- const setInvisibleColumnIds = (invisibleColumnIds) => _setInvisibleColumnIds(pull(invisibleColumnIds, GridCellFillerColId));
5087
+ const setInvisibleColumnIds = useCallback((invisibleColumnIds) => _setInvisibleColumnIds(pull(invisibleColumnIds, GridCellFillerColId)), []);
5085
5088
  /**
5086
5089
  * Set quick filter directly on grid, based on previously save quickFilter state.
5087
5090
  */
@@ -5176,17 +5179,6 @@ const GridContextProvider = (props) => {
5176
5179
  }
5177
5180
  prePopupFocusedCell.current = gridApi.getFocusedCell() ?? undefined;
5178
5181
  }, [gridApi]);
5179
- /**
5180
- * After a popup refocus the cell.
5181
- */
5182
- const postPopupOps = useCallback(() => {
5183
- if (!gridApi || gridApi.isDestroyed()) {
5184
- return;
5185
- }
5186
- if (prePopupFocusedCell.current) {
5187
- gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
5188
- }
5189
- }, [gridApi]);
5190
5182
  /**
5191
5183
  * Get all row id's in grid.
5192
5184
  */
@@ -5374,13 +5366,16 @@ const GridContextProvider = (props) => {
5374
5366
  gridApi.sizeColumnsToFit();
5375
5367
  }
5376
5368
  }, [gridApi]);
5377
- const stopEditing = useCallback(() => {
5378
- if (!gridApi || gridApi.isDestroyed()) {
5369
+ /**
5370
+ *
5371
+ */
5372
+ const resetFocusedCellAfterCellEditing = useCallback(() => {
5373
+ if (!gridApi || gridApi.isDestroyed() || startCellEditingInProgressRef.current) {
5379
5374
  return;
5380
5375
  }
5381
- gridApi.stopEditing();
5382
5376
  if (prePopupFocusedCell.current) {
5383
5377
  gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
5378
+ prePopupFocusedCell.current = undefined;
5384
5379
  }
5385
5380
  }, [gridApi]);
5386
5381
  // waitForExternallySelectedItemsToBeInSync can't use the state as it won't be updated during function execution
@@ -5401,58 +5396,65 @@ const GridContextProvider = (props) => {
5401
5396
  console.error('externallySelectedItems did not sync with ag-grid selection');
5402
5397
  }
5403
5398
  }, []);
5399
+ const startCellEditingInProgressRef = useRef(false);
5404
5400
  const startCellEditing = useCallback(async ({ rowId, colId }) => {
5405
- if (!gridApi)
5406
- return;
5407
- const colDef = gridApi.getColumnDef(colId);
5408
- if (!colDef) {
5409
- return;
5410
- }
5411
- const rowNode = gridApi.getRowNode(`${rowId}`);
5412
- if (!rowNode) {
5401
+ if (!gridApi || startCellEditingInProgressRef.current) {
5413
5402
  return;
5414
5403
  }
5415
- prePopupOps();
5416
- const shouldSelectNode = !rowNode.isSelected();
5417
- if (shouldSelectNode) {
5418
- externallySelectedItemsAreInSyncRef.current = false;
5419
- setExternallySelectedItemsAreInSync(false);
5420
- rowNode.setSelected(true, true);
5421
- await waitForExternallySelectedItemsToBeInSync();
5422
- }
5423
- // Cell already being edited, so don't re-edit until finished
5424
- if (checkUpdating([colDef.field ?? colDef.colId ?? ''], rowId)) {
5425
- return;
5426
- }
5427
- const rowIndex = rowNode.rowIndex;
5428
- if (rowIndex != null) {
5429
- defer(() => {
5430
- !gridApi.isDestroyed() &&
5431
- gridApi.startEditingCell({
5432
- rowIndex,
5433
- colKey: colId,
5434
- });
5435
- });
5404
+ startCellEditingInProgressRef.current = true;
5405
+ try {
5406
+ // Edit in progress so don't edit until finished, timeout waiting after 5s
5407
+ if (!(await waitForCondition(() => !anyUpdating(), 5000))) {
5408
+ console.error("Could not start edit because previous edit hasn't finished after 5 seconds");
5409
+ return;
5410
+ }
5411
+ const colDef = gridApi.getColumnDef(colId);
5412
+ if (!colDef) {
5413
+ return;
5414
+ }
5415
+ const rowNode = gridApi.getRowNode(`${rowId}`);
5416
+ if (!rowNode) {
5417
+ return;
5418
+ }
5419
+ prePopupOps();
5420
+ const shouldSelectNode = !rowNode.isSelected();
5421
+ if (shouldSelectNode) {
5422
+ externallySelectedItemsAreInSyncRef.current = false;
5423
+ setExternallySelectedItemsAreInSync(false);
5424
+ rowNode.setSelected(true, true);
5425
+ await waitForExternallySelectedItemsToBeInSync();
5426
+ }
5427
+ const rowIndex = rowNode.rowIndex;
5428
+ if (rowIndex != null) {
5429
+ defer(() => {
5430
+ !gridApi.isDestroyed() &&
5431
+ gridApi.startEditingCell({
5432
+ rowIndex,
5433
+ colKey: colId,
5434
+ });
5435
+ });
5436
+ }
5436
5437
  }
5437
- }, [checkUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
5438
- /**
5439
- * This differs from stopEdit in that it will also invoke cellEditingCompleteCallback
5440
- */
5441
- const cancelEdit = useCallback(() => {
5442
- stopEditing();
5443
- cellEditingCompleteCallbackRef.current?.();
5444
- }, [stopEditing]);
5445
- const cellEditingCompleteCallbackRef = useRef();
5446
- const setOnCellEditingComplete = useCallback((cellEditingCompleteCallback) => {
5447
- cellEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
5438
+ finally {
5439
+ startCellEditingInProgressRef.current = false;
5440
+ }
5441
+ }, [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
5442
+ const bulkEditingCompleteCallbackRef = useRef();
5443
+ const onBulkEditingComplete = useCallback(() => {
5444
+ resetFocusedCellAfterCellEditing();
5445
+ bulkEditingCompleteCallbackRef.current?.();
5446
+ }, [resetFocusedCellAfterCellEditing]);
5447
+ const setOnBulkEditingComplete = useCallback((cellEditingCompleteCallback) => {
5448
+ bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
5448
5449
  }, []);
5449
5450
  /**
5450
5451
  * Returns true if an editable cell on same row was selected, else false.
5451
5452
  */
5452
5453
  const selectNextEditableCell = useCallback(async (tabDirection) => {
5453
5454
  // Pretend it succeeded to prevent unwanted cellEditingCompleteCallback
5454
- if (!gridApi)
5455
+ if (!gridApi) {
5455
5456
  return true;
5457
+ }
5456
5458
  const focusedCellIsEditable = () => {
5457
5459
  const focusedCell = gridApi.isDestroyed() ? null : gridApi.getFocusedCell();
5458
5460
  const nextColumn = focusedCell?.column;
@@ -5468,16 +5470,16 @@ const GridContextProvider = (props) => {
5468
5470
  if (gridApi.isDestroyed()) {
5469
5471
  return true;
5470
5472
  }
5473
+ // Prevent resetting focus to original editing cell
5474
+ prePopupFocusedCell.current = undefined;
5471
5475
  const preRow = gridApi.getFocusedCell();
5472
5476
  if (tabDirection === 1) {
5477
+ gridApi.stopEditing();
5473
5478
  gridApi.tabToNextCell();
5474
5479
  }
5475
5480
  else {
5476
5481
  gridApi.tabToPreviousCell();
5477
5482
  }
5478
- // If we don't wait the tab to next element won't work
5479
- // I think this is due to the grid resizing
5480
- await wait(150);
5481
5483
  if (gridApi.isDestroyed()) {
5482
5484
  return true;
5483
5485
  }
@@ -5490,64 +5492,71 @@ const GridContextProvider = (props) => {
5490
5492
  if (focusedCellIsEditable()) {
5491
5493
  const focusedCell = gridApi.getFocusedCell();
5492
5494
  if (focusedCell) {
5493
- prePopupOps();
5494
- gridApi.startEditingCell({
5495
- rowIndex: focusedCell.rowIndex,
5496
- colKey: focusedCell.column.getColId(),
5497
- });
5495
+ const rowNode = gridApi.getDisplayedRowAtIndex(focusedCell.rowIndex);
5496
+ const rowId = rowNode?.data?.id;
5497
+ if (rowId == null) {
5498
+ return false;
5499
+ }
5500
+ await startCellEditing({ rowId, colId: focusedCell.column.getColId() });
5498
5501
  return true;
5499
5502
  }
5500
5503
  }
5501
5504
  }
5502
5505
  return false;
5503
- }, [gridApi, prePopupOps]);
5506
+ }, [gridApi, startCellEditing]);
5504
5507
  const updatingCells = useCallback(async (props, fnUpdate, setSaving, tabDirection) => {
5505
- setSaving && setSaving(true);
5506
- return await gridApiOp(async (gridApi) => {
5507
- const selectedRows = props.selectedRows;
5508
- let ok = false;
5509
- await modifyUpdating(props.field ?? '', selectedRows.map((data) => data.id), async () => {
5508
+ try {
5509
+ setSaving?.(true);
5510
+ return await gridApiOp(async (gridApi) => {
5511
+ const selectedRows = props.selectedRows;
5512
+ let ok = false;
5513
+ await modifyUpdating(props.field ?? '', selectedRows.map((data) => data.id), async () => {
5514
+ // MATT Disabled I don't believe these are needed anymore
5515
+ // I've left them here just in case they are
5516
+ // Need to refresh to get spinners to work on all rows
5517
+ // gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
5518
+ ok = await fnUpdate(selectedRows).catch((ex) => {
5519
+ console.error('Exception during modifyUpdating', ex);
5520
+ return false;
5521
+ });
5522
+ });
5510
5523
  // MATT Disabled I don't believe these are needed anymore
5511
5524
  // I've left them here just in case they are
5512
- // Need to refresh to get spinners to work on all rows
5513
- // gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
5514
- ok = await fnUpdate(selectedRows).catch((ex) => {
5515
- console.error('Exception during modifyUpdating', ex);
5516
- return false;
5517
- });
5518
- });
5519
- // MATT Disabled I don't believe these are needed anymore
5520
- // I've left them here just in case they are
5521
- // async processes need to refresh their own rows
5522
- // gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
5523
- if (gridApi.isDestroyed()) {
5524
- return ok;
5525
- }
5526
- if (ok) {
5527
- const cell = gridApi.getFocusedCell();
5528
- if (cell && gridApi.getFocusedCell() == null) {
5529
- !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
5525
+ // async processes need to refresh their own rows
5526
+ // gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
5527
+ if (gridApi.isDestroyed()) {
5528
+ return ok;
5530
5529
  }
5531
- // This is needed to trigger postSortRowsHook
5532
- gridApi.refreshClientSideRowModel();
5533
- }
5534
- else {
5535
- // Don't set saving if ok as the form has already closed
5536
- setSaving?.(false);
5537
- }
5538
- // Only focus next cell if user hasn't already manually changed focus
5539
- const postPopupFocusedCell = gridApi.getFocusedCell();
5540
- if (prePopupFocusedCell.current &&
5541
- postPopupFocusedCell &&
5542
- prePopupFocusedCell.current.rowIndex == postPopupFocusedCell.rowIndex &&
5543
- prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()) {
5544
- if (!tabDirection || !(await selectNextEditableCell(tabDirection))) {
5545
- cellEditingCompleteCallbackRef.current && cellEditingCompleteCallbackRef.current();
5530
+ if (ok) {
5531
+ const cell = gridApi.getFocusedCell();
5532
+ if (cell && gridApi.getFocusedCell() == null) {
5533
+ !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
5534
+ }
5535
+ // This is needed to trigger postSortRowsHook
5536
+ gridApi.refreshClientSideRowModel();
5546
5537
  }
5547
- }
5548
- return ok;
5549
- });
5550
- }, [gridApiOp, modifyUpdating, selectNextEditableCell]);
5538
+ void (async () => {
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
+ onBulkEditingComplete();
5547
+ }
5548
+ }
5549
+ else {
5550
+ onBulkEditingComplete();
5551
+ }
5552
+ })();
5553
+ return ok;
5554
+ });
5555
+ }
5556
+ finally {
5557
+ setSaving?.(false);
5558
+ }
5559
+ }, [gridApiOp, modifyUpdating, onBulkEditingComplete, selectNextEditableCell]);
5551
5560
  const redrawRows = useMemo(() => debounce((rowNodes) => {
5552
5561
  try {
5553
5562
  gridApi && gridApi.redrawRows(rowNodes ? { rowNodes } : undefined);
@@ -5578,16 +5587,16 @@ const GridContextProvider = (props) => {
5578
5587
  preHash !== postHash && gridApi.clearFocusedCell();
5579
5588
  }
5580
5589
  }, 200), [gridApi]);
5581
- const addExternalFilter = (filter) => {
5590
+ const addExternalFilter = useCallback((filter) => {
5582
5591
  externalFilters.current.push(filter);
5583
5592
  void onFilterChanged();
5584
- };
5585
- const removeExternalFilter = (filter) => {
5593
+ }, [onFilterChanged]);
5594
+ const removeExternalFilter = useCallback((filter) => {
5586
5595
  remove(externalFilters.current, (v) => v === filter);
5587
5596
  void onFilterChanged();
5588
- };
5589
- const isExternalFilterPresent = () => !isEmpty(externalFilters.current);
5590
- const doesExternalFilterPass = (node) => externalFilters.current.every((filter) => filter(node.data, node));
5597
+ }, [onFilterChanged]);
5598
+ const isExternalFilterPresent = useCallback(() => !isEmpty(externalFilters.current), []);
5599
+ const doesExternalFilterPass = useCallback((node) => externalFilters.current.every((filter) => filter(node.data, node)), []);
5591
5600
  const getColDef = useCallback((colId) => (!!colId && gridApi?.getColumnDef(colId)) || undefined, [gridApi]);
5592
5601
  const showNoRowsOverlay = useCallback(() => {
5593
5602
  gridApi?.showNoRowsOverlay();
@@ -5643,7 +5652,6 @@ const GridContextProvider = (props) => {
5643
5652
  setInvisibleColumnIds,
5644
5653
  gridReady,
5645
5654
  prePopupOps,
5646
- postPopupOps,
5647
5655
  setApis,
5648
5656
  setQuickFilter,
5649
5657
  selectRowsById,
@@ -5664,8 +5672,7 @@ const GridContextProvider = (props) => {
5664
5672
  sizeColumnsToFit,
5665
5673
  autoSizeColumns,
5666
5674
  startCellEditing,
5667
- stopEditing,
5668
- cancelEdit,
5675
+ resetFocusedCellAfterCellEditing,
5669
5676
  updatingCells,
5670
5677
  redrawRows,
5671
5678
  externallySelectedItemsAreInSync,
@@ -5676,7 +5683,8 @@ const GridContextProvider = (props) => {
5676
5683
  isExternalFilterPresent,
5677
5684
  doesExternalFilterPass,
5678
5685
  downloadCsv,
5679
- setOnCellEditingComplete,
5686
+ onBulkEditingComplete,
5687
+ setOnBulkEditingComplete,
5680
5688
  showNoRowsOverlay,
5681
5689
  }, children: props.children }));
5682
5690
  };
@@ -5723,12 +5731,23 @@ const downloadCsvUseValueFormattersProcessCellCallback = (params) => {
5723
5731
  // We add an extra encodeToString here just in case valueFormatter is returning non string values
5724
5732
  return encodeToString(result);
5725
5733
  };
5734
+ const waitForCondition = async (condition, timeoutMs) => {
5735
+ const endTime = Date.now() + timeoutMs;
5736
+ while (Date.now() < endTime) {
5737
+ if (condition()) {
5738
+ return true;
5739
+ }
5740
+ await wait(100);
5741
+ }
5742
+ console.warn('waitForCondition failed');
5743
+ return false;
5744
+ };
5726
5745
 
5727
5746
  const GridUpdatingContextProvider = (props) => {
5728
5747
  const updatingBlocks = useRef({});
5729
5748
  const updating = useRef({});
5730
5749
  const [updatedDep, setUpdatedDep] = useState(0);
5731
- const resetUpdating = () => {
5750
+ const resetUpdating = useCallback(() => {
5732
5751
  const mergedUpdatingBlocks = {};
5733
5752
  for (const key in updatingBlocks.current) {
5734
5753
  const arr = flatten(updatingBlocks.current[key]);
@@ -5738,9 +5757,9 @@ const GridUpdatingContextProvider = (props) => {
5738
5757
  }
5739
5758
  updating.current = mergedUpdatingBlocks;
5740
5759
  setUpdatedDep((updatedDep) => updatedDep + 1);
5741
- };
5742
- const updatingCols = () => Object.keys(updating.current);
5743
- const modifyUpdating = async (fields, ids, fn) => {
5760
+ }, []);
5761
+ const updatingCols = useCallback(() => Object.keys(updating.current), []);
5762
+ const modifyUpdating = useCallback(async (fields, ids, fn) => {
5744
5763
  const idRef = [...ids];
5745
5764
  castArray(fields).forEach((field) => {
5746
5765
  const fieldUpdatingIds = updatingBlocks.current[field] ?? (updatingBlocks.current[field] = []);
@@ -5753,9 +5772,12 @@ const GridUpdatingContextProvider = (props) => {
5753
5772
  remove(fieldUpdatingIds, (idList) => idList === idRef);
5754
5773
  });
5755
5774
  resetUpdating();
5756
- };
5757
- const checkUpdating = (fields, id) => castArray(fields).some((f) => updating.current[f]?.includes(id));
5758
- return (jsx(GridUpdatingContext.Provider, { value: { modifyUpdating, checkUpdating, updatingCols, updatedDep }, children: props.children }));
5775
+ }, [resetUpdating]);
5776
+ const anyUpdating = useCallback(() => {
5777
+ return !isEmpty(updating.current);
5778
+ }, []);
5779
+ const checkUpdating = useCallback((fields, id) => castArray(fields).some((f) => updating.current[f]?.includes(id)), []);
5780
+ return (jsx(GridUpdatingContext.Provider, { value: { modifyUpdating, anyUpdating, checkUpdating, updatingCols, updatedDep }, children: props.children }));
5759
5781
  };
5760
5782
 
5761
5783
  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%}";