@linzjs/step-ag-grid 27.0.0 → 27.2.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.
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": "27.0.0",
5
+ "version": "27.2.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -359,16 +359,18 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
359
359
  });
360
360
  }, [params.columnDefs, params.loading, params.readOnly, params.defaultColDef?.editable]);
361
361
 
362
+ const hasExternallySelectedItems = !!params.setExternalSelectedItems;
363
+
362
364
  /**
363
365
  * When grid is ready set the apis to the grid context and sync selected items to grid.
364
366
  */
365
367
  const onGridReady = useCallback(
366
368
  (event: GridReadyEvent) => {
367
- setApis(event.api, dataTestId);
369
+ setApis(event.api, hasExternallySelectedItems, dataTestId);
368
370
  event.api.showNoRowsOverlay();
369
371
  synchroniseExternallySelectedItemsToGrid();
370
372
  },
371
- [dataTestId, setApis, synchroniseExternallySelectedItemsToGrid],
373
+ [dataTestId, hasExternallySelectedItems, setApis, synchroniseExternallySelectedItemsToGrid],
372
374
  );
373
375
 
374
376
  /**
@@ -433,7 +435,7 @@ export const Grid = <TData extends GridBaseRow = GridBaseRow>({
433
435
  */
434
436
  const onCellClicked = useCallback(
435
437
  (event: CellClickedEvent) => {
436
- if (event.colDef?.cellRendererParams?.singleClickEdit ?? singleClickEdit) {
438
+ if (event.colDef.singleClickEdit ?? singleClickEdit) {
437
439
  void startCellEditing({ rowId: event.data.id, colId: event.column.getColId() });
438
440
  }
439
441
  },
@@ -66,7 +66,6 @@ export interface ColDefT<TData extends GridBaseRow, ValueType = any> extends Col
66
66
  | ((props: ICellRendererParams<TData, ValueType>) => ReactElement | string | false | null | undefined)
67
67
  | string;
68
68
  cellRendererParams?: {
69
- singleClickEdit?: boolean;
70
69
  rightHoverElement?: ReactElement;
71
70
  originalCellRenderer?: any;
72
71
  editAction?: (selectedRows: TData[]) => void;
@@ -14,21 +14,28 @@ import { GridBaseRow } from '../Grid';
14
14
  import { CellEditorCommon } from '../GridCell';
15
15
  import { useGridPopoverHook } from '../GridPopoverHook';
16
16
 
17
- export interface GridPopoutEditDropDownSelectedItem<TData extends GridBaseRow, TValue> {
17
+ export interface GridPopoutEditDropDownSelectedItem<TData extends GridBaseRow, TOption> {
18
18
  // Note the row that was clicked on will be first
19
19
  selectedRows: TData[];
20
20
  selectedRowIds: TData['id'][];
21
- value: TValue;
21
+ value: TOption;
22
22
  subComponentValue?: any;
23
23
  }
24
24
 
25
- interface FinalSelectOption {
26
- value: any;
25
+ interface FinalSelectOption<TOptionValue> {
26
+ value: TOptionValue;
27
27
  label?: ReactElement | string;
28
28
  disabled?: boolean | string;
29
29
  subComponent?: (props: any, ref: any) => any;
30
30
  }
31
31
 
32
+ export const primitiveToSelectOption = <T,>(value: T): SelectOption<T> => {
33
+ return {
34
+ value: value,
35
+ label: value ? String(value) : '',
36
+ };
37
+ };
38
+
32
39
  export const MenuSeparatorString = '_____MENU_SEPARATOR_____';
33
40
  export const MenuSeparator = Object.freeze({ value: MenuSeparatorString });
34
41
 
@@ -37,9 +44,11 @@ export const MenuHeaderItem = (title: string) => {
37
44
  return { label: title, value: MenuHeaderString };
38
45
  };
39
46
 
40
- export type SelectOption = null | string | FinalSelectOption;
47
+ export type SelectOption<TOptionValue = any> = FinalSelectOption<TOptionValue>;
41
48
 
42
- export interface GridFormDropDownProps<TData extends GridBaseRow, TValue = any> extends CellEditorCommon {
49
+ export type MaybePromise<T> = T | Promise<T>;
50
+
51
+ export interface GridFormDropDownProps<TData extends GridBaseRow, TOptionValue> extends CellEditorCommon {
43
52
  // This overrides CellEditorCommon to provide some common class options
44
53
  className?: // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
45
54
  | 'GridPopoverEditDropDown-containerSmall'
@@ -59,11 +68,11 @@ export interface GridFormDropDownProps<TData extends GridBaseRow, TValue = any>
59
68
  filterPlaceholder?: string;
60
69
  filterHelpText?: string;
61
70
  noOptionsMessage?: string;
62
- onSelectedItem?: (props: GridPopoutEditDropDownSelectedItem<TData, TValue>) => Promise<void> | void;
63
- onSelectFilter?: (props: GridPopoutEditDropDownSelectedItem<TData, TValue>) => Promise<void> | void;
71
+ onSelectedItem?: (props: GridPopoutEditDropDownSelectedItem<TData, TOptionValue>) => Promise<void> | void;
72
+ onSelectFilter?: (props: GridPopoutEditDropDownSelectedItem<TData, TOptionValue>) => Promise<void> | void;
64
73
  options:
65
- | SelectOption[]
66
- | ((selectedRows: TData[], filter?: string) => Promise<SelectOption[] | undefined> | SelectOption[] | undefined)
74
+ | FinalSelectOption<TOptionValue>[]
75
+ | ((selectedRows: TData[], filter?: string) => MaybePromise<FinalSelectOption<TOptionValue>[] | undefined>)
67
76
  | undefined;
68
77
  }
69
78
 
@@ -71,18 +80,20 @@ const fieldToString = (field: any) => {
71
80
  return typeof field === 'symbol' ? field.toString() : `${field}`;
72
81
  };
73
82
 
74
- export const GridFormDropDown = <TData extends GridBaseRow, TValue>(props: GridFormDropDownProps<TData, TValue>) => {
83
+ export const GridFormDropDown = <TData extends GridBaseRow, TOptionValue>(
84
+ props: GridFormDropDownProps<TData, TOptionValue>,
85
+ ) => {
75
86
  const { selectedRows, field, data } = useGridPopoverContext<TData>();
76
87
 
77
88
  // Save triggers during async action processing which triggers another selectItem(), this ref blocks that
78
89
  const [filter, setFilter] = useState(props.filterDefaultValue ?? '');
79
90
  const [filteredValues, setFilteredValues] = useState<any[]>();
80
- const [options, setOptions] = useState<FinalSelectOption[] | null>(null);
91
+ const [options, setOptions] = useState<FinalSelectOption<TOptionValue>[] | null>(null);
81
92
  const subComponentIsValid = useRef(false);
82
93
  const subComponentInitialValue = useRef<string | null>(null);
83
94
  const [subSelectedValue, setSubSelectedValue] = useState<any>(null);
84
95
  // Note: null is assumed to be the filter
85
- const [selectedItem, setSelectedItem] = useState<FinalSelectOption | null>(null);
96
+ const [selectedItem, setSelectedItem] = useState<FinalSelectOption<TOptionValue> | null>(null);
86
97
 
87
98
  const selectItemHandler = useCallback(
88
99
  async (value: any, subComponentValue?: any): Promise<boolean> => {
@@ -116,17 +127,7 @@ export const GridFormDropDown = <TData extends GridBaseRow, TValue>(props: GridF
116
127
  optionsConf = await optionsConf(selectedRows, filter);
117
128
  }
118
129
  if (optionsConf !== undefined) {
119
- const optionsList = optionsConf?.map((item) =>
120
- item == null || typeof item === 'string'
121
- ? ({
122
- value: item,
123
- label: item,
124
- disabled: false,
125
- } as FinalSelectOption)
126
- : item,
127
- );
128
-
129
- setOptions(optionsList);
130
+ setOptions(optionsConf);
130
131
  }
131
132
  })();
132
133
  }, [filter, options, props, selectedRows]);
@@ -251,7 +252,7 @@ export const GridFormDropDown = <TData extends GridBaseRow, TValue>(props: GridF
251
252
  {props.noOptionsMessage ?? 'No Options'}
252
253
  </MenuItem>
253
254
  )}
254
- {options?.map((item: FinalSelectOption, index) => {
255
+ {options?.map((item: FinalSelectOption<TOptionValue>, index) => {
255
256
  showHeader = null;
256
257
  if (item.value === MenuSeparatorString) {
257
258
  return <MenuDivider key={`$$divider_${index}`} />;
@@ -288,7 +289,7 @@ export const GridFormDropDown = <TData extends GridBaseRow, TValue>(props: GridF
288
289
  e.keepOpen = !!item.subComponent;
289
290
  }}
290
291
  >
291
- {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
292
+ {item.label ?? (item.value == null ? `<${String(item.value)}>` : `${String(item.value)}`)}
292
293
  {item.subComponent ? '...' : ''}
293
294
  </MenuItem>
294
295
 
@@ -5,15 +5,15 @@ import { ColDefT, GenericCellEditorProps, GridCell } from '../GridCell';
5
5
  import { GridFormDropDown, GridFormDropDownProps } from '../gridForm/GridFormDropDown';
6
6
  import { GenericCellColDef } from '../gridRender/GridRenderGenericCell';
7
7
 
8
- export const GridPopoverEditDropDown = <TData extends GridBaseRow, TValue = any>(
8
+ export const GridPopoverEditDropDown = <TData extends GridBaseRow, TValue = any, TOptionValue = any>(
9
9
  colDef: GenericCellColDef<TData, TValue>,
10
- props: GenericCellEditorProps<GridFormDropDownProps<TData, TValue>>,
10
+ props: GenericCellEditorProps<GridFormDropDownProps<TData, TOptionValue>>,
11
11
  ): ColDefT<TData, TValue> =>
12
- GridCell<TData, TValue, GridFormDropDownProps<TData, TValue>>(colDef, {
12
+ GridCell<TData, TValue, GridFormDropDownProps<TData, TOptionValue>>(colDef, {
13
13
  editor: GridFormDropDown,
14
14
  ...props,
15
15
  editorParams: {
16
- ...(props.editorParams as GridFormDropDownProps<TData, TValue>),
16
+ ...(props.editorParams as GridFormDropDownProps<TData, TOptionValue>),
17
17
  className: clsx(
18
18
  {
19
19
  'GridPopoverEditDropDown-containerLarge': !props.editorParams?.className?.includes(
@@ -21,11 +21,9 @@ export const GridPopoverMenu = <TData extends GridBaseRow>(
21
21
  exportable: false,
22
22
  cellStyle: { flex: 1, justifyContent: 'center' },
23
23
  cellRenderer: GridRenderPopoutMenuCell,
24
+ // Menus open on single click, this parameter is picked up in Grid.tsx
25
+ singleClickEdit: true,
24
26
  ...colDef,
25
- cellRendererParams: {
26
- // Menus open on single click, this parameter is picked up in Grid.tsx
27
- singleClickEdit: true,
28
- },
29
27
  },
30
28
  {
31
29
  editor: GridFormPopoverMenu,
@@ -11,10 +11,7 @@ export const GridPopoverMessage = <TData extends GridBaseRow, TValue = any>(
11
11
  {
12
12
  resizable: true,
13
13
  ...colDef,
14
- cellRendererParams: {
15
- singleClickEdit: true,
16
- ...colDef.cellRendererParams,
17
- },
14
+ singleClickEdit: true,
18
15
  },
19
16
  {
20
17
  editor: GridFormMessage,
@@ -10,7 +10,6 @@ export interface GenericCellColDef<TData extends GridBaseRow, TValue = any> exte
10
10
  }
11
11
 
12
12
  export interface GenericCellRendererParams<TData extends GridBaseRow> {
13
- singleClickEdit?: boolean;
14
13
  rightHoverElement?: ReactElement | undefined;
15
14
  editAction?: (selectedRows: TData[]) => void;
16
15
  shortcutKeys?: Record<string, ((params: SuppressKeyboardEventParams) => boolean | void) | undefined>;
@@ -23,7 +23,7 @@ export interface GridContextType<TData extends GridBaseRow> {
23
23
  filter?: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string),
24
24
  ) => ColDefT<TData, any>[];
25
25
  getColumnIds: (filter?: keyof ColDef | ((r: ColDef) => boolean | undefined | null | number | string)) => string[];
26
- setApis: (gridApi: GridApi | undefined, dataTestId?: string) => void;
26
+ setApis: (gridApi: GridApi | undefined, hasExternallySelectedItems: boolean, dataTestId?: string) => void;
27
27
  prePopupOps: () => void;
28
28
  postPopupOps: () => void;
29
29
  setQuickFilter: (quickFilter: string) => void;
@@ -23,6 +23,7 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
23
23
  const [quickFilter, _setQuickFilter] = useState('');
24
24
  const [invisibleColumnIds, _setInvisibleColumnIds] = useState<string[]>();
25
25
  const testId = useRef<string | undefined>();
26
+ const hasExternallySelectedItemsRef = useRef(false);
26
27
  const idsBeforeUpdate = useRef<number[]>([]);
27
28
  const prePopupFocusedCell = useRef<CellPosition>();
28
29
  const [externallySelectedItemsAreInSync, setExternallySelectedItemsAreInSync] = useState(false);
@@ -101,7 +102,8 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
101
102
  * Set the grid api when the grid is ready.
102
103
  */
103
104
  const setApis = useCallback(
104
- (gridApi: GridApi | undefined, dataTestId?: string) => {
105
+ (gridApi: GridApi | undefined, hasExternallySelectedItems: boolean, dataTestId?: string) => {
106
+ hasExternallySelectedItemsRef.current = hasExternallySelectedItems;
105
107
  testId.current = dataTestId;
106
108
  setGridApi(gridApi);
107
109
  gridApi?.setGridOption('quickFilterText', quickFilter);
@@ -474,10 +476,17 @@ export const GridContextProvider = <TData extends GridBaseRow>(props: PropsWithC
474
476
  }, [externallySelectedItemsAreInSync]);
475
477
 
476
478
  const waitForExternallySelectedItemsToBeInSync = useCallback(async () => {
479
+ if (!hasExternallySelectedItemsRef.current) {
480
+ externallySelectedItemsAreInSyncRef.current = true;
481
+ return;
482
+ }
477
483
  // Wait for up to 5 seconds
478
484
  for (let i = 0; i < 5000 / 200 && !externallySelectedItemsAreInSyncRef.current; i++) {
479
485
  await wait(200);
480
486
  }
487
+ if (!externallySelectedItemsAreInSyncRef.current) {
488
+ console.error('externallySelectedItems did not sync with ag-grid selection');
489
+ }
481
490
  }, []);
482
491
 
483
492
  const startCellEditing = useCallback(
@@ -331,7 +331,6 @@ export const ControlledMenuFr = (
331
331
  externalRef={externalRef}
332
332
  containerRef={containerRef}
333
333
  onClose={onClose}
334
- onBlur={() => console.log('blur')}
335
334
  />
336
335
  </EventHandlersContext.Provider>
337
336
  </ItemSettingsContext.Provider>
@@ -25,6 +25,7 @@ import {
25
25
  MenuHeaderItem,
26
26
  MenuSeparator,
27
27
  MenuSeparatorString,
28
+ primitiveToSelectOption,
28
29
  wait,
29
30
  } from '../..';
30
31
  import { waitForGridReady } from '../../utils/__tests__/storybookTestUtil';
@@ -72,16 +73,9 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
72
73
  console.log('optionsFn selected rows', selectedRows, filter);
73
74
  filter = filter?.toLowerCase();
74
75
  await wait(1000);
75
- return [
76
- null,
77
- 'Architect',
78
- 'Developer',
79
- 'Product Owner',
80
- 'Scrum Master',
81
- 'Tester',
82
- MenuSeparatorString,
83
- 'Custom',
84
- ].filter((v) => (filter != null ? v != null && v.toLowerCase().indexOf(filter) === 0 : true));
76
+ return [null, 'Architect', 'Developer', 'Product Owner', 'Scrum Master', 'Tester', MenuSeparatorString, 'Custom']
77
+ .filter((v) => (filter != null ? v != null && v.toLowerCase().indexOf(filter) === 0 : true))
78
+ .map(primitiveToSelectOption);
85
79
  }, []);
86
80
 
87
81
  const optionsObjects = useMemo(
@@ -102,6 +96,7 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
102
96
  {
103
97
  field: 'position2',
104
98
  headerName: 'Multi-edit',
99
+ singleClickEdit: true,
105
100
  },
106
101
  {
107
102
  multiEdit: true,
@@ -120,7 +115,7 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
120
115
  },
121
116
  },
122
117
  ),
123
- GridPopoverEditDropDown(
118
+ GridPopoverEditDropDown<ITestRow, ITestRow['position3'], string | null>(
124
119
  {
125
120
  field: 'position3',
126
121
  headerName: 'Custom callback',
@@ -128,7 +123,9 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
128
123
  {
129
124
  multiEdit: true,
130
125
  editorParams: {
131
- options: [null, 'Architect', 'Developer', 'Product Owner', 'Scrum Master', 'Tester'],
126
+ options: [null, 'Architect', 'Developer', 'Product Owner', 'Scrum Master', 'Tester'].map(
127
+ primitiveToSelectOption,
128
+ ),
132
129
  onSelectedItem: async (selected) => {
133
130
  await wait(2000);
134
131
  selected.selectedRows.forEach((row) => {
@@ -138,7 +135,7 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
138
135
  },
139
136
  },
140
137
  ),
141
- GridPopoverEditDropDown(
138
+ GridPopoverEditDropDown<ITestRow, ITestRow['position3'], string | null>(
142
139
  {
143
140
  field: 'position',
144
141
  headerName: 'Options Fn',
@@ -164,7 +161,9 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
164
161
  editorParams: {
165
162
  filtered: 'local',
166
163
  filterPlaceholder: 'Filter this',
167
- options: [null, 'Architect', 'Developer', 'Product Owner', 'Scrum Master', 'Tester'],
164
+ options: [null, 'Architect', 'Developer', 'Product Owner', 'Scrum Master', 'Tester'].map(
165
+ primitiveToSelectOption,
166
+ ),
168
167
  },
169
168
  },
170
169
  ),
@@ -185,7 +184,7 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
185
184
  },
186
185
  },
187
186
  ),
188
- GridPopoverEditDropDown(
187
+ GridPopoverEditDropDown<ITestRow, ITestRow['code'], string | null>(
189
188
  {
190
189
  field: 'code',
191
190
  headerName: 'Filter Selectable',
@@ -197,17 +196,21 @@ const GridEditDropDownTemplate: StoryFn<typeof Grid> = (props: GridProps) => {
197
196
  filterPlaceholder: 'Filter this',
198
197
  filterHelpText: 'Press enter to save custom value',
199
198
  options: optionsObjects.map((o) => {
200
- return { value: o, label: o.desc, disabled: false };
199
+ return { value: o.code, label: o.desc, disabled: false };
201
200
  }),
202
201
  onSelectedItem: (selected) => {
203
202
  // eslint-disable-next-line no-console
204
203
  console.log('onSelectedItem selected', selected);
205
- selected.selectedRows.forEach((row) => (row.code = selected.value.code));
204
+ selected.selectedRows.forEach((row) => {
205
+ row.code = selected.value;
206
+ });
206
207
  },
207
208
  onSelectFilter: (selected) => {
208
209
  // eslint-disable-next-line no-console
209
210
  console.log('onSelectFilter selected', selected);
210
- selected.selectedRows.forEach((row) => (row.code = selected.value));
211
+ selected.selectedRows.forEach((row) => {
212
+ row.code = selected.value;
213
+ });
211
214
  },
212
215
  },
213
216
  },