@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.
Files changed (57) hide show
  1. package/README.md +36 -0
  2. package/package.json +7 -6
  3. package/src/DirectEditSession.js +8 -1
  4. package/src/EditButtons.js +6 -5
  5. package/src/EditModeProvider.js +14 -5
  6. package/src/EditSession.js +37 -6
  7. package/src/edit-control/EditControlCheckbox.js +25 -0
  8. package/src/edit-control/EditControlDropdown.js +34 -0
  9. package/src/edit-control/EditControlLookupDropdown.js +31 -0
  10. package/src/edit-control/get-data-item-edit-control.js +139 -0
  11. package/src/edit-control/index.js +4 -0
  12. package/src/edit-errors.js +7 -0
  13. package/src/edit-field/EditField.js +101 -19
  14. package/src/edit-field/useEditField.js +1 -0
  15. package/src/edit-form/CreateRowForm.js +37 -0
  16. package/src/edit-form/EditForm.css.js +32 -0
  17. package/src/edit-form/EditForm.js +85 -0
  18. package/src/edit-form/EditFormButtons.js +48 -0
  19. package/src/edit-form/EditFormContext.js +4 -0
  20. package/src/edit-form/index.js +4 -0
  21. package/src/form-fields.js +40 -0
  22. package/src/index.js +9 -0
  23. package/src/lookup-values/useLookupOptions.js +106 -0
  24. package/src/useConfirmDiscard.js +34 -0
  25. package/src/useCustomEditField.js +76 -0
  26. package/src/useEditForm.js +306 -0
  27. package/src/useEditSessionState.js +72 -0
  28. package/src/useEditable.js +26 -9
  29. package/src/useEditableColumns.js +105 -0
  30. package/src/useEditableTable.js +21 -8
  31. package/types/DirectEditSession.d.ts +6 -1
  32. package/types/EditButtons.d.ts +20 -3
  33. package/types/EditModeProvider.d.ts +10 -2
  34. package/types/EditSession.d.ts +15 -0
  35. package/types/edit-control/EditControlCheckbox.d.ts +8 -0
  36. package/types/edit-control/EditControlDropdown.d.ts +14 -0
  37. package/types/edit-control/EditControlLookupDropdown.d.ts +13 -0
  38. package/types/edit-control/get-data-item-edit-control.d.ts +40 -0
  39. package/types/edit-control/index.d.ts +4 -0
  40. package/types/edit-errors.d.ts +9 -0
  41. package/types/edit-field/EditField.d.ts +37 -3
  42. package/types/edit-field/useEditField.d.ts +1 -0
  43. package/types/edit-form/CreateRowForm.d.ts +29 -0
  44. package/types/edit-form/EditForm.d.ts +36 -0
  45. package/types/edit-form/EditFormButtons.d.ts +18 -0
  46. package/types/edit-form/EditFormContext.d.ts +59 -0
  47. package/types/edit-form/index.d.ts +4 -0
  48. package/types/form-fields.d.ts +32 -0
  49. package/types/index.d.ts +12 -2
  50. package/types/lookup-values/useLookupOptions.d.ts +35 -0
  51. package/types/useConfirmDiscard.d.ts +23 -0
  52. package/types/useCustomEditField.d.ts +51 -0
  53. package/types/useEditForm.d.ts +127 -0
  54. package/types/useEditSessionState.d.ts +27 -0
  55. package/types/useEditable.d.ts +30 -3
  56. package/types/useEditableColumns.d.ts +79 -0
  57. package/types/useEditableTable.d.ts +18 -3
@@ -0,0 +1,23 @@
1
+ export interface ConfirmDiscardHookProps {
2
+ /** @default "Discard changes?" */
3
+ title?: string;
4
+ /** @default "You have unsaved changes. Discard them?" */
5
+ message?: string;
6
+ /** @default "Discard" */
7
+ confirmButtonLabel?: string;
8
+ /** @default "Keep editing" */
9
+ cancelButtonLabel?: string;
10
+ }
11
+ /**
12
+ * Returns a function that asks the user to confirm discarding unsaved
13
+ * changes. Resolves true if the user confirms. Pass it to
14
+ * `EditButtons.confirmCancel`, or call it before changing selection or
15
+ * closing a form.
16
+ *
17
+ * Requires a `ModalProvider` in scope.
18
+ *
19
+ * @example
20
+ * const confirmDiscard = useConfirmDiscard();
21
+ * <EditButtons confirmCancel={confirmDiscard} ... />
22
+ */
23
+ export declare const useConfirmDiscard: ({ cancelButtonLabel, confirmButtonLabel, message, title, }?: ConfirmDiscardHookProps) => () => Promise<boolean>;
@@ -0,0 +1,51 @@
1
+ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
+ import type { EditSession, EditSessionValue } from "./EditSession";
3
+ export interface CustomEditFieldHookProps<T extends EditSessionValue> {
4
+ /** Key of the row being edited. */
5
+ rowKey: string;
6
+ /** The column the value is saved to. */
7
+ name: string;
8
+ /**
9
+ * The saved value. The field resets to this value when it changes, and
10
+ * when the edit session ends.
11
+ */
12
+ originalValue: T | undefined;
13
+ /**
14
+ * Compares an edited value with `originalValue`. When equal, the edit is
15
+ * reverted rather than committed, so the session is no longer dirty.
16
+ * @default Object.is
17
+ */
18
+ equals?: (a: T, b: T) => boolean;
19
+ /**
20
+ * Converts the value to the scalar sent to the server. Required when `T`
21
+ * is an object.
22
+ */
23
+ serialize?: (value: T) => VuuRowDataItemType;
24
+ /** Defaults to the session from the enclosing DataEditingProvider. */
25
+ editSession?: EditSession;
26
+ /** Called when a commit fails. */
27
+ onError?: (error: Error) => void;
28
+ }
29
+ /**
30
+ * Adapts a non-standard editor (a picker, a list editor, any custom control)
31
+ * to an EditSession. Tracks the current and original values, reverts the
32
+ * edit when the user restores the original value, and commits to the session
33
+ * table with `serialize(value)`.
34
+ *
35
+ * @example
36
+ * const { value, setValue, isDirty } = useCustomEditField({
37
+ * rowKey: dataRow.key,
38
+ * name: "permissions",
39
+ * originalValue: permissions,
40
+ * equals: (a, b) => a.equals(b),
41
+ * serialize: (p) => JSON.stringify(p.toJSON()),
42
+ * });
43
+ */
44
+ export declare const useCustomEditField: <T extends EditSessionValue>({ editSession: editSessionProp, equals, name, onError, originalValue, rowKey, serialize, }: CustomEditFieldHookProps<T>) => {
45
+ committing: boolean;
46
+ error: Error | undefined;
47
+ isDirty: boolean;
48
+ reset: () => void;
49
+ setValue: (nextValue: T) => Promise<void>;
50
+ value: T | undefined;
51
+ };
@@ -0,0 +1,127 @@
1
+ import type { DataSource, DeleteRowMode } from "@vuu-ui/vuu-data-types";
2
+ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
3
+ import type { DataRow } from "@vuu-ui/vuu-table-types";
4
+ import { EditSession, type EditSessionApi, type EditSessionValue, type RowDefaultDataItemValues } from "./EditSession";
5
+ import { DirectEditSession } from "./DirectEditSession";
6
+ import { type EditErrorHandler } from "./edit-errors";
7
+ export type EditFormMode = "edit" | "create";
8
+ /**
9
+ * Edit mode only.
10
+ * - `session`: edits are staged in a session table and applied on Save.
11
+ * - `direct`: each field is validated and sent to `dataSource` as it is
12
+ * committed. There is nothing to save; `submit` just ends editing.
13
+ */
14
+ export type EditFormEditModel = "session" | "direct";
15
+ export type EditFormValues = Readonly<Record<string, EditSessionValue | undefined>>;
16
+ /** Field name to error message. Return an empty object (or nothing) when valid. */
17
+ export type EditFormFieldErrors = Readonly<Record<string, string>>;
18
+ /**
19
+ * Cross-field validation run before the form is saved. Return error messages
20
+ * keyed by field name.
21
+ */
22
+ export type EditFormValidator = (values: EditFormValues) => EditFormFieldErrors | undefined;
23
+ export interface EditFormHookProps {
24
+ /** Source table data source. */
25
+ dataSource: DataSource;
26
+ /**
27
+ * The row being edited (edit mode). Its values, merged with any edits, are
28
+ * passed to `validate`.
29
+ */
30
+ dataRow?: DataRow;
31
+ /**
32
+ * `"edit"` edits the selected row(s) of `dataSource`. `"create"` adds a
33
+ * new row via an empty session table.
34
+ * @default "edit"
35
+ */
36
+ mode?: EditFormMode;
37
+ /** Create mode: the form's fields, in tab order. */
38
+ columns?: readonly string[];
39
+ /**
40
+ * Fields that must have a value. In create mode, defaults to `columns`. In
41
+ * direct edit mode, an empty value for one of these is not sent.
42
+ */
43
+ requiredColumns?: readonly string[];
44
+ /** @default "session" */
45
+ editModel?: EditFormEditModel;
46
+ /**
47
+ * Create mode only: add the new row to this session, owned by someone else
48
+ * (e.g. a table's edit session), rather than to a session of the form's
49
+ * own. Create then adds the row to the session without saving it; the
50
+ * owner saves or cancels the session.
51
+ */
52
+ editSession?: EditSession;
53
+ /**
54
+ * Edit mode only: whether the edit session is active. When omitted, edit
55
+ * mode is read from the nearest EditModeProvider. Create mode is always
56
+ * active while the form is mounted.
57
+ */
58
+ isEditMode?: boolean;
59
+ /** @default "soft" */
60
+ deleteMode?: DeleteRowMode;
61
+ /** @default "createSessionDataSource" */
62
+ editSessionApi?: EditSessionApi;
63
+ /** Default column values merged into the new row. Pass a stable reference. */
64
+ rowDefaults?: RowDefaultDataItemValues;
65
+ validate?: EditFormValidator;
66
+ /** Called once changes have been saved. */
67
+ onSaved?: () => void;
68
+ /** Called once the form has been cancelled. */
69
+ onCancelled?: () => void;
70
+ /** Called when begin, save or cancel fails. Defaults to console.error. */
71
+ onError?: EditErrorHandler;
72
+ }
73
+ /**
74
+ * Plain column values of a row. Table DataRows are Proxies with no own
75
+ * keys, so they cannot be spread; they serialize via `toJSON`.
76
+ */
77
+ export declare const getDataRowValues: (dataRow: DataRow) => Record<string, VuuRowDataItemType>;
78
+ /**
79
+ * Owns an EditSession for a form that edits one entity, or creates one.
80
+ *
81
+ * - edit mode copies the selected row into a session table (`"Selected"`).
82
+ * - create mode begins an empty session table (`"Empty"`) and stages field
83
+ * values as a new-row draft. Render fields with
84
+ * `dataRow={{ key: EditSession.newRowKey }}` and `deferNewRow`.
85
+ *
86
+ * Call `submit()` to validate and save, `cancel()` to discard. Wrap the form
87
+ * in a `DataEditingProvider` with the returned `fieldEditSession`.
88
+ *
89
+ * With `editModel: "direct"` each field is validated and sent as it is
90
+ * committed. With a shared `editSession` (create mode), Create adds the row
91
+ * to that session, which its owner saves.
92
+ */
93
+ export declare const useEditForm: ({ columns, dataRow, dataSource, deleteMode, editModel, editSession: sharedEditSession, editSessionApi, isEditMode: isEditModeProp, mode, onCancelled, onError, onSaved, requiredColumns, rowDefaults, validate, }: EditFormHookProps) => {
94
+ canCancel: boolean;
95
+ editState: import("./EditSession").EditState;
96
+ inEditMode: boolean;
97
+ isActive: boolean;
98
+ isEnding: boolean;
99
+ isStarting: boolean;
100
+ lifecycle: import("./EditSession").EditLifecycle;
101
+ newRowState: import("./EditSession").NewRowState;
102
+ canSave: boolean;
103
+ cancel: () => Promise<void>;
104
+ /**
105
+ * The row being edited, a new-row placeholder in create mode. With
106
+ * direct editing, includes the values sent so far.
107
+ */
108
+ dataRow: DataRow | undefined;
109
+ editModel: "direct" | "session";
110
+ editSession: EditSession;
111
+ /** Create mode: rows are added to a session owned by someone else. */
112
+ isSharedSession: boolean;
113
+ /** The session fields commit their edits to. */
114
+ fieldEditSession: DirectEditSession | EditSession;
115
+ error: Error | undefined;
116
+ fieldErrors: Readonly<Record<string, string>>;
117
+ getValues: () => EditFormValues;
118
+ isDirty: boolean;
119
+ isEditMode: boolean;
120
+ mode: EditFormMode;
121
+ /** Create mode: the row was added, saving the session failed. */
122
+ rowAdded: boolean;
123
+ saving: boolean;
124
+ setValue: (name: string, value: VuuRowDataItemType) => void;
125
+ submit: (force?: boolean) => Promise<boolean>;
126
+ };
127
+ export type EditFormHookResult = ReturnType<typeof useEditForm>;
@@ -0,0 +1,27 @@
1
+ import type { EditLifecycle, EditSession, EditState, NewRowState } from "./EditSession";
2
+ export interface EditSessionStateSnapshot {
3
+ /** True when the session can be cancelled (active, or a failed save). */
4
+ canCancel: boolean;
5
+ /** True when there are valid, unsaved changes (or a stale save to force). */
6
+ canSave: boolean;
7
+ editState: EditState;
8
+ /** The error from the most recent failed begin or end, if any. */
9
+ error?: Error;
10
+ /** True while a session is active, including while it is ending. */
11
+ inEditMode: boolean;
12
+ isActive: boolean;
13
+ isDirty: boolean;
14
+ isEnding: boolean;
15
+ isStarting: boolean;
16
+ lifecycle: EditLifecycle;
17
+ newRowState: NewRowState;
18
+ }
19
+ /**
20
+ * Subscribes to an EditSession and returns a single, render-safe snapshot of
21
+ * its state: editState, lifecycle, new-row draft and the derived flags most
22
+ * UIs need (canSave, canCancel, isDirty etc).
23
+ *
24
+ * Prefer this to adding `editState`/`lifecycle`/`newRow` listeners by hand.
25
+ * Returns an idle, clean snapshot when no session is provided.
26
+ */
27
+ export declare const useEditSessionState: (editSession: EditSession | undefined) => EditSessionStateSnapshot;
@@ -1,13 +1,40 @@
1
- import type { CopyOption, DataSource } from "@vuu-ui/vuu-data-types";
2
- import { EditSession } from "./EditSession";
1
+ import type { CopyOption, DataSource, DeleteRowMode } from "@vuu-ui/vuu-data-types";
2
+ import { EditSession, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
3
+ import { type EditErrorHandler } from "./edit-errors";
3
4
  export interface EditableHookProps {
5
+ /** @default "Selected" */
4
6
  copyOption?: CopyOption;
5
7
  dataSource: DataSource;
8
+ /** @default "soft" */
9
+ deleteMode?: DeleteRowMode;
10
+ /** @default "createSessionDataSource" */
11
+ editSessionApi?: EditSessionApi;
12
+ /**
13
+ * Controls the edit session. When omitted, edit mode is read from the
14
+ * nearest EditModeProvider.
15
+ */
16
+ isEditMode?: boolean;
6
17
  onCancel: () => void;
18
+ /** Called when begin, save or cancel fails. Defaults to console.error. */
19
+ onError?: EditErrorHandler;
7
20
  onSave: () => void;
21
+ /** Default column values applied to every addRow call. Pass a stable reference. */
22
+ rowDefaults?: RowDefaultDataItemValues;
8
23
  }
9
- export declare const useEditable: ({ copyOption, dataSource, onCancel, onSave, }: EditableHookProps) => {
24
+ /**
25
+ * Owns an EditSession for a form (or any non-table editor). The session is
26
+ * begun when edit mode is entered and ended (without saving) when it is left.
27
+ * Wrap the editor in a DataEditingProvider with the returned editSession.
28
+ *
29
+ * For a higher level API, with create mode and validation, see `useEditForm`.
30
+ */
31
+ export declare const useEditable: ({ copyOption, dataSource, deleteMode, editSessionApi, isEditMode: isEditModeProp, onCancel, onError, onSave, rowDefaults, }: EditableHookProps) => {
32
+ canCancel: boolean;
33
+ canSave: boolean;
10
34
  editSession: EditSession;
35
+ editState: import("./EditSession").EditState;
36
+ isEditMode: boolean;
37
+ lifecycle: import("./EditSession").EditLifecycle;
11
38
  onCancel: () => Promise<void>;
12
39
  onSave: (force?: boolean) => Promise<void>;
13
40
  };
@@ -0,0 +1,79 @@
1
+ import type { DataEditable, TableSchema } from "@vuu-ui/vuu-data-types";
2
+ import type { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
3
+ /** Columns maintained by the Vuu server, never editable by default. */
4
+ export declare const DEFAULT_READ_ONLY_COLUMNS: readonly ["vuuCreatedTimestamp", "vuuUpdatedTimestamp", "vuuMsg"];
5
+ /** The client-side column that hosts the `UndoCellRenderer`. */
6
+ export declare const UNDO_COLUMN: ColumnDescriptor;
7
+ export interface EditableColumnsHookProps {
8
+ /**
9
+ * Column descriptors used in view mode and as the base for edit mode. A
10
+ * column's `editable` narrows what the server schema allows, its `editor`
11
+ * is used as the cell editor in edit mode.
12
+ */
13
+ columns: readonly ColumnDescriptor[];
14
+ /**
15
+ * Whether the table is currently showing the edit session. Typically
16
+ * `isEditSessionReady` from `useEditableTable`. When false, editing is
17
+ * switched off for every column.
18
+ */
19
+ isEditing: boolean;
20
+ /**
21
+ * The schema of the table being edited. A column the schema marks as not
22
+ * editable can never be edited, whatever the column descriptor says.
23
+ * Columns with no `editable` flag in the schema are editable.
24
+ */
25
+ schema?: TableSchema;
26
+ /**
27
+ * Append the client-side undo column while editing. Pass a partial
28
+ * descriptor to override defaults such as width.
29
+ */
30
+ undoColumn?: boolean | Partial<ColumnDescriptor>;
31
+ /**
32
+ * Append the session table's `vuuAction` column while editing, either
33
+ * hidden (it still drives row styling) or visible.
34
+ */
35
+ actionColumn?: "hidden" | "visible" | false;
36
+ /**
37
+ * When true, edit-mode columns are built from `editSchema` rather than
38
+ * `columns`. Pass `columnsDiverge` and `editSchema` from `useEditableTable`.
39
+ * Descriptors in `columns` with a matching name are used as a base so that
40
+ * labels, widths and formatting are preserved.
41
+ */
42
+ columnsDiverge?: boolean;
43
+ editSchema?: TableSchema;
44
+ }
45
+ /**
46
+ * Combines the schema and column `editable` values. The column can only
47
+ * narrow what the schema allows.
48
+ */
49
+ export declare const narrowEditable: (schemaEditable?: DataEditable, columnEditable?: DataEditable) => DataEditable;
50
+ /**
51
+ * Builds edit-mode column descriptors. Exposed for use outside React, e.g. in
52
+ * tests or when composing a TableConfig by hand. Most code should use
53
+ * `useEditableColumns`.
54
+ */
55
+ export declare const getEditableColumns: ({ actionColumn, columns, columnsDiverge, editSchema, isEditing, schema, undoColumn, }: EditableColumnsHookProps) => readonly ColumnDescriptor[];
56
+ /**
57
+ * Maps view-mode column descriptors to edit-mode descriptors and adds the
58
+ * action and undo columns. Editability comes from the schema, narrowed by
59
+ * each column's `editable`; a column's `editor` becomes its cell editor.
60
+ *
61
+ * When `isEditing` is false, editing is switched off for every column.
62
+ *
63
+ * @example
64
+ * const columns = useEditableColumns({
65
+ * columns: [
66
+ * { name: "ric", editable: { insert: true, update: false } },
67
+ * {
68
+ * name: "currency",
69
+ * editor: { name: "dropdown-cell", values: ["EUR", "GBP", "USD"] },
70
+ * },
71
+ * { name: "lastUpdate", editable: false },
72
+ * ],
73
+ * isEditing: isEditSessionReady,
74
+ * schema: dataSource.tableSchema,
75
+ * undoColumn: true,
76
+ * actionColumn: "hidden",
77
+ * });
78
+ */
79
+ export declare const useEditableColumns: ({ actionColumn, columns, columnsDiverge, editSchema, isEditing, schema, undoColumn, }: EditableColumnsHookProps) => readonly ColumnDescriptor[];
@@ -1,7 +1,8 @@
1
1
  import type { CopyOption, DataSource, DeleteRowMode } from "@vuu-ui/vuu-data-types";
2
2
  import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
3
3
  import type { DataRow } from "@vuu-ui/vuu-table-types";
4
- import { EditSession, type EditActionType, type EditLifecycle, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
4
+ import { EditSession, type EditActionType, type EditLifecycle, type EditSessionApi, type EditState, type RowDefaultDataItemValues } from "./EditSession";
5
+ import { type EditErrorHandler } from "./edit-errors";
5
6
  export type EditMode = "edit" | "view";
6
7
  export interface EditableTableHookProps {
7
8
  /**
@@ -11,11 +12,23 @@ export interface EditableTableHookProps {
11
12
  columns?: string[];
12
13
  dataSource?: DataSource;
13
14
  addRowsCount?: number;
15
+ /** @default "soft" */
14
16
  deleteMode?: DeleteRowMode;
17
+ /** @default "createSessionDataSource" */
15
18
  editSessionApi?: EditSessionApi;
19
+ /** Rows copied into the session table when editing begins. @default "All" */
16
20
  copyOption?: CopyOption;
17
- isEditMode: boolean;
21
+ /**
22
+ * Begins (true) or ends (false) the edit session. When omitted, edit mode
23
+ * is read from the nearest EditModeProvider.
24
+ */
25
+ isEditMode?: boolean;
18
26
  onCancel: () => void;
27
+ /**
28
+ * Called when begin, save, cancel or delete fails. Defaults to
29
+ * console.error. Use this to surface errors, e.g. as a notification.
30
+ */
31
+ onError?: EditErrorHandler;
19
32
  onSave: () => void;
20
33
  /** Default column values applied to every addRow call. Pass a stable reference — a new object triggers EditSession recreation. */
21
34
  rowDefaults?: RowDefaultDataItemValues;
@@ -25,13 +38,15 @@ export interface EditableTableHookProps {
25
38
  */
26
39
  table?: VuuTable;
27
40
  }
28
- export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, deleteMode, editSessionApi, copyOption, isEditMode, onCancel, onSave, rowDefaults, table, }: EditableTableHookProps) => {
41
+ export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, deleteMode, editSessionApi, copyOption, isEditMode: isEditModeProp, onCancel, onError, onSave, rowDefaults, table, }: EditableTableHookProps) => {
29
42
  canCancel: boolean;
30
43
  canSave: boolean;
31
44
  columnsDiverge: boolean;
32
45
  dataSource: DataSource;
33
46
  editSchema: import("@vuu-ui/vuu-data-types").TableSchema | undefined;
34
47
  editSession: EditSession;
48
+ editState: EditState;
49
+ isEditMode: boolean;
35
50
  lifecycle: EditLifecycle;
36
51
  hasSelection: boolean;
37
52
  isEditSessionReady: boolean;