@linzjs/step-ag-grid 2.4.11 → 3.0.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 (38) hide show
  1. package/dist/index.js +301 -249
  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 +7 -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 +5 -3
  13. package/dist/src/contexts/GridPopoverContextProvider.d.ts +3 -1
  14. package/dist/src/lui/TextInputFormatted.d.ts +3 -2
  15. package/dist/step-ag-grid.esm.js +302 -250
  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 +3 -7
  20. package/src/components/GridSubComponentTextArea.tsx +0 -6
  21. package/src/components/gridForm/GridFormDropDown.tsx +23 -30
  22. package/src/components/gridForm/GridFormEditBearing.tsx +10 -8
  23. package/src/components/gridForm/GridFormMessage.tsx +9 -7
  24. package/src/components/gridForm/GridFormMultiSelect.tsx +56 -48
  25. package/src/components/gridForm/GridFormPopoverMenu.tsx +94 -29
  26. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +2 -1
  27. package/src/components/gridForm/GridFormTextArea.tsx +8 -8
  28. package/src/components/gridForm/GridFormTextInput.tsx +12 -16
  29. package/src/contexts/GridPopoverContext.tsx +10 -5
  30. package/src/contexts/GridPopoverContextProvider.tsx +24 -27
  31. package/src/lui/TextAreaInput.tsx +13 -1
  32. package/src/lui/TextInputFormatted.tsx +14 -3
  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 +18 -16
  36. package/src/stories/grid/GridPopoutEditGenericTextArea.stories.tsx +0 -1
  37. package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +3 -3
  38. 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.4.11",
5
+ "version": "3.0.0",
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
  };
@@ -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 } = useContext(GridPopoverContext);
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 { GridPopoverContext } from "../../contexts/GridPopoverContext";
12
13
 
13
14
  export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
14
15
  // Note the row that was clicked on will be first
@@ -54,10 +55,10 @@ export interface GridFormPopoutDropDownProps<RowType extends GridBaseRow, ValueT
54
55
  }
55
56
 
56
57
  export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
57
- _props: GridFormPopoutDropDownProps<RowType, ValueType>,
58
+ props: GridFormPopoutDropDownProps<RowType, ValueType>,
58
59
  ) => {
59
- const props = _props as GridFormPopoutDropDownProps<RowType, ValueType> & CellParams<RowType>;
60
- const { updatingCells, stopEditing } = useContext(GridContext);
60
+ const { selectedRows, field, updateValue } = useContext(GridPopoverContext);
61
+ const { stopEditing } = useContext(GridContext);
61
62
 
62
63
  const [filter, setFilter] = useState("");
63
64
  const [filteredValues, setFilteredValues] = useState<any[]>([]);
@@ -66,9 +67,8 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
66
67
  const [subComponentValues, setSubComponentValues] = useState<{ optionValue: any; subComponentValue: any }[]>([]);
67
68
 
68
69
  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) => {
70
+ async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> =>
71
+ updateValue(async (selectedRows) => {
72
72
  const hasChanged = selectedRows.some((row) => row[field as keyof RowType] !== value);
73
73
  if (hasChanged) {
74
74
  if (props.onSelectedItem) {
@@ -78,22 +78,17 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
78
78
  }
79
79
  }
80
80
  return true;
81
- });
82
- },
83
- [props, updatingCells],
81
+ }),
82
+ [field, props, updateValue],
84
83
  );
85
84
 
86
85
  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
- }
86
+ async (value: string) =>
87
+ updateValue(async (selectedRows) => {
88
+ props.onSelectFilter && (await props.onSelectFilter({ selectedRows, value }));
93
89
  return true;
94
- });
95
- },
96
- [props, updatingCells],
90
+ }),
91
+ [props, updateValue],
97
92
  );
98
93
 
99
94
  // Load up options list if it's async function
@@ -104,7 +99,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
104
99
 
105
100
  (async () => {
106
101
  if (typeof optionsConf == "function") {
107
- optionsConf = await optionsConf(props.selectedRows, filter);
102
+ optionsConf = await optionsConf(selectedRows, filter);
108
103
  }
109
104
 
110
105
  const optionsList = optionsConf?.map((item) => {
@@ -127,7 +122,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
127
122
  }
128
123
  optionsInitialising.current = false;
129
124
  })();
130
- }, [filter, options, props]);
125
+ }, [filter, options, props, selectedRows]);
131
126
 
132
127
  // Local filtering.
133
128
  useEffect(() => {
@@ -213,9 +208,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
213
208
  )}
214
209
  <ComponentLoadingWrapper loading={!options} className={"GridFormDropDown-options"}>
215
210
  <>
216
- {options && options.length == filteredValues?.length && (
217
- <MenuItem key={`${props.field}-empty`}>[Empty]</MenuItem>
218
- )}
211
+ {options && options.length == filteredValues?.length && <MenuItem key={`${field}-empty`}>[Empty]</MenuItem>}
219
212
  {options?.map((item: FinalSelectOption<ValueType | string>, index) =>
220
213
  item.value === MenuSeparatorString ? (
221
214
  <MenuDivider key={`$$divider_${index}`} />
@@ -225,18 +218,18 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
225
218
  <div key={`menu-wrapper-${index}`}>
226
219
  {!item.subComponent ? (
227
220
  <MenuItem
228
- key={`${props.field}-${index}`}
221
+ key={`${field}-${index}`}
229
222
  disabled={!!item.disabled}
230
223
  title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
231
224
  value={item.value}
232
225
  onClick={() => {
233
- selectItemHandler(item.value as ValueType);
226
+ selectItemHandler(item.value as ValueType).then();
234
227
  }}
235
228
  >
236
229
  {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
237
230
  </MenuItem>
238
231
  ) : (
239
- <FocusableItem className={"LuiDeprecatedForms"} key={`${props.field}-${index}_subcomponent`}>
232
+ <FocusableItem className={"LuiDeprecatedForms"} key={`${field}-${index}_subcomponent`}>
240
233
  {(ref: any) =>
241
234
  item.subComponent &&
242
235
  item.subComponent(
@@ -273,7 +266,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
273
266
  }
274
267
  return false;
275
268
  },
276
- key: `${props.field}-${index}_subcomponent_inner`,
269
+ key: `${field}-${index}_subcomponent_inner`,
277
270
  },
278
271
  ref,
279
272
  )
@@ -1,11 +1,12 @@
1
1
  import "../../styles/GridFormEditBearing.scss";
2
2
 
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useContext, useState } from "react";
4
4
  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 { GridPopoverContext } from "../../contexts/GridPopoverContext";
9
10
 
10
11
  export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends CellEditorCommon {
11
12
  placeHolder?: string;
@@ -13,22 +14,23 @@ 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 } = useContext(GridPopoverContext);
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 {
@@ -37,7 +39,7 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(_props: GridFor
37
39
  }
38
40
  return true;
39
41
  },
40
- [props, value],
42
+ [field, initialValue, props, value],
41
43
  );
42
44
  const { triggerSave, popoverWrapper } = useGridPopoverHook({ className: props.className, save });
43
45
 
@@ -1,24 +1,26 @@
1
1
  import clsx from "clsx";
2
- import { useEffect, useState } from "react";
2
+ import { useContext, 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 { GridPopoverContext } 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 } = useContext(GridPopoverContext);
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, useContext, 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 { GridPopoverContext } 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 } = useContext(GridPopoverContext);
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;