@linzjs/step-ag-grid 29.1.2 → 29.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/src/components/Grid.d.ts +1 -1
  2. package/dist/src/components/GridPopoverHook.d.ts +1 -0
  3. package/dist/src/contexts/GridContext.d.ts +2 -2
  4. package/dist/src/utils/waitForCondition.d.ts +1 -1
  5. package/dist/step-ag-grid.cjs +73 -54
  6. package/dist/step-ag-grid.cjs.map +1 -1
  7. package/dist/step-ag-grid.esm.js +73 -55
  8. package/dist/step-ag-grid.esm.js.map +1 -1
  9. package/package.json +1 -1
  10. package/src/components/Grid.tsx +1 -1
  11. package/src/components/GridCell.tsx +34 -14
  12. package/src/components/GridPopoverHook.tsx +9 -15
  13. package/src/components/gridForm/GridFormDropDown.tsx +10 -3
  14. package/src/components/gridForm/GridFormMultiSelect.tsx +12 -4
  15. package/src/components/gridPopoverEdit/GridPopoverMessage.ts +1 -0
  16. package/src/contexts/GridContext.tsx +2 -2
  17. package/src/contexts/GridContextProvider.tsx +31 -23
  18. package/src/stories/grid/GridPopoutEditGeneric.stories.tsx +1 -1
  19. package/src/stories/grid/GridPopoverEditMultiSelect.stories.tsx +1 -1
  20. package/src/stories/grid/GridPopoverEditMultiSelectGrid.stories.tsx +1 -1
  21. package/src/stories/grid/gridFormInteraction/GridFormDropDownInteraction.stories.tsx +1 -1
  22. package/src/stories/grid/gridFormInteraction/GridFormEditBearingCorrectionInteraction.stories.tsx +2 -1
  23. package/src/stories/grid/gridFormInteraction/GridFormEditBearingInteraction.stories.tsx +1 -1
  24. package/src/stories/grid/gridFormInteraction/GridFormMultiSelectInteraction.stories.tsx +2 -1
  25. package/src/stories/grid/gridFormInteraction/GridFormPopoverMenuInteraction.stories.tsx +2 -2
  26. package/src/stories/grid/gridFormInteraction/GridFormTextAreaInteraction.stories.tsx +1 -1
  27. package/src/stories/grid/gridFormInteraction/GridFormTextInputInteraction.stories.tsx +1 -1
  28. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +56 -13
  29. package/src/utils/waitForCondition.tsx +6 -2
@@ -65,7 +65,7 @@ export interface GridProps<TData extends GridBaseRow = GridBaseRow> {
65
65
  * When pressing tab whilst editing the grid will select and edit the next cell if available.
66
66
  * Once the last cell to edit closes this callback is called.
67
67
  */
68
- onBulkEditingComplete?: () => void;
68
+ onBulkEditingComplete?: () => Promise<void> | void;
69
69
  /**
70
70
  * Context menu definition if required.
71
71
  */
@@ -6,6 +6,7 @@ export interface GridPopoverHookProps<TData> {
6
6
  save?: (selectedRows: TData[]) => Promise<boolean>;
7
7
  dontSaveOnExternalClick?: boolean;
8
8
  }
9
+ export declare const CancelPromise: () => Promise<boolean>;
9
10
  export declare const useGridPopoverHook: <TData extends GridBaseRow>({ className, save, invalid, dontSaveOnExternalClick, }: GridPopoverHookProps<TData>) => {
10
11
  popoverWrapper: (children: ReactElement) => import("react/jsx-runtime").JSX.Element;
11
12
  triggerSave: (reason?: string) => Promise<void>;
@@ -57,8 +57,8 @@ export interface GridContextType<TData extends GridBaseRow> {
57
57
  invisibleColumnIds: string[] | undefined;
58
58
  setInvisibleColumnIds: (colIds: string[]) => void;
59
59
  downloadCsv: (csvExportParams?: CsvExportParams) => void;
60
- onBulkEditingComplete: () => void;
61
- setOnBulkEditingComplete: (callback: (() => void) | undefined) => void;
60
+ onBulkEditingComplete: () => Promise<void> | void;
61
+ setOnBulkEditingComplete: (callback: (() => Promise<void> | void) | undefined) => void;
62
62
  showNoRowsOverlay: () => void;
63
63
  }
64
64
  export declare const GridContext: import("react").Context<GridContextType<any>>;
@@ -1 +1 @@
1
- export declare const waitForCondition: (condition: () => boolean, timeoutMs: number) => Promise<boolean>;
1
+ export declare const waitForCondition: (error: string, condition: () => boolean, timeoutMs: number) => Promise<boolean>;
@@ -3400,20 +3400,25 @@ const GridCell = (props, custom) => {
3400
3400
  sortable: true,
3401
3401
  resizable: true,
3402
3402
  valueSetter: custom?.editor ? blockValueSetter : undefined,
3403
- editable: props.editable ?? false,
3404
- ...(custom?.editor && {
3405
- cellClassRules: GridCellMultiSelectClassRules,
3406
- editable: props.editable ?? true,
3407
- cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
3408
- }),
3403
+ editable: props.editable ?? !!custom?.editor,
3404
+ ...(custom?.editor
3405
+ ? {
3406
+ cellClassRules: GridCellMultiSelectClassRules,
3407
+ cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
3408
+ }
3409
+ : {
3410
+ cellEditor: CellEditorToBlockEditing,
3411
+ }),
3409
3412
  suppressKeyboardEvent: suppressCellKeyboardEvents,
3410
- ...(custom?.editorParams && {
3411
- cellEditorParams: {
3412
- ...custom.editorParams,
3413
- multiEdit: custom.multiEdit,
3414
- preventAutoEdit: custom.preventAutoEdit ?? false,
3415
- },
3416
- }),
3413
+ ...(custom?.editorParams
3414
+ ? {
3415
+ cellEditorParams: {
3416
+ ...custom.editorParams,
3417
+ multiEdit: custom.multiEdit,
3418
+ preventAutoEdit: custom.preventAutoEdit ?? !custom?.editor,
3419
+ },
3420
+ }
3421
+ : { cellEditorParams: { preventAutoEdit: !custom?.editor } }),
3417
3422
  // If there's a valueFormatter and no filterValueGetter then create a filterValueGetter
3418
3423
  getQuickFilterText: filterValueGetter,
3419
3424
  // Default value formatter, otherwise react freaks out on objects
@@ -3430,6 +3435,19 @@ const GridCell = (props, custom) => {
3430
3435
  },
3431
3436
  };
3432
3437
  };
3438
+ /**
3439
+ * Ag-grid will start its own editor if editable is true and there is no cell editor
3440
+ * like in the case of a cell that is editable because it triggers a modal.
3441
+ * This will block that editor.
3442
+ */
3443
+ const CellEditorToBlockEditing = ({ stopEditing }) => {
3444
+ React.useEffect(() => {
3445
+ lodashEs.defer(() => {
3446
+ stopEditing();
3447
+ });
3448
+ }, [stopEditing]);
3449
+ return jsxRuntime.jsx(jsxRuntime.Fragment, {});
3450
+ };
3433
3451
  const GenericCellEditorComponentWrapper = (editor) => {
3434
3452
  const obj = { editor };
3435
3453
  return React.forwardRef(function GenericCellEditorComponentFr(cellEditorParams, _) {
@@ -3938,8 +3956,8 @@ const textMatch = (text, filter) => {
3938
3956
  negativeFilters.every((superFilter) => values.every((value) => isMatch(value, superFilter)))));
3939
3957
  };
3940
3958
 
3959
+ const CancelPromise = () => Promise.resolve(true);
3941
3960
  const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick, }) => {
3942
- const { onBulkEditingComplete, redrawRows } = useGridContext();
3943
3961
  const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext();
3944
3962
  const saveButtonRef = React.useRef(null);
3945
3963
  const [isOpen, setOpen] = React.useState(false);
@@ -3948,27 +3966,18 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
3948
3966
  }, []);
3949
3967
  const triggerSave = React.useCallback(async (reason) => {
3950
3968
  if (reason == CloseReason.CANCEL) {
3969
+ await updateValue(CancelPromise, 0);
3951
3970
  stopEditing();
3952
- onBulkEditingComplete();
3953
- redrawRows();
3954
3971
  return;
3955
3972
  }
3956
3973
  if (invalid?.()) {
3957
3974
  // Don't close, don't do anything it's invalid
3958
3975
  return;
3959
3976
  }
3960
- if (!save) {
3961
- // No save method so just close
3977
+ if (await updateValue(save ?? (() => Promise.resolve(true)), reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
3962
3978
  stopEditing();
3963
- onBulkEditingComplete();
3964
- redrawRows();
3965
- return;
3966
3979
  }
3967
- if (await updateValue(save, reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
3968
- stopEditing();
3969
- redrawRows();
3970
- }
3971
- }, [invalid, onBulkEditingComplete, redrawRows, save, stopEditing, updateValue]);
3980
+ }, [invalid, save, stopEditing, updateValue]);
3972
3981
  const popoverWrapper = React.useCallback((children) => {
3973
3982
  return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: anchorRef.current && (jsxRuntime.jsxs(ControlledMenu, { state: isOpen ? 'open' : 'closed', portal: true, unmountOnClose: true, anchorRef: anchorRef, saveButtonRef: saveButtonRef, menuClassName: 'step-ag-grid-react-menu', onClose: (event) => {
3974
3983
  // Prevent menu from closing when modals are invoked
@@ -3994,7 +4003,7 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
3994
4003
  const primitiveToSelectOption = (value) => {
3995
4004
  return {
3996
4005
  value: value,
3997
- label: value ? String(value) : '',
4006
+ label: value ? String(value) : '-',
3998
4007
  };
3999
4008
  };
4000
4009
  const MenuSeparatorString = '_____MENU_SEPARATOR_____';
@@ -4086,7 +4095,13 @@ const GridFormDropDown = (props) => {
4086
4095
  if (selectedItem === null) {
4087
4096
  if (props.onSelectFilter) {
4088
4097
  const { onSelectFilter } = props;
4089
- await onSelectFilter({ selectedRows, selectedRowIds: selectedRows.map((row) => row.id), value: filter });
4098
+ if (!lodashEs.isEmpty(filter)) {
4099
+ await onSelectFilter({
4100
+ selectedRows,
4101
+ selectedRowIds: selectedRows.map((row) => row.id),
4102
+ value: filter,
4103
+ });
4104
+ }
4090
4105
  return true;
4091
4106
  }
4092
4107
  else {
@@ -4116,7 +4131,7 @@ const GridFormDropDown = (props) => {
4116
4131
  setSubSelectedValue(null);
4117
4132
  subComponentIsValid.current = true;
4118
4133
  }, children: ({ ref }) => (jsxRuntime.jsxs("div", { style: { display: 'flex', flexDirection: 'column', width: '100%' }, children: [jsxRuntime.jsx("input", { className: 'LuiTextInput-input', ref: ref, type: "text", placeholder: props.filterPlaceholder ?? 'Filter...', "data-testid": 'filteredMenu-free-text-input', defaultValue: filter, "data-allowtabtosave": true, "data-disableenterautosave": !props.onSelectFilter &&
4119
- !(filteredValues && filteredValues.length === 1 && !filteredValues[0].subComponent), onChange: (e) => setFilter(e.target.value) }), props.filterHelpText && isNotEmpty(filter) && (jsxRuntime.jsx(FormError, { error: null, helpText: props.filterHelpText }))] })) }), jsxRuntime.jsx(MenuDivider, {}, `$$divider_filter`)] })), jsxRuntime.jsx(ComponentLoadingWrapper, { loading: !options, className: 'GridFormDropDown-options', children: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [options && (lodashEs.isEmpty(options) || (filteredValues && lodashEs.isEmpty(filteredValues))) && (jsxRuntime.jsx(MenuItem, { className: 'GridPopoverEditDropDown-noOptions', disabled: true, children: props.noOptionsMessage ?? 'No Options' }, `${fieldToString(field)}-empty`)), options?.map((item, index) => {
4134
+ !(filteredValues && filteredValues.length === 1 && !filteredValues[0].subComponent), onChange: (e) => setFilter(e.target.value.trim()) }), props.filterHelpText && isNotEmpty(filter) && (jsxRuntime.jsx(FormError, { error: null, helpText: props.filterHelpText }))] })) }), jsxRuntime.jsx(MenuDivider, {}, `$$divider_filter`)] })), jsxRuntime.jsx(ComponentLoadingWrapper, { loading: !options, className: 'GridFormDropDown-options', children: jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [options && (lodashEs.isEmpty(options) || (filteredValues && lodashEs.isEmpty(filteredValues))) && (jsxRuntime.jsx(MenuItem, { className: 'GridPopoverEditDropDown-noOptions', disabled: true, children: props.noOptionsMessage ?? 'No Options' }, `${fieldToString(field)}-empty`)), options?.map((item, index) => {
4120
4135
  showHeader = null;
4121
4136
  if (item.value === MenuSeparatorString) {
4122
4137
  return jsxRuntime.jsx(MenuDivider, {}, `$$divider_${index}`);
@@ -4346,11 +4361,13 @@ const GridFormMultiSelect = (props) => {
4346
4361
  props.invalid(selectedRows, options.filter((o) => o.checked)));
4347
4362
  }, [options, props, selectedRows]);
4348
4363
  const save = React.useCallback(async (selectedRows) => {
4349
- if (!options || !props.onSave)
4364
+ if (!options || !props.onSave) {
4350
4365
  return true;
4366
+ }
4351
4367
  // Any changes to save?
4352
- if (initialValues === JSON.stringify(options))
4368
+ if (initialValues === JSON.stringify(options)) {
4353
4369
  return true;
4370
+ }
4354
4371
  return props.onSave({
4355
4372
  selectedRows,
4356
4373
  selectedOptions: options.filter((o) => o.checked),
@@ -4443,8 +4460,9 @@ const FilterInput = (props) => {
4443
4460
  setOptions([...options]);
4444
4461
  }, [filter, headerGroups, options, setOptions]);
4445
4462
  const addCustomFilterValue = React.useCallback(() => {
4446
- if (!options || !onSelectFilter)
4463
+ if (!options || !onSelectFilter) {
4447
4464
  return;
4465
+ }
4448
4466
  const filterTrimmed = filter.trim();
4449
4467
  if (lodashEs.isEmpty(filterTrimmed)) {
4450
4468
  void triggerSave();
@@ -4453,8 +4471,9 @@ const FilterInput = (props) => {
4453
4471
  const preFilterOptions = JSON.stringify(options);
4454
4472
  onSelectFilter({ filter: filterTrimmed, options });
4455
4473
  // Detect if options list changed and update
4456
- if (preFilterOptions === JSON.stringify(options))
4474
+ if (preFilterOptions === JSON.stringify(options)) {
4457
4475
  return;
4476
+ }
4458
4477
  setOptions([...options]);
4459
4478
  setFilter('');
4460
4479
  }, [filter, onSelectFilter, options, setFilter, setOptions, triggerSave]);
@@ -5049,6 +5068,7 @@ const GridPopoverMessage = (colDef, props) => GridCell({
5049
5068
  singleClickEdit: true,
5050
5069
  }, {
5051
5070
  editor: GridFormMessage,
5071
+ preventAutoEdit: true,
5052
5072
  ...props,
5053
5073
  });
5054
5074
 
@@ -5064,7 +5084,7 @@ const GridWrapper = ({ children, maxHeight }) => (jsxRuntime.jsx("div", { classN
5064
5084
  const getColId = (colDef) => colDef.colId ?? '';
5065
5085
  const isFlexColumn = (colDef) => !!colDef.flex && !isGridCellFiller(colDef);
5066
5086
 
5067
- const waitForCondition = async (condition, timeoutMs) => {
5087
+ const waitForCondition = async (error, condition, timeoutMs) => {
5068
5088
  const endTime = Date.now() + timeoutMs;
5069
5089
  while (Date.now() < endTime) {
5070
5090
  if (condition()) {
@@ -5072,7 +5092,7 @@ const waitForCondition = async (condition, timeoutMs) => {
5072
5092
  }
5073
5093
  await wait(100);
5074
5094
  }
5075
- console.warn('waitForCondition failed');
5095
+ console.warn(error);
5076
5096
  return false;
5077
5097
  };
5078
5098
 
@@ -5421,8 +5441,7 @@ const GridContextProvider = (props) => {
5421
5441
  startCellEditingInProgressRef.current = true;
5422
5442
  try {
5423
5443
  // Edit in progress so don't edit until finished, timeout waiting after 5s
5424
- if (!(await waitForCondition(() => !anyUpdating(), 5000))) {
5425
- console.error("Could not start edit because previous edit hasn't finished after 5 seconds");
5444
+ if (!(await waitForCondition('startCellEditing failed as update still in progress, waited for 15 seconds', () => !anyUpdating(), 15000))) {
5426
5445
  return;
5427
5446
  }
5428
5447
  const colDef = gridApi.getColumnDef(colId);
@@ -5444,11 +5463,12 @@ const GridContextProvider = (props) => {
5444
5463
  const rowIndex = rowNode.rowIndex;
5445
5464
  if (rowIndex != null) {
5446
5465
  lodashEs.defer(() => {
5447
- !gridApi.isDestroyed() &&
5466
+ if (!gridApi.isDestroyed()) {
5448
5467
  gridApi.startEditingCell({
5449
5468
  rowIndex,
5450
5469
  colKey: colId,
5451
5470
  });
5471
+ }
5452
5472
  });
5453
5473
  }
5454
5474
  }
@@ -5457,9 +5477,9 @@ const GridContextProvider = (props) => {
5457
5477
  }
5458
5478
  }, [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
5459
5479
  const bulkEditingCompleteCallbackRef = React.useRef();
5460
- const onBulkEditingComplete = React.useCallback(() => {
5480
+ const onBulkEditingComplete = React.useCallback(async () => {
5461
5481
  resetFocusedCellAfterCellEditing();
5462
- bulkEditingCompleteCallbackRef.current?.();
5482
+ await bulkEditingCompleteCallbackRef.current?.();
5463
5483
  }, [resetFocusedCellAfterCellEditing]);
5464
5484
  const setOnBulkEditingComplete = React.useCallback((cellEditingCompleteCallback) => {
5465
5485
  bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
@@ -5490,8 +5510,10 @@ const GridContextProvider = (props) => {
5490
5510
  // Prevent resetting focus to original editing cell
5491
5511
  prePopupFocusedCell.current = undefined;
5492
5512
  const preRow = gridApi.getFocusedCell();
5513
+ // If we don't do this ag-grid will do its own continuation of an edit on tab, we don't want that as
5514
+ // we are managing it ourselves
5515
+ gridApi.stopEditing();
5493
5516
  if (tabDirection === 1) {
5494
- gridApi.stopEditing();
5495
5517
  gridApi.tabToNextCell();
5496
5518
  }
5497
5519
  else {
@@ -5528,29 +5550,25 @@ const GridContextProvider = (props) => {
5528
5550
  const selectedRows = props.selectedRows;
5529
5551
  let ok = false;
5530
5552
  await modifyUpdating(props.field ?? '', selectedRows.map((data) => data.id), async () => {
5531
- // MATT Disabled I don't believe these are needed anymore
5532
- // I've left them here just in case they are
5533
5553
  // Need to refresh to get spinners to work on all rows
5534
- // gridApi.refreshCells({ rowNodes: props.selectedRows as RowNode[], force: true });
5554
+ gridApi.refreshCells({ rowNodes: props.selectedRows, force: true });
5535
5555
  ok = await fnUpdate(selectedRows).catch((ex) => {
5536
5556
  console.error('Exception during modifyUpdating', ex);
5537
5557
  return false;
5538
5558
  });
5539
5559
  });
5540
- // MATT Disabled I don't believe these are needed anymore
5541
- // I've left them here just in case they are
5542
5560
  // async processes need to refresh their own rows
5543
- // gridApi.refreshCells({ rowNodes: selectedRows as RowNode[], force: true });
5561
+ gridApi.refreshCells({ rowNodes: selectedRows, force: true });
5544
5562
  if (gridApi.isDestroyed()) {
5545
5563
  return ok;
5546
5564
  }
5547
5565
  if (ok) {
5548
- const cell = gridApi.getFocusedCell();
5549
- if (cell && gridApi.getFocusedCell() == null) {
5550
- !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
5551
- }
5566
+ //const cell = gridApi.getFocusedCell();
5567
+ //if (cell && gridApi.getFocusedCell() == null) {
5568
+ // !gridApi.isDestroyed && gridApi.setFocusedCell(cell.rowIndex, cell.column);
5569
+ // }
5552
5570
  // This is needed to trigger postSortRowsHook
5553
- gridApi.refreshClientSideRowModel();
5571
+ !gridApi.isDestroyed && gridApi.refreshClientSideRowModel();
5554
5572
  }
5555
5573
  void (async () => {
5556
5574
  // Only focus next cell if user hasn't already manually changed focus
@@ -5560,11 +5578,11 @@ const GridContextProvider = (props) => {
5560
5578
  prePopupFocusedCell.current.rowIndex == postPopupFocusedCell.rowIndex &&
5561
5579
  prePopupFocusedCell.current.column.getColId() == postPopupFocusedCell.column.getColId()) {
5562
5580
  if (!tabDirection || !(await selectNextEditableCell(tabDirection))) {
5563
- onBulkEditingComplete();
5581
+ await onBulkEditingComplete();
5564
5582
  }
5565
5583
  }
5566
5584
  else {
5567
- onBulkEditingComplete();
5585
+ await onBulkEditingComplete();
5568
5586
  }
5569
5587
  })();
5570
5588
  return ok;
@@ -5876,6 +5894,7 @@ const useDeferredPromise = () => {
5876
5894
  };
5877
5895
 
5878
5896
  exports.ActionButton = ActionButton;
5897
+ exports.CancelPromise = CancelPromise;
5879
5898
  exports.ComponentLoadingWrapper = ComponentLoadingWrapper;
5880
5899
  exports.ControlledMenu = ControlledMenu;
5881
5900
  exports.Editor = Editor;