@vuu-ui/vuu-data-editing 5.0.0-alpha.2 → 5.0.0-alpha.3
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 +36 -0
- 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 +37 -6
- 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 +101 -19
- package/src/edit-field/useEditField.js +1 -0
- 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 +21 -8
- 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 +15 -0
- 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 +1 -0
- 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,77 @@
|
|
|
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
56
|
const { isValid = true, previousValue = "", value } = editState;
|
|
20
|
-
if ("commit" === editPhase &&
|
|
21
|
-
if (editSession
|
|
57
|
+
if ("commit" === editPhase && tableEditSession) {
|
|
58
|
+
if (editSession?.isNewRow(dataRow.key)) {
|
|
22
59
|
const isEmptyValue = "string" == typeof value && "" === value.trim();
|
|
23
60
|
if (!isValid && !isEmptyValue) return {
|
|
24
61
|
errorMessage: "Invalid value",
|
|
25
62
|
type: "ERROR_RESULT"
|
|
26
63
|
};
|
|
27
|
-
|
|
64
|
+
setNewRowValue(name, value);
|
|
28
65
|
if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
|
|
29
66
|
return {
|
|
30
67
|
data: void 0,
|
|
31
68
|
type: "SUCCESS_RESULT"
|
|
32
69
|
};
|
|
33
70
|
}
|
|
34
|
-
return
|
|
71
|
+
return tableEditSession.commit(dataRow.key, name, previousValue, value, isValid);
|
|
35
72
|
}
|
|
36
73
|
if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
|
|
37
|
-
|
|
74
|
+
setNewRowValue(name, value);
|
|
38
75
|
return {
|
|
39
76
|
data: void 0,
|
|
40
77
|
type: "SUCCESS_RESULT"
|
|
@@ -44,29 +81,52 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
|
|
|
44
81
|
dataRow,
|
|
45
82
|
deferNewRow,
|
|
46
83
|
editSession,
|
|
47
|
-
name
|
|
84
|
+
name,
|
|
85
|
+
setNewRowValue,
|
|
86
|
+
tableEditSession
|
|
48
87
|
]);
|
|
49
|
-
const onCommit = useCallback(()=>{
|
|
50
|
-
console.log("onCommit");
|
|
51
|
-
}, []);
|
|
52
88
|
const necessity = required ? "asterisk" : void 0;
|
|
53
89
|
const dataValue = dataRow?.[name] ?? "";
|
|
90
|
+
const [checked, setChecked] = useState(Boolean(dataValue));
|
|
91
|
+
useEffect(()=>{
|
|
92
|
+
setChecked(Boolean(dataValue));
|
|
93
|
+
}, [
|
|
94
|
+
dataValue
|
|
95
|
+
]);
|
|
96
|
+
const handleCheckboxChange = useCallback(async (evt)=>{
|
|
97
|
+
const previousValue = checked;
|
|
98
|
+
const value = evt.target.checked;
|
|
99
|
+
setChecked(value);
|
|
100
|
+
const response = await onEdit({
|
|
101
|
+
editType: "commit",
|
|
102
|
+
isValid: true,
|
|
103
|
+
previousValue,
|
|
104
|
+
value
|
|
105
|
+
}, "commit");
|
|
106
|
+
if (response?.type === "ERROR_RESULT") setChecked(previousValue);
|
|
107
|
+
}, [
|
|
108
|
+
checked,
|
|
109
|
+
onEdit
|
|
110
|
+
]);
|
|
54
111
|
const column = {
|
|
112
|
+
clientSideEditValidationCheck,
|
|
55
113
|
label,
|
|
56
114
|
name,
|
|
57
|
-
serverDataType: "string"
|
|
115
|
+
serverDataType: "checkbox" === type && "string" === serverDataType ? "boolean" : serverDataType
|
|
58
116
|
};
|
|
59
|
-
const { inputProps, onChange, onDropdownSelectionChange, onKeyDown, value } = useEditField({
|
|
117
|
+
const { commit, inputProps, onChange, onDropdownSelectionChange, onKeyDown, value, warningMessage: fieldMessage } = useEditField({
|
|
60
118
|
column,
|
|
61
119
|
textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
|
|
62
120
|
onEdit,
|
|
63
121
|
type: dataDescriptorTypeToVuuRowDataItemType(column),
|
|
64
122
|
value: dataValue
|
|
65
123
|
});
|
|
124
|
+
const warningMessage = isEditMode ? fieldMessage ?? (formFieldErrors ? formFieldErrors[name] : newRowState.errors[name]) : void 0;
|
|
66
125
|
return /*#__PURE__*/ jsxs(FormField, {
|
|
67
126
|
className: classBase,
|
|
68
127
|
"data-field": name,
|
|
69
128
|
necessity: necessity,
|
|
129
|
+
validationStatus: warningMessage ? "error" : void 0,
|
|
70
130
|
children: [
|
|
71
131
|
/*#__PURE__*/ jsx(FormFieldLabel, {
|
|
72
132
|
children: label
|
|
@@ -77,9 +137,28 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
|
|
|
77
137
|
readOnly: true
|
|
78
138
|
},
|
|
79
139
|
value: String(dataValue)
|
|
140
|
+
}) : descriptor ? getDataItemEditControl({
|
|
141
|
+
InputProps: {
|
|
142
|
+
bordered: true,
|
|
143
|
+
inputProps: {
|
|
144
|
+
...inputProps,
|
|
145
|
+
onChange,
|
|
146
|
+
value
|
|
147
|
+
},
|
|
148
|
+
onKeyDown,
|
|
149
|
+
value
|
|
150
|
+
},
|
|
151
|
+
commitOnBlur: true,
|
|
152
|
+
dataDescriptor: descriptor,
|
|
153
|
+
editOperation: isNewRow ? "insert" : "update",
|
|
154
|
+
onCommit: (_evt, committedValue)=>{
|
|
155
|
+
commit(String(committedValue));
|
|
156
|
+
},
|
|
157
|
+
table: tableEditSession instanceof DirectEditSession ? tableEditSession.dataSource.table : editSession?.sourceDataSource?.table
|
|
80
158
|
}) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
|
|
81
|
-
checked:
|
|
82
|
-
|
|
159
|
+
checked: checked,
|
|
160
|
+
disabled: readOnly,
|
|
161
|
+
onChange: handleCheckboxChange
|
|
83
162
|
}) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
|
|
84
163
|
"data-icon": "triangle-down",
|
|
85
164
|
onSelectionChange: onDropdownSelectionChange,
|
|
@@ -98,7 +177,10 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
|
|
|
98
177
|
onKeyDown: onKeyDown,
|
|
99
178
|
readOnly: readOnly,
|
|
100
179
|
value: value
|
|
101
|
-
})
|
|
180
|
+
}),
|
|
181
|
+
warningMessage ? /*#__PURE__*/ jsx(FormFieldHelperText, {
|
|
182
|
+
children: warningMessage
|
|
183
|
+
}) : null
|
|
102
184
|
]
|
|
103
185
|
});
|
|
104
186
|
};
|
|
@@ -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";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Range, useData } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { lookupOptionsFromRows } from "./useLookupValues.js";
|
|
4
|
+
const DATA_ROW_COLUMN_OFFSET = 10;
|
|
5
|
+
const rowToRecord = (columns, row)=>Object.fromEntries(columns.map((column, i)=>[
|
|
6
|
+
column,
|
|
7
|
+
row[i + DATA_ROW_COLUMN_OFFSET]
|
|
8
|
+
]));
|
|
9
|
+
const useLookupOptions = ({ columns: columnsProp, enabled = true, filter, mapRow, maxRows = 100, optionMap, table })=>{
|
|
10
|
+
const { VuuDataSource } = useData();
|
|
11
|
+
const [state, setState] = useState({
|
|
12
|
+
loading: false,
|
|
13
|
+
options: []
|
|
14
|
+
});
|
|
15
|
+
const mapRowRef = useRef(mapRow);
|
|
16
|
+
mapRowRef.current = mapRow;
|
|
17
|
+
const columns = useMemo(()=>{
|
|
18
|
+
if (columnsProp) return [
|
|
19
|
+
...columnsProp
|
|
20
|
+
];
|
|
21
|
+
if (optionMap) return [
|
|
22
|
+
optionMap.value,
|
|
23
|
+
optionMap.label,
|
|
24
|
+
...optionMap.additionalFields ?? []
|
|
25
|
+
];
|
|
26
|
+
return [];
|
|
27
|
+
}, [
|
|
28
|
+
columnsProp,
|
|
29
|
+
optionMap
|
|
30
|
+
]);
|
|
31
|
+
const dataSource = useMemo(()=>enabled && table && columns.length > 0 ? new VuuDataSource({
|
|
32
|
+
columns,
|
|
33
|
+
filterSpec: filter ? {
|
|
34
|
+
filter
|
|
35
|
+
} : void 0,
|
|
36
|
+
table
|
|
37
|
+
}) : void 0, [
|
|
38
|
+
VuuDataSource,
|
|
39
|
+
columns,
|
|
40
|
+
enabled,
|
|
41
|
+
filter,
|
|
42
|
+
table
|
|
43
|
+
]);
|
|
44
|
+
useEffect(()=>{
|
|
45
|
+
if (!dataSource) return void setState({
|
|
46
|
+
loading: false,
|
|
47
|
+
options: []
|
|
48
|
+
});
|
|
49
|
+
if (!mapRowRef.current && !optionMap) return void setState({
|
|
50
|
+
error: new Error("[useLookupOptions] either optionMap or mapRow must be provided"),
|
|
51
|
+
loading: false,
|
|
52
|
+
options: []
|
|
53
|
+
});
|
|
54
|
+
setState({
|
|
55
|
+
loading: true,
|
|
56
|
+
options: []
|
|
57
|
+
});
|
|
58
|
+
let active = true;
|
|
59
|
+
let subscribedColumns;
|
|
60
|
+
const fail = (error)=>{
|
|
61
|
+
if (active) setState({
|
|
62
|
+
error,
|
|
63
|
+
loading: false,
|
|
64
|
+
options: []
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
const subscribe = (message)=>{
|
|
68
|
+
if (!active) return;
|
|
69
|
+
if ("subscribed" === message.type) subscribedColumns = message.columns;
|
|
70
|
+
else if ("viewport-update" === message.type && subscribedColumns) {
|
|
71
|
+
if (void 0 === message.rows) {
|
|
72
|
+
if (0 === message.size) setState({
|
|
73
|
+
loading: false,
|
|
74
|
+
options: []
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const rows = message.rows.slice(0, maxRows);
|
|
80
|
+
const mapper = mapRowRef.current;
|
|
81
|
+
const options = mapper ? rows.map((row)=>mapper(rowToRecord(subscribedColumns, row))) : lookupOptionsFromRows(subscribedColumns, rows, optionMap);
|
|
82
|
+
setState({
|
|
83
|
+
loading: false,
|
|
84
|
+
options
|
|
85
|
+
});
|
|
86
|
+
} catch (cause) {
|
|
87
|
+
fail(cause instanceof Error ? cause : new Error(String(cause)));
|
|
88
|
+
}
|
|
89
|
+
} else if ("subscribe-failed" === message.type) fail(new Error(`[useLookupOptions] subscription to ${table?.table} failed: ${message.msg}`));
|
|
90
|
+
};
|
|
91
|
+
dataSource.subscribe({
|
|
92
|
+
range: Range(0, maxRows)
|
|
93
|
+
}, subscribe).catch((cause)=>fail(cause instanceof Error ? cause : new Error(String(cause))));
|
|
94
|
+
return ()=>{
|
|
95
|
+
active = false;
|
|
96
|
+
dataSource.unsubscribe();
|
|
97
|
+
};
|
|
98
|
+
}, [
|
|
99
|
+
dataSource,
|
|
100
|
+
maxRows,
|
|
101
|
+
optionMap,
|
|
102
|
+
table
|
|
103
|
+
]);
|
|
104
|
+
return state;
|
|
105
|
+
};
|
|
106
|
+
export { useLookupOptions };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useModal } from "@vuu-ui/core";
|
|
2
|
+
import { createElement, useCallback } from "react";
|
|
3
|
+
const useConfirmDiscard = ({ cancelButtonLabel = "Keep editing", confirmButtonLabel = "Discard", message = "You have unsaved changes. Discard them?", title = "Discard changes?" } = {})=>{
|
|
4
|
+
const { closePrompt, showPrompt } = useModal();
|
|
5
|
+
return useCallback(()=>new Promise((resolve)=>{
|
|
6
|
+
let resolved = false;
|
|
7
|
+
const settle = (confirmed)=>{
|
|
8
|
+
if (!resolved) {
|
|
9
|
+
resolved = true;
|
|
10
|
+
resolve(confirmed);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
showPrompt(createElement("span", null, message), {
|
|
14
|
+
cancelButtonLabel,
|
|
15
|
+
confirmButtonLabel,
|
|
16
|
+
initialFocusedItem: "cancel",
|
|
17
|
+
onCancel: ()=>settle(false),
|
|
18
|
+
onConfirm: ()=>settle(true),
|
|
19
|
+
onOpenChange: (open)=>{
|
|
20
|
+
if (!open) settle(false);
|
|
21
|
+
},
|
|
22
|
+
status: "warning",
|
|
23
|
+
title
|
|
24
|
+
});
|
|
25
|
+
}).finally(closePrompt), [
|
|
26
|
+
cancelButtonLabel,
|
|
27
|
+
closePrompt,
|
|
28
|
+
confirmButtonLabel,
|
|
29
|
+
message,
|
|
30
|
+
showPrompt,
|
|
31
|
+
title
|
|
32
|
+
]);
|
|
33
|
+
};
|
|
34
|
+
export { useConfirmDiscard };
|