@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
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
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"
|
|
26
|
-
setOpen(false);
|
|
27
|
+
if (reason == "cancel") {
|
|
27
28
|
stopEditing();
|
|
29
|
+
return;
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
|
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 {
|
|
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(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
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
|
-
}, [
|
|
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(
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
[
|
|
51
|
+
[defaultValue, field, props, value],
|
|
45
52
|
);
|
|
46
|
-
const { popoverWrapper
|
|
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={
|
|
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,
|
|
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
|
-
|
|
190
|
-
|
|
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(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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(
|
|
103
|
-
|
|
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
|
|
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({
|
|
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={!
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
[
|
|
43
|
+
[initialVale, value, props, field],
|
|
46
44
|
);
|
|
47
|
-
|
|
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
|
);
|