@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
@@ -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 and or table changed");
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
- console.error("[useEditableTable] cancel edit session failed", error);
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
- console.error("[useEditableTable] save edit session failed", error);
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
- await editSession.deleteSelectedRows();
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
- console.error("[useEditableTable] begin edit session failed", error);
134
+ reportEditError("useEditableTable", onErrorRef.current, error, "begin");
124
135
  onCancelRef.current();
125
- } else console.error("[useEditableTable] end edit session failed", error);
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) && 0 === editSession.invalidCount;
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;
@@ -1,14 +1,31 @@
1
1
  import type { EditSession } from "./EditSession";
2
2
  export interface EditButtonProps {
3
- canCancel: boolean;
4
- canSave: boolean;
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
- export declare const EditButtons: ({ canCancel, canSave: _canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
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
- * Implemented as a standalone Provider so that EditMode cna be implemented
8
- * at higher level than individual edit controls.
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;
@@ -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,4 @@
1
+ export * from "./EditControlCheckbox";
2
+ export * from "./EditControlDropdown";
3
+ export * from "./EditControlLookupDropdown";
4
+ export * from "./get-data-item-edit-control";
@@ -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 { VuuTable } from "@vuu-ui/vuu-protocol-types";
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
- dataRow: DataRow;
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
- export declare const EditField: ({ dataRow, deferNewRow, label, name, readOnly, required, type, ...options }: EditFieldProps | DropdownEditFieldProps) => import("react").JSX.Element;
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>;