@vuu-ui/vuu-data-editing 5.0.0-alpha.2 → 5.0.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/package.json +7 -6
- package/src/DirectEditSession.js +8 -1
- package/src/EditButtons.js +6 -5
- package/src/EditModeProvider.js +14 -5
- package/src/EditSession.js +37 -6
- package/src/edit-control/EditControlCheckbox.js +25 -0
- package/src/edit-control/EditControlDropdown.js +34 -0
- package/src/edit-control/EditControlLookupDropdown.js +31 -0
- package/src/edit-control/get-data-item-edit-control.js +139 -0
- package/src/edit-control/index.js +4 -0
- package/src/edit-errors.js +7 -0
- package/src/edit-field/EditField.js +101 -19
- package/src/edit-field/useEditField.js +1 -0
- package/src/edit-form/CreateRowForm.js +37 -0
- package/src/edit-form/EditForm.css.js +32 -0
- package/src/edit-form/EditForm.js +85 -0
- package/src/edit-form/EditFormButtons.js +48 -0
- package/src/edit-form/EditFormContext.js +4 -0
- package/src/edit-form/index.js +4 -0
- package/src/form-fields.js +40 -0
- package/src/index.js +9 -0
- package/src/lookup-values/useLookupOptions.js +106 -0
- package/src/useConfirmDiscard.js +34 -0
- package/src/useCustomEditField.js +76 -0
- package/src/useEditForm.js +306 -0
- package/src/useEditSessionState.js +72 -0
- package/src/useEditable.js +26 -9
- package/src/useEditableColumns.js +105 -0
- package/src/useEditableTable.js +21 -8
- package/types/DirectEditSession.d.ts +6 -1
- package/types/EditButtons.d.ts +20 -3
- package/types/EditModeProvider.d.ts +10 -2
- package/types/EditSession.d.ts +15 -0
- package/types/edit-control/EditControlCheckbox.d.ts +8 -0
- package/types/edit-control/EditControlDropdown.d.ts +14 -0
- package/types/edit-control/EditControlLookupDropdown.d.ts +13 -0
- package/types/edit-control/get-data-item-edit-control.d.ts +40 -0
- package/types/edit-control/index.d.ts +4 -0
- package/types/edit-errors.d.ts +9 -0
- package/types/edit-field/EditField.d.ts +37 -3
- package/types/edit-field/useEditField.d.ts +1 -0
- package/types/edit-form/CreateRowForm.d.ts +29 -0
- package/types/edit-form/EditForm.d.ts +36 -0
- package/types/edit-form/EditFormButtons.d.ts +18 -0
- package/types/edit-form/EditFormContext.d.ts +59 -0
- package/types/edit-form/index.d.ts +4 -0
- package/types/form-fields.d.ts +32 -0
- package/types/index.d.ts +12 -2
- package/types/lookup-values/useLookupOptions.d.ts +35 -0
- package/types/useConfirmDiscard.d.ts +23 -0
- package/types/useCustomEditField.d.ts +51 -0
- package/types/useEditForm.d.ts +127 -0
- package/types/useEditSessionState.d.ts +27 -0
- package/types/useEditable.d.ts +30 -3
- package/types/useEditableColumns.d.ts +79 -0
- package/types/useEditableTable.d.ts +18 -3
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
|
]);
|
|
@@ -120,9 +131,9 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
120
131
|
const transition = isEditMode ? editSession.begin(copyOption) : editSession.end();
|
|
121
132
|
transition.catch((error)=>{
|
|
122
133
|
if (isEditMode) {
|
|
123
|
-
|
|
134
|
+
reportEditError("useEditableTable", onErrorRef.current, error, "begin");
|
|
124
135
|
onCancelRef.current();
|
|
125
|
-
} else
|
|
136
|
+
} else reportEditError("useEditableTable", onErrorRef.current, error, "cancel");
|
|
126
137
|
});
|
|
127
138
|
}, [
|
|
128
139
|
copyOption,
|
|
@@ -130,7 +141,7 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
130
141
|
isEditMode
|
|
131
142
|
]);
|
|
132
143
|
const canCancel = "active" === lifecycle.status || "error" === lifecycle.status && "end" === lifecycle.operation;
|
|
133
|
-
const canSave = canCancel && ("dirty" === editState || "stale" === editState)
|
|
144
|
+
const canSave = canCancel && ("dirty" === editState || "stale" === editState);
|
|
134
145
|
const isEditSessionReady = isEditMode && void 0 !== sessionDataSource && sessionDataSource === editSession.sessionDataSource && subscribedSessionDataSource === sessionDataSource && "subscribed" === sessionDataSource.status && void 0 !== sessionDataSource.tableSchema;
|
|
135
146
|
const editSchema = subscribedSessionDataSource?.tableSchema;
|
|
136
147
|
const viewSchema = sourceDataSource.tableSchema;
|
|
@@ -142,6 +153,8 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
|
|
|
142
153
|
dataSource,
|
|
143
154
|
editSchema,
|
|
144
155
|
editSession,
|
|
156
|
+
editState,
|
|
157
|
+
isEditMode,
|
|
145
158
|
lifecycle,
|
|
146
159
|
hasSelection: selectionCount > 0,
|
|
147
160
|
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";
|
|
@@ -79,10 +85,17 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> impleme
|
|
|
79
85
|
isNewRowComplete(): boolean;
|
|
80
86
|
configureNewRow(columns: readonly string[], requiredColumns?: readonly string[]): void;
|
|
81
87
|
setNewRowValue(column: string, value: VuuRowDataItemType): void;
|
|
88
|
+
/** Discards the new-row draft, leaving the rest of the session untouched. */
|
|
89
|
+
clearNewRow(): void;
|
|
82
90
|
addNewRow(): Promise<RpcResult>;
|
|
83
91
|
deleteSelectedRows(): Promise<RpcResult>;
|
|
84
92
|
addRow(rowData?: Record<string, VuuRowDataItemType>): Promise<RpcResult>;
|
|
85
93
|
hasRowChanges(key: string): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* The edited values for a row, keyed by column name. Includes invalid
|
|
96
|
+
* edits. Returns an empty object when the row has no edits.
|
|
97
|
+
*/
|
|
98
|
+
getEditedValues(key: string): Record<string, EditSessionValue>;
|
|
86
99
|
isCellEdited(key: string, columnName: string): boolean;
|
|
87
100
|
undoRowChange(key: string, action: EditActionType): Promise<RpcResult | undefined>;
|
|
88
101
|
begin(copyOption?: CopyOption, sessionType?: SessionType): Promise<DataSource>;
|
|
@@ -92,6 +105,8 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> impleme
|
|
|
92
105
|
*/
|
|
93
106
|
reconcileWithSessionSchema(): void;
|
|
94
107
|
get dataSource(): import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
|
|
108
|
+
/** The dataSource of the table being edited. */
|
|
109
|
+
get sourceDataSource(): DataSource | undefined;
|
|
95
110
|
get sessionDataSource(): DataSource | undefined;
|
|
96
111
|
end(saveChanges?: boolean, force?: boolean): Promise<void>;
|
|
97
112
|
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;
|
|
@@ -11,6 +11,7 @@ export interface EditFieldHookProps<T extends VuuRowDataItemType = VuuRowDataIte
|
|
|
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) => 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) => void;
|
|
58
|
+
submit: (force?: boolean) => Promise<boolean>;
|
|
59
|
+
} | undefined;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { CreateRowForm, type CreateRowFormField, type CreateRowFormProps, } from "./CreateRowForm";
|
|
2
|
+
export { EditForm, type EditFormProps } from "./EditForm";
|
|
3
|
+
export { EditFormButtons, type EditFormButtonsProps, } from "./EditFormButtons";
|
|
4
|
+
export { useEditFormContext } from "./EditFormContext";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { DataValueDescriptor, TableSchema } from "@vuu-ui/vuu-data-types";
|
|
2
|
+
export interface FormFieldsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Table columns, e.g. `config.columns`. Their `label`, `editor`, `type`
|
|
5
|
+
* and `editable` carry over to the fields, and they come first, in this
|
|
6
|
+
* order.
|
|
7
|
+
*/
|
|
8
|
+
columns?: readonly DataValueDescriptor[];
|
|
9
|
+
/**
|
|
10
|
+
* Choose the fields, in order, rather than generating one per schema
|
|
11
|
+
* column. Each is still overlaid on its schema column (and table column).
|
|
12
|
+
*/
|
|
13
|
+
fields?: readonly DataValueDescriptor[];
|
|
14
|
+
/**
|
|
15
|
+
* `insert` for a create form: fields that can't be set on a new row are
|
|
16
|
+
* left out. @default "update"
|
|
17
|
+
*/
|
|
18
|
+
operation?: "insert" | "update";
|
|
19
|
+
/** The server schema, usually `dataSource.tableSchema`. */
|
|
20
|
+
schema?: TableSchema;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The fields of a generated form: one per schema column, except the
|
|
24
|
+
* server-maintained `vuuCreatedTimestamp`, `vuuUpdatedTimestamp` and
|
|
25
|
+
* `vuuMsg`. Columns also in `columns` come first, in that order, then the
|
|
26
|
+
* rest in schema order. Each field is the schema column overlaid with the
|
|
27
|
+
* matching table column, so a column's `label`, `editor` and `type` carry
|
|
28
|
+
* over, and its `editable` can narrow, never widen, the server's.
|
|
29
|
+
*
|
|
30
|
+
* Without a schema, the fields are generated from `columns`.
|
|
31
|
+
*/
|
|
32
|
+
export declare const getFormFields: ({ columns, fields, operation, schema, }: FormFieldsOptions) => DataValueDescriptor[];
|
package/types/index.d.ts
CHANGED
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
export { DataEditingProvider, useEditSession, useTableEditSession, } from "./DataEditingProvider";
|
|
2
|
-
export { DirectEditSession, type DirectEditSessionConstructorProps, } from "./DirectEditSession";
|
|
2
|
+
export { DirectEditSession, type DirectEditSessionConstructorProps, type DirectEditValidator, } from "./DirectEditSession";
|
|
3
3
|
export type { TableEditSession, TableEditSessionEvents, } from "./TableEditSession";
|
|
4
4
|
export { useCellEdited } from "./useCellEdited";
|
|
5
5
|
export { useEditState } from "./useEditState";
|
|
6
|
+
export { useEditSessionState, type EditSessionStateSnapshot, } from "./useEditSessionState";
|
|
7
|
+
export type { EditErrorHandler, EditOperation } from "./edit-errors";
|
|
6
8
|
export { getVuuEditMessage, isInlineEditingSession, isEditRowReadOnly, withDataRowEditErrors, } from "./edit-utils";
|
|
7
9
|
export { EditButtons, type EditButtonProps } from "./EditButtons";
|
|
8
10
|
export { EditField, type DropdownEditFieldProps, type EditFieldProps, type EditFieldType, type TextFieldType, } from "./edit-field/EditField";
|
|
9
11
|
export { useEditField, type EditFieldHookProps, } from "./edit-field/useEditField";
|
|
10
12
|
export { EditModeProvider, useEditMode, type EditModeContextProps, } from "./EditModeProvider";
|
|
11
|
-
export { EditError, EditSession, SupersededEditError, type AddRowResultData, type DeleteSelectedRowsResultData, type EditActionType, type EditCommitOptions, type EditLifecycle, type EditSessionConstructorProps, type EditSessionValue, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
|
|
13
|
+
export { EditError, EditSession, SupersededEditError, type AddRowResultData, type DeleteSelectedRowsResultData, type EditActionType, type EditCommitOptions, type EditLifecycle, type EditSessionApi, type EditSessionConstructorProps, type EditSessionValue, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
|
|
12
14
|
export { StaleUpdateError } from "@vuu-ui/vuu-utils";
|
|
13
15
|
export { lookupOptionsFromRows, useLookupValues, type LookupOption, type LookupValuedHookProps, type OptionMap, } from "./lookup-values/useLookupValues";
|
|
14
16
|
export { useEditable, type EditableHookProps } from "./useEditable";
|
|
15
17
|
export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator, } from "./editActionRowClassNameGenerator";
|
|
16
18
|
export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps, } from "./UndoCellRenderer";
|
|
17
19
|
export { useEditableTable, type EditableTableHookProps, type EditMode, } from "./useEditableTable";
|
|
20
|
+
export { DEFAULT_READ_ONLY_COLUMNS, getEditableColumns, UNDO_COLUMN, useEditableColumns, narrowEditable, type EditableColumnsHookProps, } from "./useEditableColumns";
|
|
21
|
+
export { getDataRowValues, useEditForm, type EditFormFieldErrors, type EditFormHookProps, type EditFormHookResult, type EditFormMode, type EditFormValidator, type EditFormValues, } from "./useEditForm";
|
|
22
|
+
export { useLookupOptions, type LookupOptionsHookProps, type LookupOptionsHookResult, type LookupRow, } from "./lookup-values/useLookupOptions";
|
|
23
|
+
export { useCustomEditField, type CustomEditFieldHookProps, } from "./useCustomEditField";
|
|
24
|
+
export { useConfirmDiscard, type ConfirmDiscardHookProps, } from "./useConfirmDiscard";
|
|
25
|
+
export * from "./edit-form";
|
|
26
|
+
export * from "./edit-control";
|
|
27
|
+
export { type FormFieldsOptions, getFormFields } from "./form-fields";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { VuuRowDataItemType, VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
2
|
+
import { type LookupOption, type OptionMap } from "./useLookupValues";
|
|
3
|
+
export type LookupRow = Readonly<Record<string, VuuRowDataItemType>>;
|
|
4
|
+
export interface LookupOptionsHookProps<T = LookupOption> {
|
|
5
|
+
/** @default true */
|
|
6
|
+
enabled?: boolean;
|
|
7
|
+
/** The lookup table. */
|
|
8
|
+
table?: VuuTable;
|
|
9
|
+
/**
|
|
10
|
+
* Maps rows to `LookupOption` (`{ value, label, metadata }`). Either
|
|
11
|
+
* `optionMap` or `columns` plus `mapRow` is required.
|
|
12
|
+
*/
|
|
13
|
+
optionMap?: OptionMap;
|
|
14
|
+
/** Columns to subscribe to. Defaults to the columns named in `optionMap`. */
|
|
15
|
+
columns?: readonly string[];
|
|
16
|
+
/** Maps a row (column name to value) to an option. Overrides `optionMap`. */
|
|
17
|
+
mapRow?: (row: LookupRow) => T;
|
|
18
|
+
/** A Vuu filter expression, e.g. `ccy = "EUR"`. */
|
|
19
|
+
filter?: string;
|
|
20
|
+
/** @default 100 */
|
|
21
|
+
maxRows?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface LookupOptionsHookResult<T> {
|
|
24
|
+
options: T[];
|
|
25
|
+
loading: boolean;
|
|
26
|
+
error?: Error;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Subscribes to a (typically small) lookup table and maps its rows to
|
|
30
|
+
* options, e.g. for a dropdown. Unlike `useLookupValues`, this supports any
|
|
31
|
+
* row mapping, a filter and a row limit, and reports `loading` and `error`.
|
|
32
|
+
*
|
|
33
|
+
* Pass stable (memoized) values for `optionMap`, `columns` and `mapRow`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const useLookupOptions: <T = LookupOption>({ columns: columnsProp, enabled, filter, mapRow, maxRows, optionMap, table, }: LookupOptionsHookProps<T>) => LookupOptionsHookResult<T>;
|