@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
@@ -1,4 +1,4 @@
1
- import { useCallback, useState } from "react";
1
+ import { useCallback, useMemo, useState } from "react";
2
2
  import { TextInputFormatted } from "../../lui/TextInputFormatted";
3
3
  import { useGridPopoverHook } from "../GridPopoverHook";
4
4
  import { GridBaseRow } from "../Grid";
@@ -21,7 +21,7 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTe
21
21
 
22
22
  const helpText = props.helpText ?? "Press enter or tab to save";
23
23
 
24
- const initValue = initialVale == null ? "" : `${initialVale}`;
24
+ const initValue = useMemo(() => (initialVale == null ? "" : `${initialVale}`), [initialVale]);
25
25
  const [value, setValue] = useState(initValue);
26
26
 
27
27
  const invalid = useCallback(() => TextInputValidator<RowType>(props, value, data), [data, props, value]);
@@ -29,6 +29,7 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTe
29
29
  const save = useCallback(
30
30
  async (selectedRows: RowType[]): Promise<boolean> => {
31
31
  if (invalid()) return false;
32
+
32
33
  const trimmedValue = value.trim();
33
34
  if (initValue === trimmedValue) return true;
34
35
 
@@ -47,7 +48,11 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTe
47
48
  },
48
49
  [invalid, value, initValue, props, field],
49
50
  );
50
- const { popoverWrapper, onlyInputKeyboardEventHandlers } = useGridPopoverHook({ className: props.className, save });
51
+ const { popoverWrapper } = useGridPopoverHook({
52
+ className: props.className,
53
+ invalid,
54
+ save,
55
+ });
51
56
 
52
57
  return popoverWrapper(
53
58
  <div style={{ display: "flex", flexDirection: "row", width: props.width ?? 240 }} className={"FormTest"}>
@@ -58,7 +63,6 @@ export const GridFormTextInput = <RowType extends GridBaseRow>(props: GridFormTe
58
63
  formatted={props.units}
59
64
  style={{ width: "100%" }}
60
65
  placeholder={props.placeholder}
61
- {...onlyInputKeyboardEventHandlers}
62
66
  helpText={helpText}
63
67
  />
64
68
  </div>,
@@ -25,6 +25,7 @@ export interface GridContextType {
25
25
  setSaving?: (saving: boolean) => void,
26
26
  ) => Promise<boolean>;
27
27
  redrawRows: (rowNodes?: RowNode[]) => void;
28
+ selectNextCell: (tabDirection: -1 | 0 | 1) => void;
28
29
  }
29
30
 
30
31
  export const GridContext = createContext<GridContextType>({
@@ -87,4 +88,7 @@ export const GridContext = createContext<GridContextType>({
87
88
  redrawRows: () => {
88
89
  console.error("no context provider for redrawRows");
89
90
  },
91
+ selectNextCell: () => {
92
+ console.error("no context provider for selectNextCell");
93
+ },
90
94
  });
@@ -275,6 +275,13 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
275
275
  });
276
276
  };
277
277
 
278
+ const selectNextCell = (tabDirection: -1 | 0 | 1 = 0) => {
279
+ gridApiOp((gridApi) => {
280
+ if (tabDirection == 1) gridApi.tabToNextCell();
281
+ if (tabDirection == -1) gridApi.tabToPreviousCell();
282
+ });
283
+ };
284
+
278
285
  return (
279
286
  <GridContext.Provider
280
287
  value={{
@@ -296,6 +303,7 @@ export const GridContextProvider = (props: GridContextProps): ReactElement => {
296
303
  stopEditing,
297
304
  updatingCells,
298
305
  redrawRows,
306
+ selectNextCell,
299
307
  }}
300
308
  >
301
309
  {props.children}
@@ -17,7 +17,7 @@ export interface GridPopoverContextType<RowType extends GridBaseRow> {
17
17
  value: any;
18
18
  data: RowType;
19
19
  selectedRows: RowType[];
20
- updateValue: (saveFn: (selectedRows: any[]) => Promise<boolean>) => Promise<boolean>;
20
+ updateValue: (saveFn: (selectedRows: any[]) => Promise<boolean>, tabDirection: 1 | 0 | -1) => Promise<boolean>;
21
21
  }
22
22
 
23
23
  export const GridPopoverContext = createContext<GridPopoverContextType<any>>({
@@ -11,7 +11,7 @@ interface GridPopoverContextProps {
11
11
  }
12
12
 
13
13
  export const GridPopoverContextProvider = ({ props, children }: GridPopoverContextProps) => {
14
- const { getSelectedRows, updatingCells, stopEditing } = useContext(GridContext);
14
+ const { getSelectedRows, updatingCells, stopEditing, selectNextCell } = useContext(GridContext);
15
15
  const anchorRef = useRef<Element>(props.eGridCell);
16
16
 
17
17
  const [saving, setSaving] = useState(false);
@@ -27,16 +27,19 @@ export const GridPopoverContextProvider = ({ props, children }: GridPopoverConte
27
27
  const field = props.colDef?.field ?? "";
28
28
 
29
29
  const updateValue = useCallback(
30
- async (saveFn: (selectedRows: any[]) => Promise<boolean>): Promise<boolean> => {
30
+ async (saveFn: (selectedRows: any[]) => Promise<boolean>, tabDirection: 1 | 0 | -1): Promise<boolean> => {
31
31
  let result = false;
32
32
  if (!saving) {
33
33
  result = await updatingCells({ selectedRows, field }, saveFn, setSaving);
34
- if (result) stopEditing();
34
+ if (result) {
35
+ stopEditing();
36
+ tabDirection && selectNextCell(tabDirection);
37
+ }
35
38
  }
36
39
 
37
40
  return result;
38
41
  },
39
- [field, saving, selectedRows, stopEditing, updatingCells],
42
+ [field, saving, selectNextCell, selectedRows, stopEditing, updatingCells],
40
43
  );
41
44
 
42
45
  return (
@@ -2,7 +2,12 @@ import { createContext } from "react";
2
2
 
3
3
  export type GridUpdatingContextType = {
4
4
  checkUpdating: (fields: string | string[], id: number | string) => boolean;
5
- modifyUpdating: (field: string, ids: (number | string)[], fn: () => void | Promise<void>) => Promise<void>;
5
+ modifyUpdating: (
6
+ fields: string | string[],
7
+ ids: (number | string)[],
8
+ fn: () => void | Promise<void>,
9
+ ) => Promise<void>;
10
+ updatedDep: number;
6
11
  };
7
12
 
8
13
  export const GridUpdatingContext = createContext<GridUpdatingContextType>({
@@ -13,4 +18,5 @@ export const GridUpdatingContext = createContext<GridUpdatingContextType>({
13
18
  modifyUpdating: async () => {
14
19
  console.error("Missing GridUpdatingContext");
15
20
  },
21
+ updatedDep: 0,
16
22
  });
@@ -1,4 +1,4 @@
1
- import { ReactNode, useRef } from "react";
1
+ import { ReactNode, useRef, useState } from "react";
2
2
  import { castArray, flatten, remove } from "lodash-es";
3
3
  import { GridUpdatingContext } from "./GridUpdatingContext";
4
4
 
@@ -15,6 +15,7 @@ type UpdatingBlock = Record<FieldName, IdList[]>;
15
15
  export const GridUpdatingContextProvider = (props: UpdatingContextProviderProps) => {
16
16
  const updatingBlocks = useRef<UpdatingBlock>({});
17
17
  const updating = useRef<GridUpdatingContextStatus>({});
18
+ const [updatedDep, setUpdatedDep] = useState(0);
18
19
 
19
20
  const resetUpdating = () => {
20
21
  const mergedUpdatingBlocks: GridUpdatingContextStatus = {};
@@ -22,15 +23,25 @@ export const GridUpdatingContextProvider = (props: UpdatingContextProviderProps)
22
23
  mergedUpdatingBlocks[key] = flatten(updatingBlocks.current[key]);
23
24
  }
24
25
  updating.current = mergedUpdatingBlocks;
26
+ setUpdatedDep((updatedDep) => updatedDep + 1);
25
27
  };
26
28
 
27
- const modifyUpdating = async (field: string, ids: (number | string)[], fn: () => void | Promise<void>) => {
28
- const fieldUpdatingIds = updatingBlocks.current[field] ?? (updatingBlocks.current[field] = []);
29
+ const modifyUpdating = async (
30
+ fields: string | string[],
31
+ ids: (number | string)[],
32
+ fn: () => void | Promise<void>,
33
+ ) => {
29
34
  const idRef = [...ids];
30
- fieldUpdatingIds.push(idRef);
35
+ castArray(fields).forEach((field) => {
36
+ const fieldUpdatingIds = updatingBlocks.current[field] ?? (updatingBlocks.current[field] = []);
37
+ fieldUpdatingIds.push(idRef);
38
+ });
31
39
  resetUpdating();
32
40
  await fn();
33
- remove(fieldUpdatingIds, (idList) => idList === idRef);
41
+ castArray(fields).forEach((field) => {
42
+ const fieldUpdatingIds = updatingBlocks.current[field];
43
+ remove(fieldUpdatingIds, (idList) => idList === idRef);
44
+ });
34
45
  resetUpdating();
35
46
  };
36
47
 
@@ -38,7 +49,7 @@ export const GridUpdatingContextProvider = (props: UpdatingContextProviderProps)
38
49
  castArray(fields).some((f) => updating.current[f]?.includes(id));
39
50
 
40
51
  return (
41
- <GridUpdatingContext.Provider value={{ modifyUpdating, checkUpdating }}>
52
+ <GridUpdatingContext.Provider value={{ modifyUpdating, checkUpdating, updatedDep }}>
42
53
  {props.children}
43
54
  </GridUpdatingContext.Provider>
44
55
  );
@@ -0,0 +1,29 @@
1
+ import { LuiIcon } from "@linzjs/lui";
2
+
3
+ export interface FormErrorProps {
4
+ helpText?: string;
5
+ error?: string | boolean | null;
6
+ }
7
+
8
+ export const FormError = (props: FormErrorProps) => {
9
+ return (
10
+ <>
11
+ {props.error && (
12
+ <span className="LuiTextInput-error">
13
+ <LuiIcon alt="error" name="ic_error" className="LuiTextInput-error-icon" size="sm" status="error" />
14
+ {props.error}
15
+ </span>
16
+ )}
17
+
18
+ {props.helpText && !props.error && (
19
+ <span
20
+ style={{
21
+ fontSize: "0.7rem",
22
+ }}
23
+ >
24
+ {props.helpText}
25
+ </span>
26
+ )}
27
+ </>
28
+ );
29
+ };
@@ -1,8 +1,8 @@
1
1
  import { InputHTMLAttributes, useState } from "react";
2
2
  import clsx from "clsx";
3
- import { LuiIcon } from "@linzjs/lui";
4
3
  import { v4 as uuidVersion4 } from "uuid";
5
4
  import { omit } from "lodash-es";
5
+ import { FormError } from "./FormError";
6
6
 
7
7
  export const useGenerateOrDefaultId = (idFromProps?: string) => {
8
8
  const [id] = useState(idFromProps ? idFromProps : uuidVersion4());
@@ -56,23 +56,7 @@ export const TextAreaInput = (props: LuiTextAreaInputProps) => {
56
56
  </div>
57
57
  </label>
58
58
 
59
- {/* Error message */}
60
- {props.error && (
61
- <span className="LuiTextAreaInput-error">
62
- <LuiIcon alt="error" name="ic_error" className="LuiTextAreaInput-error-icon" size="sm" status="error" />
63
- {props.error}
64
- </span>
65
- )}
66
-
67
- {props.helpText && !props.error && (
68
- <span
69
- style={{
70
- fontSize: "0.7rem",
71
- }}
72
- >
73
- {props.helpText}
74
- </span>
75
- )}
59
+ <FormError error={props.error} helpText={props.helpText} />
76
60
  </div>
77
61
  );
78
62
  };
@@ -3,8 +3,8 @@ import "./TextInputFormatted.scss";
3
3
  import { DetailedHTMLProps, InputHTMLAttributes } from "react";
4
4
 
5
5
  import clsx from "clsx";
6
- import { LuiIcon } from "@linzjs/lui";
7
6
  import { omit } from "lodash-es";
7
+ import { FormError } from "./FormError";
8
8
 
9
9
  export interface LuiTextInputProps extends DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
10
10
  // overrides value in base class to be string type only
@@ -35,22 +35,7 @@ export const TextInputFormatted = (props: LuiTextInputProps): JSX.Element => {
35
35
  <span className={"LuiTextInput-formatted"}>{props.formatted}</span>
36
36
  </span>
37
37
 
38
- {props.error && (
39
- <span className="LuiTextInput-error">
40
- <LuiIcon alt="error" name="ic_error" className="LuiTextInput-error-icon" size="sm" status="error" />
41
- {props.error}
42
- </span>
43
- )}
44
-
45
- {props.helpText && !props.error && (
46
- <span
47
- style={{
48
- fontSize: "0.7rem",
49
- }}
50
- >
51
- {props.helpText}
52
- </span>
53
- )}
38
+ <FormError error={props.error} helpText={props.helpText} />
54
39
  </div>
55
40
  );
56
41
  };
@@ -1,14 +1,4 @@
1
- import {
2
- forwardRef,
3
- useRef,
4
- useMemo,
5
- useCallback,
6
- useEffect,
7
- KeyboardEvent,
8
- FocusEvent,
9
- MutableRefObject,
10
- ForwardedRef,
11
- } from "react";
1
+ import { forwardRef, useRef, useMemo, useCallback, useEffect, FocusEvent, MutableRefObject, ForwardedRef } from "react";
12
2
  import { createPortal } from "react-dom";
13
3
  import { MenuList } from "./MenuList";
14
4
  import { useBEM } from "../hooks";
@@ -120,14 +110,119 @@ export const ControlledMenuFr = (
120
110
  [isWithinMenu],
121
111
  );
122
112
 
113
+ const lastTabDownEl = useRef<Element>();
114
+ const lastEnterDownEl = useRef<Element>();
115
+ const handleKeyboardTabAndEnter = useCallback(
116
+ (isDown: boolean) => (ev: KeyboardEvent) => {
117
+ const thisDocument = anchorRef?.current ? anchorRef?.current.ownerDocument : document;
118
+ const activeElement = thisDocument.activeElement;
119
+ if (!anchorRef?.current || !activeElement) return;
120
+ if (ev.key !== "Tab" && ev.key !== "Enter") return;
121
+
122
+ if (ev.repeat) {
123
+ ev.preventDefault();
124
+ ev.stopPropagation();
125
+ return;
126
+ }
127
+
128
+ const inputElsIterator = thisDocument.querySelectorAll<HTMLElement>(".szh-menu--state-open input,textarea");
129
+ let inputEls: HTMLElement[] = [];
130
+ inputElsIterator.forEach((el) => inputEls.push(el));
131
+ inputEls = inputEls.filter((el) => !(el as any).disabled);
132
+
133
+ if (inputEls.length === 0) return;
134
+ const firstInputEl = inputEls[0];
135
+ const lastInputEl = inputEls[inputEls.length - 1];
136
+ if (activeElement !== firstInputEl && activeElement !== lastInputEl) return;
137
+
138
+ const suppressAutoSaveEvents = activeElement.getAttribute("data-suppressAutoSaveEvents");
139
+ const invokeSave = (reason: string) => {
140
+ if (!saveButtonRef?.current || suppressAutoSaveEvents) return;
141
+ saveButtonRef.current?.setAttribute("data-reason", reason);
142
+ saveButtonRef?.current?.click();
143
+ };
144
+
145
+ const isTextArea = activeElement.nodeName === "TEXTAREA";
146
+ switch (activeElement.nodeName) {
147
+ case "TEXTAREA":
148
+ case "INPUT": {
149
+ if (activeElement === lastInputEl && activeElement === firstInputEl) {
150
+ if (ev.key === "Tab") {
151
+ // Can't forward/backwards tab out of popup
152
+ ev.preventDefault();
153
+ ev.stopPropagation();
154
+ if (isDown) {
155
+ lastTabDownEl.current = activeElement;
156
+ } else {
157
+ lastTabDownEl.current == activeElement &&
158
+ invokeSave(ev.shiftKey ? CloseReason.TAB_BACKWARD : CloseReason.TAB_FORWARD);
159
+ }
160
+ }
161
+ if (ev.key === "Enter" && !isTextArea) {
162
+ ev.preventDefault();
163
+ ev.stopPropagation();
164
+ if (isDown) {
165
+ lastEnterDownEl.current = activeElement;
166
+ } else {
167
+ lastEnterDownEl.current == activeElement && invokeSave(CloseReason.CLICK);
168
+ }
169
+ }
170
+ } else if (activeElement === lastInputEl) {
171
+ if (ev.key === "Tab" && !ev.shiftKey) {
172
+ // Can't backward tab out of popup
173
+ ev.preventDefault();
174
+ ev.stopPropagation();
175
+
176
+ if (isDown) {
177
+ lastTabDownEl.current = activeElement;
178
+ } else {
179
+ lastTabDownEl.current == activeElement && invokeSave(CloseReason.TAB_FORWARD);
180
+ }
181
+ }
182
+ if (ev.key === "Enter" && !isTextArea) {
183
+ ev.preventDefault();
184
+ ev.stopPropagation();
185
+ if (isDown) {
186
+ lastEnterDownEl.current = activeElement;
187
+ } else {
188
+ lastEnterDownEl.current == activeElement && invokeSave(CloseReason.CLICK);
189
+ }
190
+ }
191
+ } else if (activeElement === firstInputEl) {
192
+ if (ev.key === "Tab" && ev.shiftKey) {
193
+ // Can't backward tab out of popup
194
+ ev.preventDefault();
195
+ ev.stopPropagation();
196
+
197
+ if (isDown) {
198
+ lastTabDownEl.current = activeElement;
199
+ } else {
200
+ lastTabDownEl.current == activeElement && invokeSave(CloseReason.TAB_BACKWARD);
201
+ }
202
+ }
203
+ }
204
+ break;
205
+ }
206
+ }
207
+ },
208
+ [anchorRef, saveButtonRef],
209
+ );
210
+
211
+ const handleKeydownTabAndEnter = useMemo(() => handleKeyboardTabAndEnter(true), [handleKeyboardTabAndEnter]);
212
+ const handleKeyupTabAndEnter = useMemo(() => handleKeyboardTabAndEnter(false), [handleKeyboardTabAndEnter]);
213
+
123
214
  useEffect(() => {
124
215
  if (isMenuOpen(state)) {
125
216
  const thisDocument = anchorRef?.current ? anchorRef?.current.ownerDocument : document;
217
+ thisDocument.addEventListener("keydown", handleKeydownTabAndEnter, true);
218
+ thisDocument.addEventListener("keyup", handleKeyupTabAndEnter, true);
126
219
  thisDocument.addEventListener("mousedown", handleScreenEventForSave, true);
127
220
  thisDocument.addEventListener("mouseup", handleScreenEventForCancel, true);
128
221
  thisDocument.addEventListener("click", handleScreenEventForCancel, true);
129
222
  thisDocument.addEventListener("dblclick", handleScreenEventForCancel, true);
130
223
  return () => {
224
+ thisDocument.addEventListener("keydown", handleKeydownTabAndEnter, true);
225
+ thisDocument.addEventListener("keyup", handleKeyupTabAndEnter, true);
131
226
  thisDocument.removeEventListener("mousedown", handleScreenEventForSave, true);
132
227
  thisDocument.removeEventListener("mouseup", handleScreenEventForCancel, true);
133
228
  thisDocument.removeEventListener("click", handleScreenEventForCancel, true);
@@ -135,7 +230,14 @@ export const ControlledMenuFr = (
135
230
  };
136
231
  }
137
232
  return () => {};
138
- }, [handleScreenEventForSave, handleScreenEventForCancel, state, anchorRef]);
233
+ }, [
234
+ handleScreenEventForSave,
235
+ handleScreenEventForCancel,
236
+ state,
237
+ anchorRef,
238
+ handleKeydownTabAndEnter,
239
+ handleKeyupTabAndEnter,
240
+ ]);
139
241
 
140
242
  const itemSettings = useMemo(
141
243
  () => ({
@@ -1,4 +1,4 @@
1
- import { Ref, useContext, useMemo } from "react";
1
+ import { KeyboardEvent, Ref, useContext, useMemo } from "react";
2
2
  import { useBEM, useItemState, useCombinedRef } from "../hooks";
3
3
  import { mergeProps, commonProps, safeCall, menuClass, menuItemClass, withHovering, Keys, RMEvent } from "../utils";
4
4
  import { BaseProps, ClickEvent, EventHandler, Hoverable, MenuItemTypeProp, RenderProp } from "../types";
@@ -106,7 +106,12 @@ const MenuItemFr = ({
106
106
  value,
107
107
  syntheticEvent: e,
108
108
  };
109
- if (e.key !== undefined) event.key = e.key;
109
+
110
+ if (e.key !== undefined) {
111
+ const ke = e as KeyboardEvent;
112
+ event.key = ke.key;
113
+ event.shiftKey = ke.shiftKey;
114
+ }
110
115
  if (isCheckBox) event.checked = !isChecked;
111
116
  if (isRadio) event.name = radioGroup.name;
112
117
  safeCall(onClick, event);
@@ -114,6 +119,14 @@ const MenuItemFr = ({
114
119
  eventHandlers.handleClick(event, isCheckBox || isRadio);
115
120
  };
116
121
 
122
+ const handleKeyDown = (e: KeyboardEvent) => {
123
+ // if tab is allowed the handleKeyUp event can't process the tab
124
+ if (e.key === "Tab") {
125
+ e.preventDefault();
126
+ e.stopPropagation();
127
+ }
128
+ };
129
+
117
130
  /**
118
131
  * Keyboard events are triggered on up, otherwise sub-components get spaces and enters typed in them
119
132
  */
@@ -122,7 +135,10 @@ const MenuItemFr = ({
122
135
 
123
136
  switch (e.key) {
124
137
  case Keys.ENTER:
138
+ case Keys.TAB:
125
139
  case Keys.SPACE:
140
+ e.preventDefault();
141
+ e.stopPropagation();
126
142
  if (isAnchor) {
127
143
  menuItemRef?.current && menuItemRef.current.click();
128
144
  } else {
@@ -147,6 +163,7 @@ const MenuItemFr = ({
147
163
  {
148
164
  ...restStateProps,
149
165
  onPointerDown: setHover,
166
+ onKeyDown: handleKeyDown,
150
167
  onKeyUp: handleKeyUp,
151
168
  onClick: handleClick,
152
169
  },
@@ -49,6 +49,8 @@ export interface Event {
49
49
  * Indicates the key if the event is triggered by keyboard. Can be 'Enter', ' '(Space) or 'Escape'.
50
50
  */
51
51
  key?: string;
52
+
53
+ shiftKey?: boolean;
52
54
  }
53
55
 
54
56
  export interface MenuCloseEvent extends Event {
@@ -17,10 +17,12 @@ export interface RMEvent {
17
17
  checked?: boolean;
18
18
  name?: string;
19
19
  key?: string;
20
+ shiftKey?: boolean;
20
21
  }
21
22
 
22
23
  export const Keys = Object.freeze({
23
24
  ENTER: "Enter",
25
+ TAB: "Tab",
24
26
  ESC: "Escape",
25
27
  SPACE: " ",
26
28
  HOME: "Home",
@@ -47,6 +49,8 @@ export const CloseReason = Object.freeze({
47
49
  CANCEL: "cancel",
48
50
  BLUR: "blur",
49
51
  SCROLL: "scroll",
52
+ TAB_FORWARD: "tab_forward",
53
+ TAB_BACKWARD: "tab_backward",
50
54
  });
51
55
 
52
56
  export const FocusPositions = Object.freeze({
@@ -6,6 +6,7 @@ import { wait } from "../../utils/util";
6
6
  import { CellEditorCommon } from "../../components/GridCell";
7
7
  import { useGridPopoverHook } from "../../components/GridPopoverHook";
8
8
  import { useGridPopoverContext } from "../../contexts/GridPopoverContext";
9
+ import { FormError } from "../../lui/FormError";
9
10
 
10
11
  export interface IFormTestRow {
11
12
  id: number;
@@ -37,10 +38,16 @@ export const FormTest = (props: CellEditorCommon): JSX.Element => {
37
38
  [nameType, numba],
38
39
  );
39
40
 
41
+ const invalid = useCallback(() => {
42
+ if (numba.length < 3) return "Number should be at least 3 characters";
43
+ return null;
44
+ }, [numba.length]);
45
+
40
46
  const [showModal, setShowModal] = useState<boolean>(false);
41
47
 
42
- const { popoverWrapper, firstInputKeyboardEventHandlers, lastInputKeyboardEventHandlers } = useGridPopoverHook({
48
+ const { popoverWrapper, triggerSave } = useGridPopoverHook({
43
49
  className: props.className,
50
+ invalid,
44
51
  save,
45
52
  });
46
53
 
@@ -50,7 +57,7 @@ export const FormTest = (props: CellEditorCommon): JSX.Element => {
50
57
  <LuiAlertModal
51
58
  data-testid="WarningAlertWithButtons-modal"
52
59
  level="warning"
53
- // If panel is popped out, append modal to poppped out window DOM, otherwise use default
60
+ // If panel is popped out, append modal to popped out window DOM, otherwise use default
54
61
  //appendToElement={() => (poppedOut && popoutElement) || document.body}
55
62
  >
56
63
  <h2>Header</h2>
@@ -73,6 +80,7 @@ export const FormTest = (props: CellEditorCommon): JSX.Element => {
73
80
  level="primary"
74
81
  onClick={() => {
75
82
  setShowModal(false);
83
+ triggerSave().then();
76
84
  }}
77
85
  data-testid="WarningAlertWithButtons-ok"
78
86
  >
@@ -81,26 +89,21 @@ export const FormTest = (props: CellEditorCommon): JSX.Element => {
81
89
  </LuiAlertModalButtons>
82
90
  </LuiAlertModal>
83
91
  )}
84
- <div style={{ display: "flex", flexDirection: "row" }} className={"FormTest Grid-popoverContainer"}>
85
- <div className={"FormTest-textInput"}>
86
- <LuiTextInput
87
- label={"Name type"}
88
- value={nameType}
89
- onChange={(e) => setNameType(e.target.value)}
90
- inputProps={firstInputKeyboardEventHandlers}
91
- />
92
- </div>
93
- <div className={"FormTest-textInput"}>
94
- <LuiTextInput
95
- label={"Number"}
96
- value={numba}
97
- onChange={(e) => setNumba(e.target.value)}
98
- inputProps={lastInputKeyboardEventHandlers}
99
- />
100
- </div>
101
- <div className={"FormTest-textInput"}>
102
- <LuiButton onClick={() => setShowModal(true)}>Show Modal</LuiButton>
92
+ <div className={"Grid-popoverContainer"}>
93
+ <div className="FormTest">
94
+ <div className={"FormTest-textInput"}>
95
+ <LuiTextInput label={"Name type"} value={nameType} onChange={(e) => setNameType(e.target.value)} />
96
+ </div>
97
+ <div className={"FormTest-textInput"}>
98
+ <LuiTextInput label={"Number"} value={numba} onChange={(e) => setNumba(e.target.value)} />
99
+ </div>
100
+ <div style={{ marginTop: 25 }}>
101
+ <LuiButton style={{ height: 48 }} onClick={() => setShowModal(true)}>
102
+ Show Modal
103
+ </LuiButton>
104
+ </div>
103
105
  </div>
106
+ <FormError error={invalid()} />
104
107
  </div>
105
108
  </>,
106
109
  );
@@ -37,7 +37,7 @@ export default {
37
37
 
38
38
  interface ITestRow {
39
39
  id: number;
40
- bearing1: number | null;
40
+ bearing1: string | number | null;
41
41
  bearingCorrection: number | null;
42
42
  }
43
43
 
@@ -85,8 +85,9 @@ const GridReadOnlyTemplate: ComponentStory<typeof Grid> = (props: GridProps) =>
85
85
 
86
86
  const [rowData] = useState([
87
87
  { id: 1000, bearing1: 1.234, bearingCorrection: 90 },
88
- { id: 1001, bearing1: 1.565, bearingCorrection: 240 },
88
+ { id: 1001, bearing1: "0E-12", bearingCorrection: 240 },
89
89
  { id: 1002, bearing1: null, bearingCorrection: 355.1 },
90
+ { id: 1003, bearing1: null, bearingCorrection: 0 },
90
91
  ] as ITestRow[]);
91
92
 
92
93
  return (
@@ -13,6 +13,7 @@ import { wait } from "../../utils/util";
13
13
  import { ColDefT, GridCell } from "../../components/GridCell";
14
14
  import { GridPopoverEditDropDown } from "../../components/gridPopoverEdit/GridPopoverEditDropDown";
15
15
  import { GridFormSubComponentTextInput } from "../../components/gridForm/GridFormSubComponentTextInput";
16
+ import { GridPopoverMenu } from "../../components/gridPopoverEdit/GridPopoverMenu";
16
17
 
17
18
  export default {
18
19
  title: "Components / Grids",
@@ -280,11 +281,20 @@ const GridEditDropDownTemplate: ComponentStory<typeof Grid> = (props: GridProps)
280
281
  onSelectedItem: async (selected) => {
281
282
  // eslint-disable-next-line no-console
282
283
  console.log("onSelectedItem", selected);
284
+ await wait(500);
283
285
  selected.selectedRows.forEach((row) => (row.sub = selected.subComponentValue ?? selected.value));
284
286
  },
285
287
  },
286
288
  },
287
289
  ),
290
+ GridPopoverMenu(
291
+ {},
292
+ {
293
+ editorParams: {
294
+ options: async () => [{ label: "Hello", action: async () => {}, supportsMultiEdit: true }],
295
+ },
296
+ },
297
+ ),
288
298
  ],
289
299
  [optionsFn, optionsObjects],
290
300
  );