@vuu-ui/vuu-data-react 3.0.0 → 3.1.0-beta.2

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 (119) hide show
  1. package/package.json +24 -20
  2. package/src/data-editing/EditForm.css.js +53 -0
  3. package/src/data-editing/EditForm.js +82 -0
  4. package/src/data-editing/UnsavedChangesReport.css.js +27 -0
  5. package/src/data-editing/UnsavedChangesReport.js +41 -0
  6. package/src/data-editing/edit-rule-validation-checker.js +30 -0
  7. package/src/data-editing/edit-validation-rules.js +33 -0
  8. package/src/data-editing/form-edit-state.js +16 -0
  9. package/src/data-editing/get-data-item-edit-control.js +63 -0
  10. package/src/data-editing/index.js +6 -0
  11. package/src/data-editing/useEditForm.js +253 -0
  12. package/src/datasource-provider/VuuDataSourceProvider.js +20 -0
  13. package/src/datasource-provider/index.js +1 -0
  14. package/src/datasource-provider/useAutoLoginToVuuServer.js +42 -0
  15. package/src/hooks/useLookupValues.js +93 -0
  16. package/src/hooks/useSessionDataSource.js +37 -0
  17. package/src/hooks/useTypeaheadSuggestions.js +50 -0
  18. package/src/hooks/useVisualLinks.js +87 -0
  19. package/src/hooks/useVuuMenuActions.js +269 -0
  20. package/src/hooks/useVuuTables.js +32 -0
  21. package/src/index.js +10 -0
  22. package/src/lost-connection-indicator/LostConnectionIndicator.css.js +29 -0
  23. package/src/lost-connection-indicator/LostConnectionIndicator.js +30 -0
  24. package/src/lost-connection-indicator/useLostConnection.js +28 -0
  25. package/src/session-editing-form/SessionEditingForm.css.js +47 -0
  26. package/src/session-editing-form/SessionEditingForm.js +236 -0
  27. package/src/session-editing-form/index.js +1 -0
  28. package/cjs/data-editing/EditForm.css.js +0 -6
  29. package/cjs/data-editing/EditForm.css.js.map +0 -1
  30. package/cjs/data-editing/EditForm.js +0 -90
  31. package/cjs/data-editing/EditForm.js.map +0 -1
  32. package/cjs/data-editing/UnsavedChangesReport.css.js +0 -6
  33. package/cjs/data-editing/UnsavedChangesReport.css.js.map +0 -1
  34. package/cjs/data-editing/UnsavedChangesReport.js +0 -29
  35. package/cjs/data-editing/UnsavedChangesReport.js.map +0 -1
  36. package/cjs/data-editing/edit-rule-validation-checker.js +0 -41
  37. package/cjs/data-editing/edit-rule-validation-checker.js.map +0 -1
  38. package/cjs/data-editing/edit-validation-rules.js +0 -52
  39. package/cjs/data-editing/edit-validation-rules.js.map +0 -1
  40. package/cjs/data-editing/form-edit-state.js +0 -26
  41. package/cjs/data-editing/form-edit-state.js.map +0 -1
  42. package/cjs/data-editing/get-data-item-edit-control.js +0 -106
  43. package/cjs/data-editing/get-data-item-edit-control.js.map +0 -1
  44. package/cjs/data-editing/useEditForm.js +0 -258
  45. package/cjs/data-editing/useEditForm.js.map +0 -1
  46. package/cjs/datasource-provider/VuuDataSourceProvider.js +0 -34
  47. package/cjs/datasource-provider/VuuDataSourceProvider.js.map +0 -1
  48. package/cjs/datasource-provider/useAutoLoginToVuuServer.js +0 -52
  49. package/cjs/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
  50. package/cjs/hooks/useLookupValues.js +0 -100
  51. package/cjs/hooks/useLookupValues.js.map +0 -1
  52. package/cjs/hooks/useSessionDataSource.js +0 -58
  53. package/cjs/hooks/useSessionDataSource.js.map +0 -1
  54. package/cjs/hooks/useTypeaheadSuggestions.js +0 -57
  55. package/cjs/hooks/useTypeaheadSuggestions.js.map +0 -1
  56. package/cjs/hooks/useVisualLinks.js +0 -84
  57. package/cjs/hooks/useVisualLinks.js.map +0 -1
  58. package/cjs/hooks/useVuuMenuActions.js +0 -352
  59. package/cjs/hooks/useVuuMenuActions.js.map +0 -1
  60. package/cjs/hooks/useVuuTables.js +0 -38
  61. package/cjs/hooks/useVuuTables.js.map +0 -1
  62. package/cjs/index.js +0 -42
  63. package/cjs/index.js.map +0 -1
  64. package/cjs/lost-connection-indicator/LostConnectionIndicator.css.js +0 -6
  65. package/cjs/lost-connection-indicator/LostConnectionIndicator.css.js.map +0 -1
  66. package/cjs/lost-connection-indicator/LostConnectionIndicator.js +0 -24
  67. package/cjs/lost-connection-indicator/LostConnectionIndicator.js.map +0 -1
  68. package/cjs/lost-connection-indicator/useLostConnection.js +0 -34
  69. package/cjs/lost-connection-indicator/useLostConnection.js.map +0 -1
  70. package/cjs/session-editing-form/SessionEditingForm.css.js +0 -6
  71. package/cjs/session-editing-form/SessionEditingForm.css.js.map +0 -1
  72. package/cjs/session-editing-form/SessionEditingForm.js +0 -270
  73. package/cjs/session-editing-form/SessionEditingForm.js.map +0 -1
  74. package/esm/data-editing/EditForm.css.js +0 -4
  75. package/esm/data-editing/EditForm.css.js.map +0 -1
  76. package/esm/data-editing/EditForm.js +0 -88
  77. package/esm/data-editing/EditForm.js.map +0 -1
  78. package/esm/data-editing/UnsavedChangesReport.css.js +0 -4
  79. package/esm/data-editing/UnsavedChangesReport.css.js.map +0 -1
  80. package/esm/data-editing/UnsavedChangesReport.js +0 -27
  81. package/esm/data-editing/UnsavedChangesReport.js.map +0 -1
  82. package/esm/data-editing/edit-rule-validation-checker.js +0 -37
  83. package/esm/data-editing/edit-rule-validation-checker.js.map +0 -1
  84. package/esm/data-editing/edit-validation-rules.js +0 -50
  85. package/esm/data-editing/edit-validation-rules.js.map +0 -1
  86. package/esm/data-editing/form-edit-state.js +0 -23
  87. package/esm/data-editing/form-edit-state.js.map +0 -1
  88. package/esm/data-editing/get-data-item-edit-control.js +0 -104
  89. package/esm/data-editing/get-data-item-edit-control.js.map +0 -1
  90. package/esm/data-editing/useEditForm.js +0 -256
  91. package/esm/data-editing/useEditForm.js.map +0 -1
  92. package/esm/datasource-provider/VuuDataSourceProvider.js +0 -32
  93. package/esm/datasource-provider/VuuDataSourceProvider.js.map +0 -1
  94. package/esm/datasource-provider/useAutoLoginToVuuServer.js +0 -50
  95. package/esm/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
  96. package/esm/hooks/useLookupValues.js +0 -98
  97. package/esm/hooks/useLookupValues.js.map +0 -1
  98. package/esm/hooks/useSessionDataSource.js +0 -56
  99. package/esm/hooks/useSessionDataSource.js.map +0 -1
  100. package/esm/hooks/useTypeaheadSuggestions.js +0 -54
  101. package/esm/hooks/useTypeaheadSuggestions.js.map +0 -1
  102. package/esm/hooks/useVisualLinks.js +0 -82
  103. package/esm/hooks/useVisualLinks.js.map +0 -1
  104. package/esm/hooks/useVuuMenuActions.js +0 -348
  105. package/esm/hooks/useVuuMenuActions.js.map +0 -1
  106. package/esm/hooks/useVuuTables.js +0 -36
  107. package/esm/hooks/useVuuTables.js.map +0 -1
  108. package/esm/index.js +0 -16
  109. package/esm/index.js.map +0 -1
  110. package/esm/lost-connection-indicator/LostConnectionIndicator.css.js +0 -4
  111. package/esm/lost-connection-indicator/LostConnectionIndicator.css.js.map +0 -1
  112. package/esm/lost-connection-indicator/LostConnectionIndicator.js +0 -22
  113. package/esm/lost-connection-indicator/LostConnectionIndicator.js.map +0 -1
  114. package/esm/lost-connection-indicator/useLostConnection.js +0 -32
  115. package/esm/lost-connection-indicator/useLostConnection.js.map +0 -1
  116. package/esm/session-editing-form/SessionEditingForm.css.js +0 -4
  117. package/esm/session-editing-form/SessionEditingForm.css.js.map +0 -1
  118. package/esm/session-editing-form/SessionEditingForm.js +0 -268
  119. package/esm/session-editing-form/SessionEditingForm.js.map +0 -1
package/package.json CHANGED
@@ -1,23 +1,30 @@
1
1
  {
2
- "version": "3.0.0",
2
+ "version": "3.1.0-beta.2",
3
+ "main": "src/index.ts",
3
4
  "author": "heswell",
4
5
  "license": "Apache-2.0",
6
+ "scripts": {
7
+ "build:dev": "node ../../scripts/run-build.mjs",
8
+ "build": "node ../../scripts/run-build-rslib.ts",
9
+ "test": "vitest run",
10
+ "type-defs": "node ../../scripts/build-type-defs.mjs"
11
+ },
5
12
  "devDependencies": {
6
- "@vuu-ui/vuu-data-types": "3.0.0",
7
- "@vuu-ui/vuu-filter-types": "3.0.0",
8
- "@vuu-ui/vuu-protocol-types": "3.0.0",
9
- "@vuu-ui/vuu-table-types": "3.0.0"
13
+ "@vuu-ui/vuu-data-types": "3.1.0-beta.2",
14
+ "@vuu-ui/vuu-filter-types": "3.1.0-beta.2",
15
+ "@vuu-ui/vuu-protocol-types": "3.1.0-beta.2",
16
+ "@vuu-ui/vuu-table-types": "3.1.0-beta.2"
10
17
  },
11
18
  "dependencies": {
12
- "@vuu-ui/vuu-context-menu": "3.0.0",
13
- "@vuu-ui/vuu-data-remote": "3.0.0",
14
- "@vuu-ui/vuu-filter-parser": "3.0.0",
15
- "@vuu-ui/vuu-filters": "3.0.0",
16
- "@vuu-ui/vuu-layout": "3.0.0",
17
- "@vuu-ui/vuu-notifications": "3.0.0",
18
- "@vuu-ui/vuu-ui-controls": "3.0.0",
19
- "@vuu-ui/vuu-utils": "3.0.0",
20
- "@vuu-ui/vuu-table": "3.0.0",
19
+ "@vuu-ui/vuu-context-menu": "3.1.0-beta.2",
20
+ "@vuu-ui/vuu-data-remote": "3.1.0-beta.2",
21
+ "@vuu-ui/vuu-filter-parser": "3.1.0-beta.2",
22
+ "@vuu-ui/vuu-filters": "3.1.0-beta.2",
23
+ "@vuu-ui/vuu-layout": "3.1.0-beta.2",
24
+ "@vuu-ui/vuu-notifications": "3.1.0-beta.2",
25
+ "@vuu-ui/vuu-ui-controls": "3.1.0-beta.2",
26
+ "@vuu-ui/vuu-utils": "3.1.0-beta.2",
27
+ "@vuu-ui/vuu-table": "3.1.0-beta.2",
21
28
  "@salt-ds/core": "1.54.1",
22
29
  "@salt-ds/styles": "0.2.1",
23
30
  "@salt-ds/window": "0.1.1"
@@ -29,19 +36,16 @@
29
36
  },
30
37
  "files": [
31
38
  "README.md",
32
- "esm",
33
- "cjs",
39
+ "src",
34
40
  "/types"
35
41
  ],
36
42
  "exports": {
37
43
  ".": {
38
- "require": "./cjs/index.js",
39
- "import": "./esm/index.js",
44
+ "import": "./src/index.js",
40
45
  "types": "./types/index.d.ts"
41
46
  }
42
47
  },
43
- "main": "cjs/index.js",
44
- "module": "esm/index.js",
48
+ "module": "index.js",
45
49
  "name": "@vuu-ui/vuu-data-react",
46
50
  "type": "module",
47
51
  "types": "types/index.d.ts"
@@ -0,0 +1,53 @@
1
+ const css = `
2
+ .EditForm {
3
+ gap: var(--salt-spacing-400);
4
+ height: 100%;
5
+ padding: var(--salt-spacing-200) var(--salt-spacing-200)
6
+ var(--salt-spacing-200) var(--salt-spacing-400);
7
+ flex-direction: column;
8
+ width: 100%;
9
+ display: flex;
10
+ }
11
+
12
+ .EditForm-form-fields {
13
+ gap: var(--salt-spacing-400);
14
+ flex-direction: column;
15
+ display: flex;
16
+ }
17
+
18
+ .EditForm-buttons {
19
+ align-items: center;
20
+ gap: var(--salt-spacing-200);
21
+ justify-content: flex-end;
22
+ display: flex;
23
+ }
24
+
25
+ .EditForm-field {
26
+ display: flex;
27
+
28
+ & .saltFormField {
29
+ flex: auto;
30
+ }
31
+
32
+ & .EditForm-edit-indicator {
33
+ flex: 0 0 14px;
34
+ position: relative;
35
+ }
36
+
37
+ &[data-edited="true"] {
38
+ & .EditForm-edit-indicator:after {
39
+ background-color: var(--salt-content-secondary-foreground);
40
+ content: "";
41
+ border-radius: 5px;
42
+ width: 10px;
43
+ height: 10px;
44
+ position: absolute;
45
+ top: 50%;
46
+ right: -3px;
47
+ }
48
+ }
49
+ }
50
+
51
+
52
+ `;
53
+ export default css;
@@ -0,0 +1,82 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Button, FormField, FormFieldLabel } from "@salt-ds/core";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import clsx from "clsx";
6
+ import { registerRules } from "./edit-validation-rules.js";
7
+ import { useEditForm } from "./useEditForm.js";
8
+ import EditForm from "./EditForm.css";
9
+ import { getDataItemEditControl } from "./get-data-item-edit-control.js";
10
+ const classBase = "EditForm";
11
+ registerRules();
12
+ const EditForm_EditForm = ({ className, dataSource, formFieldDescriptors, onSubmit: onSubmitProp, ...htmlAttributes })=>{
13
+ const targetWindow = useWindow();
14
+ useComponentCssInjection({
15
+ testId: "vuu-edit-form",
16
+ css: EditForm,
17
+ window: targetWindow
18
+ });
19
+ const { editedFields, editEntity, errorMessages, formFieldsContainerRef, isClean, ok, onCancel, onChange, onCommit, onFocus, onSubmit } = useEditForm({
20
+ dataSource,
21
+ formFieldDescriptors,
22
+ onSubmit: onSubmitProp
23
+ });
24
+ return /*#__PURE__*/ jsxs("div", {
25
+ ...htmlAttributes,
26
+ className: clsx(classBase, className),
27
+ onFocus: onFocus,
28
+ children: [
29
+ /*#__PURE__*/ jsx("div", {
30
+ className: `${classBase}-form-fields`,
31
+ ref: formFieldsContainerRef,
32
+ children: formFieldDescriptors.map((dataDescriptor)=>{
33
+ const { name, label = name } = dataDescriptor;
34
+ const errorMessage = errorMessages[name];
35
+ const isEdited = !isClean && editedFields.includes(name);
36
+ return /*#__PURE__*/ jsxs("div", {
37
+ className: `${classBase}-field`,
38
+ "data-edited": isEdited,
39
+ children: [
40
+ /*#__PURE__*/ jsxs(FormField, {
41
+ "data-field": name,
42
+ children: [
43
+ /*#__PURE__*/ jsx(FormFieldLabel, {
44
+ children: label
45
+ }),
46
+ getDataItemEditControl({
47
+ InputProps: {
48
+ onChange,
49
+ value: editEntity?.[name]?.toString() ?? ""
50
+ },
51
+ dataDescriptor,
52
+ errorMessage,
53
+ onCommit
54
+ })
55
+ ]
56
+ }),
57
+ /*#__PURE__*/ jsx("div", {
58
+ className: `${classBase}-edit-indicator`
59
+ })
60
+ ]
61
+ }, name);
62
+ })
63
+ }),
64
+ /*#__PURE__*/ jsxs("div", {
65
+ className: `${classBase}-buttons`,
66
+ children: [
67
+ /*#__PURE__*/ jsx(Button, {
68
+ disabled: isClean,
69
+ onClick: onCancel,
70
+ children: "Cancel"
71
+ }),
72
+ /*#__PURE__*/ jsx(Button, {
73
+ onClick: onSubmit,
74
+ disabled: !ok || isClean,
75
+ children: "Save"
76
+ })
77
+ ]
78
+ })
79
+ ]
80
+ });
81
+ };
82
+ export { EditForm_EditForm as EditForm };
@@ -0,0 +1,27 @@
1
+ const css = `
2
+ .vuuUnsavedChanges-table {
3
+ border-collapse: collapse;
4
+ width: 100%;
5
+ }
6
+
7
+ .vuuUnsavedChanges-row {
8
+ box-sizing: content-box;
9
+ border-bottom: solid 1px var(--salt-separable-secondary-borderColor);
10
+ height: 32px;
11
+
12
+ & td {
13
+ padding: 0 var(--salt-spacing-200);
14
+ }
15
+ }
16
+
17
+ .vuuUnsavedChanges-fieldName {
18
+ text-transform: capitalize;
19
+ }
20
+
21
+ .vuuUnsavedChanges-new {
22
+ font-weight: bold;
23
+ }
24
+
25
+
26
+ `;
27
+ export default css;
@@ -0,0 +1,41 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useComponentCssInjection } from "@salt-ds/styles";
3
+ import { useWindow } from "@salt-ds/window";
4
+ import { buildFormEditState } from "./form-edit-state.js";
5
+ import UnsavedChangesReport from "./UnsavedChangesReport.css";
6
+ const classBase = "vuuUnsavedChanges";
7
+ const UnsavedChangesReport_UnsavedChangesReport = ({ entity, editedEntity })=>{
8
+ const targetWindow = useWindow();
9
+ useComponentCssInjection({
10
+ testId: "vuu-unsaved-changes-report",
11
+ css: UnsavedChangesReport,
12
+ window: targetWindow
13
+ });
14
+ const { editedFields } = buildFormEditState(entity, editedEntity);
15
+ return /*#__PURE__*/ jsx("div", {
16
+ className: classBase,
17
+ children: /*#__PURE__*/ jsx("table", {
18
+ className: `${classBase}-table`,
19
+ children: /*#__PURE__*/ jsx("tbody", {
20
+ children: editedFields.map((fieldName, i)=>/*#__PURE__*/ jsxs("tr", {
21
+ className: `${classBase}-row`,
22
+ children: [
23
+ /*#__PURE__*/ jsx("td", {
24
+ className: `${classBase}-fieldName`,
25
+ children: fieldName
26
+ }),
27
+ /*#__PURE__*/ jsx("td", {
28
+ className: `${classBase}-old`,
29
+ children: entity[fieldName]
30
+ }),
31
+ /*#__PURE__*/ jsx("td", {
32
+ className: `${classBase}-new`,
33
+ children: editedEntity[fieldName]
34
+ })
35
+ ]
36
+ }, i))
37
+ })
38
+ })
39
+ });
40
+ };
41
+ export { UnsavedChangesReport_UnsavedChangesReport as UnsavedChangesReport };
@@ -0,0 +1,30 @@
1
+ import { getEditRuleValidator, isTypeDescriptor } from "@vuu-ui/vuu-utils";
2
+ const OK = {
3
+ ok: true
4
+ };
5
+ const NO_VALIDATION_RULES = [];
6
+ function getEditValidationRules(descriptor, editPhase) {
7
+ if (isTypeDescriptor(descriptor.type)) return "*" === editPhase ? descriptor.type.rules ?? [] : descriptor.type.rules?.filter(({ phase: a = "commit" })=>a === editPhase) ?? NO_VALIDATION_RULES;
8
+ return NO_VALIDATION_RULES;
9
+ }
10
+ const buildValidationChecker = (rules)=>(value, editPhase)=>applyRules(rules, value, editPhase);
11
+ function applyRules(rules, value, editPhase = "commit") {
12
+ const result = {
13
+ ok: true
14
+ };
15
+ for (const rule of rules){
16
+ const { phase = "commit" } = rule;
17
+ if ("*" === editPhase || phase === editPhase) {
18
+ const applyRuleToValue = getEditRuleValidator(rule.name);
19
+ if (applyRuleToValue) {
20
+ const res = applyRuleToValue(rule, value);
21
+ if (!res.ok) {
22
+ result.ok = false;
23
+ (result.messages ?? (result.messages = [])).push(res.message);
24
+ }
25
+ } else throw Error(`editable-utils applyRules, no validator registered for rule '${rule.name}'`);
26
+ }
27
+ }
28
+ return result;
29
+ }
30
+ export { OK, buildValidationChecker, getEditValidationRules };
@@ -0,0 +1,33 @@
1
+ import { registerComponent } from "@vuu-ui/vuu-utils";
2
+ import { OK } from "./edit-rule-validation-checker.js";
3
+ const isString = (value)=>"string" == typeof value;
4
+ const NUMERIC = /^(?:[0-9]|\.)+$/;
5
+ const CharValidatorNumeric = (rule, value)=>{
6
+ if (isString(value)) {
7
+ if ("" === value.trim()) return OK;
8
+ else if (value.match(NUMERIC)) return OK;
9
+ }
10
+ return {
11
+ ok: false,
12
+ message: "only numeric characters are permitted"
13
+ };
14
+ };
15
+ const ValueValidatorInteger = (rule, value)=>{
16
+ if (isString(value)) if ("" === value.trim()) return OK;
17
+ else {
18
+ if (!value.match(NUMERIC)) return {
19
+ ok: false,
20
+ message: "value must be an integer, invalid character"
21
+ };
22
+ if (parseFloat(value) === parseInt(value)) return OK;
23
+ }
24
+ return {
25
+ ok: false,
26
+ message: "must be an integer value"
27
+ };
28
+ };
29
+ const registerRules = ()=>{
30
+ registerComponent("char-numeric", CharValidatorNumeric, "data-edit-validator", {});
31
+ registerComponent("value-integer", ValueValidatorInteger, "data-edit-validator", {});
32
+ };
33
+ export { registerRules };
@@ -0,0 +1,16 @@
1
+ const CLEAN_FORM = {
2
+ isClean: true,
3
+ editedFields: []
4
+ };
5
+ const buildFormEditState = (entity, newEntity)=>{
6
+ if (void 0 === entity) return CLEAN_FORM;
7
+ {
8
+ const editedFields = [];
9
+ for (const [fieldName, value] of Object.entries(entity))if (value !== newEntity[fieldName]) editedFields.push(fieldName);
10
+ return {
11
+ isClean: 0 === editedFields.length,
12
+ editedFields
13
+ };
14
+ }
15
+ };
16
+ export { CLEAN_FORM, buildFormEditState };
@@ -0,0 +1,63 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { VuuDatePicker, VuuInput, VuuTimePicker, VuuTypeaheadInput } from "@vuu-ui/vuu-ui-controls";
3
+ import { asTimeString, isDataValueEditable, isDateTimeDataValue, isTimeDataValue } from "@vuu-ui/vuu-utils";
4
+ import { ToggleFilter } from "@vuu-ui/vuu-filters";
5
+ const getDataItemEditControl = ({ InputProps, TypeaheadProps, className, commitOnBlur, commitWhenCleared, dataDescriptor, editOperation = "update", errorMessage, onCommit, table, values, variant })=>{
6
+ const handleCommitNumber = (evt, value)=>{
7
+ onCommit(evt, value.toString());
8
+ };
9
+ const dataVariant = variant && "toggle" !== variant ? variant : void 0;
10
+ if (!isDataValueEditable(dataDescriptor, editOperation, true)) return /*#__PURE__*/ jsx(VuuInput, {
11
+ variant: "secondary",
12
+ ...InputProps,
13
+ onCommit: onCommit,
14
+ readOnly: true,
15
+ "data-edit-control": true
16
+ });
17
+ if (isTimeDataValue(dataDescriptor)) {
18
+ if (InputProps?.inputProps) {
19
+ const { value, onChange } = InputProps.inputProps;
20
+ return /*#__PURE__*/ jsx(VuuTimePicker, {
21
+ className: className,
22
+ value: asTimeString(value, true),
23
+ onChange: onChange,
24
+ onCommit: onCommit,
25
+ "data-edit-control": true
26
+ });
27
+ }
28
+ } else if (isDateTimeDataValue(dataDescriptor)) return /*#__PURE__*/ jsx(VuuDatePicker, {
29
+ className: className,
30
+ onCommit: handleCommitNumber,
31
+ "data-edit-control": true
32
+ });
33
+ else if ("string" === dataDescriptor.serverDataType && table) if ("toggle" === variant && values?.length) return /*#__PURE__*/ jsx(ToggleFilter, {
34
+ className: className,
35
+ column: dataDescriptor.name,
36
+ "data-edit-control": true,
37
+ onCommit: onCommit,
38
+ table: table,
39
+ values: values,
40
+ value: InputProps?.inputProps?.value ?? "all"
41
+ });
42
+ else return /*#__PURE__*/ jsx(VuuTypeaheadInput, {
43
+ ...InputProps,
44
+ ...TypeaheadProps,
45
+ className: className,
46
+ column: dataDescriptor.name,
47
+ "data-edit-control": true,
48
+ "data-variant": dataVariant,
49
+ onCommit: onCommit,
50
+ table: table
51
+ });
52
+ return /*#__PURE__*/ jsx(VuuInput, {
53
+ variant: "secondary",
54
+ ...InputProps,
55
+ className: className,
56
+ commitOnBlur: commitOnBlur,
57
+ commitWhenCleared: commitWhenCleared,
58
+ onCommit: onCommit,
59
+ errorMessage: errorMessage,
60
+ "data-edit-control": true
61
+ });
62
+ };
63
+ export { getDataItemEditControl };
@@ -0,0 +1,6 @@
1
+ export * from "./get-data-item-edit-control.js";
2
+ export * from "./EditForm.js";
3
+ export * from "./edit-rule-validation-checker.js";
4
+ export * from "./UnsavedChangesReport.js";
5
+ export * from "./useEditForm.js";
6
+ export * from "./form-edit-state.js";
@@ -0,0 +1,253 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Range, buildColumnMap, dataSourceRowToEntity, messageHasDataRows, queryClosest } from "@vuu-ui/vuu-utils";
3
+ import { Button } from "@salt-ds/core";
4
+ import { useCallback, useMemo, useRef, useState } from "react";
5
+ import { UnsavedChangesReport } from "./UnsavedChangesReport.js";
6
+ import { buildValidationChecker, getEditValidationRules } from "./edit-rule-validation-checker.js";
7
+ import { CLEAN_FORM, buildFormEditState } from "./form-edit-state.js";
8
+ import { useModal } from "@vuu-ui/vuu-ui-controls";
9
+ const CLEAN_VALIDATION = {
10
+ ok: true,
11
+ messages: {}
12
+ };
13
+ const getValidationChecker = (descriptor, editPhase)=>{
14
+ const rules = getEditValidationRules(descriptor, editPhase) ?? [];
15
+ return buildValidationChecker(rules);
16
+ };
17
+ const nextValidationState = (state, dataDescriptor, value)=>{
18
+ const check = getValidationChecker(dataDescriptor, "change");
19
+ const result = check(value, "change");
20
+ const { name } = dataDescriptor;
21
+ const { ok: wasOk, messages: existingMessages } = state;
22
+ if (!result.ok) return {
23
+ ok: false,
24
+ messages: {
25
+ ...existingMessages,
26
+ [name]: result.messages.join("\n")
27
+ }
28
+ };
29
+ if (!wasOk) {
30
+ const fieldsInError = Object.keys(existingMessages);
31
+ if (fieldsInError.includes(name)) if (1 === fieldsInError.length) return {
32
+ ok: true,
33
+ messages: {}
34
+ };
35
+ else {
36
+ const messages = {
37
+ ...existingMessages
38
+ };
39
+ delete messages[name];
40
+ return {
41
+ ok: false,
42
+ messages
43
+ };
44
+ }
45
+ }
46
+ return state;
47
+ };
48
+ function find(descriptors, fieldname) {
49
+ const d = descriptors.find(({ name })=>name === fieldname);
50
+ if (d) return d;
51
+ throw Error(`DataValueDescriptor not found for field ${fieldname}`);
52
+ }
53
+ const getField = (target)=>{
54
+ const fieldElement = queryClosest(target, "[data-field]");
55
+ if (fieldElement) return fieldElement.dataset.field;
56
+ throw Error("no field ");
57
+ };
58
+ const useEditForm = ({ dataSource, formFieldDescriptors, onSubmit })=>{
59
+ const { showDialog, closeDialog } = useModal();
60
+ const currentDataSource = useRef(void 0);
61
+ const formFieldsContainerRef = useRef(null);
62
+ const entityRef = useRef(void 0);
63
+ const focusedFieldRef = useRef("");
64
+ const originalEntityRef = useRef(void 0);
65
+ const formEditStateRef = useRef(CLEAN_FORM);
66
+ const validationStateRef = useRef({
67
+ ok: true,
68
+ messages: {}
69
+ });
70
+ const [entity, _setEntity] = useState();
71
+ const [, forceUpdate] = useState({});
72
+ const setFormEditState = useCallback((newState)=>{
73
+ formEditStateRef.current = newState;
74
+ }, []);
75
+ const setEntity = useCallback((newEntity)=>{
76
+ setFormEditState(buildFormEditState(originalEntityRef.current, newEntity));
77
+ entityRef.current = newEntity;
78
+ _setEntity(newEntity);
79
+ }, [
80
+ setFormEditState
81
+ ]);
82
+ const submitChanges = useCallback(async ()=>{
83
+ const rpcResponse = await currentDataSource.current?.rpcRequest?.({
84
+ params: {},
85
+ rpcName: "VP_BULK_EDIT_SUBMIT_RPC",
86
+ type: "RPC_REQUEST"
87
+ });
88
+ console.log({
89
+ rpcResponse
90
+ });
91
+ }, []);
92
+ const showSaveOrDiscardPrompt = useCallback(async ()=>{
93
+ const { current: currentEntity } = entityRef;
94
+ const { current: originalEntity } = originalEntityRef;
95
+ let resolver;
96
+ const save = async ()=>{
97
+ await submitChanges();
98
+ closeDialog();
99
+ resolver?.("saved");
100
+ };
101
+ const discard = ()=>{
102
+ closeDialog();
103
+ resolver?.("discarded");
104
+ };
105
+ requestAnimationFrame(()=>{
106
+ showDialog(/*#__PURE__*/ jsx(UnsavedChangesReport, {
107
+ entity: originalEntity,
108
+ editedEntity: currentEntity
109
+ }), "Unsaved Changes", [
110
+ /*#__PURE__*/ jsx(Button, {
111
+ onClick: discard,
112
+ children: "Discard Changes"
113
+ }, "cancel"),
114
+ /*#__PURE__*/ jsx(Button, {
115
+ onClick: save,
116
+ children: "Save Changes"
117
+ }, "submit")
118
+ ], true);
119
+ });
120
+ return new Promise((resolve)=>{
121
+ resolver = resolve;
122
+ });
123
+ }, [
124
+ closeDialog,
125
+ showDialog,
126
+ submitChanges
127
+ ]);
128
+ useMemo(async ()=>{
129
+ if (dataSource) {
130
+ if (false === formEditStateRef.current.isClean) await showSaveOrDiscardPrompt();
131
+ currentDataSource.current = dataSource;
132
+ originalEntityRef.current = void 0;
133
+ const columnMap = buildColumnMap(dataSource.columns);
134
+ dataSource?.subscribe({
135
+ range: Range(0, 1)
136
+ }, (message)=>{
137
+ if (messageHasDataRows(message)) {
138
+ const [row] = message.rows;
139
+ if (row) {
140
+ const entity = dataSourceRowToEntity(row, columnMap);
141
+ if (void 0 === originalEntityRef.current) {
142
+ originalEntityRef.current = entity;
143
+ setEntity(entity);
144
+ }
145
+ const { editedFields } = buildFormEditState(entityRef.current, entity);
146
+ if (1 === editedFields.length) setEntity(entity);
147
+ }
148
+ }
149
+ });
150
+ }
151
+ }, [
152
+ dataSource,
153
+ setEntity,
154
+ showSaveOrDiscardPrompt
155
+ ]);
156
+ const setValidationState = useCallback((state)=>{
157
+ validationStateRef.current = state;
158
+ forceUpdate({});
159
+ }, []);
160
+ const handleFieldCommit = useCallback(async (_, value)=>{
161
+ const { current: fieldName } = focusedFieldRef;
162
+ const dataDescriptor = find(formFieldDescriptors, fieldName);
163
+ const { current: state } = validationStateRef;
164
+ const newState = nextValidationState(state, dataDescriptor, value);
165
+ if (newState !== state) setValidationState(newState);
166
+ if (newState.ok && dataSource?.tableSchema) {
167
+ const { key } = dataSource.tableSchema;
168
+ const keyValue = entity?.[key];
169
+ const response = await dataSource.rpcRequest?.({
170
+ params: {
171
+ column: fieldName,
172
+ data: value,
173
+ key: keyValue
174
+ },
175
+ rpcName: "editCell",
176
+ type: "RPC_REQUEST"
177
+ });
178
+ console.log({
179
+ response
180
+ });
181
+ }
182
+ }, [
183
+ dataSource,
184
+ entity,
185
+ formFieldDescriptors,
186
+ setValidationState
187
+ ]);
188
+ const handleFieldChange = useCallback((evt)=>{
189
+ const { current: fieldName } = focusedFieldRef;
190
+ if (fieldName) {
191
+ const input = queryClosest(evt.target, "input", true);
192
+ const dataDescriptor = find(formFieldDescriptors, fieldName);
193
+ const value = input.value;
194
+ const { current: state } = validationStateRef;
195
+ const newState = nextValidationState(state, dataDescriptor, value);
196
+ if (newState !== state) setValidationState(newState);
197
+ setEntity({
198
+ ...entity,
199
+ [fieldName]: value
200
+ });
201
+ }
202
+ }, [
203
+ entity,
204
+ formFieldDescriptors,
205
+ setEntity,
206
+ setValidationState
207
+ ]);
208
+ const handleFormSubmit = useCallback(async ()=>{
209
+ submitChanges();
210
+ setFormEditState(CLEAN_FORM);
211
+ originalEntityRef.current = entity;
212
+ onSubmit?.();
213
+ forceUpdate({});
214
+ }, [
215
+ entity,
216
+ onSubmit,
217
+ setFormEditState,
218
+ submitChanges
219
+ ]);
220
+ const handleFormCancel = useCallback(async ()=>{
221
+ setFormEditState(CLEAN_FORM);
222
+ setValidationState(CLEAN_VALIDATION);
223
+ setEntity(originalEntityRef.current);
224
+ }, [
225
+ setEntity,
226
+ setFormEditState,
227
+ setValidationState
228
+ ]);
229
+ const handleFocus = useCallback((evt)=>{
230
+ if (formFieldsContainerRef.current?.contains(evt.target)) {
231
+ const fieldName = getField(evt.target);
232
+ if (fieldName) {
233
+ if (fieldName) focusedFieldRef.current = fieldName;
234
+ }
235
+ }
236
+ }, []);
237
+ const { current: { ok, messages: errorMessages } } = validationStateRef;
238
+ const { current: { isClean, editedFields } } = formEditStateRef;
239
+ return {
240
+ editedFields,
241
+ editEntity: entity,
242
+ errorMessages,
243
+ formFieldsContainerRef,
244
+ isClean,
245
+ ok,
246
+ onCancel: handleFormCancel,
247
+ onChange: handleFieldChange,
248
+ onCommit: handleFieldCommit,
249
+ onFocus: handleFocus,
250
+ onSubmit: handleFormSubmit
251
+ };
252
+ };
253
+ export { useEditForm };