@linzjs/step-ag-grid 29.16.0 → 29.19.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 (33) hide show
  1. package/README.md +1 -1
  2. package/dist/index.css +27 -0
  3. package/dist/src/components/Grid.d.ts +3 -2
  4. package/dist/src/components/gridForm/GridFormInlineTextInput.d.ts +15 -0
  5. package/dist/src/components/gridForm/index.d.ts +1 -0
  6. package/dist/src/components/gridHook/useGridRangeSelection.d.ts +1 -1
  7. package/dist/src/components/gridPopoverEdit/GridInlineTextInput.d.ts +5 -0
  8. package/dist/src/components/gridPopoverEdit/index.d.ts +1 -0
  9. package/dist/src/contexts/GridContext.d.ts +1 -1
  10. package/dist/src/utils/__tests__/storybookTestUtil.ts +5 -1
  11. package/dist/step-ag-grid.cjs +212 -84
  12. package/dist/step-ag-grid.cjs.map +1 -1
  13. package/dist/step-ag-grid.esm.js +211 -85
  14. package/dist/step-ag-grid.esm.js.map +1 -1
  15. package/package.json +14 -12
  16. package/src/components/Grid.tsx +33 -10
  17. package/src/components/GridPopoverHook.tsx +5 -2
  18. package/src/components/gridForm/GridFormInlineTextInput.tsx +119 -0
  19. package/src/components/gridForm/index.ts +1 -0
  20. package/src/components/gridHook/useGridContextMenu.tsx +3 -1
  21. package/src/components/gridHook/useGridCopy.ts +5 -2
  22. package/src/components/gridHook/useGridRangeSelection.ts +25 -4
  23. package/src/components/gridPopoverEdit/GridInlineTextInput.ts +13 -0
  24. package/src/components/gridPopoverEdit/index.ts +1 -0
  25. package/src/contexts/GridContext.tsx +6 -1
  26. package/src/contexts/GridContextProvider.tsx +30 -13
  27. package/src/stories/grid/GridFilterColumnsMultiSelect.stories.tsx +43 -2
  28. package/src/stories/grid/GridInlineText.stories.tsx +185 -0
  29. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +3 -3
  30. package/src/styles/Grid.scss +5 -0
  31. package/src/styles/GridFormInlineTextInput.scss +23 -0
  32. package/src/styles/index.scss +1 -1
  33. package/src/utils/__tests__/storybookTestUtil.ts +5 -1
@@ -2632,8 +2632,9 @@ const useGridContextMenu = ({ contextMenuSelectRow, contextMenu: ContextMenu, co
2632
2632
  const clickedRowRef = useRef(null);
2633
2633
  const eventRef = useRef(undefined);
2634
2634
  const openMenu = useCallback((e) => {
2635
- if (!e)
2635
+ if (!e) {
2636
2636
  return;
2637
+ }
2637
2638
  prePopupOps();
2638
2639
  setAnchorPoint({ x: e.clientX, y: e.clientY });
2639
2640
  setOpen(true);
@@ -2903,7 +2904,11 @@ const useGridCopy = ({ ranges, rangeStartRef, rangeEndRef, hasSelectedMoreThanOn
2903
2904
  contextMenu: (GridRangeSelectContextMenu),
2904
2905
  context: { onCopy, copyType, setCopyType },
2905
2906
  });
2906
- const rangeSelectInterceptContextMenu = useCallback((event) => rangeStartRef.current == null ? cellContextMenu(event) : rangeSelectContextMenu(event), [cellContextMenu, rangeSelectContextMenu]);
2907
+ const rangeSelectInterceptContextMenu = useCallback((event) => {
2908
+ return hasSelectedMoreThanOneCellRef.current && rangeStartRef.current !== null
2909
+ ? rangeSelectContextMenu(event)
2910
+ : cellContextMenu(event);
2911
+ }, [cellContextMenu, rangeSelectContextMenu]);
2907
2912
  return {
2908
2913
  onCopyEvent,
2909
2914
  rangeSelectInterceptContextMenu,
@@ -3027,6 +3032,23 @@ const useGridRangeSelection = ({ enableRangeSelection, gridDivRef, rangeStartRef
3027
3032
  rangeEndRef.current = null;
3028
3033
  updateRangeSelectionCellClasses();
3029
3034
  }, [updateRangeSelectionCellClasses]);
3035
+ const mouseDownRef = useRef([0, 0]);
3036
+ const trackMouseDown = useCallback((e) => {
3037
+ mouseDownRef.current = [e.screenX, e.screenY];
3038
+ }, []);
3039
+ const trackMouseUp = useCallback((e) => {
3040
+ if (rangeEndRef.current) {
3041
+ rangeEndRef.current.clickLocation = [e.screenX, e.screenY];
3042
+ }
3043
+ }, []);
3044
+ useEffect(() => {
3045
+ document.addEventListener('mousedown', trackMouseDown, { capture: true });
3046
+ document.addEventListener('mouseup', trackMouseUp, { capture: true });
3047
+ return () => {
3048
+ document.removeEventListener('mousemove', trackMouseDown, { capture: true });
3049
+ document.removeEventListener('mouseup', trackMouseUp, { capture: true });
3050
+ };
3051
+ }, []);
3030
3052
  const onCellMouseOver = useCallback((e, mouseDown) => {
3031
3053
  if (!enableRangeSelection) {
3032
3054
  return;
@@ -3040,19 +3062,20 @@ const useGridRangeSelection = ({ enableRangeSelection, gridDivRef, rangeStartRef
3040
3062
  rangeEndRef.current = {
3041
3063
  rowId: e.node.data.id,
3042
3064
  colId: e.column.getColId(),
3043
- timestamp: Date.now(),
3065
+ clickLocation: [0, 0],
3044
3066
  };
3045
3067
  if (mouseDown) {
3046
3068
  const sortedNodes = [];
3047
3069
  e.api.forEachNodeAfterFilterAndSort((node) => sortedNodes.push(node));
3048
3070
  rangeSortedNodesRef.current = sortedNodes;
3049
3071
  setRefreshIntervalEnabled(true);
3050
- rangeStartRef.current = { ...rangeEndRef.current };
3072
+ rangeStartRef.current = { ...rangeEndRef.current, clickLocation: mouseDownRef.current };
3051
3073
  }
3052
3074
  if (rangeStartRef.current &&
3053
3075
  rangeEndRef.current &&
3054
3076
  (rangeStartRef.current.rowId !== rangeEndRef.current.rowId ||
3055
3077
  rangeStartRef.current.colId !== rangeEndRef.current.colId)) {
3078
+ e.api.stopEditing();
3056
3079
  hasSelectedMoreThanOneCellRef.current = true;
3057
3080
  window.getSelection()?.removeAllRanges();
3058
3081
  }
@@ -3257,7 +3280,7 @@ defaultPostSort = true, rowData, rowHeight = theme === 'ag-theme-step-default' ?
3257
3280
  // ─── Selection ────────────────────────────────
3258
3281
  autoSelectFirstRow, enableRangeSelection = false, externalSelectedIds, externalSelectedItems, selectColumnPinned = 'left', selectable, setExternalSelectedIds, setExternalSelectedItems,
3259
3282
  // ─── Editing ──────────────────────────────────
3260
- singleClickEdit = false, stopEditingWhenCellsLoseFocus = false,
3283
+ singleClickEdit = false, enableMultilineBulkEdit = false,
3261
3284
  // ─── Context Menu ─────────────────────────────
3262
3285
  contextMenuSelectRow = false, contextMenu,
3263
3286
  // ─── Callbacks / Events ───────────────────────
@@ -3539,10 +3562,16 @@ maxInitialWidth,
3539
3562
  * When grid is ready set the apis to the grid context and sync selected items to grid.
3540
3563
  */
3541
3564
  const onGridReady = useCallback((event) => {
3542
- setApis(event.api, hasExternallySelectedItems, dataTestId);
3565
+ setApis(event.api, hasExternallySelectedItems, enableMultilineBulkEdit, dataTestId);
3543
3566
  event.api.showNoRowsOverlay();
3544
3567
  synchroniseExternallySelectedItemsToGrid();
3545
- }, [dataTestId, hasExternallySelectedItems, setApis, synchroniseExternallySelectedItemsToGrid]);
3568
+ }, [
3569
+ dataTestId,
3570
+ enableMultilineBulkEdit,
3571
+ hasExternallySelectedItems,
3572
+ setApis,
3573
+ synchroniseExternallySelectedItemsToGrid,
3574
+ ]);
3546
3575
  /**
3547
3576
  * When the grid is being initialized the data may be empty.
3548
3577
  * This will resize columns when we have at least one row.
@@ -3585,6 +3614,11 @@ maxInitialWidth,
3585
3614
  * Handle double click edit
3586
3615
  */
3587
3616
  const onCellDoubleClick = useCallback((event) => {
3617
+ const cellEditing = event.api.getEditingCells()?.[0];
3618
+ if (cellEditing && cellEditing.colId === event.column.getColId() && cellEditing.rowIndex === event.rowIndex) {
3619
+ // Ignore double click in already editing cell
3620
+ return;
3621
+ }
3588
3622
  const editable = fnOrVar(event.colDef?.editable, event);
3589
3623
  if (editable && !invokeEditAction(event)) {
3590
3624
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
@@ -3669,18 +3703,25 @@ maxInitialWidth,
3669
3703
  * Handle single click edits
3670
3704
  */
3671
3705
  const onCellClicked = useCallback((event) => {
3672
- if (rangeStartRef.current && rangeEndRef.current) {
3706
+ if (enableRangeSelection && rangeStartRef.current && rangeEndRef.current) {
3673
3707
  // This is to detect difference between a single click and a click drag return to cell.
3674
- if (rangeEndRef.current.timestamp - rangeStartRef.current.timestamp < 100) {
3675
- clearRangeSelection();
3708
+ const maxDistanceBetweenC = Math.max(Math.abs(rangeEndRef.current.clickLocation[0] - rangeStartRef.current.clickLocation[0]), Math.abs(rangeEndRef.current.clickLocation[1] - rangeStartRef.current.clickLocation[1]));
3709
+ if (maxDistanceBetweenC > 8) {
3710
+ return;
3676
3711
  }
3677
- return;
3712
+ clearRangeSelection();
3678
3713
  }
3679
3714
  const editable = fnOrVar(event.colDef?.editable, event);
3680
3715
  if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
3716
+ const editingCell = event.api.getEditingCells()[0];
3717
+ // If we are editing and single click we don't want the editing to stop
3718
+ if (editingCell && editingCell.colId === event.column.getColId() && editingCell.rowIndex === event.rowIndex) {
3719
+ return;
3720
+ }
3721
+ event.api.setFocusedCell(event.rowIndex ?? 0, event.column.getColId(), event.rowPinned);
3681
3722
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
3682
3723
  }
3683
- }, [clearRangeSelection, singleClickEdit, startCellEditing]);
3724
+ }, [clearRangeSelection, enableRangeSelection, singleClickEdit, startCellEditing]);
3684
3725
  /**
3685
3726
  * Set of column I'd's that are prevented from auto-sizing as they are user set
3686
3727
  */
@@ -3961,7 +4002,7 @@ maxInitialWidth,
3961
4002
  event.api.sizeColumnsToFit();
3962
4003
  }
3963
4004
  }, [sizeColumns]);
3964
- return (jsxs("div", { "data-testid": dataTestId, className: clsx('Grid-container', theme, 'theme-specific', staleGrid && 'Grid-sortIsStale', gridReady && rowData && autoSized && 'Grid-ready'), children: [contextMenuComponent, rangeSelectContextMenuComponent, jsx("div", { style: { flex: 1 }, ref: gridDivRef, onCopy: onCopyEvent, children: jsx(AgGridReact, { theme: 'legacy', domLayout: params.domLayout, defaultColDef: defaultColDef, columnDefs: columnDefsAdjusted, getRowId: getRowId, rowData: rowData, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, animateRows: params.animateRows ?? false, doesExternalFilterPass: doesExternalFilterPass, isExternalFilterPresent: isExternalFilterPresent, maintainColumnOrder: true, noRowsOverlayComponent: noRowsOverlayComponent, onCellClicked: onCellClicked, onCellContextMenu: rangeSelectInterceptContextMenu, onCellDoubleClicked: onCellDoubleClick, onCellEditingStopped: onCellEditingStopped, onCellFocused: onCellFocused, onCellKeyDown: onCellKeyPress, onCellMouseDown: onCellMouseDown, onCellMouseOver: onCellMouseOver, onColumnMoved: columnMoved, onColumnResized: onColumnResized, onColumnVisible: () => void setInitialContentSize(), onGridReady: onGridReady, onGridSizeChanged: onGridSizeChanged, onModelUpdated: onModelUpdated, onRowClicked: params.onRowClicked, onRowDataUpdated: onRowDataUpdated, onRowDoubleClicked: params.onRowDoubleClicked, onRowDragCancel: clearHighlightRowClasses, onRowDragEnd: onRowDragEnd, onRowDragMove: onRowDragMove, onSelectionChanged: synchroniseExternalStateToGridSelection, onSortChanged: onSortChanged, pinnedBottomRowData: params.pinnedBottomRowData, pinnedTopRowData: params.pinnedTopRowData, postSortRows: params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows, preventDefaultOnContextMenu: true, quickFilterParser: quickFilterParser, rowClassRules: params.rowClassRules, rowDragText: params.rowDragText, rowHeight: rowHeight, rowSelection: selectable
4005
+ return (jsxs("div", { "data-testid": dataTestId, className: clsx('Grid-container', theme, 'theme-specific', staleGrid && 'Grid-sortIsStale', gridReady && rowData && autoSized && 'Grid-ready', params.hideSelectedRow && 'Grid-hideSelectedRow'), children: [contextMenuComponent, rangeSelectContextMenuComponent, jsx("div", { style: { flex: 1 }, ref: gridDivRef, onCopy: onCopyEvent, children: jsx(AgGridReact, { theme: 'legacy', domLayout: params.domLayout, defaultColDef: defaultColDef, columnDefs: columnDefsAdjusted, getRowId: getRowId, rowData: rowData, alwaysShowVerticalScroll: params.alwaysShowVerticalScroll, animateRows: params.animateRows ?? false, doesExternalFilterPass: doesExternalFilterPass, isExternalFilterPresent: isExternalFilterPresent, maintainColumnOrder: true, noRowsOverlayComponent: noRowsOverlayComponent, onCellClicked: onCellClicked, onCellContextMenu: rangeSelectInterceptContextMenu, onCellDoubleClicked: onCellDoubleClick, onCellEditingStopped: onCellEditingStopped, onCellFocused: onCellFocused, onCellKeyDown: onCellKeyPress, onCellMouseDown: onCellMouseDown, onCellMouseOver: onCellMouseOver, onColumnMoved: columnMoved, onColumnResized: onColumnResized, onColumnVisible: () => void setInitialContentSize(), onGridReady: onGridReady, onGridSizeChanged: onGridSizeChanged, onModelUpdated: onModelUpdated, onRowClicked: params.onRowClicked, onRowDataUpdated: onRowDataUpdated, onRowDoubleClicked: params.onRowDoubleClicked, onRowDragCancel: clearHighlightRowClasses, onRowDragEnd: onRowDragEnd, onRowDragMove: onRowDragMove, onSelectionChanged: synchroniseExternalStateToGridSelection, onSortChanged: onSortChanged, pinnedBottomRowData: params.pinnedBottomRowData, pinnedTopRowData: params.pinnedTopRowData, postSortRows: params.onRowDragEnd || !defaultPostSort ? undefined : postSortRows, preventDefaultOnContextMenu: true, quickFilterParser: quickFilterParser, rowClassRules: params.rowClassRules, rowDragText: params.rowDragText, rowHeight: rowHeight, rowSelection: selectable
3965
4006
  ? {
3966
4007
  enableSelectionWithoutKeys: params.enableSelectionWithoutKeys ?? false,
3967
4008
  enableClickSelection: params.enableClickSelection ?? false,
@@ -3971,7 +4012,7 @@ maxInitialWidth,
3971
4012
  headerCheckbox: false,
3972
4013
  }),
3973
4014
  }
3974
- : undefined, stopEditingWhenCellsLoseFocus: stopEditingWhenCellsLoseFocus, selectionColumnDef: selectionColumnDef, suppressCellFocus: params.suppressCellFocus, suppressClickEdit: true, suppressColumnVirtualisation: suppressColumnVirtualization, suppressStartEditOnTab: true }) })] }));
4015
+ : undefined, selectionColumnDef: selectionColumnDef, suppressCellFocus: params.suppressCellFocus, suppressClickEdit: true, suppressColumnVirtualisation: suppressColumnVirtualization, suppressStartEditOnTab: true }) })] }));
3975
4016
  };
3976
4017
  const quickFilterParser = (filterStr) => {
3977
4018
  // filter is exact matches exactly groups separated by commas
@@ -4786,6 +4827,7 @@ const textMatch = (text, filter) => {
4786
4827
 
4787
4828
  const CancelPromise = () => Promise.resolve(true);
4788
4829
  const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick, }) => {
4830
+ const { resetFocusedCellAfterCellEditing } = useGridContext();
4789
4831
  const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext();
4790
4832
  const saveButtonRef = useRef(null);
4791
4833
  const [isOpen, setOpen] = useState(false);
@@ -4796,6 +4838,7 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
4796
4838
  if (reason == CloseReason.CANCEL) {
4797
4839
  await updateValue(CancelPromise, 0);
4798
4840
  stopEditing();
4841
+ resetFocusedCellAfterCellEditing();
4799
4842
  return;
4800
4843
  }
4801
4844
  if (invalid?.()) {
@@ -4804,8 +4847,9 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
4804
4847
  }
4805
4848
  if (await updateValue(save ?? (() => Promise.resolve(true)), reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
4806
4849
  stopEditing();
4850
+ resetFocusedCellAfterCellEditing();
4807
4851
  }
4808
- }, [invalid, save, stopEditing, updateValue]);
4852
+ }, [invalid, resetFocusedCellAfterCellEditing, save, stopEditing, updateValue]);
4809
4853
  const popoverWrapper = useCallback((children) => {
4810
4854
  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) => {
4811
4855
  // Prevent menu from closing when modals are invoked
@@ -5174,6 +5218,128 @@ const GridFormEditBearing = (props) => {
5174
5218
  : props.formatValue(bearingNumberParser(value)), error: bearingStringValidator(value, props.range), helpText: 'Press enter or tab to save' }) }));
5175
5219
  };
5176
5220
 
5221
+ const TextInputValidator = (props, value, data, context) => {
5222
+ if (value == null)
5223
+ return null;
5224
+ // This can happen because subcomponent is invoked without type safety
5225
+ if (typeof value !== 'string') {
5226
+ return 'Value is not a string';
5227
+ }
5228
+ if (props.required && value.trim() == '') {
5229
+ return 'Must not be empty';
5230
+ }
5231
+ if (props.maxLength && value.length > props.maxLength) {
5232
+ return `Must be no longer than ${props.maxLength} characters`;
5233
+ }
5234
+ if (props.maxBytes && stringByteLengthIsInvalid(value, props.maxBytes)) {
5235
+ return `Must be no longer than ${props.maxBytes} bytes`;
5236
+ }
5237
+ const nf = props.numberFormat;
5238
+ if (nf) {
5239
+ if (value != '' && !isFloat(value)) {
5240
+ return `Must be a valid number`;
5241
+ }
5242
+ if (value != '') {
5243
+ const number = parseFloat(value);
5244
+ if (nf.notZero && number === 0) {
5245
+ return `Must not be 0`;
5246
+ }
5247
+ if (nf.gtMin != null && number <= nf.gtMin) {
5248
+ return `Must be greater than ${nf.gtMin.toLocaleString()}`;
5249
+ }
5250
+ if (nf.geMin != null && number < nf.geMin) {
5251
+ return `Must not be less than ${nf.geMin.toLocaleString()}`;
5252
+ }
5253
+ if (nf.ltMax != null && number >= nf.ltMax) {
5254
+ return `Must be less than ${nf.ltMax.toLocaleString()}`;
5255
+ }
5256
+ if (nf.leMax != null && number > nf.leMax) {
5257
+ return `Must not be greater than ${nf.leMax.toLocaleString()}`;
5258
+ }
5259
+ if (nf.precision != null && value != '') {
5260
+ if (parseFloat(number.toPrecision(nf.precision)) != number) {
5261
+ return `Must have no more than ${nf.precision} digits precision`;
5262
+ }
5263
+ }
5264
+ if (nf.scale != null && value != '') {
5265
+ if (parseFloat(number.toFixed(nf.scale)) != number) {
5266
+ return nf.scale == 0 ? `Must be a whole number` : `Must have no more than ${nf.scale} decimal places`;
5267
+ }
5268
+ }
5269
+ }
5270
+ }
5271
+ if (props.invalid) {
5272
+ return props.invalid(value, data, context);
5273
+ }
5274
+ return null;
5275
+ };
5276
+
5277
+ const GridFormInlineTextInput = (props) => {
5278
+ const { onSave } = props;
5279
+ const { getSelectedRows, prePopupOps } = useGridContext();
5280
+ const { field, value: initialVale, data } = useGridPopoverContext();
5281
+ const initValue = useMemo(() => (initialVale == null ? '' : `${initialVale}`), [initialVale]);
5282
+ const [value, setValue] = useState(initValue);
5283
+ const invalid = useCallback(() => TextInputValidator(props, value, data, {}), [data, props, value]);
5284
+ useEffect(() => {
5285
+ prePopupOps();
5286
+ }, [prePopupOps]);
5287
+ const save = useCallback(async () => {
5288
+ if (invalid()) {
5289
+ return false;
5290
+ }
5291
+ const trimmedValue = value.trim();
5292
+ // No change, so don't save
5293
+ if (initValue === trimmedValue) {
5294
+ return true;
5295
+ }
5296
+ if (!onSave) {
5297
+ console.error(`No onSave handler for GridFormInlineTextInput "${String(field)}`);
5298
+ return false;
5299
+ }
5300
+ const selectedRows = getSelectedRows();
5301
+ const selectedRowIds = selectedRows.map((data) => data.id);
5302
+ return onSave({
5303
+ selectedRowIds,
5304
+ selectedRows,
5305
+ value: trimmedValue,
5306
+ });
5307
+ }, [invalid, value, initValue, onSave, getSelectedRows, field]);
5308
+ const inputRef = useRef(null);
5309
+ useEffect(() => {
5310
+ inputRef.current?.focus();
5311
+ inputRef.current?.select();
5312
+ }, []);
5313
+ const { triggerSave } = useGridPopoverHook({
5314
+ className: props.className,
5315
+ invalid,
5316
+ save,
5317
+ });
5318
+ const tabDirectionRef = useRef(CloseReason.BLUR);
5319
+ const tabRecorder = useCallback((e) => {
5320
+ if (e.key === 'Tab') {
5321
+ tabDirectionRef.current = e.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD;
5322
+ e.preventDefault();
5323
+ e.stopPropagation();
5324
+ void triggerSave(tabDirectionRef.current);
5325
+ }
5326
+ }, [triggerSave]);
5327
+ useEffect(() => {
5328
+ const input = inputRef.current;
5329
+ input?.addEventListener('keydown', tabRecorder, { capture: true });
5330
+ return () => {
5331
+ input?.removeEventListener('keydown', tabRecorder, { capture: true });
5332
+ };
5333
+ }, [tabRecorder]);
5334
+ return (jsx("div", { className: clsx('GridFormInlineTextInput', invalid() && 'GridFormInlineTextInput-error'), title: String(invalid() ?? ''), children: jsx("input", { ref: inputRef, type: 'text', value: value, onChange: (e) => setValue(e.target.value), placeholder: props.placeholder ?? 'Type here', onBlur: () => {
5335
+ void triggerSave(CloseReason.BLUR);
5336
+ }, onKeyUp: (e) => {
5337
+ if (e.key === 'Enter') {
5338
+ void triggerSave(CloseReason.BLUR);
5339
+ }
5340
+ } }) }));
5341
+ };
5342
+
5177
5343
  const GridFormMessage = (props) => {
5178
5344
  const { selectedRows } = useGridPopoverContext();
5179
5345
  const [message, setMessage] = useState(null);
@@ -5585,62 +5751,6 @@ const TextAreaInput = (props) => {
5585
5751
  }, "data-allowtabtosave": props.allowTabToSave, children: props.value }) })] }), jsx(FormError, { error: props.error, helpText: props.helpText })] }));
5586
5752
  };
5587
5753
 
5588
- const TextInputValidator = (props, value, data, context) => {
5589
- if (value == null)
5590
- return null;
5591
- // This can happen because subcomponent is invoked without type safety
5592
- if (typeof value !== 'string') {
5593
- return 'Value is not a string';
5594
- }
5595
- if (props.required && value.trim() == '') {
5596
- return 'Must not be empty';
5597
- }
5598
- if (props.maxLength && value.length > props.maxLength) {
5599
- return `Must be no longer than ${props.maxLength} characters`;
5600
- }
5601
- if (props.maxBytes && stringByteLengthIsInvalid(value, props.maxBytes)) {
5602
- return `Must be no longer than ${props.maxBytes} bytes`;
5603
- }
5604
- const nf = props.numberFormat;
5605
- if (nf) {
5606
- if (value != '' && !isFloat(value)) {
5607
- return `Must be a valid number`;
5608
- }
5609
- if (value != '') {
5610
- const number = parseFloat(value);
5611
- if (nf.notZero && number === 0) {
5612
- return `Must not be 0`;
5613
- }
5614
- if (nf.gtMin != null && number <= nf.gtMin) {
5615
- return `Must be greater than ${nf.gtMin.toLocaleString()}`;
5616
- }
5617
- if (nf.geMin != null && number < nf.geMin) {
5618
- return `Must not be less than ${nf.geMin.toLocaleString()}`;
5619
- }
5620
- if (nf.ltMax != null && number >= nf.ltMax) {
5621
- return `Must be less than ${nf.ltMax.toLocaleString()}`;
5622
- }
5623
- if (nf.leMax != null && number > nf.leMax) {
5624
- return `Must not be greater than ${nf.leMax.toLocaleString()}`;
5625
- }
5626
- if (nf.precision != null && value != '') {
5627
- if (parseFloat(number.toPrecision(nf.precision)) != number) {
5628
- return `Must have no more than ${nf.precision} digits precision`;
5629
- }
5630
- }
5631
- if (nf.scale != null && value != '') {
5632
- if (parseFloat(number.toFixed(nf.scale)) != number) {
5633
- return nf.scale == 0 ? `Must be a whole number` : `Must have no more than ${nf.scale} decimal places`;
5634
- }
5635
- }
5636
- }
5637
- }
5638
- if (props.invalid) {
5639
- return props.invalid(value, data, context);
5640
- }
5641
- return null;
5642
- };
5643
-
5644
5754
  const GridFormSubComponentTextArea = (props) => {
5645
5755
  const { value, data, setValue, setValid, context } = useContext(GridSubComponentContext);
5646
5756
  const helpText = props.helpText ?? 'Press tab to save';
@@ -5819,6 +5929,11 @@ const GridEditBoolean = (colDef, editorProps) => {
5819
5929
  });
5820
5930
  };
5821
5931
 
5932
+ const GridInlineTextInput = (colDef, params) => GridCell(colDef, {
5933
+ editor: GridFormInlineTextInput,
5934
+ ...params,
5935
+ });
5936
+
5822
5937
  const GridPopoutEditMultiSelect = (colDef, props) => GridCell(colDef, {
5823
5938
  editor: GridFormMultiSelect,
5824
5939
  ...props,
@@ -5959,6 +6074,7 @@ const GridContextProvider = (props) => {
5959
6074
  const [invisibleColumnIds, _setInvisibleColumnIds] = useState();
5960
6075
  const testId = useRef();
5961
6076
  const hasExternallySelectedItemsRef = useRef(false);
6077
+ const enableMultilineBulkEditRef = useRef(false);
5962
6078
  const idsBeforeUpdate = useRef([]);
5963
6079
  const prePopupFocusedCell = useRef();
5964
6080
  const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
@@ -6023,8 +6139,9 @@ const GridContextProvider = (props) => {
6023
6139
  /**
6024
6140
  * Set the grid api when the grid is ready.
6025
6141
  */
6026
- const setApis = useCallback((gridApi, hasExternallySelectedItems, dataTestId) => {
6142
+ const setApis = useCallback((gridApi, hasExternallySelectedItems, enableMultilineBulkEdit, dataTestId) => {
6027
6143
  hasExternallySelectedItemsRef.current = hasExternallySelectedItems;
6144
+ enableMultilineBulkEditRef.current = enableMultilineBulkEdit;
6028
6145
  testId.current = dataTestId;
6029
6146
  setGridApi(gridApi);
6030
6147
  gridApi?.setGridOption('quickFilterText', quickFilter);
@@ -6169,7 +6286,7 @@ const GridContextProvider = (props) => {
6169
6286
  if (flash) {
6170
6287
  delay(() => {
6171
6288
  try {
6172
- !gridApi.isDestroyed && gridApi.flashCells({ rowNodes });
6289
+ !gridApi.isDestroyed() && gridApi.flashCells({ rowNodes });
6173
6290
  }
6174
6291
  catch {
6175
6292
  // ignore, flash cells sometimes throws errors as nodes have gone out of scope
@@ -6235,13 +6352,13 @@ const GridContextProvider = (props) => {
6235
6352
  * Then we size the flexed columns.
6236
6353
  */
6237
6354
  const autoSizeColumns = useCallback(async ({ skipHeader, colIds, userSizedColIds } = {}) => {
6238
- if (!gridApi || !gridApi.getColumnState()) {
6355
+ if (!gridApi || gridApi.isDestroyed() || !gridApi.getColumnState()) {
6239
6356
  return null;
6240
6357
  }
6241
6358
  try {
6242
6359
  const colIdsSet = colIds instanceof Set ? colIds : new Set(colIds);
6243
6360
  const getVisibleColStates = () => {
6244
- const colStates = gridApi.getColumnState();
6361
+ const colStates = gridApi.getColumnState() ?? [];
6245
6362
  return colStates.filter((colState) => {
6246
6363
  const colId = colState.colId;
6247
6364
  return (isEmpty(colIdsSet) || colIdsSet.has(colId)) && !userSizedColIds?.has(colId);
@@ -6301,9 +6418,11 @@ const GridContextProvider = (props) => {
6301
6418
  if (!gridApi || gridApi.isDestroyed() || startCellEditingInProgressRef.current) {
6302
6419
  return;
6303
6420
  }
6304
- if (prePopupFocusedCell.current) {
6305
- gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
6306
- prePopupFocusedCell.current = undefined;
6421
+ const cellToFocus = prePopupFocusedCell.current;
6422
+ if (cellToFocus) {
6423
+ defer(() => {
6424
+ gridApi.setFocusedCell(cellToFocus.rowIndex, cellToFocus.column);
6425
+ });
6307
6426
  }
6308
6427
  }, [gridApi]);
6309
6428
  // waitForExternallySelectedItemsToBeInSync can't use the state as it won't be updated during function execution
@@ -6369,9 +6488,8 @@ const GridContextProvider = (props) => {
6369
6488
  }, [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
6370
6489
  const bulkEditingCompleteCallbackRef = useRef();
6371
6490
  const onBulkEditingComplete = useCallback(async () => {
6372
- resetFocusedCellAfterCellEditing();
6373
6491
  await bulkEditingCompleteCallbackRef.current?.();
6374
- }, [resetFocusedCellAfterCellEditing]);
6492
+ }, []);
6375
6493
  const setOnBulkEditingComplete = useCallback((cellEditingCompleteCallback) => {
6376
6494
  bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
6377
6495
  }, []);
@@ -6411,10 +6529,18 @@ const GridContextProvider = (props) => {
6411
6529
  return true;
6412
6530
  }
6413
6531
  const postRow = gridApi.getFocusedCell();
6414
- if (preRow?.rowIndex !== postRow?.rowIndex || preRow?.column === postRow?.column) {
6415
- // We didn't find an editable cell in the same row, or the cell column didn't change
6416
- // implying it was start/end of grid
6417
- break;
6532
+ if (enableMultilineBulkEditRef.current) {
6533
+ // Last cell in grid, nowhere to tab
6534
+ if (preRow?.rowIndex === postRow?.rowIndex && preRow?.column === postRow?.column) {
6535
+ break;
6536
+ }
6537
+ }
6538
+ else {
6539
+ if (preRow?.rowIndex !== postRow?.rowIndex || preRow?.column === postRow?.column) {
6540
+ // We didn't find an editable cell in the same row, or the cell column didn't change
6541
+ // implying it was start/end of grid
6542
+ break;
6543
+ }
6418
6544
  }
6419
6545
  if (focusedCellIsEditable()) {
6420
6546
  const focusedCell = gridApi.getFocusedCell();
@@ -6769,5 +6895,5 @@ const useDeferredPromise = () => {
6769
6895
  };
6770
6896
  };
6771
6897
 
6772
- export { ActionButton, CancelPromise, ComponentLoadingWrapper, ControlledMenu, Editor, FocusableItem, FormError, GenericCellEditorComponentWrapper, Grid, GridButton, GridCell, GridCellBearingValueFormatter, GridCellFiller, GridCellFillerColId, GridCellMultiEditor, GridCellMultiSelectClassRules, GridCellRenderer, GridContext, GridContextProvider, GridEditBoolean, GridFilterButtons, GridFilterColumnsMultiSelect, GridFilterColumnsToggle, GridFilterDownloadCsvButton, GridFilterHeaderIconButton, GridFilterQuick, GridFilters, GridFormDropDown, GridFormEditBearing, GridFormMessage, GridFormMultiSelect, GridFormMultiSelectGrid, GridFormPopoverMenu, GridFormSubComponentTextArea, GridFormSubComponentTextInput, GridFormTextArea, GridFormTextInput, GridIcon, GridLoadableCell, GridNoRowsOverlay, GridNoRowsOverlayFr, GridPopoutEditMultiSelect, GridPopoutEditMultiSelectGrid, GridPopoverContext, GridPopoverContextProvider, GridPopoverEditBearing, GridPopoverEditBearingCorrection, GridPopoverEditBearingCorrectionEditorParams, GridPopoverEditBearingEditorParams, GridPopoverEditDropDown, GridPopoverMenu, GridPopoverMessage, GridPopoverTextArea, GridPopoverTextInput, GridRenderPopoutMenuCell, GridSubComponentContext, GridUpdatingContext, GridUpdatingContextProvider, GridWrapper, Menu, MenuButton, MenuDivider, MenuGroup, MenuHeader, MenuHeaderItem, MenuHeaderString, MenuItem, MenuRadioGroup, MenuSeparator, MenuSeparatorString, PopoutMenuSeparator, SubMenu, TextAreaInput, TextInputFormatted, TextInputValidator, agGridSelectRowColId, bearingCorrectionRangeValidator, bearingCorrectionValueFormatter, bearingNumberParser, bearingRangeValidator, bearingStringValidator, bearingValueFormatter, colStateFlexed, colStateId, colStateNotFlexed, compareNaturalInsensitive, convertDDToDMS, createCheckboxMultiFilterParams, defaultValueFormatter, downloadCsvUseValueFormattersProcessCellCallback, findParentWithClass, fnOrVar, generateFilterGetter, hasParentClass, isFloat, isGridCellFiller, isNotEmpty, primitiveToSelectOption, sanitiseFileName, stringByteLengthIsInvalid, suppressCellKeyboardEvents, textMatch, useDeferredPromise, useGridContext, useGridContextMenu, useGridFilter, useGridPopoverContext, useGridPopoverHook, useMenuState, usePostSortRowsHook, wait, waitForCondition };
6898
+ export { ActionButton, CancelPromise, ComponentLoadingWrapper, ControlledMenu, Editor, FocusableItem, FormError, GenericCellEditorComponentWrapper, Grid, GridButton, GridCell, GridCellBearingValueFormatter, GridCellFiller, GridCellFillerColId, GridCellMultiEditor, GridCellMultiSelectClassRules, GridCellRenderer, GridContext, GridContextProvider, GridEditBoolean, GridFilterButtons, GridFilterColumnsMultiSelect, GridFilterColumnsToggle, GridFilterDownloadCsvButton, GridFilterHeaderIconButton, GridFilterQuick, GridFilters, GridFormDropDown, GridFormEditBearing, GridFormInlineTextInput, GridFormMessage, GridFormMultiSelect, GridFormMultiSelectGrid, GridFormPopoverMenu, GridFormSubComponentTextArea, GridFormSubComponentTextInput, GridFormTextArea, GridFormTextInput, GridIcon, GridInlineTextInput, GridLoadableCell, GridNoRowsOverlay, GridNoRowsOverlayFr, GridPopoutEditMultiSelect, GridPopoutEditMultiSelectGrid, GridPopoverContext, GridPopoverContextProvider, GridPopoverEditBearing, GridPopoverEditBearingCorrection, GridPopoverEditBearingCorrectionEditorParams, GridPopoverEditBearingEditorParams, GridPopoverEditDropDown, GridPopoverMenu, GridPopoverMessage, GridPopoverTextArea, GridPopoverTextInput, GridRenderPopoutMenuCell, GridSubComponentContext, GridUpdatingContext, GridUpdatingContextProvider, GridWrapper, Menu, MenuButton, MenuDivider, MenuGroup, MenuHeader, MenuHeaderItem, MenuHeaderString, MenuItem, MenuRadioGroup, MenuSeparator, MenuSeparatorString, PopoutMenuSeparator, SubMenu, TextAreaInput, TextInputFormatted, TextInputValidator, agGridSelectRowColId, bearingCorrectionRangeValidator, bearingCorrectionValueFormatter, bearingNumberParser, bearingRangeValidator, bearingStringValidator, bearingValueFormatter, colStateFlexed, colStateId, colStateNotFlexed, compareNaturalInsensitive, convertDDToDMS, createCheckboxMultiFilterParams, defaultValueFormatter, downloadCsvUseValueFormattersProcessCellCallback, findParentWithClass, fnOrVar, generateFilterGetter, hasParentClass, isFloat, isGridCellFiller, isNotEmpty, primitiveToSelectOption, sanitiseFileName, stringByteLengthIsInvalid, suppressCellKeyboardEvents, textMatch, useDeferredPromise, useGridContext, useGridContextMenu, useGridFilter, useGridPopoverContext, useGridPopoverHook, useMenuState, usePostSortRowsHook, wait, waitForCondition };
6773
6899
  //# sourceMappingURL=step-ag-grid.esm.js.map