@linzjs/step-ag-grid 29.15.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 (35) 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/gridFilter/GridFilterColumnsMultiSelect.d.ts +2 -1
  5. package/dist/src/components/gridForm/GridFormInlineTextInput.d.ts +15 -0
  6. package/dist/src/components/gridForm/index.d.ts +1 -0
  7. package/dist/src/components/gridHook/useGridRangeSelection.d.ts +1 -1
  8. package/dist/src/components/gridPopoverEdit/GridInlineTextInput.d.ts +5 -0
  9. package/dist/src/components/gridPopoverEdit/index.d.ts +1 -0
  10. package/dist/src/contexts/GridContext.d.ts +1 -1
  11. package/dist/src/utils/__tests__/storybookTestUtil.ts +5 -1
  12. package/dist/step-ag-grid.cjs +233 -90
  13. package/dist/step-ag-grid.cjs.map +1 -1
  14. package/dist/step-ag-grid.esm.js +232 -91
  15. package/dist/step-ag-grid.esm.js.map +1 -1
  16. package/package.json +14 -12
  17. package/src/components/Grid.tsx +33 -10
  18. package/src/components/GridPopoverHook.tsx +5 -2
  19. package/src/components/gridFilter/GridFilterColumnsMultiSelect.tsx +23 -4
  20. package/src/components/gridForm/GridFormInlineTextInput.tsx +119 -0
  21. package/src/components/gridForm/index.ts +1 -0
  22. package/src/components/gridHook/useGridContextMenu.tsx +3 -1
  23. package/src/components/gridHook/useGridCopy.ts +5 -2
  24. package/src/components/gridHook/useGridRangeSelection.ts +25 -4
  25. package/src/components/gridPopoverEdit/GridInlineTextInput.ts +13 -0
  26. package/src/components/gridPopoverEdit/index.ts +1 -0
  27. package/src/contexts/GridContext.tsx +6 -1
  28. package/src/contexts/GridContextProvider.tsx +30 -13
  29. package/src/stories/grid/GridFilterColumnsMultiSelect.stories.tsx +52 -7
  30. package/src/stories/grid/GridInlineText.stories.tsx +185 -0
  31. package/src/stories/grid/interactions/GridKeyboardInteractions.stories.tsx +3 -3
  32. package/src/styles/Grid.scss +5 -0
  33. package/src/styles/GridFormInlineTextInput.scss +23 -0
  34. package/src/styles/index.scss +1 -1
  35. 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
@@ -4247,10 +4288,18 @@ class GridFilterColumnsMultiSelect {
4247
4288
  const norm = this.normalizeCellValue(raw);
4248
4289
  values.add(norm);
4249
4290
  });
4291
+ const { customOrder } = this.params;
4292
+ const orderMap = customOrder?.length ? new Map(customOrder.map((val, idx) => [val, idx])) : null;
4250
4293
  return Array.from(values).sort((a, b) => {
4251
- if (a === EMPTY_KEY && b !== EMPTY_KEY)
4252
- return -1;
4253
- if (b === EMPTY_KEY && a !== EMPTY_KEY)
4294
+ if (orderMap) {
4295
+ const aIdx = orderMap.get(a) ?? Infinity;
4296
+ const bIdx = orderMap.get(b) ?? Infinity;
4297
+ if (aIdx !== bIdx)
4298
+ return aIdx - bIdx;
4299
+ }
4300
+ if (a === EMPTY_KEY)
4301
+ return b === EMPTY_KEY ? 0 : -1;
4302
+ if (b === EMPTY_KEY)
4254
4303
  return 1;
4255
4304
  return a.localeCompare(b);
4256
4305
  });
@@ -4297,9 +4346,12 @@ class GridFilterColumnsMultiSelect {
4297
4346
  return this.gui;
4298
4347
  }
4299
4348
  isFilterActive() {
4300
- return this.selectedValues.size > 0;
4349
+ return this.selectedValues.size !== this.allValues.length;
4301
4350
  }
4302
4351
  doesFilterPass(p) {
4352
+ if (this.selectedValues.size === 0) {
4353
+ return false;
4354
+ }
4303
4355
  const field = this.params.colDef.field;
4304
4356
  if (!p.data || typeof p.data !== 'object') {
4305
4357
  return this.selectedValues.has(EMPTY_KEY);
@@ -4309,7 +4361,10 @@ class GridFilterColumnsMultiSelect {
4309
4361
  return this.selectedValues.has(norm);
4310
4362
  }
4311
4363
  getModel() {
4312
- return this.selectedValues.size > 0 ? { values: Array.from(this.selectedValues) } : null;
4364
+ if (this.selectedValues.size !== this.allValues.length) {
4365
+ return { values: Array.from(this.selectedValues) };
4366
+ }
4367
+ return null;
4313
4368
  }
4314
4369
  setModel(model) {
4315
4370
  this.selectedValues = new Set(model?.values || []);
@@ -4322,9 +4377,10 @@ class GridFilterColumnsMultiSelect {
4322
4377
  }
4323
4378
  }
4324
4379
  }
4325
- const createCheckboxMultiFilterParams = (labels = {}, labelFormatter) => ({
4380
+ const createCheckboxMultiFilterParams = (labels = {}, labelFormatter, customOrder) => ({
4326
4381
  labels: { [EMPTY_KEY]: DEFAULT_EMPTY_LABEL, ...labels },
4327
4382
  labelFormatter,
4383
+ customOrder,
4328
4384
  });
4329
4385
 
4330
4386
  const GridFilterHeaderIconButton = forwardRef(function columnsButton({ icon, title, onClick, buttonProps, disabled = false, size = 'md' }, ref) {
@@ -4771,6 +4827,7 @@ const textMatch = (text, filter) => {
4771
4827
 
4772
4828
  const CancelPromise = () => Promise.resolve(true);
4773
4829
  const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick, }) => {
4830
+ const { resetFocusedCellAfterCellEditing } = useGridContext();
4774
4831
  const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext();
4775
4832
  const saveButtonRef = useRef(null);
4776
4833
  const [isOpen, setOpen] = useState(false);
@@ -4781,6 +4838,7 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
4781
4838
  if (reason == CloseReason.CANCEL) {
4782
4839
  await updateValue(CancelPromise, 0);
4783
4840
  stopEditing();
4841
+ resetFocusedCellAfterCellEditing();
4784
4842
  return;
4785
4843
  }
4786
4844
  if (invalid?.()) {
@@ -4789,8 +4847,9 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
4789
4847
  }
4790
4848
  if (await updateValue(save ?? (() => Promise.resolve(true)), reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
4791
4849
  stopEditing();
4850
+ resetFocusedCellAfterCellEditing();
4792
4851
  }
4793
- }, [invalid, save, stopEditing, updateValue]);
4852
+ }, [invalid, resetFocusedCellAfterCellEditing, save, stopEditing, updateValue]);
4794
4853
  const popoverWrapper = useCallback((children) => {
4795
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) => {
4796
4855
  // Prevent menu from closing when modals are invoked
@@ -5159,6 +5218,128 @@ const GridFormEditBearing = (props) => {
5159
5218
  : props.formatValue(bearingNumberParser(value)), error: bearingStringValidator(value, props.range), helpText: 'Press enter or tab to save' }) }));
5160
5219
  };
5161
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
+
5162
5343
  const GridFormMessage = (props) => {
5163
5344
  const { selectedRows } = useGridPopoverContext();
5164
5345
  const [message, setMessage] = useState(null);
@@ -5570,62 +5751,6 @@ const TextAreaInput = (props) => {
5570
5751
  }, "data-allowtabtosave": props.allowTabToSave, children: props.value }) })] }), jsx(FormError, { error: props.error, helpText: props.helpText })] }));
5571
5752
  };
5572
5753
 
5573
- const TextInputValidator = (props, value, data, context) => {
5574
- if (value == null)
5575
- return null;
5576
- // This can happen because subcomponent is invoked without type safety
5577
- if (typeof value !== 'string') {
5578
- return 'Value is not a string';
5579
- }
5580
- if (props.required && value.trim() == '') {
5581
- return 'Must not be empty';
5582
- }
5583
- if (props.maxLength && value.length > props.maxLength) {
5584
- return `Must be no longer than ${props.maxLength} characters`;
5585
- }
5586
- if (props.maxBytes && stringByteLengthIsInvalid(value, props.maxBytes)) {
5587
- return `Must be no longer than ${props.maxBytes} bytes`;
5588
- }
5589
- const nf = props.numberFormat;
5590
- if (nf) {
5591
- if (value != '' && !isFloat(value)) {
5592
- return `Must be a valid number`;
5593
- }
5594
- if (value != '') {
5595
- const number = parseFloat(value);
5596
- if (nf.notZero && number === 0) {
5597
- return `Must not be 0`;
5598
- }
5599
- if (nf.gtMin != null && number <= nf.gtMin) {
5600
- return `Must be greater than ${nf.gtMin.toLocaleString()}`;
5601
- }
5602
- if (nf.geMin != null && number < nf.geMin) {
5603
- return `Must not be less than ${nf.geMin.toLocaleString()}`;
5604
- }
5605
- if (nf.ltMax != null && number >= nf.ltMax) {
5606
- return `Must be less than ${nf.ltMax.toLocaleString()}`;
5607
- }
5608
- if (nf.leMax != null && number > nf.leMax) {
5609
- return `Must not be greater than ${nf.leMax.toLocaleString()}`;
5610
- }
5611
- if (nf.precision != null && value != '') {
5612
- if (parseFloat(number.toPrecision(nf.precision)) != number) {
5613
- return `Must have no more than ${nf.precision} digits precision`;
5614
- }
5615
- }
5616
- if (nf.scale != null && value != '') {
5617
- if (parseFloat(number.toFixed(nf.scale)) != number) {
5618
- return nf.scale == 0 ? `Must be a whole number` : `Must have no more than ${nf.scale} decimal places`;
5619
- }
5620
- }
5621
- }
5622
- }
5623
- if (props.invalid) {
5624
- return props.invalid(value, data, context);
5625
- }
5626
- return null;
5627
- };
5628
-
5629
5754
  const GridFormSubComponentTextArea = (props) => {
5630
5755
  const { value, data, setValue, setValid, context } = useContext(GridSubComponentContext);
5631
5756
  const helpText = props.helpText ?? 'Press tab to save';
@@ -5804,6 +5929,11 @@ const GridEditBoolean = (colDef, editorProps) => {
5804
5929
  });
5805
5930
  };
5806
5931
 
5932
+ const GridInlineTextInput = (colDef, params) => GridCell(colDef, {
5933
+ editor: GridFormInlineTextInput,
5934
+ ...params,
5935
+ });
5936
+
5807
5937
  const GridPopoutEditMultiSelect = (colDef, props) => GridCell(colDef, {
5808
5938
  editor: GridFormMultiSelect,
5809
5939
  ...props,
@@ -5944,6 +6074,7 @@ const GridContextProvider = (props) => {
5944
6074
  const [invisibleColumnIds, _setInvisibleColumnIds] = useState();
5945
6075
  const testId = useRef();
5946
6076
  const hasExternallySelectedItemsRef = useRef(false);
6077
+ const enableMultilineBulkEditRef = useRef(false);
5947
6078
  const idsBeforeUpdate = useRef([]);
5948
6079
  const prePopupFocusedCell = useRef();
5949
6080
  const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
@@ -6008,8 +6139,9 @@ const GridContextProvider = (props) => {
6008
6139
  /**
6009
6140
  * Set the grid api when the grid is ready.
6010
6141
  */
6011
- const setApis = useCallback((gridApi, hasExternallySelectedItems, dataTestId) => {
6142
+ const setApis = useCallback((gridApi, hasExternallySelectedItems, enableMultilineBulkEdit, dataTestId) => {
6012
6143
  hasExternallySelectedItemsRef.current = hasExternallySelectedItems;
6144
+ enableMultilineBulkEditRef.current = enableMultilineBulkEdit;
6013
6145
  testId.current = dataTestId;
6014
6146
  setGridApi(gridApi);
6015
6147
  gridApi?.setGridOption('quickFilterText', quickFilter);
@@ -6154,7 +6286,7 @@ const GridContextProvider = (props) => {
6154
6286
  if (flash) {
6155
6287
  delay(() => {
6156
6288
  try {
6157
- !gridApi.isDestroyed && gridApi.flashCells({ rowNodes });
6289
+ !gridApi.isDestroyed() && gridApi.flashCells({ rowNodes });
6158
6290
  }
6159
6291
  catch {
6160
6292
  // ignore, flash cells sometimes throws errors as nodes have gone out of scope
@@ -6220,13 +6352,13 @@ const GridContextProvider = (props) => {
6220
6352
  * Then we size the flexed columns.
6221
6353
  */
6222
6354
  const autoSizeColumns = useCallback(async ({ skipHeader, colIds, userSizedColIds } = {}) => {
6223
- if (!gridApi || !gridApi.getColumnState()) {
6355
+ if (!gridApi || gridApi.isDestroyed() || !gridApi.getColumnState()) {
6224
6356
  return null;
6225
6357
  }
6226
6358
  try {
6227
6359
  const colIdsSet = colIds instanceof Set ? colIds : new Set(colIds);
6228
6360
  const getVisibleColStates = () => {
6229
- const colStates = gridApi.getColumnState();
6361
+ const colStates = gridApi.getColumnState() ?? [];
6230
6362
  return colStates.filter((colState) => {
6231
6363
  const colId = colState.colId;
6232
6364
  return (isEmpty(colIdsSet) || colIdsSet.has(colId)) && !userSizedColIds?.has(colId);
@@ -6286,9 +6418,11 @@ const GridContextProvider = (props) => {
6286
6418
  if (!gridApi || gridApi.isDestroyed() || startCellEditingInProgressRef.current) {
6287
6419
  return;
6288
6420
  }
6289
- if (prePopupFocusedCell.current) {
6290
- gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
6291
- prePopupFocusedCell.current = undefined;
6421
+ const cellToFocus = prePopupFocusedCell.current;
6422
+ if (cellToFocus) {
6423
+ defer(() => {
6424
+ gridApi.setFocusedCell(cellToFocus.rowIndex, cellToFocus.column);
6425
+ });
6292
6426
  }
6293
6427
  }, [gridApi]);
6294
6428
  // waitForExternallySelectedItemsToBeInSync can't use the state as it won't be updated during function execution
@@ -6354,9 +6488,8 @@ const GridContextProvider = (props) => {
6354
6488
  }, [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
6355
6489
  const bulkEditingCompleteCallbackRef = useRef();
6356
6490
  const onBulkEditingComplete = useCallback(async () => {
6357
- resetFocusedCellAfterCellEditing();
6358
6491
  await bulkEditingCompleteCallbackRef.current?.();
6359
- }, [resetFocusedCellAfterCellEditing]);
6492
+ }, []);
6360
6493
  const setOnBulkEditingComplete = useCallback((cellEditingCompleteCallback) => {
6361
6494
  bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
6362
6495
  }, []);
@@ -6396,10 +6529,18 @@ const GridContextProvider = (props) => {
6396
6529
  return true;
6397
6530
  }
6398
6531
  const postRow = gridApi.getFocusedCell();
6399
- if (preRow?.rowIndex !== postRow?.rowIndex || preRow?.column === postRow?.column) {
6400
- // We didn't find an editable cell in the same row, or the cell column didn't change
6401
- // implying it was start/end of grid
6402
- 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
+ }
6403
6544
  }
6404
6545
  if (focusedCellIsEditable()) {
6405
6546
  const focusedCell = gridApi.getFocusedCell();
@@ -6754,5 +6895,5 @@ const useDeferredPromise = () => {
6754
6895
  };
6755
6896
  };
6756
6897
 
6757
- 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 };
6758
6899
  //# sourceMappingURL=step-ag-grid.esm.js.map