@vuu-ui/vuu-data-react 0.13.6 → 0.13.8

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 (85) hide show
  1. package/cjs/index.js +1556 -40
  2. package/cjs/index.js.map +1 -1
  3. package/esm/index.js +1535 -15
  4. package/esm/index.js.map +1 -1
  5. package/package.json +14 -14
  6. package/cjs/data-editing/EditForm.css.js +0 -6
  7. package/cjs/data-editing/EditForm.css.js.map +0 -1
  8. package/cjs/data-editing/EditForm.js +0 -90
  9. package/cjs/data-editing/EditForm.js.map +0 -1
  10. package/cjs/data-editing/UnsavedChangesReport.css.js +0 -6
  11. package/cjs/data-editing/UnsavedChangesReport.css.js.map +0 -1
  12. package/cjs/data-editing/UnsavedChangesReport.js +0 -29
  13. package/cjs/data-editing/UnsavedChangesReport.js.map +0 -1
  14. package/cjs/data-editing/edit-rule-validation-checker.js +0 -41
  15. package/cjs/data-editing/edit-rule-validation-checker.js.map +0 -1
  16. package/cjs/data-editing/edit-validation-rules.js +0 -52
  17. package/cjs/data-editing/edit-validation-rules.js.map +0 -1
  18. package/cjs/data-editing/form-edit-state.js +0 -26
  19. package/cjs/data-editing/form-edit-state.js.map +0 -1
  20. package/cjs/data-editing/get-data-item-edit-control.js +0 -56
  21. package/cjs/data-editing/get-data-item-edit-control.js.map +0 -1
  22. package/cjs/data-editing/useEditForm.js +0 -249
  23. package/cjs/data-editing/useEditForm.js.map +0 -1
  24. package/cjs/datasource-provider/RestDataSourceProvider.js +0 -78
  25. package/cjs/datasource-provider/RestDataSourceProvider.js.map +0 -1
  26. package/cjs/datasource-provider/VuuDataSourceProvider.js +0 -34
  27. package/cjs/datasource-provider/VuuDataSourceProvider.js.map +0 -1
  28. package/cjs/datasource-provider/useAutoLoginToVuuServer.js +0 -54
  29. package/cjs/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
  30. package/cjs/hooks/useLookupValues.js +0 -100
  31. package/cjs/hooks/useLookupValues.js.map +0 -1
  32. package/cjs/hooks/useSessionDataSource.js +0 -72
  33. package/cjs/hooks/useSessionDataSource.js.map +0 -1
  34. package/cjs/hooks/useTypeaheadSuggestions.js +0 -41
  35. package/cjs/hooks/useTypeaheadSuggestions.js.map +0 -1
  36. package/cjs/hooks/useVisualLinks.js +0 -83
  37. package/cjs/hooks/useVisualLinks.js.map +0 -1
  38. package/cjs/hooks/useVuuMenuActions.js +0 -362
  39. package/cjs/hooks/useVuuMenuActions.js.map +0 -1
  40. package/cjs/hooks/useVuuTables.js +0 -38
  41. package/cjs/hooks/useVuuTables.js.map +0 -1
  42. package/cjs/session-editing-form/SessionEditingForm.css.js +0 -6
  43. package/cjs/session-editing-form/SessionEditingForm.css.js.map +0 -1
  44. package/cjs/session-editing-form/SessionEditingForm.js +0 -269
  45. package/cjs/session-editing-form/SessionEditingForm.js.map +0 -1
  46. package/esm/data-editing/EditForm.css.js +0 -4
  47. package/esm/data-editing/EditForm.css.js.map +0 -1
  48. package/esm/data-editing/EditForm.js +0 -88
  49. package/esm/data-editing/EditForm.js.map +0 -1
  50. package/esm/data-editing/UnsavedChangesReport.css.js +0 -4
  51. package/esm/data-editing/UnsavedChangesReport.css.js.map +0 -1
  52. package/esm/data-editing/UnsavedChangesReport.js +0 -27
  53. package/esm/data-editing/UnsavedChangesReport.js.map +0 -1
  54. package/esm/data-editing/edit-rule-validation-checker.js +0 -37
  55. package/esm/data-editing/edit-rule-validation-checker.js.map +0 -1
  56. package/esm/data-editing/edit-validation-rules.js +0 -50
  57. package/esm/data-editing/edit-validation-rules.js.map +0 -1
  58. package/esm/data-editing/form-edit-state.js +0 -23
  59. package/esm/data-editing/form-edit-state.js.map +0 -1
  60. package/esm/data-editing/get-data-item-edit-control.js +0 -54
  61. package/esm/data-editing/get-data-item-edit-control.js.map +0 -1
  62. package/esm/data-editing/useEditForm.js +0 -247
  63. package/esm/data-editing/useEditForm.js.map +0 -1
  64. package/esm/datasource-provider/RestDataSourceProvider.js +0 -75
  65. package/esm/datasource-provider/RestDataSourceProvider.js.map +0 -1
  66. package/esm/datasource-provider/VuuDataSourceProvider.js +0 -32
  67. package/esm/datasource-provider/VuuDataSourceProvider.js.map +0 -1
  68. package/esm/datasource-provider/useAutoLoginToVuuServer.js +0 -52
  69. package/esm/datasource-provider/useAutoLoginToVuuServer.js.map +0 -1
  70. package/esm/hooks/useLookupValues.js +0 -98
  71. package/esm/hooks/useLookupValues.js.map +0 -1
  72. package/esm/hooks/useSessionDataSource.js +0 -70
  73. package/esm/hooks/useSessionDataSource.js.map +0 -1
  74. package/esm/hooks/useTypeaheadSuggestions.js +0 -38
  75. package/esm/hooks/useTypeaheadSuggestions.js.map +0 -1
  76. package/esm/hooks/useVisualLinks.js +0 -81
  77. package/esm/hooks/useVisualLinks.js.map +0 -1
  78. package/esm/hooks/useVuuMenuActions.js +0 -358
  79. package/esm/hooks/useVuuMenuActions.js.map +0 -1
  80. package/esm/hooks/useVuuTables.js +0 -36
  81. package/esm/hooks/useVuuTables.js.map +0 -1
  82. package/esm/session-editing-form/SessionEditingForm.css.js +0 -4
  83. package/esm/session-editing-form/SessionEditingForm.css.js.map +0 -1
  84. package/esm/session-editing-form/SessionEditingForm.js +0 -267
  85. package/esm/session-editing-form/SessionEditingForm.js.map +0 -1
package/cjs/index.js CHANGED
@@ -1,43 +1,1559 @@
1
1
  'use strict';
2
2
 
3
- var getDataItemEditControl = require('./data-editing/get-data-item-edit-control.js');
4
- var EditForm = require('./data-editing/EditForm.js');
5
- var editRuleValidationChecker = require('./data-editing/edit-rule-validation-checker.js');
6
- var UnsavedChangesReport = require('./data-editing/UnsavedChangesReport.js');
7
- var useEditForm = require('./data-editing/useEditForm.js');
8
- var formEditState = require('./data-editing/form-edit-state.js');
9
- var RestDataSourceProvider = require('./datasource-provider/RestDataSourceProvider.js');
10
- var VuuDataSourceProvider = require('./datasource-provider/VuuDataSourceProvider.js');
11
- var useLookupValues = require('./hooks/useLookupValues.js');
12
- var useSessionDataSource = require('./hooks/useSessionDataSource.js');
13
- var useVuuMenuActions = require('./hooks/useVuuMenuActions.js');
14
- var useVuuTables = require('./hooks/useVuuTables.js');
15
- var useVisualLinks = require('./hooks/useVisualLinks.js');
16
- var useTypeaheadSuggestions = require('./hooks/useTypeaheadSuggestions.js');
17
- var SessionEditingForm = require('./session-editing-form/SessionEditingForm.js');
18
-
19
-
20
-
21
- exports.getDataItemEditControl = getDataItemEditControl.getDataItemEditControl;
22
- exports.EditForm = EditForm.EditForm;
23
- exports.OK = editRuleValidationChecker.OK;
24
- exports.buildValidationChecker = editRuleValidationChecker.buildValidationChecker;
25
- exports.getEditValidationRules = editRuleValidationChecker.getEditValidationRules;
26
- exports.UnsavedChangesReport = UnsavedChangesReport.UnsavedChangesReport;
27
- exports.useEditForm = useEditForm.useEditForm;
28
- exports.CLEAN_FORM = formEditState.CLEAN_FORM;
29
- exports.buildFormEditState = formEditState.buildFormEditState;
30
- exports.RestDataSourceProvider = RestDataSourceProvider.RestDataSourceProvider;
31
- exports.isRestDataSourceExtension = RestDataSourceProvider.isRestDataSourceExtension;
32
- exports.VuuDataSourceProvider = VuuDataSourceProvider.VuuDataSourceProvider;
33
- exports.useLookupValues = useLookupValues.useLookupValues;
34
- exports.useSessionDataSource = useSessionDataSource.useSessionDataSource;
35
- exports.isRowMenu = useVuuMenuActions.isRowMenu;
36
- exports.isSelectionMenu = useVuuMenuActions.isSelectionMenu;
37
- exports.useVuuMenuActions = useVuuMenuActions.useVuuMenuActions;
38
- exports.useVuuTables = useVuuTables.useVuuTables;
39
- exports.useVisualLinks = useVisualLinks.useVisualLinks;
40
- exports.getTypeaheadParams = useTypeaheadSuggestions.getTypeaheadParams;
41
- exports.useTypeaheadSuggestions = useTypeaheadSuggestions.useTypeaheadSuggestions;
42
- exports.SessionEditingForm = SessionEditingForm.SessionEditingForm;
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var vuuUiControls = require('@vuu-ui/vuu-ui-controls');
5
+ var vuuUtils = require('@vuu-ui/vuu-utils');
6
+ var core = require('@salt-ds/core');
7
+ var styles = require('@salt-ds/styles');
8
+ var window = require('@salt-ds/window');
9
+ var cx = require('clsx');
10
+ var vuuPopups = require('@vuu-ui/vuu-popups');
11
+ var react = require('react');
12
+ var vuuDataRemote = require('@vuu-ui/vuu-data-remote');
13
+ var vuuLayout = require('@vuu-ui/vuu-layout');
14
+ var vuuContextMenu = require('@vuu-ui/vuu-context-menu');
15
+ var vuuFilterParser = require('@vuu-ui/vuu-filter-parser');
16
+ var vuuTable = require('@vuu-ui/vuu-table');
17
+
18
+ const getDataItemEditControl = ({
19
+ InputProps: InputProps2,
20
+ TypeaheadProps,
21
+ commitWhenCleared,
22
+ dataDescriptor,
23
+ errorMessage,
24
+ onCommit,
25
+ table
26
+ }) => {
27
+ const handleCommitNumber = (evt, value) => {
28
+ onCommit(evt, value.toString());
29
+ };
30
+ if (dataDescriptor.editable === false) {
31
+ return /* @__PURE__ */ jsxRuntime.jsx(
32
+ vuuUiControls.VuuInput,
33
+ {
34
+ variant: "secondary",
35
+ ...InputProps2,
36
+ onCommit,
37
+ readOnly: true
38
+ }
39
+ );
40
+ } else if (vuuUtils.isDateTimeDataValue(dataDescriptor)) {
41
+ return /* @__PURE__ */ jsxRuntime.jsx(vuuUiControls.VuuDatePicker, { onCommit: handleCommitNumber });
42
+ } else if (dataDescriptor.serverDataType === "string" && table) {
43
+ return /* @__PURE__ */ jsxRuntime.jsx(
44
+ vuuUiControls.VuuTypeaheadInput,
45
+ {
46
+ ...InputProps2,
47
+ ...TypeaheadProps,
48
+ column: dataDescriptor.name,
49
+ onCommit,
50
+ table
51
+ }
52
+ );
53
+ }
54
+ return /* @__PURE__ */ jsxRuntime.jsx(
55
+ vuuUiControls.VuuInput,
56
+ {
57
+ variant: "secondary",
58
+ ...InputProps2,
59
+ commitWhenCleared,
60
+ onCommit,
61
+ errorMessage
62
+ }
63
+ );
64
+ };
65
+
66
+ const OK = { ok: true };
67
+ const NO_VALIDATION_RULES = [];
68
+ function getEditValidationRules(descriptor, editPhase) {
69
+ if (vuuUtils.isTypeDescriptor(descriptor.type)) {
70
+ return editPhase === "*" ? descriptor.type.rules ?? [] : descriptor.type.rules?.filter(
71
+ ({ phase: a = "commit" }) => a === editPhase
72
+ ) ?? NO_VALIDATION_RULES;
73
+ }
74
+ return NO_VALIDATION_RULES;
75
+ }
76
+ const buildValidationChecker = (rules) => (value, editPhase) => applyRules(rules, value, editPhase);
77
+ function applyRules(rules, value, editPhase = "commit") {
78
+ const result = { ok: true };
79
+ for (const rule of rules) {
80
+ const { phase = "commit" } = rule;
81
+ if (editPhase === "*" || phase === editPhase) {
82
+ const applyRuleToValue = vuuUtils.getEditRuleValidator(rule.name);
83
+ if (applyRuleToValue) {
84
+ const res = applyRuleToValue(rule, value);
85
+ if (!res.ok) {
86
+ result.ok = false;
87
+ (result.messages ?? (result.messages = [])).push(res.message);
88
+ }
89
+ } else {
90
+ throw Error(
91
+ `editable-utils applyRules, no validator registered for rule '${rule.name}'`
92
+ );
93
+ }
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+
99
+ const isString = (value) => typeof value === "string";
100
+ const NUMERIC = /^(?:[0-9]|\.)+$/;
101
+ const CharValidatorNumeric = (rule, value) => {
102
+ if (isString(value)) {
103
+ if (value.trim() === "") {
104
+ return OK;
105
+ } else if (value.match(NUMERIC)) {
106
+ return OK;
107
+ }
108
+ }
109
+ return { ok: false, message: "only numeric characters are permitted" };
110
+ };
111
+ const ValueValidatorInteger = (rule, value) => {
112
+ if (isString(value)) {
113
+ if (value.trim() === "") {
114
+ return OK;
115
+ } else {
116
+ if (!value.match(NUMERIC)) {
117
+ return {
118
+ ok: false,
119
+ message: "value must be an integer, invalid character"
120
+ };
121
+ }
122
+ if (parseFloat(value) === parseInt(value)) {
123
+ return OK;
124
+ }
125
+ }
126
+ }
127
+ return { ok: false, message: "must be an integer value" };
128
+ };
129
+ const registerRules = () => {
130
+ vuuUtils.registerComponent(
131
+ "char-numeric",
132
+ CharValidatorNumeric,
133
+ "data-edit-validator",
134
+ {}
135
+ );
136
+ vuuUtils.registerComponent(
137
+ "value-integer",
138
+ ValueValidatorInteger,
139
+ "data-edit-validator",
140
+ {}
141
+ );
142
+ };
143
+
144
+ const CLEAN_FORM = {
145
+ isClean: true,
146
+ editedFields: []
147
+ };
148
+ const buildFormEditState = (entity, newEntity) => {
149
+ if (entity === void 0) {
150
+ return CLEAN_FORM;
151
+ } else {
152
+ const editedFields = [];
153
+ for (const [fieldName, value] of Object.entries(entity)) {
154
+ if (value !== newEntity[fieldName]) {
155
+ editedFields.push(fieldName);
156
+ }
157
+ }
158
+ return {
159
+ isClean: editedFields.length === 0,
160
+ editedFields
161
+ };
162
+ }
163
+ };
164
+
165
+ var unsavedChangesCss = ".vuuUnsavedChanges-table {\n border-collapse: collapse;\n width: 100%;\n}\n\n.vuuUnsavedChanges-row {\n box-sizing: content-box;\n border-bottom: solid 1px var(--salt-separable-secondary-borderColor);\n height: 32px;\n\n td {\n padding: 0 var(--salt-spacing-200);\n }\n}\n\n.vuuUnsavedChanges-fieldName {\n text-transform: capitalize;\n}\n\n.vuuUnsavedChanges-new {\n font-weight: bold;\n}\n";
166
+
167
+ const classBase$2 = "vuuUnsavedChanges";
168
+ const UnsavedChangesReport = ({
169
+ entity,
170
+ editedEntity
171
+ }) => {
172
+ const targetWindow = window.useWindow();
173
+ styles.useComponentCssInjection({
174
+ testId: "vuu-unsaved-changes-report",
175
+ css: unsavedChangesCss,
176
+ window: targetWindow
177
+ });
178
+ const { editedFields } = buildFormEditState(entity, editedEntity);
179
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: classBase$2, children: /* @__PURE__ */ jsxRuntime.jsx("table", { className: `${classBase$2}-table`, children: /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: editedFields.map((fieldName, i) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: `${classBase$2}-row`, children: [
180
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: `${classBase$2}-fieldName`, children: fieldName }),
181
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: `${classBase$2}-old`, children: entity[fieldName] }),
182
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: `${classBase$2}-new`, children: editedEntity[fieldName] })
183
+ ] }, i)) }) }) });
184
+ };
185
+
186
+ const CLEAN_VALIDATION = {
187
+ ok: true,
188
+ messages: {}
189
+ };
190
+ const getValidationChecker = (descriptor, editPhase) => {
191
+ const rules = getEditValidationRules(descriptor, editPhase) ?? [];
192
+ return buildValidationChecker(rules);
193
+ };
194
+ const nextValidationState = (state, dataDescriptor, value) => {
195
+ const check = getValidationChecker(dataDescriptor, "change");
196
+ const result = check(value, "change");
197
+ const { name } = dataDescriptor;
198
+ const { ok: wasOk, messages: existingMessages } = state;
199
+ if (result.ok) {
200
+ if (!wasOk) {
201
+ const fieldsInError = Object.keys(existingMessages);
202
+ if (fieldsInError.includes(name)) {
203
+ if (fieldsInError.length === 1) {
204
+ return { ok: true, messages: {} };
205
+ } else {
206
+ const messages = { ...existingMessages };
207
+ delete messages[name];
208
+ return { ok: false, messages };
209
+ }
210
+ }
211
+ }
212
+ } else {
213
+ return {
214
+ ok: false,
215
+ messages: {
216
+ ...existingMessages,
217
+ [name]: result.messages.join("\n")
218
+ }
219
+ };
220
+ }
221
+ return state;
222
+ };
223
+ function find(descriptors, fieldname) {
224
+ const d = descriptors.find(({ name }) => name === fieldname);
225
+ if (d) {
226
+ return d;
227
+ }
228
+ throw Error(`DataValueDescriptor not found for field ${fieldname}`);
229
+ }
230
+ const getField$1 = (target) => {
231
+ const fieldElement = vuuUtils.queryClosest(target, "[data-field]");
232
+ if (fieldElement) {
233
+ return fieldElement.dataset.field;
234
+ } else {
235
+ throw Error("no field ");
236
+ }
237
+ };
238
+ const useEditForm = ({
239
+ dataSource,
240
+ formFieldDescriptors,
241
+ onSubmit
242
+ }) => {
243
+ const { showDialog, closeDialog } = vuuPopups.useDialogContext();
244
+ const currentDataSource = react.useRef(void 0);
245
+ const formFieldsContainerRef = react.useRef(null);
246
+ const entityRef = react.useRef(void 0);
247
+ const focusedFieldRef = react.useRef("");
248
+ const originalEntityRef = react.useRef(void 0);
249
+ const formEditStateRef = react.useRef(CLEAN_FORM);
250
+ const validationStateRef = react.useRef({
251
+ ok: true,
252
+ messages: {}
253
+ });
254
+ const [entity, _setEntity] = react.useState();
255
+ const [, forceUpdate] = react.useState({});
256
+ const setFormEditState = react.useCallback((newState) => {
257
+ formEditStateRef.current = newState;
258
+ }, []);
259
+ const setEntity = react.useCallback(
260
+ (newEntity) => {
261
+ setFormEditState(
262
+ buildFormEditState(originalEntityRef.current, newEntity)
263
+ );
264
+ entityRef.current = newEntity;
265
+ _setEntity(newEntity);
266
+ },
267
+ [setFormEditState]
268
+ );
269
+ const submitChanges = react.useCallback(async () => {
270
+ const rpcResponse = await currentDataSource.current?.rpcCall?.(
271
+ vuuUtils.viewportRpcRequest("VP_BULK_EDIT_SUBMIT_RPC")
272
+ );
273
+ console.log({ rpcResponse });
274
+ }, []);
275
+ const showSaveOrDiscardPrompt = react.useCallback(async () => {
276
+ const { current: currentEntity } = entityRef;
277
+ const { current: originalEntity } = originalEntityRef;
278
+ let resolver = void 0;
279
+ const save = async () => {
280
+ await submitChanges();
281
+ closeDialog();
282
+ resolver?.("saved");
283
+ };
284
+ const discard = () => {
285
+ closeDialog();
286
+ resolver?.("discarded");
287
+ };
288
+ requestAnimationFrame(() => {
289
+ showDialog(
290
+ /* @__PURE__ */ jsxRuntime.jsx(
291
+ UnsavedChangesReport,
292
+ {
293
+ entity: originalEntity,
294
+ editedEntity: currentEntity
295
+ }
296
+ ),
297
+ "Unsaved Changes",
298
+ [
299
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: discard, children: "Discard Changes" }, "cancel"),
300
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: save, children: "Save Changes" }, "submit")
301
+ ],
302
+ true
303
+ // hideCloseButton
304
+ );
305
+ });
306
+ return new Promise((resolve) => {
307
+ resolver = resolve;
308
+ });
309
+ }, [closeDialog, showDialog, submitChanges]);
310
+ react.useMemo(async () => {
311
+ if (dataSource) {
312
+ if (formEditStateRef.current.isClean === false) {
313
+ await showSaveOrDiscardPrompt();
314
+ }
315
+ currentDataSource.current = dataSource;
316
+ originalEntityRef.current = void 0;
317
+ const columnMap = vuuUtils.buildColumnMap(dataSource.columns);
318
+ dataSource?.subscribe({ range: vuuUtils.Range(0, 1) }, (message) => {
319
+ if (vuuUtils.messageHasDataRows(message)) {
320
+ const [row] = message.rows;
321
+ if (row) {
322
+ const entity2 = vuuUtils.dataSourceRowToEntity(row, columnMap);
323
+ if (originalEntityRef.current === void 0) {
324
+ originalEntityRef.current = entity2;
325
+ setEntity(entity2);
326
+ }
327
+ const { editedFields: editedFields2 } = buildFormEditState(
328
+ entityRef.current,
329
+ entity2
330
+ );
331
+ if (editedFields2.length === 1) {
332
+ setEntity(entity2);
333
+ }
334
+ }
335
+ }
336
+ });
337
+ }
338
+ }, [dataSource, setEntity, showSaveOrDiscardPrompt]);
339
+ const setValidationState = react.useCallback((state) => {
340
+ validationStateRef.current = state;
341
+ forceUpdate({});
342
+ }, []);
343
+ const handleFieldCommit = react.useCallback(
344
+ (_, value) => {
345
+ const { current: fieldName } = focusedFieldRef;
346
+ const dataDescriptor = find(formFieldDescriptors, fieldName);
347
+ const { current: state } = validationStateRef;
348
+ const newState = nextValidationState(state, dataDescriptor, value);
349
+ if (newState !== state) {
350
+ setValidationState(newState);
351
+ }
352
+ if (newState.ok && dataSource?.tableSchema) {
353
+ const { key } = dataSource.tableSchema;
354
+ const keyValue = entity?.[key];
355
+ dataSource?.applyEdit(keyValue, fieldName, value).then((rpcResponse) => {
356
+ console.log({ rpcResponse });
357
+ });
358
+ }
359
+ },
360
+ [dataSource, entity, formFieldDescriptors, setValidationState]
361
+ );
362
+ const handleFieldChange = react.useCallback(
363
+ (evt) => {
364
+ const { current: fieldName } = focusedFieldRef;
365
+ if (fieldName) {
366
+ const input = vuuUtils.queryClosest(evt.target, "input", true);
367
+ const dataDescriptor = find(formFieldDescriptors, fieldName);
368
+ const value = input.value;
369
+ const { current: state } = validationStateRef;
370
+ const newState = nextValidationState(state, dataDescriptor, value);
371
+ if (newState !== state) {
372
+ setValidationState(newState);
373
+ }
374
+ setEntity({ ...entity, [fieldName]: value });
375
+ }
376
+ },
377
+ [entity, formFieldDescriptors, setEntity, setValidationState]
378
+ );
379
+ const handleFormSubmit = react.useCallback(async () => {
380
+ submitChanges();
381
+ setFormEditState(CLEAN_FORM);
382
+ originalEntityRef.current = entity;
383
+ onSubmit?.();
384
+ forceUpdate({});
385
+ }, [entity, onSubmit, setFormEditState, submitChanges]);
386
+ const handleFormCancel = react.useCallback(async () => {
387
+ setFormEditState(CLEAN_FORM);
388
+ setValidationState(CLEAN_VALIDATION);
389
+ setEntity(originalEntityRef.current);
390
+ }, [setEntity, setFormEditState, setValidationState]);
391
+ const handleFocus = react.useCallback((evt) => {
392
+ if (formFieldsContainerRef.current?.contains(evt.target)) {
393
+ const fieldName = getField$1(evt.target);
394
+ if (fieldName) {
395
+ if (fieldName) {
396
+ focusedFieldRef.current = fieldName;
397
+ }
398
+ }
399
+ }
400
+ }, []);
401
+ const {
402
+ current: { ok, messages: errorMessages }
403
+ } = validationStateRef;
404
+ const {
405
+ current: { isClean, editedFields }
406
+ } = formEditStateRef;
407
+ return {
408
+ editedFields,
409
+ editEntity: entity,
410
+ errorMessages,
411
+ formFieldsContainerRef,
412
+ isClean,
413
+ ok,
414
+ onCancel: handleFormCancel,
415
+ onChange: handleFieldChange,
416
+ onCommit: handleFieldCommit,
417
+ onFocus: handleFocus,
418
+ onSubmit: handleFormSubmit
419
+ };
420
+ };
421
+
422
+ var editFormCss = ".EditForm {\n display: flex;\n flex-direction: column;\n gap: var(--salt-spacing-400);\n height: 100%;\n padding: var(--salt-spacing-200) var(--salt-spacing-200)\n var(--salt-spacing-200) var(--salt-spacing-400);\n width: 100%;\n}\n\n.EditForm-form-fields {\n display: flex;\n flex-direction: column;\n gap: var(--salt-spacing-400);\n}\n\n.EditForm-buttons {\n align-items: center;\n display: flex;\n gap: var(--salt-spacing-200);\n justify-content: flex-end;\n}\n\n.EditForm-field {\n display: flex;\n\n .saltFormField {\n flex: 1 1 auto;\n }\n\n .EditForm-edit-indicator {\n flex: 0 0 14px;\n position: relative;\n }\n\n &[data-edited=\"true\"] {\n .EditForm-edit-indicator:after {\n background-color: var(--salt-content-secondary-foreground);\n border-radius: 5px;\n content: \"\";\n height: 10px;\n position: absolute;\n top: 50%;\n right: -3px;\n width: 10px;\n }\n }\n}\n";
423
+
424
+ const classBase$1 = "EditForm";
425
+ registerRules();
426
+ const EditForm = ({
427
+ className,
428
+ dataSource,
429
+ formFieldDescriptors,
430
+ onSubmit: onSubmitProp,
431
+ ...htmlAttributes
432
+ }) => {
433
+ const targetWindow = window.useWindow();
434
+ styles.useComponentCssInjection({
435
+ testId: "vuu-edit-form",
436
+ css: editFormCss,
437
+ window: targetWindow
438
+ });
439
+ const {
440
+ editedFields,
441
+ editEntity,
442
+ errorMessages,
443
+ formFieldsContainerRef,
444
+ isClean,
445
+ ok,
446
+ onCancel,
447
+ onChange,
448
+ onCommit,
449
+ onFocus,
450
+ onSubmit
451
+ } = useEditForm({
452
+ dataSource,
453
+ formFieldDescriptors,
454
+ onSubmit: onSubmitProp
455
+ });
456
+ return /* @__PURE__ */ jsxRuntime.jsxs(
457
+ "div",
458
+ {
459
+ ...htmlAttributes,
460
+ className: cx(classBase$1, className),
461
+ onFocus,
462
+ children: [
463
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classBase$1}-form-fields`, ref: formFieldsContainerRef, children: formFieldDescriptors.map((dataDescriptor) => {
464
+ const { name, label = name } = dataDescriptor;
465
+ const errorMessage = errorMessages[name];
466
+ const isEdited = !isClean && editedFields.includes(name);
467
+ return /* @__PURE__ */ jsxRuntime.jsxs(
468
+ "div",
469
+ {
470
+ className: `${classBase$1}-field`,
471
+ "data-edited": isEdited,
472
+ children: [
473
+ /* @__PURE__ */ jsxRuntime.jsxs(core.FormField, { "data-field": name, children: [
474
+ /* @__PURE__ */ jsxRuntime.jsx(core.FormFieldLabel, { children: label }),
475
+ getDataItemEditControl({
476
+ InputProps: {
477
+ onChange,
478
+ value: editEntity?.[name]?.toString() ?? ""
479
+ },
480
+ dataDescriptor,
481
+ errorMessage,
482
+ onCommit
483
+ })
484
+ ] }),
485
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classBase$1}-edit-indicator` })
486
+ ]
487
+ },
488
+ name
489
+ );
490
+ }) }),
491
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${classBase$1}-buttons`, children: [
492
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { disabled: isClean, onClick: onCancel, children: "Cancel" }),
493
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: onSubmit, disabled: !ok || isClean, children: "Save" })
494
+ ] })
495
+ ]
496
+ }
497
+ );
498
+ };
499
+
500
+ const serverAPI = (schemas) => ({
501
+ getTableList: async () => {
502
+ if (schemas) {
503
+ return {
504
+ tables: Object.keys(schemas).map((key) => {
505
+ const [module, table] = key.split(":");
506
+ return { module, table };
507
+ })
508
+ };
509
+ } else {
510
+ console.log(`Rest data source does not yet support table list`);
511
+ return { tables: [] };
512
+ }
513
+ },
514
+ getTableSchema: async ({ module, table }) => {
515
+ const schema = schemas?.[`${module}:${table}`];
516
+ if (schema) {
517
+ return schema;
518
+ } else {
519
+ throw Error(
520
+ `Rest data source does not yet support table schema (${table})`
521
+ );
522
+ }
523
+ },
524
+ rpcCall: async (message) => Promise.reject(
525
+ Error(`Rest data source does not yet support RPC (${message.type})`)
526
+ )
527
+ });
528
+ const getServerAPI$1 = (schemas) => async () => serverAPI(schemas);
529
+ const isRestDataSourceExtension = (o) => {
530
+ return vuuUtils.isObject(o) && "createHttpHeaders" in o && typeof o["createHttpHeaders"] === "function";
531
+ };
532
+ const getRestDataSourceClass = ({
533
+ createHttpHeaders
534
+ }) => {
535
+ if (createHttpHeaders) {
536
+ return class ExtendedClass extends vuuDataRemote.RestDataSource {
537
+ constructor(props) {
538
+ super(props);
539
+ }
540
+ get httpHeaders() {
541
+ return createHttpHeaders();
542
+ }
543
+ };
544
+ } else {
545
+ return vuuDataRemote.RestDataSource;
546
+ }
547
+ };
548
+ const RestDataSourceProvider = ({
549
+ children,
550
+ createHttpHeaders,
551
+ tableSchemas,
552
+ url
553
+ }) => {
554
+ vuuDataRemote.RestDataSource.api = url;
555
+ const restDataSourceClass = getRestDataSourceClass({ createHttpHeaders });
556
+ return /* @__PURE__ */ jsxRuntime.jsx(
557
+ vuuUtils.DataProvider,
558
+ {
559
+ VuuDataSource: restDataSourceClass,
560
+ dataSourceExtensions: { createHttpHeaders },
561
+ getServerAPI: getServerAPI$1(tableSchemas),
562
+ isLocalData: false,
563
+ tableSchemas,
564
+ children
565
+ }
566
+ );
567
+ };
568
+
569
+ const useAutoLoginToVuuServer = ({
570
+ authenticate = true,
571
+ autoConnect = true,
572
+ // autoLogin = true,
573
+ secure = true,
574
+ websocketUrl
575
+ } = {}) => {
576
+ const [errorMessage, setErrorMessage] = react.useState("");
577
+ react.useEffect(() => {
578
+ const connect = async () => {
579
+ try {
580
+ let token = "no-token";
581
+ if (authenticate) {
582
+ token = await vuuDataRemote.authenticate(
583
+ "steve",
584
+ "xyz",
585
+ "/api/authn"
586
+ );
587
+ }
588
+ const url = websocketUrl ?? `${secure ? "wss" : "ws"}://localhost/8090/websocket`;
589
+ vuuDataRemote.ConnectionManager.connect({
590
+ url,
591
+ token,
592
+ username: "steve"
593
+ });
594
+ } catch (e) {
595
+ if (e instanceof Error) {
596
+ console.error(e.message);
597
+ setErrorMessage(e.message);
598
+ }
599
+ }
600
+ };
601
+ if (autoConnect) {
602
+ connect();
603
+ }
604
+ return () => {
605
+ if (autoConnect) {
606
+ vuuDataRemote.ConnectionManager.disconnect();
607
+ }
608
+ };
609
+ }, [authenticate, autoConnect, secure, websocketUrl]);
610
+ if (errorMessage) {
611
+ return /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Unable to authenticate against Vuu Server A Vuu Server instance must be running to show this example." });
612
+ }
613
+ };
614
+
615
+ const getServerAPI = () => vuuDataRemote.ConnectionManager.serverAPI;
616
+ const VuuDataSourceProvider = ({
617
+ authenticate,
618
+ autoConnect = false,
619
+ autoLogin = false,
620
+ children,
621
+ websocketUrl
622
+ }) => {
623
+ useAutoLoginToVuuServer({
624
+ authenticate,
625
+ autoConnect,
626
+ autoLogin,
627
+ websocketUrl
628
+ });
629
+ return /* @__PURE__ */ jsxRuntime.jsx(
630
+ vuuUtils.DataProvider,
631
+ {
632
+ VuuDataSource: vuuDataRemote.VuuDataSource,
633
+ getServerAPI,
634
+ isLocalData: false,
635
+ children
636
+ }
637
+ );
638
+ };
639
+
640
+ const NO_VALUES = [];
641
+ const toListOption = (value) => ({
642
+ label: value,
643
+ value
644
+ });
645
+ const lookupValueMap = /* @__PURE__ */ new Map();
646
+ const loadLookupValues = ({
647
+ labelColumn,
648
+ table,
649
+ valueColumn
650
+ }) => {
651
+ const tableKey = `${table.module}:${table.table}`;
652
+ const lookupValues = lookupValueMap.get(tableKey);
653
+ if (lookupValues) {
654
+ return lookupValues;
655
+ } else {
656
+ const promise = new Promise((resolve) => {
657
+ const columns = [valueColumn, labelColumn];
658
+ const columnMap = vuuUtils.buildColumnMap(columns);
659
+ const dataSource = new vuuDataRemote.VuuDataSource({
660
+ bufferSize: 0,
661
+ table
662
+ });
663
+ dataSource.subscribe(
664
+ {
665
+ columns,
666
+ range: vuuUtils.Range(0, 100)
667
+ },
668
+ (message) => {
669
+ if (message.type === "viewport-update") {
670
+ if (message.rows) {
671
+ const listOptions = message.rows.map((row) => ({
672
+ value: row[columnMap[valueColumn]],
673
+ label: row[columnMap[labelColumn]]
674
+ }));
675
+ resolve(listOptions);
676
+ dataSource.unsubscribe();
677
+ }
678
+ }
679
+ }
680
+ );
681
+ });
682
+ lookupValueMap.set(tableKey, promise);
683
+ return promise;
684
+ }
685
+ };
686
+ const getLookupDetails = ({ name, type }) => {
687
+ if (vuuUtils.isTypeDescriptor(type) && vuuUtils.isLookupRenderer(type.renderer)) {
688
+ return type.renderer.lookup;
689
+ } else {
690
+ throw Error(
691
+ `useLookupValues column ${name} is not configured to use lookup values`
692
+ );
693
+ }
694
+ };
695
+ const useLookupValues = (column, initialValueProp) => {
696
+ const { type: columnType } = column;
697
+ const { getLookupValues } = vuuUtils.useShellContext();
698
+ const initialState = react.useMemo(() => {
699
+ if (vuuUtils.isTypeDescriptor(columnType) && vuuUtils.isValueListRenderer(columnType?.renderer)) {
700
+ const values2 = columnType.renderer.values.map(toListOption);
701
+ return {
702
+ initialValue: vuuUtils.getSelectedOption(values2, initialValueProp) ?? null,
703
+ values: values2
704
+ };
705
+ } else {
706
+ const lookupDetails = getLookupDetails(column);
707
+ const values2 = getLookupValues?.(lookupDetails.table) ?? NO_VALUES;
708
+ return {
709
+ initialValue: vuuUtils.getSelectedOption(values2, initialValueProp) ?? null,
710
+ values: values2
711
+ };
712
+ }
713
+ }, [column, columnType, getLookupValues, initialValueProp]);
714
+ const [{ initialValue, values }, setLookupState] = react.useState(initialState);
715
+ react.useMemo(() => {
716
+ if (values === NO_VALUES) {
717
+ const lookupDetails = getLookupDetails(column);
718
+ loadLookupValues(lookupDetails).then(
719
+ (values2) => setLookupState({
720
+ initialValue: vuuUtils.getSelectedOption(values2, initialValueProp) ?? null,
721
+ values: values2
722
+ })
723
+ );
724
+ }
725
+ }, [values, column, initialValueProp]);
726
+ return {
727
+ initialValue,
728
+ values
729
+ };
730
+ };
731
+
732
+ const NO_CONFIG = {};
733
+ const useSessionDataSource = ({
734
+ dataSourceSessionKey = "data-source",
735
+ tableSchema
736
+ }) => {
737
+ const { id, load, save, loadSession, saveSession, title } = vuuLayout.useViewContext();
738
+ const { VuuDataSource } = vuuUtils.useData();
739
+ const { "datasource-config": dataSourceConfigFromState } = react.useMemo(() => load?.() ?? NO_CONFIG, [load]);
740
+ const dataSourceConfigRef = react.useRef(
741
+ dataSourceConfigFromState
742
+ );
743
+ const handleDataSourceConfigChange = react.useCallback(
744
+ (config, _range, confirmed) => {
745
+ if (confirmed !== false) {
746
+ const { noChanges } = vuuUtils.isConfigChanged(
747
+ dataSourceConfigRef.current,
748
+ config
749
+ );
750
+ if (noChanges === false) {
751
+ dataSourceConfigRef.current = config;
752
+ save?.(config, "datasource-config");
753
+ }
754
+ }
755
+ },
756
+ [save]
757
+ );
758
+ const dataSource = react.useMemo(() => {
759
+ let ds = loadSession?.(dataSourceSessionKey);
760
+ if (ds) {
761
+ if (dataSourceConfigFromState) {
762
+ ds.applyConfig(dataSourceConfigFromState, true);
763
+ }
764
+ if (ds.range.from > 0) {
765
+ ds.range = ds.range.reset;
766
+ }
767
+ return ds;
768
+ }
769
+ const columns = dataSourceConfigFromState?.columns ?? tableSchema.columns.map((col) => col.name);
770
+ ds = new VuuDataSource({
771
+ // bufferSize: 0,
772
+ viewport: id,
773
+ table: tableSchema.table,
774
+ ...dataSourceConfigRef.current,
775
+ columns,
776
+ title
777
+ });
778
+ ds.on("config", handleDataSourceConfigChange);
779
+ saveSession?.(ds, "data-source");
780
+ return ds;
781
+ }, [
782
+ VuuDataSource,
783
+ dataSourceConfigFromState,
784
+ dataSourceSessionKey,
785
+ handleDataSourceConfigChange,
786
+ id,
787
+ loadSession,
788
+ saveSession,
789
+ tableSchema.columns,
790
+ tableSchema.table,
791
+ title
792
+ ]);
793
+ return dataSource;
794
+ };
795
+
796
+ var sessionEditingFormCss = ".vuuSessionEditingForm {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 400px;\n}\n\n.vuuSessionEditingForm-content {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n gap: 3px;\n overflow: auto;\n padding: var(--salt-spacing-200);\n}\n\n\n.vuuSessionEditingForm-fieldValue.vuuReadOnly {\n font-weight: var(--salt-text-label-fontWeight-strong);\n}\n\n.vuuSessionEditingForm-buttonbar {\n align-items: center;\n border-top: solid 1px var(--salt-container-primary-borderColor);\n box-sizing: content-box;\n display: flex;\n justify-content: flex-end;\n flex: 0 0 autox;\n gap: 6px;\n height: var(--salt-size-base);\n margin: var(--salt-spacing-200);\n padding-top: var(--salt-spacing-200);\n\n}\n\n.vuuSessionEditingForm-errorBanner {\n --vuu-icon-left: 3px;\n --vuu-icon-size: 18px;\n --vuu-icon-top: 3px;\n border: solid 1px var(--salt-status-error-borderColor);\n line-height: 24px;\n padding: 0 6px 0 26px;\n position: relative;\n}";
797
+
798
+ const classBase = "vuuSessionEditingForm";
799
+ const getField = (fields, name) => {
800
+ const field = fields.find((f) => f.name === name);
801
+ if (field) {
802
+ return field;
803
+ } else {
804
+ throw Error(`SessionEditingForm, no field '${name}' found`);
805
+ }
806
+ };
807
+ const getFieldNameAndValue = ({
808
+ target
809
+ }) => {
810
+ const formField = vuuUtils.queryClosest(target, ".saltFormField");
811
+ if (formField) {
812
+ const {
813
+ dataset: { field }
814
+ } = formField;
815
+ if (field === void 0) {
816
+ throw Error("SessionEditingForm, form field has no field data attribute");
817
+ }
818
+ return [field, target.value];
819
+ } else {
820
+ throw Error("Form control is not enclosed in FormField");
821
+ }
822
+ };
823
+ const Status = {
824
+ uninitialised: 0,
825
+ unchanged: 1,
826
+ changed: 2,
827
+ invalid: 3
828
+ };
829
+ const getDataSource = (dataSource, schema) => {
830
+ if (dataSource) {
831
+ return dataSource;
832
+ } else if (schema) {
833
+ return new vuuDataRemote.VuuDataSource({
834
+ bufferSize: 0,
835
+ table: schema.table,
836
+ columns: schema.columns.map((col) => col.name)
837
+ });
838
+ } else {
839
+ throw Error(
840
+ "SessionEditingForm: either a DataSource or a TableSchema must be provided"
841
+ );
842
+ }
843
+ };
844
+ const SessionEditingForm = ({
845
+ className,
846
+ config: { fields, key: keyField },
847
+ dataSource: dataSourceProp,
848
+ id: idProp,
849
+ onClose,
850
+ schema,
851
+ ...htmlAttributes
852
+ }) => {
853
+ const targetWindow = window.useWindow();
854
+ styles.useComponentCssInjection({
855
+ testId: "vuu-session-editing-form",
856
+ css: sessionEditingFormCss,
857
+ window: targetWindow
858
+ });
859
+ const [fieldStatusValues, setFieldStatusValues] = react.useState({});
860
+ const [values, setValues] = react.useState();
861
+ const [errorMessage, setErrorMessage] = react.useState("");
862
+ const formContentRef = react.useRef(null);
863
+ const initialDataRef = react.useRef(void 0);
864
+ const dataStatusRef = react.useRef(Status.uninitialised);
865
+ const dataSource = react.useMemo(() => {
866
+ const ds = getDataSource(dataSourceProp, schema);
867
+ const { columns } = ds;
868
+ const columnMap = vuuUtils.buildColumnMap(ds.columns);
869
+ const applyServerData = (data) => {
870
+ if (columnMap) {
871
+ const values2 = {};
872
+ for (const column of columns) {
873
+ values2[column] = data[columnMap[column]];
874
+ }
875
+ if (dataStatusRef.current === Status.uninitialised) {
876
+ dataStatusRef.current = Status.unchanged;
877
+ initialDataRef.current = values2;
878
+ }
879
+ setValues(values2);
880
+ }
881
+ };
882
+ ds.subscribe({ range: vuuUtils.Range(0, 5) }, (message) => {
883
+ if (message.type === "viewport-update" && message.rows) {
884
+ if (dataStatusRef.current === Status.uninitialised) {
885
+ applyServerData(message.rows[0]);
886
+ } else {
887
+ console.log("what do we do with server updates");
888
+ }
889
+ }
890
+ });
891
+ return ds;
892
+ }, [dataSourceProp, schema]);
893
+ const id = core.useIdMemo(idProp);
894
+ const handleChange = react.useCallback(
895
+ (evt) => {
896
+ const [field, value] = getFieldNameAndValue(evt);
897
+ const { type } = getField(fields, field);
898
+ const typedValue = vuuUtils.getTypedValue(value, type);
899
+ setValues((values2 = {}) => {
900
+ const newValues = {
901
+ ...values2,
902
+ [field]: typedValue
903
+ };
904
+ const notUpdated = vuuUtils.shallowEquals(newValues, initialDataRef.current);
905
+ dataStatusRef.current = notUpdated ? Status.unchanged : typedValue !== void 0 ? Status.changed : Status.invalid;
906
+ return newValues;
907
+ });
908
+ },
909
+ [fields]
910
+ );
911
+ const handleBlur = react.useCallback(
912
+ (evt) => {
913
+ const [field, value] = getFieldNameAndValue(evt);
914
+ const rowKey = values?.[keyField];
915
+ const { type } = getField(fields, field);
916
+ const typedValue = vuuUtils.getTypedValue(value, type, true);
917
+ if (typeof rowKey === "string") {
918
+ dataSource.menuRpcCall(vuuUtils.vuuEditCellRequest(rowKey, field, typedValue)).then((response) => {
919
+ if (vuuUtils.isErrorResponse(response)) {
920
+ console.log(`edit rejected ${response.error}`);
921
+ setFieldStatusValues((map) => ({
922
+ ...map,
923
+ [field]: response.error
924
+ }));
925
+ } else {
926
+ setFieldStatusValues((map) => ({
927
+ ...map,
928
+ [field]: void 0
929
+ }));
930
+ }
931
+ });
932
+ }
933
+ },
934
+ [dataSource, fields, keyField, values]
935
+ );
936
+ const applyAction = react.useCallback(
937
+ (action) => {
938
+ if (action.type === "CLOSE_DIALOG_ACTION") {
939
+ onClose?.();
940
+ }
941
+ },
942
+ [onClose]
943
+ );
944
+ const handleSubmit = react.useCallback(async () => {
945
+ const rpcResponse = await dataSource.menuRpcCall({
946
+ type: "VP_EDIT_SUBMIT_FORM_RPC"
947
+ });
948
+ if (vuuUtils.isErrorResponse(rpcResponse)) {
949
+ setErrorMessage(rpcResponse.error);
950
+ } else if (vuuUtils.isActionMessage(rpcResponse)) {
951
+ applyAction(rpcResponse.action);
952
+ }
953
+ }, [applyAction, dataSource]);
954
+ const handleKeyDown = react.useCallback(
955
+ (evt) => {
956
+ if (evt.key === "Enter" && dataStatusRef.current === Status.changed) {
957
+ handleSubmit();
958
+ }
959
+ },
960
+ [handleSubmit]
961
+ );
962
+ const handleCancel = react.useCallback(() => {
963
+ onClose?.();
964
+ }, [onClose]);
965
+ const getFormControl = (field) => {
966
+ const value = String(values?.[field.name] ?? "");
967
+ if (field.readonly || field.name === keyField) {
968
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `${classBase}-fieldValue vuuReadOnly`, children: value });
969
+ } else {
970
+ return /* @__PURE__ */ jsxRuntime.jsx(
971
+ core.Input,
972
+ {
973
+ className: `${classBase}-fieldValue`,
974
+ onBlur: handleBlur,
975
+ onChange: handleChange,
976
+ value,
977
+ id: `${id}-input-${field.name}`
978
+ }
979
+ );
980
+ }
981
+ };
982
+ react.useEffect(() => {
983
+ if (formContentRef.current) {
984
+ const firstInput = formContentRef.current.querySelector(
985
+ "input"
986
+ );
987
+ if (firstInput) {
988
+ setTimeout(() => {
989
+ firstInput.focus();
990
+ firstInput.select();
991
+ }, 100);
992
+ }
993
+ }
994
+ }, []);
995
+ react.useEffect(() => {
996
+ return () => {
997
+ if (dataSource) {
998
+ dataSource.unsubscribe();
999
+ }
1000
+ };
1001
+ }, [dataSource]);
1002
+ const isDirty = dataStatusRef.current === Status.changed;
1003
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...htmlAttributes, className: cx(classBase, className), children: [
1004
+ errorMessage ? /* @__PURE__ */ jsxRuntime.jsx(
1005
+ "div",
1006
+ {
1007
+ className: `${classBase}-errorBanner`,
1008
+ "data-icon": "error",
1009
+ title: errorMessage,
1010
+ children: "Error, edit(s) not saved"
1011
+ }
1012
+ ) : void 0,
1013
+ /* @__PURE__ */ jsxRuntime.jsx(
1014
+ "div",
1015
+ {
1016
+ className: `${classBase}-content`,
1017
+ ref: formContentRef,
1018
+ onKeyDown: handleKeyDown,
1019
+ children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsxs(
1020
+ core.FormField,
1021
+ {
1022
+ className: `${classBase}-field`,
1023
+ "data-field": field.name,
1024
+ necessity: field.required ? "required" : "optional",
1025
+ readOnly: field.readonly,
1026
+ validationStatus: fieldStatusValues[field.name] ? "error" : void 0,
1027
+ children: [
1028
+ /* @__PURE__ */ jsxRuntime.jsx(core.FormFieldLabel, { children: field?.label ?? field.description }),
1029
+ getFormControl(field),
1030
+ /* @__PURE__ */ jsxRuntime.jsx(core.FormFieldHelperText, { children: fieldStatusValues[field.name] ?? "" })
1031
+ ]
1032
+ },
1033
+ field.name
1034
+ ))
1035
+ }
1036
+ ),
1037
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${classBase}-buttonbar salt-theme salt-density-high`, children: [
1038
+ /* @__PURE__ */ jsxRuntime.jsx(
1039
+ core.Button,
1040
+ {
1041
+ type: "submit",
1042
+ variant: "cta",
1043
+ disabled: !isDirty,
1044
+ onClick: handleSubmit,
1045
+ children: "Submit"
1046
+ }
1047
+ ),
1048
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { variant: "secondary", onClick: handleCancel, children: "Cancel" })
1049
+ ] })
1050
+ ] });
1051
+ };
1052
+
1053
+ const isRoot = (menu) => menu.name === "ROOT";
1054
+ const isCellMenu = (options) => options.context === "cell";
1055
+ const isRowMenu = (options) => options.context === "row";
1056
+ const isSelectionMenu = (options) => options.context === "selected-rows";
1057
+ const getColumnsFromOptions = (options) => {
1058
+ if (options && typeof options === "object" && "columns" in options) {
1059
+ return options.columns;
1060
+ }
1061
+ };
1062
+ const isVuuMenuItem = (menu) => "rpcName" in menu;
1063
+ const isGroupMenuItem = (menu) => "menus" in menu;
1064
+ const hasFilter = ({ filter }) => typeof filter === "string" && filter.length > 0;
1065
+ const { KEY } = vuuUtils.metadataKeys;
1066
+ const getMenuItemOptions = (menu, options) => {
1067
+ switch (menu.context) {
1068
+ case "cell":
1069
+ return {
1070
+ ...menu,
1071
+ field: options.column.name,
1072
+ rowKey: options.row[KEY],
1073
+ value: options.row[options.columnMap[options.column.name]]
1074
+ };
1075
+ case "row":
1076
+ return {
1077
+ ...menu,
1078
+ columns: options.columns,
1079
+ row: vuuUtils.dataSourceRowToDataRowDto(options.row, options.columnMap),
1080
+ rowKey: options.row[KEY]
1081
+ };
1082
+ case "selected-rows":
1083
+ return {
1084
+ ...menu,
1085
+ columns: options.columns
1086
+ };
1087
+ default:
1088
+ return menu;
1089
+ }
1090
+ };
1091
+ const vuuContextCompatibleWithTableLocation = (uiLocation, vuuContext, selectedRowCount = 0) => {
1092
+ switch (uiLocation) {
1093
+ case "grid":
1094
+ if (vuuContext === "selected-rows") {
1095
+ return selectedRowCount > 0;
1096
+ } else {
1097
+ return true;
1098
+ }
1099
+ case "header":
1100
+ return vuuContext === "grid";
1101
+ default:
1102
+ return false;
1103
+ }
1104
+ };
1105
+ const gridRowMeetsFilterCriteria = (context, row, selectedRows, filter, columnMap) => {
1106
+ if (context === "cell" || context === "row") {
1107
+ const filterPredicate = vuuFilterParser.getFilterPredicate(columnMap, filter);
1108
+ return filterPredicate(row);
1109
+ } else if (context === "selected-rows") {
1110
+ if (selectedRows.length === 0) {
1111
+ return false;
1112
+ } else {
1113
+ const filterPredicate = vuuFilterParser.getFilterPredicate(columnMap, filter);
1114
+ return selectedRows.every(filterPredicate);
1115
+ }
1116
+ }
1117
+ return true;
1118
+ };
1119
+ const menuShouldBeRenderedInThisContext = (menuItem, tableLocation, options) => {
1120
+ if (isGroupMenuItem(menuItem)) {
1121
+ return menuItem.menus.some(
1122
+ (childMenu) => menuShouldBeRenderedInThisContext(childMenu, tableLocation, options)
1123
+ );
1124
+ }
1125
+ if (!vuuContextCompatibleWithTableLocation(
1126
+ tableLocation,
1127
+ menuItem.context,
1128
+ options.selectedRows?.length
1129
+ )) {
1130
+ return false;
1131
+ }
1132
+ if (tableLocation === "grid" && hasFilter(menuItem)) {
1133
+ return gridRowMeetsFilterCriteria(
1134
+ menuItem.context,
1135
+ options.row,
1136
+ options.selectedRows,
1137
+ menuItem.filter,
1138
+ options.columnMap
1139
+ );
1140
+ }
1141
+ if (isCellMenu(menuItem) && menuItem.field !== "*") {
1142
+ return menuItem.field === options.column.name;
1143
+ }
1144
+ return true;
1145
+ };
1146
+ const buildMenuDescriptorFromVuuMenu = (menu, tableLocation, options) => {
1147
+ if (menuShouldBeRenderedInThisContext(menu, tableLocation, options)) {
1148
+ if (isVuuMenuItem(menu)) {
1149
+ return {
1150
+ label: menu.name,
1151
+ id: "MENU_RPC_CALL",
1152
+ options: getMenuItemOptions(menu, options)
1153
+ };
1154
+ } else {
1155
+ const children = menu.menus.map(
1156
+ (childMenu) => buildMenuDescriptorFromVuuMenu(childMenu, tableLocation, options)
1157
+ ).filter(
1158
+ (childMenu) => childMenu !== void 0
1159
+ );
1160
+ if (children.length > 0) {
1161
+ return {
1162
+ label: menu.name,
1163
+ children
1164
+ };
1165
+ }
1166
+ }
1167
+ }
1168
+ };
1169
+ const keyFirst = (c1, c2) => c1.isKeyField ? -1 : c2.isKeyField ? 1 : 0;
1170
+ const defaultFormConfig = {
1171
+ fields: [],
1172
+ key: "",
1173
+ title: ""
1174
+ };
1175
+ const configFromSchema = (schema) => {
1176
+ if (schema) {
1177
+ const { columns, key } = schema;
1178
+ return {
1179
+ key,
1180
+ title: `Parameters for command`,
1181
+ fields: columns.map((col) => ({
1182
+ description: col.name,
1183
+ label: col.name,
1184
+ name: col.name,
1185
+ type: col.serverDataType,
1186
+ isKeyField: col.name === key
1187
+ })).sort(keyFirst)
1188
+ };
1189
+ }
1190
+ };
1191
+ const getFormConfig = (action) => {
1192
+ const { tableSchema: schema } = action;
1193
+ const config = configFromSchema(schema) ?? defaultFormConfig;
1194
+ return {
1195
+ config,
1196
+ schema
1197
+ };
1198
+ };
1199
+ const useVuuMenuActions = ({
1200
+ clientSideMenuActionHandler,
1201
+ dataSource,
1202
+ onRpcResponse
1203
+ }) => {
1204
+ const { VuuDataSource } = vuuUtils.useData();
1205
+ const menuBuilder = react.useCallback(
1206
+ (location, options) => {
1207
+ const descriptors = [];
1208
+ if (dataSource) {
1209
+ const { links, menu } = dataSource;
1210
+ const { visualLink } = dataSource;
1211
+ if (location === "grid" && links && !visualLink) {
1212
+ links.forEach((linkDescriptor) => {
1213
+ const { link, label: linkLabel } = linkDescriptor;
1214
+ const label = linkLabel ? linkLabel : link.toTable;
1215
+ descriptors.push({
1216
+ label: `Link to ${label}`,
1217
+ id: "link-table",
1218
+ options: linkDescriptor
1219
+ });
1220
+ });
1221
+ }
1222
+ if (menu && vuuTable.isTableLocation(location)) {
1223
+ const menuDescriptor = buildMenuDescriptorFromVuuMenu(
1224
+ menu,
1225
+ location,
1226
+ options
1227
+ );
1228
+ if (isRoot(menu) && vuuContextMenu.isGroupMenuItemDescriptor(menuDescriptor)) {
1229
+ descriptors.push(...menuDescriptor.children);
1230
+ } else if (menuDescriptor) {
1231
+ descriptors.push(menuDescriptor);
1232
+ }
1233
+ }
1234
+ } else {
1235
+ throw Error("useVuuMenuActions no dataSource provided");
1236
+ }
1237
+ return descriptors;
1238
+ },
1239
+ [dataSource]
1240
+ );
1241
+ const { showDialog, closeDialog } = vuuPopups.useDialogContext();
1242
+ const showNotification = vuuPopups.useNotifications();
1243
+ const showBulkEditDialog = react.useCallback(
1244
+ (ds, table, columns) => {
1245
+ const sessionDs = new VuuDataSource({
1246
+ columns: columns?.map(vuuUtils.toColumnName),
1247
+ table,
1248
+ viewport: table.table
1249
+ });
1250
+ const handleClose = () => {
1251
+ sessionDs.unsubscribe();
1252
+ closeDialog();
1253
+ };
1254
+ showDialog(
1255
+ /* @__PURE__ */ jsxRuntime.jsx(
1256
+ vuuTable.BulkEditDialog,
1257
+ {
1258
+ columns,
1259
+ sessionDs,
1260
+ parentDs: ds,
1261
+ closeDialog: handleClose
1262
+ }
1263
+ ),
1264
+ "Bulk Amend"
1265
+ );
1266
+ return true;
1267
+ },
1268
+ [VuuDataSource, closeDialog, showDialog]
1269
+ );
1270
+ const showSessionEditingForm = react.useCallback(
1271
+ (ds, action) => {
1272
+ const { tableSchema } = action;
1273
+ if (tableSchema) {
1274
+ const formConfig = getFormConfig(action);
1275
+ showDialog(
1276
+ /* @__PURE__ */ jsxRuntime.jsx(SessionEditingForm, { ...formConfig, onClose: closeDialog }),
1277
+ "Set Parameters"
1278
+ );
1279
+ }
1280
+ const sessionDs = ds.createSessionDataSource?.(action.table);
1281
+ const handleSubmit = () => {
1282
+ sessionDs?.rpcCall?.(vuuUtils.viewportRpcRequest("VP_BULK_EDIT_SUBMIT_RPC"));
1283
+ closeDialog();
1284
+ };
1285
+ const handleChange = (isValid) => {
1286
+ console.log("placeholder: ", isValid);
1287
+ };
1288
+ if (sessionDs) {
1289
+ showDialog(
1290
+ /* @__PURE__ */ jsxRuntime.jsx(
1291
+ vuuTable.BulkEditPanel,
1292
+ {
1293
+ dataSource: sessionDs,
1294
+ onSubmit: handleSubmit,
1295
+ parentDs: ds,
1296
+ onValidationStatusChange: handleChange
1297
+ }
1298
+ ),
1299
+ "Multi Row Edit",
1300
+ [
1301
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: closeDialog, children: "Cancel" }, "cancel"),
1302
+ /* @__PURE__ */ jsxRuntime.jsx(core.Button, { onClick: handleSubmit, children: "Save" }, "submit")
1303
+ ]
1304
+ );
1305
+ }
1306
+ },
1307
+ [closeDialog, showDialog]
1308
+ );
1309
+ const getMenuRpcRequest = (options) => {
1310
+ const { rpcName } = options;
1311
+ if (isCellMenu(options)) {
1312
+ return {
1313
+ field: options.field,
1314
+ rowKey: options.rowKey,
1315
+ rpcName,
1316
+ value: options.value,
1317
+ type: "VIEW_PORT_MENU_CELL_RPC"
1318
+ };
1319
+ } else if (isRowMenu(options)) {
1320
+ return {
1321
+ rowKey: options.rowKey,
1322
+ row: options.row,
1323
+ rpcName,
1324
+ type: "VIEW_PORT_MENU_ROW_RPC"
1325
+ };
1326
+ } else if (isSelectionMenu(options)) {
1327
+ return {
1328
+ rpcName,
1329
+ type: "VIEW_PORT_MENUS_SELECT_RPC"
1330
+ };
1331
+ } else {
1332
+ return {
1333
+ rpcName,
1334
+ type: "VIEW_PORT_MENU_TABLE_RPC"
1335
+ };
1336
+ }
1337
+ };
1338
+ const menuActionHandler = react.useCallback(
1339
+ (menuItemId, options) => {
1340
+ if (clientSideMenuActionHandler?.(menuItemId, options)) {
1341
+ return true;
1342
+ } else if (menuItemId === "MENU_RPC_CALL") {
1343
+ const rpcRequest = getMenuRpcRequest(options);
1344
+ dataSource?.menuRpcCall(rpcRequest).then((rpcResponse) => {
1345
+ if (rpcResponse) {
1346
+ if (onRpcResponse?.(rpcResponse) === true) {
1347
+ return true;
1348
+ }
1349
+ if (vuuUtils.isActionMessage(rpcResponse)) {
1350
+ if (vuuUtils.hasShowNotificationAction(rpcResponse)) {
1351
+ const {
1352
+ action: { message, title = "Success" }
1353
+ } = rpcResponse;
1354
+ showNotification({
1355
+ type: "success",
1356
+ body: message,
1357
+ header: title
1358
+ });
1359
+ } else if (vuuUtils.isOpenBulkEditResponse(rpcResponse)) {
1360
+ showBulkEditDialog(
1361
+ dataSource,
1362
+ rpcResponse.action.table,
1363
+ getColumnsFromOptions(options)
1364
+ );
1365
+ } else if (vuuUtils.isSessionTableActionMessage(rpcResponse)) {
1366
+ showSessionEditingForm(dataSource, rpcResponse.action);
1367
+ }
1368
+ }
1369
+ }
1370
+ });
1371
+ return true;
1372
+ } else if (menuItemId === "link-table") {
1373
+ if (dataSource) {
1374
+ dataSource.visualLink = options;
1375
+ }
1376
+ return true;
1377
+ } else {
1378
+ console.log(
1379
+ `useViewServer handleMenuAction, can't handle action type ${menuItemId}`
1380
+ );
1381
+ }
1382
+ return false;
1383
+ },
1384
+ [
1385
+ clientSideMenuActionHandler,
1386
+ dataSource,
1387
+ onRpcResponse,
1388
+ showBulkEditDialog,
1389
+ showNotification,
1390
+ showSessionEditingForm
1391
+ ]
1392
+ );
1393
+ return {
1394
+ menuBuilder,
1395
+ menuActionHandler
1396
+ };
1397
+ };
1398
+
1399
+ const useVuuTables = () => {
1400
+ const [tableSchemas, setTableSchemas] = react.useState();
1401
+ const { getServerAPI } = vuuUtils.useData();
1402
+ const buildTables = react.useCallback((schemas) => {
1403
+ const vuuTables = /* @__PURE__ */ new Map();
1404
+ schemas.forEach((schema) => {
1405
+ const { module, table } = schema.table;
1406
+ vuuTables.set(`${module}:${table}`, schema);
1407
+ });
1408
+ return vuuTables;
1409
+ }, []);
1410
+ react.useEffect(() => {
1411
+ async function fetchTableMetadata() {
1412
+ try {
1413
+ const server = await getServerAPI();
1414
+ const { tables } = await server.getTableList();
1415
+ const tableSchemas2 = await Promise.all(
1416
+ tables.map((vuuTable) => server.getTableSchema(vuuTable))
1417
+ );
1418
+ setTableSchemas(tableSchemas2);
1419
+ } catch (err) {
1420
+ console.warn(
1421
+ `useVuuTables: error fetching table metadata ${String(err)}`
1422
+ );
1423
+ }
1424
+ }
1425
+ fetchTableMetadata();
1426
+ }, [buildTables, getServerAPI]);
1427
+ return tableSchemas;
1428
+ };
1429
+
1430
+ const useVisualLinks = (dataSource) => {
1431
+ const { dispatch } = vuuLayout.useViewContext();
1432
+ const clearVisualLinkTarget = react.useCallback(() => {
1433
+ if (dataSource.visualLink) {
1434
+ dispatch?.({
1435
+ type: "broadcast-message",
1436
+ message: {
1437
+ targetId: dataSource.visualLink.parentClientVpId,
1438
+ type: "highlight-off"
1439
+ }
1440
+ });
1441
+ }
1442
+ }, [dataSource, dispatch]);
1443
+ const removeVisualLink = react.useCallback(() => {
1444
+ if (dataSource.visualLink) {
1445
+ dispatch?.({
1446
+ type: "broadcast-message",
1447
+ message: {
1448
+ targetId: dataSource.visualLink.parentClientVpId,
1449
+ type: "highlight-off"
1450
+ }
1451
+ });
1452
+ dataSource.visualLink = void 0;
1453
+ }
1454
+ }, [dataSource, dispatch]);
1455
+ const handleLinkRemoved = react.useCallback(() => {
1456
+ dispatch?.({
1457
+ type: "remove-toolbar-contribution",
1458
+ location: "post-title"
1459
+ });
1460
+ }, [dispatch]);
1461
+ const highlightVisualLinkTarget = react.useCallback(() => {
1462
+ if (dataSource.visualLink) {
1463
+ dispatch?.({
1464
+ type: "broadcast-message",
1465
+ message: {
1466
+ targetId: dataSource.visualLink.parentClientVpId,
1467
+ type: "highlight-on"
1468
+ }
1469
+ });
1470
+ }
1471
+ }, [dataSource, dispatch]);
1472
+ const handleLinkCreated = react.useCallback(() => {
1473
+ dispatch?.({
1474
+ type: "add-toolbar-contribution",
1475
+ location: "post-title",
1476
+ content: /* @__PURE__ */ jsxRuntime.jsx(
1477
+ vuuUiControls.IconButton,
1478
+ {
1479
+ "aria-label": "remove-link",
1480
+ icon: "link",
1481
+ onClick: removeVisualLink,
1482
+ onMouseEnter: highlightVisualLinkTarget,
1483
+ onMouseLeave: clearVisualLinkTarget,
1484
+ variant: "secondary"
1485
+ }
1486
+ )
1487
+ });
1488
+ }, [
1489
+ dispatch,
1490
+ removeVisualLink,
1491
+ highlightVisualLinkTarget,
1492
+ clearVisualLinkTarget
1493
+ ]);
1494
+ react.useEffect(() => {
1495
+ dataSource.on("visual-link-created", handleLinkCreated);
1496
+ dataSource.on("visual-link-removed", handleLinkRemoved);
1497
+ return () => {
1498
+ dataSource.removeListener("visual-link-created", handleLinkCreated);
1499
+ dataSource.removeListener("visual-link-removed", handleLinkRemoved);
1500
+ };
1501
+ }, [dataSource, handleLinkCreated, handleLinkRemoved]);
1502
+ };
1503
+
1504
+ const getTypeaheadParams = (table, column, text = "", selectedValues = []) => {
1505
+ if (text !== "" && !selectedValues.includes(text.toLowerCase())) {
1506
+ return [table, column, text];
1507
+ }
1508
+ return [table, column];
1509
+ };
1510
+ const useTypeaheadSuggestions = () => {
1511
+ const { getServerAPI } = vuuUtils.useData();
1512
+ return react.useCallback(
1513
+ async (params) => {
1514
+ const rpcMessage = params.length === 2 ? {
1515
+ type: "RPC_CALL",
1516
+ service: "TypeAheadRpcHandler",
1517
+ method: "getUniqueFieldValues",
1518
+ params
1519
+ } : {
1520
+ type: "RPC_CALL",
1521
+ service: "TypeAheadRpcHandler",
1522
+ method: "getUniqueFieldValuesStartingWith",
1523
+ params
1524
+ };
1525
+ try {
1526
+ const serverAPI = await getServerAPI();
1527
+ const response = await serverAPI.rpcCall(rpcMessage);
1528
+ return response;
1529
+ } catch (err) {
1530
+ return false;
1531
+ }
1532
+ },
1533
+ [getServerAPI]
1534
+ );
1535
+ };
1536
+
1537
+ exports.CLEAN_FORM = CLEAN_FORM;
1538
+ exports.EditForm = EditForm;
1539
+ exports.OK = OK;
1540
+ exports.RestDataSourceProvider = RestDataSourceProvider;
1541
+ exports.SessionEditingForm = SessionEditingForm;
1542
+ exports.UnsavedChangesReport = UnsavedChangesReport;
1543
+ exports.VuuDataSourceProvider = VuuDataSourceProvider;
1544
+ exports.buildFormEditState = buildFormEditState;
1545
+ exports.buildValidationChecker = buildValidationChecker;
1546
+ exports.getDataItemEditControl = getDataItemEditControl;
1547
+ exports.getEditValidationRules = getEditValidationRules;
1548
+ exports.getTypeaheadParams = getTypeaheadParams;
1549
+ exports.isRestDataSourceExtension = isRestDataSourceExtension;
1550
+ exports.isRowMenu = isRowMenu;
1551
+ exports.isSelectionMenu = isSelectionMenu;
1552
+ exports.useEditForm = useEditForm;
1553
+ exports.useLookupValues = useLookupValues;
1554
+ exports.useSessionDataSource = useSessionDataSource;
1555
+ exports.useTypeaheadSuggestions = useTypeaheadSuggestions;
1556
+ exports.useVisualLinks = useVisualLinks;
1557
+ exports.useVuuMenuActions = useVuuMenuActions;
1558
+ exports.useVuuTables = useVuuTables;
43
1559
  //# sourceMappingURL=index.js.map