@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.
- package/dist/index.js +325 -265
- package/dist/index.js.map +1 -1
- package/dist/src/components/GridPopoverHook.d.ts +2 -13
- package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
- package/dist/src/components/gridForm/GridFormPopoverMenu.d.ts +0 -1
- package/dist/src/contexts/GridContext.d.ts +1 -0
- package/dist/src/contexts/GridPopoverContext.d.ts +1 -1
- package/dist/src/contexts/GridUpdatingContext.d.ts +3 -2
- package/dist/src/contexts/GridUpdatingContextProvider.d.ts +1 -1
- package/dist/src/lui/FormError.d.ts +6 -0
- package/dist/src/react-menu3/components/MenuItem.d.ts +1 -1
- package/dist/src/react-menu3/components/SubMenu.d.ts +1 -1
- package/dist/src/react-menu3/hooks/useBEM.d.ts +1 -1
- package/dist/src/react-menu3/types.d.ts +18 -17
- package/dist/src/react-menu3/utils/constants.d.ts +4 -0
- package/dist/src/react-menu3/utils/utils.d.ts +1 -1
- package/dist/step-ag-grid.esm.js +326 -266
- package/dist/step-ag-grid.esm.js.map +1 -1
- package/package.json +1 -1
- package/src/components/GridPopoverHook.tsx +30 -79
- package/src/components/gridForm/GridFormDropDown.tsx +62 -46
- package/src/components/gridForm/GridFormEditBearing.tsx +16 -8
- package/src/components/gridForm/GridFormMultiSelect.tsx +12 -4
- package/src/components/gridForm/GridFormPopoverMenu.tsx +25 -22
- package/src/components/gridForm/GridFormSubComponentTextArea.tsx +1 -14
- package/src/components/gridForm/GridFormSubComponentTextInput.tsx +1 -18
- package/src/components/gridForm/GridFormTextArea.tsx +8 -5
- package/src/components/gridForm/GridFormTextInput.tsx +8 -4
- package/src/contexts/GridContext.tsx +4 -0
- package/src/contexts/GridContextProvider.tsx +8 -0
- package/src/contexts/GridPopoverContext.tsx +1 -1
- package/src/contexts/GridPopoverContextProvider.tsx +7 -4
- package/src/contexts/GridUpdatingContext.tsx +7 -1
- package/src/contexts/GridUpdatingContextProvider.tsx +17 -6
- package/src/lui/FormError.tsx +29 -0
- package/src/lui/TextAreaInput.tsx +2 -18
- package/src/lui/TextInputFormatted.tsx +2 -17
- package/src/react-menu3/components/ControlledMenu.tsx +114 -12
- package/src/react-menu3/components/MenuItem.tsx +19 -2
- package/src/react-menu3/types.ts +2 -0
- package/src/react-menu3/utils/constants.ts +4 -0
- package/src/stories/grid/FormTest.tsx +24 -21
- package/src/stories/grid/GridPopoutBearing.stories.tsx +3 -2
- package/src/stories/grid/GridPopoutEditDropDown.stories.tsx +10 -0
- package/src/stories/grid/GridPopoutEditMultiSelect.stories.tsx +1 -1
- package/src/stories/grid/GridReadOnly.stories.tsx +11 -11
- package/src/styles/GridFormDropDown.scss +1 -1
- 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
|
|
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
|
|
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
|
|
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)
|
|
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: (
|
|
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 (
|
|
28
|
-
|
|
29
|
+
const modifyUpdating = async (
|
|
30
|
+
fields: string | string[],
|
|
31
|
+
ids: (number | string)[],
|
|
32
|
+
fn: () => void | Promise<void>,
|
|
33
|
+
) => {
|
|
29
34
|
const idRef = [...ids];
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
{
|
|
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
|
-
}, [
|
|
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
|
-
|
|
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
|
},
|
package/src/react-menu3/types.ts
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
85
|
-
<div className=
|
|
86
|
-
<
|
|
87
|
-
label={"Name type"}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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:
|
|
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
|
);
|