@vuu-ui/vuu-data-editing 4.0.0-alpha.10 → 4.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0-alpha.10",
2
+ "version": "4.0.0-alpha.11",
3
3
  "description": "Shared data editing APIs and React components for VUU UI",
4
4
  "main": "./src/index.js",
5
5
  "author": "heswell",
@@ -12,13 +12,13 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@salt-ds/core": "1.67.0",
15
- "@vuu-ui/vuu-ui-controls": "4.0.0-alpha.10",
16
- "@vuu-ui/vuu-utils": "4.0.0-alpha.10"
15
+ "@vuu-ui/vuu-ui-controls": "4.0.0-alpha.11",
16
+ "@vuu-ui/vuu-utils": "4.0.0-alpha.11"
17
17
  },
18
18
  "devDependencies": {
19
- "@vuu-ui/vuu-data-types": "4.0.0-alpha.10",
20
- "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.10",
21
- "@vuu-ui/vuu-table-types": "4.0.0-alpha.10"
19
+ "@vuu-ui/vuu-data-types": "4.0.0-alpha.11",
20
+ "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.11",
21
+ "@vuu-ui/vuu-table-types": "4.0.0-alpha.11"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": "^19.2.3"
@@ -1,11 +1,16 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { createContext, useContext, useState } from "react";
2
+ import { createContext, useContext, useEffect, useState } from "react";
3
3
  const EditModeContext = /*#__PURE__*/ createContext({
4
4
  isEditMode: false,
5
5
  setEditMode: ()=>"EditModeProvider in place"
6
6
  });
7
- const EditModeProvider = ({ children })=>{
8
- const [isEditMode, setEditMode] = useState(false);
7
+ const EditModeProvider = ({ children, isEditMode: isEditModeProp = false })=>{
8
+ const [isEditMode, setEditMode] = useState(isEditModeProp);
9
+ useEffect(()=>{
10
+ setEditMode(isEditModeProp);
11
+ }, [
12
+ isEditModeProp
13
+ ]);
9
14
  return /*#__PURE__*/ jsx(EditModeContext.Provider, {
10
15
  value: {
11
16
  isEditMode,
@@ -0,0 +1,13 @@
1
+ const css = `
2
+ .vuuEditField {
3
+ & .saltDropdown {
4
+ --vuu-icon-height: 15px;
5
+ --vuu-icon-left: calc(100% - var(--salt-spacing-300));
6
+ --vuu-icon-size: 8px;
7
+ --vuu-icon-top: -3px;
8
+ }
9
+ }
10
+
11
+
12
+ `;
13
+ export default css;
@@ -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
- import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils";
6
+ import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils/src/data-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 { editing, inputProps, onDropdownSelectionChange, onKeyDown, previousValue = "", value, warningMessage, ...editProps } = 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,21 @@ 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
93
  ...editProps,
69
94
  bordered: true,
70
- readOnly: readOnly
95
+ onKeyDown: onKeyDown,
96
+ readOnly: readOnly,
97
+ value: value
71
98
  })
72
99
  ]
73
100
  });
@@ -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)) {
@@ -186,6 +185,26 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
186
185
  column,
187
186
  onEdit
188
187
  ]);
188
+ const handleDropdownSelectionChange = useCallback((_evt, [selectedOption])=>{
189
+ if (!selectedOption) return;
190
+ const value = String(selectedOption.value);
191
+ isDirtyRef.current = value !== initialValueRef.current;
192
+ setEditState((state)=>({
193
+ ...state,
194
+ editing: true,
195
+ message: void 0,
196
+ value
197
+ }));
198
+ commit(value).then((committed)=>{
199
+ if (committed) setEditState((state)=>({
200
+ ...state,
201
+ editing: false,
202
+ value
203
+ }));
204
+ });
205
+ }, [
206
+ commit
207
+ ]);
189
208
  return {
190
209
  editing: editState.editing,
191
210
  inputProps: {
@@ -193,11 +212,12 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
193
212
  onFocus: handleFocus,
194
213
  type: textType
195
214
  },
196
- onChange: handleChange,
197
215
  onKeyDown: handleKeyDown,
198
216
  previousValue: editState.previousValue,
199
217
  value: editState.value,
200
- warningMessage: editState.message
218
+ warningMessage: editState.message,
219
+ onChange: handleChange,
220
+ onDropdownSelectionChange: handleDropdownSelectionChange
201
221
  };
202
222
  };
203
223
  export { useEditField };
package/src/index.js CHANGED
@@ -2,6 +2,7 @@ export { StaleUpdateError } from "@vuu-ui/vuu-utils";
2
2
  export { DataEditingProvider, useEditSession } from "./DataEditingProvider.js";
3
3
  export { EditField } from "./edit-field/EditField.js";
4
4
  export { useEditField } from "./edit-field/useEditField.js";
5
+ export { lookupOptionsFromRows, useLookupValues } from "./lookup-values/useLookupValues.js";
5
6
  export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
6
7
  export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
7
8
  export { EditButtons } from "./EditButtons.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 };
@@ -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
+ isEditMode?: boolean;
12
13
  }) => import("react").JSX.Element;
13
14
  export declare const useEditMode: () => EditModeContextProps;
@@ -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.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;
25
+ onDropdownSelectionChange: (_evt: SyntheticEvent, [selectedOption]: LookupOption[]) => void;
24
26
  };
package/types/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { StaleUpdateError } from "@vuu-ui/vuu-utils";
2
2
  export { DataEditingProvider, useEditSession } from "./DataEditingProvider";
3
3
  export { EditField } from './edit-field/EditField';
4
4
  export { useEditField } from './edit-field/useEditField';
5
+ export { lookupOptionsFromRows, useLookupValues, type LookupOption, type LookupValuedHookProps, type OptionMap, } from "./lookup-values/useLookupValues";
5
6
  export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils";
6
7
  export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator";
7
8
  export { EditButtons, type EditButtonProps } from "./EditButtons";
@@ -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[];