@linzjs/step-ag-grid 29.1.4 → 29.1.6

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.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "29.1.4",
5
+ "version": "29.1.6",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -142,6 +142,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
142
142
  rowData,
143
143
  rowHeight = theme === 'ag-theme-step-default' ? 40 : theme === 'ag-theme-step-compact' ? 36 : 40,
144
144
  selectable,
145
+ onCellFocused: paramsOnCellFocused,
145
146
  ...params
146
147
  }: GridProps<TData>): ReactElement => {
147
148
  const {
@@ -450,7 +451,6 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
450
451
  const invokeEditAction = (e: CellDoubleClickedEvent | CellKeyDownEvent): boolean => {
451
452
  const editAction = e.colDef?.cellRendererParams?.editAction;
452
453
  if (!editAction) return false;
453
-
454
454
  const editable = fnOrVar(e.colDef?.editable, e);
455
455
  if (editable) {
456
456
  if (!e.node.isSelected()) {
@@ -625,7 +625,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
625
625
 
626
626
  const onCellFocused = useCallback(
627
627
  (event: CellFocusedEvent<TData>) => {
628
- if (!params.onCellFocused || event.rowIndex == null) {
628
+ if (!paramsOnCellFocused || event.rowIndex == null) {
629
629
  return;
630
630
  }
631
631
  const api = event.api;
@@ -639,9 +639,17 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
639
639
  if (!colDef || typeof colDef === 'string') {
640
640
  return;
641
641
  }
642
- params.onCellFocused({ colDef, data });
642
+ // Prevent repeated callbacks to cell focus when it focus didn't change
643
+ const { sourceEvent } = event;
644
+ if (sourceEvent) {
645
+ if ((window as any).__stepaggrid_lastfocuseventtarget === sourceEvent.target) {
646
+ return;
647
+ }
648
+ (window as any).__stepaggrid_lastfocuseventtarget = sourceEvent.target;
649
+ }
650
+ paramsOnCellFocused({ colDef, data });
643
651
  },
644
- [params],
652
+ [paramsOnCellFocused],
645
653
  );
646
654
 
647
655
  const onRowDragEnd = useCallback(
@@ -812,6 +820,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
812
820
  pinnedBottomRowData={params.pinnedBottomRowData}
813
821
  onRowClicked={params.onRowClicked}
814
822
  onRowDoubleClicked={params.onRowDoubleClicked}
823
+ suppressStartEditOnTab={true}
815
824
  />
816
825
  </div>
817
826
  </div>
@@ -11,8 +11,7 @@ import {
11
11
  ValueFormatterParams,
12
12
  ValueGetterFunc,
13
13
  } from 'ag-grid-community';
14
- import { defer } from 'lodash-es';
15
- import { forwardRef, ReactElement, useContext, useEffect } from 'react';
14
+ import { forwardRef, ReactElement, useContext } from 'react';
16
15
 
17
16
  import { GridPopoverContextProvider } from '../contexts/GridPopoverContextProvider';
18
17
  import { GridUpdatingContext } from '../contexts/GridUpdatingContext';
@@ -155,14 +154,10 @@ export const GridCell = <TData extends GridBaseRow, TValue = any, Props extends
155
154
  resizable: true,
156
155
  valueSetter: custom?.editor ? blockValueSetter : undefined,
157
156
  editable: props.editable ?? !!custom?.editor,
158
- ...(custom?.editor
159
- ? {
160
- cellClassRules: GridCellMultiSelectClassRules,
161
- cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
162
- }
163
- : {
164
- cellEditor: CellEditorToBlockEditing,
165
- }),
157
+ ...(custom?.editor && {
158
+ cellClassRules: GridCellMultiSelectClassRules,
159
+ cellEditor: GenericCellEditorComponentWrapper(custom?.editor),
160
+ }),
166
161
  suppressKeyboardEvent: suppressCellKeyboardEvents,
167
162
  ...(custom?.editorParams
168
163
  ? {
@@ -190,20 +185,6 @@ export const GridCell = <TData extends GridBaseRow, TValue = any, Props extends
190
185
  };
191
186
  };
192
187
 
193
- /**
194
- * Ag-grid will start its own editor if editable is true and there is no cell editor
195
- * like in the case of a cell that is editable because it triggers a modal.
196
- * This will block that editor.
197
- */
198
- const CellEditorToBlockEditing = ({ stopEditing }: { stopEditing: () => void }) => {
199
- useEffect(() => {
200
- defer(() => {
201
- stopEditing();
202
- });
203
- }, [stopEditing]);
204
- return <></>;
205
- };
206
-
207
188
  export interface CellEditorCommon {
208
189
  className?: string | undefined;
209
190
  }
@@ -110,6 +110,5 @@ export const useGridPopoverHook = <TData extends GridBaseRow>({
110
110
  return {
111
111
  popoverWrapper,
112
112
  triggerSave,
113
- gridPopoverOpen: isOpen,
114
113
  };
115
114
  };
@@ -1,5 +1,5 @@
1
1
  import debounce from 'debounce-promise';
2
- import { isEmpty } from 'lodash-es';
2
+ import { compact, isEmpty } from 'lodash-es';
3
3
  import { Fragment, ReactElement, useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
 
5
5
  import { useGridPopoverContext } from '../../contexts/GridPopoverContext';
@@ -90,7 +90,9 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
90
90
  // Save triggers during async action processing which triggers another selectItem(), this ref blocks that
91
91
  const [filter, setFilter] = useState(props.filterDefaultValue ?? '');
92
92
  const [filteredValues, setFilteredValues] = useState<any[]>();
93
- const [options, setOptions] = useState<FinalSelectOption<TOptionValue>[] | null>(null);
93
+ const [options, setOptions] = useState<FinalSelectOption<TOptionValue>[] | null>(
94
+ !!propOptions && typeof propOptions !== 'function' ? propOptions : null,
95
+ );
94
96
  const subComponentIsValid = useRef(false);
95
97
  const subComponentInitialValue = useRef<string | null>(null);
96
98
  const [subSelectedValue, setSubSelectedValue] = useState<any>(null);
@@ -164,7 +166,7 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
164
166
  subSelectedValue,
165
167
  ]);
166
168
 
167
- const { popoverWrapper, gridPopoverOpen } = useGridPopoverHook({
169
+ const { popoverWrapper } = useGridPopoverHook({
168
170
  className: props.className,
169
171
  invalid: () => !options || !!(selectedItem && !subComponentIsValid.current),
170
172
  save,
@@ -172,39 +174,49 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
172
174
  });
173
175
 
174
176
  // Load up options list if it's async function
175
- const prevIsOpen = usePrevious(gridPopoverOpen);
176
- const prevFilter = usePrevious(filter);
177
177
  useEffect(() => {
178
- if ((gridPopoverOpen !== prevIsOpen || filter !== prevFilter) && gridPopoverOpen) {
179
- let optionsConf = propOptions;
178
+ // If options is null then we need to load/reload
179
+ // Options will be set to null during a reload based filter, or on open popup
180
+ if (options !== null) {
181
+ return;
182
+ }
180
183
 
181
- void (async () => {
182
- if (typeof optionsConf === 'function') {
183
- optionsConf = await optionsConf(selectedRows, filter);
184
- }
185
- if (optionsConf !== undefined) {
186
- setOptions(optionsConf);
187
- }
188
- })();
184
+ // propOptions is a const list
185
+ if (typeof propOptions !== 'function') {
186
+ if (propOptions) {
187
+ setOptions(propOptions);
188
+ }
189
+ return;
189
190
  }
190
- }, [filter, gridPopoverOpen, options, prevFilter, prevIsOpen, propOptions, selectedRows]);
191
+
192
+ // propOptions is function, probably loading from web
193
+ void (async () => {
194
+ const r = await propOptions(selectedRows, filter);
195
+ setOptions(r ?? []);
196
+ })();
197
+ }, [filter, options, propOptions, selectedRows]);
191
198
 
192
199
  // Local filtering.
193
200
  useEffect(() => {
194
- if (props.filtered == 'local') {
195
- if (options == null) return;
196
- setFilteredValues(
197
- options
198
- .map((option) => {
199
- if (option.label != null && typeof option.label !== 'string') {
200
- console.error('Cannot filter non-string labels', option);
201
- return undefined;
202
- }
203
- return textMatch((option.label as string) || '', filter) ? option : undefined;
204
- })
205
- .filter((r) => r !== undefined),
206
- );
201
+ if (props.filtered !== 'local') {
202
+ return;
203
+ }
204
+ if (options == null) {
205
+ setFilteredValues([]);
206
+ return;
207
207
  }
208
+
209
+ setFilteredValues(
210
+ compact(
211
+ options.map((option) => {
212
+ if (option.label != null && typeof option.label !== 'string') {
213
+ console.warn('GridFormDropDown: Cannot filter non-string labels', option);
214
+ return undefined;
215
+ }
216
+ return textMatch((option.label as string) || '', filter) ? option : undefined;
217
+ }),
218
+ ),
219
+ );
208
220
  }, [props.filtered, filter, options]);
209
221
 
210
222
  const reSearchOnFilterChange = useMemo(
@@ -215,15 +227,12 @@ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
215
227
  [],
216
228
  );
217
229
 
218
- const previousFilter = useRef<string>(filter);
219
-
220
- // Reload filtering.
230
+ const previousFilter = usePrevious(filter);
221
231
  useEffect(() => {
222
- if (previousFilter.current != filter && props.filtered == 'reload') {
223
- previousFilter.current = filter;
232
+ if (previousFilter != null && previousFilter != filter && props.filtered === 'reload') {
224
233
  void reSearchOnFilterChange();
225
234
  }
226
- }, [filter, props, reSearchOnFilterChange]);
235
+ }, [filter, previousFilter, props.filtered, reSearchOnFilterChange]);
227
236
 
228
237
  let lastHeader: ReactElement | null = null;
229
238
  let showHeader: ReactElement | null = null;
@@ -15,6 +15,11 @@ export interface AutoSizeColumnsProps {
15
15
 
16
16
  export type AutoSizeColumnsResult = { width: number } | null;
17
17
 
18
+ export interface StartCellEditingProps {
19
+ rowId: number;
20
+ colId: string;
21
+ }
22
+
18
23
  export interface GridContextType<TData extends GridBaseRow> {
19
24
  gridReady: boolean;
20
25
  gridRenderState: () => null | 'empty' | 'rows-visible';
@@ -43,7 +48,7 @@ export interface GridContextType<TData extends GridBaseRow> {
43
48
  getFirstRowId: () => number;
44
49
  autoSizeColumns: (props?: AutoSizeColumnsProps) => AutoSizeColumnsResult;
45
50
  sizeColumnsToFit: () => void;
46
- startCellEditing: ({ rowId, colId }: { rowId: number; colId: string }) => Promise<void>;
51
+ startCellEditing: ({ rowId, colId }: StartCellEditingProps) => Promise<void>;
47
52
  // Restores the previous focus after cell editing
48
53
  resetFocusedCellAfterCellEditing: () => void;
49
54
  updatingCells: (
@@ -596,11 +596,9 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
596
596
  const preRow = gridApi.getFocusedCell();
597
597
  // If we don't do this ag-grid will do its own continuation of an edit on tab, we don't want that as
598
598
  // we are managing it ourselves
599
- gridApi.stopEditing();
600
- if (tabDirection === 1) {
601
- gridApi.tabToNextCell();
602
- } else {
603
- gridApi.tabToPreviousCell();
599
+ const didTab = tabDirection === 1 ? gridApi.tabToNextCell() : gridApi.tabToPreviousCell();
600
+ if (!didTab) {
601
+ break;
604
602
  }
605
603
 
606
604
  if (gridApi.isDestroyed()) {
@@ -9,7 +9,6 @@ import { useRef } from 'react';
9
9
  import { expect, fn, userEvent, within } from 'storybook/test';
10
10
 
11
11
  import {
12
- CancelPromise,
13
12
  GridContextProvider,
14
13
  GridFormEditBearing,
15
14
  GridFormEditBearingProps,
@@ -275,13 +275,11 @@ GridKeyboardInteractions.play = async ({ canvasElement }) => {
275
275
  await test(() => userEvent.keyboard('{Enter}'), '8', '2');
276
276
  expect(multiEditAction).toHaveBeenCalled();
277
277
 
278
- console.log('Open 2nd to last popup menu, tab to next disabled popup');
279
- await test(() => userEvent.tab(), '9', '2');
278
+ console.log('Open 2nd to last popup menu, tab to next disabled popup should fail');
279
+ await test(() => userEvent.tab(), '8', '2');
280
280
  expect(bulkEditingCallback).toHaveBeenCalled();
281
281
  bulkEditingCallback.mockClear();
282
282
 
283
- console.log('Fail to edit last popup menu, tab back to 2nd to last popup menu');
284
- await userEvent.tab({ shift: true });
285
283
  console.log('Open 2nd to last popup menu, tab to desc cell');
286
284
  await test(() => userEvent.tab({ shift: true }), '5', '2');
287
285
  console.log('Cancel edit');
@@ -312,9 +310,9 @@ GridKeyboardInteractions.play = async ({ canvasElement }) => {
312
310
  await userEvent.tab();
313
311
 
314
312
  expect(eAction).not.toHaveBeenCalled();
315
- /*await userEvent.keyboard('{Enter}');
313
+ await userEvent.keyboard('{Enter}');
316
314
  await userEvent.keyboard('e');
317
315
  await waitFor(() => {
318
316
  expect(eAction).toHaveBeenCalled();
319
- });*/
317
+ });
320
318
  };