@evoke-platform/ui-components 1.10.0-testing.10 → 1.10.0-testing.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +3 -1
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +82 -13
- package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Footer.js +3 -3
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +30 -13
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +1 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +16 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +70 -18
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +37 -15
- package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Header.js +42 -4
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +31 -6
- package/dist/published/components/custom/FormV2/components/utils.js +2 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +449 -1
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
- package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
- package/dist/published/theme/hooks.d.ts +4 -3
- package/package.json +1 -1
|
@@ -11,7 +11,7 @@ import { encodePageSlug, getDefaultPages, getPrefixedUrl, transformToWhere } fro
|
|
|
11
11
|
import RelatedObjectInstance from './RelatedObjectInstance';
|
|
12
12
|
const ObjectPropertyInput = (props) => {
|
|
13
13
|
const { id, fieldDefinition, readOnly, error, mode, displayOption, filter, defaultValueCriteria, sortBy, orderBy, isModal, initialValue, viewLayout, hasDescription, createActionId, formId, } = props;
|
|
14
|
-
const { fetchedOptions, setFetchedOptions, parameters, fieldHeight, handleChange: handleChangeObjectField, instance, } = useFormContext();
|
|
14
|
+
const { fetchedOptions, setFetchedOptions, parameters, fieldHeight, handleChange: handleChangeObjectField, onAutosave: onAutosave, instance, } = useFormContext();
|
|
15
15
|
const { defaultPages, findDefaultPageSlugFor } = useApp();
|
|
16
16
|
const [selectedInstance, setSelectedInstance] = useState(initialValue || undefined);
|
|
17
17
|
const [openCreateDialog, setOpenCreateDialog] = useState(false);
|
|
@@ -90,14 +90,27 @@ const ObjectPropertyInput = (props) => {
|
|
|
90
90
|
});
|
|
91
91
|
if (updatedFilter.where) {
|
|
92
92
|
setLoadingOptions(true);
|
|
93
|
-
apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`), (error, instances) => {
|
|
93
|
+
apiServices.get(getPrefixedUrl(`/objects/${fieldDefinition.objectId}/instances?filter=${encodeURIComponent(JSON.stringify(updatedFilter))}`), async (error, instances) => {
|
|
94
94
|
if (error) {
|
|
95
95
|
console.error(error);
|
|
96
96
|
setLoadingOptions(false);
|
|
97
97
|
}
|
|
98
98
|
if (instances && instances.length > 0) {
|
|
99
99
|
setSelectedInstance(instances[0]);
|
|
100
|
-
|
|
100
|
+
try {
|
|
101
|
+
await handleChangeObjectField(id, instances[0]);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.error('Failed to update field:', error);
|
|
105
|
+
setLoadingOptions(false);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await onAutosave?.(id);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
console.error('Autosave failed:', error);
|
|
113
|
+
}
|
|
101
114
|
}
|
|
102
115
|
setLoadingOptions(false);
|
|
103
116
|
});
|
|
@@ -131,7 +144,15 @@ const ObjectPropertyInput = (props) => {
|
|
|
131
144
|
}
|
|
132
145
|
});
|
|
133
146
|
}
|
|
134
|
-
}, [
|
|
147
|
+
}, [
|
|
148
|
+
fieldDefinition,
|
|
149
|
+
updatedCriteria,
|
|
150
|
+
layout,
|
|
151
|
+
fetchedOptions?.[`${id}Options`],
|
|
152
|
+
fetchedOptions?.[`${id}UpdatedCriteria`],
|
|
153
|
+
hasFetched,
|
|
154
|
+
id,
|
|
155
|
+
]);
|
|
135
156
|
const debouncedGetDropdownOptions = useCallback(debounce(getDropdownOptions, 200), [getDropdownOptions]);
|
|
136
157
|
useEffect(() => {
|
|
137
158
|
if (displayOption === 'dropdown') {
|
|
@@ -383,19 +404,38 @@ const ObjectPropertyInput = (props) => {
|
|
|
383
404
|
if (selectedInstance?.id) {
|
|
384
405
|
e.preventDefault();
|
|
385
406
|
}
|
|
386
|
-
}, loading: loadingOptions, onChange: (event, value) => {
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
407
|
+
}, loading: loadingOptions, onChange: async (event, value) => {
|
|
408
|
+
try {
|
|
409
|
+
if (isNil(value)) {
|
|
410
|
+
setDropdownInput(undefined);
|
|
411
|
+
setSelectedInstance(undefined);
|
|
412
|
+
await handleChangeObjectField(id, null);
|
|
413
|
+
// Trigger autosave immediately upon clearing
|
|
414
|
+
try {
|
|
415
|
+
await onAutosave?.(id);
|
|
416
|
+
}
|
|
417
|
+
catch (error) {
|
|
418
|
+
console.error('Autosave failed:', error);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else if (value?.value === '__new__') {
|
|
422
|
+
setOpenCreateDialog(true);
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
const selectedInstance = options.find((o) => o.id === value?.value);
|
|
426
|
+
setSelectedInstance(selectedInstance);
|
|
427
|
+
await handleChangeObjectField(id, selectedInstance);
|
|
428
|
+
// Trigger autosave immediately upon selection
|
|
429
|
+
try {
|
|
430
|
+
await onAutosave?.(id);
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
console.error('Autosave failed:', error);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
394
436
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
setSelectedInstance(selectedInstance);
|
|
398
|
-
handleChangeObjectField(id, selectedInstance);
|
|
437
|
+
catch (error) {
|
|
438
|
+
console.error('Failed to update field:', error);
|
|
399
439
|
}
|
|
400
440
|
}, selectOnFocus: false, onBlur: () => {
|
|
401
441
|
if (dropdownInput) {
|
|
@@ -468,9 +508,21 @@ const ObjectPropertyInput = (props) => {
|
|
|
468
508
|
: undefined, "aria-label": selectedInstance?.name }, selectedInstance?.name ? selectedInstance?.name : readOnly && 'None')),
|
|
469
509
|
!readOnly && selectedInstance ? (React.createElement(Tooltip, { title: `Unlink` },
|
|
470
510
|
React.createElement("span", null,
|
|
471
|
-
React.createElement(IconButton, { onClick: (event) => {
|
|
511
|
+
React.createElement(IconButton, { onClick: async (event) => {
|
|
472
512
|
event.stopPropagation();
|
|
473
|
-
|
|
513
|
+
try {
|
|
514
|
+
await handleChangeObjectField(id, null);
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
console.error('Failed to update field:', error);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
await onAutosave?.(id);
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
console.error('Autosave failed:', error);
|
|
525
|
+
}
|
|
474
526
|
setSelectedInstance(undefined);
|
|
475
527
|
}, sx: { p: 0, marginBottom: '4px' }, "aria-label": `Unlink` },
|
|
476
528
|
React.createElement(Close, { sx: { width: '20px', height: '20px' } }))))) : (React.createElement(Button, { sx: {
|
|
@@ -29,7 +29,7 @@ const styles = {
|
|
|
29
29
|
};
|
|
30
30
|
const RelatedObjectInstance = (props) => {
|
|
31
31
|
const { relatedObject, open, title, id, setSelectedInstance, handleClose, mode, displayOption, filter, layout, formId, actionId, fieldDefinition, setSnackbarError, setOptions, options, } = props;
|
|
32
|
-
const { handleChange: handleChangeObjectField, richTextEditor, fieldHeight, width } = useFormContext();
|
|
32
|
+
const { handleChange: handleChangeObjectField, onAutosave, richTextEditor, fieldHeight, width } = useFormContext();
|
|
33
33
|
const [selectedRow, setSelectedRow] = useState();
|
|
34
34
|
const [relationType, setRelationType] = useState(displayOption === 'dropdown' || mode === 'newOnly' ? 'new' : 'existing');
|
|
35
35
|
const apiServices = useApiServices();
|
|
@@ -42,7 +42,20 @@ const RelatedObjectInstance = (props) => {
|
|
|
42
42
|
const linkExistingInstance = async () => {
|
|
43
43
|
if (selectedRow) {
|
|
44
44
|
setSelectedInstance(selectedRow);
|
|
45
|
-
|
|
45
|
+
try {
|
|
46
|
+
await handleChangeObjectField(id, selectedRow);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Failed to update field:', error);
|
|
50
|
+
onClose();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
await onAutosave?.(id);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error('Autosave failed:', error);
|
|
58
|
+
}
|
|
46
59
|
}
|
|
47
60
|
onClose();
|
|
48
61
|
};
|
|
@@ -56,22 +69,31 @@ const RelatedObjectInstance = (props) => {
|
|
|
56
69
|
}
|
|
57
70
|
submission = await formatSubmission(submission, apiServices, relatedObject.id);
|
|
58
71
|
try {
|
|
59
|
-
await apiServices
|
|
60
|
-
.post(getPrefixedUrl(`/objects/${relatedObject.id}/instances/actions`), {
|
|
72
|
+
const response = await apiServices.post(getPrefixedUrl(`/objects/${relatedObject.id}/instances/actions`), {
|
|
61
73
|
actionId: actionId,
|
|
62
74
|
input: submission,
|
|
63
|
-
})
|
|
64
|
-
.then((response) => {
|
|
65
|
-
handleChangeObjectField(id, response);
|
|
66
|
-
setSelectedInstance(response);
|
|
67
|
-
setSnackbarError({
|
|
68
|
-
showAlert: true,
|
|
69
|
-
message: 'New instance created',
|
|
70
|
-
isError: false,
|
|
71
|
-
});
|
|
72
|
-
setOptions(options.concat([response]));
|
|
73
|
-
onClose();
|
|
74
75
|
});
|
|
76
|
+
try {
|
|
77
|
+
await handleChangeObjectField(id, response);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error('Failed to update field:', error);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await onAutosave?.(id);
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
console.error('Autosave failed:', error);
|
|
88
|
+
}
|
|
89
|
+
setSelectedInstance(response);
|
|
90
|
+
setSnackbarError({
|
|
91
|
+
showAlert: true,
|
|
92
|
+
message: 'New instance created',
|
|
93
|
+
isError: false,
|
|
94
|
+
});
|
|
95
|
+
setOptions(options.concat([response]));
|
|
96
|
+
onClose();
|
|
75
97
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
76
98
|
}
|
|
77
99
|
catch (err) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isEmpty } from 'lodash';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import { Autorenew } from '../../../../icons';
|
|
3
4
|
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
4
5
|
import Button from '../../../core/Button/Button';
|
|
5
6
|
import { Typography } from '../../../core/Typography';
|
|
@@ -14,6 +15,7 @@ const Header = (props) => {
|
|
|
14
15
|
});
|
|
15
16
|
const isSmallerThanMd = isBelow('md');
|
|
16
17
|
const { isXs, isSm } = breakpoints;
|
|
18
|
+
const isSmall = isSm || isXs;
|
|
17
19
|
return (React.createElement(Box, { sx: {
|
|
18
20
|
paddingX: isSmallerThanMd ? 2 : 3,
|
|
19
21
|
paddingTop: '0px',
|
|
@@ -29,10 +31,20 @@ const Header = (props) => {
|
|
|
29
31
|
flex: 1,
|
|
30
32
|
},
|
|
31
33
|
} },
|
|
32
|
-
title && (React.createElement(Box, { sx: { flex: '1 1
|
|
33
|
-
React.createElement(Title, { ...props })
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
title && (React.createElement(Box, { sx: { flex: '1 1 auto', minWidth: 0, display: 'flex', alignItems: 'center', gap: 1 } },
|
|
35
|
+
React.createElement(Title, { ...props }),
|
|
36
|
+
props.autosaving && !isSmall && React.createElement(SavingIndicator, null))),
|
|
37
|
+
hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center' } },
|
|
38
|
+
React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
|
|
39
|
+
React.createElement(AccordionActions, { ...props })),
|
|
40
|
+
React.createElement(Box, { sx: {
|
|
41
|
+
width: '96px',
|
|
42
|
+
minWidth: '72px',
|
|
43
|
+
display: 'flex',
|
|
44
|
+
justifyContent: 'flex-end',
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
marginLeft: 0.5,
|
|
47
|
+
} }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } })))),
|
|
36
48
|
React.createElement("div", { ref: validationErrorsRef, className: 'evoke-form-renderer-header' }, shouldShowValidationErrors && !isEmpty(errors) ? React.createElement(ValidationErrors, { errors: errors }) : null)));
|
|
37
49
|
};
|
|
38
50
|
// Default slot components for convenience
|
|
@@ -63,4 +75,30 @@ export const AccordionActions = ({ onExpandAll, onCollapseAll, expandedSections
|
|
|
63
75
|
fontSize: '14px',
|
|
64
76
|
} }, "Collapse all")));
|
|
65
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* SavingIndicator displays a spinning icon with "Saving" text
|
|
80
|
+
* to indicate that an autosave operation is in progress.
|
|
81
|
+
*/
|
|
82
|
+
const SavingIndicator = () => (React.createElement(Box, { sx: {
|
|
83
|
+
display: 'flex',
|
|
84
|
+
alignItems: 'center',
|
|
85
|
+
gap: 0.5,
|
|
86
|
+
} },
|
|
87
|
+
React.createElement(Typography, { sx: {
|
|
88
|
+
fontSize: '14px',
|
|
89
|
+
color: 'text.secondary',
|
|
90
|
+
} }, "Saving"),
|
|
91
|
+
React.createElement(Autorenew, { sx: {
|
|
92
|
+
fontSize: '16px',
|
|
93
|
+
color: 'text.secondary',
|
|
94
|
+
animation: 'spin 1s linear infinite',
|
|
95
|
+
'@keyframes spin': {
|
|
96
|
+
'0%': {
|
|
97
|
+
transform: 'rotate(0deg)',
|
|
98
|
+
},
|
|
99
|
+
'100%': {
|
|
100
|
+
transform: 'rotate(360deg)',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
} })));
|
|
66
104
|
export default Header;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useApiServices, useAuthenticationContext, } from '@evoke-platform/context';
|
|
2
2
|
import { WarningRounded } from '@mui/icons-material';
|
|
3
3
|
import DOMPurify from 'dompurify';
|
|
4
|
+
import { isEmpty } from 'lodash';
|
|
4
5
|
import React, { useEffect, useMemo } from 'react';
|
|
5
6
|
import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
|
|
6
7
|
import { TextField, Typography } from '../../../core';
|
|
@@ -37,7 +38,7 @@ function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, displ
|
|
|
37
38
|
}
|
|
38
39
|
export function RecursiveEntryRenderer(props) {
|
|
39
40
|
const { entry } = props;
|
|
40
|
-
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor, parameters, handleChange, fieldHeight, triggerFieldReset, associatedObject, form, width, } = useFormContext();
|
|
41
|
+
const { fetchedOptions, setFetchedOptions, object, getValues, errors, instance, richTextEditor, parameters, handleChange, onAutosave, fieldHeight, triggerFieldReset, associatedObject, form, width, } = useFormContext();
|
|
41
42
|
// If the entry is hidden, clear its value and any nested values, and skip rendering
|
|
42
43
|
if (!entryIsVisible(entry, getValues(), instance)) {
|
|
43
44
|
return null;
|
|
@@ -97,9 +98,24 @@ export function RecursiveEntryRenderer(props) {
|
|
|
97
98
|
React.createElement(UserProperty, { id: entryId, value: fieldValue, error: !!errors?.[entryId], readOnly: entry.type === 'readonlyField', hasDescription: !!display?.description })));
|
|
98
99
|
}
|
|
99
100
|
else if (fieldDefinition.type === 'collection') {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
101
|
+
if (fieldDefinition?.manyToManyPropertyId) {
|
|
102
|
+
if (middleObject && !isEmpty(middleObject)) {
|
|
103
|
+
return (initialMiddleObjectInstances && (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
104
|
+
React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances: fetchedOptions[`${entryId}MiddleObjectInstances`] ||
|
|
105
|
+
initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria: validation?.criteria, hasDescription: !!display?.description }))));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// when in the builder preview, the middle object won't be fetched so instead show an empty field
|
|
109
|
+
const singleSelectProperty = structuredClone(fieldDefinition);
|
|
110
|
+
singleSelectProperty.type = 'choices';
|
|
111
|
+
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
112
|
+
React.createElement(FormField, { id: entryId, property: singleSelectProperty, defaultValue: fieldValue || getValues(entryId), selectOptions: [], size: fieldHeight })));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
117
|
+
React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: entry.type !== 'readonlyField', criteria: validation?.criteria, viewLayout: display?.viewLayout, entry: entry })));
|
|
118
|
+
}
|
|
103
119
|
}
|
|
104
120
|
else if (fieldDefinition.type === 'richText') {
|
|
105
121
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) }, richTextEditor ? (React.createElement(richTextEditor, {
|
|
@@ -111,7 +127,11 @@ export function RecursiveEntryRenderer(props) {
|
|
|
111
127
|
disabled: entry.type === 'readonlyField',
|
|
112
128
|
rows: display?.rowCount,
|
|
113
129
|
hasError: !!errors?.[entryId],
|
|
114
|
-
})) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange,
|
|
130
|
+
})) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange, onBlur: () => {
|
|
131
|
+
onAutosave?.(entryId)?.catch((error) => {
|
|
132
|
+
console.error('Autosave failed:', error);
|
|
133
|
+
});
|
|
134
|
+
}, readOnly: entry.type === 'readonlyField', placeholder: display?.placeholder, error: !!errors?.[entryId], errorMessage: errors?.[entryId]?.message, isMultiLineText: !!display?.rowCount, rows: display?.rowCount, size: fieldHeight }))));
|
|
115
135
|
}
|
|
116
136
|
else if (fieldDefinition.type === 'document') {
|
|
117
137
|
return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
|
|
@@ -154,7 +174,12 @@ export function RecursiveEntryRenderer(props) {
|
|
|
154
174
|
: `${entryId}-reset-false`, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors), errorMessage: undefined },
|
|
155
175
|
React.createElement(FormField, { id: entryId,
|
|
156
176
|
// TODO: Ideally the FormField prop should be called parameter but can't change the name for backwards compatibility reasons
|
|
157
|
-
property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange,
|
|
177
|
+
property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange, onBlur: () => {
|
|
178
|
+
// Blur event - reads current value from formData
|
|
179
|
+
onAutosave?.(entryId)?.catch((error) => {
|
|
180
|
+
console.error('Autosave failed:', error);
|
|
181
|
+
});
|
|
182
|
+
}, readOnly: entry.type === 'readonlyField', placeholder: display?.placeholder, mask: validation?.mask, isOptionEqualToValue: isOptionEqualToValue, error: !!errors?.[entryId], errorMessage: errors?.[entryId]?.message, isMultiLineText: !!display?.rowCount, rows: display?.rowCount, required: entry.display?.required || false, size: fieldHeight, sortBy: display?.choicesDisplay?.sortBy && display.choicesDisplay.sortBy, displayOption: fieldDefinition.type === 'boolean'
|
|
158
183
|
? display?.booleanDisplay
|
|
159
184
|
: display?.choicesDisplay?.type && display.choicesDisplay.type, label: display?.label, description: display?.description, tooltip: display?.tooltip, selectOptions: fieldDefinition?.enum, additionalProps: additionalProps, isCombobox: fieldDefinition.nonStrictEnum, strictlyTrue: fieldDefinition.strictlyTrue })));
|
|
160
185
|
}
|
|
@@ -562,6 +562,7 @@ export const uploadDocuments = async (files, metadata, apiServices, instanceId,
|
|
|
562
562
|
const allDocuments = [];
|
|
563
563
|
const formData = new FormData();
|
|
564
564
|
for (const [index, file] of files.entries()) {
|
|
565
|
+
// Only upload File instances; SavedDocumentReference objects are already uploaded
|
|
565
566
|
if ('size' in file) {
|
|
566
567
|
formData.append(`files[${index}]`, file);
|
|
567
568
|
}
|
|
@@ -623,6 +624,7 @@ export const deleteDocuments = async (submittedFields, requestSuccess, apiServic
|
|
|
623
624
|
export async function formatSubmission(submission, apiServices, objectId, instanceId, form, setSnackbarError) {
|
|
624
625
|
for (const [key, value] of Object.entries(submission)) {
|
|
625
626
|
if (isArray(value)) {
|
|
627
|
+
// Only upload if array contains File instances (not SavedDocumentReference)
|
|
626
628
|
const fileInArray = value.some((item) => item instanceof File);
|
|
627
629
|
if (fileInArray && instanceId && apiServices && objectId) {
|
|
628
630
|
try {
|