@vuu-ui/vuu-data-editing 5.0.0-alpha.2 → 5.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +40 -3
- package/package.json +7 -6
- package/src/DirectEditSession.js +8 -1
- package/src/EditButtons.js +6 -5
- package/src/EditModeProvider.js +14 -5
- package/src/EditSession.js +78 -8
- package/src/edit-control/EditControlCheckbox.js +25 -0
- package/src/edit-control/EditControlDropdown.js +34 -0
- package/src/edit-control/EditControlLookupDropdown.js +31 -0
- package/src/edit-control/get-data-item-edit-control.js +139 -0
- package/src/edit-control/index.js +4 -0
- package/src/edit-errors.js +7 -0
- package/src/edit-field/EditField.js +117 -24
- package/src/edit-field/useEditField.js +66 -52
- package/src/edit-form/CreateRowForm.js +37 -0
- package/src/edit-form/EditForm.css.js +32 -0
- package/src/edit-form/EditForm.js +85 -0
- package/src/edit-form/EditFormButtons.js +48 -0
- package/src/edit-form/EditFormContext.js +4 -0
- package/src/edit-form/index.js +4 -0
- package/src/form-fields.js +40 -0
- package/src/index.js +9 -0
- package/src/lookup-values/useLookupOptions.js +106 -0
- package/src/useConfirmDiscard.js +34 -0
- package/src/useCustomEditField.js +76 -0
- package/src/useEditForm.js +306 -0
- package/src/useEditSessionState.js +72 -0
- package/src/useEditable.js +26 -9
- package/src/useEditableColumns.js +105 -0
- package/src/useEditableTable.js +32 -9
- package/types/DirectEditSession.d.ts +6 -1
- package/types/EditButtons.d.ts +20 -3
- package/types/EditModeProvider.d.ts +10 -2
- package/types/EditSession.d.ts +18 -1
- package/types/edit-control/EditControlCheckbox.d.ts +8 -0
- package/types/edit-control/EditControlDropdown.d.ts +14 -0
- package/types/edit-control/EditControlLookupDropdown.d.ts +13 -0
- package/types/edit-control/get-data-item-edit-control.d.ts +40 -0
- package/types/edit-control/index.d.ts +4 -0
- package/types/edit-errors.d.ts +9 -0
- package/types/edit-field/EditField.d.ts +37 -3
- package/types/edit-field/useEditField.d.ts +2 -1
- package/types/edit-form/CreateRowForm.d.ts +29 -0
- package/types/edit-form/EditForm.d.ts +36 -0
- package/types/edit-form/EditFormButtons.d.ts +18 -0
- package/types/edit-form/EditFormContext.d.ts +59 -0
- package/types/edit-form/index.d.ts +4 -0
- package/types/form-fields.d.ts +32 -0
- package/types/index.d.ts +12 -2
- package/types/lookup-values/useLookupOptions.d.ts +35 -0
- package/types/useConfirmDiscard.d.ts +23 -0
- package/types/useCustomEditField.d.ts +51 -0
- package/types/useEditForm.d.ts +127 -0
- package/types/useEditSessionState.d.ts +27 -0
- package/types/useEditable.d.ts +30 -3
- package/types/useEditableColumns.d.ts +79 -0
- package/types/useEditableTable.d.ts +18 -3
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { isDataValueEditable } from "@vuu-ui/vuu-utils";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { UNDO_CELL_RENDERER } from "./UndoCellRenderer.js";
|
|
4
|
+
const DEFAULT_READ_ONLY_COLUMNS = [
|
|
5
|
+
"vuuCreatedTimestamp",
|
|
6
|
+
"vuuUpdatedTimestamp",
|
|
7
|
+
"vuuMsg"
|
|
8
|
+
];
|
|
9
|
+
const UNDO_COLUMN = {
|
|
10
|
+
name: "undo",
|
|
11
|
+
source: "client",
|
|
12
|
+
width: 80,
|
|
13
|
+
type: {
|
|
14
|
+
name: "string",
|
|
15
|
+
renderer: {
|
|
16
|
+
name: UNDO_CELL_RENDERER
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const typeName = (column)=>{
|
|
21
|
+
if ("string" == typeof column.type) return column.type;
|
|
22
|
+
if (column.type) return column.type.name;
|
|
23
|
+
return "string";
|
|
24
|
+
};
|
|
25
|
+
const isReadOnlyColumn = (name)=>DEFAULT_READ_ONLY_COLUMNS.includes(name);
|
|
26
|
+
const narrowEditable = (schemaEditable = true, columnEditable = true)=>{
|
|
27
|
+
if (true === schemaEditable) return columnEditable;
|
|
28
|
+
if (false === schemaEditable || false === columnEditable) return false;
|
|
29
|
+
const insert = isDataValueEditable({
|
|
30
|
+
editable: schemaEditable
|
|
31
|
+
}, "insert") && isDataValueEditable({
|
|
32
|
+
editable: columnEditable
|
|
33
|
+
}, "insert");
|
|
34
|
+
const update = isDataValueEditable({
|
|
35
|
+
editable: schemaEditable
|
|
36
|
+
}, "update") && isDataValueEditable({
|
|
37
|
+
editable: columnEditable
|
|
38
|
+
}, "update");
|
|
39
|
+
return insert === update ? insert : {
|
|
40
|
+
insert,
|
|
41
|
+
update
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
const withEditor = (column, editor)=>{
|
|
45
|
+
if (void 0 === editor || "typeahead" === editor.name) return column;
|
|
46
|
+
return {
|
|
47
|
+
...column,
|
|
48
|
+
type: {
|
|
49
|
+
name: typeName(column),
|
|
50
|
+
renderer: editor
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
const toViewColumn = (column)=>void 0 === column.editable || false === column.editable ? column : {
|
|
55
|
+
...column,
|
|
56
|
+
editable: false
|
|
57
|
+
};
|
|
58
|
+
const getEditableColumns = ({ actionColumn = false, columns, columnsDiverge = false, editSchema, isEditing, schema = editSchema, undoColumn = false })=>{
|
|
59
|
+
if (!isEditing) return columns.some((c)=>void 0 !== c.editable && false !== c.editable) ? columns.map(toViewColumn) : columns;
|
|
60
|
+
const baseColumns = columnsDiverge && editSchema ? editSchema.columns.filter(({ name })=>"vuuAction" !== name).map((schemaColumn)=>columns.find(({ name })=>name === schemaColumn.name) ?? {
|
|
61
|
+
name: schemaColumn.name,
|
|
62
|
+
serverDataType: schemaColumn.serverDataType
|
|
63
|
+
}) : columns;
|
|
64
|
+
const editColumns = baseColumns.map((column)=>{
|
|
65
|
+
if ("client" === column.source) return column;
|
|
66
|
+
if (isReadOnlyColumn(column.name)) return void 0 === column.editable ? column : {
|
|
67
|
+
...column,
|
|
68
|
+
editable: false
|
|
69
|
+
};
|
|
70
|
+
const schemaColumn = schema?.columns.find(({ name })=>name === column.name);
|
|
71
|
+
const editable = narrowEditable(schemaColumn?.editable, column.editable);
|
|
72
|
+
return withEditor({
|
|
73
|
+
...column,
|
|
74
|
+
editable
|
|
75
|
+
}, column.editor);
|
|
76
|
+
});
|
|
77
|
+
if (actionColumn) editColumns.push({
|
|
78
|
+
hidden: "hidden" === actionColumn,
|
|
79
|
+
name: "vuuAction",
|
|
80
|
+
serverDataType: "string"
|
|
81
|
+
});
|
|
82
|
+
if (undoColumn) editColumns.push(true === undoColumn ? UNDO_COLUMN : {
|
|
83
|
+
...UNDO_COLUMN,
|
|
84
|
+
...undoColumn
|
|
85
|
+
});
|
|
86
|
+
return editColumns;
|
|
87
|
+
};
|
|
88
|
+
const useEditableColumns = ({ actionColumn, columns, columnsDiverge, editSchema, isEditing, schema, undoColumn })=>useMemo(()=>getEditableColumns({
|
|
89
|
+
actionColumn,
|
|
90
|
+
columns,
|
|
91
|
+
columnsDiverge,
|
|
92
|
+
editSchema,
|
|
93
|
+
isEditing,
|
|
94
|
+
schema,
|
|
95
|
+
undoColumn
|
|
96
|
+
}), [
|
|
97
|
+
actionColumn,
|
|
98
|
+
columns,
|
|
99
|
+
columnsDiverge,
|
|
100
|
+
editSchema,
|
|
101
|
+
isEditing,
|
|
102
|
+
schema,
|
|
103
|
+
undoColumn
|
|
104
|
+
]);
|
|
105
|
+
export { DEFAULT_READ_ONLY_COLUMNS, UNDO_COLUMN, getEditableColumns, narrowEditable, useEditableColumns };
|
package/src/useEditableTable.js
CHANGED
|
@@ -3,14 +3,20 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
3
3
|
import { EditSession } from "./EditSession.js";
|
|
4
4
|
import { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR } from "./editActionRowClassNameGenerator.js";
|
|
5
5
|
import { isEditRowReadOnly } from "./edit-utils.js";
|
|
6
|
+
import { useEditMode } from "./EditModeProvider.js";
|
|
7
|
+
import { reportEditError } from "./edit-errors.js";
|
|
6
8
|
const EDIT_ACTION_ROW_CLASS_NAME_GENERATORS = [
|
|
7
9
|
EDIT_ACTION_ROW_CLASS_NAME_GENERATOR
|
|
8
10
|
];
|
|
9
|
-
const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "soft", editSessionApi = "createSessionDataSource", copyOption = "All", isEditMode, onCancel, onSave, rowDefaults, table })=>{
|
|
11
|
+
const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "soft", editSessionApi = "createSessionDataSource", copyOption = "All", isEditMode: isEditModeProp, onCancel, onError, onSave, rowDefaults, table })=>{
|
|
10
12
|
const { VuuDataSource } = useData();
|
|
13
|
+
const { isEditMode: isEditModeContext } = useEditMode();
|
|
14
|
+
const isEditMode = isEditModeProp ?? isEditModeContext;
|
|
11
15
|
const [selectionCount, setSelectionCount] = useState(0);
|
|
16
|
+
const onErrorRef = useRef(onError);
|
|
17
|
+
onErrorRef.current = onError;
|
|
12
18
|
useLayoutEffectSkipFirst(()=>{
|
|
13
|
-
console.warn("[useEditableTable] columns
|
|
19
|
+
if ("production" !== process.env.NODE_ENV && !dataSourceProp) console.warn("[useEditableTable] columns or table changed, a new DataSource and EditSession will be created. Pass stable (memoized) values.");
|
|
14
20
|
}, [
|
|
15
21
|
columns,
|
|
16
22
|
table
|
|
@@ -52,7 +58,7 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
52
58
|
setSelectionCount(0);
|
|
53
59
|
onCancel();
|
|
54
60
|
} catch (error) {
|
|
55
|
-
|
|
61
|
+
reportEditError("useEditableTable", onErrorRef.current, error, "cancel");
|
|
56
62
|
}
|
|
57
63
|
}, [
|
|
58
64
|
editSession,
|
|
@@ -64,14 +70,19 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
64
70
|
setSelectionCount(0);
|
|
65
71
|
onSave();
|
|
66
72
|
} catch (error) {
|
|
67
|
-
|
|
73
|
+
reportEditError("useEditableTable", onErrorRef.current, error, "save");
|
|
68
74
|
}
|
|
69
75
|
}, [
|
|
70
76
|
editSession,
|
|
71
77
|
onSave
|
|
72
78
|
]);
|
|
73
79
|
const handleDelete = useCallback(async ()=>{
|
|
74
|
-
|
|
80
|
+
try {
|
|
81
|
+
const response = await editSession.deleteSelectedRows();
|
|
82
|
+
if ("ERROR_RESULT" === response.type) reportEditError("useEditableTable", onErrorRef.current, new Error(response.errorMessage), "delete");
|
|
83
|
+
} catch (error) {
|
|
84
|
+
reportEditError("useEditableTable", onErrorRef.current, error, "delete");
|
|
85
|
+
}
|
|
75
86
|
}, [
|
|
76
87
|
editSession
|
|
77
88
|
]);
|
|
@@ -90,10 +101,20 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
90
101
|
editSession.reconcileWithSessionSchema();
|
|
91
102
|
setSubscribedSessionDataSource(sessionDataSource);
|
|
92
103
|
};
|
|
104
|
+
const handleUnsubscribed = ()=>{
|
|
105
|
+
if (editSession.canCancel) editSession.end().then(()=>{
|
|
106
|
+
setSelectionCount(0);
|
|
107
|
+
onCancelRef.current();
|
|
108
|
+
});
|
|
109
|
+
};
|
|
93
110
|
setSubscribedSessionDataSource(void 0);
|
|
94
111
|
sessionDataSource.on("subscribed", handleSubscribed);
|
|
112
|
+
sessionDataSource.on("unsubscribed", handleUnsubscribed);
|
|
95
113
|
if ("subscribed" === sessionDataSource.status && sessionDataSource.tableSchema) handleSubscribed();
|
|
96
|
-
return ()=>
|
|
114
|
+
return ()=>{
|
|
115
|
+
sessionDataSource.removeListener("subscribed", handleSubscribed);
|
|
116
|
+
sessionDataSource.removeListener("unsubscribed", handleUnsubscribed);
|
|
117
|
+
};
|
|
97
118
|
}, [
|
|
98
119
|
editSession,
|
|
99
120
|
sessionDataSource
|
|
@@ -120,9 +141,9 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
120
141
|
const transition = isEditMode ? editSession.begin(copyOption) : editSession.end();
|
|
121
142
|
transition.catch((error)=>{
|
|
122
143
|
if (isEditMode) {
|
|
123
|
-
|
|
144
|
+
reportEditError("useEditableTable", onErrorRef.current, error, "begin");
|
|
124
145
|
onCancelRef.current();
|
|
125
|
-
} else
|
|
146
|
+
} else reportEditError("useEditableTable", onErrorRef.current, error, "cancel");
|
|
126
147
|
});
|
|
127
148
|
}, [
|
|
128
149
|
copyOption,
|
|
@@ -130,7 +151,7 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
130
151
|
isEditMode
|
|
131
152
|
]);
|
|
132
153
|
const canCancel = "active" === lifecycle.status || "error" === lifecycle.status && "end" === lifecycle.operation;
|
|
133
|
-
const canSave = canCancel && ("dirty" === editState || "stale" === editState)
|
|
154
|
+
const canSave = canCancel && ("dirty" === editState || "stale" === editState);
|
|
134
155
|
const isEditSessionReady = isEditMode && void 0 !== sessionDataSource && sessionDataSource === editSession.sessionDataSource && subscribedSessionDataSource === sessionDataSource && "subscribed" === sessionDataSource.status && void 0 !== sessionDataSource.tableSchema;
|
|
135
156
|
const editSchema = subscribedSessionDataSource?.tableSchema;
|
|
136
157
|
const viewSchema = sourceDataSource.tableSchema;
|
|
@@ -142,6 +163,8 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
142
163
|
dataSource,
|
|
143
164
|
editSchema,
|
|
144
165
|
editSession,
|
|
166
|
+
editState,
|
|
167
|
+
isEditMode,
|
|
145
168
|
lifecycle,
|
|
146
169
|
hasSelection: selectionCount > 0,
|
|
147
170
|
isEditSessionReady,
|
|
@@ -2,8 +2,13 @@ import type { DataSource } from "@vuu-ui/vuu-data-types";
|
|
|
2
2
|
import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
|
|
3
3
|
import { EventEmitter } from "@vuu-ui/vuu-utils";
|
|
4
4
|
import type { TableEditSession, TableEditSessionEvents } from "./TableEditSession";
|
|
5
|
+
/**
|
|
6
|
+
* Checks a value before it is sent. Return an error message to reject it.
|
|
7
|
+
*/
|
|
8
|
+
export type DirectEditValidator = (key: string, columnName: string, value: VuuRowDataItemType) => string | undefined;
|
|
5
9
|
export type DirectEditSessionConstructorProps = {
|
|
6
10
|
dataSource: DataSource;
|
|
11
|
+
validate?: DirectEditValidator;
|
|
7
12
|
};
|
|
8
13
|
/**
|
|
9
14
|
* A lightweight edit session for services that allow cells to be edited
|
|
@@ -13,7 +18,7 @@ export type DirectEditSessionConstructorProps = {
|
|
|
13
18
|
*/
|
|
14
19
|
export declare class DirectEditSession extends EventEmitter<TableEditSessionEvents> implements TableEditSession {
|
|
15
20
|
#private;
|
|
16
|
-
constructor({ dataSource }: DirectEditSessionConstructorProps);
|
|
21
|
+
constructor({ dataSource, validate }: DirectEditSessionConstructorProps);
|
|
17
22
|
get dataSource(): DataSource;
|
|
18
23
|
get inEditMode(): boolean;
|
|
19
24
|
isCellEdited(_key: string, _columnName: string): boolean;
|
package/types/EditButtons.d.ts
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import type { EditSession } from "./EditSession";
|
|
2
2
|
export interface EditButtonProps {
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Enables the Cancel button. Defaults to `editSession.canCancel`, so only
|
|
5
|
+
* provide this to override the session-derived value.
|
|
6
|
+
*/
|
|
7
|
+
canCancel?: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* An extra condition for enabling Save. Save is only enabled when this is
|
|
10
|
+
* not false and `editSession.canSave` is true.
|
|
11
|
+
*/
|
|
12
|
+
canSave?: boolean;
|
|
5
13
|
editSession?: EditSession;
|
|
6
14
|
hasSelection?: boolean;
|
|
7
15
|
onCancel?: () => void;
|
|
8
16
|
onDelete?: () => void;
|
|
9
17
|
onSave: (force?: boolean) => void;
|
|
10
18
|
saveLabel?: string;
|
|
19
|
+
/** Return false (or resolve false) to abort the save. */
|
|
11
20
|
confirmSave?: () => boolean | Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Called before cancelling a session with unsaved changes. Return false
|
|
23
|
+
* (or resolve false) to keep editing. See `useConfirmDiscard`.
|
|
24
|
+
*/
|
|
12
25
|
confirmCancel?: () => boolean | Promise<boolean>;
|
|
13
26
|
}
|
|
14
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Delete, Save and Cancel buttons for an edit session. Save switches to
|
|
29
|
+
* "Save (force)" when the last save was rejected as stale.
|
|
30
|
+
*/
|
|
31
|
+
export declare const EditButtons: ({ canCancel: canCancelProp, canSave: canSaveProp, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
|
|
@@ -4,11 +4,19 @@ export interface EditModeContextProps {
|
|
|
4
4
|
setEditMode: (inEditMode: boolean) => void;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Shares a view/edit mode flag between components that are rendered apart,
|
|
8
|
+
* e.g. a toolbar toggle and an edit form. Implemented as a standalone Provider
|
|
9
|
+
* so that edit mode can be controlled above the individual edit controls.
|
|
10
|
+
*
|
|
11
|
+
* `isEditMode` seeds (and resets) the mode; descendants can change it with
|
|
12
|
+
* `useEditMode().setEditMode`.
|
|
9
13
|
*/
|
|
10
14
|
export declare const EditModeProvider: ({ children, isEditMode: isEditModeProp, }: {
|
|
11
15
|
children: ReactNode;
|
|
12
16
|
isEditMode?: boolean;
|
|
13
17
|
}) => import("react").JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* Returns the edit mode from the nearest EditModeProvider. With no provider,
|
|
20
|
+
* `isEditMode` is always false.
|
|
21
|
+
*/
|
|
14
22
|
export declare const useEditMode: () => EditModeContextProps;
|
package/types/EditSession.d.ts
CHANGED
|
@@ -34,6 +34,12 @@ export type NewRowState = {
|
|
|
34
34
|
errors: Readonly<Record<string, string>>;
|
|
35
35
|
submitting: boolean;
|
|
36
36
|
values: Readonly<Record<string, VuuRowDataItemType>>;
|
|
37
|
+
/**
|
|
38
|
+
* Set when the server rejected the row. The message is reported against
|
|
39
|
+
* this column (the final column, where an inline add row shows it) and is
|
|
40
|
+
* cleared by the next change to the draft.
|
|
41
|
+
*/
|
|
42
|
+
rowErrorColumn?: string;
|
|
37
43
|
};
|
|
38
44
|
export type EditLifecycle = {
|
|
39
45
|
status: "idle";
|
|
@@ -78,11 +84,20 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> impleme
|
|
|
78
84
|
isNewRowFinalColumn(columnName: string): boolean;
|
|
79
85
|
isNewRowComplete(): boolean;
|
|
80
86
|
configureNewRow(columns: readonly string[], requiredColumns?: readonly string[]): void;
|
|
81
|
-
setNewRowValue(column: string, value: VuuRowDataItemType): void;
|
|
87
|
+
setNewRowValue(column: string, value: VuuRowDataItemType | undefined): void;
|
|
88
|
+
setNewRowError(column: string, errorMessage?: string): void;
|
|
89
|
+
clearNewRowError(column: string): void;
|
|
90
|
+
/** Discards the new-row draft, leaving the rest of the session untouched. */
|
|
91
|
+
clearNewRow(): void;
|
|
82
92
|
addNewRow(): Promise<RpcResult>;
|
|
83
93
|
deleteSelectedRows(): Promise<RpcResult>;
|
|
84
94
|
addRow(rowData?: Record<string, VuuRowDataItemType>): Promise<RpcResult>;
|
|
85
95
|
hasRowChanges(key: string): boolean;
|
|
96
|
+
/**
|
|
97
|
+
* The edited values for a row, keyed by column name. Includes invalid
|
|
98
|
+
* edits. Returns an empty object when the row has no edits.
|
|
99
|
+
*/
|
|
100
|
+
getEditedValues(key: string): Record<string, EditSessionValue>;
|
|
86
101
|
isCellEdited(key: string, columnName: string): boolean;
|
|
87
102
|
undoRowChange(key: string, action: EditActionType): Promise<RpcResult | undefined>;
|
|
88
103
|
begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
|
|
@@ -92,6 +107,8 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> impleme
|
|
|
92
107
|
*/
|
|
93
108
|
reconcileWithSessionSchema(): void;
|
|
94
109
|
get dataSource(): import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
|
|
110
|
+
/** The dataSource of the table being edited. */
|
|
111
|
+
get sourceDataSource(): DataSource | undefined;
|
|
95
112
|
get sessionDataSource(): DataSource | undefined;
|
|
96
113
|
end(saveChanges?: boolean, force?: boolean): Promise<void>;
|
|
97
114
|
get lifecycle(): EditLifecycle;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CommitHandler } from "@vuu-ui/vuu-utils";
|
|
2
|
+
export interface EditControlCheckboxProps {
|
|
3
|
+
className?: string;
|
|
4
|
+
onCommit: CommitHandler<HTMLElement>;
|
|
5
|
+
value?: unknown;
|
|
6
|
+
}
|
|
7
|
+
/** A checkbox that commits its value each time it is toggled. */
|
|
8
|
+
export declare const EditControlCheckbox: ({ className, onCommit, value, }: EditControlCheckboxProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CommitHandler } from "@vuu-ui/vuu-utils";
|
|
2
|
+
export interface DropdownOption {
|
|
3
|
+
label: string;
|
|
4
|
+
value: string;
|
|
5
|
+
}
|
|
6
|
+
export interface EditControlDropdownProps {
|
|
7
|
+
className?: string;
|
|
8
|
+
onCommit: CommitHandler<HTMLElement>;
|
|
9
|
+
/** Either a list of values, or of options with distinct labels. */
|
|
10
|
+
values: readonly string[] | readonly DropdownOption[];
|
|
11
|
+
value?: unknown;
|
|
12
|
+
}
|
|
13
|
+
/** A dropdown that commits its value when an option is selected. */
|
|
14
|
+
export declare const EditControlDropdown: ({ className, onCommit, value, values, }: EditControlDropdownProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LookupTableDetails } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
import type { CommitHandler } from "@vuu-ui/vuu-utils";
|
|
3
|
+
export interface EditControlLookupDropdownProps {
|
|
4
|
+
className?: string;
|
|
5
|
+
lookup: LookupTableDetails;
|
|
6
|
+
onCommit: CommitHandler<HTMLElement>;
|
|
7
|
+
value?: unknown;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A dropdown offering the values of a lookup table, labelled with its
|
|
11
|
+
* `labelColumn`. Commits the `valueColumn` value of the selected row.
|
|
12
|
+
*/
|
|
13
|
+
export declare const EditControlLookupDropdown: ({ className, lookup: { labelColumn, table, valueColumn }, onCommit, value, }: EditControlLookupDropdownProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DataValueDescriptor, TableSchemaTable } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import { type VuuTypeaheadInputProps } from "@vuu-ui/vuu-ui-controls";
|
|
3
|
+
import { type CommitHandler } from "@vuu-ui/vuu-utils";
|
|
4
|
+
import type { InputProps } from "@salt-ds/core";
|
|
5
|
+
/**
|
|
6
|
+
* A rendering hint for the typeahead used in filters, shorthand for
|
|
7
|
+
* `minCharacterCountToTriggerSuggestions`.
|
|
8
|
+
* 'search' needs a character typed before suggestions show (1),
|
|
9
|
+
* 'pick' shows them on focus, like a dropdown (0).
|
|
10
|
+
*/
|
|
11
|
+
export type DataItemEditControlVariant = "search" | "pick";
|
|
12
|
+
/**
|
|
13
|
+
* - "edit" (default): editing a value. Booleans are edited with a checkbox,
|
|
14
|
+
* and a typeahead is used only when the descriptor's `editor` asks for one.
|
|
15
|
+
* - "filter": entering a filter value. Every string column on a Vuu table
|
|
16
|
+
* gets a typeahead, suggesting the values present in the table.
|
|
17
|
+
*/
|
|
18
|
+
export type DataItemEditControlPurpose = "edit" | "filter";
|
|
19
|
+
export interface DataItemEditControlProps {
|
|
20
|
+
InputProps?: Partial<InputProps>;
|
|
21
|
+
TypeaheadProps?: Pick<VuuTypeaheadInputProps, "highlightFirstSuggestion" | "minCharacterCountToTriggerSuggestions" | "selectOnTab">;
|
|
22
|
+
className?: string;
|
|
23
|
+
commitOnBlur?: boolean;
|
|
24
|
+
commitWhenCleared?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* A table column or form field Descriptor. Its `editor`, when present,
|
|
27
|
+
* determines the control.
|
|
28
|
+
*/
|
|
29
|
+
dataDescriptor: DataValueDescriptor;
|
|
30
|
+
editOperation?: "insert" | "update";
|
|
31
|
+
errorMessage?: string;
|
|
32
|
+
onCommit: CommitHandler<HTMLElement>;
|
|
33
|
+
/** @default "edit" */
|
|
34
|
+
purpose?: DataItemEditControlPurpose;
|
|
35
|
+
/** The table the value belongs to. Required for a typeahead. */
|
|
36
|
+
table?: TableSchemaTable;
|
|
37
|
+
variant?: DataItemEditControlVariant;
|
|
38
|
+
}
|
|
39
|
+
export type ValidationStatus = "initial" | true | string;
|
|
40
|
+
export declare const getDataItemEditControl: ({ InputProps, TypeaheadProps, className, commitOnBlur, commitWhenCleared, dataDescriptor, editOperation, errorMessage, onCommit, purpose, table, variant, }: DataItemEditControlProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The edit operation that failed, passed to an `EditErrorHandler`. */
|
|
2
|
+
export type EditOperation = "begin" | "cancel" | "delete" | "save";
|
|
3
|
+
/**
|
|
4
|
+
* Called when an edit session operation fails. When no handler is supplied,
|
|
5
|
+
* hooks log the error to the console.
|
|
6
|
+
*/
|
|
7
|
+
export type EditErrorHandler = (error: Error, operation: EditOperation) => void;
|
|
8
|
+
export declare const toError: (cause: unknown) => Error;
|
|
9
|
+
export declare const reportEditError: (source: string, onError: EditErrorHandler | undefined, cause: unknown, operation: EditOperation) => void;
|
|
@@ -1,16 +1,39 @@
|
|
|
1
1
|
import type { DataRow } from "@vuu-ui/vuu-table-types";
|
|
2
|
-
import type {
|
|
2
|
+
import type { DataValueDescriptor, DataValueValidationChecker } from "@vuu-ui/vuu-data-types";
|
|
3
|
+
import type { VuuColumnDataType, VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
3
4
|
import { type OptionMap } from "../lookup-values/useLookupValues";
|
|
4
5
|
import "./EditField.css";
|
|
5
6
|
export type TextFieldType = "email" | "password";
|
|
6
7
|
export type EditFieldType = "checkbox" | "dropdown" | TextFieldType;
|
|
7
8
|
export interface EditFieldProps {
|
|
8
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Client side validation, run on change and before commit. Invalid values
|
|
11
|
+
* are not sent to the server.
|
|
12
|
+
*/
|
|
13
|
+
clientSideEditValidationCheck?: DataValueValidationChecker;
|
|
14
|
+
/**
|
|
15
|
+
* The row being edited. Use `{ key: EditSession.newRowKey }` for a new row.
|
|
16
|
+
* Defaults to the row of the enclosing `EditForm`.
|
|
17
|
+
*/
|
|
18
|
+
dataRow?: DataRow;
|
|
19
|
+
/**
|
|
20
|
+
* For new rows only. When true, values are staged in the EditSession draft
|
|
21
|
+
* and the row is not added until `editSession.addNewRow()` is called (e.g.
|
|
22
|
+
* from a form Save button). When false, the row is added as soon as the
|
|
23
|
+
* final (or last required) field is committed. Defaults to true inside a
|
|
24
|
+
* create-mode `EditForm`, otherwise false.
|
|
25
|
+
*/
|
|
9
26
|
deferNewRow?: boolean;
|
|
10
27
|
label: string;
|
|
11
28
|
name: string;
|
|
12
29
|
readOnly?: boolean;
|
|
13
30
|
required?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* The server data type of the column. Determines how typed values are
|
|
33
|
+
* converted before commit. Defaults to "boolean" for checkbox fields,
|
|
34
|
+
* otherwise "string".
|
|
35
|
+
*/
|
|
36
|
+
serverDataType?: VuuColumnDataType;
|
|
14
37
|
type?: EditFieldType;
|
|
15
38
|
}
|
|
16
39
|
export interface DropdownEditFieldProps extends EditFieldProps {
|
|
@@ -18,4 +41,15 @@ export interface DropdownEditFieldProps extends EditFieldProps {
|
|
|
18
41
|
optionMap: OptionMap;
|
|
19
42
|
table: VuuTable;
|
|
20
43
|
}
|
|
21
|
-
|
|
44
|
+
/**
|
|
45
|
+
* A field described by a column descriptor, the same descriptor a Table
|
|
46
|
+
* column uses. The control is chosen by `getDataItemEditControl`, from the
|
|
47
|
+
* descriptor's `editor` and `serverDataType`. `editable` and `required` are
|
|
48
|
+
* respected.
|
|
49
|
+
*/
|
|
50
|
+
export interface ColumnEditFieldProps extends Omit<EditFieldProps, "label" | "name" | "readOnly" | "required" | "serverDataType" | "type"> {
|
|
51
|
+
column: DataValueDescriptor;
|
|
52
|
+
/** Defaults to `column.label`, then `column.name` */
|
|
53
|
+
label?: string;
|
|
54
|
+
}
|
|
55
|
+
export declare const EditField: (props: EditFieldProps | DropdownEditFieldProps | ColumnEditFieldProps) => import("react").JSX.Element;
|
|
@@ -4,13 +4,14 @@ import { type ChangeEventHandler, type FocusEventHandler, type KeyboardEvent, ty
|
|
|
4
4
|
import type { LookupOption } from "../lookup-values/useLookupValues";
|
|
5
5
|
import type { TextFieldType } from "./EditField";
|
|
6
6
|
export interface EditFieldHookProps<T extends VuuRowDataItemType = VuuRowDataItemType> {
|
|
7
|
-
column: Pick<RuntimeColumnDescriptor, "clientSideEditValidationCheck" | "label" | "name">;
|
|
7
|
+
column: Pick<RuntimeColumnDescriptor, "clientSideEditValidationCheck" | "label" | "name" | "required">;
|
|
8
8
|
value?: T;
|
|
9
9
|
onEdit?: TableCellEditHandler;
|
|
10
10
|
textType?: TextFieldType;
|
|
11
11
|
type?: "string" | "number" | "boolean";
|
|
12
12
|
}
|
|
13
13
|
export declare const useEditField: <T extends string | number | boolean = string>({ column, value, onEdit, textType, type, }: EditFieldHookProps<T>) => {
|
|
14
|
+
commit: (value?: string) => Promise<boolean>;
|
|
14
15
|
editing: boolean;
|
|
15
16
|
inputProps: {
|
|
16
17
|
onBlur: FocusEventHandler<HTMLElement>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { DataValueDescriptor } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type EditFormProps } from "./EditForm";
|
|
4
|
+
/**
|
|
5
|
+
* A create form field. The control is chosen from its `editor` and
|
|
6
|
+
* `serverDataType` by `getDataItemEditControl`. Required unless
|
|
7
|
+
* `required: false`.
|
|
8
|
+
*/
|
|
9
|
+
export type CreateRowFormField = DataValueDescriptor;
|
|
10
|
+
export interface CreateRowFormProps extends Omit<EditFormProps, "children" | "columns" | "dataRow" | "isEditMode" | "mode" | "requiredColumns"> {
|
|
11
|
+
/** Rendered after the fields. */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Fields, in tab order. Every field is required unless it sets
|
|
15
|
+
* `required: false`. Omit to compose custom editors as `children`, which
|
|
16
|
+
* stage their values with `setValue` from `useEditFormContext`.
|
|
17
|
+
*/
|
|
18
|
+
fields?: readonly CreateRowFormField[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A form that adds one row to `dataSource`, or, given an `editSession`, to
|
|
22
|
+
* that session. Renders an `EditField` per entry in `fields`, stages the values as a new-row draft, and adds the row on
|
|
23
|
+
* Create. Required fields left empty, `validate` errors and server
|
|
24
|
+
* rejections are shown on the form; the row is only added once all pass.
|
|
25
|
+
*
|
|
26
|
+
* The form is single-use: close it from `onSaved`/`onCancelled`, or remount
|
|
27
|
+
* it (change its `key`) to create another row.
|
|
28
|
+
*/
|
|
29
|
+
export declare const CreateRowForm: ({ children, fields, ...props }: CreateRowFormProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import { type EditFormHookProps, type EditFormHookResult } from "../useEditForm";
|
|
3
|
+
import "./EditForm.css";
|
|
4
|
+
export interface EditFormProps extends EditFormHookProps, Omit<HTMLAttributes<HTMLFormElement>, "children" | "onError" | "onSubmit" | "title"> {
|
|
5
|
+
/**
|
|
6
|
+
* Form fields, typically `EditField`s. Pass a function to receive the form
|
|
7
|
+
* state, e.g. to render fields conditionally.
|
|
8
|
+
*/
|
|
9
|
+
children: ReactNode | ((form: EditFormHookResult) => ReactNode);
|
|
10
|
+
/** Return false (or resolve false) to keep editing. See `useConfirmDiscard`. */
|
|
11
|
+
confirmCancel?: () => boolean | Promise<boolean>;
|
|
12
|
+
/** Hide the Cancel button. */
|
|
13
|
+
hideCancel?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* @default "Save" in edit mode, "Done" with direct editing, "Create" in
|
|
16
|
+
* create mode, or "Add" when adding to a shared `editSession`
|
|
17
|
+
*/
|
|
18
|
+
saveLabel?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Set false to render the buttons yourself, with `EditFormButtons`.
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
showButtons?: boolean;
|
|
24
|
+
title?: ReactNode;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A form that edits one row of `dataSource`, or creates a new one. Owns the
|
|
28
|
+
* EditSession (via `useEditForm`), provides it to child `EditField`s, and
|
|
29
|
+
* renders an error banner plus Save/Cancel buttons.
|
|
30
|
+
*
|
|
31
|
+
* In edit mode, fields are editable while `isEditMode` is true (or the
|
|
32
|
+
* enclosing `EditModeProvider` is in edit mode). Create mode is always
|
|
33
|
+
* editable. `EditField`s pick up the form's row (a new-row placeholder in
|
|
34
|
+
* create mode), so need no `dataRow` prop.
|
|
35
|
+
*/
|
|
36
|
+
export declare const EditForm: ({ children, className, columns, confirmCancel, dataRow, dataSource, deleteMode, editModel, editSession: sharedEditSession, editSessionApi, hideCancel, isEditMode, mode, onCancelled, onError, onSaved, requiredColumns, rowDefaults, saveLabel, showButtons, title, validate, ...htmlAttributes }: EditFormProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface EditFormButtonsProps {
|
|
2
|
+
/** Return false (or resolve false) to keep editing. See `useConfirmDiscard`. */
|
|
3
|
+
confirmCancel?: () => boolean | Promise<boolean>;
|
|
4
|
+
/** Hide the Cancel button. */
|
|
5
|
+
hideCancel?: boolean;
|
|
6
|
+
/**
|
|
7
|
+
* @default "Save" in edit mode, "Done" with direct editing, "Create" in
|
|
8
|
+
* create mode, or "Add" when adding to a shared session
|
|
9
|
+
*/
|
|
10
|
+
saveLabel?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Save (or Create) and Cancel buttons for the enclosing `EditForm`. The form
|
|
14
|
+
* renders these itself while editing; render them yourself, with
|
|
15
|
+
* `showButtons={false}` on the form, to place them elsewhere in the form or
|
|
16
|
+
* to show them, disabled, outside edit mode.
|
|
17
|
+
*/
|
|
18
|
+
export declare const EditFormButtons: ({ confirmCancel, hideCancel, saveLabel: saveLabelProp, }: EditFormButtonsProps) => import("react").JSX.Element;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export declare const EditFormContext: import("react").Context<{
|
|
2
|
+
canCancel: boolean;
|
|
3
|
+
editState: import("..").EditState;
|
|
4
|
+
inEditMode: boolean;
|
|
5
|
+
isActive: boolean;
|
|
6
|
+
isEnding: boolean;
|
|
7
|
+
isStarting: boolean;
|
|
8
|
+
lifecycle: import("..").EditLifecycle;
|
|
9
|
+
newRowState: import("..").NewRowState;
|
|
10
|
+
canSave: boolean;
|
|
11
|
+
cancel: () => Promise<void>;
|
|
12
|
+
dataRow: import("@vuu-ui/vuu-table-types").DataRow | undefined;
|
|
13
|
+
editModel: "direct" | "session";
|
|
14
|
+
editSession: import("..").EditSession;
|
|
15
|
+
isSharedSession: boolean;
|
|
16
|
+
fieldEditSession: import("..").DirectEditSession | import("..").EditSession;
|
|
17
|
+
error: Error | undefined;
|
|
18
|
+
fieldErrors: Readonly<Record<string, string>>;
|
|
19
|
+
getValues: () => import("..").EditFormValues;
|
|
20
|
+
isDirty: boolean;
|
|
21
|
+
isEditMode: boolean;
|
|
22
|
+
mode: import("..").EditFormMode;
|
|
23
|
+
rowAdded: boolean;
|
|
24
|
+
saving: boolean;
|
|
25
|
+
setValue: (name: string, value: import("@vuu-ui/vuu-protocol-types").VuuRowDataItemType | undefined) => void;
|
|
26
|
+
submit: (force?: boolean) => Promise<boolean>;
|
|
27
|
+
} | undefined>;
|
|
28
|
+
/**
|
|
29
|
+
* Returns the state of the enclosing `EditForm`, or undefined outside a form.
|
|
30
|
+
* Use it in custom field components to read `fieldErrors`, `mode`, `saving`
|
|
31
|
+
* etc.
|
|
32
|
+
*/
|
|
33
|
+
export declare const useEditFormContext: () => {
|
|
34
|
+
canCancel: boolean;
|
|
35
|
+
editState: import("..").EditState;
|
|
36
|
+
inEditMode: boolean;
|
|
37
|
+
isActive: boolean;
|
|
38
|
+
isEnding: boolean;
|
|
39
|
+
isStarting: boolean;
|
|
40
|
+
lifecycle: import("..").EditLifecycle;
|
|
41
|
+
newRowState: import("..").NewRowState;
|
|
42
|
+
canSave: boolean;
|
|
43
|
+
cancel: () => Promise<void>;
|
|
44
|
+
dataRow: import("@vuu-ui/vuu-table-types").DataRow | undefined;
|
|
45
|
+
editModel: "direct" | "session";
|
|
46
|
+
editSession: import("..").EditSession;
|
|
47
|
+
isSharedSession: boolean;
|
|
48
|
+
fieldEditSession: import("..").DirectEditSession | import("..").EditSession;
|
|
49
|
+
error: Error | undefined;
|
|
50
|
+
fieldErrors: Readonly<Record<string, string>>;
|
|
51
|
+
getValues: () => import("..").EditFormValues;
|
|
52
|
+
isDirty: boolean;
|
|
53
|
+
isEditMode: boolean;
|
|
54
|
+
mode: import("..").EditFormMode;
|
|
55
|
+
rowAdded: boolean;
|
|
56
|
+
saving: boolean;
|
|
57
|
+
setValue: (name: string, value: import("@vuu-ui/vuu-protocol-types").VuuRowDataItemType | undefined) => void;
|
|
58
|
+
submit: (force?: boolean) => Promise<boolean>;
|
|
59
|
+
} | undefined;
|