@linzjs/step-ag-grid 4.0.2 → 5.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 (49) hide show
  1. package/dist/index.js +325 -265
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/GridPopoverHook.d.ts +2 -13
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
  6. package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +0 -1
  7. package/dist/src/contexts/GridContext.d.ts +1 -0
  8. package/dist/src/contexts/GridPopoverContext.d.ts +1 -1
  9. package/dist/src/contexts/GridUpdatingContext.d.ts +3 -2
  10. package/dist/src/contexts/GridUpdatingContextProvider.d.ts +1 -1
  11. package/dist/src/lui/FormError.d.ts +6 -0
  12. package/dist/src/react-menu3/components/MenuItem.d.ts +1 -1
  13. package/dist/src/react-menu3/components/SubMenu.d.ts +1 -1
  14. package/dist/src/react-menu3/hooks/useBEM.d.ts +1 -1
  15. package/dist/src/react-menu3/types.d.ts +18 -17
  16. package/dist/src/react-menu3/utils/constants.d.ts +4 -0
  17. package/dist/src/react-menu3/utils/utils.d.ts +1 -1
  18. package/dist/step-ag-grid.esm.js +326 -266
  19. package/dist/step-ag-grid.esm.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/GridPopoverHook.tsx +30 -79
  22. package/src/components/gridForm/GridFormDropDown.tsx +62 -46
  23. package/src/components/gridForm/GridFormEditBearing.tsx +16 -8
  24. package/src/components/gridForm/GridFormMultiSelect.tsx +12 -4
  25. package/src/components/gridForm/GridFormPopoverMenu.tsx +25 -22
  26. package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -14
  27. package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -18
  28. package/src/components/gridForm/GridFormTextArea.tsx +8 -5
  29. package/src/components/gridForm/GridFormTextInput.tsx +8 -4
  30. package/src/contexts/GridContext.tsx +4 -0
  31. package/src/contexts/GridContextProvider.tsx +8 -0
  32. package/src/contexts/GridPopoverContext.tsx +1 -1
  33. package/src/contexts/GridPopoverContextProvider.tsx +7 -4
  34. package/src/contexts/GridUpdatingContext.tsx +7 -1
  35. package/src/contexts/GridUpdatingContextProvider.tsx +17 -6
  36. package/src/lui/FormError.tsx +29 -0
  37. package/src/lui/TextAreaInput.tsx +2 -18
  38. package/src/lui/TextInputFormatted.tsx +2 -17
  39. package/src/react-menu3/components/ControlledMenu.tsx +114 -12
  40. package/src/react-menu3/components/MenuItem.tsx +19 -2
  41. package/src/react-menu3/types.ts +2 -0
  42. package/src/react-menu3/utils/constants.ts +4 -0
  43. package/src/stories/grid/FormTest.tsx +24 -21
  44. package/src/stories/grid/GridPopoutBearing.stories.tsx +3 -2
  45. package/src/stories/grid/GridPopoutEditDropDown.stories.tsx +10 -0
  46. package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +1 -1
  47. package/src/stories/grid/GridReadOnly.stories.tsx +11 -11
  48. package/src/styles/GridFormDropDown.scss +1 -1
  49. package/src/utils/bearing.ts +1 -1
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": "4.0.2",
5
+ "version": "5.0.0",
6
6
  "keywords": [
7
7
  "aggrid",
8
8
  "ag-grid",
@@ -1,12 +1,14 @@
1
- import { KeyboardEvent, KeyboardEventHandler, useCallback, useContext, useEffect, useRef, useState } from "react";
1
+ 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
5
  import { useGridPopoverContext } from "../contexts/GridPopoverContext";
6
6
  import { MenuCloseEvent } from "../react-menu3/types";
7
+ import { CloseReason } from "../react-menu3/utils";
7
8
 
8
9
  export interface GridPopoverHookProps<RowType> {
9
10
  className: string | undefined;
11
+ invalid?: () => Promise<boolean | string | null> | boolean | string | null;
10
12
  save?: (selectedRows: RowType[]) => Promise<boolean>;
11
13
  }
12
14
 
@@ -22,86 +24,31 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
22
24
 
23
25
  const triggerSave = useCallback(
24
26
  async (reason?: string) => {
25
- if (reason == "cancel" || !props.save || (updateValue && (await updateValue(props.save)))) {
26
- setOpen(false);
27
+ if (reason == "cancel") {
27
28
  stopEditing();
29
+ return;
28
30
  }
29
- },
30
- [props.save, stopEditing, updateValue],
31
- );
32
-
33
- const onlyInputKeyboardEventHandlers: {
34
- onKeyUp?: KeyboardEventHandler<HTMLElement> | undefined;
35
- onKeyDown?: KeyboardEventHandler<HTMLElement> | undefined;
36
- } = {
37
- onKeyUp: (e: KeyboardEvent) => {
38
- const isTextArea = (e.currentTarget as any).type === "textarea";
39
- if (e.key === "Enter" && !isTextArea) {
40
- e.preventDefault();
41
- e.stopPropagation();
42
- triggerSave().then();
43
- } else if (e.key === "Tab") {
44
- e.preventDefault();
45
- e.stopPropagation();
46
- }
47
- },
48
- onKeyDown: (e: KeyboardEvent) => {
49
- const isTextArea = (e.currentTarget as any).type === "textarea";
50
- if (e.key === "Enter" && !isTextArea) {
51
- e.preventDefault();
52
- e.stopPropagation();
53
- } else if (e.key === "Tab") {
54
- e.preventDefault();
55
- e.stopPropagation();
56
- !e.shiftKey && triggerSave().then();
31
+ if (props.invalid && props.invalid()) {
32
+ return;
57
33
  }
58
- },
59
- };
60
- const firstInputKeyboardEventHandlers: {
61
- onKeyUp?: KeyboardEventHandler<HTMLElement> | undefined;
62
- onKeyDown?: KeyboardEventHandler<HTMLElement> | undefined;
63
- } = {
64
- onKeyUp: (e: KeyboardEvent) => {
65
- if (e.key === "Tab" && e.shiftKey) {
66
- e.preventDefault();
67
- e.stopPropagation();
68
- }
69
- },
70
- onKeyDown: (e: KeyboardEvent) => {
71
- if (e.key === "Tab" && e.shiftKey) {
72
- e.preventDefault();
73
- e.stopPropagation();
74
- }
75
- },
76
- };
77
34
 
78
- const lastInputKeyboardEventHandlers: {
79
- onKeyUp?: KeyboardEventHandler<HTMLElement> | undefined;
80
- onKeyDown?: KeyboardEventHandler<HTMLElement> | undefined;
81
- } = {
82
- onKeyUp: (e: KeyboardEvent) => {
83
- const isTextArea = (e.currentTarget as any).type === "textarea";
84
- if (e.key === "Enter" && !isTextArea) {
85
- e.preventDefault();
86
- e.stopPropagation();
87
- triggerSave().then();
88
- } else if (e.key === "Tab" && !e.shiftKey) {
89
- e.preventDefault();
90
- e.stopPropagation();
91
- }
92
- },
93
- onKeyDown: (e: KeyboardEvent) => {
94
- const isTextArea = (e.currentTarget as any).type === "textarea";
95
- if (e.key === "Enter" && !isTextArea) {
96
- e.preventDefault();
97
- e.stopPropagation();
98
- } else if (e.key === "Tab" && !e.shiftKey) {
99
- e.preventDefault();
100
- e.stopPropagation();
101
- triggerSave().then();
35
+ if (!props.save) {
36
+ stopEditing();
37
+ } else if (props.save) {
38
+ // forms that don't provide an invalid fn must wait until they have saved to close
39
+ if (props.invalid) stopEditing();
40
+ if (
41
+ await updateValue(
42
+ props.save,
43
+ reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0,
44
+ )
45
+ ) {
46
+ if (!props.invalid) stopEditing();
47
+ }
102
48
  }
103
49
  },
104
- };
50
+ [props, stopEditing, updateValue],
51
+ );
105
52
 
106
53
  const popoverWrapper = useCallback(
107
54
  (children: JSX.Element) => {
@@ -139,7 +86,14 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
139
86
  />
140
87
  )}
141
88
  {children}
142
- <button ref={saveButtonRef} onClick={() => triggerSave().then()} style={{ display: "none" }} />
89
+ <button
90
+ ref={saveButtonRef}
91
+ data-reason={""}
92
+ onClick={(e) => {
93
+ triggerSave(e.currentTarget.getAttribute("data-reason") ?? undefined).then();
94
+ }}
95
+ style={{ display: "none" }}
96
+ />
143
97
  </ControlledMenu>
144
98
  )}
145
99
  </>
@@ -151,8 +105,5 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(props: GridPopov
151
105
  return {
152
106
  popoverWrapper,
153
107
  triggerSave,
154
- onlyInputKeyboardEventHandlers,
155
- firstInputKeyboardEventHandlers,
156
- lastInputKeyboardEventHandlers,
157
108
  };
158
109
  };
@@ -1,10 +1,9 @@
1
1
  import "../../styles/GridFormDropDown.scss";
2
2
 
3
3
  import { FocusableItem, MenuDivider, MenuHeader, MenuItem } from "../../react-menu3";
4
- import { KeyboardEvent, useCallback, useContext, useEffect, useRef, useState } from "react";
4
+ import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
7
- import { GridContext } from "../../contexts/GridContext";
8
7
  import { delay } from "lodash-es";
9
8
  import debounce from "debounce-promise";
10
9
  import { CellEditorCommon } from "../GridCell";
@@ -12,6 +11,7 @@ import { useGridPopoverHook } from "../GridPopoverHook";
12
11
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
13
12
  import { GridSubComponentContext } from "contexts/GridSubComponentContext";
14
13
  import { ClickEvent, MenuInstance } from "../../react-menu3/types";
14
+ import { CloseReason } from "../../react-menu3/utils";
15
15
 
16
16
  export interface GridPopoutEditDropDownSelectedItem<RowType, ValueType> {
17
17
  // Note the row that was clicked on will be first
@@ -65,31 +65,35 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
65
65
  props: GridFormPopoutDropDownProps<RowType, ValueType>,
66
66
  ) => {
67
67
  const { selectedRows, field, updateValue, data } = useGridPopoverContext<RowType>();
68
- const { stopEditing } = useContext(GridContext);
69
68
 
69
+ // Save triggers during async action processing which triggers another selectItem(), this ref blocks that
70
+ const hasSubmitted = useRef(false);
70
71
  const [filter, setFilter] = useState("");
71
72
  const [filteredValues, setFilteredValues] = useState<any[]>([]);
72
73
  const optionsInitialising = useRef(false);
73
74
  const [options, setOptions] = useState<FinalSelectOption<ValueType>[] | null>(null);
74
75
  const subComponentIsValid = useRef(false);
75
76
  const [subSelectedValue, setSubSelectedValue] = useState<any>();
76
- const [selectedSubComponent, setSelectedSubComponent] = useState<any>();
77
+ const [selectedSubComponent, setSelectedSubComponent] = useState<FinalSelectOption<any> | null>(null);
77
78
 
78
79
  const selectItemHandler = useCallback(
79
- async (value: ValueType, subComponentValue?: ValueType): Promise<boolean> => {
80
- if (subComponentValue !== undefined && !subComponentIsValid.current) return false;
81
-
82
- return updateValue(async (selectedRows) => {
83
- const hasChanged = selectedRows.some((row) => row[field as keyof RowType] !== value);
84
- if (hasChanged) {
85
- if (props.onSelectedItem) {
86
- await props.onSelectedItem({ selectedRows, value, subComponentValue });
87
- } else {
88
- selectedRows.forEach((row) => (row[field as keyof RowType] = value));
80
+ async (value: ValueType, subComponentValue?: ValueType, reason?: string): Promise<boolean> => {
81
+ if (hasSubmitted.current || (subComponentValue !== undefined && !subComponentIsValid.current)) return false;
82
+ hasSubmitted.current = true;
83
+ return updateValue(
84
+ async (selectedRows) => {
85
+ const hasChanged = selectedRows.some((row) => row[field as keyof RowType] !== value);
86
+ if (hasChanged) {
87
+ if (props.onSelectedItem) {
88
+ await props.onSelectedItem({ selectedRows, value, subComponentValue });
89
+ } else {
90
+ selectedRows.forEach((row) => (row[field as keyof RowType] = value));
91
+ }
89
92
  }
90
- }
91
- return true;
92
- });
93
+ return true;
94
+ },
95
+ reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0,
96
+ );
93
97
  },
94
98
  [field, props, updateValue],
95
99
  );
@@ -99,7 +103,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
99
103
  updateValue(async (selectedRows) => {
100
104
  props.onSelectFilter && (await props.onSelectFilter({ selectedRows, value }));
101
105
  return true;
102
- }),
106
+ }, 0),
103
107
  [props, updateValue],
104
108
  );
105
109
 
@@ -173,35 +177,37 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
173
177
  }
174
178
  }, [filter, props, researchOnFilterChange]);
175
179
 
176
- const onFilterKeyDown = useCallback(
177
- async (e: KeyboardEvent) => {
178
- if (!options) return;
179
- if (e.key == "Enter" || e.key == "Tab") {
180
- const activeOptions = options.filter((option) => !filteredValues.includes(option.value));
181
- if (activeOptions.length == 1) {
182
- await selectItemHandler(activeOptions[0].value);
183
- stopEditing();
184
- } else if (props.onSelectFilter) {
185
- await selectFilterHandler(filter);
186
- stopEditing();
187
- } else {
188
- e.preventDefault();
189
- e.stopPropagation();
190
- }
191
- }
192
- },
193
- [filteredValues, options, selectItemHandler, selectFilterHandler, stopEditing, filter, props],
194
- );
195
-
196
180
  const save = useCallback(async () => {
197
- // Handler for sub-selected value
198
- if (!selectedSubComponent) return true;
199
- if (!subComponentIsValid.current) return false;
200
- await selectItemHandler(selectedSubComponent.value as ValueType, subSelectedValue);
181
+ if (!options) return true;
182
+ const activeOptions = options.filter((option) => !filteredValues.includes(option.value));
183
+ if (activeOptions.length === 1) {
184
+ await selectItemHandler(activeOptions[0].value);
185
+ } else if (activeOptions.length === 0 && props.onSelectFilter) {
186
+ await selectFilterHandler(filter);
187
+ } else {
188
+ // Handler for sub-selected value
189
+ if (!selectedSubComponent) return true;
190
+ if (selectedSubComponent.subComponent && !subComponentIsValid.current) return false;
191
+ await selectItemHandler(selectedSubComponent.value as ValueType, subSelectedValue);
192
+ }
201
193
  return true;
202
- }, [selectItemHandler, selectedSubComponent, subSelectedValue]);
194
+ }, [
195
+ filter,
196
+ filteredValues,
197
+ options,
198
+ props.onSelectFilter,
199
+ selectFilterHandler,
200
+ selectItemHandler,
201
+ selectedSubComponent,
202
+ subSelectedValue,
203
+ ]);
204
+
205
+ const { popoverWrapper } = useGridPopoverHook({
206
+ className: props.className,
207
+ invalid: () => !!(selectedSubComponent && !subComponentIsValid.current),
208
+ save,
209
+ });
203
210
 
204
- const { popoverWrapper } = useGridPopoverHook({ className: props.className, save });
205
211
  return popoverWrapper(
206
212
  <>
207
213
  {props.filtered && (
@@ -219,7 +225,6 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
219
225
  data-testid={"filteredMenu-free-text-input"}
220
226
  defaultValue={filter}
221
227
  onChange={(e) => setFilter(e.target.value.toLowerCase())}
222
- onKeyDown={(e) => onFilterKeyDown(e)}
223
228
  />
224
229
  </div>
225
230
  )}
@@ -247,18 +252,29 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(
247
252
  onClick={(e: ClickEvent) => {
248
253
  if (item.subComponent) {
249
254
  if (selectedSubComponent === item) {
255
+ // toggle selection off
250
256
  setSelectedSubComponent(null);
251
257
  subComponentIsValid.current = true;
252
258
  } else {
259
+ // toggle selection on
253
260
  setSelectedSubComponent(item);
254
261
  }
255
262
  e.keepOpen = true;
256
263
  } else {
257
- selectItemHandler(item.value as ValueType).then();
264
+ selectItemHandler(
265
+ item.value as ValueType,
266
+ undefined,
267
+ e.key === "Tab"
268
+ ? e.shiftKey
269
+ ? CloseReason.TAB_BACKWARD
270
+ : CloseReason.TAB_FORWARD
271
+ : CloseReason.CLICK,
272
+ ).then();
258
273
  }
259
274
  }}
260
275
  >
261
276
  {item.label ?? (item.value == null ? `<${item.value}>` : `${item.value}`)}
277
+ {item.subComponent ? "..." : ""}
262
278
  </MenuItem>
263
279
 
264
280
  {item.subComponent && selectedSubComponent === item && (
@@ -1,6 +1,6 @@
1
1
  import "../../styles/GridFormEditBearing.scss";
2
2
 
3
- import { useCallback, useState } from "react";
3
+ import { useCallback, useMemo, 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";
@@ -17,14 +17,21 @@ export interface GridFormEditBearingProps<RowType extends GridBaseRow> extends C
17
17
  export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridFormEditBearingProps<RowType>) => {
18
18
  const { field, value: initialValue } = useGridPopoverContext<RowType>();
19
19
 
20
- const [value, setValue] = useState<string>(`${initialValue ?? ""}`);
20
+ // This clears out any scientific precision
21
+ const defaultValue = useMemo(
22
+ () => (initialValue == null ? "" : parseFloat(parseFloat(initialValue).toFixed(10)).toString()),
23
+ [initialValue],
24
+ );
25
+
26
+ const [value, setValue] = useState<string>(defaultValue);
27
+
28
+ const invalid = useCallback(() => bearingStringValidator(value, props.range), [props.range, value]);
21
29
 
22
30
  const save = useCallback(
23
31
  async (selectedRows: RowType[]): Promise<boolean> => {
24
- if (bearingStringValidator(value)) return false;
25
32
  const parsedValue = bearingNumberParser(value);
26
33
  // Value didn't change so don't save just cancel
27
- if (parsedValue === initialValue) {
34
+ if (parsedValue === bearingNumberParser(defaultValue)) {
28
35
  return true;
29
36
  }
30
37
 
@@ -41,25 +48,26 @@ export const GridFormEditBearing = <RowType extends GridBaseRow>(props: GridForm
41
48
  }
42
49
  return true;
43
50
  },
44
- [field, initialValue, props, value],
51
+ [defaultValue, field, props, value],
45
52
  );
46
- const { popoverWrapper, onlyInputKeyboardEventHandlers } = useGridPopoverHook({
53
+ const { popoverWrapper } = useGridPopoverHook({
47
54
  className: props.className,
55
+ invalid,
48
56
  save,
49
57
  });
50
58
 
51
59
  return popoverWrapper(
52
60
  <div className={"GridFormEditBearing-input Grid-popoverContainer"}>
53
61
  <TextInputFormatted
54
- value={value ?? ""}
62
+ value={defaultValue}
55
63
  onChange={(e) => {
56
64
  setValue(e.target.value.trim());
57
65
  }}
58
66
  autoFocus={true}
59
67
  placeholder={props.placeHolder}
60
- {...onlyInputKeyboardEventHandlers}
61
68
  formatted={bearingStringValidator(value, props.range) ? "?" : convertDDToDMS(bearingNumberParser(value))}
62
69
  error={bearingStringValidator(value, props.range)}
70
+ helpText={"Press enter or tab to save"}
63
71
  />
64
72
  </div>,
65
73
  );
@@ -61,6 +61,11 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
61
61
  const optionsInitialising = useRef(false);
62
62
  const [options, setOptions] = useState<MultiFinalSelectOption<ValueType>[]>();
63
63
 
64
+ const invalid = useCallback(() => {
65
+ const validations = pick(subComponentIsValid.current, Object.keys(selectedValues));
66
+ return Object.values(validations).some((v) => !v);
67
+ }, [selectedValues]);
68
+
64
69
  const save = useCallback(
65
70
  async (selectedRows: RowType[]): Promise<boolean> => {
66
71
  if (props.onSave) {
@@ -149,8 +154,9 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
149
154
  [selectedValues],
150
155
  );
151
156
 
152
- const { popoverWrapper, lastInputKeyboardEventHandlers, triggerSave } = useGridPopoverHook({
157
+ const { popoverWrapper, triggerSave } = useGridPopoverHook({
153
158
  className: props.className,
159
+ invalid,
154
160
  save,
155
161
  });
156
162
  return popoverWrapper(
@@ -186,10 +192,12 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(
186
192
  <div key={`${index}`}>
187
193
  <MenuItem
188
194
  onClick={(e: ClickEvent) => {
189
- e.keepOpen = true;
190
- toggleValue(item);
195
+ // Global react-menu MenuItem handler handles tabs
196
+ if (e.key !== "Tab") {
197
+ e.keepOpen = true;
198
+ toggleValue(item);
199
+ }
191
200
  }}
192
- {...lastInputKeyboardEventHandlers}
193
201
  >
194
202
  <LuiCheckboxInput
195
203
  isChecked={`${item.value}` in selectedValues}
@@ -7,6 +7,7 @@ import { CellEditorCommon } from "../GridCell";
7
7
  import { GridSubComponentContext } from "../../contexts/GridSubComponentContext";
8
8
  import { ClickEvent } from "../../react-menu3/types";
9
9
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
10
+ import { CloseReason } from "../../react-menu3/utils";
10
11
 
11
12
  export interface GridFormPopoutMenuProps<RowType extends GridBaseRow> extends CellEditorCommon {
12
13
  options: (selectedRows: RowType[]) => Promise<MenuOption<RowType>[]>;
@@ -28,7 +29,6 @@ export interface MenuOption<RowType extends GridBaseRow> {
28
29
  label: JSX.Element | string | MenuSeparatorType;
29
30
  action?: (selectedRows: RowType[], menuOption: SelectedMenuOptionResult<RowType>) => Promise<void>;
30
31
  disabled?: string | boolean;
31
- supportsMultiEdit: boolean;
32
32
  hidden?: boolean;
33
33
  subComponent?: (props: any) => JSX.Element;
34
34
  }
@@ -79,54 +79,57 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
79
79
  }, [options, props.defaultAction, props.options, selectedRows]);
80
80
 
81
81
  const actionClick = useCallback(
82
- async (menuOption: MenuOption<RowType>) => {
82
+ async (menuOption: MenuOption<RowType>, reason: string) => {
83
83
  actionProcessing.current = true;
84
- return updateValue(async () => {
85
- const result = { ...menuOption, subValue: subSelectedValue };
86
- await (menuOption.action ?? defaultAction)(selectedRows, result);
87
- actionProcessing.current = false;
88
- return true;
89
- });
84
+ return updateValue(
85
+ async () => {
86
+ const result = { ...menuOption, subValue: subSelectedValue };
87
+ await (menuOption.action ?? defaultAction)(selectedRows, result);
88
+ actionProcessing.current = false;
89
+ return true;
90
+ },
91
+ reason === CloseReason.TAB_FORWARD ? 1 : reason === CloseReason.TAB_BACKWARD ? -1 : 0,
92
+ );
90
93
  },
91
94
  [defaultAction, selectedRows, subSelectedValue, updateValue],
92
95
  );
93
96
 
94
97
  const onMenuItemClick = useCallback(
95
- (e: ClickEvent, item: MenuOption<RowType>) => {
98
+ async (e: ClickEvent, item: MenuOption<RowType>) => {
96
99
  if (item.subComponent) {
97
100
  subComponentIsValid.current = false;
98
101
  setSubSelectedValue(null);
99
102
  setSubComponentSelected(subComponentSelected === item ? null : item);
100
103
  e.keepOpen = true;
101
104
  } else {
102
- actionClick(item).then();
103
- e.keepOpen = true;
105
+ await actionClick(
106
+ item,
107
+ e.key === "Tab" ? (e.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD) : CloseReason.CLICK,
108
+ ).then();
104
109
  }
105
110
  },
106
111
  [actionClick, subComponentSelected],
107
112
  );
108
113
 
109
- const selectedRowCount = selectedRows.length;
110
-
111
- const filteredOptions = options?.filter((menuOption) => {
112
- return menuOption.label === PopoutMenuSeparator || selectedRowCount === 1 || menuOption.supportsMultiEdit;
113
- });
114
-
115
114
  const save = useCallback(async () => {
116
115
  // if a subcomponent is open we assume that it's meant to be saved.
117
116
  if (!actionProcessing.current && subComponentSelected) {
118
117
  if (!subComponentIsValid.current) return false;
119
- await actionClick(subComponentSelected);
120
- return false;
118
+ await actionClick(subComponentSelected, "click");
119
+ return true;
121
120
  }
122
121
  // Otherwise assume it's a cancel, either way we close the menu
123
122
  return true;
124
123
  }, [actionClick, subComponentSelected]);
125
124
 
126
- const { popoverWrapper, triggerSave } = useGridPopoverHook({ className: props.className, save });
125
+ const { popoverWrapper, triggerSave } = useGridPopoverHook({
126
+ className: props.className,
127
+ invalid: () => subComponentSelected && !subComponentIsValid.current,
128
+ save,
129
+ });
127
130
 
128
131
  return popoverWrapper(
129
- <ComponentLoadingWrapper loading={!filteredOptions} className={"GridFormPopupMenu"}>
132
+ <ComponentLoadingWrapper loading={!options} className={"GridFormPopupMenu"}>
130
133
  <>
131
134
  {options?.map((item, index) =>
132
135
  item.label === PopoutMenuSeparator ? (
@@ -137,7 +140,7 @@ export const GridFormPopoverMenu = <RowType extends GridBaseRow>(props: GridForm
137
140
  <MenuItem
138
141
  key={`${item.label}`}
139
142
  onClick={(e: ClickEvent) => onMenuItemClick(e, item)}
140
- disabled={!!item.disabled || !filteredOptions?.includes(item)}
143
+ disabled={!!item.disabled}
141
144
  title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
142
145
  >
143
146
  {item.label as JSX.Element | string}
@@ -19,7 +19,7 @@ export interface GridSubComponentTextAreaProps<RowType extends GridBaseRow>
19
19
  export const GridFormSubComponentTextArea = <RowType extends GridBaseRow>(
20
20
  props: GridSubComponentTextAreaProps<RowType>,
21
21
  ): JSX.Element => {
22
- const { value, data, setValue, setValid, triggerSave } = useContext(GridSubComponentContext);
22
+ const { value, data, setValue, setValid } = useContext(GridSubComponentContext);
23
23
 
24
24
  const helpText = props.helpText ?? "Press tab to save";
25
25
 
@@ -44,19 +44,6 @@ export const GridFormSubComponentTextArea = <RowType extends GridBaseRow>(
44
44
  helpText={helpText}
45
45
  autoFocus={true}
46
46
  placeholder={props.placeholder}
47
- onKeyDown={(e) => {
48
- if (e.key === "Tab") {
49
- e.preventDefault();
50
- e.stopPropagation();
51
- }
52
- }}
53
- onKeyUp={(e) => {
54
- if (e.key === "Tab") {
55
- e.preventDefault();
56
- e.stopPropagation();
57
- !e.shiftKey && triggerSave().then();
58
- }
59
- }}
60
47
  />
61
48
  </div>
62
49
  );
@@ -17,7 +17,7 @@ export interface GridFormSubComponentTextInputProps<RowType extends GridBaseRow>
17
17
  export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
18
18
  props: GridFormSubComponentTextInputProps<RowType>,
19
19
  ): JSX.Element => {
20
- const { value, setValue, setValid, triggerSave, data } = useContext(GridSubComponentContext);
20
+ const { value, setValue, setValid, data } = useContext(GridSubComponentContext);
21
21
 
22
22
  const helpText = props.helpText ?? "Press enter or tab to save";
23
23
 
@@ -39,23 +39,6 @@ export const GridFormSubComponentTextInput = <RowType extends GridBaseRow>(
39
39
  onChange={(e) => setValue(e.target.value)}
40
40
  helpText={helpText}
41
41
  autoFocus={true}
42
- onKeyDown={(e) => {
43
- if (e.key === "Tab" || e.key === "Enter") {
44
- e.preventDefault();
45
- e.stopPropagation();
46
- }
47
- }}
48
- onKeyUp={(e) => {
49
- if (e.key === "Tab") {
50
- e.preventDefault();
51
- e.stopPropagation();
52
- !e.shiftKey && triggerSave().then();
53
- } else if (e.key === "Enter") {
54
- triggerSave().then();
55
- e.preventDefault();
56
- e.stopPropagation();
57
- }
58
- }}
59
42
  placeholder={props.placeholder}
60
43
  style={{
61
44
  width: "100%",
@@ -25,8 +25,6 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTex
25
25
 
26
26
  const save = useCallback(
27
27
  async (selectedRows: RowType[]): Promise<boolean> => {
28
- if (invalid()) return false;
29
-
30
28
  if (initialVale === (value ?? "")) return true;
31
29
 
32
30
  if (props.onSave) {
@@ -42,9 +40,15 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTex
42
40
  });
43
41
  return true;
44
42
  },
45
- [invalid, initialVale, value, props, field],
43
+ [initialVale, value, props, field],
46
44
  );
47
- const { popoverWrapper, onlyInputKeyboardEventHandlers } = useGridPopoverHook({ className: props.className, save });
45
+
46
+ const { popoverWrapper } = useGridPopoverHook({
47
+ className: props.className,
48
+ invalid,
49
+ save,
50
+ });
51
+
48
52
  return popoverWrapper(
49
53
  <div style={{ display: "flex", flexDirection: "row", width: props.width ?? 240 }}>
50
54
  <TextAreaInput
@@ -53,7 +57,6 @@ export const GridFormTextArea = <RowType extends GridBaseRow>(props: GridFormTex
53
57
  error={invalid()}
54
58
  placeholder={props.placeholder}
55
59
  helpText={helpText}
56
- {...onlyInputKeyboardEventHandlers}
57
60
  />
58
61
  </div>,
59
62
  );