@vuu-ui/vuu-data-editing 5.0.0-alpha.2 → 5.0.0-alpha.4
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/README.md +40 -3
- package/package.json +7 -6
- package/src/DirectEditSession.js +8 -1
- package/src/EditButtons.js +6 -5
- package/src/EditModeProvider.js +14 -5
- package/src/EditSession.js +78 -8
- package/src/edit-control/EditControlCheckbox.js +25 -0
- package/src/edit-control/EditControlDropdown.js +34 -0
- package/src/edit-control/EditControlLookupDropdown.js +31 -0
- package/src/edit-control/get-data-item-edit-control.js +139 -0
- package/src/edit-control/index.js +4 -0
- package/src/edit-errors.js +7 -0
- package/src/edit-field/EditField.js +117 -24
- package/src/edit-field/useEditField.js +66 -52
- package/src/edit-form/CreateRowForm.js +37 -0
- package/src/edit-form/EditForm.css.js +32 -0
- package/src/edit-form/EditForm.js +85 -0
- package/src/edit-form/EditFormButtons.js +48 -0
- package/src/edit-form/EditFormContext.js +4 -0
- package/src/edit-form/index.js +4 -0
- package/src/form-fields.js +40 -0
- package/src/index.js +9 -0
- package/src/lookup-values/useLookupOptions.js +106 -0
- package/src/useConfirmDiscard.js +34 -0
- package/src/useCustomEditField.js +76 -0
- package/src/useEditForm.js +306 -0
- package/src/useEditSessionState.js +72 -0
- package/src/useEditable.js +26 -9
- package/src/useEditableColumns.js +105 -0
- package/src/useEditableTable.js +32 -9
- package/types/DirectEditSession.d.ts +6 -1
- package/types/EditButtons.d.ts +20 -3
- package/types/EditModeProvider.d.ts +10 -2
- package/types/EditSession.d.ts +18 -1
- package/types/edit-control/EditControlCheckbox.d.ts +8 -0
- package/types/edit-control/EditControlDropdown.d.ts +14 -0
- package/types/edit-control/EditControlLookupDropdown.d.ts +13 -0
- package/types/edit-control/get-data-item-edit-control.d.ts +40 -0
- package/types/edit-control/index.d.ts +4 -0
- package/types/edit-errors.d.ts +9 -0
- package/types/edit-field/EditField.d.ts +37 -3
- package/types/edit-field/useEditField.d.ts +2 -1
- package/types/edit-form/CreateRowForm.d.ts +29 -0
- package/types/edit-form/EditForm.d.ts +36 -0
- package/types/edit-form/EditFormButtons.d.ts +18 -0
- package/types/edit-form/EditFormContext.d.ts +59 -0
- package/types/edit-form/index.d.ts +4 -0
- package/types/form-fields.d.ts +32 -0
- package/types/index.d.ts +12 -2
- package/types/lookup-values/useLookupOptions.d.ts +35 -0
- package/types/useConfirmDiscard.d.ts +23 -0
- package/types/useCustomEditField.d.ts +51 -0
- package/types/useEditForm.d.ts +127 -0
- package/types/useEditSessionState.d.ts +27 -0
- package/types/useEditable.d.ts +30 -3
- package/types/useEditableColumns.d.ts +79 -0
- package/types/useEditableTable.d.ts +18 -3
|
@@ -1,40 +1,87 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Checkbox, Dropdown, FormField, FormFieldLabel, Input, Option } from "@salt-ds/core";
|
|
3
|
-
import { useCallback } from "react";
|
|
2
|
+
import { Checkbox, Dropdown, FormField, FormFieldHelperText, FormFieldLabel, Input, Option } from "@salt-ds/core";
|
|
3
|
+
import { useCallback, useEffect, useState } from "react";
|
|
4
4
|
import { useEditField } from "./useEditField.js";
|
|
5
|
-
import { useEditSession } from "../DataEditingProvider.js";
|
|
5
|
+
import { useEditSession, useTableEditSession } from "../DataEditingProvider.js";
|
|
6
6
|
import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils";
|
|
7
7
|
import { useEditMode } from "../EditModeProvider.js";
|
|
8
|
+
import { getDataItemEditControl } from "../edit-control/index.js";
|
|
9
|
+
import { DirectEditSession } from "../DirectEditSession.js";
|
|
10
|
+
import { useEditFormContext } from "../edit-form/EditFormContext.js";
|
|
11
|
+
import { useEditSessionState } from "../useEditSessionState.js";
|
|
8
12
|
import { useLookupValues } from "../lookup-values/useLookupValues.js";
|
|
9
13
|
import "./EditField.css";
|
|
10
14
|
const classBase = "vuuEditField";
|
|
11
|
-
const
|
|
15
|
+
const isColumnEditFieldProps = (props)=>void 0 !== props.column;
|
|
16
|
+
const resolveDataRow = (dataRow)=>{
|
|
17
|
+
if (void 0 === dataRow) throw Error("EditField needs a dataRow prop or an enclosing EditForm");
|
|
18
|
+
return dataRow;
|
|
19
|
+
};
|
|
20
|
+
const EditField = (props)=>isColumnEditFieldProps(props) ? /*#__PURE__*/ jsx(ColumnEditField, {
|
|
21
|
+
...props
|
|
22
|
+
}) : /*#__PURE__*/ jsx(TypedEditField, {
|
|
23
|
+
...props
|
|
24
|
+
});
|
|
25
|
+
const ColumnEditField = ({ column: descriptor, label = descriptor.label ?? descriptor.name, ...props })=>/*#__PURE__*/ jsx(TypedEditField, {
|
|
26
|
+
...props,
|
|
27
|
+
descriptor: descriptor,
|
|
28
|
+
label: label,
|
|
29
|
+
name: descriptor.name,
|
|
30
|
+
required: descriptor.required,
|
|
31
|
+
serverDataType: descriptor.serverDataType
|
|
32
|
+
});
|
|
33
|
+
const TypedEditField = ({ clientSideEditValidationCheck, dataRow: dataRowProp, deferNewRow: deferNewRowProp, descriptor, label, name, readOnly = false, required, serverDataType = "string", type, ...options })=>{
|
|
12
34
|
const editSession = useEditSession();
|
|
35
|
+
const tableEditSession = useTableEditSession();
|
|
36
|
+
const form = useEditFormContext();
|
|
37
|
+
const dataRow = resolveDataRow(dataRowProp ?? form?.dataRow);
|
|
38
|
+
const deferNewRow = deferNewRowProp ?? form?.mode === "create";
|
|
13
39
|
const { isEditMode } = useEditMode();
|
|
40
|
+
const formFieldErrors = form?.fieldErrors;
|
|
41
|
+
const isNewRow = editSession?.isNewRow(dataRow.key) ?? false;
|
|
42
|
+
const { newRowState } = useEditSessionState(isNewRow ? editSession : void 0);
|
|
14
43
|
const lookupValues = useLookupValues({
|
|
15
|
-
enabled: "dropdown" === type,
|
|
44
|
+
enabled: void 0 === descriptor && "dropdown" === type,
|
|
16
45
|
...options
|
|
17
46
|
});
|
|
47
|
+
const formSetValue = form?.setValue;
|
|
48
|
+
const setNewRowValue = useCallback((name, value)=>{
|
|
49
|
+
if (formSetValue) formSetValue(name, value);
|
|
50
|
+
else editSession?.setNewRowValue(name, value);
|
|
51
|
+
}, [
|
|
52
|
+
editSession,
|
|
53
|
+
formSetValue
|
|
54
|
+
]);
|
|
18
55
|
const onEdit = useCallback(async (editState, editPhase)=>{
|
|
19
|
-
const { isValid = true, previousValue = "", value } = editState;
|
|
20
|
-
if ("commit" === editPhase &&
|
|
21
|
-
if (editSession
|
|
56
|
+
const { editType, isValid = true, previousValue = "", value } = editState;
|
|
57
|
+
if ("commit" === editPhase && tableEditSession) {
|
|
58
|
+
if (editSession?.isNewRow(dataRow.key)) {
|
|
59
|
+
if ("cancel" === editType) {
|
|
60
|
+
editSession.clearNewRowError(name);
|
|
61
|
+
return {
|
|
62
|
+
data: void 0,
|
|
63
|
+
type: "SUCCESS_RESULT"
|
|
64
|
+
};
|
|
65
|
+
}
|
|
22
66
|
const isEmptyValue = "string" == typeof value && "" === value.trim();
|
|
23
|
-
if (!isValid && !isEmptyValue)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
67
|
+
if (!isValid && !isEmptyValue) {
|
|
68
|
+
editSession.setNewRowError(name);
|
|
69
|
+
return {
|
|
70
|
+
errorMessage: "Invalid value",
|
|
71
|
+
type: "ERROR_RESULT"
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
setNewRowValue(name, isEmptyValue ? void 0 : value);
|
|
28
75
|
if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
|
|
29
76
|
return {
|
|
30
77
|
data: void 0,
|
|
31
78
|
type: "SUCCESS_RESULT"
|
|
32
79
|
};
|
|
33
80
|
}
|
|
34
|
-
return
|
|
81
|
+
return tableEditSession.commit(dataRow.key, name, previousValue, value, isValid);
|
|
35
82
|
}
|
|
36
83
|
if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
|
|
37
|
-
|
|
84
|
+
setNewRowValue(name, value);
|
|
38
85
|
return {
|
|
39
86
|
data: void 0,
|
|
40
87
|
type: "SUCCESS_RESULT"
|
|
@@ -44,29 +91,53 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
|
|
|
44
91
|
dataRow,
|
|
45
92
|
deferNewRow,
|
|
46
93
|
editSession,
|
|
47
|
-
name
|
|
94
|
+
name,
|
|
95
|
+
setNewRowValue,
|
|
96
|
+
tableEditSession
|
|
48
97
|
]);
|
|
49
|
-
const onCommit = useCallback(()=>{
|
|
50
|
-
console.log("onCommit");
|
|
51
|
-
}, []);
|
|
52
98
|
const necessity = required ? "asterisk" : void 0;
|
|
53
99
|
const dataValue = dataRow?.[name] ?? "";
|
|
100
|
+
const [checked, setChecked] = useState(Boolean(dataValue));
|
|
101
|
+
useEffect(()=>{
|
|
102
|
+
setChecked(Boolean(dataValue));
|
|
103
|
+
}, [
|
|
104
|
+
dataValue
|
|
105
|
+
]);
|
|
106
|
+
const handleCheckboxChange = useCallback(async (evt)=>{
|
|
107
|
+
const previousValue = checked;
|
|
108
|
+
const value = evt.target.checked;
|
|
109
|
+
setChecked(value);
|
|
110
|
+
const response = await onEdit({
|
|
111
|
+
editType: "commit",
|
|
112
|
+
isValid: true,
|
|
113
|
+
previousValue,
|
|
114
|
+
value
|
|
115
|
+
}, "commit");
|
|
116
|
+
if (response?.type === "ERROR_RESULT") setChecked(previousValue);
|
|
117
|
+
}, [
|
|
118
|
+
checked,
|
|
119
|
+
onEdit
|
|
120
|
+
]);
|
|
54
121
|
const column = {
|
|
122
|
+
clientSideEditValidationCheck,
|
|
55
123
|
label,
|
|
56
124
|
name,
|
|
57
|
-
|
|
125
|
+
required,
|
|
126
|
+
serverDataType: "checkbox" === type && "string" === serverDataType ? "boolean" : serverDataType
|
|
58
127
|
};
|
|
59
|
-
const { inputProps, onChange, onDropdownSelectionChange, onKeyDown, value } = useEditField({
|
|
128
|
+
const { commit, inputProps, onChange, onDropdownSelectionChange, onKeyDown, value, warningMessage: fieldMessage } = useEditField({
|
|
60
129
|
column,
|
|
61
130
|
textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
|
|
62
131
|
onEdit,
|
|
63
132
|
type: dataDescriptorTypeToVuuRowDataItemType(column),
|
|
64
133
|
value: dataValue
|
|
65
134
|
});
|
|
135
|
+
const warningMessage = isEditMode ? fieldMessage ?? (formFieldErrors ? formFieldErrors[name] : newRowState.errors[name]) : void 0;
|
|
66
136
|
return /*#__PURE__*/ jsxs(FormField, {
|
|
67
137
|
className: classBase,
|
|
68
138
|
"data-field": name,
|
|
69
139
|
necessity: necessity,
|
|
140
|
+
validationStatus: warningMessage ? "error" : void 0,
|
|
70
141
|
children: [
|
|
71
142
|
/*#__PURE__*/ jsx(FormFieldLabel, {
|
|
72
143
|
children: label
|
|
@@ -77,9 +148,28 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
|
|
|
77
148
|
readOnly: true
|
|
78
149
|
},
|
|
79
150
|
value: String(dataValue)
|
|
151
|
+
}) : descriptor ? getDataItemEditControl({
|
|
152
|
+
InputProps: {
|
|
153
|
+
bordered: true,
|
|
154
|
+
inputProps: {
|
|
155
|
+
...inputProps,
|
|
156
|
+
onChange,
|
|
157
|
+
value
|
|
158
|
+
},
|
|
159
|
+
onKeyDown,
|
|
160
|
+
value
|
|
161
|
+
},
|
|
162
|
+
commitOnBlur: true,
|
|
163
|
+
dataDescriptor: descriptor,
|
|
164
|
+
editOperation: isNewRow ? "insert" : "update",
|
|
165
|
+
onCommit: (_evt, committedValue)=>{
|
|
166
|
+
commit(String(committedValue));
|
|
167
|
+
},
|
|
168
|
+
table: tableEditSession instanceof DirectEditSession ? tableEditSession.dataSource.table : editSession?.sourceDataSource?.table
|
|
80
169
|
}) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
|
|
81
|
-
checked:
|
|
82
|
-
|
|
170
|
+
checked: checked,
|
|
171
|
+
disabled: readOnly,
|
|
172
|
+
onChange: handleCheckboxChange
|
|
83
173
|
}) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
|
|
84
174
|
"data-icon": "triangle-down",
|
|
85
175
|
onSelectionChange: onDropdownSelectionChange,
|
|
@@ -98,7 +188,10 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
|
|
|
98
188
|
onKeyDown: onKeyDown,
|
|
99
189
|
readOnly: readOnly,
|
|
100
190
|
value: value
|
|
101
|
-
})
|
|
191
|
+
}),
|
|
192
|
+
warningMessage ? /*#__PURE__*/ jsx(FormFieldHelperText, {
|
|
193
|
+
children: warningMessage
|
|
194
|
+
}) : null
|
|
102
195
|
]
|
|
103
196
|
});
|
|
104
197
|
};
|
|
@@ -8,11 +8,13 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
|
8
8
|
});
|
|
9
9
|
const initialValueRef = useRef(value?.toString() ?? "");
|
|
10
10
|
const isDirtyRef = useRef(false);
|
|
11
|
+
const hasUnresolvedErrorRef = useRef(false);
|
|
11
12
|
useEffect(()=>{
|
|
12
13
|
const nextValue = stringValueOf(value);
|
|
13
14
|
if (initialValueRef.current !== nextValue) {
|
|
14
15
|
initialValueRef.current = nextValue;
|
|
15
16
|
isDirtyRef.current = false;
|
|
17
|
+
hasUnresolvedErrorRef.current = false;
|
|
16
18
|
setEditState((editState)=>({
|
|
17
19
|
...editState,
|
|
18
20
|
message: void 0,
|
|
@@ -23,59 +25,69 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
|
23
25
|
value
|
|
24
26
|
]);
|
|
25
27
|
const commit = useCallback(async (value = editState.value)=>{
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
const { current: initialValue } = initialValueRef;
|
|
29
|
+
const previousValue = getTypedValue(initialValue, type);
|
|
30
|
+
const isEmptyOptional = column?.required === false && "" === value.trim();
|
|
31
|
+
const result = isEmptyOptional ? void 0 : column.clientSideEditValidationCheck?.(value, "*");
|
|
32
|
+
if (result?.ok === false) {
|
|
33
|
+
hasUnresolvedErrorRef.current = true;
|
|
34
|
+
setEditState((state)=>({
|
|
35
|
+
...state,
|
|
36
|
+
message: result?.messages?.join(",")
|
|
37
|
+
}));
|
|
38
|
+
await onEdit?.({
|
|
39
|
+
editType: "commit",
|
|
40
|
+
isValid: false,
|
|
41
|
+
previousValue,
|
|
42
|
+
value
|
|
43
|
+
}, "commit");
|
|
44
|
+
} else try {
|
|
45
|
+
const typedValue = isEmptyOptional ? "" : getTypedValue(value, type, true);
|
|
46
|
+
const response = await onEdit?.({
|
|
47
|
+
editType: "commit",
|
|
48
|
+
isValid: true,
|
|
49
|
+
previousValue,
|
|
50
|
+
value: typedValue
|
|
51
|
+
}, "commit");
|
|
52
|
+
if (isRpcSuccess(response)) {
|
|
53
|
+
isDirtyRef.current = false;
|
|
54
|
+
hasUnresolvedErrorRef.current = false;
|
|
55
|
+
setEditState((state)=>({
|
|
56
|
+
editing: false,
|
|
57
|
+
previousValue: state.previousValue === value ? void 0 : void 0 === state.previousValue ? initialValue : state.previousValue,
|
|
58
|
+
value
|
|
59
|
+
}));
|
|
60
|
+
} else if (isRpcError(response)) {
|
|
61
|
+
hasUnresolvedErrorRef.current = true;
|
|
62
|
+
setEditState((state)=>({
|
|
63
|
+
...state,
|
|
64
|
+
message: response.errorMessage
|
|
65
|
+
}));
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
initialValueRef.current = value;
|
|
69
|
+
return true;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
if (e instanceof DataValidationError) {
|
|
72
|
+
hasUnresolvedErrorRef.current = true;
|
|
73
|
+
const { actualType, expectedType, message } = e;
|
|
74
|
+
if (column) {
|
|
75
|
+
const { name, label = name } = column;
|
|
76
|
+
const message = `${label} is a ${expectedType} value, data entered is ${actualType} `;
|
|
50
77
|
setEditState((state)=>({
|
|
51
|
-
...state,
|
|
52
|
-
message: response.errorMessage
|
|
53
|
-
}));
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
initialValueRef.current = value;
|
|
57
|
-
return true;
|
|
58
|
-
} catch (e) {
|
|
59
|
-
if (e instanceof DataValidationError) {
|
|
60
|
-
const { actualType, expectedType, message } = e;
|
|
61
|
-
if (column) {
|
|
62
|
-
const { name, label = name } = column;
|
|
63
|
-
const message = `${label} is a ${expectedType} value, data entered is ${actualType} `;
|
|
64
|
-
setEditState((state)=>({
|
|
65
|
-
...state,
|
|
66
|
-
message
|
|
67
|
-
}));
|
|
68
|
-
onEdit?.({
|
|
69
|
-
editType: "commit",
|
|
70
|
-
isValid: false,
|
|
71
|
-
previousValue,
|
|
72
|
-
value
|
|
73
|
-
}, "commit");
|
|
74
|
-
} else setEditState((state)=>({
|
|
75
78
|
...state,
|
|
76
79
|
message
|
|
77
80
|
}));
|
|
78
|
-
|
|
81
|
+
await onEdit?.({
|
|
82
|
+
editType: "commit",
|
|
83
|
+
isValid: false,
|
|
84
|
+
previousValue,
|
|
85
|
+
value
|
|
86
|
+
}, "commit");
|
|
87
|
+
} else setEditState((state)=>({
|
|
88
|
+
...state,
|
|
89
|
+
message
|
|
90
|
+
}));
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
return false;
|
|
@@ -86,9 +98,10 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
|
86
98
|
type
|
|
87
99
|
]);
|
|
88
100
|
const toggleActivation = useCallback(async (input, cancel = false)=>{
|
|
89
|
-
if (editState.editing) if (isDirtyRef.current) if (cancel) {
|
|
101
|
+
if (editState.editing) if (isDirtyRef.current || hasUnresolvedErrorRef.current) if (cancel) {
|
|
90
102
|
const { value: previousValue } = editState;
|
|
91
103
|
isDirtyRef.current = false;
|
|
104
|
+
hasUnresolvedErrorRef.current = false;
|
|
92
105
|
setEditState({
|
|
93
106
|
editing: false,
|
|
94
107
|
value: initialValueRef.current,
|
|
@@ -98,7 +111,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
|
98
111
|
editType: "cancel",
|
|
99
112
|
isValid: true,
|
|
100
113
|
previousValue,
|
|
101
|
-
value: getTypedValue(initialValueRef.current, type
|
|
114
|
+
value: getTypedValue(initialValueRef.current, type) ?? initialValueRef.current
|
|
102
115
|
}, "commit");
|
|
103
116
|
} else {
|
|
104
117
|
const commitSuccessful = await commit();
|
|
@@ -147,7 +160,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
|
147
160
|
]);
|
|
148
161
|
const handleBlur = useCallback(async ()=>{
|
|
149
162
|
if (editState.editing) {
|
|
150
|
-
if (isDirtyRef.current) await commit();
|
|
163
|
+
if (isDirtyRef.current || hasUnresolvedErrorRef.current) await commit();
|
|
151
164
|
setEditState((editState)=>({
|
|
152
165
|
...editState,
|
|
153
166
|
editing: false
|
|
@@ -201,6 +214,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
|
201
214
|
commit
|
|
202
215
|
]);
|
|
203
216
|
return {
|
|
217
|
+
commit,
|
|
204
218
|
editing: editState.editing,
|
|
205
219
|
inputProps: {
|
|
206
220
|
onBlur: handleBlur,
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { EditSession } from "../EditSession.js";
|
|
4
|
+
import { EditField } from "../edit-field/EditField.js";
|
|
5
|
+
import { EditForm } from "./EditForm.js";
|
|
6
|
+
const NEW_ROW = {
|
|
7
|
+
key: EditSession.newRowKey
|
|
8
|
+
};
|
|
9
|
+
const NO_FIELDS = [];
|
|
10
|
+
const CreateRowForm = ({ children, fields = NO_FIELDS, ...props })=>{
|
|
11
|
+
const fieldNames = fields.map(({ name })=>name).join();
|
|
12
|
+
const requiredNames = fields.filter(({ required })=>false !== required).map(({ name })=>name).join();
|
|
13
|
+
const columns = useMemo(()=>fieldNames ? fieldNames.split(",") : [], [
|
|
14
|
+
fieldNames
|
|
15
|
+
]);
|
|
16
|
+
const requiredColumns = useMemo(()=>requiredNames ? requiredNames.split(",") : [], [
|
|
17
|
+
requiredNames
|
|
18
|
+
]);
|
|
19
|
+
return /*#__PURE__*/ jsxs(EditForm, {
|
|
20
|
+
...props,
|
|
21
|
+
columns: columns,
|
|
22
|
+
mode: "create",
|
|
23
|
+
requiredColumns: requiredColumns,
|
|
24
|
+
children: [
|
|
25
|
+
fields.map((field)=>/*#__PURE__*/ jsx(EditField, {
|
|
26
|
+
column: false === field.required ? field : {
|
|
27
|
+
...field,
|
|
28
|
+
required: true
|
|
29
|
+
},
|
|
30
|
+
dataRow: NEW_ROW,
|
|
31
|
+
deferNewRow: true
|
|
32
|
+
}, field.name)),
|
|
33
|
+
children
|
|
34
|
+
]
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
export { CreateRowForm };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const css = `
|
|
2
|
+
.vuuEditForm {
|
|
3
|
+
gap: var(--salt-spacing-200);
|
|
4
|
+
flex-direction: column;
|
|
5
|
+
min-height: 0;
|
|
6
|
+
display: flex;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.vuuEditForm-title {
|
|
10
|
+
font-size: var(--salt-text-h3-fontSize);
|
|
11
|
+
font-weight: var(--salt-text-h3-fontWeight);
|
|
12
|
+
line-height: var(--salt-text-h3-lineHeight);
|
|
13
|
+
margin: 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.vuuEditForm-fields {
|
|
17
|
+
gap: var(--salt-spacing-200);
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
flex: auto;
|
|
20
|
+
display: flex;
|
|
21
|
+
overflow: auto;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.vuuEditForm-buttons {
|
|
25
|
+
gap: var(--salt-spacing-100);
|
|
26
|
+
justify-content: flex-end;
|
|
27
|
+
display: flex;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
`;
|
|
32
|
+
export default css;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Banner, BannerContent } from "@salt-ds/core";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { DataEditingProvider } from "../DataEditingProvider.js";
|
|
5
|
+
import { EditModeProvider } from "../EditModeProvider.js";
|
|
6
|
+
import { useEditForm } from "../useEditForm.js";
|
|
7
|
+
import { EditFormButtons } from "./EditFormButtons.js";
|
|
8
|
+
import { EditFormContext } from "./EditFormContext.js";
|
|
9
|
+
import "./EditForm.css";
|
|
10
|
+
const classBase = "vuuEditForm";
|
|
11
|
+
const EditForm = ({ children, className, columns, confirmCancel, dataRow, dataSource, deleteMode, editModel, editSession: sharedEditSession, editSessionApi, hideCancel = false, isEditMode, mode = "edit", onCancelled, onError, onSaved, requiredColumns, rowDefaults, saveLabel, showButtons = true, title, validate, ...htmlAttributes })=>{
|
|
12
|
+
const form = useEditForm({
|
|
13
|
+
columns,
|
|
14
|
+
dataRow,
|
|
15
|
+
dataSource,
|
|
16
|
+
deleteMode,
|
|
17
|
+
editModel,
|
|
18
|
+
editSession: sharedEditSession,
|
|
19
|
+
editSessionApi,
|
|
20
|
+
isEditMode,
|
|
21
|
+
mode,
|
|
22
|
+
onCancelled,
|
|
23
|
+
onError,
|
|
24
|
+
onSaved,
|
|
25
|
+
requiredColumns,
|
|
26
|
+
rowDefaults,
|
|
27
|
+
validate
|
|
28
|
+
});
|
|
29
|
+
const { canSave, error, fieldEditSession, saving, submit } = form;
|
|
30
|
+
const handleSubmit = useCallback((e)=>{
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
if (canSave) submit();
|
|
33
|
+
}, [
|
|
34
|
+
canSave,
|
|
35
|
+
submit
|
|
36
|
+
]);
|
|
37
|
+
const content = /*#__PURE__*/ jsxs("form", {
|
|
38
|
+
...htmlAttributes,
|
|
39
|
+
"aria-busy": saving || void 0,
|
|
40
|
+
className: [
|
|
41
|
+
classBase,
|
|
42
|
+
`${classBase}-${mode}`,
|
|
43
|
+
className
|
|
44
|
+
].filter(Boolean).join(" "),
|
|
45
|
+
noValidate: true,
|
|
46
|
+
onSubmit: handleSubmit,
|
|
47
|
+
children: [
|
|
48
|
+
title ? /*#__PURE__*/ jsx("h2", {
|
|
49
|
+
className: `${classBase}-title`,
|
|
50
|
+
children: title
|
|
51
|
+
}) : null,
|
|
52
|
+
error ? /*#__PURE__*/ jsx(Banner, {
|
|
53
|
+
className: `${classBase}-error`,
|
|
54
|
+
status: "error",
|
|
55
|
+
children: /*#__PURE__*/ jsx(BannerContent, {
|
|
56
|
+
children: error.message
|
|
57
|
+
})
|
|
58
|
+
}) : null,
|
|
59
|
+
/*#__PURE__*/ jsx("div", {
|
|
60
|
+
className: `${classBase}-fields`,
|
|
61
|
+
children: "function" == typeof children ? children(form) : children
|
|
62
|
+
}),
|
|
63
|
+
showButtons && form.isEditMode ? /*#__PURE__*/ jsx("div", {
|
|
64
|
+
className: `${classBase}-buttons`,
|
|
65
|
+
children: /*#__PURE__*/ jsx(EditFormButtons, {
|
|
66
|
+
confirmCancel: confirmCancel,
|
|
67
|
+
hideCancel: hideCancel,
|
|
68
|
+
saveLabel: saveLabel
|
|
69
|
+
})
|
|
70
|
+
}) : null
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
const ownsEditMode = "create" === mode || void 0 !== isEditMode;
|
|
74
|
+
return /*#__PURE__*/ jsx(EditFormContext.Provider, {
|
|
75
|
+
value: form,
|
|
76
|
+
children: /*#__PURE__*/ jsx(DataEditingProvider, {
|
|
77
|
+
editSession: fieldEditSession,
|
|
78
|
+
children: ownsEditMode ? /*#__PURE__*/ jsx(EditModeProvider, {
|
|
79
|
+
isEditMode: form.isEditMode,
|
|
80
|
+
children: content
|
|
81
|
+
}) : content
|
|
82
|
+
})
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
export { EditForm };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@salt-ds/core";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { EditButtons } from "../EditButtons.js";
|
|
5
|
+
import { useEditFormContext } from "./EditFormContext.js";
|
|
6
|
+
const assertForm = (form)=>{
|
|
7
|
+
if (void 0 === form) throw Error("EditFormButtons must be rendered within an EditForm");
|
|
8
|
+
return form;
|
|
9
|
+
};
|
|
10
|
+
const EditFormButtons = ({ confirmCancel, hideCancel = false, saveLabel: saveLabelProp })=>{
|
|
11
|
+
const { cancel, canSave, editModel, editSession, isDirty, isSharedSession, mode, saving, submit } = assertForm(useEditFormContext());
|
|
12
|
+
const saveLabel = saveLabelProp ?? ("create" === mode ? isSharedSession ? "Add" : "Create" : "direct" === editModel ? "Done" : "Save");
|
|
13
|
+
const handleCancel = useCallback(async ()=>{
|
|
14
|
+
if (confirmCancel && isDirty && !await confirmCancel()) return;
|
|
15
|
+
cancel();
|
|
16
|
+
}, [
|
|
17
|
+
cancel,
|
|
18
|
+
confirmCancel,
|
|
19
|
+
isDirty
|
|
20
|
+
]);
|
|
21
|
+
if ("direct" === editModel) return /*#__PURE__*/ jsx(Button, {
|
|
22
|
+
onClick: ()=>submit(),
|
|
23
|
+
sentiment: "accented",
|
|
24
|
+
children: saveLabel
|
|
25
|
+
});
|
|
26
|
+
return "edit" === mode ? /*#__PURE__*/ jsx(EditButtons, {
|
|
27
|
+
canSave: canSave,
|
|
28
|
+
editSession: editSession,
|
|
29
|
+
onCancel: hideCancel ? void 0 : handleCancel,
|
|
30
|
+
onSave: submit,
|
|
31
|
+
saveLabel: saveLabel
|
|
32
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
33
|
+
children: [
|
|
34
|
+
/*#__PURE__*/ jsx(Button, {
|
|
35
|
+
disabled: !canSave,
|
|
36
|
+
sentiment: "accented",
|
|
37
|
+
type: "submit",
|
|
38
|
+
children: saveLabel
|
|
39
|
+
}),
|
|
40
|
+
hideCancel ? null : /*#__PURE__*/ jsx(Button, {
|
|
41
|
+
disabled: saving,
|
|
42
|
+
onClick: handleCancel,
|
|
43
|
+
children: "Cancel"
|
|
44
|
+
})
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
export { EditFormButtons };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isDataValueEditable } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { DEFAULT_READ_ONLY_COLUMNS, narrowEditable } from "./useEditableColumns.js";
|
|
3
|
+
const isServerMaintained = (name)=>DEFAULT_READ_ONLY_COLUMNS.includes(name);
|
|
4
|
+
const toDescriptor = (name, schemaColumn, ...overlays)=>{
|
|
5
|
+
const descriptor = {
|
|
6
|
+
name,
|
|
7
|
+
serverDataType: schemaColumn?.serverDataType
|
|
8
|
+
};
|
|
9
|
+
let editable = schemaColumn?.editable;
|
|
10
|
+
for (const overlay of overlays)if (overlay) {
|
|
11
|
+
const { label, editor, required, serverDataType, type } = overlay;
|
|
12
|
+
if (void 0 !== label) descriptor.label = label;
|
|
13
|
+
if (void 0 !== editor) descriptor.editor = editor;
|
|
14
|
+
if (void 0 !== required) descriptor.required = required;
|
|
15
|
+
if (void 0 !== type) descriptor.type = type;
|
|
16
|
+
if (void 0 !== serverDataType && void 0 === schemaColumn) descriptor.serverDataType = serverDataType;
|
|
17
|
+
editable = narrowEditable(editable, overlay.editable);
|
|
18
|
+
}
|
|
19
|
+
if (void 0 === descriptor.required && schemaColumn?.required) descriptor.required = true;
|
|
20
|
+
if (void 0 !== editable) descriptor.editable = editable;
|
|
21
|
+
return descriptor;
|
|
22
|
+
};
|
|
23
|
+
const getFormFields = ({ columns = [], fields, operation = "update", schema })=>{
|
|
24
|
+
const schemaColumns = new Map(schema?.columns.map((column)=>[
|
|
25
|
+
column.name,
|
|
26
|
+
column
|
|
27
|
+
]));
|
|
28
|
+
const tableColumns = new Map(columns.map((column)=>[
|
|
29
|
+
column.name,
|
|
30
|
+
column
|
|
31
|
+
]));
|
|
32
|
+
let descriptors;
|
|
33
|
+
if (fields) descriptors = fields.map((field)=>toDescriptor(field.name, schemaColumns.get(field.name), tableColumns.get(field.name), field));
|
|
34
|
+
else {
|
|
35
|
+
const names = schema ? columns.map(({ name })=>name).filter((name)=>schemaColumns.has(name)).concat(schema.columns.map(({ name })=>name).filter((name)=>!tableColumns.has(name))) : columns.map(({ name })=>name);
|
|
36
|
+
descriptors = names.filter((name)=>!isServerMaintained(name)).map((name)=>toDescriptor(name, schemaColumns.get(name), tableColumns.get(name)));
|
|
37
|
+
}
|
|
38
|
+
return "insert" === operation ? descriptors.filter((descriptor)=>isDataValueEditable(descriptor, "insert", true)) : descriptors;
|
|
39
|
+
};
|
|
40
|
+
export { getFormFields };
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
export * from "./edit-form/index.js";
|
|
2
|
+
export * from "./edit-control/index.js";
|
|
1
3
|
export { DataEditingProvider, useEditSession, useTableEditSession } from "./DataEditingProvider.js";
|
|
2
4
|
export { DirectEditSession } from "./DirectEditSession.js";
|
|
3
5
|
export { useCellEdited } from "./useCellEdited.js";
|
|
4
6
|
export { useEditState } from "./useEditState.js";
|
|
7
|
+
export { useEditSessionState } from "./useEditSessionState.js";
|
|
5
8
|
export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
|
|
6
9
|
export { EditButtons } from "./EditButtons.js";
|
|
7
10
|
export { EditField } from "./edit-field/EditField.js";
|
|
@@ -14,3 +17,9 @@ export { useEditable } from "./useEditable.js";
|
|
|
14
17
|
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
|
|
15
18
|
export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
|
|
16
19
|
export { useEditableTable } from "./useEditableTable.js";
|
|
20
|
+
export { DEFAULT_READ_ONLY_COLUMNS, UNDO_COLUMN, getEditableColumns, narrowEditable, useEditableColumns } from "./useEditableColumns.js";
|
|
21
|
+
export { getDataRowValues, useEditForm } from "./useEditForm.js";
|
|
22
|
+
export { useLookupOptions } from "./lookup-values/useLookupOptions.js";
|
|
23
|
+
export { useCustomEditField } from "./useCustomEditField.js";
|
|
24
|
+
export { useConfirmDiscard } from "./useConfirmDiscard.js";
|
|
25
|
+
export { getFormFields } from "./form-fields.js";
|