@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
@@ -1,40 +1,77 @@
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
56
  const { isValid = true, previousValue = "", value } = editState;
20
- if ("commit" === editPhase && editSession) {
21
- if (editSession.isNewRow(dataRow.key)) {
57
+ if ("commit" === editPhase && tableEditSession) {
58
+ if (editSession?.isNewRow(dataRow.key)) {
22
59
  const isEmptyValue = "string" == typeof value && "" === value.trim();
23
60
  if (!isValid && !isEmptyValue) return {
24
61
  errorMessage: "Invalid value",
25
62
  type: "ERROR_RESULT"
26
63
  };
27
- editSession.setNewRowValue(name, value);
64
+ setNewRowValue(name, value);
28
65
  if (!deferNewRow && (editSession.isNewRowFinalColumn(name) || editSession.isNewRowComplete())) return editSession.addNewRow();
29
66
  return {
30
67
  data: void 0,
31
68
  type: "SUCCESS_RESULT"
32
69
  };
33
70
  }
34
- return editSession.commit(dataRow.key, name, previousValue, value, isValid);
71
+ return tableEditSession.commit(dataRow.key, name, previousValue, value, isValid);
35
72
  }
36
73
  if ("change" === editPhase && deferNewRow && editSession?.isNewRow(dataRow.key)) {
37
- editSession.setNewRowValue(name, value);
74
+ setNewRowValue(name, value);
38
75
  return {
39
76
  data: void 0,
40
77
  type: "SUCCESS_RESULT"
@@ -44,29 +81,52 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
44
81
  dataRow,
45
82
  deferNewRow,
46
83
  editSession,
47
- name
84
+ name,
85
+ setNewRowValue,
86
+ tableEditSession
48
87
  ]);
49
- const onCommit = useCallback(()=>{
50
- console.log("onCommit");
51
- }, []);
52
88
  const necessity = required ? "asterisk" : void 0;
53
89
  const dataValue = dataRow?.[name] ?? "";
90
+ const [checked, setChecked] = useState(Boolean(dataValue));
91
+ useEffect(()=>{
92
+ setChecked(Boolean(dataValue));
93
+ }, [
94
+ dataValue
95
+ ]);
96
+ const handleCheckboxChange = useCallback(async (evt)=>{
97
+ const previousValue = checked;
98
+ const value = evt.target.checked;
99
+ setChecked(value);
100
+ const response = await onEdit({
101
+ editType: "commit",
102
+ isValid: true,
103
+ previousValue,
104
+ value
105
+ }, "commit");
106
+ if (response?.type === "ERROR_RESULT") setChecked(previousValue);
107
+ }, [
108
+ checked,
109
+ onEdit
110
+ ]);
54
111
  const column = {
112
+ clientSideEditValidationCheck,
55
113
  label,
56
114
  name,
57
- serverDataType: "string"
115
+ serverDataType: "checkbox" === type && "string" === serverDataType ? "boolean" : serverDataType
58
116
  };
59
- const { inputProps, onChange, onDropdownSelectionChange, onKeyDown, value } = useEditField({
117
+ const { commit, inputProps, onChange, onDropdownSelectionChange, onKeyDown, value, warningMessage: fieldMessage } = useEditField({
60
118
  column,
61
119
  textType: "checkbox" !== type && "dropdown" !== type ? type : void 0,
62
120
  onEdit,
63
121
  type: dataDescriptorTypeToVuuRowDataItemType(column),
64
122
  value: dataValue
65
123
  });
124
+ const warningMessage = isEditMode ? fieldMessage ?? (formFieldErrors ? formFieldErrors[name] : newRowState.errors[name]) : void 0;
66
125
  return /*#__PURE__*/ jsxs(FormField, {
67
126
  className: classBase,
68
127
  "data-field": name,
69
128
  necessity: necessity,
129
+ validationStatus: warningMessage ? "error" : void 0,
70
130
  children: [
71
131
  /*#__PURE__*/ jsx(FormFieldLabel, {
72
132
  children: label
@@ -77,9 +137,28 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
77
137
  readOnly: true
78
138
  },
79
139
  value: String(dataValue)
140
+ }) : descriptor ? getDataItemEditControl({
141
+ InputProps: {
142
+ bordered: true,
143
+ inputProps: {
144
+ ...inputProps,
145
+ onChange,
146
+ value
147
+ },
148
+ onKeyDown,
149
+ value
150
+ },
151
+ commitOnBlur: true,
152
+ dataDescriptor: descriptor,
153
+ editOperation: isNewRow ? "insert" : "update",
154
+ onCommit: (_evt, committedValue)=>{
155
+ commit(String(committedValue));
156
+ },
157
+ table: tableEditSession instanceof DirectEditSession ? tableEditSession.dataSource.table : editSession?.sourceDataSource?.table
80
158
  }) : "checkbox" === type ? /*#__PURE__*/ jsx(Checkbox, {
81
- checked: Boolean(dataValue),
82
- onChange: onCommit
159
+ checked: checked,
160
+ disabled: readOnly,
161
+ onChange: handleCheckboxChange
83
162
  }) : "dropdown" === type ? /*#__PURE__*/ jsx(Dropdown, {
84
163
  "data-icon": "triangle-down",
85
164
  onSelectionChange: onDropdownSelectionChange,
@@ -98,7 +177,10 @@ const EditField = ({ dataRow, deferNewRow = false, label, name, readOnly = false
98
177
  onKeyDown: onKeyDown,
99
178
  readOnly: readOnly,
100
179
  value: value
101
- })
180
+ }),
181
+ warningMessage ? /*#__PURE__*/ jsx(FormFieldHelperText, {
182
+ children: warningMessage
183
+ }) : null
102
184
  ]
103
185
  });
104
186
  };
@@ -201,6 +201,7 @@ const useEditField = ({ column, value, onEdit, textType, type = "string" })=>{
201
201
  commit
202
202
  ]);
203
203
  return {
204
+ commit,
204
205
  editing: editState.editing,
205
206
  inputProps: {
206
207
  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";
@@ -0,0 +1,106 @@
1
+ import { Range, useData } from "@vuu-ui/vuu-utils";
2
+ import { useEffect, useMemo, useRef, useState } from "react";
3
+ import { lookupOptionsFromRows } from "./useLookupValues.js";
4
+ const DATA_ROW_COLUMN_OFFSET = 10;
5
+ const rowToRecord = (columns, row)=>Object.fromEntries(columns.map((column, i)=>[
6
+ column,
7
+ row[i + DATA_ROW_COLUMN_OFFSET]
8
+ ]));
9
+ const useLookupOptions = ({ columns: columnsProp, enabled = true, filter, mapRow, maxRows = 100, optionMap, table })=>{
10
+ const { VuuDataSource } = useData();
11
+ const [state, setState] = useState({
12
+ loading: false,
13
+ options: []
14
+ });
15
+ const mapRowRef = useRef(mapRow);
16
+ mapRowRef.current = mapRow;
17
+ const columns = useMemo(()=>{
18
+ if (columnsProp) return [
19
+ ...columnsProp
20
+ ];
21
+ if (optionMap) return [
22
+ optionMap.value,
23
+ optionMap.label,
24
+ ...optionMap.additionalFields ?? []
25
+ ];
26
+ return [];
27
+ }, [
28
+ columnsProp,
29
+ optionMap
30
+ ]);
31
+ const dataSource = useMemo(()=>enabled && table && columns.length > 0 ? new VuuDataSource({
32
+ columns,
33
+ filterSpec: filter ? {
34
+ filter
35
+ } : void 0,
36
+ table
37
+ }) : void 0, [
38
+ VuuDataSource,
39
+ columns,
40
+ enabled,
41
+ filter,
42
+ table
43
+ ]);
44
+ useEffect(()=>{
45
+ if (!dataSource) return void setState({
46
+ loading: false,
47
+ options: []
48
+ });
49
+ if (!mapRowRef.current && !optionMap) return void setState({
50
+ error: new Error("[useLookupOptions] either optionMap or mapRow must be provided"),
51
+ loading: false,
52
+ options: []
53
+ });
54
+ setState({
55
+ loading: true,
56
+ options: []
57
+ });
58
+ let active = true;
59
+ let subscribedColumns;
60
+ const fail = (error)=>{
61
+ if (active) setState({
62
+ error,
63
+ loading: false,
64
+ options: []
65
+ });
66
+ };
67
+ const subscribe = (message)=>{
68
+ if (!active) return;
69
+ if ("subscribed" === message.type) subscribedColumns = message.columns;
70
+ else if ("viewport-update" === message.type && subscribedColumns) {
71
+ if (void 0 === message.rows) {
72
+ if (0 === message.size) setState({
73
+ loading: false,
74
+ options: []
75
+ });
76
+ return;
77
+ }
78
+ try {
79
+ const rows = message.rows.slice(0, maxRows);
80
+ const mapper = mapRowRef.current;
81
+ const options = mapper ? rows.map((row)=>mapper(rowToRecord(subscribedColumns, row))) : lookupOptionsFromRows(subscribedColumns, rows, optionMap);
82
+ setState({
83
+ loading: false,
84
+ options
85
+ });
86
+ } catch (cause) {
87
+ fail(cause instanceof Error ? cause : new Error(String(cause)));
88
+ }
89
+ } else if ("subscribe-failed" === message.type) fail(new Error(`[useLookupOptions] subscription to ${table?.table} failed: ${message.msg}`));
90
+ };
91
+ dataSource.subscribe({
92
+ range: Range(0, maxRows)
93
+ }, subscribe).catch((cause)=>fail(cause instanceof Error ? cause : new Error(String(cause))));
94
+ return ()=>{
95
+ active = false;
96
+ dataSource.unsubscribe();
97
+ };
98
+ }, [
99
+ dataSource,
100
+ maxRows,
101
+ optionMap,
102
+ table
103
+ ]);
104
+ return state;
105
+ };
106
+ export { useLookupOptions };
@@ -0,0 +1,34 @@
1
+ import { useModal } from "@vuu-ui/core";
2
+ import { createElement, useCallback } from "react";
3
+ const useConfirmDiscard = ({ cancelButtonLabel = "Keep editing", confirmButtonLabel = "Discard", message = "You have unsaved changes. Discard them?", title = "Discard changes?" } = {})=>{
4
+ const { closePrompt, showPrompt } = useModal();
5
+ return useCallback(()=>new Promise((resolve)=>{
6
+ let resolved = false;
7
+ const settle = (confirmed)=>{
8
+ if (!resolved) {
9
+ resolved = true;
10
+ resolve(confirmed);
11
+ }
12
+ };
13
+ showPrompt(createElement("span", null, message), {
14
+ cancelButtonLabel,
15
+ confirmButtonLabel,
16
+ initialFocusedItem: "cancel",
17
+ onCancel: ()=>settle(false),
18
+ onConfirm: ()=>settle(true),
19
+ onOpenChange: (open)=>{
20
+ if (!open) settle(false);
21
+ },
22
+ status: "warning",
23
+ title
24
+ });
25
+ }).finally(closePrompt), [
26
+ cancelButtonLabel,
27
+ closePrompt,
28
+ confirmButtonLabel,
29
+ message,
30
+ showPrompt,
31
+ title
32
+ ]);
33
+ };
34
+ export { useConfirmDiscard };