@vuu-ui/vuu-data-editing 4.0.0-alpha.8 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +364 -1
  2. package/package.json +8 -7
  3. package/src/DataEditingProvider.js +5 -3
  4. package/src/DirectEditSession.js +35 -0
  5. package/src/EditButtons.js +8 -14
  6. package/src/EditModeProvider.js +8 -3
  7. package/src/EditSession.js +41 -12
  8. package/src/TableEditSession.js +0 -0
  9. package/src/edit-field/EditField.css.js +13 -0
  10. package/src/edit-field/EditField.js +37 -7
  11. package/src/edit-field/useEditField.js +27 -12
  12. package/src/edit-utils.js +1 -1
  13. package/src/index.js +8 -5
  14. package/src/lookup-values/useLookupValues.js +78 -0
  15. package/src/useEditState.js +15 -0
  16. package/src/useEditable.js +6 -5
  17. package/src/useEditableTable.js +2 -0
  18. package/types/DataEditingProvider.d.ts +15 -3
  19. package/types/DirectEditSession.d.ts +22 -0
  20. package/types/EditButtons.d.ts +1 -1
  21. package/types/EditModeProvider.d.ts +3 -2
  22. package/types/EditSession.d.ts +7 -1
  23. package/types/TableEditSession.d.ts +28 -0
  24. package/types/UndoCellRenderer.d.ts +1 -1
  25. package/types/edit-field/EditField.d.ts +11 -2
  26. package/types/edit-field/useEditField.d.ts +4 -2
  27. package/types/edit-utils.d.ts +1 -1
  28. package/types/index.d.ts +16 -12
  29. package/types/lookup-values/useLookupValues.d.ts +19 -0
  30. package/types/useCellEdited.d.ts +2 -2
  31. package/types/useEditable.d.ts +2 -2
  32. package/types/useEditableTable.d.ts +2 -0
  33. package/types/src/DataEditingProvider.d.ts +0 -8
  34. package/types/src/EditButtons.d.ts +0 -14
  35. package/types/src/EditModeProvider.d.ts +0 -13
  36. package/types/src/EditSession.d.ts +0 -86
  37. package/types/src/UndoCellRenderer.d.ts +0 -12
  38. package/types/src/edit-utils.d.ts +0 -5
  39. package/types/src/editActionRowClassNameGenerator.d.ts +0 -3
  40. package/types/src/index.d.ts +0 -10
  41. package/types/src/useCellEdited.d.ts +0 -2
  42. package/types/src/useEditableTable.d.ts +0 -44
@@ -1,13 +1,20 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Checkbox, FormField, FormFieldLabel, Input } from "@salt-ds/core";
2
+ import { Checkbox, Dropdown, FormField, FormFieldLabel, Input, Option } from "@salt-ds/core";
3
3
  import { useCallback } from "react";
4
4
  import { useEditField } from "./useEditField.js";
5
5
  import { useEditSession } from "../DataEditingProvider.js";
6
6
  import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils";
7
7
  import { useEditMode } from "../EditModeProvider.js";
8
- const EditField = ({ dataRow, label, name, readOnly = false, required, type })=>{
8
+ import { useLookupValues } from "../lookup-values/useLookupValues.js";
9
+ import "./EditField.css";
10
+ const classBase = "vuuEditField";
11
+ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false, required, type, ...options })=>{
9
12
  const editSession = useEditSession();
10
13
  const { isEditMode } = useEditMode();
14
+ const lookupValues = useLookupValues({
15
+ enabled: "dropdown" === type,
16
+ ...options
17
+ });
11
18
  const onEdit = useCallback(async (editState, editPhase)=>{
12
19
  const { isValid = true, previousValue = "", value } = editState;
13
20
  if ("commit" === editPhase && editSession) {
@@ -18,7 +25,7 @@ const EditField = ({ dataRow, label, name, readOnly = false, required, type })=>
18
25
  type: "ERROR_RESULT"
19
26
  };
20
27
  editSession.setNewRowValue(name, value);
21
- if (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete()) return editSession.addNewRow();
28
+ if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
22
29
  return {
23
30
  data: void 0,
24
31
  type: "SUCCESS_RESULT"
@@ -26,8 +33,16 @@ const EditField = ({ dataRow, label, name, readOnly = false, required, type })=>
26
33
  }
27
34
  return editSession.commit(dataRow.key, name, previousValue, value, isValid);
28
35
  }
36
+ if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
37
+ editSession.setNewRowValue(name, value);
38
+ return {
39
+ data: void 0,
40
+ type: "SUCCESS_RESULT"
41
+ };
42
+ }
29
43
  }, [
30
44
  dataRow,
45
+ deferNewRow,
31
46
  editSession,
32
47
  name
33
48
  ]);
@@ -41,14 +56,15 @@ const EditField = ({ dataRow, label, name, readOnly = false, required, type })=>
41
56
  name,
42
57
  serverDataType: "string"
43
58
  };
44
- const { editing, inputProps, warningMessage, previousValue = "", ...editProps } = useEditField({
59
+ const { inputProps, onChange, onDropdownSelectionChange, onKeyDown, value } = useEditField({
45
60
  column,
46
- textType: "checkbox" !== type ? type : void 0,
61
+ textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
47
62
  onEdit,
48
63
  type: dataDescriptorTypeToVuuRowDataItemType(column),
49
64
  value: dataValue
50
65
  });
51
66
  return /*#__PURE__*/ jsxs(FormField, {
67
+ className: classBase,
52
68
  "data-field": name,
53
69
  necessity: necessity,
54
70
  children: [
@@ -64,10 +80,24 @@ const EditField = ({ dataRow, label, name, readOnly = false, required, type })=>
64
80
  }) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
65
81
  checked: Boolean(dataValue),
66
82
  onChange: onCommit
83
+ }) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
84
+ "data-icon": "triangle-down",
85
+ onSelectionChange: onDropdownSelectionChange,
86
+ placeholder: "Please select value",
87
+ value: value,
88
+ children: lookupValues.map((listOption)=>/*#__PURE__*/ jsx(Option, {
89
+ value: listOption,
90
+ children: listOption.label
91
+ }, listOption.value))
67
92
  }) : /*#__PURE__*/ jsx(Input, {
68
- ...editProps,
69
93
  bordered: true,
70
- readOnly: readOnly
94
+ inputProps: {
95
+ ...inputProps,
96
+ onChange
97
+ },
98
+ onKeyDown: onKeyDown,
99
+ readOnly: readOnly,
100
+ value: value
71
101
  })
72
102
  ]
73
103
  });
@@ -22,8 +22,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
22
22
  }, [
23
23
  value
24
24
  ]);
25
- const commit = useCallback(async ()=>{
26
- const { value } = editState;
25
+ const commit = useCallback(async (value = editState.value)=>{
27
26
  const result = column.clientSideEditValidationCheck?.(value, "*");
28
27
  if (result?.ok === false) setEditState((state)=>({
29
28
  ...state,
@@ -42,9 +41,9 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
42
41
  }, "commit");
43
42
  if (isRpcSuccess(response)) {
44
43
  isDirtyRef.current = false;
45
- setEditState(({ previousValue, value })=>({
44
+ setEditState((state)=>({
46
45
  editing: false,
47
- previousValue: previousValue === value ? void 0 : void 0 === previousValue ? initialValue : previousValue,
46
+ previousValue: state.previousValue === value ? void 0 : void 0 === state.previousValue ? initialValue : state.previousValue,
48
47
  value
49
48
  }));
50
49
  } else if (isRpcError(response)) {
@@ -148,12 +147,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
148
147
  ]);
149
148
  const handleBlur = useCallback(async ()=>{
150
149
  if (editState.editing) {
151
- if (isDirtyRef.current) {
152
- const commitSuccessful = await commit();
153
- console.log({
154
- commitSuccessful
155
- });
156
- }
150
+ if (isDirtyRef.current) await commit();
157
151
  setEditState((editState)=>({
158
152
  ...editState,
159
153
  editing: false
@@ -186,6 +180,26 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
186
180
  column,
187
181
  onEdit
188
182
  ]);
183
+ const handleDropdownSelectionChange = useCallback((_evt, [selectedOption])=>{
184
+ if (!selectedOption) return;
185
+ const value = String(selectedOption.value);
186
+ isDirtyRef.current = value !== initialValueRef.current;
187
+ setEditState((state)=>({
188
+ ...state,
189
+ editing: true,
190
+ message: void 0,
191
+ value
192
+ }));
193
+ commit(value).then((committed)=>{
194
+ if (committed) setEditState((state)=>({
195
+ ...state,
196
+ editing: false,
197
+ value
198
+ }));
199
+ });
200
+ }, [
201
+ commit
202
+ ]);
189
203
  return {
190
204
  editing: editState.editing,
191
205
  inputProps: {
@@ -193,11 +207,12 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
193
207
  onFocus: handleFocus,
194
208
  type: textType
195
209
  },
196
- onChange: handleChange,
197
210
  onKeyDown: handleKeyDown,
198
211
  previousValue: editState.previousValue,
199
212
  value: editState.value,
200
- warningMessage: editState.message
213
+ warningMessage: editState.message,
214
+ onChange: handleChange,
215
+ onDropdownSelectionChange: handleDropdownSelectionChange
201
216
  };
202
217
  };
203
218
  export { useEditField };
package/src/edit-utils.js CHANGED
@@ -4,7 +4,7 @@ const timeFormatter = formatDate({
4
4
  time: "hh:mm:ss"
5
5
  });
6
6
  const dataRowEditErrors = Symbol("vuuDataRowEditErrors");
7
- const isEditRowReadOnly = (dataRow)=>"deleteRow" === dataRow.vuuAction;
7
+ const isEditRowReadOnly = (dataRow)=>dataRow.hasColumn("vuuAction") && "deleteRow" === dataRow.vuuAction;
8
8
  const withDataRowEditErrors = (dataRow, errors)=>{
9
9
  Object.defineProperty(dataRow, dataRowEditErrors, {
10
10
  configurable: true,
package/src/index.js CHANGED
@@ -1,13 +1,16 @@
1
- export { StaleUpdateError } from "@vuu-ui/vuu-utils";
2
- export { DataEditingProvider, useEditSession } from "./DataEditingProvider.js";
1
+ export { DataEditingProvider, useEditSession, useTableEditSession } from "./DataEditingProvider.js";
2
+ export { DirectEditSession } from "./DirectEditSession.js";
3
+ export { useCellEdited } from "./useCellEdited.js";
4
+ export { useEditState } from "./useEditState.js";
3
5
  export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
4
- export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
5
6
  export { EditButtons } from "./EditButtons.js";
6
7
  export { EditField } from "./edit-field/EditField.js";
7
8
  export { useEditField } from "./edit-field/useEditField.js";
8
9
  export { EditModeProvider, useEditMode } from "./EditModeProvider.js";
9
10
  export { EditError, EditSession, SupersededEditError } from "./EditSession.js";
10
- export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
11
- export { useCellEdited } from "./useCellEdited.js";
11
+ export { StaleUpdateError } from "@vuu-ui/vuu-utils";
12
+ export { lookupOptionsFromRows, useLookupValues } from "./lookup-values/useLookupValues.js";
12
13
  export { useEditable } from "./useEditable.js";
14
+ export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
15
+ export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
13
16
  export { useEditableTable } from "./useEditableTable.js";
@@ -0,0 +1,78 @@
1
+ import { Range, useData } from "@vuu-ui/vuu-utils";
2
+ import { useEffect, useMemo, useState } from "react";
3
+ const DATA_ROW_COLUMN_OFFSET = 10;
4
+ const lookupOptionsFromRows = (columns, rows, optionMap)=>{
5
+ const requestedColumns = [
6
+ optionMap.value,
7
+ optionMap.label,
8
+ ...optionMap.additionalFields ?? []
9
+ ];
10
+ const missingColumns = requestedColumns.filter((column)=>!columns.includes(column));
11
+ if (missingColumns.length > 0) throw Error(`[useLookupValues] lookup columns not found: ${requestedColumns.join(", ")}`);
12
+ return rows.map((row)=>{
13
+ const metadata = Object.fromEntries((optionMap.additionalFields ?? []).map((field)=>[
14
+ field,
15
+ String(row[columns.indexOf(field) + DATA_ROW_COLUMN_OFFSET])
16
+ ]));
17
+ return {
18
+ value: String(row[columns.indexOf(optionMap.value) + DATA_ROW_COLUMN_OFFSET]),
19
+ label: String(row[columns.indexOf(optionMap.label) + DATA_ROW_COLUMN_OFFSET]),
20
+ ...Object.keys(metadata).length > 0 ? {
21
+ metadata
22
+ } : {}
23
+ };
24
+ });
25
+ };
26
+ const useLookupValues = ({ enabled, optionMap, table })=>{
27
+ if (enabled && (void 0 === optionMap || void 0 === table)) throw Error("[useLookupValues] optionsMap and Table must be provided");
28
+ const { VuuDataSource } = useData();
29
+ const [options, setOptions] = useState([]);
30
+ const lookupColumns = useMemo(()=>optionMap ? [
31
+ optionMap.value,
32
+ optionMap.label,
33
+ ...optionMap.additionalFields ?? []
34
+ ] : [], [
35
+ optionMap
36
+ ]);
37
+ const dataSource = useMemo(()=>enabled && optionMap && table ? new VuuDataSource({
38
+ columns: lookupColumns,
39
+ table
40
+ }) : void 0, [
41
+ VuuDataSource,
42
+ enabled,
43
+ lookupColumns,
44
+ optionMap,
45
+ table
46
+ ]);
47
+ useEffect(()=>{
48
+ setOptions([]);
49
+ if (!dataSource || !optionMap) return;
50
+ let active = true;
51
+ let columns;
52
+ const subscribe = (message)=>{
53
+ if (!active) return;
54
+ if ("subscribed" === message.type) {
55
+ const schemaColumns = new Set(message.tableSchema.columns.map(({ name })=>name));
56
+ if (!lookupColumns.every((column)=>message.columns.includes(column) && schemaColumns.has(column))) throw Error(`[useLookupValues] lookup columns not found: ${lookupColumns.join(", ")}`);
57
+ columns = message.columns;
58
+ } else if ("viewport-update" === message.type && columns) setOptions(lookupOptionsFromRows(columns, message.rows ?? [], optionMap));
59
+ else if ("subscribe-failed" === message.type) console.error(`Lookup table ${table?.table} subscription failed: ${message.msg}`);
60
+ };
61
+ dataSource.subscribe({
62
+ range: Range(0, 100)
63
+ }, subscribe).catch((cause)=>{
64
+ if (active) console.error("Role client dropdown subscription failed:", cause);
65
+ });
66
+ return ()=>{
67
+ active = false;
68
+ dataSource.unsubscribe();
69
+ };
70
+ }, [
71
+ dataSource,
72
+ lookupColumns,
73
+ optionMap,
74
+ table
75
+ ]);
76
+ return options;
77
+ };
78
+ export { lookupOptionsFromRows, useLookupValues };
@@ -0,0 +1,15 @@
1
+ import { useCallback, useSyncExternalStore } from "react";
2
+ function useEditState(editSession) {
3
+ const subscribe = useCallback((onStoreChange)=>{
4
+ if (!editSession) return ()=>void 0;
5
+ editSession.on("editState", onStoreChange);
6
+ return ()=>editSession.removeListener("editState", onStoreChange);
7
+ }, [
8
+ editSession
9
+ ]);
10
+ const getSnapshot = useCallback(()=>editSession?.editState ?? "clean", [
11
+ editSession
12
+ ]);
13
+ return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
14
+ }
15
+ export { useEditState };
@@ -1,4 +1,5 @@
1
- import { EditSession, useEditMode } from "./index.js";
1
+ import { EditSession } from "./EditSession.js";
2
+ import { useEditMode } from "./EditModeProvider.js";
2
3
  import { useCallback, useEffect, useMemo } from "react";
3
4
  const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=>{
4
5
  const { isEditMode } = useEditMode();
@@ -12,7 +13,7 @@ const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=
12
13
  await editSession.end();
13
14
  onCancel();
14
15
  } catch (error) {
15
- console.error("[useEditableTable] cancel edit session failed", error);
16
+ console.error("[useEditable] cancel edit session failed", error);
16
17
  }
17
18
  }, [
18
19
  editSession,
@@ -23,7 +24,7 @@ const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=
23
24
  await editSession.end(true, force);
24
25
  onSave();
25
26
  } catch (error) {
26
- console.error("[useEditableTable] save edit session failed", error);
27
+ console.error("[useEditable] save edit session failed", error);
27
28
  }
28
29
  }, [
29
30
  editSession,
@@ -33,9 +34,9 @@ const useEditable = ({ copyOption = "Selected", dataSource, onCancel, onSave })=
33
34
  const transition = isEditMode ? editSession.begin(copyOption) : editSession.end();
34
35
  transition.catch((error)=>{
35
36
  if (isEditMode) {
36
- console.error("[useEditableTable] begin edit session failed", error);
37
+ console.error("[useEditable] begin edit session failed", error);
37
38
  handleCancel();
38
- } else console.error("[useEditableTable] end edit session failed", error);
39
+ } else console.error("[useEditable] end edit session failed", error);
39
40
  });
40
41
  }, [
41
42
  copyOption,
@@ -2,6 +2,7 @@ import { useData, useLayoutEffectSkipFirst } from "@vuu-ui/vuu-utils";
2
2
  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
+ import { isEditRowReadOnly } from "./edit-utils.js";
5
6
  const EDIT_ACTION_ROW_CLASS_NAME_GENERATORS = [
6
7
  EDIT_ACTION_ROW_CLASS_NAME_GENERATOR
7
8
  ];
@@ -148,6 +149,7 @@ const useEditableTable = ({ columns, dataSource: dataSourceProp, deleteMode = "s
148
149
  onDelete: handleDelete,
149
150
  onSave: handleSave,
150
151
  onUndoRowChange: handleUndoRowChange,
152
+ isRowSelectable: isEditMode ? (dataRow)=>!isEditRowReadOnly(dataRow) : void 0,
151
153
  rowClassNameGenerators: isEditMode ? EDIT_ACTION_ROW_CLASS_NAME_GENERATORS : void 0,
152
154
  sessionDataSource,
153
155
  sourceDataSource
@@ -1,8 +1,20 @@
1
1
  import { type ReactNode } from "react";
2
- import type { EditSession } from "./EditSession";
2
+ import { EditSession } from "./EditSession";
3
+ import type { TableEditSession } from "./TableEditSession";
3
4
  export declare const DataEditingProvider: ({ children, editSession, }: {
4
5
  children: ReactNode;
5
- editSession: EditSession;
6
- }) => import("react").JSX.Element;
6
+ editSession: TableEditSession;
7
+ }) => import("react/jsx-runtime").JSX.Element;
8
+ /**
9
+ * Returns the EditSession provided by an enclosing DataEditingProvider. If the
10
+ * provider holds some other TableEditSession (e.g. DirectEditSession), there is
11
+ * no EditSession in scope and undefined is returned.
12
+ */
7
13
  export declare function useEditSession(throwIfUnavailable?: false): EditSession | undefined;
8
14
  export declare function useEditSession(throwIfUnavailable: true): EditSession;
15
+ /**
16
+ * Returns whichever TableEditSession is provided by an enclosing
17
+ * DataEditingProvider. Intended for use by Table cells, which commit and
18
+ * cancel edits regardless of the type of session.
19
+ */
20
+ export declare const useTableEditSession: () => TableEditSession | undefined;
@@ -0,0 +1,22 @@
1
+ import type { DataSource } from "@vuu-ui/vuu-data-types";
2
+ import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
3
+ import { EventEmitter } from "@vuu-ui/vuu-utils";
4
+ import type { TableEditSession, TableEditSessionEvents } from "./TableEditSession";
5
+ export type DirectEditSessionConstructorProps = {
6
+ dataSource: DataSource;
7
+ };
8
+ /**
9
+ * A lightweight edit session for services that allow cells to be edited
10
+ * directly on the source table. There is no begin/end lifecycle, no session
11
+ * table and no local tracking of edits - each committed edit is sent
12
+ * immediately via the dataSource editCell RPC.
13
+ */
14
+ export declare class DirectEditSession extends EventEmitter<TableEditSessionEvents> implements TableEditSession {
15
+ #private;
16
+ constructor({ dataSource }: DirectEditSessionConstructorProps);
17
+ get dataSource(): DataSource;
18
+ get inEditMode(): boolean;
19
+ isCellEdited(_key: string, _columnName: string): boolean;
20
+ cancel(_key: string, _columnName: string, _restoredValue: VuuRowDataItemType): Promise<RpcResult>;
21
+ commit(key: string, columnName: string, _originalValue: VuuRowDataItemType, typedValue: string | number | boolean, isValid: boolean): Promise<RpcResult>;
22
+ }
@@ -11,4 +11,4 @@ export interface EditButtonProps {
11
11
  confirmSave?: () => boolean | Promise<boolean>;
12
12
  confirmCancel?: () => boolean | Promise<boolean>;
13
13
  }
14
- export declare const EditButtons: ({ canCancel, canSave: _canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react").JSX.Element;
14
+ export declare const EditButtons: ({ canCancel, canSave, confirmCancel, confirmSave, editSession, hasSelection, onCancel, onDelete, onSave, saveLabel, }: EditButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -7,7 +7,8 @@ export interface EditModeContextProps {
7
7
  * Implemented as a standalone Provider so that EditMode cna be implemented
8
8
  * at higher level than individual edit controls.
9
9
  */
10
- export declare const EditModeProvider: ({ children }: {
10
+ export declare const EditModeProvider: ({ children, isEditMode: isEditModeProp, }: {
11
11
  children: ReactNode;
12
- }) => import("react").JSX.Element;
12
+ isEditMode?: boolean;
13
+ }) => import("react/jsx-runtime").JSX.Element;
13
14
  export declare const useEditMode: () => EditModeContextProps;
@@ -1,6 +1,7 @@
1
1
  import type { CopyOption, DataSource, DeleteRowMode, SessionType } 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
+ import type { TableEditSession } from "./TableEditSession";
4
5
  export type EditState = "clean" | "dirty" | "invalid" | "stale";
5
6
  export type EditActionType = "deleteRow" | "addRow" | "editCell";
6
7
  export type EditSessionValue = VuuRowDataItemType | object;
@@ -20,6 +21,9 @@ export type EditSessionConstructorProps = {
20
21
  /** Default column values merged into every addRow call for absent columns. Pass a stable reference. */
21
22
  rowDefaults?: RowDefaultDataItemValues;
22
23
  };
24
+ export type AddRowResultData = {
25
+ key?: string | number;
26
+ };
23
27
  export type NewRowState = {
24
28
  columns: readonly string[];
25
29
  requiredColumns?: readonly string[];
@@ -54,7 +58,7 @@ type EditSessionEvents = {
54
58
  lifecycle: (lifecycle: EditLifecycle) => void;
55
59
  newRow: (newRowState: NewRowState) => void;
56
60
  };
57
- export declare class EditSession extends EventEmitter<EditSessionEvents> {
61
+ export declare class EditSession extends EventEmitter<EditSessionEvents> implements TableEditSession {
58
62
  #private;
59
63
  static readonly newRowKey = "__vuu_new_row__";
60
64
  constructor({ dataSource, deleteMode, editSessionApi, rowDefaults, }: EditSessionConstructorProps);
@@ -63,6 +67,7 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
63
67
  get deleteCount(): number;
64
68
  get addCount(): number;
65
69
  get editState(): EditState;
70
+ get isDirty(): boolean;
66
71
  get canCancel(): boolean;
67
72
  get canSave(): boolean;
68
73
  get newRowState(): NewRowState;
@@ -88,6 +93,7 @@ export declare class EditSession extends EventEmitter<EditSessionEvents> {
88
93
  end(saveChanges?: boolean, force?: boolean): Promise<void>;
89
94
  get lifecycle(): EditLifecycle;
90
95
  get inEditMode(): boolean;
96
+ cancel(key: string, columnName: string, restoredValue: VuuRowDataItemType): Promise<RpcResult>;
91
97
  commit(key: string, columnName: string, originalValue: EditSessionValue, typedValue: EditSessionValue, isValid: boolean, { dataSourceValue, syncToDataSource }?: EditCommitOptions): Promise<RpcResult>;
92
98
  }
93
99
  export {};
@@ -0,0 +1,28 @@
1
+ import type { RpcResult, VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
+ export type TableEditSessionEvents = {
3
+ cellEditChanged: (key: string, columnName: string) => void;
4
+ };
5
+ /**
6
+ * The subset of edit session behaviour consumed by Table and its cells.
7
+ * Implemented by EditSession (edits staged in a session table, committed or
8
+ * discarded at end of session) and DirectEditSession (edits applied directly
9
+ * to the source table).
10
+ */
11
+ export interface TableEditSession {
12
+ /**
13
+ * True while a staged edit session is in progress. Always false for
14
+ * DirectEditSession.
15
+ */
16
+ readonly inEditMode: boolean;
17
+ commit(key: string, columnName: string, originalValue: VuuRowDataItemType, typedValue: string | number | boolean, isValid: boolean): Promise<RpcResult>;
18
+ /**
19
+ * Invoked when user abandons an in-progress cell edit, restoring the value
20
+ * the cell held before editing began. Values that have only been typed, not
21
+ * committed, have never been sent to the server, so nothing is sent here.
22
+ * Any invalid edit previously recorded for the cell is discarded.
23
+ */
24
+ cancel(key: string, columnName: string, restoredValue: VuuRowDataItemType): Promise<RpcResult>;
25
+ isCellEdited(key: string, columnName: string): boolean;
26
+ on<E extends keyof TableEditSessionEvents>(event: E, listener: TableEditSessionEvents[E]): void;
27
+ removeListener<E extends keyof TableEditSessionEvents>(event: E, listener: TableEditSessionEvents[E]): void;
28
+ }
@@ -9,4 +9,4 @@ export declare const getUndoButtonContent: (componentProps?: UndoCellRendererCom
9
9
  text: string | undefined;
10
10
  };
11
11
  export declare const getUndoTooltipContent: (action: unknown) => "Undo delete row" | "Undo insert row" | "Undo row edits" | undefined;
12
- export declare const UndoCellRenderer: ({ column, dataRow, }: TableCellRendererProps) => import("react").JSX.Element | null;
12
+ export declare const UndoCellRenderer: ({ column, dataRow, }: TableCellRendererProps) => import("react/jsx-runtime").JSX.Element | null;
@@ -1,12 +1,21 @@
1
1
  import type { DataRow } from "@vuu-ui/vuu-table-types";
2
+ import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
3
+ import { type OptionMap } from "../lookup-values/useLookupValues";
4
+ import "./EditField.css";
2
5
  export type TextFieldType = "email" | "password";
3
- export type EditFieldType = "checkbox" | TextFieldType;
6
+ export type EditFieldType = "checkbox" | "dropdown" | TextFieldType;
4
7
  export interface EditFieldProps {
5
8
  dataRow: DataRow;
9
+ deferNewRow?: boolean;
6
10
  label: string;
7
11
  name: string;
8
12
  readOnly?: boolean;
9
13
  required?: boolean;
10
14
  type?: EditFieldType;
11
15
  }
12
- export declare const EditField: ({ dataRow, label, name, readOnly, required, type, }: EditFieldProps) => import("react").JSX.Element;
16
+ export interface DropdownEditFieldProps extends EditFieldProps {
17
+ type: "dropdown";
18
+ optionMap: OptionMap;
19
+ table: VuuTable;
20
+ }
21
+ export declare const EditField: ({ dataRow, deferNewRow, label, name, readOnly, required, type, ...options }: EditFieldProps | DropdownEditFieldProps) => import("react/jsx-runtime").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
2
  import type { RuntimeColumnDescriptor, TableCellEditHandler } from "@vuu-ui/vuu-table-types";
3
- import { type ChangeEventHandler, type FocusEventHandler, type KeyboardEvent } from "react";
3
+ import { type ChangeEventHandler, type FocusEventHandler, type KeyboardEvent, type SyntheticEvent } from "react";
4
+ import type { LookupOption } from "../lookup-values/useLookupValues";
4
5
  import type { TextFieldType } from "./EditField";
5
6
  export interface EditFieldHookProps<T extends VuuRowDataItemType = VuuRowDataItemType> {
6
7
  column: Pick<RuntimeColumnDescriptor, "clientSideEditValidationCheck" | "label" | "name">;
@@ -16,9 +17,10 @@ export declare const useEditField: <T extends string | number | boolean = string
16
17
  onFocus: FocusEventHandler<HTMLElement>;
17
18
  type: TextFieldType | undefined;
18
19
  };
19
- onChange: ChangeEventHandler;
20
20
  onKeyDown: (evt: KeyboardEvent<HTMLElement>) => Promise<void>;
21
21
  previousValue: string | undefined;
22
22
  value: string;
23
23
  warningMessage: string | undefined;
24
+ onChange: ChangeEventHandler<HTMLInputElement>;
25
+ onDropdownSelectionChange: (_evt: SyntheticEvent, [selectedOption]: LookupOption[]) => void;
24
26
  };
@@ -2,4 +2,4 @@ import type { DataRow, RuntimeColumnDescriptor } from "@vuu-ui/vuu-table-types";
2
2
  export { isInlineEditingSession } from "@vuu-ui/vuu-utils";
3
3
  export declare const isEditRowReadOnly: (dataRow: DataRow) => boolean;
4
4
  export declare const withDataRowEditErrors: <T extends DataRow>(dataRow: T, errors: Record<string, string>) => T;
5
- export declare const getVuuEditMessage: (dataRow: DataRow, column: RuntimeColumnDescriptor, originalValue: string) => string | import("react").JSX.Element | undefined;
5
+ export declare const getVuuEditMessage: (dataRow: DataRow, column: RuntimeColumnDescriptor, originalValue: string) => string | import("react/jsx-runtime").JSX.Element | undefined;
package/types/index.d.ts CHANGED
@@ -1,13 +1,17 @@
1
- export { StaleUpdateError } from "@vuu-ui/vuu-utils";
2
- export { DataEditingProvider, useEditSession } from "./DataEditingProvider";
3
- export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils";
4
- export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator";
5
- export { EditButtons, type EditButtonProps } from "./EditButtons";
6
- export { EditField } from './edit-field/EditField';
7
- export { useEditField } from './edit-field/useEditField';
8
- export { EditModeProvider, useEditMode, type EditModeContextProps } from "./EditModeProvider";
9
- export { EditError, EditSession, SupersededEditError, type EditActionType, type EditLifecycle, type EditSessionConstructorProps, type EditState, type NewRowState, type RowDefaultDataItemValues } from "./EditSession";
10
- export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps } from "./UndoCellRenderer";
1
+ export { DataEditingProvider, useEditSession, useTableEditSession, } from "./DataEditingProvider";
2
+ export { DirectEditSession, type DirectEditSessionConstructorProps, } from "./DirectEditSession";
3
+ export type { TableEditSession, TableEditSessionEvents, } from "./TableEditSession";
11
4
  export { useCellEdited } from "./useCellEdited";
12
- export { useEditable } from './useEditable';
13
- export { useEditableTable, type EditableTableHookProps, type EditMode } from "./useEditableTable";
5
+ export { useEditState } from "./useEditState";
6
+ export { getVuuEditMessage, isInlineEditingSession, isEditRowReadOnly, withDataRowEditErrors, } from "./edit-utils";
7
+ export { EditButtons, type EditButtonProps } from "./EditButtons";
8
+ export { EditField, type DropdownEditFieldProps, type EditFieldProps, type EditFieldType, type TextFieldType, } from "./edit-field/EditField";
9
+ export { useEditField, type EditFieldHookProps, } from "./edit-field/useEditField";
10
+ export { EditModeProvider, useEditMode, type EditModeContextProps, } from "./EditModeProvider";
11
+ export { EditError, EditSession, SupersededEditError, type AddRowResultData, type EditActionType, type EditCommitOptions, type EditLifecycle, type EditSessionConstructorProps, type EditSessionValue, type NewRowState, type EditState, type RowDefaultDataItemValues, } from "./EditSession";
12
+ export { StaleUpdateError } from "@vuu-ui/vuu-utils";
13
+ export { lookupOptionsFromRows, useLookupValues, type LookupOption, type LookupValuedHookProps, type OptionMap, } from "./lookup-values/useLookupValues";
14
+ export { useEditable, type EditableHookProps } from "./useEditable";
15
+ export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator, } from "./editActionRowClassNameGenerator";
16
+ export { getUndoButtonContent, getUndoTooltipContent, UNDO_CELL_RENDERER, UndoCellRenderer, type UndoCellRendererComponentProps, } from "./UndoCellRenderer";
17
+ export { useEditableTable, type EditableTableHookProps, type EditMode, } from "./useEditableTable";
@@ -0,0 +1,19 @@
1
+ import type { DataSourceRow } from "@vuu-ui/vuu-data-types";
2
+ import type { VuuTable } from "@vuu-ui/vuu-protocol-types";
3
+ export type LookupOption = {
4
+ label: string;
5
+ metadata?: Record<string, string>;
6
+ value: number | string;
7
+ };
8
+ export type OptionMap = {
9
+ additionalFields?: readonly string[];
10
+ label: string;
11
+ value: string;
12
+ };
13
+ export interface LookupValuedHookProps {
14
+ enabled: boolean;
15
+ optionMap?: OptionMap;
16
+ table?: VuuTable;
17
+ }
18
+ export declare const lookupOptionsFromRows: (columns: string[], rows: DataSourceRow[], optionMap: OptionMap) => LookupOption[];
19
+ export declare const useLookupValues: ({ enabled, optionMap, table, }: LookupValuedHookProps) => LookupOption[];
@@ -1,2 +1,2 @@
1
- import type { EditSession } from "./EditSession";
2
- export declare const useCellEdited: (editSession: EditSession | undefined, key: string, columnName: string) => boolean;
1
+ import type { TableEditSession } from "./TableEditSession";
2
+ export declare const useCellEdited: (editSession: TableEditSession | undefined, key: string, columnName: string) => boolean;
@@ -1,12 +1,12 @@
1
1
  import type { CopyOption, DataSource } from "@vuu-ui/vuu-data-types";
2
- import { EditSession } from ".";
2
+ import { EditSession } from "./EditSession";
3
3
  export interface EditableHookProps {
4
4
  copyOption?: CopyOption;
5
5
  dataSource: DataSource;
6
6
  onCancel: () => void;
7
7
  onSave: () => void;
8
8
  }
9
- export declare const useEditable: ({ copyOption, dataSource, onCancel, onSave }: EditableHookProps) => {
9
+ export declare const useEditable: ({ copyOption, dataSource, onCancel, onSave, }: EditableHookProps) => {
10
10
  editSession: EditSession;
11
11
  onCancel: () => Promise<void>;
12
12
  onSave: (force?: boolean) => Promise<void>;
@@ -1,5 +1,6 @@
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
+ import type { DataRow } from "@vuu-ui/vuu-table-types";
3
4
  import { EditSession, type EditActionType, type EditLifecycle, type EditSessionApi, type RowDefaultDataItemValues } from "./EditSession";
4
5
  export type EditMode = "edit" | "view";
5
6
  export interface EditableTableHookProps {
@@ -38,6 +39,7 @@ export declare const useEditableTable: ({ columns, dataSource: dataSourceProp, d
38
39
  onDelete: () => Promise<void>;
39
40
  onSave: (force?: boolean) => Promise<void>;
40
41
  onUndoRowChange: (key: string, action: EditActionType) => undefined;
42
+ isRowSelectable: ((dataRow: DataRow) => boolean) | undefined;
41
43
  rowClassNameGenerators: string[] | undefined;
42
44
  sessionDataSource: import("@vuu-ui/vuu-data-types").DataSourceBase<import("@vuu-ui/vuu-data-types").DataSourceRowWithBigint> | undefined;
43
45
  sourceDataSource: DataSource;