@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.
- package/package.json +24 -20
- package/src/data-editing/EditForm.css.js +53 -0
- package/src/data-editing/EditForm.js +82 -0
- package/src/data-editing/UnsavedChangesReport.css.js +27 -0
- package/src/data-editing/UnsavedChangesReport.js +41 -0
- package/src/data-editing/edit-rule-validation-checker.js +30 -0
- package/src/data-editing/edit-validation-rules.js +33 -0
- package/src/data-editing/form-edit-state.js +16 -0
- package/src/data-editing/get-data-item-edit-control.js +63 -0
- package/src/data-editing/index.js +6 -0
- package/src/data-editing/useEditForm.js +253 -0
- package/src/datasource-provider/VuuDataSourceProvider.js +20 -0
- package/src/datasource-provider/index.js +1 -0
- package/src/datasource-provider/useAutoLoginToVuuServer.js +42 -0
- package/src/hooks/useLookupValues.js +93 -0
- package/src/hooks/useSessionDataSource.js +37 -0
- package/src/hooks/useTypeaheadSuggestions.js +50 -0
- package/src/hooks/useVisualLinks.js +87 -0
- package/src/hooks/useVuuMenuActions.js +269 -0
- package/src/hooks/useVuuTables.js +32 -0
- package/src/index.js +10 -0
- package/src/lost-connection-indicator/LostConnectionIndicator.css.js +29 -0
- package/src/lost-connection-indicator/LostConnectionIndicator.js +30 -0
- package/src/lost-connection-indicator/useLostConnection.js +28 -0
- package/src/session-editing-form/SessionEditingForm.css.js +47 -0
- package/src/session-editing-form/SessionEditingForm.js +236 -0
- package/src/session-editing-form/index.js +1 -0
- package/cjs/data-editing/EditForm.css.js +0 -6
- package/cjs/data-editing/EditForm.css.js.map +0 -1
- package/cjs/data-editing/EditForm.js +0 -90
- package/cjs/data-editing/EditForm.js.map +0 -1
- package/cjs/data-editing/UnsavedChangesReport.css.js +0 -6
- package/cjs/data-editing/UnsavedChangesReport.css.js.map +0 -1
- package/cjs/data-editing/UnsavedChangesReport.js +0 -29
- package/cjs/data-editing/UnsavedChangesReport.js.map +0 -1
- package/cjs/data-editing/edit-rule-validation-checker.js +0 -41
- package/cjs/data-editing/edit-rule-validation-checker.js.map +0 -1
- package/cjs/data-editing/edit-validation-rules.js +0 -52
- package/cjs/data-editing/edit-validation-rules.js.map +0 -1
- package/cjs/data-editing/form-edit-state.js +0 -26
- package/cjs/data-editing/form-edit-state.js.map +0 -1
- package/cjs/data-editing/get-data-item-edit-control.js +0 -106
- package/cjs/data-editing/get-data-item-edit-control.js.map +0 -1
- package/cjs/data-editing/useEditForm.js +0 -258
- package/cjs/data-editing/useEditForm.js.map +0 -1
- package/cjs/datasource-provider/VuuDataSourceProvider.js +0 -34
- package/cjs/datasource-provider/VuuDataSourceProvider.js.map +0 -1
- package/cjs/datasource-provider/useAutoLoginToVuuServer.js +0 -52
- package/cjs/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
- package/cjs/hooks/useLookupValues.js +0 -100
- package/cjs/hooks/useLookupValues.js.map +0 -1
- package/cjs/hooks/useSessionDataSource.js +0 -58
- package/cjs/hooks/useSessionDataSource.js.map +0 -1
- package/cjs/hooks/useTypeaheadSuggestions.js +0 -57
- package/cjs/hooks/useTypeaheadSuggestions.js.map +0 -1
- package/cjs/hooks/useVisualLinks.js +0 -84
- package/cjs/hooks/useVisualLinks.js.map +0 -1
- package/cjs/hooks/useVuuMenuActions.js +0 -352
- package/cjs/hooks/useVuuMenuActions.js.map +0 -1
- package/cjs/hooks/useVuuTables.js +0 -38
- package/cjs/hooks/useVuuTables.js.map +0 -1
- package/cjs/index.js +0 -42
- package/cjs/index.js.map +0 -1
- package/cjs/lost-connection-indicator/LostConnectionIndicator.css.js +0 -6
- package/cjs/lost-connection-indicator/LostConnectionIndicator.css.js.map +0 -1
- package/cjs/lost-connection-indicator/LostConnectionIndicator.js +0 -24
- package/cjs/lost-connection-indicator/LostConnectionIndicator.js.map +0 -1
- package/cjs/lost-connection-indicator/useLostConnection.js +0 -34
- package/cjs/lost-connection-indicator/useLostConnection.js.map +0 -1
- package/cjs/session-editing-form/SessionEditingForm.css.js +0 -6
- package/cjs/session-editing-form/SessionEditingForm.css.js.map +0 -1
- package/cjs/session-editing-form/SessionEditingForm.js +0 -270
- package/cjs/session-editing-form/SessionEditingForm.js.map +0 -1
- package/esm/data-editing/EditForm.css.js +0 -4
- package/esm/data-editing/EditForm.css.js.map +0 -1
- package/esm/data-editing/EditForm.js +0 -88
- package/esm/data-editing/EditForm.js.map +0 -1
- package/esm/data-editing/UnsavedChangesReport.css.js +0 -4
- package/esm/data-editing/UnsavedChangesReport.css.js.map +0 -1
- package/esm/data-editing/UnsavedChangesReport.js +0 -27
- package/esm/data-editing/UnsavedChangesReport.js.map +0 -1
- package/esm/data-editing/edit-rule-validation-checker.js +0 -37
- package/esm/data-editing/edit-rule-validation-checker.js.map +0 -1
- package/esm/data-editing/edit-validation-rules.js +0 -50
- package/esm/data-editing/edit-validation-rules.js.map +0 -1
- package/esm/data-editing/form-edit-state.js +0 -23
- package/esm/data-editing/form-edit-state.js.map +0 -1
- package/esm/data-editing/get-data-item-edit-control.js +0 -104
- package/esm/data-editing/get-data-item-edit-control.js.map +0 -1
- package/esm/data-editing/useEditForm.js +0 -256
- package/esm/data-editing/useEditForm.js.map +0 -1
- package/esm/datasource-provider/VuuDataSourceProvider.js +0 -32
- package/esm/datasource-provider/VuuDataSourceProvider.js.map +0 -1
- package/esm/datasource-provider/useAutoLoginToVuuServer.js +0 -50
- package/esm/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
- package/esm/hooks/useLookupValues.js +0 -98
- package/esm/hooks/useLookupValues.js.map +0 -1
- package/esm/hooks/useSessionDataSource.js +0 -56
- package/esm/hooks/useSessionDataSource.js.map +0 -1
- package/esm/hooks/useTypeaheadSuggestions.js +0 -54
- package/esm/hooks/useTypeaheadSuggestions.js.map +0 -1
- package/esm/hooks/useVisualLinks.js +0 -82
- package/esm/hooks/useVisualLinks.js.map +0 -1
- package/esm/hooks/useVuuMenuActions.js +0 -348
- package/esm/hooks/useVuuMenuActions.js.map +0 -1
- package/esm/hooks/useVuuTables.js +0 -36
- package/esm/hooks/useVuuTables.js.map +0 -1
- package/esm/index.js +0 -16
- package/esm/index.js.map +0 -1
- package/esm/lost-connection-indicator/LostConnectionIndicator.css.js +0 -4
- package/esm/lost-connection-indicator/LostConnectionIndicator.css.js.map +0 -1
- package/esm/lost-connection-indicator/LostConnectionIndicator.js +0 -22
- package/esm/lost-connection-indicator/LostConnectionIndicator.js.map +0 -1
- package/esm/lost-connection-indicator/useLostConnection.js +0 -32
- package/esm/lost-connection-indicator/useLostConnection.js.map +0 -1
- package/esm/session-editing-form/SessionEditingForm.css.js +0 -4
- package/esm/session-editing-form/SessionEditingForm.css.js.map +0 -1
- package/esm/session-editing-form/SessionEditingForm.js +0 -268
- package/esm/session-editing-form/SessionEditingForm.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "3.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.
|
|
7
|
-
"@vuu-ui/vuu-filter-types": "3.0.
|
|
8
|
-
"@vuu-ui/vuu-protocol-types": "3.0.
|
|
9
|
-
"@vuu-ui/vuu-table-types": "3.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.
|
|
13
|
-
"@vuu-ui/vuu-data-remote": "3.0.
|
|
14
|
-
"@vuu-ui/vuu-filter-parser": "3.0.
|
|
15
|
-
"@vuu-ui/vuu-filters": "3.0.
|
|
16
|
-
"@vuu-ui/vuu-layout": "3.0.
|
|
17
|
-
"@vuu-ui/vuu-notifications": "3.0.
|
|
18
|
-
"@vuu-ui/vuu-ui-controls": "3.0.
|
|
19
|
-
"@vuu-ui/vuu-utils": "3.0.
|
|
20
|
-
"@vuu-ui/vuu-table": "3.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
|
-
"
|
|
33
|
-
"cjs",
|
|
39
|
+
"src",
|
|
34
40
|
"/types"
|
|
35
41
|
],
|
|
36
42
|
"exports": {
|
|
37
43
|
".": {
|
|
38
|
-
"
|
|
39
|
-
"import": "./esm/index.js",
|
|
44
|
+
"import": "./src/index.js",
|
|
40
45
|
"types": "./types/index.d.ts"
|
|
41
46
|
}
|
|
42
47
|
},
|
|
43
|
-
"
|
|
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,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 };
|