@vuu-ui/vuu-data-editing 5.0.0-alpha.2 → 5.0.0-alpha.4

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 +40 -3
  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 +78 -8
  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 +117 -24
  14. package/src/edit-field/useEditField.js +66 -52
  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 +32 -9
  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 +18 -1
  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 +2 -1
  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
@@ -1,40 +1,87 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Checkbox, Dropdown, FormField, FormFieldLabel, Input, Option } from "@salt-ds/core";
3
- import { useCallback } from "react";
2
+ import { Checkbox, Dropdown, FormField, FormFieldHelperText, FormFieldLabel, Input, Option } from "@salt-ds/core";
3
+ import { useCallback, useEffect, useState } from "react";
4
4
  import { useEditField } from "./useEditField.js";
5
- import { useEditSession } from "../DataEditingProvider.js";
5
+ import { useEditSession, useTableEditSession } from "../DataEditingProvider.js";
6
6
  import { dataDescriptorTypeToVuuRowDataItemType } from "@vuu-ui/vuu-utils";
7
7
  import { useEditMode } from "../EditModeProvider.js";
8
+ import { getDataItemEditControl } from "../edit-control/index.js";
9
+ import { DirectEditSession } from "../DirectEditSession.js";
10
+ import { useEditFormContext } from "../edit-form/EditFormContext.js";
11
+ import { useEditSessionState } from "../useEditSessionState.js";
8
12
  import { useLookupValues } from "../lookup-values/useLookupValues.js";
9
13
  import "./EditField.css";
10
14
  const classBase = "vuuEditField";
11
- const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false, required, type, ...options })=>{
15
+ const isColumnEditFieldProps = (props)=>void 0 !== props.column;
16
+ const resolveDataRow = (dataRow)=>{
17
+ if (void 0 === dataRow) throw Error("EditField needs a dataRow prop or an enclosing EditForm");
18
+ return dataRow;
19
+ };
20
+ const EditField = (props)=>isColumnEditFieldProps(props) ? /*#__PURE__*/ jsx(ColumnEditField, {
21
+ ...props
22
+ }) : /*#__PURE__*/ jsx(TypedEditField, {
23
+ ...props
24
+ });
25
+ const ColumnEditField = ({ column: descriptor, label = descriptor.label ?? descriptor.name, ...props })=>/*#__PURE__*/ jsx(TypedEditField, {
26
+ ...props,
27
+ descriptor: descriptor,
28
+ label: label,
29
+ name: descriptor.name,
30
+ required: descriptor.required,
31
+ serverDataType: descriptor.serverDataType
32
+ });
33
+ const TypedEditField = ({ clientSideEditValidationCheck, dataRow: dataRowProp, deferNewRow: deferNewRowProp, descriptor, label, name, readOnly = false, required, serverDataType = "string", type, ...options })=>{
12
34
  const editSession = useEditSession();
35
+ const tableEditSession = useTableEditSession();
36
+ const form = useEditFormContext();
37
+ const dataRow = resolveDataRow(dataRowProp ?? form?.dataRow);
38
+ const deferNewRow = deferNewRowProp ?? form?.mode === "create";
13
39
  const { isEditMode } = useEditMode();
40
+ const formFieldErrors = form?.fieldErrors;
41
+ const isNewRow = editSession?.isNewRow(dataRow.key) ?? false;
42
+ const { newRowState } = useEditSessionState(isNewRow ? editSession : void 0);
14
43
  const lookupValues = useLookupValues({
15
- enabled: "dropdown" === type,
44
+ enabled: void 0 === descriptor && "dropdown" === type,
16
45
  ...options
17
46
  });
47
+ const formSetValue = form?.setValue;
48
+ const setNewRowValue = useCallback((name, value)=>{
49
+ if (formSetValue) formSetValue(name, value);
50
+ else editSession?.setNewRowValue(name, value);
51
+ }, [
52
+ editSession,
53
+ formSetValue
54
+ ]);
18
55
  const onEdit = useCallback(async (editState, editPhase)=>{
19
- const { isValid = true, previousValue = "", value } = editState;
20
- if ("commit" === editPhase && editSession) {
21
- if (editSession.isNewRow(dataRow.key)) {
56
+ const { editType, isValid = true, previousValue = "", value } = editState;
57
+ if ("commit" === editPhase && tableEditSession) {
58
+ if (editSession?.isNewRow(dataRow.key)) {
59
+ if ("cancel" === editType) {
60
+ editSession.clearNewRowError(name);
61
+ return {
62
+ data: void 0,
63
+ type: "SUCCESS_RESULT"
64
+ };
65
+ }
22
66
  const isEmptyValue = "string" == typeof value && "" === value.trim();
23
- if (!isValid && !isEmptyValue) return {
24
- errorMessage: "Invalid value",
25
- type: "ERROR_RESULT"
26
- };
27
- editSession.setNewRowValue(name, value);
67
+ if (!isValid && !isEmptyValue) {
68
+ editSession.setNewRowError(name);
69
+ return {
70
+ errorMessage: "Invalid value",
71
+ type: "ERROR_RESULT"
72
+ };
73
+ }
74
+ setNewRowValue(name, isEmptyValue ? void 0 : value);
28
75
  if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
29
76
  return {
30
77
  data: void 0,
31
78
  type: "SUCCESS_RESULT"
32
79
  };
33
80
  }
34
- return editSession.commit(dataRow.key, name, previousValue, value, isValid);
81
+ return tableEditSession.commit(dataRow.key, name, previousValue, value, isValid);
35
82
  }
36
83
  if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
37
- editSession.setNewRowValue(name, value);
84
+ setNewRowValue(name, value);
38
85
  return {
39
86
  data: void 0,
40
87
  type: "SUCCESS_RESULT"
@@ -44,29 +91,53 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
44
91
  dataRow,
45
92
  deferNewRow,
46
93
  editSession,
47
- name
94
+ name,
95
+ setNewRowValue,
96
+ tableEditSession
48
97
  ]);
49
- const onCommit = useCallback(()=>{
50
- console.log("onCommit");
51
- }, []);
52
98
  const necessity = required ? "asterisk" : void 0;
53
99
  const dataValue = dataRow?.[name] ?? "";
100
+ const [checked, setChecked] = useState(Boolean(dataValue));
101
+ useEffect(()=>{
102
+ setChecked(Boolean(dataValue));
103
+ }, [
104
+ dataValue
105
+ ]);
106
+ const handleCheckboxChange = useCallback(async (evt)=>{
107
+ const previousValue = checked;
108
+ const value = evt.target.checked;
109
+ setChecked(value);
110
+ const response = await onEdit({
111
+ editType: "commit",
112
+ isValid: true,
113
+ previousValue,
114
+ value
115
+ }, "commit");
116
+ if (response?.type === "ERROR_RESULT") setChecked(previousValue);
117
+ }, [
118
+ checked,
119
+ onEdit
120
+ ]);
54
121
  const column = {
122
+ clientSideEditValidationCheck,
55
123
  label,
56
124
  name,
57
- serverDataType: "string"
125
+ required,
126
+ serverDataType: "checkbox" === type && "string" === serverDataType ? "boolean" : serverDataType
58
127
  };
59
- const { inputProps, onChange, onDropdownSelectionChange, onKeyDown, value } = useEditField({
128
+ const { commit, inputProps, onChange, onDropdownSelectionChange, onKeyDown, value, warningMessage: fieldMessage } = useEditField({
60
129
  column,
61
130
  textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
62
131
  onEdit,
63
132
  type: dataDescriptorTypeToVuuRowDataItemType(column),
64
133
  value: dataValue
65
134
  });
135
+ const warningMessage = isEditMode ? fieldMessage ?? (formFieldErrors ? formFieldErrors[name] : newRowState.errors[name]) : void 0;
66
136
  return /*#__PURE__*/ jsxs(FormField, {
67
137
  className: classBase,
68
138
  "data-field": name,
69
139
  necessity: necessity,
140
+ validationStatus: warningMessage ? "error" : void 0,
70
141
  children: [
71
142
  /*#__PURE__*/ jsx(FormFieldLabel, {
72
143
  children: label
@@ -77,9 +148,28 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
77
148
  readOnly: true
78
149
  },
79
150
  value: String(dataValue)
151
+ }) : descriptor ? getDataItemEditControl({
152
+ InputProps: {
153
+ bordered: true,
154
+ inputProps: {
155
+ ...inputProps,
156
+ onChange,
157
+ value
158
+ },
159
+ onKeyDown,
160
+ value
161
+ },
162
+ commitOnBlur: true,
163
+ dataDescriptor: descriptor,
164
+ editOperation: isNewRow ? "insert" : "update",
165
+ onCommit: (_evt, committedValue)=>{
166
+ commit(String(committedValue));
167
+ },
168
+ table: tableEditSession instanceof DirectEditSession ? tableEditSession.dataSource.table : editSession?.sourceDataSource?.table
80
169
  }) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
81
- checked: Boolean(dataValue),
82
- onChange: onCommit
170
+ checked: checked,
171
+ disabled: readOnly,
172
+ onChange: handleCheckboxChange
83
173
  }) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
84
174
  "data-icon": "triangle-down",
85
175
  onSelectionChange: onDropdownSelectionChange,
@@ -98,7 +188,10 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
98
188
  onKeyDown: onKeyDown,
99
189
  readOnly: readOnly,
100
190
  value: value
101
- })
191
+ }),
192
+ warningMessage ? /*#__PURE__*/ jsx(FormFieldHelperText, {
193
+ children: warningMessage
194
+ }) : null
102
195
  ]
103
196
  });
104
197
  };
@@ -8,11 +8,13 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
8
8
  });
9
9
  const initialValueRef = useRef(value?.toString() ?? "");
10
10
  const isDirtyRef = useRef(false);
11
+ const hasUnresolvedErrorRef = useRef(false);
11
12
  useEffect(()=>{
12
13
  const nextValue = stringValueOf(value);
13
14
  if (initialValueRef.current !== nextValue) {
14
15
  initialValueRef.current = nextValue;
15
16
  isDirtyRef.current = false;
17
+ hasUnresolvedErrorRef.current = false;
16
18
  setEditState((editState)=>({
17
19
  ...editState,
18
20
  message: void 0,
@@ -23,59 +25,69 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
23
25
  value
24
26
  ]);
25
27
  const commit = useCallback(async (value = editState.value)=>{
26
- const result = column.clientSideEditValidationCheck?.(value, "*");
27
- if (result?.ok === false) setEditState((state)=>({
28
- ...state,
29
- message: result?.messages?.join(",")
30
- }));
31
- else {
32
- const { current: initialValue } = initialValueRef;
33
- const previousValue = getTypedValue(initialValue, type);
34
- try {
35
- const typedValue = getTypedValue(value, type, true);
36
- const response = await onEdit?.({
37
- editType: "commit",
38
- isValid: true,
39
- previousValue,
40
- value: typedValue
41
- }, "commit");
42
- if (isRpcSuccess(response)) {
43
- isDirtyRef.current = false;
44
- setEditState((state)=>({
45
- editing: false,
46
- previousValue: state.previousValue === value ? void 0 : void 0 === state.previousValue ? initialValue : state.previousValue,
47
- value
48
- }));
49
- } else if (isRpcError(response)) {
28
+ const { current: initialValue } = initialValueRef;
29
+ const previousValue = getTypedValue(initialValue, type);
30
+ const isEmptyOptional = column?.required === false && "" === value.trim();
31
+ const result = isEmptyOptional ? void 0 : column.clientSideEditValidationCheck?.(value, "*");
32
+ if (result?.ok === false) {
33
+ hasUnresolvedErrorRef.current = true;
34
+ setEditState((state)=>({
35
+ ...state,
36
+ message: result?.messages?.join(",")
37
+ }));
38
+ await onEdit?.({
39
+ editType: "commit",
40
+ isValid: false,
41
+ previousValue,
42
+ value
43
+ }, "commit");
44
+ } else try {
45
+ const typedValue = isEmptyOptional ? "" : getTypedValue(value, type, true);
46
+ const response = await onEdit?.({
47
+ editType: "commit",
48
+ isValid: true,
49
+ previousValue,
50
+ value: typedValue
51
+ }, "commit");
52
+ if (isRpcSuccess(response)) {
53
+ isDirtyRef.current = false;
54
+ hasUnresolvedErrorRef.current = false;
55
+ setEditState((state)=>({
56
+ editing: false,
57
+ previousValue: state.previousValue === value ? void 0 : void 0 === state.previousValue ? initialValue : state.previousValue,
58
+ value
59
+ }));
60
+ } else if (isRpcError(response)) {
61
+ hasUnresolvedErrorRef.current = true;
62
+ setEditState((state)=>({
63
+ ...state,
64
+ message: response.errorMessage
65
+ }));
66
+ return false;
67
+ }
68
+ initialValueRef.current = value;
69
+ return true;
70
+ } catch (e) {
71
+ if (e instanceof DataValidationError) {
72
+ hasUnresolvedErrorRef.current = true;
73
+ const { actualType, expectedType, message } = e;
74
+ if (column) {
75
+ const { name, label = name } = column;
76
+ const message = `${label} is a ${expectedType} value, data entered is ${actualType} `;
50
77
  setEditState((state)=>({
51
- ...state,
52
- message: response.errorMessage
53
- }));
54
- return false;
55
- }
56
- initialValueRef.current = value;
57
- return true;
58
- } catch (e) {
59
- if (e instanceof DataValidationError) {
60
- const { actualType, expectedType, message } = e;
61
- if (column) {
62
- const { name, label = name } = column;
63
- const message = `${label} is a ${expectedType} value, data entered is ${actualType} `;
64
- setEditState((state)=>({
65
- ...state,
66
- message
67
- }));
68
- onEdit?.({
69
- editType: "commit",
70
- isValid: false,
71
- previousValue,
72
- value
73
- }, "commit");
74
- } else setEditState((state)=>({
75
78
  ...state,
76
79
  message
77
80
  }));
78
- }
81
+ await onEdit?.({
82
+ editType: "commit",
83
+ isValid: false,
84
+ previousValue,
85
+ value
86
+ }, "commit");
87
+ } else setEditState((state)=>({
88
+ ...state,
89
+ message
90
+ }));
79
91
  }
80
92
  }
81
93
  return false;
@@ -86,9 +98,10 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
86
98
  type
87
99
  ]);
88
100
  const toggleActivation = useCallback(async (input, cancel = false)=>{
89
- if (editState.editing) if (isDirtyRef.current) if (cancel) {
101
+ if (editState.editing) if (isDirtyRef.current || hasUnresolvedErrorRef.current) if (cancel) {
90
102
  const { value: previousValue } = editState;
91
103
  isDirtyRef.current = false;
104
+ hasUnresolvedErrorRef.current = false;
92
105
  setEditState({
93
106
  editing: false,
94
107
  value: initialValueRef.current,
@@ -98,7 +111,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
98
111
  editType: "cancel",
99
112
  isValid: true,
100
113
  previousValue,
101
- value: getTypedValue(initialValueRef.current, type, true)
114
+ value: getTypedValue(initialValueRef.current, type) ?? initialValueRef.current
102
115
  }, "commit");
103
116
  } else {
104
117
  const commitSuccessful = await commit();
@@ -147,7 +160,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
147
160
  ]);
148
161
  const handleBlur = useCallback(async ()=>{
149
162
  if (editState.editing) {
150
- if (isDirtyRef.current) await commit();
163
+ if (isDirtyRef.current || hasUnresolvedErrorRef.current) await commit();
151
164
  setEditState((editState)=>({
152
165
  ...editState,
153
166
  editing: false
@@ -201,6 +214,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
201
214
  commit
202
215
  ]);
203
216
  return {
217
+ commit,
204
218
  editing: editState.editing,
205
219
  inputProps: {
206
220
  onBlur: handleBlur,
@@ -0,0 +1,37 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { EditSession } from "../EditSession.js";
4
+ import { EditField } from "../edit-field/EditField.js";
5
+ import { EditForm } from "./EditForm.js";
6
+ const NEW_ROW = {
7
+ key: EditSession.newRowKey
8
+ };
9
+ const NO_FIELDS = [];
10
+ const CreateRowForm = ({ children, fields = NO_FIELDS, ...props })=>{
11
+ const fieldNames = fields.map(({ name })=>name).join();
12
+ const requiredNames = fields.filter(({ required })=>false !== required).map(({ name })=>name).join();
13
+ const columns = useMemo(()=>fieldNames ? fieldNames.split(",") : [], [
14
+ fieldNames
15
+ ]);
16
+ const requiredColumns = useMemo(()=>requiredNames ? requiredNames.split(",") : [], [
17
+ requiredNames
18
+ ]);
19
+ return /*#__PURE__*/ jsxs(EditForm, {
20
+ ...props,
21
+ columns: columns,
22
+ mode: "create",
23
+ requiredColumns: requiredColumns,
24
+ children: [
25
+ fields.map((field)=>/*#__PURE__*/ jsx(EditField, {
26
+ column: false === field.required ? field : {
27
+ ...field,
28
+ required: true
29
+ },
30
+ dataRow: NEW_ROW,
31
+ deferNewRow: true
32
+ }, field.name)),
33
+ children
34
+ ]
35
+ });
36
+ };
37
+ export { CreateRowForm };
@@ -0,0 +1,32 @@
1
+ const css = `
2
+ .vuuEditForm {
3
+ gap: var(--salt-spacing-200);
4
+ flex-direction: column;
5
+ min-height: 0;
6
+ display: flex;
7
+ }
8
+
9
+ .vuuEditForm-title {
10
+ font-size: var(--salt-text-h3-fontSize);
11
+ font-weight: var(--salt-text-h3-fontWeight);
12
+ line-height: var(--salt-text-h3-lineHeight);
13
+ margin: 0;
14
+ }
15
+
16
+ .vuuEditForm-fields {
17
+ gap: var(--salt-spacing-200);
18
+ flex-direction: column;
19
+ flex: auto;
20
+ display: flex;
21
+ overflow: auto;
22
+ }
23
+
24
+ .vuuEditForm-buttons {
25
+ gap: var(--salt-spacing-100);
26
+ justify-content: flex-end;
27
+ display: flex;
28
+ }
29
+
30
+
31
+ `;
32
+ export default css;
@@ -0,0 +1,85 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Banner, BannerContent } from "@salt-ds/core";
3
+ import { useCallback } from "react";
4
+ import { DataEditingProvider } from "../DataEditingProvider.js";
5
+ import { EditModeProvider } from "../EditModeProvider.js";
6
+ import { useEditForm } from "../useEditForm.js";
7
+ import { EditFormButtons } from "./EditFormButtons.js";
8
+ import { EditFormContext } from "./EditFormContext.js";
9
+ import "./EditForm.css";
10
+ const classBase = "vuuEditForm";
11
+ const EditForm = ({ children, className, columns, confirmCancel, dataRow, dataSource, deleteMode, editModel, editSession: sharedEditSession, editSessionApi, hideCancel = false, isEditMode, mode = "edit", onCancelled, onError, onSaved, requiredColumns, rowDefaults, saveLabel, showButtons = true, title, validate, ...htmlAttributes })=>{
12
+ const form = useEditForm({
13
+ columns,
14
+ dataRow,
15
+ dataSource,
16
+ deleteMode,
17
+ editModel,
18
+ editSession: sharedEditSession,
19
+ editSessionApi,
20
+ isEditMode,
21
+ mode,
22
+ onCancelled,
23
+ onError,
24
+ onSaved,
25
+ requiredColumns,
26
+ rowDefaults,
27
+ validate
28
+ });
29
+ const { canSave, error, fieldEditSession, saving, submit } = form;
30
+ const handleSubmit = useCallback((e)=>{
31
+ e.preventDefault();
32
+ if (canSave) submit();
33
+ }, [
34
+ canSave,
35
+ submit
36
+ ]);
37
+ const content = /*#__PURE__*/ jsxs("form", {
38
+ ...htmlAttributes,
39
+ "aria-busy": saving || void 0,
40
+ className: [
41
+ classBase,
42
+ `${classBase}-${mode}`,
43
+ className
44
+ ].filter(Boolean).join(" "),
45
+ noValidate: true,
46
+ onSubmit: handleSubmit,
47
+ children: [
48
+ title ? /*#__PURE__*/ jsx("h2", {
49
+ className: `${classBase}-title`,
50
+ children: title
51
+ }) : null,
52
+ error ? /*#__PURE__*/ jsx(Banner, {
53
+ className: `${classBase}-error`,
54
+ status: "error",
55
+ children: /*#__PURE__*/ jsx(BannerContent, {
56
+ children: error.message
57
+ })
58
+ }) : null,
59
+ /*#__PURE__*/ jsx("div", {
60
+ className: `${classBase}-fields`,
61
+ children: "function" == typeof children ? children(form) : children
62
+ }),
63
+ showButtons && form.isEditMode ? /*#__PURE__*/ jsx("div", {
64
+ className: `${classBase}-buttons`,
65
+ children: /*#__PURE__*/ jsx(EditFormButtons, {
66
+ confirmCancel: confirmCancel,
67
+ hideCancel: hideCancel,
68
+ saveLabel: saveLabel
69
+ })
70
+ }) : null
71
+ ]
72
+ });
73
+ const ownsEditMode = "create" === mode || void 0 !== isEditMode;
74
+ return /*#__PURE__*/ jsx(EditFormContext.Provider, {
75
+ value: form,
76
+ children: /*#__PURE__*/ jsx(DataEditingProvider, {
77
+ editSession: fieldEditSession,
78
+ children: ownsEditMode ? /*#__PURE__*/ jsx(EditModeProvider, {
79
+ isEditMode: form.isEditMode,
80
+ children: content
81
+ }) : content
82
+ })
83
+ });
84
+ };
85
+ export { EditForm };
@@ -0,0 +1,48 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@salt-ds/core";
3
+ import { useCallback } from "react";
4
+ import { EditButtons } from "../EditButtons.js";
5
+ import { useEditFormContext } from "./EditFormContext.js";
6
+ const assertForm = (form)=>{
7
+ if (void 0 === form) throw Error("EditFormButtons must be rendered within an EditForm");
8
+ return form;
9
+ };
10
+ const EditFormButtons = ({ confirmCancel, hideCancel = false, saveLabel: saveLabelProp })=>{
11
+ const { cancel, canSave, editModel, editSession, isDirty, isSharedSession, mode, saving, submit } = assertForm(useEditFormContext());
12
+ const saveLabel = saveLabelProp ?? ("create" === mode ? isSharedSession ? "Add" : "Create" : "direct" === editModel ? "Done" : "Save");
13
+ const handleCancel = useCallback(async ()=>{
14
+ if (confirmCancel && isDirty && !await confirmCancel()) return;
15
+ cancel();
16
+ }, [
17
+ cancel,
18
+ confirmCancel,
19
+ isDirty
20
+ ]);
21
+ if ("direct" === editModel) return /*#__PURE__*/ jsx(Button, {
22
+ onClick: ()=>submit(),
23
+ sentiment: "accented",
24
+ children: saveLabel
25
+ });
26
+ return "edit" === mode ? /*#__PURE__*/ jsx(EditButtons, {
27
+ canSave: canSave,
28
+ editSession: editSession,
29
+ onCancel: hideCancel ? void 0 : handleCancel,
30
+ onSave: submit,
31
+ saveLabel: saveLabel
32
+ }) : /*#__PURE__*/ jsxs(Fragment, {
33
+ children: [
34
+ /*#__PURE__*/ jsx(Button, {
35
+ disabled: !canSave,
36
+ sentiment: "accented",
37
+ type: "submit",
38
+ children: saveLabel
39
+ }),
40
+ hideCancel ? null : /*#__PURE__*/ jsx(Button, {
41
+ disabled: saving,
42
+ onClick: handleCancel,
43
+ children: "Cancel"
44
+ })
45
+ ]
46
+ });
47
+ };
48
+ export { EditFormButtons };
@@ -0,0 +1,4 @@
1
+ import { createContext, useContext } from "react";
2
+ const EditFormContext = createContext(void 0);
3
+ const useEditFormContext = ()=>useContext(EditFormContext);
4
+ export { EditFormContext, useEditFormContext };
@@ -0,0 +1,4 @@
1
+ export { CreateRowForm } from "./CreateRowForm.js";
2
+ export { EditForm } from "./EditForm.js";
3
+ export { EditFormButtons } from "./EditFormButtons.js";
4
+ export { useEditFormContext } from "./EditFormContext.js";
@@ -0,0 +1,40 @@
1
+ import { isDataValueEditable } from "@vuu-ui/vuu-utils";
2
+ import { DEFAULT_READ_ONLY_COLUMNS, narrowEditable } from "./useEditableColumns.js";
3
+ const isServerMaintained = (name)=>DEFAULT_READ_ONLY_COLUMNS.includes(name);
4
+ const toDescriptor = (name, schemaColumn, ...overlays)=>{
5
+ const descriptor = {
6
+ name,
7
+ serverDataType: schemaColumn?.serverDataType
8
+ };
9
+ let editable = schemaColumn?.editable;
10
+ for (const overlay of overlays)if (overlay) {
11
+ const { label, editor, required, serverDataType, type } = overlay;
12
+ if (void 0 !== label) descriptor.label = label;
13
+ if (void 0 !== editor) descriptor.editor = editor;
14
+ if (void 0 !== required) descriptor.required = required;
15
+ if (void 0 !== type) descriptor.type = type;
16
+ if (void 0 !== serverDataType && void 0 === schemaColumn) descriptor.serverDataType = serverDataType;
17
+ editable = narrowEditable(editable, overlay.editable);
18
+ }
19
+ if (void 0 === descriptor.required && schemaColumn?.required) descriptor.required = true;
20
+ if (void 0 !== editable) descriptor.editable = editable;
21
+ return descriptor;
22
+ };
23
+ const getFormFields = ({ columns = [], fields, operation = "update", schema })=>{
24
+ const schemaColumns = new Map(schema?.columns.map((column)=>[
25
+ column.name,
26
+ column
27
+ ]));
28
+ const tableColumns = new Map(columns.map((column)=>[
29
+ column.name,
30
+ column
31
+ ]));
32
+ let descriptors;
33
+ if (fields) descriptors = fields.map((field)=>toDescriptor(field.name, schemaColumns.get(field.name), tableColumns.get(field.name), field));
34
+ else {
35
+ const names = schema ? columns.map(({ name })=>name).filter((name)=>schemaColumns.has(name)).concat(schema.columns.map(({ name })=>name).filter((name)=>!tableColumns.has(name))) : columns.map(({ name })=>name);
36
+ descriptors = names.filter((name)=>!isServerMaintained(name)).map((name)=>toDescriptor(name, schemaColumns.get(name), tableColumns.get(name)));
37
+ }
38
+ return "insert" === operation ? descriptors.filter((descriptor)=>isDataValueEditable(descriptor, "insert", true)) : descriptors;
39
+ };
40
+ export { getFormFields };
package/src/index.js CHANGED
@@ -1,7 +1,10 @@
1
+ export * from "./edit-form/index.js";
2
+ export * from "./edit-control/index.js";
1
3
  export { DataEditingProvider, useEditSession, useTableEditSession } from "./DataEditingProvider.js";
2
4
  export { DirectEditSession } from "./DirectEditSession.js";
3
5
  export { useCellEdited } from "./useCellEdited.js";
4
6
  export { useEditState } from "./useEditState.js";
7
+ export { useEditSessionState } from "./useEditSessionState.js";
5
8
  export { getVuuEditMessage, isEditRowReadOnly, isInlineEditingSession, withDataRowEditErrors } from "./edit-utils.js";
6
9
  export { EditButtons } from "./EditButtons.js";
7
10
  export { EditField } from "./edit-field/EditField.js";
@@ -14,3 +17,9 @@ export { useEditable } from "./useEditable.js";
14
17
  export { EDIT_ACTION_ROW_CLASS_NAME_GENERATOR, editActionRowClassNameGenerator } from "./editActionRowClassNameGenerator.js";
15
18
  export { UNDO_CELL_RENDERER, UndoCellRenderer, getUndoButtonContent, getUndoTooltipContent } from "./UndoCellRenderer.js";
16
19
  export { useEditableTable } from "./useEditableTable.js";
20
+ export { DEFAULT_READ_ONLY_COLUMNS, UNDO_COLUMN, getEditableColumns, narrowEditable, useEditableColumns } from "./useEditableColumns.js";
21
+ export { getDataRowValues, useEditForm } from "./useEditForm.js";
22
+ export { useLookupOptions } from "./lookup-values/useLookupOptions.js";
23
+ export { useCustomEditField } from "./useCustomEditField.js";
24
+ export { useConfirmDiscard } from "./useConfirmDiscard.js";
25
+ export { getFormFields } from "./form-fields.js";