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