@vuu-ui/vuu-data-editing 3.5.0-beta.4 → 3.5.1
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/EditModeProvider.js +8 -3
- package/src/EditSession.js +11 -3
- package/src/edit-field/EditField.css.js +13 -0
- package/src/edit-field/EditField.js +105 -0
- package/src/edit-field/useEditField.js +218 -0
- package/src/index.js +4 -0
- package/src/lookup-values/useLookupValues.js +78 -0
- package/src/useEditable.js +53 -0
- package/types/EditModeProvider.d.ts +3 -2
- package/types/EditSession.d.ts +8 -1
- package/types/edit-field/EditField.d.ts +21 -0
- package/types/edit-field/useEditField.d.ts +26 -0
- package/types/index.d.ts +5 -1
- package/types/lookup-values/useLookupValues.d.ts +19 -0
- package/types/src/DataEditingProvider.d.ts +8 -0
- package/types/src/EditButtons.d.ts +14 -0
- package/types/src/EditModeProvider.d.ts +13 -0
- package/types/src/EditSession.d.ts +86 -0
- package/types/src/UndoCellRenderer.d.ts +12 -0
- package/types/src/edit-utils.d.ts +5 -0
- package/types/src/editActionRowClassNameGenerator.d.ts +3 -0
- package/types/src/index.d.ts +10 -0
- package/types/src/useCellEdited.d.ts +2 -0
- package/types/src/useEditableTable.d.ts +44 -0
- package/types/useEditable.d.ts +13 -0
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.5.
|
|
2
|
+
"version": "3.5.1",
|
|
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.54.1",
|
|
15
|
-
"@vuu-ui/vuu-ui-controls": "3.5.
|
|
16
|
-
"@vuu-ui/vuu-utils": "3.5.
|
|
15
|
+
"@vuu-ui/vuu-ui-controls": "3.5.1",
|
|
16
|
+
"@vuu-ui/vuu-utils": "3.5.1"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@vuu-ui/vuu-data-types": "3.5.
|
|
20
|
-
"@vuu-ui/vuu-protocol-types": "3.5.
|
|
21
|
-
"@vuu-ui/vuu-table-types": "3.5.
|
|
19
|
+
"@vuu-ui/vuu-data-types": "3.5.1",
|
|
20
|
+
"@vuu-ui/vuu-protocol-types": "3.5.1",
|
|
21
|
+
"@vuu-ui/vuu-table-types": "3.5.1"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"react": "^19.2.3"
|
package/src/EditModeProvider.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { createContext, useContext, useState } from "react";
|
|
2
|
+
import { createContext, useContext, useEffect, useState } from "react";
|
|
3
3
|
const EditModeContext = /*#__PURE__*/ createContext({
|
|
4
4
|
isEditMode: false,
|
|
5
5
|
setEditMode: ()=>"EditModeProvider in place"
|
|
6
6
|
});
|
|
7
|
-
const EditModeProvider = ({ children })=>{
|
|
8
|
-
const [isEditMode, setEditMode] = useState(
|
|
7
|
+
const EditModeProvider = ({ children, isEditMode: isEditModeProp = false })=>{
|
|
8
|
+
const [isEditMode, setEditMode] = useState(isEditModeProp);
|
|
9
|
+
useEffect(()=>{
|
|
10
|
+
setEditMode(isEditModeProp);
|
|
11
|
+
}, [
|
|
12
|
+
isEditModeProp
|
|
13
|
+
]);
|
|
9
14
|
return /*#__PURE__*/ jsx(EditModeContext.Provider, {
|
|
10
15
|
value: {
|
|
11
16
|
isEditMode,
|
package/src/EditSession.js
CHANGED
|
@@ -69,6 +69,12 @@ class EditSession extends EventEmitter {
|
|
|
69
69
|
get isDirty() {
|
|
70
70
|
return "clean" !== this.editState;
|
|
71
71
|
}
|
|
72
|
+
get canCancel() {
|
|
73
|
+
return "active" === this.lifecycle.status || "error" === this.lifecycle.status && "end" === this.lifecycle.operation;
|
|
74
|
+
}
|
|
75
|
+
get canSave() {
|
|
76
|
+
return this.canCancel && ("dirty" === this.editState || "stale" === this.editState) && 0 === this.invalidCount;
|
|
77
|
+
}
|
|
72
78
|
#emitEditStateChange(oldState, force = false) {
|
|
73
79
|
const newState = this.editState;
|
|
74
80
|
if (force || newState !== oldState) this.emit("editState", newState);
|
|
@@ -480,15 +486,17 @@ class EditSession extends EventEmitter {
|
|
|
480
486
|
type: "SUCCESS_RESULT"
|
|
481
487
|
};
|
|
482
488
|
}
|
|
483
|
-
async commit(key, columnName, originalValue, typedValue, isValid) {
|
|
489
|
+
async commit(key, columnName, originalValue, typedValue, isValid, { dataSourceValue, syncToDataSource = true } = {}) {
|
|
484
490
|
if ("active" !== this.#lifecycle.status && !("error" === this.#lifecycle.status && "end" === this.#lifecycle.operation)) throw new Error("No edit session in progress");
|
|
485
491
|
const revision = this.#setCellCommitRevision(key, columnName);
|
|
486
492
|
const rowEditDetails = this.#getOrCreateRowEdits(key);
|
|
487
493
|
const { cellEdits } = rowEditDetails;
|
|
488
494
|
if (isValid) {
|
|
489
495
|
const cellEdit = this.#storeCellEdit(key, cellEdits, columnName, originalValue, typedValue, isValid);
|
|
490
|
-
if (this.dataSource?.editCell) {
|
|
491
|
-
const
|
|
496
|
+
if (syncToDataSource && this.dataSource?.editCell) {
|
|
497
|
+
const valueForDataSource = dataSourceValue ?? typedValue;
|
|
498
|
+
if ("object" == typeof valueForDataSource && null !== valueForDataSource) throw new Error("Custom edit values require a scalar dataSourceValue.");
|
|
499
|
+
const response = await this.dataSource.editCell(key, columnName, valueForDataSource);
|
|
492
500
|
if (!this.inEditMode) return {
|
|
493
501
|
data: void 0,
|
|
494
502
|
type: "SUCCESS_RESULT"
|
|
@@ -0,0 +1,105 @@
|
|
|
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";
|
|
4
|
+
import { useEditField } from "./useEditField.js";
|
|
5
|
+
import { useEditSession } from "../DataEditingProvider.js";
|
|
6
|
+
import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils";
|
|
7
|
+
import { useEditMode } from "../EditModeProvider.js";
|
|
8
|
+
import { useLookupValues } from "../lookup-values/useLookupValues.js";
|
|
9
|
+
import "./EditField.css";
|
|
10
|
+
const classBase = "vuuEditField";
|
|
11
|
+
const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false, required, type, ...options })=>{
|
|
12
|
+
const editSession = useEditSession();
|
|
13
|
+
const { isEditMode } = useEditMode();
|
|
14
|
+
const lookupValues = useLookupValues({
|
|
15
|
+
enabled: "dropdown" === type,
|
|
16
|
+
...options
|
|
17
|
+
});
|
|
18
|
+
const onEdit = useCallback(async (editState, editPhase)=>{
|
|
19
|
+
const { isValid = true, previousValue = "", value } = editState;
|
|
20
|
+
if ("commit" === editPhase && editSession) {
|
|
21
|
+
if (editSession.isNewRow(dataRow.key)) {
|
|
22
|
+
const isEmptyValue = "string" == typeof value && "" === value.trim();
|
|
23
|
+
if (!isValid && !isEmptyValue) return {
|
|
24
|
+
errorMessage: "Invalid value",
|
|
25
|
+
type: "ERROR_RESULT"
|
|
26
|
+
};
|
|
27
|
+
editSession.setNewRowValue(name, value);
|
|
28
|
+
if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
|
|
29
|
+
return {
|
|
30
|
+
data: void 0,
|
|
31
|
+
type: "SUCCESS_RESULT"
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return editSession.commit(dataRow.key, name, previousValue, value, isValid);
|
|
35
|
+
}
|
|
36
|
+
if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
|
|
37
|
+
editSession.setNewRowValue(name, value);
|
|
38
|
+
return {
|
|
39
|
+
data: void 0,
|
|
40
|
+
type: "SUCCESS_RESULT"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}, [
|
|
44
|
+
dataRow,
|
|
45
|
+
deferNewRow,
|
|
46
|
+
editSession,
|
|
47
|
+
name
|
|
48
|
+
]);
|
|
49
|
+
const onCommit = useCallback(()=>{
|
|
50
|
+
console.log("onCommit");
|
|
51
|
+
}, []);
|
|
52
|
+
const necessity = required ? "asterisk" : void 0;
|
|
53
|
+
const dataValue = dataRow?.[name] ?? "";
|
|
54
|
+
const column = {
|
|
55
|
+
label,
|
|
56
|
+
name,
|
|
57
|
+
serverDataType: "string"
|
|
58
|
+
};
|
|
59
|
+
const { inputProps, onChange, onDropdownSelectionChange, onKeyDown, value } = useEditField({
|
|
60
|
+
column,
|
|
61
|
+
textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
|
|
62
|
+
onEdit,
|
|
63
|
+
type: dataDescriptorTypeToVuuRowDataItemType(column),
|
|
64
|
+
value: dataValue
|
|
65
|
+
});
|
|
66
|
+
return /*#__PURE__*/ jsxs(FormField, {
|
|
67
|
+
className: classBase,
|
|
68
|
+
"data-field": name,
|
|
69
|
+
necessity: necessity,
|
|
70
|
+
children: [
|
|
71
|
+
/*#__PURE__*/ jsx(FormFieldLabel, {
|
|
72
|
+
children: label
|
|
73
|
+
}),
|
|
74
|
+
false === isEditMode ? /*#__PURE__*/ jsx(Input, {
|
|
75
|
+
bordered: true,
|
|
76
|
+
inputProps: {
|
|
77
|
+
readOnly: true
|
|
78
|
+
},
|
|
79
|
+
value: String(dataValue)
|
|
80
|
+
}) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
|
|
81
|
+
checked: Boolean(dataValue),
|
|
82
|
+
onChange: onCommit
|
|
83
|
+
}) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
|
|
84
|
+
"data-icon": "triangle-down",
|
|
85
|
+
onSelectionChange: onDropdownSelectionChange,
|
|
86
|
+
placeholder: "Please select value",
|
|
87
|
+
value: value,
|
|
88
|
+
children: lookupValues.map((listOption)=>/*#__PURE__*/ jsx(Option, {
|
|
89
|
+
value: listOption,
|
|
90
|
+
children: listOption.label
|
|
91
|
+
}, listOption.value))
|
|
92
|
+
}) : /*#__PURE__*/ jsx(Input, {
|
|
93
|
+
bordered: true,
|
|
94
|
+
inputProps: {
|
|
95
|
+
...inputProps,
|
|
96
|
+
onChange
|
|
97
|
+
},
|
|
98
|
+
onKeyDown: onKeyDown,
|
|
99
|
+
readOnly: readOnly,
|
|
100
|
+
value: value
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
export { EditField };
|
|
@@ -0,0 +1,218 @@
|
|
|
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 (value = editState.value)=>{
|
|
26
|
+
const result = column.clientSideEditValidationCheck?.(value, "*");
|
|
27
|
+
if (result?.ok === false) setEditState((state)=>({
|
|
28
|
+
...state,
|
|
29
|
+
message: result?.messages?.join(",")
|
|
30
|
+
}));
|
|
31
|
+
else {
|
|
32
|
+
const { current: initialValue } = initialValueRef;
|
|
33
|
+
const previousValue = getTypedValue(initialValue, type);
|
|
34
|
+
try {
|
|
35
|
+
const typedValue = getTypedValue(value, type, true);
|
|
36
|
+
const response = await onEdit?.({
|
|
37
|
+
editType: "commit",
|
|
38
|
+
isValid: true,
|
|
39
|
+
previousValue,
|
|
40
|
+
value: typedValue
|
|
41
|
+
}, "commit");
|
|
42
|
+
if (isRpcSuccess(response)) {
|
|
43
|
+
isDirtyRef.current = false;
|
|
44
|
+
setEditState((state)=>({
|
|
45
|
+
editing: false,
|
|
46
|
+
previousValue: state.previousValue === value ? void 0 : void 0 === state.previousValue ? initialValue : state.previousValue,
|
|
47
|
+
value
|
|
48
|
+
}));
|
|
49
|
+
} else if (isRpcError(response)) {
|
|
50
|
+
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
|
+
...state,
|
|
76
|
+
message
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}, [
|
|
83
|
+
column,
|
|
84
|
+
editState,
|
|
85
|
+
onEdit,
|
|
86
|
+
type
|
|
87
|
+
]);
|
|
88
|
+
const toggleActivation = useCallback(async (input, cancel = false)=>{
|
|
89
|
+
if (editState.editing) if (isDirtyRef.current) if (cancel) {
|
|
90
|
+
const { value: previousValue } = editState;
|
|
91
|
+
isDirtyRef.current = false;
|
|
92
|
+
setEditState({
|
|
93
|
+
editing: false,
|
|
94
|
+
value: initialValueRef.current,
|
|
95
|
+
message: void 0
|
|
96
|
+
});
|
|
97
|
+
onEdit?.({
|
|
98
|
+
editType: "cancel",
|
|
99
|
+
isValid: true,
|
|
100
|
+
previousValue,
|
|
101
|
+
value: getTypedValue(initialValueRef.current, type, true)
|
|
102
|
+
}, "commit");
|
|
103
|
+
} else {
|
|
104
|
+
const commitSuccessful = await commit();
|
|
105
|
+
if (commitSuccessful) {
|
|
106
|
+
setEditState((editState)=>({
|
|
107
|
+
...editState,
|
|
108
|
+
editing: false
|
|
109
|
+
}));
|
|
110
|
+
dispatchCustomEvent(input, "vuu-commit");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else setEditState((editState)=>({
|
|
114
|
+
...editState,
|
|
115
|
+
editing: false
|
|
116
|
+
}));
|
|
117
|
+
else if (!cancel) {
|
|
118
|
+
setEditState((editState)=>({
|
|
119
|
+
...editState,
|
|
120
|
+
editing: true
|
|
121
|
+
}));
|
|
122
|
+
input.select();
|
|
123
|
+
}
|
|
124
|
+
}, [
|
|
125
|
+
commit,
|
|
126
|
+
editState,
|
|
127
|
+
onEdit,
|
|
128
|
+
type
|
|
129
|
+
]);
|
|
130
|
+
const handleKeyDown = useCallback(async (evt)=>{
|
|
131
|
+
const { key, target } = evt;
|
|
132
|
+
const input = target;
|
|
133
|
+
if ("Enter" === key) toggleActivation(input);
|
|
134
|
+
else if ("ArrowRight" === key || "ArrowLeft" === key || "ArrowUp" === key || "ArrowDown" === key) {
|
|
135
|
+
if (editState.editing) evt.stopPropagation();
|
|
136
|
+
} else if ("Escape" === evt.key) toggleActivation(input, true);
|
|
137
|
+
}, [
|
|
138
|
+
editState,
|
|
139
|
+
toggleActivation
|
|
140
|
+
]);
|
|
141
|
+
const handleFocus = useCallback((e)=>{
|
|
142
|
+
const input = e.target;
|
|
143
|
+
const source = e.relatedTarget;
|
|
144
|
+
if (source?.classList.contains("vuuTableCell") && source.contains(input)) toggleActivation(input);
|
|
145
|
+
}, [
|
|
146
|
+
toggleActivation
|
|
147
|
+
]);
|
|
148
|
+
const handleBlur = useCallback(async ()=>{
|
|
149
|
+
if (editState.editing) {
|
|
150
|
+
if (isDirtyRef.current) await commit();
|
|
151
|
+
setEditState((editState)=>({
|
|
152
|
+
...editState,
|
|
153
|
+
editing: false
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
}, [
|
|
157
|
+
commit,
|
|
158
|
+
editState
|
|
159
|
+
]);
|
|
160
|
+
const handleChange = useCallback((evt)=>{
|
|
161
|
+
const { value } = evt.target;
|
|
162
|
+
isDirtyRef.current = value !== initialValueRef.current;
|
|
163
|
+
const result = column.clientSideEditValidationCheck?.(value, "change");
|
|
164
|
+
setEditState({
|
|
165
|
+
editing: true,
|
|
166
|
+
value
|
|
167
|
+
});
|
|
168
|
+
onEdit?.({
|
|
169
|
+
editType: "change",
|
|
170
|
+
isValid: result?.ok !== false,
|
|
171
|
+
previousValue: initialValueRef.current,
|
|
172
|
+
value
|
|
173
|
+
}, "change");
|
|
174
|
+
if (result?.ok === false) setEditState({
|
|
175
|
+
editing: true,
|
|
176
|
+
value,
|
|
177
|
+
message: result.messages?.join(",")
|
|
178
|
+
});
|
|
179
|
+
}, [
|
|
180
|
+
column,
|
|
181
|
+
onEdit
|
|
182
|
+
]);
|
|
183
|
+
const handleDropdownSelectionChange = useCallback((_evt, [selectedOption])=>{
|
|
184
|
+
if (!selectedOption) return;
|
|
185
|
+
const value = String(selectedOption.value);
|
|
186
|
+
isDirtyRef.current = value !== initialValueRef.current;
|
|
187
|
+
setEditState((state)=>({
|
|
188
|
+
...state,
|
|
189
|
+
editing: true,
|
|
190
|
+
message: void 0,
|
|
191
|
+
value
|
|
192
|
+
}));
|
|
193
|
+
commit(value).then((committed)=>{
|
|
194
|
+
if (committed) setEditState((state)=>({
|
|
195
|
+
...state,
|
|
196
|
+
editing: false,
|
|
197
|
+
value
|
|
198
|
+
}));
|
|
199
|
+
});
|
|
200
|
+
}, [
|
|
201
|
+
commit
|
|
202
|
+
]);
|
|
203
|
+
return {
|
|
204
|
+
editing: editState.editing,
|
|
205
|
+
inputProps: {
|
|
206
|
+
onBlur: handleBlur,
|
|
207
|
+
onFocus: handleFocus,
|
|
208
|
+
type: textType
|
|
209
|
+
},
|
|
210
|
+
onKeyDown: handleKeyDown,
|
|
211
|
+
previousValue: editState.previousValue,
|
|
212
|
+
value: editState.value,
|
|
213
|
+
warningMessage: editState.message,
|
|
214
|
+
onChange: handleChange,
|
|
215
|
+
onDropdownSelectionChange: handleDropdownSelectionChange
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
export { useEditField };
|
package/src/index.js
CHANGED
|
@@ -4,9 +4,13 @@ export { useCellEdited } from "./useCellEdited.js";
|
|
|
4
4
|
export { useEditState } from "./useEditState.js";
|
|
5
5
|
export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
|
|
6
6
|
export { EditButtons } from "./EditButtons.js";
|
|
7
|
+
export { EditField } from "./edit-field/EditField.js";
|
|
8
|
+
export { useEditField } from "./edit-field/useEditField.js";
|
|
7
9
|
export { EditModeProvider, useEditMode } from "./EditModeProvider.js";
|
|
8
10
|
export { EditError, EditSession, SupersededEditError } from "./EditSession.js";
|
|
9
11
|
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
12
|
+
export { lookupOptionsFromRows, useLookupValues } from "./lookup-values/useLookupValues.js";
|
|
13
|
+
export { useEditable } from "./useEditable.js";
|
|
10
14
|
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
|
|
11
15
|
export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
|
|
12
16
|
export { useEditableTable } from "./useEditableTable.js";
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Range, useData } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useEffect, useMemo, useState } from "react";
|
|
3
|
+
const DATA_ROW_COLUMN_OFFSET = 10;
|
|
4
|
+
const lookupOptionsFromRows = (columns, rows, optionMap)=>{
|
|
5
|
+
const requestedColumns = [
|
|
6
|
+
optionMap.value,
|
|
7
|
+
optionMap.label,
|
|
8
|
+
...optionMap.additionalFields ?? []
|
|
9
|
+
];
|
|
10
|
+
const missingColumns = requestedColumns.filter((column)=>!columns.includes(column));
|
|
11
|
+
if (missingColumns.length > 0) throw Error(`[useLookupValues] lookup columns not found: ${requestedColumns.join(", ")}`);
|
|
12
|
+
return rows.map((row)=>{
|
|
13
|
+
const metadata = Object.fromEntries((optionMap.additionalFields ?? []).map((field)=>[
|
|
14
|
+
field,
|
|
15
|
+
String(row[columns.indexOf(field) + DATA_ROW_COLUMN_OFFSET])
|
|
16
|
+
]));
|
|
17
|
+
return {
|
|
18
|
+
value: String(row[columns.indexOf(optionMap.value) + DATA_ROW_COLUMN_OFFSET]),
|
|
19
|
+
label: String(row[columns.indexOf(optionMap.label) + DATA_ROW_COLUMN_OFFSET]),
|
|
20
|
+
...Object.keys(metadata).length > 0 ? {
|
|
21
|
+
metadata
|
|
22
|
+
} : {}
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
const useLookupValues = ({ enabled, optionMap, table })=>{
|
|
27
|
+
if (enabled && (void 0 === optionMap || void 0 === table)) throw Error("[useLookupValues] optionsMap and Table must be provided");
|
|
28
|
+
const { VuuDataSource } = useData();
|
|
29
|
+
const [options, setOptions] = useState([]);
|
|
30
|
+
const lookupColumns = useMemo(()=>optionMap ? [
|
|
31
|
+
optionMap.value,
|
|
32
|
+
optionMap.label,
|
|
33
|
+
...optionMap.additionalFields ?? []
|
|
34
|
+
] : [], [
|
|
35
|
+
optionMap
|
|
36
|
+
]);
|
|
37
|
+
const dataSource = useMemo(()=>enabled && optionMap && table ? new VuuDataSource({
|
|
38
|
+
columns: lookupColumns,
|
|
39
|
+
table
|
|
40
|
+
}) : void 0, [
|
|
41
|
+
VuuDataSource,
|
|
42
|
+
enabled,
|
|
43
|
+
lookupColumns,
|
|
44
|
+
optionMap,
|
|
45
|
+
table
|
|
46
|
+
]);
|
|
47
|
+
useEffect(()=>{
|
|
48
|
+
setOptions([]);
|
|
49
|
+
if (!dataSource || !optionMap) return;
|
|
50
|
+
let active = true;
|
|
51
|
+
let columns;
|
|
52
|
+
const subscribe = (message)=>{
|
|
53
|
+
if (!active) return;
|
|
54
|
+
if ("subscribed" === message.type) {
|
|
55
|
+
const schemaColumns = new Set(message.tableSchema.columns.map(({ name })=>name));
|
|
56
|
+
if (!lookupColumns.every((column)=>message.columns.includes(column) && schemaColumns.has(column))) throw Error(`[useLookupValues] lookup columns not found: ${lookupColumns.join(", ")}`);
|
|
57
|
+
columns = message.columns;
|
|
58
|
+
} else if ("viewport-update" === message.type && columns) setOptions(lookupOptionsFromRows(columns, message.rows ?? [], optionMap));
|
|
59
|
+
else if ("subscribe-failed" === message.type) console.error(`Lookup table ${table?.table} subscription failed: ${message.msg}`);
|
|
60
|
+
};
|
|
61
|
+
dataSource.subscribe({
|
|
62
|
+
range: Range(0, 100)
|
|
63
|
+
}, subscribe).catch((cause)=>{
|
|
64
|
+
if (active) console.error("Role client dropdown subscription failed:", cause);
|
|
65
|
+
});
|
|
66
|
+
return ()=>{
|
|
67
|
+
active = false;
|
|
68
|
+
dataSource.unsubscribe();
|
|
69
|
+
};
|
|
70
|
+
}, [
|
|
71
|
+
dataSource,
|
|
72
|
+
lookupColumns,
|
|
73
|
+
optionMap,
|
|
74
|
+
table
|
|
75
|
+
]);
|
|
76
|
+
return options;
|
|
77
|
+
};
|
|
78
|
+
export { lookupOptionsFromRows, useLookupValues };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { EditSession } from "./EditSession.js";
|
|
2
|
+
import { useEditMode } from "./EditModeProvider.js";
|
|
3
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
4
|
+
const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=>{
|
|
5
|
+
const { isEditMode } = useEditMode();
|
|
6
|
+
const editSession = useMemo(()=>new EditSession({
|
|
7
|
+
dataSource
|
|
8
|
+
}), [
|
|
9
|
+
dataSource
|
|
10
|
+
]);
|
|
11
|
+
const handleCancel = useCallback(async ()=>{
|
|
12
|
+
try {
|
|
13
|
+
await editSession.end();
|
|
14
|
+
onCancel();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error("[useEditable] cancel edit session failed", error);
|
|
17
|
+
}
|
|
18
|
+
}, [
|
|
19
|
+
editSession,
|
|
20
|
+
onCancel
|
|
21
|
+
]);
|
|
22
|
+
const handleSave = useCallback(async (force = false)=>{
|
|
23
|
+
try {
|
|
24
|
+
await editSession.end(true, force);
|
|
25
|
+
onSave();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error("[useEditable] save edit session failed", error);
|
|
28
|
+
}
|
|
29
|
+
}, [
|
|
30
|
+
editSession,
|
|
31
|
+
onSave
|
|
32
|
+
]);
|
|
33
|
+
useEffect(()=>{
|
|
34
|
+
const transition = isEditMode ? editSession.begin(copyOption) : editSession.end();
|
|
35
|
+
transition.catch((error)=>{
|
|
36
|
+
if (isEditMode) {
|
|
37
|
+
console.error("[useEditable] begin edit session failed", error);
|
|
38
|
+
handleCancel();
|
|
39
|
+
} else console.error("[useEditable] end edit session failed", error);
|
|
40
|
+
});
|
|
41
|
+
}, [
|
|
42
|
+
copyOption,
|
|
43
|
+
editSession,
|
|
44
|
+
isEditMode,
|
|
45
|
+
handleCancel
|
|
46
|
+
]);
|
|
47
|
+
return {
|
|
48
|
+
editSession,
|
|
49
|
+
onCancel: handleCancel,
|
|
50
|
+
onSave: handleSave
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
export { useEditable };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactNode } from "react";
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
2
|
export interface EditModeContextProps {
|
|
3
3
|
isEditMode: boolean;
|
|
4
4
|
setEditMode: (inEditMode: boolean) => void;
|
|
@@ -7,7 +7,8 @@ export interface EditModeContextProps {
|
|
|
7
7
|
* Implemented as a standalone Provider so that EditMode cna be implemented
|
|
8
8
|
* at higher level than individual edit controls.
|
|
9
9
|
*/
|
|
10
|
-
export declare const EditModeProvider: ({ children }: {
|
|
10
|
+
export declare const EditModeProvider: ({ children, isEditMode: isEditModeProp, }: {
|
|
11
11
|
children: ReactNode;
|
|
12
|
+
isEditMode?: boolean;
|
|
12
13
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
13
14
|
export declare const useEditMode: () => EditModeContextProps;
|
package/types/EditSession.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ import { EventEmitter } from "@vuu-ui/vuu-utils";
|
|
|
4
4
|
import type { TableEditSession } from "./TableEditSession";
|
|
5
5
|
export type EditState = "clean" | "dirty" | "invalid" | "stale";
|
|
6
6
|
export type EditActionType = "deleteRow" | "addRow" | "editCell";
|
|
7
|
+
export type EditSessionValue = VuuRowDataItemType | object;
|
|
8
|
+
export type EditCommitOptions = {
|
|
9
|
+
dataSourceValue?: VuuRowDataItemType;
|
|
10
|
+
syncToDataSource?: boolean;
|
|
11
|
+
};
|
|
7
12
|
/** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
|
|
8
13
|
export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
|
|
9
14
|
export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
|
|
@@ -60,6 +65,8 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> impleme
|
|
|
60
65
|
get addCount(): number;
|
|
61
66
|
get editState(): EditState;
|
|
62
67
|
get isDirty(): boolean;
|
|
68
|
+
get canCancel(): boolean;
|
|
69
|
+
get canSave(): boolean;
|
|
63
70
|
get newRowState(): NewRowState;
|
|
64
71
|
isNewRow(key: string): key is "__vuu_new_row__";
|
|
65
72
|
isNewRowFinalColumn(columnName: string): boolean;
|
|
@@ -84,6 +91,6 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> impleme
|
|
|
84
91
|
get lifecycle(): EditLifecycle;
|
|
85
92
|
get inEditMode(): boolean;
|
|
86
93
|
cancel(key: string, columnName: string, restoredValue: VuuRowDataItemType): Promise<RpcResult>;
|
|
87
|
-
commit(key: string, columnName: string, originalValue:
|
|
94
|
+
commit(key: string, columnName: string, originalValue: EditSessionValue, typedValue: EditSessionValue, isValid: boolean, { dataSourceValue, syncToDataSource }?: EditCommitOptions): Promise<RpcResult>;
|
|
88
95
|
}
|
|
89
96
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DataRow } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
3
|
+
import { type OptionMap } from "../lookup-values/useLookupValues";
|
|
4
|
+
import "./EditField.css";
|
|
5
|
+
export type TextFieldType = "email" | "password";
|
|
6
|
+
export type EditFieldType = "checkbox" | "dropdown" | TextFieldType;
|
|
7
|
+
export interface EditFieldProps {
|
|
8
|
+
dataRow: DataRow;
|
|
9
|
+
deferNewRow?: boolean;
|
|
10
|
+
label: string;
|
|
11
|
+
name: string;
|
|
12
|
+
readOnly?: boolean;
|
|
13
|
+
required?: boolean;
|
|
14
|
+
type?: EditFieldType;
|
|
15
|
+
}
|
|
16
|
+
export interface DropdownEditFieldProps extends EditFieldProps {
|
|
17
|
+
type: "dropdown";
|
|
18
|
+
optionMap: OptionMap;
|
|
19
|
+
table: VuuTable;
|
|
20
|
+
}
|
|
21
|
+
export declare const EditField: ({ dataRow, deferNewRow, label, name, readOnly, required, type, ...options }: EditFieldProps | DropdownEditFieldProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,26 @@
|
|
|
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, type SyntheticEvent } from "react";
|
|
4
|
+
import type { LookupOption } from "../lookup-values/useLookupValues";
|
|
5
|
+
import type { TextFieldType } from "./EditField";
|
|
6
|
+
export interface EditFieldHookProps<T extends VuuRowDataItemType = VuuRowDataItemType> {
|
|
7
|
+
column: Pick<RuntimeColumnDescriptor, "clientSideEditValidationCheck" | "label" | "name">;
|
|
8
|
+
value?: T;
|
|
9
|
+
onEdit?: TableCellEditHandler;
|
|
10
|
+
textType?: TextFieldType;
|
|
11
|
+
type?: "string" | "number" | "boolean";
|
|
12
|
+
}
|
|
13
|
+
export declare const useEditField: <T extends string | number | boolean = string>({ column, value, onEdit, textType, type, }: EditFieldHookProps<T>) => {
|
|
14
|
+
editing: boolean;
|
|
15
|
+
inputProps: {
|
|
16
|
+
onBlur: FocusEventHandler<HTMLElement>;
|
|
17
|
+
onFocus: FocusEventHandler<HTMLElement>;
|
|
18
|
+
type: TextFieldType | undefined;
|
|
19
|
+
};
|
|
20
|
+
onKeyDown: (evt: KeyboardEvent<HTMLElement>) => Promise<void>;
|
|
21
|
+
previousValue: string | undefined;
|
|
22
|
+
value: string;
|
|
23
|
+
warningMessage: string | undefined;
|
|
24
|
+
onChange: ChangeEventHandler<HTMLInputElement>;
|
|
25
|
+
onDropdownSelectionChange: (_evt: SyntheticEvent, [selectedOption]: LookupOption[]) => void;
|
|
26
|
+
};
|
package/types/index.d.ts
CHANGED
|
@@ -5,9 +5,13 @@ export { useCellEdited } from "./useCellEdited";
|
|
|
5
5
|
export { useEditState } from "./useEditState";
|
|
6
6
|
export { getVuuEditMessage, isInlineEditingSession, isEditRowReadOnly, withDataRowEditErrors, } from "./edit-utils";
|
|
7
7
|
export { EditButtons, type EditButtonProps } from "./EditButtons";
|
|
8
|
+
export { EditField, type DropdownEditFieldProps, type EditFieldProps, type EditFieldType, type TextFieldType, } from "./edit-field/EditField";
|
|
9
|
+
export { useEditField, type EditFieldHookProps, } from "./edit-field/useEditField";
|
|
8
10
|
export { EditModeProvider, useEditMode, type EditModeContextProps, } from "./EditModeProvider";
|
|
9
|
-
export { EditError, EditSession, SupersededEditError, type EditActionType, type EditLifecycle, type EditSessionConstructorProps, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
|
|
11
|
+
export { EditError, EditSession, SupersededEditError, type EditActionType, type EditCommitOptions, type EditLifecycle, type EditSessionConstructorProps, type EditSessionValue, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
|
|
10
12
|
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
13
|
+
export { lookupOptionsFromRows, useLookupValues, type LookupOption, type LookupValuedHookProps, type OptionMap, } from "./lookup-values/useLookupValues";
|
|
14
|
+
export { useEditable, type EditableHookProps } from "./useEditable";
|
|
11
15
|
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator, } from "./editActionRowClassNameGenerator";
|
|
12
16
|
export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps, } from "./UndoCellRenderer";
|
|
13
17
|
export { useEditableTable, type EditableTableHookProps, type EditMode, } from "./useEditableTable";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DataSourceRow } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
3
|
+
export type LookupOption = {
|
|
4
|
+
label: string;
|
|
5
|
+
metadata?: Record<string, string>;
|
|
6
|
+
value: number | string;
|
|
7
|
+
};
|
|
8
|
+
export type OptionMap = {
|
|
9
|
+
additionalFields?: readonly string[];
|
|
10
|
+
label: string;
|
|
11
|
+
value: string;
|
|
12
|
+
};
|
|
13
|
+
export interface LookupValuedHookProps {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
optionMap?: OptionMap;
|
|
16
|
+
table?: VuuTable;
|
|
17
|
+
}
|
|
18
|
+
export declare const lookupOptionsFromRows: (columns: string[], rows: DataSourceRow[], optionMap: OptionMap) => LookupOption[];
|
|
19
|
+
export declare const useLookupValues: ({ enabled, optionMap, table, }: LookupValuedHookProps) => LookupOption[];
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { EditSession } from "./EditSession";
|
|
3
|
+
export declare const DataEditingProvider: ({ children, editSession, }: {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
editSession: EditSession;
|
|
6
|
+
}) => import("react").JSX.Element;
|
|
7
|
+
export declare function useEditSession(throwIfUnavailable?: false): EditSession | undefined;
|
|
8
|
+
export declare function useEditSession(throwIfUnavailable: true): EditSession;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { EditSession } from "./EditSession";
|
|
2
|
+
export interface EditButtonProps {
|
|
3
|
+
canCancel: boolean;
|
|
4
|
+
canSave: boolean;
|
|
5
|
+
editSession?: EditSession;
|
|
6
|
+
hasSelection?: boolean;
|
|
7
|
+
onCancel?: () => void;
|
|
8
|
+
onDelete?: () => void;
|
|
9
|
+
onSave: (force?: boolean) => void;
|
|
10
|
+
saveLabel?: string;
|
|
11
|
+
confirmSave?: () => boolean | Promise<boolean>;
|
|
12
|
+
confirmCancel?: () => boolean | Promise<boolean>;
|
|
13
|
+
}
|
|
14
|
+
export declare const EditButtons: ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ReactNode } from "react";
|
|
2
|
+
export interface EditModeContextProps {
|
|
3
|
+
isEditMode: boolean;
|
|
4
|
+
setEditMode: (inEditMode: boolean) => void;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Implemented as a standalone Provider so that EditMode cna be implemented
|
|
8
|
+
* at higher level than individual edit controls.
|
|
9
|
+
*/
|
|
10
|
+
export declare const EditModeProvider: ({ children }: {
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}) => import("react").JSX.Element;
|
|
13
|
+
export declare const useEditMode: () => EditModeContextProps;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { CopyOption, DataSource, DeleteRowMode, SessionType } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
|
|
3
|
+
import { EventEmitter } from "@vuu-ui/vuu-utils";
|
|
4
|
+
export type EditState = "clean" | "dirty" | "invalid" | "stale";
|
|
5
|
+
export type EditActionType = "deleteRow" | "addRow" | "editCell";
|
|
6
|
+
/** Column name to default value mapping applied to every addRow call when a column is absent from the row data. */
|
|
7
|
+
export type RowDefaultDataItemValues = Record<string, VuuRowDataItemType>;
|
|
8
|
+
export type EditSessionApi = "createSessionDataSource" | "beginEditSession";
|
|
9
|
+
export type EditSessionConstructorProps = {
|
|
10
|
+
dataSource: DataSource;
|
|
11
|
+
/** @default "soft" */
|
|
12
|
+
deleteMode?: DeleteRowMode;
|
|
13
|
+
/** @default "createSessionDataSource" */
|
|
14
|
+
editSessionApi?: EditSessionApi;
|
|
15
|
+
/** Default column values merged into every addRow call for absent columns. Pass a stable reference. */
|
|
16
|
+
rowDefaults?: RowDefaultDataItemValues;
|
|
17
|
+
};
|
|
18
|
+
export type NewRowState = {
|
|
19
|
+
columns: readonly string[];
|
|
20
|
+
requiredColumns?: readonly string[];
|
|
21
|
+
draftRevision: number;
|
|
22
|
+
errors: Readonly<Record<string, string>>;
|
|
23
|
+
submitting: boolean;
|
|
24
|
+
values: Readonly<Record<string, VuuRowDataItemType>>;
|
|
25
|
+
};
|
|
26
|
+
export type EditLifecycle = {
|
|
27
|
+
status: "idle";
|
|
28
|
+
} | {
|
|
29
|
+
status: "starting";
|
|
30
|
+
} | {
|
|
31
|
+
status: "active";
|
|
32
|
+
sessionDataSource: DataSource;
|
|
33
|
+
} | {
|
|
34
|
+
status: "ending";
|
|
35
|
+
sessionDataSource: DataSource;
|
|
36
|
+
} | {
|
|
37
|
+
status: "error";
|
|
38
|
+
operation: "begin" | "end";
|
|
39
|
+
error: Error;
|
|
40
|
+
sessionDataSource?: DataSource;
|
|
41
|
+
};
|
|
42
|
+
export declare class EditError extends Error {
|
|
43
|
+
}
|
|
44
|
+
export declare class SupersededEditError extends Error {
|
|
45
|
+
}
|
|
46
|
+
type EditSessionEvents = {
|
|
47
|
+
cellEditChanged: (key: string, columnName: string) => void;
|
|
48
|
+
editState: (editState: EditState) => void;
|
|
49
|
+
lifecycle: (lifecycle: EditLifecycle) => void;
|
|
50
|
+
newRow: (newRowState: NewRowState) => void;
|
|
51
|
+
};
|
|
52
|
+
export declare class EditSession extends EventEmitter<EditSessionEvents> {
|
|
53
|
+
#private;
|
|
54
|
+
static readonly newRowKey = "__vuu_new_row__";
|
|
55
|
+
constructor({ dataSource, deleteMode, editSessionApi, rowDefaults, }: EditSessionConstructorProps);
|
|
56
|
+
get editCount(): number;
|
|
57
|
+
get invalidCount(): number;
|
|
58
|
+
get deleteCount(): number;
|
|
59
|
+
get addCount(): number;
|
|
60
|
+
get editState(): EditState;
|
|
61
|
+
get newRowState(): NewRowState;
|
|
62
|
+
isNewRow(key: string): key is "__vuu_new_row__";
|
|
63
|
+
isNewRowFinalColumn(columnName: string): boolean;
|
|
64
|
+
isNewRowComplete(): boolean;
|
|
65
|
+
configureNewRow(columns: readonly string[], requiredColumns?: readonly string[]): void;
|
|
66
|
+
setNewRowValue(column: string, value: VuuRowDataItemType): void;
|
|
67
|
+
addNewRow(): Promise<RpcResult>;
|
|
68
|
+
deleteSelectedRows(): Promise<RpcResult>;
|
|
69
|
+
addRow(rowData?: Record<string, VuuRowDataItemType>): Promise<RpcResult>;
|
|
70
|
+
hasRowChanges(key: string): boolean;
|
|
71
|
+
isCellEdited(key: string, columnName: string): boolean;
|
|
72
|
+
undoRowChange(key: string, action: EditActionType): Promise<RpcResult | undefined>;
|
|
73
|
+
begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
|
|
74
|
+
/**
|
|
75
|
+
* Safe to call repeatedly - schema of a remote session table typically arrives
|
|
76
|
+
* after begin() resolves, so callers re-invoke this once 'subscribed' fires.
|
|
77
|
+
*/
|
|
78
|
+
reconcileWithSessionSchema(): void;
|
|
79
|
+
get dataSource(): import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
|
|
80
|
+
get sessionDataSource(): DataSource | undefined;
|
|
81
|
+
end(saveChanges?: boolean, force?: boolean): Promise<void>;
|
|
82
|
+
get lifecycle(): EditLifecycle;
|
|
83
|
+
get inEditMode(): boolean;
|
|
84
|
+
commit(key: string, columnName: string, originalValue: VuuRowDataItemType, typedValue: string | number | boolean, isValid: boolean): Promise<RpcResult>;
|
|
85
|
+
}
|
|
86
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TableCellRendererProps } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
export declare const UNDO_CELL_RENDERER = "vuu.undo-cell";
|
|
3
|
+
export interface UndoCellRendererComponentProps {
|
|
4
|
+
icon?: string;
|
|
5
|
+
text?: string | false;
|
|
6
|
+
}
|
|
7
|
+
export declare const getUndoButtonContent: (componentProps?: UndoCellRendererComponentProps) => {
|
|
8
|
+
icon: string | undefined;
|
|
9
|
+
text: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare const getUndoTooltipContent: (action: unknown) => "Undo delete row" | "Undo insert row" | "Undo row edits" | undefined;
|
|
12
|
+
export declare const UndoCellRenderer: ({ column, dataRow, }: TableCellRendererProps) => import("react").JSX.Element | null;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { DataRow, RuntimeColumnDescriptor } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
export { isInlineEditingSession } from "@vuu-ui/vuu-utils";
|
|
3
|
+
export declare const isEditRowReadOnly: (dataRow: DataRow) => boolean;
|
|
4
|
+
export declare const withDataRowEditErrors: <T extends DataRow>(dataRow: T, errors: Record<string, string>) => T;
|
|
5
|
+
export declare const getVuuEditMessage: (dataRow: DataRow, column: RuntimeColumnDescriptor, originalValue: string) => string | import("react").JSX.Element | undefined;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { DataEditingProvider, useEditSession } from "./DataEditingProvider";
|
|
2
|
+
export { useCellEdited } from "./useCellEdited";
|
|
3
|
+
export { getVuuEditMessage, isInlineEditingSession, isEditRowReadOnly, withDataRowEditErrors, } from "./edit-utils";
|
|
4
|
+
export { EditButtons, type EditButtonProps } from "./EditButtons";
|
|
5
|
+
export { EditModeProvider, useEditMode, type EditModeContextProps, } from "./EditModeProvider";
|
|
6
|
+
export { EditError, EditSession, SupersededEditError, type EditActionType, type EditLifecycle, type EditSessionConstructorProps, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
|
|
7
|
+
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
8
|
+
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator, } from "./editActionRowClassNameGenerator";
|
|
9
|
+
export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps, } from "./UndoCellRenderer";
|
|
10
|
+
export { useEditableTable, type EditableTableHookProps, type EditMode, } from "./useEditableTable";
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CopyOption, DataSource, DeleteRowMode } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
3
|
+
import { EditSession, type EditActionType, type EditLifecycle, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
|
|
4
|
+
export type EditMode = "edit" | "view";
|
|
5
|
+
export interface EditableTableHookProps {
|
|
6
|
+
/**
|
|
7
|
+
* columns to be included in subscription. If not provided,
|
|
8
|
+
* default will be '*'. Ignored if dataSource prop present.
|
|
9
|
+
*/
|
|
10
|
+
columns?: string[];
|
|
11
|
+
dataSource?: DataSource;
|
|
12
|
+
addRowsCount?: number;
|
|
13
|
+
deleteMode?: DeleteRowMode;
|
|
14
|
+
editSessionApi?: EditSessionApi;
|
|
15
|
+
copyOption?: CopyOption;
|
|
16
|
+
isEditMode: boolean;
|
|
17
|
+
onCancel: () => void;
|
|
18
|
+
onSave: () => void;
|
|
19
|
+
/** Default column values applied to every addRow call. Pass a stable reference — a new object triggers EditSession recreation. */
|
|
20
|
+
rowDefaults?: RowDefaultDataItemValues;
|
|
21
|
+
/**
|
|
22
|
+
* If dataSource not provided, new DataSource
|
|
23
|
+
* will be created using table and columns
|
|
24
|
+
*/
|
|
25
|
+
table?: VuuTable;
|
|
26
|
+
}
|
|
27
|
+
export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, deleteMode, editSessionApi, copyOption, isEditMode, onCancel, onSave, rowDefaults, table, }: EditableTableHookProps) => {
|
|
28
|
+
canCancel: boolean;
|
|
29
|
+
canSave: boolean;
|
|
30
|
+
columnsDiverge: boolean;
|
|
31
|
+
dataSource: DataSource;
|
|
32
|
+
editSchema: import("@vuu-ui/vuu-data-types").TableSchema | undefined;
|
|
33
|
+
editSession: EditSession;
|
|
34
|
+
lifecycle: EditLifecycle;
|
|
35
|
+
hasSelection: boolean;
|
|
36
|
+
isEditSessionReady: boolean;
|
|
37
|
+
onCancel: () => Promise<void>;
|
|
38
|
+
onDelete: () => Promise<void>;
|
|
39
|
+
onSave: (force?: boolean) => Promise<void>;
|
|
40
|
+
onUndoRowChange: (key: string, action: EditActionType) => undefined;
|
|
41
|
+
rowClassNameGenerators: string[] | undefined;
|
|
42
|
+
sessionDataSource: import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
|
|
43
|
+
sourceDataSource: DataSource;
|
|
44
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CopyOption, DataSource } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import { EditSession } from "./EditSession";
|
|
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
|
+
};
|