@linzjs/step-ag-grid 2.5.0 → 3.0.1

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 (39) hide show
  1. package/dist/index.js +361 -282
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/GridCell.d.ts +4 -7
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormEditBearing.d.ts +1 -1
  6. package/dist/src/components/gridForm/GridFormMessage.d.ts +3 -3
  7. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +2 -2
  8. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +8 -3
  9. package/dist/src/components/gridForm/GridFormSubComponentTextInput.d.ts +1 -1
  10. package/dist/src/components/gridForm/GridFormTextArea.d.ts +1 -1
  11. package/dist/src/components/gridForm/GridFormTextInput.d.ts +1 -1
  12. package/dist/src/contexts/GridPopoverContext.d.ts +10 -6
  13. package/dist/src/contexts/GridPopoverContextProvider.d.ts +3 -1
  14. package/dist/src/lui/TextInputFormatted.d.ts +0 -1
  15. package/dist/step-ag-grid.esm.js +362 -283
  16. package/dist/step-ag-grid.esm.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/components/GridCell.tsx +9 -33
  19. package/src/components/GridPopoverHook.tsx +4 -8
  20. package/src/components/GridSubComponentTextArea.tsx +0 -6
  21. package/src/components/gridForm/GridFormDropDown.tsx +35 -37
  22. package/src/components/gridForm/GridFormEditBearing.tsx +12 -8
  23. package/src/components/gridForm/GridFormMessage.tsx +8 -6
  24. package/src/components/gridForm/GridFormMultiSelect.tsx +56 -48
  25. package/src/components/gridForm/GridFormPopoverMenu.tsx +112 -29
  26. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +2 -1
  27. package/src/components/gridForm/GridFormTextArea.tsx +11 -9
  28. package/src/components/gridForm/GridFormTextInput.tsx +13 -16
  29. package/src/contexts/GridPopoverContext.tsx +18 -9
  30. package/src/contexts/GridPopoverContextProvider.tsx +24 -27
  31. package/src/lui/TextAreaInput.tsx +13 -1
  32. package/src/lui/TextInputFormatted.tsx +3 -2
  33. package/src/react-menu3/components/MenuList.tsx +42 -10
  34. package/src/react-menu3/contexts/SettingsContext.ts +1 -1
  35. package/src/stories/grid/FormTest.tsx +17 -15
  36. package/src/stories/grid/GridPopoutEditDropDown.stories.tsx +3 -1
  37. package/src/stories/grid/GridPopoutEditGenericTextArea.stories.tsx +0 -1
  38. package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +3 -3
  39. package/src/stories/grid/GridReadOnly.stories.tsx +15 -5
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": "2.5.0",
5
+ "version": "3.0.1",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -9,15 +9,14 @@ import {
9
9
  } from "./gridRender/GridRenderGenericCell";
10
10
  import { ColDef, ICellEditorParams, ICellRendererParams } from "ag-grid-community";
11
11
  import { GridLoadableCell } from "./GridLoadableCell";
12
- import { GridIcon } from "../components/GridIcon";
12
+ import { GridIcon } from "./GridIcon";
13
13
  import { ValueFormatterParams } from "ag-grid-community/dist/lib/entities/colDef";
14
- import { GridPopoverContext } from "../contexts/GridPopoverContext";
15
14
  import { GridPopoverContextProvider } from "../contexts/GridPopoverContextProvider";
16
15
 
17
16
  export interface GenericCellEditorProps<E> {
18
17
  multiEdit?: boolean;
19
18
  editor?: (editorProps: E) => JSX.Element;
20
- editorParams?: E; // Omit<E, keyof CellParams<IFormTestRow>>
19
+ editorParams?: E;
21
20
  }
22
21
 
23
22
  export const GridCellRenderer = (props: ICellRendererParams) => {
@@ -46,6 +45,7 @@ export const GridCellRenderer = (props: ICellRendererParams) => {
46
45
  // This is so that typescript retains the row type to pass to the GridCells
47
46
  export interface ColDefT<RowType extends GridBaseRow> extends ColDef {
48
47
  _?: RowType;
48
+ editor?: (editorProps: any) => JSX.Element;
49
49
  }
50
50
 
51
51
  /*
@@ -66,7 +66,7 @@ export const GridCell = <RowType extends GridBaseRow, Props extends CellEditorCo
66
66
  ...(custom?.editor && {
67
67
  cellClass: custom?.multiEdit ? GenericMultiEditCellClass : undefined,
68
68
  editable: props.editable ?? true,
69
- cellEditor: GenericCellEditorComponent(custom.editor),
69
+ cellEditor: GenericCellEditorComponentWrapper(custom),
70
70
  }),
71
71
  ...(custom?.editorParams && {
72
72
  cellEditorParams: { ...custom.editorParams, multiEdit: custom.multiEdit },
@@ -91,37 +91,13 @@ export interface CellEditorCommon {
91
91
  className?: string | undefined;
92
92
  }
93
93
 
94
- export interface CellParams<RowType extends GridBaseRow> {
95
- value: any;
96
- data: RowType;
97
- field: string | undefined;
98
- selectedRows: RowType[];
99
- }
100
-
101
- // TODO memo?
102
- export const GenericCellEditorComponent = (editor: (props: any) => JSX.Element) =>
103
- forwardRef(function GenericCellEditorComponent2(props: ICellEditorParams, _) {
94
+ export const GenericCellEditorComponentWrapper = (custom?: { editor?: (props: any) => JSX.Element }) => {
95
+ return forwardRef(function GenericCellEditorComponentFr(cellEditorParams: ICellEditorParams, _) {
104
96
  return (
105
- <GridPopoverContextProvider>
106
- <GenericCellEditorComponent3 {...{ ...props, editor }} />
97
+ <GridPopoverContextProvider props={cellEditorParams}>
98
+ {<cellEditorParams.colDef.cellRenderer {...cellEditorParams} {...cellEditorParams.colDef.cellRendererParams} />}
99
+ {custom?.editor && <custom.editor {...cellEditorParams} />}
107
100
  </GridPopoverContextProvider>
108
101
  );
109
102
  });
110
-
111
- const GenericCellEditorComponent3 = (props: ICellEditorParams & { editor: (props: any) => JSX.Element }) => {
112
- const { setProps, propsRef } = useContext(GridPopoverContext);
113
-
114
- const { colDef } = props;
115
- const { cellEditorParams } = colDef;
116
- const multiEdit = cellEditorParams?.multiEdit ?? false;
117
-
118
- // TODO don't need all these props in context
119
- setProps(props, multiEdit);
120
-
121
- return (
122
- <>
123
- {<colDef.cellRenderer {...props} />}
124
- {props?.editor && <props.editor {...cellEditorParams} {...propsRef.current} />}
125
- </>
126
- );
127
103
  };
@@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useRef, useState } from "react";
2
2
  import { GridContext } from "../contexts/GridContext";
3
3
  import { GridBaseRow } from "./Grid";
4
4
  import { ControlledMenu } from "../react-menu3";
5
- import { GridPopoverContext } from "../contexts/GridPopoverContext";
5
+ import { useGridPopoverContext } from "../contexts/GridPopoverContext";
6
6
  import { MenuCloseEvent } from "../react-menu3/types";
7
7
 
8
8
  export interface GridPopoverHookProps<RowType> {
@@ -12,7 +12,7 @@ export interface GridPopoverHookProps<RowType> {
12
12
 
13
13
  export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopoverHookProps<RowType>) => {
14
14
  const { stopEditing } = useContext(GridContext);
15
- const { anchorRef, saving, propsRef } = useContext(GridPopoverContext);
15
+ const { anchorRef, saving, updateValue } = useGridPopoverContext<RowType>();
16
16
  const saveButtonRef = useRef<HTMLButtonElement>(null);
17
17
  const [isOpen, setOpen] = useState(false);
18
18
 
@@ -22,16 +22,12 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
22
22
 
23
23
  const triggerSave = useCallback(
24
24
  async (reason?: string) => {
25
- if (
26
- reason == "cancel" ||
27
- !props.save ||
28
- (propsRef.current?.updateValue && (await propsRef.current?.updateValue(props.save)))
29
- ) {
25
+ if (reason == "cancel" || !props.save || (updateValue && (await updateValue(props.save)))) {
30
26
  setOpen(false);
31
27
  stopEditing();
32
28
  }
33
29
  },
34
- [props, stopEditing, propsRef],
30
+ [props.save, stopEditing, updateValue],
35
31
  );
36
32
 
37
33
  const popoverWrapper = useCallback(
@@ -49,12 +49,6 @@ export const GridSubComponentTextArea = (props: GridSubComponentTextAreaProps):
49
49
  <TextInputFormatted
50
50
  className={"free-text-input"}
51
51
  value={value}
52
- onMouseEnter={(e) => {
53
- if (document.activeElement != e.currentTarget) {
54
- e.currentTarget.focus();
55
- e.currentTarget.selectionStart = e.currentTarget.value.length;
56
- }
57
- }}
58
52
  onChange={(e) => setValue(e.target.value)}
59
53
  error={validate(value)}
60
54
  inputProps={{
@@ -1,14 +1,15 @@
1
1
  import "../../styles/GridFormDropDown.scss";
2
2
 
3
- import { MenuItem, MenuDivider, FocusableItem, MenuHeader } from "../../react-menu3";
4
- import { useCallback, useContext, useEffect, useRef, useState, KeyboardEvent } from "react";
3
+ import { FocusableItem, MenuDivider, MenuHeader, MenuItem } from "../../react-menu3";
4
+ import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
7
7
  import { GridContext } from "../../contexts/GridContext";
8
8
  import { delay } from "lodash-es";
9
9
  import debounce from "debounce-promise";
10
- import { CellEditorCommon, CellParams } from "../GridCell";
10
+ import { CellEditorCommon } from "../GridCell";
11
11
  import { useGridPopoverHook } from "../GridPopoverHook";
12
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
12
13
 
13
14
  export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
14
15
  // Note the row that was clicked on will be first
@@ -43,6 +44,7 @@ export interface GridFormPopoutDropDownProps<RowType extends GridBaseRow, ValueT
43
44
  | "GridPopoverEditDropDown-containerUnlimited"
44
45
  | string
45
46
  | undefined;
47
+ // local means the filter won't change if it's reloaded, reload means it does change
46
48
  filtered?: "local" | "reload";
47
49
  filterPlaceholder?: string;
48
50
  onSelectedItem?: (props: GridPopoutEditDropDownSelectedItem<RowType, ValueType>) => Promise<void>;
@@ -53,11 +55,15 @@ export interface GridFormPopoutDropDownProps<RowType extends GridBaseRow, ValueT
53
55
  optionsRequestCancel?: () => void;
54
56
  }
55
57
 
58
+ const fieldToString = (field: any) => {
59
+ return typeof field == "symbol" ? field.toString() : `${field}`;
60
+ };
61
+
56
62
  export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
57
- _props: GridFormPopoutDropDownProps<RowType, ValueType>,
63
+ props: GridFormPopoutDropDownProps<RowType, ValueType>,
58
64
  ) => {
59
- const props = _props as GridFormPopoutDropDownProps<RowType, ValueType> & CellParams<RowType>;
60
- const { updatingCells, stopEditing } = useContext(GridContext);
65
+ const { selectedRows, field, updateValue } = useGridPopoverContext<RowType>();
66
+ const { stopEditing } = useContext(GridContext);
61
67
 
62
68
  const [filter, setFilter] = useState("");
63
69
  const [filteredValues, setFilteredValues] = useState<any[]>([]);
@@ -66,9 +72,8 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
66
72
  const [subComponentValues, setSubComponentValues] = useState<{ optionValue: any; subComponentValue: any }[]>([]);
67
73
 
68
74
  const selectItemHandler = useCallback(
69
- async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> => {
70
- const field = props.field;
71
- return await updatingCells({ selectedRows: props.selectedRows, field }, async (selectedRows) => {
75
+ async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> =>
76
+ updateValue(async (selectedRows) => {
72
77
  const hasChanged = selectedRows.some((row) => row[field as keyof RowType] !== value);
73
78
  if (hasChanged) {
74
79
  if (props.onSelectedItem) {
@@ -78,22 +83,17 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
78
83
  }
79
84
  }
80
85
  return true;
81
- });
82
- },
83
- [props, updatingCells],
86
+ }),
87
+ [field, props, updateValue],
84
88
  );
85
89
 
86
90
  const selectFilterHandler = useCallback(
87
- async (value: string): Promise<boolean> => {
88
- const field = props.field;
89
- return await updatingCells({ selectedRows: props.selectedRows, field }, async (selectedRows) => {
90
- if (props.onSelectFilter) {
91
- await props.onSelectFilter({ selectedRows, value });
92
- }
91
+ async (value: string) =>
92
+ updateValue(async (selectedRows) => {
93
+ props.onSelectFilter && (await props.onSelectFilter({ selectedRows, value }));
93
94
  return true;
94
- });
95
- },
96
- [props, updatingCells],
95
+ }),
96
+ [props, updateValue],
97
97
  );
98
98
 
99
99
  // Load up options list if it's async function
@@ -104,7 +104,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
104
104
 
105
105
  (async () => {
106
106
  if (typeof optionsConf == "function") {
107
- optionsConf = await optionsConf(props.selectedRows, filter);
107
+ optionsConf = await optionsConf(selectedRows, filter);
108
108
  }
109
109
 
110
110
  const optionsList = optionsConf?.map((item) => {
@@ -127,7 +127,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
127
127
  }
128
128
  optionsInitialising.current = false;
129
129
  })();
130
- }, [filter, options, props]);
130
+ }, [filter, options, props, selectedRows]);
131
131
 
132
132
  // Local filtering.
133
133
  useEffect(() => {
@@ -214,7 +214,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
214
214
  <ComponentLoadingWrapper loading={!options} className={"GridFormDropDown-options"}>
215
215
  <>
216
216
  {options && options.length == filteredValues?.length && (
217
- <MenuItem key={`${props.field}-empty`}>[Empty]</MenuItem>
217
+ <MenuItem key={`${fieldToString(field)}-empty`}>[Empty]</MenuItem>
218
218
  )}
219
219
  {options?.map((item: FinalSelectOption<ValueType | string>, index) =>
220
220
  item.value === MenuSeparatorString ? (
@@ -225,23 +225,22 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
225
225
  <div key={`menu-wrapper-${index}`}>
226
226
  {!item.subComponent ? (
227
227
  <MenuItem
228
- key={`${props.field}-${index}`}
228
+ key={`${fieldToString(field)}-${index}`}
229
229
  disabled={!!item.disabled}
230
230
  title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
231
231
  value={item.value}
232
232
  onClick={() => {
233
- selectItemHandler(item.value as ValueType);
233
+ selectItemHandler(item.value as ValueType).then();
234
234
  }}
235
235
  >
236
236
  {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
237
237
  </MenuItem>
238
238
  ) : (
239
- <FocusableItem className={"LuiDeprecatedForms"} key={`${props.field}-${index}_subcomponent`}>
239
+ <FocusableItem className={"LuiDeprecatedForms"} key={`${fieldToString(field)}-${index}_subcomponent`}>
240
240
  {(ref: any) =>
241
- item.subComponent &&
242
- item.subComponent(
243
- {
244
- setValue: (value: any) => {
241
+ item.subComponent && (
242
+ <item.subComponent
243
+ setValue={(value: any) => {
245
244
  const localSubComponentValues = [...subComponentValues];
246
245
  const subComponentValueIndex = localSubComponentValues.findIndex(
247
246
  ({ optionValue }) => optionValue === item.value,
@@ -255,8 +254,8 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
255
254
  });
256
255
  }
257
256
  setSubComponentValues(localSubComponentValues);
258
- },
259
- keyDown: (key: string, event: KeyboardEvent<HTMLInputElement>) => {
257
+ }}
258
+ keyDown={(key: string, event: KeyboardEvent<HTMLInputElement>) => {
260
259
  const subComponentItem = subComponentValues.find(
261
260
  ({ optionValue }) => optionValue === item.value,
262
261
  );
@@ -272,10 +271,9 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
272
271
  });
273
272
  }
274
273
  return false;
275
- },
276
- key: `${props.field}-${index}_subcomponent_inner`,
277
- },
278
- ref,
274
+ }}
275
+ key={`${fieldToString(field)}-${index}_subcomponent_inner`}
276
+ />
279
277
  )
280
278
  }
281
279
  </FocusableItem>
@@ -5,7 +5,8 @@ import { GridBaseRow } from "../Grid";
5
5
  import { TextInputFormatted } from "../../lui/TextInputFormatted";
6
6
  import { bearingNumberParser, bearingStringValidator, convertDDToDMS } from "../../utils/bearing";
7
7
  import { useGridPopoverHook } from "../GridPopoverHook";
8
- import { CellEditorCommon, CellParams } from "../GridCell";
8
+ import { CellEditorCommon } from "../GridCell";
9
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
9
10
 
10
11
  export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends CellEditorCommon {
11
12
  placeHolder?: string;
@@ -13,31 +14,34 @@ export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends C
13
14
  onSave?: (selectedRows: RowType[], value: number | null) => Promise<boolean>;
14
15
  }
15
16
 
16
- export const GridFormEditBearing = <RowType extends GridBaseRow>(_props: GridFormEditBearingProps<RowType>) => {
17
- const props = _props as GridFormEditBearingProps<RowType> & CellParams<RowType>;
18
- const [value, setValue] = useState<string>(`${props.value ?? ""}`);
17
+ export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridFormEditBearingProps<RowType>) => {
18
+ const { field, value: initialValue } = useGridPopoverContext<RowType>();
19
+
20
+ const [value, setValue] = useState<string>(`${initialValue ?? ""}`);
21
+
19
22
  const save = useCallback(
20
23
  async (selectedRows: RowType[]): Promise<boolean> => {
21
24
  if (bearingStringValidator(value)) return false;
22
25
  const parsedValue = bearingNumberParser(value);
23
26
  // Value didn't change so don't save just cancel
24
- if (parsedValue === props.value) {
27
+ if (parsedValue === initialValue) {
25
28
  return true;
26
29
  }
27
30
 
28
31
  if (props.onSave) {
29
32
  return await props.onSave(selectedRows, parsedValue);
30
33
  } else {
31
- const field = props.field;
32
34
  if (field == null) {
33
35
  console.error("field is not defined in ColDef");
34
36
  } else {
35
- selectedRows.forEach((row) => ((row as Record<string, any>)[field] = parsedValue));
37
+ selectedRows.forEach((row) => {
38
+ row[field] = parsedValue as any;
39
+ });
36
40
  }
37
41
  }
38
42
  return true;
39
43
  },
40
- [props, value],
44
+ [field, initialValue, props, value],
41
45
  );
42
46
  const { triggerSave, popoverWrapper } = useGridPopoverHook({ className: props.className, save });
43
47
 
@@ -3,22 +3,24 @@ import { useEffect, useState } from "react";
3
3
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
4
4
  import { GridBaseRow } from "../Grid";
5
5
  import { useGridPopoverHook } from "../GridPopoverHook";
6
- import { CellEditorCommon, CellParams } from "../GridCell";
6
+ import { CellEditorCommon } from "../GridCell";
7
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
7
8
 
8
9
  export interface GridFormMessageProps<RowType extends GridBaseRow> extends CellEditorCommon {
9
- message: (cellParams: CellParams<RowType>) => Promise<string | JSX.Element> | string | JSX.Element;
10
+ message: (selectedRows: RowType[]) => Promise<string | JSX.Element> | string | JSX.Element;
10
11
  }
11
12
 
12
- export const GridFormMessage = <RowType extends GridBaseRow>(_props: GridFormMessageProps<RowType>) => {
13
- const props = _props as GridFormMessageProps<RowType> & CellParams<RowType>;
13
+ export const GridFormMessage = <RowType extends GridBaseRow>(props: GridFormMessageProps<RowType>) => {
14
+ const { selectedRows } = useGridPopoverContext<RowType>();
15
+
14
16
  const [message, setMessage] = useState<string | JSX.Element | null>(null);
15
17
  const { popoverWrapper } = useGridPopoverHook({ className: props.className });
16
18
 
17
19
  useEffect(() => {
18
20
  (async () => {
19
- setMessage(await props.message(props));
21
+ setMessage(await props.message(selectedRows));
20
22
  })().then();
21
- }, [props]);
23
+ }, [props, selectedRows]);
22
24
 
23
25
  return popoverWrapper(
24
26
  <ComponentLoadingWrapper loading={message === null} className={clsx("GridFormMessage-container", props.className)}>
@@ -1,16 +1,17 @@
1
1
  import "../../styles/GridFormMultiSelect.scss";
2
2
 
3
- import { MenuItem, MenuDivider, FocusableItem } from "../../react-menu3";
4
- import { useCallback, useEffect, useRef, useState, KeyboardEvent, useMemo } from "react";
3
+ import { FocusableItem, MenuDivider, MenuItem } from "../../react-menu3";
4
+ import { KeyboardEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
7
- import { delay, pick, pickBy } from "lodash-es";
7
+ import { delay, fromPairs, isEqual, omit, pick, toPairs } from "lodash-es";
8
8
  import { LuiCheckboxInput } from "@linzjs/lui";
9
9
  import { useGridPopoverHook } from "../GridPopoverHook";
10
10
  import { MenuSeparatorString } from "./GridFormDropDown";
11
- import { CellEditorCommon, CellParams } from "../GridCell";
11
+ import { CellEditorCommon } from "../GridCell";
12
12
  import { ClickEvent } from "../../react-menu3/types";
13
13
  import { GridSubComponentContext } from "contexts/GridSubComponentContext";
14
+ import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
14
15
 
15
16
  interface MultiFinalSelectOption<ValueType> {
16
17
  value: ValueType;
@@ -21,11 +22,10 @@ interface MultiFinalSelectOption<ValueType> {
21
22
  export type MultiSelectOption<ValueType> = ValueType | MultiFinalSelectOption<ValueType>;
22
23
 
23
24
  export interface SelectedOptionResult<ValueType> extends MultiFinalSelectOption<ValueType> {
24
- subValue: any;
25
+ subValue?: any;
25
26
  }
26
27
 
27
28
  export interface GridFormMultiSelectProps<RowType extends GridBaseRow, ValueType> extends CellEditorCommon {
28
- // This overrides CellEditorCommon to provide some common class options
29
29
  className?:
30
30
  | "GridMultiSelect-containerSmall"
31
31
  | "GridMultiSelect-containerMedium"
@@ -39,28 +39,22 @@ export interface GridFormMultiSelectProps<RowType extends GridBaseRow, ValueType
39
39
  options:
40
40
  | MultiSelectOption<ValueType>[]
41
41
  | ((selectedRows: RowType[]) => Promise<MultiSelectOption<ValueType>[]> | MultiSelectOption<ValueType>[]);
42
- initialSelectedValues?: (selectedRows: RowType[]) => any[] | Record<string, any>;
42
+ initialSelectedValues?: (selectedRows: RowType[]) => any[] | Record<string, null | any>;
43
43
  }
44
44
 
45
45
  export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
46
46
  props: GridFormMultiSelectProps<RowType, ValueType>,
47
47
  ) => {
48
- const { selectedRows } = props as unknown as CellParams<RowType>;
48
+ const { selectedRows } = useGridPopoverContext<RowType>();
49
49
 
50
- const initialiseValues = useMemo(
51
- () => props.initialSelectedValues && props.initialSelectedValues(selectedRows),
52
- [props, selectedRows],
53
- );
50
+ const initialiseValues = useMemo(() => {
51
+ const r = props.initialSelectedValues && props.initialSelectedValues(selectedRows);
52
+ // convert array of strings to object<value,null>
53
+ return Array.isArray(r) ? fromPairs(r.map((v) => [v, null])) : r;
54
+ }, [props, selectedRows]);
54
55
 
55
56
  const subComponentIsValid = useRef<Record<string, boolean>>({});
56
- const [subSelectedValues, setSubSelectedValues] = useState<Record<string, any>>(
57
- initialiseValues && !Array.isArray(initialiseValues)
58
- ? pickBy(initialiseValues, (value) => typeof value !== "boolean")
59
- : {},
60
- );
61
- const [selectedValues, setSelectedValues] = useState<any[]>(() =>
62
- initialiseValues ? (Array.isArray(initialiseValues) ? initialiseValues : Object.keys(initialiseValues)) : [],
63
- );
57
+ const [selectedValues, setSelectedValues] = useState<Record<string, null | any>>(() => initialiseValues ?? {});
64
58
 
65
59
  const [filter, setFilter] = useState("");
66
60
  const [filteredValues, setFilteredValues] = useState<any[]>([]);
@@ -70,23 +64,33 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
70
64
  const save = useCallback(
71
65
  async (selectedRows: RowType[]): Promise<boolean> => {
72
66
  if (props.onSave) {
73
- const validations = pick(subComponentIsValid.current, selectedValues);
67
+ if (!options) {
68
+ console.error("options not initialised");
69
+ return false;
70
+ }
71
+
72
+ const validations = pick(subComponentIsValid.current, Object.keys(selectedValues));
74
73
  const notValid = Object.values(validations).some((v) => !v);
75
74
  if (notValid) return false;
76
75
 
77
- const selectedOptions = selectedValues.map((row) => (options ?? []).find((opt) => opt.value == row)) ?? [];
78
- const selectedResults = selectedOptions.map(
79
- (selectedOption) =>
80
- ({
81
- ...selectedOption,
82
- subValue: subSelectedValues[`${selectedOption?.value}`],
83
- } as SelectedOptionResult<ValueType>),
84
- );
85
- return await props.onSave(selectedRows, selectedResults);
76
+ const menuOptionSubValueResult = toPairs(selectedValues).map(([value, subValue]) => {
77
+ const o = {
78
+ ...options.find((o) => o.value == value),
79
+ } as SelectedOptionResult<ValueType>;
80
+ o.subValue = subValue;
81
+ return o;
82
+ });
83
+
84
+ if (isEqual(initialiseValues, selectedValues)) {
85
+ // No changes to save
86
+ return true;
87
+ }
88
+
89
+ return await props.onSave(selectedRows, menuOptionSubValueResult);
86
90
  }
87
91
  return true;
88
92
  },
89
- [options, props, selectedValues, subSelectedValues],
93
+ [initialiseValues, options, props, selectedValues],
90
94
  );
91
95
 
92
96
  // Load up options list if it's async function
@@ -101,7 +105,7 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
101
105
  }
102
106
 
103
107
  const optionsList = optionsConf?.map((item) => {
104
- if (item == null || typeof item == "string" || typeof item == "number") {
108
+ if (item == null || typeof item === "string" || typeof item === "number") {
105
109
  item = { value: item as ValueType, label: item } as MultiFinalSelectOption<ValueType>;
106
110
  }
107
111
  return item;
@@ -134,6 +138,17 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
134
138
  );
135
139
  }, [props.filtered, filter, options]);
136
140
 
141
+ const toggleValue = useCallback(
142
+ (item: MultiFinalSelectOption<ValueType>) => {
143
+ if (`${item.value}` in selectedValues) {
144
+ setSelectedValues(omit(selectedValues, [`${item.value}`]));
145
+ } else {
146
+ setSelectedValues({ ...selectedValues, [`${item.value}`]: null });
147
+ }
148
+ },
149
+ [selectedValues],
150
+ );
151
+
137
152
  const { popoverWrapper, triggerSave } = useGridPopoverHook({ className: props.className, save });
138
153
  return popoverWrapper(
139
154
  <ComponentLoadingWrapper loading={!options} className={"GridFormMultiSelect-container"}>
@@ -167,30 +182,21 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
167
182
  ) : filteredValues.includes(item.value) ? null : (
168
183
  <div key={`${index}`}>
169
184
  <MenuItem
170
- key={`${index}`}
171
185
  onClick={(e: ClickEvent) => {
172
186
  e.keepOpen = true;
173
- if (selectedValues.includes(item.value)) {
174
- setSelectedValues(selectedValues.filter((value) => value != item.value));
175
- } else {
176
- setSelectedValues([...selectedValues, item.value]);
177
- }
187
+ toggleValue(item);
178
188
  }}
179
189
  onKeyDown={async (e: KeyboardEvent) => {
180
190
  if (e.key === "Enter") triggerSave().then();
181
191
  else if (e.key === " ") {
182
- if (selectedValues.includes(item.value)) {
183
- setSelectedValues(selectedValues.filter((value) => value != item.value));
184
- } else {
185
- setSelectedValues([...selectedValues, item.value]);
186
- }
192
+ toggleValue(item);
187
193
  e.preventDefault();
188
194
  e.stopPropagation();
189
195
  }
190
196
  }}
191
197
  >
192
198
  <LuiCheckboxInput
193
- isChecked={selectedValues.includes(item.value)}
199
+ isChecked={`${item.value}` in selectedValues}
194
200
  value={`${item.value}`}
195
201
  label={item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
196
202
  inputProps={{
@@ -206,16 +212,18 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
206
212
  />
207
213
  </MenuItem>
208
214
 
209
- {selectedValues.includes(item.value) && item.subComponent && (
215
+ {`${item.value}` in selectedValues && item.subComponent && (
210
216
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
211
217
  {(_: any) =>
212
218
  item.subComponent && (
213
219
  <GridSubComponentContext.Provider
214
220
  value={{
215
- value: subSelectedValues[`${item.value}`],
221
+ value: selectedValues[`${item.value}`],
216
222
  setValue: (value: any) => {
217
- subSelectedValues[`${item.value}`] = value;
218
- setSubSelectedValues({ ...subSelectedValues });
223
+ setSelectedValues({
224
+ ...selectedValues,
225
+ [`${item.value}`]: value,
226
+ });
219
227
  },
220
228
  setValid: (valid: boolean) => {
221
229
  subComponentIsValid.current[`${item.value}`] = valid;