@vuu-ui/vuu-data-editing 4.0.0-alpha.2 → 4.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/package.json +6 -6
- package/src/EditButtons.js +2 -2
- package/src/EditSession.js +13 -4
- package/src/edit-field/EditField.js +75 -0
- package/src/edit-field/useEditField.js +203 -0
- package/src/index.js +6 -3
- package/src/useEditable.js +52 -0
- package/types/EditButtons.d.ts +1 -1
- package/types/EditModeProvider.d.ts +1 -1
- package/types/EditSession.d.ts +8 -1
- package/types/edit-field/EditField.d.ts +12 -0
- package/types/edit-field/useEditField.d.ts +24 -0
- package/types/index.d.ts +11 -8
- package/types/useEditState.d.ts +2 -0
- package/types/useEditable.d.ts +13 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.0.0-alpha.
|
|
2
|
+
"version": "4.0.0-alpha.4",
|
|
3
3
|
"description": "Shared data editing APIs and React components for VUU UI",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"author": "heswell",
|
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@salt-ds/core": "1.67.0",
|
|
15
|
-
"@vuu-ui/vuu-ui-controls": "4.0.0-alpha.
|
|
16
|
-
"@vuu-ui/vuu-utils": "4.0.0-alpha.
|
|
15
|
+
"@vuu-ui/vuu-ui-controls": "4.0.0-alpha.4",
|
|
16
|
+
"@vuu-ui/vuu-utils": "4.0.0-alpha.4"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@vuu-ui/vuu-data-types": "4.0.0-alpha.
|
|
20
|
-
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.
|
|
21
|
-
"@vuu-ui/vuu-table-types": "4.0.0-alpha.
|
|
19
|
+
"@vuu-ui/vuu-data-types": "4.0.0-alpha.4",
|
|
20
|
+
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.4",
|
|
21
|
+
"@vuu-ui/vuu-table-types": "4.0.0-alpha.4"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"react": "^19.2.3"
|
package/src/EditButtons.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Button } from "@salt-ds/core";
|
|
3
3
|
import { useCallback, useEffect, useState } from "react";
|
|
4
|
-
const EditButtons = ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection = false, onCancel, onDelete, onSave, saveLabel = "Save" })=>{
|
|
4
|
+
const EditButtons = ({ canCancel, canSave: _canSave, confirmCancel, confirmSave, editSession, hasSelection = false, onCancel, onDelete, onSave, saveLabel = "Save" })=>{
|
|
5
5
|
const [editState, setEditState] = useState(()=>editSession?.editState ?? "clean");
|
|
6
6
|
const handleSave = useCallback(async ()=>{
|
|
7
7
|
if (confirmSave) {
|
|
@@ -42,7 +42,7 @@ const EditButtons = ({ canCancel, canSave, confirmCancel, confirmSave, editSessi
|
|
|
42
42
|
children: "Delete"
|
|
43
43
|
}),
|
|
44
44
|
/*#__PURE__*/ jsx(Button, {
|
|
45
|
-
disabled: !canSave,
|
|
45
|
+
disabled: !editSession?.canSave,
|
|
46
46
|
onClick: handleSave,
|
|
47
47
|
sentiment: "accented",
|
|
48
48
|
children: "stale" === editState ? `${saveLabel} (force)` : saveLabel
|
package/src/EditSession.js
CHANGED
|
@@ -66,6 +66,12 @@ class EditSession extends EventEmitter {
|
|
|
66
66
|
if (this.#isStale) return "stale";
|
|
67
67
|
return 0 === this.#editCount && 0 === this.#deleteCount && 0 === this.#addCount ? "clean" : "dirty";
|
|
68
68
|
}
|
|
69
|
+
get canCancel() {
|
|
70
|
+
return "active" === this.lifecycle.status || "error" === this.lifecycle.status && "end" === this.lifecycle.operation;
|
|
71
|
+
}
|
|
72
|
+
get canSave() {
|
|
73
|
+
return this.canCancel && ("dirty" === this.editState || "stale" === this.editState) && 0 === this.invalidCount;
|
|
74
|
+
}
|
|
69
75
|
#emitEditStateChange(oldState, force = false) {
|
|
70
76
|
const newState = this.editState;
|
|
71
77
|
if (force || newState !== oldState) this.emit("editState", newState);
|
|
@@ -393,7 +399,8 @@ class EditSession extends EventEmitter {
|
|
|
393
399
|
sessionDataSource
|
|
394
400
|
});
|
|
395
401
|
try {
|
|
396
|
-
await this.dataSource?.endEditSession?.(saveChanges, force);
|
|
402
|
+
const response = await this.dataSource?.endEditSession?.(saveChanges, force);
|
|
403
|
+
if (isRpcError(response)) throw new Error(response.errorMessage);
|
|
397
404
|
this.#clearEdits();
|
|
398
405
|
this.#sessionDataSource = void 0;
|
|
399
406
|
this.#setLifecycle({
|
|
@@ -468,15 +475,17 @@ class EditSession extends EventEmitter {
|
|
|
468
475
|
#isLatestCellCommit(key, columnName, revision) {
|
|
469
476
|
return this.#cellCommitRevisions.get(key)?.get(columnName) === revision;
|
|
470
477
|
}
|
|
471
|
-
async commit(key, columnName, originalValue, typedValue, isValid) {
|
|
478
|
+
async commit(key, columnName, originalValue, typedValue, isValid, { dataSourceValue, syncToDataSource = true } = {}) {
|
|
472
479
|
if ("active" !== this.#lifecycle.status && !("error" === this.#lifecycle.status && "end" === this.#lifecycle.operation)) throw new Error("No edit session in progress");
|
|
473
480
|
const revision = this.#setCellCommitRevision(key, columnName);
|
|
474
481
|
const rowEditDetails = this.#getOrCreateRowEdits(key);
|
|
475
482
|
const { cellEdits } = rowEditDetails;
|
|
476
483
|
if (isValid) {
|
|
477
484
|
const cellEdit = this.#storeCellEdit(key, cellEdits, columnName, originalValue, typedValue, isValid);
|
|
478
|
-
if (this.dataSource?.editCell) {
|
|
479
|
-
const
|
|
485
|
+
if (syncToDataSource && this.dataSource?.editCell) {
|
|
486
|
+
const valueForDataSource = dataSourceValue ?? typedValue;
|
|
487
|
+
if ("object" == typeof valueForDataSource && null !== valueForDataSource) throw new Error("Custom edit values require a scalar dataSourceValue.");
|
|
488
|
+
const response = await this.dataSource.editCell(key, columnName, valueForDataSource);
|
|
480
489
|
if (!this.inEditMode) return {
|
|
481
490
|
data: void 0,
|
|
482
491
|
type: "SUCCESS_RESULT"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Checkbox, FormField, FormFieldLabel, Input } from "@salt-ds/core";
|
|
3
|
+
import { useCallback } from "react";
|
|
4
|
+
import { useEditField } from "./useEditField.js";
|
|
5
|
+
import { useEditSession } from "../DataEditingProvider.js";
|
|
6
|
+
import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils/src/data-utils";
|
|
7
|
+
import { useEditMode } from "../EditModeProvider.js";
|
|
8
|
+
const EditField = ({ dataRow, label, name, readOnly = false, required, type })=>{
|
|
9
|
+
const editSession = useEditSession();
|
|
10
|
+
const { isEditMode } = useEditMode();
|
|
11
|
+
const onEdit = useCallback(async (editState, editPhase)=>{
|
|
12
|
+
const { isValid = true, previousValue = "", value } = editState;
|
|
13
|
+
if ("commit" === editPhase && editSession) {
|
|
14
|
+
if (editSession.isNewRow(dataRow.key)) {
|
|
15
|
+
const isEmptyValue = "string" == typeof value && "" === value.trim();
|
|
16
|
+
if (!isValid && !isEmptyValue) return {
|
|
17
|
+
errorMessage: "Invalid value",
|
|
18
|
+
type: "ERROR_RESULT"
|
|
19
|
+
};
|
|
20
|
+
editSession.setNewRowValue(name, value);
|
|
21
|
+
if (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete()) return editSession.addNewRow();
|
|
22
|
+
return {
|
|
23
|
+
data: void 0,
|
|
24
|
+
type: "SUCCESS_RESULT"
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return editSession.commit(dataRow.key, name, previousValue, value, isValid);
|
|
28
|
+
}
|
|
29
|
+
}, [
|
|
30
|
+
dataRow,
|
|
31
|
+
editSession,
|
|
32
|
+
name
|
|
33
|
+
]);
|
|
34
|
+
const onCommit = useCallback(()=>{
|
|
35
|
+
console.log("onCommit");
|
|
36
|
+
}, []);
|
|
37
|
+
const necessity = required ? "asterisk" : void 0;
|
|
38
|
+
const dataValue = dataRow?.[name] ?? "";
|
|
39
|
+
const column = {
|
|
40
|
+
label,
|
|
41
|
+
name,
|
|
42
|
+
serverDataType: "string"
|
|
43
|
+
};
|
|
44
|
+
const { editing, inputProps, warningMessage, previousValue = "", ...editProps } = useEditField({
|
|
45
|
+
column,
|
|
46
|
+
textType: "checkbox" !== type ? type : void 0,
|
|
47
|
+
onEdit,
|
|
48
|
+
type: dataDescriptorTypeToVuuRowDataItemType(column),
|
|
49
|
+
value: dataValue
|
|
50
|
+
});
|
|
51
|
+
return /*#__PURE__*/ jsxs(FormField, {
|
|
52
|
+
"data-field": name,
|
|
53
|
+
necessity: necessity,
|
|
54
|
+
children: [
|
|
55
|
+
/*#__PURE__*/ jsx(FormFieldLabel, {
|
|
56
|
+
children: label
|
|
57
|
+
}),
|
|
58
|
+
false === isEditMode ? /*#__PURE__*/ jsx(Input, {
|
|
59
|
+
bordered: true,
|
|
60
|
+
inputProps: {
|
|
61
|
+
readOnly: true
|
|
62
|
+
},
|
|
63
|
+
value: String(dataValue)
|
|
64
|
+
}) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
|
|
65
|
+
checked: Boolean(dataValue),
|
|
66
|
+
onChange: onCommit
|
|
67
|
+
}) : /*#__PURE__*/ jsx(Input, {
|
|
68
|
+
...editProps,
|
|
69
|
+
bordered: true,
|
|
70
|
+
readOnly: readOnly
|
|
71
|
+
})
|
|
72
|
+
]
|
|
73
|
+
});
|
|
74
|
+
};
|
|
75
|
+
export { EditField };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { DataValidationError, dispatchCustomEvent, getTypedValue, isRpcError, isRpcSuccess } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
const stringValueOf = (value)=>value?.toString() ?? "";
|
|
4
|
+
const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
|
|
5
|
+
const [editState, setEditState] = useState({
|
|
6
|
+
editing: false,
|
|
7
|
+
value: stringValueOf(value)
|
|
8
|
+
});
|
|
9
|
+
const initialValueRef = useRef(value?.toString() ?? "");
|
|
10
|
+
const isDirtyRef = useRef(false);
|
|
11
|
+
useEffect(()=>{
|
|
12
|
+
const nextValue = stringValueOf(value);
|
|
13
|
+
if (initialValueRef.current !== nextValue) {
|
|
14
|
+
initialValueRef.current = nextValue;
|
|
15
|
+
isDirtyRef.current = false;
|
|
16
|
+
setEditState((editState)=>({
|
|
17
|
+
...editState,
|
|
18
|
+
message: void 0,
|
|
19
|
+
value: nextValue
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
}, [
|
|
23
|
+
value
|
|
24
|
+
]);
|
|
25
|
+
const commit = useCallback(async ()=>{
|
|
26
|
+
const { value } = editState;
|
|
27
|
+
const result = column.clientSideEditValidationCheck?.(value, "*");
|
|
28
|
+
if (result?.ok === false) setEditState((state)=>({
|
|
29
|
+
...state,
|
|
30
|
+
message: result?.messages?.join(",")
|
|
31
|
+
}));
|
|
32
|
+
else {
|
|
33
|
+
const { current: initialValue } = initialValueRef;
|
|
34
|
+
const previousValue = getTypedValue(initialValue, type);
|
|
35
|
+
try {
|
|
36
|
+
const typedValue = getTypedValue(value, type, true);
|
|
37
|
+
const response = await onEdit?.({
|
|
38
|
+
editType: "commit",
|
|
39
|
+
isValid: true,
|
|
40
|
+
previousValue,
|
|
41
|
+
value: typedValue
|
|
42
|
+
}, "commit");
|
|
43
|
+
if (isRpcSuccess(response)) {
|
|
44
|
+
isDirtyRef.current = false;
|
|
45
|
+
setEditState(({ previousValue, value })=>({
|
|
46
|
+
editing: false,
|
|
47
|
+
previousValue: previousValue === value ? void 0 : void 0 === previousValue ? initialValue : previousValue,
|
|
48
|
+
value
|
|
49
|
+
}));
|
|
50
|
+
} else if (isRpcError(response)) {
|
|
51
|
+
setEditState((state)=>({
|
|
52
|
+
...state,
|
|
53
|
+
message: response.errorMessage
|
|
54
|
+
}));
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
initialValueRef.current = value;
|
|
58
|
+
return true;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
if (e instanceof DataValidationError) {
|
|
61
|
+
const { actualType, expectedType, message } = e;
|
|
62
|
+
if (column) {
|
|
63
|
+
const { name, label = name } = column;
|
|
64
|
+
const message = `${label} is a ${expectedType} value, data entered is ${actualType} `;
|
|
65
|
+
setEditState((state)=>({
|
|
66
|
+
...state,
|
|
67
|
+
message
|
|
68
|
+
}));
|
|
69
|
+
onEdit?.({
|
|
70
|
+
editType: "commit",
|
|
71
|
+
isValid: false,
|
|
72
|
+
previousValue,
|
|
73
|
+
value
|
|
74
|
+
}, "commit");
|
|
75
|
+
} else setEditState((state)=>({
|
|
76
|
+
...state,
|
|
77
|
+
message
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}, [
|
|
84
|
+
column,
|
|
85
|
+
editState,
|
|
86
|
+
onEdit,
|
|
87
|
+
type
|
|
88
|
+
]);
|
|
89
|
+
const toggleActivation = useCallback(async (input, cancel = false)=>{
|
|
90
|
+
if (editState.editing) if (isDirtyRef.current) if (cancel) {
|
|
91
|
+
const { value: previousValue } = editState;
|
|
92
|
+
isDirtyRef.current = false;
|
|
93
|
+
setEditState({
|
|
94
|
+
editing: false,
|
|
95
|
+
value: initialValueRef.current,
|
|
96
|
+
message: void 0
|
|
97
|
+
});
|
|
98
|
+
onEdit?.({
|
|
99
|
+
editType: "cancel",
|
|
100
|
+
isValid: true,
|
|
101
|
+
previousValue,
|
|
102
|
+
value: getTypedValue(initialValueRef.current, type, true)
|
|
103
|
+
}, "commit");
|
|
104
|
+
} else {
|
|
105
|
+
const commitSuccessful = await commit();
|
|
106
|
+
if (commitSuccessful) {
|
|
107
|
+
setEditState((editState)=>({
|
|
108
|
+
...editState,
|
|
109
|
+
editing: false
|
|
110
|
+
}));
|
|
111
|
+
dispatchCustomEvent(input, "vuu-commit");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
else setEditState((editState)=>({
|
|
115
|
+
...editState,
|
|
116
|
+
editing: false
|
|
117
|
+
}));
|
|
118
|
+
else if (!cancel) {
|
|
119
|
+
setEditState((editState)=>({
|
|
120
|
+
...editState,
|
|
121
|
+
editing: true
|
|
122
|
+
}));
|
|
123
|
+
input.select();
|
|
124
|
+
}
|
|
125
|
+
}, [
|
|
126
|
+
commit,
|
|
127
|
+
editState,
|
|
128
|
+
onEdit,
|
|
129
|
+
type
|
|
130
|
+
]);
|
|
131
|
+
const handleKeyDown = useCallback(async (evt)=>{
|
|
132
|
+
const { key, target } = evt;
|
|
133
|
+
const input = target;
|
|
134
|
+
if ("Enter" === key) toggleActivation(input);
|
|
135
|
+
else if ("ArrowRight" === key || "ArrowLeft" === key || "ArrowUp" === key || "ArrowDown" === key) {
|
|
136
|
+
if (editState.editing) evt.stopPropagation();
|
|
137
|
+
} else if ("Escape" === evt.key) toggleActivation(input, true);
|
|
138
|
+
}, [
|
|
139
|
+
editState,
|
|
140
|
+
toggleActivation
|
|
141
|
+
]);
|
|
142
|
+
const handleFocus = useCallback((e)=>{
|
|
143
|
+
const input = e.target;
|
|
144
|
+
const source = e.relatedTarget;
|
|
145
|
+
if (source?.classList.contains("vuuTableCell") && source.contains(input)) toggleActivation(input);
|
|
146
|
+
}, [
|
|
147
|
+
toggleActivation
|
|
148
|
+
]);
|
|
149
|
+
const handleBlur = useCallback(async ()=>{
|
|
150
|
+
if (editState.editing) {
|
|
151
|
+
if (isDirtyRef.current) {
|
|
152
|
+
const commitSuccessful = await commit();
|
|
153
|
+
console.log({
|
|
154
|
+
commitSuccessful
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
setEditState((editState)=>({
|
|
158
|
+
...editState,
|
|
159
|
+
editing: false
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
}, [
|
|
163
|
+
commit,
|
|
164
|
+
editState
|
|
165
|
+
]);
|
|
166
|
+
const handleChange = useCallback((evt)=>{
|
|
167
|
+
const { value } = evt.target;
|
|
168
|
+
isDirtyRef.current = value !== initialValueRef.current;
|
|
169
|
+
const result = column.clientSideEditValidationCheck?.(value, "change");
|
|
170
|
+
setEditState({
|
|
171
|
+
editing: true,
|
|
172
|
+
value
|
|
173
|
+
});
|
|
174
|
+
onEdit?.({
|
|
175
|
+
editType: "change",
|
|
176
|
+
isValid: result?.ok !== false,
|
|
177
|
+
previousValue: initialValueRef.current,
|
|
178
|
+
value
|
|
179
|
+
}, "change");
|
|
180
|
+
if (result?.ok === false) setEditState({
|
|
181
|
+
editing: true,
|
|
182
|
+
value,
|
|
183
|
+
message: result.messages?.join(",")
|
|
184
|
+
});
|
|
185
|
+
}, [
|
|
186
|
+
column,
|
|
187
|
+
onEdit
|
|
188
|
+
]);
|
|
189
|
+
return {
|
|
190
|
+
editing: editState.editing,
|
|
191
|
+
inputProps: {
|
|
192
|
+
onBlur: handleBlur,
|
|
193
|
+
onFocus: handleFocus,
|
|
194
|
+
type: textType
|
|
195
|
+
},
|
|
196
|
+
onChange: handleChange,
|
|
197
|
+
onKeyDown: handleKeyDown,
|
|
198
|
+
previousValue: editState.previousValue,
|
|
199
|
+
value: editState.value,
|
|
200
|
+
warningMessage: editState.message
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
export { useEditField };
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
1
2
|
export { DataEditingProvider, useEditSession } from "./DataEditingProvider.js";
|
|
2
|
-
export { useCellEdited } from "./useCellEdited.js";
|
|
3
3
|
export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
|
|
4
|
+
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
|
|
4
5
|
export { EditButtons } from "./EditButtons.js";
|
|
6
|
+
export { EditField } from "./edit-field/EditField.js";
|
|
7
|
+
export { useEditField } from "./edit-field/useEditField.js";
|
|
5
8
|
export { EditModeProvider, useEditMode } from "./EditModeProvider.js";
|
|
6
9
|
export { EditError, EditSession, SupersededEditError } from "./EditSession.js";
|
|
7
|
-
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
8
|
-
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
|
|
9
10
|
export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
|
|
11
|
+
export { useCellEdited } from "./useCellEdited.js";
|
|
12
|
+
export { useEditable } from "./useEditable.js";
|
|
10
13
|
export { useEditableTable } from "./useEditableTable.js";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { EditSession, useEditMode } from "./index.js";
|
|
2
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
3
|
+
const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=>{
|
|
4
|
+
const { isEditMode } = useEditMode();
|
|
5
|
+
const editSession = useMemo(()=>new EditSession({
|
|
6
|
+
dataSource
|
|
7
|
+
}), [
|
|
8
|
+
dataSource
|
|
9
|
+
]);
|
|
10
|
+
const handleCancel = useCallback(async ()=>{
|
|
11
|
+
try {
|
|
12
|
+
await editSession.end();
|
|
13
|
+
onCancel();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
console.error("[useEditableTable] cancel edit session failed", error);
|
|
16
|
+
}
|
|
17
|
+
}, [
|
|
18
|
+
editSession,
|
|
19
|
+
onCancel
|
|
20
|
+
]);
|
|
21
|
+
const handleSave = useCallback(async (force = false)=>{
|
|
22
|
+
try {
|
|
23
|
+
await editSession.end(true, force);
|
|
24
|
+
onSave();
|
|
25
|
+
} catch (error) {
|
|
26
|
+
console.error("[useEditableTable] save edit session failed", error);
|
|
27
|
+
}
|
|
28
|
+
}, [
|
|
29
|
+
editSession,
|
|
30
|
+
onSave
|
|
31
|
+
]);
|
|
32
|
+
useEffect(()=>{
|
|
33
|
+
const transition = isEditMode ? editSession.begin(copyOption) : editSession.end();
|
|
34
|
+
transition.catch((error)=>{
|
|
35
|
+
if (isEditMode) {
|
|
36
|
+
console.error("[useEditableTable] begin edit session failed", error);
|
|
37
|
+
handleCancel();
|
|
38
|
+
} else console.error("[useEditableTable] end edit session failed", error);
|
|
39
|
+
});
|
|
40
|
+
}, [
|
|
41
|
+
copyOption,
|
|
42
|
+
editSession,
|
|
43
|
+
isEditMode,
|
|
44
|
+
handleCancel
|
|
45
|
+
]);
|
|
46
|
+
return {
|
|
47
|
+
editSession,
|
|
48
|
+
onCancel: handleCancel,
|
|
49
|
+
onSave: handleSave
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
export { useEditable };
|
package/types/EditButtons.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export interface EditButtonProps {
|
|
|
11
11
|
confirmSave?: () => boolean | Promise<boolean>;
|
|
12
12
|
confirmCancel?: () => boolean | Promise<boolean>;
|
|
13
13
|
}
|
|
14
|
-
export declare const EditButtons: ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
|
|
14
|
+
export declare const EditButtons: ({ canCancel, canSave: _canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
|
package/types/EditSession.d.ts
CHANGED
|
@@ -3,6 +3,11 @@ import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
|
|
|
3
3
|
import { EventEmitter } from "@vuu-ui/vuu-utils";
|
|
4
4
|
export type EditState = "clean" | "dirty" | "invalid" | "stale";
|
|
5
5
|
export type EditActionType = "deleteRow" | "addRow" | "editCell";
|
|
6
|
+
export type EditSessionValue = VuuRowDataItemType | object;
|
|
7
|
+
export type EditCommitOptions = {
|
|
8
|
+
dataSourceValue?: VuuRowDataItemType;
|
|
9
|
+
syncToDataSource?: boolean;
|
|
10
|
+
};
|
|
6
11
|
/** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
|
|
7
12
|
export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
|
|
8
13
|
export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
|
|
@@ -58,6 +63,8 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
|
|
|
58
63
|
get deleteCount(): number;
|
|
59
64
|
get addCount(): number;
|
|
60
65
|
get editState(): EditState;
|
|
66
|
+
get canCancel(): boolean;
|
|
67
|
+
get canSave(): boolean;
|
|
61
68
|
get newRowState(): NewRowState;
|
|
62
69
|
isNewRow(key: string): key is "__vuu_new_row__";
|
|
63
70
|
isNewRowFinalColumn(columnName: string): boolean;
|
|
@@ -81,6 +88,6 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
|
|
|
81
88
|
end(saveChanges?: boolean, force?: boolean): Promise<void>;
|
|
82
89
|
get lifecycle(): EditLifecycle;
|
|
83
90
|
get inEditMode(): boolean;
|
|
84
|
-
commit(key: string, columnName: string, originalValue:
|
|
91
|
+
commit(key: string, columnName: string, originalValue: EditSessionValue, typedValue: EditSessionValue, isValid: boolean, { dataSourceValue, syncToDataSource }?: EditCommitOptions): Promise<RpcResult>;
|
|
85
92
|
}
|
|
86
93
|
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { DataRow } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
export type TextFieldType = "email" | "password";
|
|
3
|
+
export type EditFieldType = "checkbox" | TextFieldType;
|
|
4
|
+
export interface EditFieldProps {
|
|
5
|
+
dataRow: DataRow;
|
|
6
|
+
label: string;
|
|
7
|
+
name: string;
|
|
8
|
+
readOnly?: boolean;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
type?: EditFieldType;
|
|
11
|
+
}
|
|
12
|
+
export declare const EditField: ({ dataRow, label, name, readOnly, required, type, }: EditFieldProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
|
|
2
|
+
import type { RuntimeColumnDescriptor, TableCellEditHandler } from "@vuu-ui/vuu-table-types";
|
|
3
|
+
import { type ChangeEventHandler, type FocusEventHandler, type KeyboardEvent } from "react";
|
|
4
|
+
import type { TextFieldType } from "./EditField";
|
|
5
|
+
export interface EditFieldHookProps<T extends VuuRowDataItemType = VuuRowDataItemType> {
|
|
6
|
+
column: Pick<RuntimeColumnDescriptor, "clientSideEditValidationCheck" | "label" | "name">;
|
|
7
|
+
value?: T;
|
|
8
|
+
onEdit?: TableCellEditHandler;
|
|
9
|
+
textType?: TextFieldType;
|
|
10
|
+
type?: "string" | "number" | "boolean";
|
|
11
|
+
}
|
|
12
|
+
export declare const useEditField: <T extends string | number | boolean = string>({ column, value, onEdit, textType, type, }: EditFieldHookProps<T>) => {
|
|
13
|
+
editing: boolean;
|
|
14
|
+
inputProps: {
|
|
15
|
+
onBlur: FocusEventHandler<HTMLElement>;
|
|
16
|
+
onFocus: FocusEventHandler<HTMLElement>;
|
|
17
|
+
type: TextFieldType | undefined;
|
|
18
|
+
};
|
|
19
|
+
onChange: ChangeEventHandler;
|
|
20
|
+
onKeyDown: (evt: KeyboardEvent<HTMLElement>) => Promise<void>;
|
|
21
|
+
previousValue: string | undefined;
|
|
22
|
+
value: string;
|
|
23
|
+
warningMessage: string | undefined;
|
|
24
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
1
2
|
export { DataEditingProvider, useEditSession } from "./DataEditingProvider";
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
3
|
+
export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils";
|
|
4
|
+
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator";
|
|
4
5
|
export { EditButtons, type EditButtonProps } from "./EditButtons";
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
8
|
-
export {
|
|
9
|
-
export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps
|
|
10
|
-
export {
|
|
6
|
+
export { EditField } from './edit-field/EditField';
|
|
7
|
+
export { useEditField } from './edit-field/useEditField';
|
|
8
|
+
export { EditModeProvider, useEditMode, type EditModeContextProps } from "./EditModeProvider";
|
|
9
|
+
export { EditError, EditSession, SupersededEditError, type EditActionType, type EditLifecycle, type EditSessionConstructorProps, type EditState, type NewRowState, type RowDefaultDataItemValues } from "./EditSession";
|
|
10
|
+
export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps } from "./UndoCellRenderer";
|
|
11
|
+
export { useCellEdited } from "./useCellEdited";
|
|
12
|
+
export { useEditable } from './useEditable';
|
|
13
|
+
export { useEditableTable, type EditableTableHookProps, type EditMode } from "./useEditableTable";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CopyOption, DataSource } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import { EditSession } from ".";
|
|
3
|
+
export interface EditableHookProps {
|
|
4
|
+
copyOption?: CopyOption;
|
|
5
|
+
dataSource: DataSource;
|
|
6
|
+
onCancel: () => void;
|
|
7
|
+
onSave: () => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const useEditable: ({ copyOption, dataSource, onCancel, onSave }: EditableHookProps) => {
|
|
10
|
+
editSession: EditSession;
|
|
11
|
+
onCancel: () => Promise<void>;
|
|
12
|
+
onSave: (force?: boolean) => Promise<void>;
|
|
13
|
+
};
|