@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
package/README.md CHANGED
@@ -276,5 +276,5 @@ If your grid has a data-testid a global will be exposed in window with the helpe
276
276
  This will throw an exception if the row id is not found.
277
277
 
278
278
  ```tsx
279
- window.__stepAgGrid.grids[dataTestId].scrollRowIntoViewById("1000")
279
+ window.__stepAgGrid.grids[dataTestId].scrollRowIntoViewById("1001")
280
280
  ```
package/dist/index.css CHANGED
@@ -340,6 +340,11 @@
340
340
  flex-direction: column;
341
341
  }
342
342
 
343
+ .Grid-hideSelectedRow div.ag-row::before {
344
+ content: "";
345
+ background-color: white !important;
346
+ }
347
+
343
348
  .Grid-wrapper {
344
349
  display: flex;
345
350
  flex-direction: column;
@@ -636,3 +641,25 @@ div.ag-row div.ag-cell.rangeSelectBottom {
636
641
  .MenuItem-icon {
637
642
  margin-right: 8px;
638
643
  }
644
+
645
+ .GridFormInlineTextInput {
646
+ height: 100%;
647
+ }
648
+
649
+ .GridFormInlineTextInput-error > input {
650
+ border: 2px solid red !important;
651
+ }
652
+
653
+ .GridFormInlineTextInput > input {
654
+ display: block;
655
+ left: 0;
656
+ top: 0;
657
+ right: 0;
658
+ bottom: 0;
659
+ position: absolute;
660
+ padding-left: 9px;
661
+ padding-right: 9px;
662
+ outline: 0;
663
+ border: 0;
664
+ font-family: "Open Sans", system-ui, sans-serif;
665
+ }
@@ -23,16 +23,17 @@ export interface GridProps<TData extends GridBaseRow = GridBaseRow> {
23
23
  enableClickSelection?: boolean;
24
24
  enableRangeSelection?: boolean;
25
25
  enableSelectionWithoutKeys?: boolean;
26
+ enableMultilineBulkEdit?: boolean;
26
27
  externalSelectedIds?: TData['id'][];
27
28
  externalSelectedItems?: TData[];
28
29
  hideSelectColumn?: boolean;
30
+ hideSelectedRow?: boolean;
29
31
  selectColumnPinned?: ColDef['pinned'];
30
32
  selectable?: boolean;
31
33
  setExternalSelectedIds?: (ids: TData['id'][]) => void;
32
34
  setExternalSelectedItems?: (items: TData[]) => void;
33
35
  onBulkEditingComplete?: () => Promise<void> | void;
34
36
  singleClickEdit?: boolean;
35
- stopEditingWhenCellsLoseFocus?: boolean;
36
37
  contextMenu?: GridContextMenuComponent<TData>;
37
38
  contextMenuSelectRow?: boolean;
38
39
  onCellFocused?: (props: {
@@ -64,4 +65,4 @@ export interface GridProps<TData extends GridBaseRow = GridBaseRow> {
64
65
  /**
65
66
  * Wrapper for AgGrid to add commonly used functionality.
66
67
  */
67
- export declare const Grid: <TData extends GridBaseRow = GridBaseRow>({ theme, "data-testid": dataTestId, suppressReadOnlyStyle, defaultPostSort, rowData, rowHeight, rowSelection, sizeColumns, autoSelectFirstRow, enableRangeSelection, externalSelectedIds, externalSelectedItems, selectColumnPinned, selectable, setExternalSelectedIds, setExternalSelectedItems, singleClickEdit, stopEditingWhenCellsLoseFocus, contextMenuSelectRow, contextMenu, onCellFocused: paramsOnCellFocused, onColumnMoved, suppressColumnVirtualization, maxInitialWidth, ...params }: GridProps<TData>) => ReactElement;
68
+ export declare const Grid: <TData extends GridBaseRow = GridBaseRow>({ theme, "data-testid": dataTestId, suppressReadOnlyStyle, defaultPostSort, rowData, rowHeight, rowSelection, sizeColumns, autoSelectFirstRow, enableRangeSelection, externalSelectedIds, externalSelectedItems, selectColumnPinned, selectable, setExternalSelectedIds, setExternalSelectedItems, singleClickEdit, enableMultilineBulkEdit, contextMenuSelectRow, contextMenu, onCellFocused: paramsOnCellFocused, onColumnMoved, suppressColumnVirtualization, maxInitialWidth, ...params }: GridProps<TData>) => ReactElement;
@@ -0,0 +1,15 @@
1
+ import { TextInputValidatorProps } from '../../utils/textValidator';
2
+ import { MaybePromise } from '../../utils/util';
3
+ import { CellEditorCommon } from '../GridCell';
4
+ import { GridBaseRow } from '../types';
5
+ export interface GridFormInlineTextInput<TData extends GridBaseRow> extends TextInputValidatorProps<TData>, CellEditorCommon {
6
+ placeholder?: string;
7
+ units?: string;
8
+ onSave?: (props: {
9
+ selectedRows: TData[];
10
+ selectedRowIds: TData['id'][];
11
+ value: string;
12
+ }) => MaybePromise<boolean>;
13
+ helpText?: string;
14
+ }
15
+ export declare const GridFormInlineTextInput: <TData extends GridBaseRow>(props: GridFormInlineTextInput<TData>) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +1,6 @@
1
1
  export * from './GridFormDropDown';
2
2
  export * from './GridFormEditBearing';
3
+ export * from './GridFormInlineTextInput';
3
4
  export * from './GridFormMessage';
4
5
  export * from './GridFormMultiSelect';
5
6
  export * from './GridFormMultiSelectGrid';
@@ -4,7 +4,7 @@ import { GridBaseRow } from '../types';
4
4
  export interface CellLocation {
5
5
  rowId: string;
6
6
  colId: string;
7
- timestamp: number;
7
+ clickLocation: [number, number];
8
8
  }
9
9
  export interface GridRanges<TData extends GridBaseRow> {
10
10
  selectedColIds: string[];
@@ -0,0 +1,5 @@
1
+ import { GenericCellEditorProps } from '../GridCell';
2
+ import { GridFormInlineTextInput } from '../gridForm/GridFormInlineTextInput';
3
+ import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
4
+ import { ColDefT, GridBaseRow } from '../types';
5
+ export declare const GridInlineTextInput: <TData extends GridBaseRow, TValue = any>(colDef: GenericCellColDef<TData, TValue>, params: GenericCellEditorProps<GridFormInlineTextInput<TData>>) => ColDefT<TData, TValue>;
@@ -1,5 +1,6 @@
1
1
  export * from './GridButton';
2
2
  export * from './GridEditBoolean';
3
+ export * from './GridInlineTextInput';
3
4
  export * from './GridPopoutEditMultiSelect';
4
5
  export * from './GridPopoutEditMultiSelectGrid';
5
6
  export * from './GridPopoverEditBearing';
@@ -20,7 +20,7 @@ export interface GridContextType<TData extends GridBaseRow> {
20
20
  getColDef: (colId?: string) => ColDef | undefined;
21
21
  getColumns: (filter?: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string)) => ColDefT<TData, any>[];
22
22
  getColumnIds: (filter?: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string)) => string[];
23
- setApis: (gridApi: GridApi | undefined, hasExternallySelectedItems: boolean, dataTestId?: string) => void;
23
+ setApis: (gridApi: GridApi | undefined, hasExternallySelectedItems: boolean, enableMultilineBulkEdit: boolean, dataTestId?: string) => void;
24
24
  prePopupOps: () => void;
25
25
  setQuickFilter: (quickFilter: string) => void;
26
26
  editingCells: () => boolean;
@@ -1,7 +1,7 @@
1
1
  import { sortBy } from 'lodash-es';
2
2
  import { expect, userEvent, waitFor } from 'storybook/test';
3
3
 
4
- import { findQuick, getAllQuick } from './testQuick';
4
+ import { findQuick, getAllQuick, queryAllQuick } from './testQuick';
5
5
 
6
6
  export const waitForGridReady = ({ canvasElement }: { canvasElement: HTMLElement }) =>
7
7
  waitFor(() => expect(canvasElement.querySelector('.Grid-ready')).toBeInTheDocument(), { timeout: 5000 });
@@ -22,6 +22,10 @@ export const clickColumnHeaderToSort = async (colId: string, within: HTMLElement
22
22
  await user.keyboard('{Enter}');
23
23
  };
24
24
 
25
+ export const getNumberOfGridRows = (props?: { grid?: HTMLElement }): number => {
26
+ return queryAllQuick({ classes: '.ag-row' }, props?.grid).length;
27
+ };
28
+
25
29
  export const gridColumnValues = (colId: string, within: HTMLElement): string[] => {
26
30
  return sortBy(
27
31
  getAllQuick({ tagName: `[col-id="${colId}"]`, classes: `.ag-cell` }, within),
@@ -2634,8 +2634,9 @@ const useGridContextMenu = ({ contextMenuSelectRow, contextMenu: ContextMenu, co
2634
2634
  const clickedRowRef = React.useRef(null);
2635
2635
  const eventRef = React.useRef(undefined);
2636
2636
  const openMenu = React.useCallback((e) => {
2637
- if (!e)
2637
+ if (!e) {
2638
2638
  return;
2639
+ }
2639
2640
  prePopupOps();
2640
2641
  setAnchorPoint({ x: e.clientX, y: e.clientY });
2641
2642
  setOpen(true);
@@ -2905,7 +2906,11 @@ const useGridCopy = ({ ranges, rangeStartRef, rangeEndRef, hasSelectedMoreThanOn
2905
2906
  contextMenu: (GridRangeSelectContextMenu),
2906
2907
  context: { onCopy, copyType, setCopyType },
2907
2908
  });
2908
- const rangeSelectInterceptContextMenu = React.useCallback((event) => rangeStartRef.current == null ? cellContextMenu(event) : rangeSelectContextMenu(event), [cellContextMenu, rangeSelectContextMenu]);
2909
+ const rangeSelectInterceptContextMenu = React.useCallback((event) => {
2910
+ return hasSelectedMoreThanOneCellRef.current && rangeStartRef.current !== null
2911
+ ? rangeSelectContextMenu(event)
2912
+ : cellContextMenu(event);
2913
+ }, [cellContextMenu, rangeSelectContextMenu]);
2909
2914
  return {
2910
2915
  onCopyEvent,
2911
2916
  rangeSelectInterceptContextMenu,
@@ -3029,6 +3034,23 @@ const useGridRangeSelection = ({ enableRangeSelection, gridDivRef, rangeStartRef
3029
3034
  rangeEndRef.current = null;
3030
3035
  updateRangeSelectionCellClasses();
3031
3036
  }, [updateRangeSelectionCellClasses]);
3037
+ const mouseDownRef = React.useRef([0, 0]);
3038
+ const trackMouseDown = React.useCallback((e) => {
3039
+ mouseDownRef.current = [e.screenX, e.screenY];
3040
+ }, []);
3041
+ const trackMouseUp = React.useCallback((e) => {
3042
+ if (rangeEndRef.current) {
3043
+ rangeEndRef.current.clickLocation = [e.screenX, e.screenY];
3044
+ }
3045
+ }, []);
3046
+ React.useEffect(() => {
3047
+ document.addEventListener('mousedown', trackMouseDown, { capture: true });
3048
+ document.addEventListener('mouseup', trackMouseUp, { capture: true });
3049
+ return () => {
3050
+ document.removeEventListener('mousemove', trackMouseDown, { capture: true });
3051
+ document.removeEventListener('mouseup', trackMouseUp, { capture: true });
3052
+ };
3053
+ }, []);
3032
3054
  const onCellMouseOver = React.useCallback((e, mouseDown) => {
3033
3055
  if (!enableRangeSelection) {
3034
3056
  return;
@@ -3042,19 +3064,20 @@ const useGridRangeSelection = ({ enableRangeSelection, gridDivRef, rangeStartRef
3042
3064
  rangeEndRef.current = {
3043
3065
  rowId: e.node.data.id,
3044
3066
  colId: e.column.getColId(),
3045
- timestamp: Date.now(),
3067
+ clickLocation: [0, 0],
3046
3068
  };
3047
3069
  if (mouseDown) {
3048
3070
  const sortedNodes = [];
3049
3071
  e.api.forEachNodeAfterFilterAndSort((node) => sortedNodes.push(node));
3050
3072
  rangeSortedNodesRef.current = sortedNodes;
3051
3073
  setRefreshIntervalEnabled(true);
3052
- rangeStartRef.current = { ...rangeEndRef.current };
3074
+ rangeStartRef.current = { ...rangeEndRef.current, clickLocation: mouseDownRef.current };
3053
3075
  }
3054
3076
  if (rangeStartRef.current &&
3055
3077
  rangeEndRef.current &&
3056
3078
  (rangeStartRef.current.rowId !== rangeEndRef.current.rowId ||
3057
3079
  rangeStartRef.current.colId !== rangeEndRef.current.colId)) {
3080
+ e.api.stopEditing();
3058
3081
  hasSelectedMoreThanOneCellRef.current = true;
3059
3082
  window.getSelection()?.removeAllRanges();
3060
3083
  }
@@ -3259,7 +3282,7 @@ defaultPostSort = true, rowData, rowHeight = theme === 'ag-theme-step-default' ?
3259
3282
  // ─── Selection ────────────────────────────────
3260
3283
  autoSelectFirstRow, enableRangeSelection = false, externalSelectedIds, externalSelectedItems, selectColumnPinned = 'left', selectable, setExternalSelectedIds, setExternalSelectedItems,
3261
3284
  // ─── Editing ──────────────────────────────────
3262
- singleClickEdit = false, stopEditingWhenCellsLoseFocus = false,
3285
+ singleClickEdit = false, enableMultilineBulkEdit = false,
3263
3286
  // ─── Context Menu ─────────────────────────────
3264
3287
  contextMenuSelectRow = false, contextMenu,
3265
3288
  // ─── Callbacks / Events ───────────────────────
@@ -3541,10 +3564,16 @@ maxInitialWidth,
3541
3564
  * When grid is ready set the apis to the grid context and sync selected items to grid.
3542
3565
  */
3543
3566
  const onGridReady = React.useCallback((event) => {
3544
- setApis(event.api, hasExternallySelectedItems, dataTestId);
3567
+ setApis(event.api, hasExternallySelectedItems, enableMultilineBulkEdit, dataTestId);
3545
3568
  event.api.showNoRowsOverlay();
3546
3569
  synchroniseExternallySelectedItemsToGrid();
3547
- }, [dataTestId, hasExternallySelectedItems, setApis, synchroniseExternallySelectedItemsToGrid]);
3570
+ }, [
3571
+ dataTestId,
3572
+ enableMultilineBulkEdit,
3573
+ hasExternallySelectedItems,
3574
+ setApis,
3575
+ synchroniseExternallySelectedItemsToGrid,
3576
+ ]);
3548
3577
  /**
3549
3578
  * When the grid is being initialized the data may be empty.
3550
3579
  * This will resize columns when we have at least one row.
@@ -3587,6 +3616,11 @@ maxInitialWidth,
3587
3616
  * Handle double click edit
3588
3617
  */
3589
3618
  const onCellDoubleClick = React.useCallback((event) => {
3619
+ const cellEditing = event.api.getEditingCells()?.[0];
3620
+ if (cellEditing && cellEditing.colId === event.column.getColId() && cellEditing.rowIndex === event.rowIndex) {
3621
+ // Ignore double click in already editing cell
3622
+ return;
3623
+ }
3590
3624
  const editable = fnOrVar(event.colDef?.editable, event);
3591
3625
  if (editable && !invokeEditAction(event)) {
3592
3626
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
@@ -3671,18 +3705,25 @@ maxInitialWidth,
3671
3705
  * Handle single click edits
3672
3706
  */
3673
3707
  const onCellClicked = React.useCallback((event) => {
3674
- if (rangeStartRef.current && rangeEndRef.current) {
3708
+ if (enableRangeSelection && rangeStartRef.current && rangeEndRef.current) {
3675
3709
  // This is to detect difference between a single click and a click drag return to cell.
3676
- if (rangeEndRef.current.timestamp - rangeStartRef.current.timestamp < 100) {
3677
- clearRangeSelection();
3710
+ const maxDistanceBetweenC = Math.max(Math.abs(rangeEndRef.current.clickLocation[0] - rangeStartRef.current.clickLocation[0]), Math.abs(rangeEndRef.current.clickLocation[1] - rangeStartRef.current.clickLocation[1]));
3711
+ if (maxDistanceBetweenC > 8) {
3712
+ return;
3678
3713
  }
3679
- return;
3714
+ clearRangeSelection();
3680
3715
  }
3681
3716
  const editable = fnOrVar(event.colDef?.editable, event);
3682
3717
  if ((editable && event.colDef.singleClickEdit) ?? singleClickEdit) {
3718
+ const editingCell = event.api.getEditingCells()[0];
3719
+ // If we are editing and single click we don't want the editing to stop
3720
+ if (editingCell && editingCell.colId === event.column.getColId() && editingCell.rowIndex === event.rowIndex) {
3721
+ return;
3722
+ }
3723
+ event.api.setFocusedCell(event.rowIndex ?? 0, event.column.getColId(), event.rowPinned);
3683
3724
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
3684
3725
  }
3685
- }, [clearRangeSelection, singleClickEdit, startCellEditing]);
3726
+ }, [clearRangeSelection, enableRangeSelection, singleClickEdit, startCellEditing]);
3686
3727
  /**
3687
3728
  * Set of column I'd's that are prevented from auto-sizing as they are user set
3688
3729
  */
@@ -3963,7 +4004,7 @@ maxInitialWidth,
3963
4004
  event.api.sizeColumnsToFit();
3964
4005
  }
3965
4006
  }, [sizeColumns]);
3966
- return (jsxRuntime.jsxs("div", { "data-testid": dataTestId, className: clsx('Grid-container', theme, 'theme-specific', staleGrid && 'Grid-sortIsStale', gridReady && rowData && autoSized && 'Grid-ready'), children: [contextMenuComponent, rangeSelectContextMenuComponent, jsxRuntime.jsx("div", { style: { flex: 1 }, ref: gridDivRef, onCopy: onCopyEvent, children: jsxRuntime.jsx(agGridReact.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
4007
+ return (jsxRuntime.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, jsxRuntime.jsx("div", { style: { flex: 1 }, ref: gridDivRef, onCopy: onCopyEvent, children: jsxRuntime.jsx(agGridReact.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
3967
4008
  ? {
3968
4009
  enableSelectionWithoutKeys: params.enableSelectionWithoutKeys ?? false,
3969
4010
  enableClickSelection: params.enableClickSelection ?? false,
@@ -3973,7 +4014,7 @@ maxInitialWidth,
3973
4014
  headerCheckbox: false,
3974
4015
  }),
3975
4016
  }
3976
- : undefined, stopEditingWhenCellsLoseFocus: stopEditingWhenCellsLoseFocus, selectionColumnDef: selectionColumnDef, suppressCellFocus: params.suppressCellFocus, suppressClickEdit: true, suppressColumnVirtualisation: suppressColumnVirtualization, suppressStartEditOnTab: true }) })] }));
4017
+ : undefined, selectionColumnDef: selectionColumnDef, suppressCellFocus: params.suppressCellFocus, suppressClickEdit: true, suppressColumnVirtualisation: suppressColumnVirtualization, suppressStartEditOnTab: true }) })] }));
3977
4018
  };
3978
4019
  const quickFilterParser = (filterStr) => {
3979
4020
  // filter is exact matches exactly groups separated by commas
@@ -4788,6 +4829,7 @@ const textMatch = (text, filter) => {
4788
4829
 
4789
4830
  const CancelPromise = () => Promise.resolve(true);
4790
4831
  const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick, }) => {
4832
+ const { resetFocusedCellAfterCellEditing } = useGridContext();
4791
4833
  const { anchorRef, saving, updateValue, stopEditing } = useGridPopoverContext();
4792
4834
  const saveButtonRef = React.useRef(null);
4793
4835
  const [isOpen, setOpen] = React.useState(false);
@@ -4798,6 +4840,7 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
4798
4840
  if (reason == CloseReason.CANCEL) {
4799
4841
  await updateValue(CancelPromise, 0);
4800
4842
  stopEditing();
4843
+ resetFocusedCellAfterCellEditing();
4801
4844
  return;
4802
4845
  }
4803
4846
  if (invalid?.()) {
@@ -4806,8 +4849,9 @@ const useGridPopoverHook = ({ className, save, invalid, dontSaveOnExternalClick,
4806
4849
  }
4807
4850
  if (await updateValue(save ?? (() => Promise.resolve(true)), reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0)) {
4808
4851
  stopEditing();
4852
+ resetFocusedCellAfterCellEditing();
4809
4853
  }
4810
- }, [invalid, save, stopEditing, updateValue]);
4854
+ }, [invalid, resetFocusedCellAfterCellEditing, save, stopEditing, updateValue]);
4811
4855
  const popoverWrapper = React.useCallback((children) => {
4812
4856
  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) => {
4813
4857
  // Prevent menu from closing when modals are invoked
@@ -5176,6 +5220,128 @@ const GridFormEditBearing = (props) => {
5176
5220
  : props.formatValue(bearingNumberParser(value)), error: bearingStringValidator(value, props.range), helpText: 'Press enter or tab to save' }) }));
5177
5221
  };
5178
5222
 
5223
+ const TextInputValidator = (props, value, data, context) => {
5224
+ if (value == null)
5225
+ return null;
5226
+ // This can happen because subcomponent is invoked without type safety
5227
+ if (typeof value !== 'string') {
5228
+ return 'Value is not a string';
5229
+ }
5230
+ if (props.required && value.trim() == '') {
5231
+ return 'Must not be empty';
5232
+ }
5233
+ if (props.maxLength && value.length > props.maxLength) {
5234
+ return `Must be no longer than ${props.maxLength} characters`;
5235
+ }
5236
+ if (props.maxBytes && stringByteLengthIsInvalid(value, props.maxBytes)) {
5237
+ return `Must be no longer than ${props.maxBytes} bytes`;
5238
+ }
5239
+ const nf = props.numberFormat;
5240
+ if (nf) {
5241
+ if (value != '' && !isFloat(value)) {
5242
+ return `Must be a valid number`;
5243
+ }
5244
+ if (value != '') {
5245
+ const number = parseFloat(value);
5246
+ if (nf.notZero && number === 0) {
5247
+ return `Must not be 0`;
5248
+ }
5249
+ if (nf.gtMin != null && number <= nf.gtMin) {
5250
+ return `Must be greater than ${nf.gtMin.toLocaleString()}`;
5251
+ }
5252
+ if (nf.geMin != null && number < nf.geMin) {
5253
+ return `Must not be less than ${nf.geMin.toLocaleString()}`;
5254
+ }
5255
+ if (nf.ltMax != null && number >= nf.ltMax) {
5256
+ return `Must be less than ${nf.ltMax.toLocaleString()}`;
5257
+ }
5258
+ if (nf.leMax != null && number > nf.leMax) {
5259
+ return `Must not be greater than ${nf.leMax.toLocaleString()}`;
5260
+ }
5261
+ if (nf.precision != null && value != '') {
5262
+ if (parseFloat(number.toPrecision(nf.precision)) != number) {
5263
+ return `Must have no more than ${nf.precision} digits precision`;
5264
+ }
5265
+ }
5266
+ if (nf.scale != null && value != '') {
5267
+ if (parseFloat(number.toFixed(nf.scale)) != number) {
5268
+ return nf.scale == 0 ? `Must be a whole number` : `Must have no more than ${nf.scale} decimal places`;
5269
+ }
5270
+ }
5271
+ }
5272
+ }
5273
+ if (props.invalid) {
5274
+ return props.invalid(value, data, context);
5275
+ }
5276
+ return null;
5277
+ };
5278
+
5279
+ const GridFormInlineTextInput = (props) => {
5280
+ const { onSave } = props;
5281
+ const { getSelectedRows, prePopupOps } = useGridContext();
5282
+ const { field, value: initialVale, data } = useGridPopoverContext();
5283
+ const initValue = React.useMemo(() => (initialVale == null ? '' : `${initialVale}`), [initialVale]);
5284
+ const [value, setValue] = React.useState(initValue);
5285
+ const invalid = React.useCallback(() => TextInputValidator(props, value, data, {}), [data, props, value]);
5286
+ React.useEffect(() => {
5287
+ prePopupOps();
5288
+ }, [prePopupOps]);
5289
+ const save = React.useCallback(async () => {
5290
+ if (invalid()) {
5291
+ return false;
5292
+ }
5293
+ const trimmedValue = value.trim();
5294
+ // No change, so don't save
5295
+ if (initValue === trimmedValue) {
5296
+ return true;
5297
+ }
5298
+ if (!onSave) {
5299
+ console.error(`No onSave handler for GridFormInlineTextInput "${String(field)}`);
5300
+ return false;
5301
+ }
5302
+ const selectedRows = getSelectedRows();
5303
+ const selectedRowIds = selectedRows.map((data) => data.id);
5304
+ return onSave({
5305
+ selectedRowIds,
5306
+ selectedRows,
5307
+ value: trimmedValue,
5308
+ });
5309
+ }, [invalid, value, initValue, onSave, getSelectedRows, field]);
5310
+ const inputRef = React.useRef(null);
5311
+ React.useEffect(() => {
5312
+ inputRef.current?.focus();
5313
+ inputRef.current?.select();
5314
+ }, []);
5315
+ const { triggerSave } = useGridPopoverHook({
5316
+ className: props.className,
5317
+ invalid,
5318
+ save,
5319
+ });
5320
+ const tabDirectionRef = React.useRef(CloseReason.BLUR);
5321
+ const tabRecorder = React.useCallback((e) => {
5322
+ if (e.key === 'Tab') {
5323
+ tabDirectionRef.current = e.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD;
5324
+ e.preventDefault();
5325
+ e.stopPropagation();
5326
+ void triggerSave(tabDirectionRef.current);
5327
+ }
5328
+ }, [triggerSave]);
5329
+ React.useEffect(() => {
5330
+ const input = inputRef.current;
5331
+ input?.addEventListener('keydown', tabRecorder, { capture: true });
5332
+ return () => {
5333
+ input?.removeEventListener('keydown', tabRecorder, { capture: true });
5334
+ };
5335
+ }, [tabRecorder]);
5336
+ return (jsxRuntime.jsx("div", { className: clsx('GridFormInlineTextInput', invalid() && 'GridFormInlineTextInput-error'), title: String(invalid() ?? ''), children: jsxRuntime.jsx("input", { ref: inputRef, type: 'text', value: value, onChange: (e) => setValue(e.target.value), placeholder: props.placeholder ?? 'Type here', onBlur: () => {
5337
+ void triggerSave(CloseReason.BLUR);
5338
+ }, onKeyUp: (e) => {
5339
+ if (e.key === 'Enter') {
5340
+ void triggerSave(CloseReason.BLUR);
5341
+ }
5342
+ } }) }));
5343
+ };
5344
+
5179
5345
  const GridFormMessage = (props) => {
5180
5346
  const { selectedRows } = useGridPopoverContext();
5181
5347
  const [message, setMessage] = React.useState(null);
@@ -5587,62 +5753,6 @@ const TextAreaInput = (props) => {
5587
5753
  }, "data-allowtabtosave": props.allowTabToSave, children: props.value }) })] }), jsxRuntime.jsx(FormError, { error: props.error, helpText: props.helpText })] }));
5588
5754
  };
5589
5755
 
5590
- const TextInputValidator = (props, value, data, context) => {
5591
- if (value == null)
5592
- return null;
5593
- // This can happen because subcomponent is invoked without type safety
5594
- if (typeof value !== 'string') {
5595
- return 'Value is not a string';
5596
- }
5597
- if (props.required && value.trim() == '') {
5598
- return 'Must not be empty';
5599
- }
5600
- if (props.maxLength && value.length > props.maxLength) {
5601
- return `Must be no longer than ${props.maxLength} characters`;
5602
- }
5603
- if (props.maxBytes && stringByteLengthIsInvalid(value, props.maxBytes)) {
5604
- return `Must be no longer than ${props.maxBytes} bytes`;
5605
- }
5606
- const nf = props.numberFormat;
5607
- if (nf) {
5608
- if (value != '' && !isFloat(value)) {
5609
- return `Must be a valid number`;
5610
- }
5611
- if (value != '') {
5612
- const number = parseFloat(value);
5613
- if (nf.notZero && number === 0) {
5614
- return `Must not be 0`;
5615
- }
5616
- if (nf.gtMin != null && number <= nf.gtMin) {
5617
- return `Must be greater than ${nf.gtMin.toLocaleString()}`;
5618
- }
5619
- if (nf.geMin != null && number < nf.geMin) {
5620
- return `Must not be less than ${nf.geMin.toLocaleString()}`;
5621
- }
5622
- if (nf.ltMax != null && number >= nf.ltMax) {
5623
- return `Must be less than ${nf.ltMax.toLocaleString()}`;
5624
- }
5625
- if (nf.leMax != null && number > nf.leMax) {
5626
- return `Must not be greater than ${nf.leMax.toLocaleString()}`;
5627
- }
5628
- if (nf.precision != null && value != '') {
5629
- if (parseFloat(number.toPrecision(nf.precision)) != number) {
5630
- return `Must have no more than ${nf.precision} digits precision`;
5631
- }
5632
- }
5633
- if (nf.scale != null && value != '') {
5634
- if (parseFloat(number.toFixed(nf.scale)) != number) {
5635
- return nf.scale == 0 ? `Must be a whole number` : `Must have no more than ${nf.scale} decimal places`;
5636
- }
5637
- }
5638
- }
5639
- }
5640
- if (props.invalid) {
5641
- return props.invalid(value, data, context);
5642
- }
5643
- return null;
5644
- };
5645
-
5646
5756
  const GridFormSubComponentTextArea = (props) => {
5647
5757
  const { value, data, setValue, setValid, context } = React.useContext(GridSubComponentContext);
5648
5758
  const helpText = props.helpText ?? 'Press tab to save';
@@ -5821,6 +5931,11 @@ const GridEditBoolean = (colDef, editorProps) => {
5821
5931
  });
5822
5932
  };
5823
5933
 
5934
+ const GridInlineTextInput = (colDef, params) => GridCell(colDef, {
5935
+ editor: GridFormInlineTextInput,
5936
+ ...params,
5937
+ });
5938
+
5824
5939
  const GridPopoutEditMultiSelect = (colDef, props) => GridCell(colDef, {
5825
5940
  editor: GridFormMultiSelect,
5826
5941
  ...props,
@@ -5961,6 +6076,7 @@ const GridContextProvider = (props) => {
5961
6076
  const [invisibleColumnIds, _setInvisibleColumnIds] = React.useState();
5962
6077
  const testId = React.useRef();
5963
6078
  const hasExternallySelectedItemsRef = React.useRef(false);
6079
+ const enableMultilineBulkEditRef = React.useRef(false);
5964
6080
  const idsBeforeUpdate = React.useRef([]);
5965
6081
  const prePopupFocusedCell = React.useRef();
5966
6082
  const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = React.useState(false);
@@ -6025,8 +6141,9 @@ const GridContextProvider = (props) => {
6025
6141
  /**
6026
6142
  * Set the grid api when the grid is ready.
6027
6143
  */
6028
- const setApis = React.useCallback((gridApi, hasExternallySelectedItems, dataTestId) => {
6144
+ const setApis = React.useCallback((gridApi, hasExternallySelectedItems, enableMultilineBulkEdit, dataTestId) => {
6029
6145
  hasExternallySelectedItemsRef.current = hasExternallySelectedItems;
6146
+ enableMultilineBulkEditRef.current = enableMultilineBulkEdit;
6030
6147
  testId.current = dataTestId;
6031
6148
  setGridApi(gridApi);
6032
6149
  gridApi?.setGridOption('quickFilterText', quickFilter);
@@ -6171,7 +6288,7 @@ const GridContextProvider = (props) => {
6171
6288
  if (flash) {
6172
6289
  lodashEs.delay(() => {
6173
6290
  try {
6174
- !gridApi.isDestroyed && gridApi.flashCells({ rowNodes });
6291
+ !gridApi.isDestroyed() && gridApi.flashCells({ rowNodes });
6175
6292
  }
6176
6293
  catch {
6177
6294
  // ignore, flash cells sometimes throws errors as nodes have gone out of scope
@@ -6237,13 +6354,13 @@ const GridContextProvider = (props) => {
6237
6354
  * Then we size the flexed columns.
6238
6355
  */
6239
6356
  const autoSizeColumns = React.useCallback(async ({ skipHeader, colIds, userSizedColIds } = {}) => {
6240
- if (!gridApi || !gridApi.getColumnState()) {
6357
+ if (!gridApi || gridApi.isDestroyed() || !gridApi.getColumnState()) {
6241
6358
  return null;
6242
6359
  }
6243
6360
  try {
6244
6361
  const colIdsSet = colIds instanceof Set ? colIds : new Set(colIds);
6245
6362
  const getVisibleColStates = () => {
6246
- const colStates = gridApi.getColumnState();
6363
+ const colStates = gridApi.getColumnState() ?? [];
6247
6364
  return colStates.filter((colState) => {
6248
6365
  const colId = colState.colId;
6249
6366
  return (lodashEs.isEmpty(colIdsSet) || colIdsSet.has(colId)) && !userSizedColIds?.has(colId);
@@ -6303,9 +6420,11 @@ const GridContextProvider = (props) => {
6303
6420
  if (!gridApi || gridApi.isDestroyed() || startCellEditingInProgressRef.current) {
6304
6421
  return;
6305
6422
  }
6306
- if (prePopupFocusedCell.current) {
6307
- gridApi.setFocusedCell(prePopupFocusedCell.current.rowIndex, prePopupFocusedCell.current.column);
6308
- prePopupFocusedCell.current = undefined;
6423
+ const cellToFocus = prePopupFocusedCell.current;
6424
+ if (cellToFocus) {
6425
+ lodashEs.defer(() => {
6426
+ gridApi.setFocusedCell(cellToFocus.rowIndex, cellToFocus.column);
6427
+ });
6309
6428
  }
6310
6429
  }, [gridApi]);
6311
6430
  // waitForExternallySelectedItemsToBeInSync can't use the state as it won't be updated during function execution
@@ -6371,9 +6490,8 @@ const GridContextProvider = (props) => {
6371
6490
  }, [anyUpdating, gridApi, prePopupOps, waitForExternallySelectedItemsToBeInSync]);
6372
6491
  const bulkEditingCompleteCallbackRef = React.useRef();
6373
6492
  const onBulkEditingComplete = React.useCallback(async () => {
6374
- resetFocusedCellAfterCellEditing();
6375
6493
  await bulkEditingCompleteCallbackRef.current?.();
6376
- }, [resetFocusedCellAfterCellEditing]);
6494
+ }, []);
6377
6495
  const setOnBulkEditingComplete = React.useCallback((cellEditingCompleteCallback) => {
6378
6496
  bulkEditingCompleteCallbackRef.current = cellEditingCompleteCallback;
6379
6497
  }, []);
@@ -6413,10 +6531,18 @@ const GridContextProvider = (props) => {
6413
6531
  return true;
6414
6532
  }
6415
6533
  const postRow = gridApi.getFocusedCell();
6416
- if (preRow?.rowIndex !== postRow?.rowIndex || preRow?.column === postRow?.column) {
6417
- // We didn't find an editable cell in the same row, or the cell column didn't change
6418
- // implying it was start/end of grid
6419
- break;
6534
+ if (enableMultilineBulkEditRef.current) {
6535
+ // Last cell in grid, nowhere to tab
6536
+ if (preRow?.rowIndex === postRow?.rowIndex && preRow?.column === postRow?.column) {
6537
+ break;
6538
+ }
6539
+ }
6540
+ else {
6541
+ if (preRow?.rowIndex !== postRow?.rowIndex || preRow?.column === postRow?.column) {
6542
+ // We didn't find an editable cell in the same row, or the cell column didn't change
6543
+ // implying it was start/end of grid
6544
+ break;
6545
+ }
6420
6546
  }
6421
6547
  if (focusedCellIsEditable()) {
6422
6548
  const focusedCell = gridApi.getFocusedCell();
@@ -6800,6 +6926,7 @@ exports.GridFilterQuick = GridFilterQuick;
6800
6926
  exports.GridFilters = GridFilters;
6801
6927
  exports.GridFormDropDown = GridFormDropDown;
6802
6928
  exports.GridFormEditBearing = GridFormEditBearing;
6929
+ exports.GridFormInlineTextInput = GridFormInlineTextInput;
6803
6930
  exports.GridFormMessage = GridFormMessage;
6804
6931
  exports.GridFormMultiSelect = GridFormMultiSelect;
6805
6932
  exports.GridFormMultiSelectGrid = GridFormMultiSelectGrid;
@@ -6809,6 +6936,7 @@ exports.GridFormSubComponentTextInput = GridFormSubComponentTextInput;
6809
6936
  exports.GridFormTextArea = GridFormTextArea;
6810
6937
  exports.GridFormTextInput = GridFormTextInput;
6811
6938
  exports.GridIcon = GridIcon;
6939
+ exports.GridInlineTextInput = GridInlineTextInput;
6812
6940
  exports.GridLoadableCell = GridLoadableCell;
6813
6941
  exports.GridNoRowsOverlay = GridNoRowsOverlay;
6814
6942
  exports.GridNoRowsOverlayFr = GridNoRowsOverlayFr;