@evoke-platform/ui-components 1.10.0-dev.1 → 1.10.0-dev.10

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 (38) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +1 -1
  2. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.d.ts +1 -0
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +430 -0
  4. package/dist/published/components/custom/CriteriaBuilder/ValueEditor.js +19 -6
  5. package/dist/published/components/custom/Form/utils.js +1 -0
  6. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
  7. package/dist/published/components/custom/FormV2/FormRenderer.js +15 -3
  8. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +0 -2
  9. package/dist/published/components/custom/FormV2/FormRendererContainer.js +92 -18
  10. package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
  11. package/dist/published/components/custom/FormV2/components/Footer.js +4 -3
  12. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +2 -1
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +30 -13
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +1 -1
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.d.ts +0 -3
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +31 -27
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
  18. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +16 -4
  19. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +1 -0
  20. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
  21. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +32 -7
  22. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +70 -18
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +37 -15
  25. package/dist/published/components/custom/FormV2/components/Header.d.ts +2 -0
  26. package/dist/published/components/custom/FormV2/components/Header.js +47 -6
  27. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +33 -19
  28. package/dist/published/components/custom/FormV2/components/utils.js +4 -7
  29. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +432 -4
  30. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +651 -13
  31. package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
  32. package/dist/published/components/custom/FormV2/tests/test-data.js +140 -0
  33. package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
  34. package/dist/published/stories/FormRendererContainer.stories.d.ts +0 -10
  35. package/dist/published/stories/FormRendererContainer.stories.js +7 -3
  36. package/dist/published/stories/FormRendererData.js +3 -43
  37. package/dist/published/theme/hooks.d.ts +4 -3
  38. package/package.json +3 -1
@@ -32,7 +32,6 @@ const styles = {
32
32
  },
33
33
  icon: {
34
34
  color: '#fff',
35
- zIndex: 40,
36
35
  fontSize: '16px',
37
36
  },
38
37
  deleteIcon: {
@@ -56,7 +55,7 @@ const styles = {
56
55
  };
57
56
  export const Image = (props) => {
58
57
  const { id, canUpdateProperty, error, value, hasDescription } = props;
59
- const { handleChange } = useFormContext();
58
+ const { handleChange, onAutosave: onAutosave } = useFormContext();
60
59
  const [image, setImage] = useState();
61
60
  useEffect(() => {
62
61
  if (typeof value === 'string') {
@@ -64,15 +63,41 @@ export const Image = (props) => {
64
63
  }
65
64
  }, [value]);
66
65
  const handleUpload = async (file) => {
66
+ // max file size 300KB
67
67
  if (file?.size && file.size <= 300000) {
68
- const dataUrl = file ? await blobToDataUrl(file) : null;
68
+ const dataUrl = await blobToDataUrl(file);
69
69
  setImage(dataUrl);
70
- handleChange(id, dataUrl);
70
+ try {
71
+ await handleChange(id, dataUrl);
72
+ }
73
+ catch (error) {
74
+ console.error('Failed to update field:', error);
75
+ return;
76
+ }
77
+ try {
78
+ await onAutosave?.(id);
79
+ }
80
+ catch (error) {
81
+ console.error('Autosave failed:', error);
82
+ }
71
83
  }
72
84
  };
73
- const handleRemove = (e) => {
85
+ const handleRemove = async (e) => {
74
86
  setImage(null);
75
- handleChange(id, '');
87
+ try {
88
+ await handleChange(id, '');
89
+ }
90
+ catch (error) {
91
+ console.error('Failed to update field:', error);
92
+ e.stopPropagation();
93
+ return;
94
+ }
95
+ try {
96
+ await onAutosave?.(id);
97
+ }
98
+ catch (error) {
99
+ console.error('Autosave failed:', error);
100
+ }
76
101
  e.stopPropagation();
77
102
  };
78
103
  const { getRootProps, getInputProps, open } = useDropzone({
@@ -81,7 +106,7 @@ export const Image = (props) => {
81
106
  accept: { 'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.svg'] },
82
107
  });
83
108
  return (React.createElement(React.Fragment, null, image ? (React.createElement(Box, { sx: styles.imageContainer },
84
- React.createElement(Box, { sx: { position: 'relative', left: 0, zIndex: 5 } },
109
+ React.createElement(Box, { sx: { position: 'relative', left: 0 } },
85
110
  React.createElement(CardMedia, { component: "img", image: image, alt: 'Uploaded Image', sx: styles.image }),
86
111
  canUpdateProperty && (React.createElement(IconButton, { onClick: handleRemove, "aria-label": "Remove image", sx: styles.deleteIcon },
87
112
  React.createElement(ClearRounded, { sx: styles.icon })))))) : canUpdateProperty ? (React.createElement(Box, { sx: {
@@ -6,7 +6,7 @@ import { Autocomplete, IconButton, Paper, TextField, Typography } from '../../..
6
6
  import { getPrefixedUrl, isOptionEqualToValue } from '../utils';
7
7
  const UserProperty = (props) => {
8
8
  const { id, error, value, readOnly, hasDescription } = props;
9
- const { fetchedOptions, setFetchedOptions, handleChange, fieldHeight } = useFormContext();
9
+ const { fetchedOptions, setFetchedOptions, handleChange, onAutosave: onAutosave, fieldHeight } = useFormContext();
10
10
  const [loadingOptions, setLoadingOptions] = useState(false);
11
11
  const apiServices = useApiServices();
12
12
  const [options, setOptions] = useState(fetchedOptions[`${id}Options`] || []);
@@ -40,9 +40,21 @@ const UserProperty = (props) => {
40
40
  });
41
41
  }
42
42
  }, [id]);
43
- function handleChangeUserProperty(id, value) {
43
+ async function handleChangeUserProperty(id, value) {
44
44
  const updatedValue = typeof value?.value === 'string' ? { name: value.label, id: value.value } : null;
45
- handleChange(id, updatedValue);
45
+ try {
46
+ await handleChange(id, updatedValue);
47
+ }
48
+ catch (error) {
49
+ console.error('Failed to update field:', error);
50
+ return; // Exit early if handleChange fails
51
+ }
52
+ try {
53
+ await onAutosave?.(id);
54
+ }
55
+ catch (error) {
56
+ console.error('Autosave failed:', error);
57
+ }
46
58
  }
47
59
  return (options && (React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue || readOnly ? '' : React.createElement(ExpandMore, null), clearIcon: !loadingOptions && userValue ? (React.createElement(IconButton, { size: "small", disableRipple: true, onKeyDown: (e) => {
48
60
  if (e.key === 'Enter') {
@@ -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
- handleChangeObjectField(id, instances[0]);
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
- }, [fieldDefinition, updatedCriteria, layout, fetchedOptions, hasFetched, id]);
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
- if (isNil(value)) {
388
- setDropdownInput(undefined);
389
- setSelectedInstance(undefined);
390
- handleChangeObjectField(id, null);
391
- }
392
- else if (value?.value === '__new__') {
393
- setOpenCreateDialog(true);
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
- else {
396
- const selectedInstance = options.find((o) => o.id === value?.value);
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
- handleChangeObjectField(id, null);
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
- handleChangeObjectField(id, selectedRow);
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) {
@@ -6,6 +6,7 @@ import { ExpandedSection } from './types';
6
6
  export type HeaderProps = {
7
7
  hasAccordions: boolean;
8
8
  shouldShowValidationErrors: boolean;
9
+ validationErrorsRef?: React.Ref<HTMLDivElement>;
9
10
  title?: string;
10
11
  expandedSections?: ExpandedSection[];
11
12
  onExpandAll?: () => void;
@@ -13,6 +14,7 @@ export type HeaderProps = {
13
14
  form: EvokeForm;
14
15
  errors?: FieldErrors;
15
16
  action?: Action;
17
+ autosaving?: boolean;
16
18
  sx?: SxProps;
17
19
  };
18
20
  declare const Header: React.FC<HeaderProps>;
@@ -1,12 +1,13 @@
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';
6
7
  import Box from '../../../layout/Box/Box';
7
8
  import ValidationErrors from './ValidationFiles/ValidationErrors';
8
9
  const Header = (props) => {
9
- const { title, errors, hasAccordions, shouldShowValidationErrors, form, sx } = props;
10
+ const { title, errors, hasAccordions, shouldShowValidationErrors, validationErrorsRef, form, sx } = props;
10
11
  const { width } = useFormContext();
11
12
  const { breakpoints, isBelow } = useWidgetSize({
12
13
  scroll: false,
@@ -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',
@@ -25,12 +27,25 @@ const Header = (props) => {
25
27
  borderBottom: !form.id ? undefined : '1px solid #e9ecef',
26
28
  gap: isSm || isXs ? 2 : 3,
27
29
  ...sx,
30
+ '.evoke-form-renderer-header': {
31
+ flex: 1,
32
+ },
28
33
  } },
29
- title && (React.createElement(Box, { sx: { flex: '1 1 100%' } },
30
- React.createElement(Title, { ...props }))),
31
- hasAccordions && (React.createElement(Box, { sx: { flex: '1 1 100%' } },
32
- React.createElement(AccordionActions, { ...props }))),
33
- shouldShowValidationErrors && !isEmpty(errors) ? React.createElement(ValidationErrors, { errors: errors }) : null));
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%' } })))),
48
+ React.createElement("div", { ref: validationErrorsRef, className: 'evoke-form-renderer-header' }, shouldShowValidationErrors && !isEmpty(errors) ? React.createElement(ValidationErrors, { errors: errors }) : null)));
34
49
  };
35
50
  // Default slot components for convenience
36
51
  export const Title = ({ title }) => (React.createElement(Typography, { sx: {
@@ -60,4 +75,30 @@ export const AccordionActions = ({ onExpandAll, onCollapseAll, expandedSections
60
75
  fontSize: '14px',
61
76
  } }, "Collapse all")));
62
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
+ } })));
63
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;
@@ -90,18 +91,31 @@ export function RecursiveEntryRenderer(props) {
90
91
  ? display?.defaultValue.orderBy
91
92
  : undefined, defaultValueCriteria: typeof display?.defaultValue === 'object' && 'criteria' in display.defaultValue
92
93
  ? display?.defaultValue?.criteria
93
- : undefined, viewLayout: display?.viewLayout, hasDescription: !!display?.description,
94
- // formId={display?.createFormId} // TODO: this should be added as part of the builder update
95
- createActionId: '_create' })));
94
+ : undefined, viewLayout: display?.viewLayout, hasDescription: !!display?.description, formId: display?.createFormId, createActionId: display?.createActionId })));
96
95
  }
97
96
  else if (fieldDefinition.type === 'user') {
98
97
  return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
99
98
  React.createElement(UserProperty, { id: entryId, value: fieldValue, error: !!errors?.[entryId], readOnly: entry.type === 'readonlyField', hasDescription: !!display?.description })));
100
99
  }
101
100
  else if (fieldDefinition.type === 'collection') {
102
- return fieldDefinition?.manyToManyPropertyId ? (middleObject && initialMiddleObjectInstances && (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
103
- React.createElement(DropdownRepeatableField, { initialMiddleObjectInstances: fetchedOptions[`${entryId}MiddleObjectInstances`] || initialMiddleObjectInstances, fieldDefinition: fieldDefinition, id: entryId, middleObject: middleObject, readOnly: entry.type === 'readonlyField', criteria: validation?.criteria, hasDescription: !!display?.description })))) : (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
104
- React.createElement(RepeatableField, { fieldDefinition: fieldDefinition, canUpdateProperty: entry.type !== 'readonlyField', criteria: validation?.criteria, viewLayout: display?.viewLayout, entry: entry, createActionId: '_create', updateActionId: '_update', deleteActionId: '_delete' })));
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
+ }
105
119
  }
106
120
  else if (fieldDefinition.type === 'richText') {
107
121
  return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) }, richTextEditor ? (React.createElement(richTextEditor, {
@@ -113,7 +127,11 @@ export function RecursiveEntryRenderer(props) {
113
127
  disabled: entry.type === 'readonlyField',
114
128
  rows: display?.rowCount,
115
129
  hasError: !!errors?.[entryId],
116
- })) : (React.createElement(FormField, { id: entryId, property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange, readOnly: entry.type === 'readonlyField', placeholder: display?.placeholder, error: !!errors?.[entryId], errorMessage: errors?.[entryId]?.message, isMultiLineText: !!display?.rowCount, rows: display?.rowCount, size: fieldHeight }))));
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 }))));
117
135
  }
118
136
  else if (fieldDefinition.type === 'document') {
119
137
  return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
@@ -156,18 +174,14 @@ export function RecursiveEntryRenderer(props) {
156
174
  : `${entryId}-reset-false`, ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors), errorMessage: undefined },
157
175
  React.createElement(FormField, { id: entryId,
158
176
  // TODO: Ideally the FormField prop should be called parameter but can't change the name for backwards compatibility reasons
159
- property: fieldDefinition, defaultValue: fieldValue || getValues(entryId), onChange: handleChange, 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, getOptionLabel: (option) => {
160
- if (typeof option === 'string') {
161
- return (entry?.enumWithLabels?.find((e) => e.value === option)
162
- ?.label ?? option);
163
- }
164
- else {
165
- return (entry?.enumWithLabels?.find((e) => e.value === option.value)?.label ?? String(option.value));
166
- }
167
- }, size: fieldHeight, sortBy: display?.choicesDisplay?.sortBy && display.choicesDisplay.sortBy, displayOption: fieldDefinition.type === 'boolean'
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'
168
183
  ? display?.booleanDisplay
169
- : display?.choicesDisplay?.type && display.choicesDisplay.type, label: display?.label, description: display?.description, tooltip: display?.tooltip, selectOptions: entry?.enumWithLabels &&
170
- entry.enumWithLabels, additionalProps: additionalProps, isCombobox: fieldDefinition.nonStrictEnum, strictlyTrue: fieldDefinition.strictlyTrue })));
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 })));
171
185
  }
172
186
  }
173
187
  else if (entry.type === 'columns') {
@@ -482,11 +482,6 @@ export function convertDocToEntries(document) {
482
482
  sortBy: 'ASC',
483
483
  },
484
484
  },
485
- enumWithLabels: [
486
- { label: 'Public', value: 'Public' },
487
- { label: 'Private', value: 'Private' },
488
- { label: 'Portal', value: 'Portal' },
489
- ],
490
485
  });
491
486
  }
492
487
  entries.push({
@@ -507,8 +502,8 @@ export function formatDataToDoc(data) {
507
502
  uploadedDate: data.uploadedDate,
508
503
  versionId: data.versionId,
509
504
  metadata: {
510
- type: data.type,
511
- view_permission: data.view_permission,
505
+ type: data.type ?? '',
506
+ view_permission: data.view_permission ?? '',
512
507
  },
513
508
  };
514
509
  }
@@ -567,6 +562,7 @@ export const uploadDocuments = async (files, metadata, apiServices, instanceId,
567
562
  const allDocuments = [];
568
563
  const formData = new FormData();
569
564
  for (const [index, file] of files.entries()) {
565
+ // Only upload File instances; SavedDocumentReference objects are already uploaded
570
566
  if ('size' in file) {
571
567
  formData.append(`files[${index}]`, file);
572
568
  }
@@ -628,6 +624,7 @@ export const deleteDocuments = async (submittedFields, requestSuccess, apiServic
628
624
  export async function formatSubmission(submission, apiServices, objectId, instanceId, form, setSnackbarError) {
629
625
  for (const [key, value] of Object.entries(submission)) {
630
626
  if (isArray(value)) {
627
+ // Only upload if array contains File instances (not SavedDocumentReference)
631
628
  const fileInArray = value.some((item) => item instanceof File);
632
629
  if (fileInArray && instanceId && apiServices && objectId) {
633
630
  try {