@evoke-platform/ui-components 1.13.0-dev.6 → 1.13.0

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 (53) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.d.ts +4 -4
  2. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +72 -145
  3. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +67 -189
  4. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.d.ts +6 -6
  5. package/dist/published/components/custom/CriteriaBuilder/PropertyTree.js +25 -12
  6. package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.d.ts +5 -4
  7. package/dist/published/components/custom/CriteriaBuilder/PropertyTreeItem.js +22 -34
  8. package/dist/published/components/custom/CriteriaBuilder/types.d.ts +11 -2
  9. package/dist/published/components/custom/CriteriaBuilder/utils.d.ts +34 -6
  10. package/dist/published/components/custom/CriteriaBuilder/utils.js +89 -18
  11. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +1 -1
  12. package/dist/published/components/custom/Form/FormComponents/DocumentComponent/DocumentList.js +3 -6
  13. package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
  14. package/dist/published/components/custom/Form/utils.d.ts +0 -1
  15. package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +1 -2
  16. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -1
  17. package/dist/published/components/custom/FormV2/FormRenderer.js +2 -1
  18. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -1
  19. package/dist/published/components/custom/FormV2/FormRendererContainer.js +23 -26
  20. package/dist/published/components/custom/FormV2/components/Body.js +1 -1
  21. package/dist/published/components/custom/FormV2/components/Footer.js +1 -1
  22. package/dist/published/components/custom/FormV2/components/FormContext.d.ts +0 -1
  23. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.d.ts +0 -1
  24. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +3 -3
  25. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.d.ts +2 -3
  26. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +10 -46
  27. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +3 -4
  28. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +23 -29
  29. package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/InstanceLookup.js +1 -1
  30. package/dist/published/components/custom/FormV2/components/FormSections.js +0 -1
  31. package/dist/published/components/custom/FormV2/components/Header.d.ts +1 -0
  32. package/dist/published/components/custom/FormV2/components/Header.js +19 -8
  33. package/dist/published/components/custom/FormV2/components/HtmlView.d.ts +9 -0
  34. package/dist/published/components/custom/FormV2/components/HtmlView.js +42 -0
  35. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +4 -8
  36. package/dist/published/components/custom/FormV2/components/types.d.ts +1 -6
  37. package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -5
  38. package/dist/published/components/custom/FormV2/components/utils.js +79 -156
  39. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +2 -2
  40. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +84 -0
  41. package/dist/published/components/custom/HistoryLog/HistoryData.js +1 -2
  42. package/dist/published/components/custom/HistoryLog/index.js +1 -2
  43. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +10 -24
  44. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +3 -0
  45. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +3 -1
  46. package/dist/published/stories/Backdrop.stories.d.ts +2 -2
  47. package/dist/published/stories/CriteriaBuilder.stories.js +22 -70
  48. package/dist/published/stories/FormLabel.stories.d.ts +2 -2
  49. package/dist/published/stories/FormRenderer.stories.d.ts +3 -3
  50. package/dist/published/stories/FormRendererContainer.stories.d.ts +15 -5
  51. package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +9 -0
  52. package/dist/published/theme/hooks.d.ts +1 -2
  53. package/package.json +10 -15
@@ -1,4 +1,4 @@
1
- import { useApiServices, } from '@evoke-platform/context';
1
+ import { useApiServices } from '@evoke-platform/context';
2
2
  import { isNil } from 'lodash';
3
3
  import prettyBytes from 'pretty-bytes';
4
4
  import React, { useCallback, useEffect, useState } from 'react';
@@ -7,12 +7,12 @@ import { InfoRounded, UploadCloud } from '../../../../../../icons';
7
7
  import { useFormContext } from '../../../../../../theme/hooks';
8
8
  import { Skeleton, Snackbar, Typography } from '../../../../../core';
9
9
  import { Box, Grid } from '../../../../../layout';
10
- import { getEntryId, getPrefixedUrl, getUnnestedEntries, uploadFiles } from '../../utils';
10
+ import { getPrefixedUrl } from '../../utils';
11
11
  import { DocumentList } from './DocumentList';
12
12
  export const Document = (props) => {
13
- const { id, fieldType = 'document', canUpdateProperty, error, value, validate, hasDescription } = props;
13
+ const { id, canUpdateProperty, error, value, validate, hasDescription } = props;
14
14
  const apiServices = useApiServices();
15
- const { fetchedOptions, setFetchedOptions, object, handleChange, onAutosave: onAutosave, instance, form, } = useFormContext();
15
+ const { fetchedOptions, setFetchedOptions, object, handleChange, onAutosave: onAutosave, instance, } = useFormContext();
16
16
  const [snackbarError, setSnackbarError] = useState();
17
17
  const [documents, setDocuments] = useState();
18
18
  const [hasUpdatePermission, setHasUpdatePermission] = useState(fetchedOptions[`${id}UpdatePermission`]);
@@ -35,17 +35,8 @@ export const Document = (props) => {
35
35
  }, [value]);
36
36
  const checkPermissions = useCallback(() => {
37
37
  if (canUpdateProperty && !fetchedOptions[`${id}UpdatePermission`] && instance?.id) {
38
- // Find the entry to get the configured createActionId
39
- const allEntries = getUnnestedEntries(form?.entries ?? []) ?? [];
40
- const entry = allEntries?.find((entry) => getEntryId(entry) === id);
41
- const createActionId = entry?.display?.createActionId ?? '_create';
42
- // For 'file' type properties, check regular object instance permissions
43
- // For 'document' type properties, check document attachment permissions
44
- const endpoint = fieldType === 'file'
45
- ? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=execute&field=${createActionId}`)
46
- : getPrefixedUrl(`/objects/${object?.id}/instances/${instance.id}/documents/checkAccess?action=update`);
47
38
  apiServices
48
- .get(endpoint)
39
+ .get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=update`))
49
40
  .then((accessCheck) => {
50
41
  setFetchedOptions({
51
42
  [`${id}UpdatePermission`]: accessCheck.result,
@@ -59,43 +50,16 @@ export const Document = (props) => {
59
50
  setHasUpdatePermission(false);
60
51
  });
61
52
  }
62
- }, [
63
- canUpdateProperty,
64
- fetchedOptions[`${id}UpdatePermission`],
65
- instance?.id,
66
- object,
67
- id,
68
- apiServices,
69
- form,
70
- fieldType,
71
- ]);
53
+ }, [canUpdateProperty, fetchedOptions, instance, object]);
72
54
  useEffect(() => {
73
55
  checkPermissions();
74
56
  }, [checkPermissions]);
75
57
  const handleUpload = async (files) => {
76
- if (!files?.length) {
77
- return;
78
- }
79
- let uploadedFiles = files;
80
- // Get the createActionId from display options, default to '_create'
81
- const allEntries = getUnnestedEntries(form?.entries ?? []);
82
- const entry = allEntries?.find((entry) => getEntryId(entry) === id);
83
- const createActionId = entry?.display?.createActionId ?? '_create';
84
- // Immediately upload files for 'file' type properties when autosave is not enabled.
85
- // Linking will happen upon final submission.
86
- // If autosave is enabled, upload and linking will happen in the autosave handler.
87
- if (fieldType === 'file' && !onAutosave) {
88
- const { successfulUploads, errorMessage } = await uploadFiles(files, apiServices, createActionId, undefined, false);
89
- uploadedFiles = successfulUploads;
90
- if (errorMessage) {
91
- setSnackbarError({ message: errorMessage, type: 'error' });
92
- }
93
- }
94
- // Store uploaded file references (or File objects) in form state
95
- const newDocuments = [...(documents ?? []), ...uploadedFiles];
58
+ // Store File objects in form state - they will be uploaded during autosave via formatSubmission()
59
+ const newDocuments = [...(documents ?? []), ...(files ?? [])];
96
60
  setDocuments(newDocuments);
97
61
  try {
98
- await handleChange?.(id, newDocuments);
62
+ handleChange && (await handleChange(id, newDocuments));
99
63
  }
100
64
  catch (error) {
101
65
  console.error('Failed to update field:', error);
@@ -163,7 +127,7 @@ export const Document = (props) => {
163
127
  } }, validate?.maxDocuments === 1
164
128
  ? `Maximum size is ${formattedMaxSize}.`
165
129
  : `The maximum size of each document is ${formattedMaxSize}.`)))))),
166
- canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, fieldType: fieldType, handleChange: handleChange, onAutosave: onAutosave, value: documents, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
130
+ canUpdateProperty && isNil(hasUpdatePermission) ? (React.createElement(Skeleton, { variant: "rectangular", height: formattedMaxSize || allowedTypesMessage ? '136px' : '115px', sx: { margin: '5px 0', borderRadius: '8px' } })) : (React.createElement(DocumentList, { id: id, handleChange: handleChange, onAutosave: onAutosave, value: value, setSnackbarError: (type, message) => setSnackbarError({ message, type }), canUpdateProperty: canUpdateProperty && !!hasUpdatePermission })),
167
131
  React.createElement(Snackbar, { open: !!snackbarError?.message, handleClose: () => setSnackbarError(null), message: snackbarError?.message, error: snackbarError?.type === 'error' }),
168
132
  errors.length > 0 && (React.createElement(Box, { display: 'flex', alignItems: 'center' },
169
133
  React.createElement(InfoRounded, { sx: { fontSize: '.75rem', marginRight: '3px', color: '#D3271B' } }),
@@ -1,12 +1,11 @@
1
1
  import React from 'react';
2
- import { DocumentReference } from '../../types';
2
+ import { SavedDocumentReference } from '../../types';
3
3
  type DocumentListProps = {
4
- handleChange?: (propertyId: string, value: (File | DocumentReference)[] | undefined) => void;
4
+ handleChange?: (propertyId: string, value: (File | SavedDocumentReference)[] | undefined) => void;
5
5
  onAutosave?: (fieldId: string) => void | Promise<void>;
6
6
  id: string;
7
- fieldType?: 'document' | 'file';
8
7
  canUpdateProperty: boolean;
9
- value: (File | DocumentReference)[] | undefined;
8
+ value: (File | SavedDocumentReference)[] | undefined;
10
9
  setSnackbarError: (type: 'error' | 'success', message: string) => void;
11
10
  };
12
11
  export declare const DocumentList: (props: DocumentListProps) => React.JSX.Element;
@@ -1,4 +1,5 @@
1
1
  import { useApiServices } from '@evoke-platform/context';
2
+ import { isEqual } from 'lodash';
2
3
  import prettyBytes from 'pretty-bytes';
3
4
  import React, { useEffect, useState } from 'react';
4
5
  import { FileWithExtension, LaunchRounded, TrashCan, WarningRounded } from '../../../../../../icons';
@@ -23,33 +24,29 @@ const viewableFileTypes = [
23
24
  'text/plain',
24
25
  ];
25
26
  export const DocumentList = (props) => {
26
- const { handleChange, onAutosave, id, fieldType = 'document', canUpdateProperty, value: documents, setSnackbarError, } = props;
27
+ const { handleChange, onAutosave, id, canUpdateProperty, value: documents, setSnackbarError } = props;
27
28
  const apiServices = useApiServices();
28
29
  const { fetchedOptions, setFetchedOptions, object, instance } = useFormContext();
29
30
  // Determine property type once at component level
30
- const isFileType = fieldType === 'file';
31
+ const propertyType = object?.properties?.find((p) => p.id === id)?.type;
32
+ const isFileType = propertyType === 'file';
31
33
  const [hasViewPermission, setHasViewPermission] = useState(fetchedOptions[`${id}ViewPermission`] ?? true);
32
34
  // savedDocuments is either FileInstance[] or DocumentType[], never a mix
33
35
  const [savedDocuments, setSavedDocuments] = useState(fetchedOptions[`${id}SavedDocuments`]);
34
36
  useEffect(() => {
35
- // Use documents prop (value) as the source of truth, not instance[id]
36
- // This ensures newly uploaded files trigger a fetch even before they're saved to instance
37
- const currentValue = documents;
37
+ const currentValue = instance?.[id];
38
38
  if (currentValue?.length) {
39
- // Filter out File objects only - we want to fetch details for all DocumentReferences including unsaved ones
40
- const currentDocumentIds = currentValue
41
- .filter((doc) => !(doc instanceof File))
42
- .map((doc) => doc.id);
43
- if (currentDocumentIds.length) {
44
- // Check if there are any document IDs that we haven't fetched yet
45
- const savedDocumentIds = savedDocuments?.map((doc) => doc.id) ?? [];
46
- const missingDocumentIds = currentDocumentIds.filter((id) => !savedDocumentIds.includes(id));
47
- if (missingDocumentIds.length > 0) {
48
- getDocuments(currentDocumentIds);
49
- }
39
+ const currentDocumentIds = currentValue.map((doc) => doc.id);
40
+ if (currentDocumentIds.length &&
41
+ // these need to be sorted otherwise it will evaluate as not equal if the ids are in different orders causing unnecessary fetches
42
+ !isEqual(currentDocumentIds.slice().sort(), savedDocuments
43
+ ?.map((doc) => doc.id)
44
+ .slice()
45
+ .sort())) {
46
+ getDocuments(currentDocumentIds);
50
47
  }
51
48
  }
52
- }, [documents, savedDocuments]);
49
+ }, [id, documents, object]);
53
50
  useEffect(() => {
54
51
  if (fetchedOptions[`${id}SavedDocuments`]) {
55
52
  setSavedDocuments(fetchedOptions[`${id}SavedDocuments`]);
@@ -86,31 +83,26 @@ export const DocumentList = (props) => {
86
83
  if (!fetchedOptions[`${id}ViewPermission`]) {
87
84
  checkPermissions();
88
85
  }
89
- }, [object, id, fetchedOptions[`${id}ViewPermission`]]);
86
+ }, [object]);
90
87
  const checkPermissions = () => {
91
88
  if (instance?.[id]?.length) {
92
- const endpoint = isFileType
93
- ? getPrefixedUrl(`/objects/sys__file/instances/checkAccess?action=read&field=content`)
94
- : getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=view`);
95
89
  apiServices
96
- .get(endpoint)
90
+ .get(getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/checkAccess?action=view`))
97
91
  .then((viewPermissionCheck) => {
98
92
  setFetchedOptions({
99
93
  [`${id}ViewPermission`]: viewPermissionCheck.result,
100
94
  });
101
95
  setHasViewPermission(viewPermissionCheck.result);
102
- })
103
- .catch(() => setHasViewPermission(false));
96
+ });
104
97
  }
105
98
  };
106
99
  const isFile = (doc) => doc instanceof File;
107
- const isUnsavedFile = (doc) => isFile(doc) || !!doc.unsaved;
108
100
  const fileExists = (doc) => savedDocuments?.find((d) => d.id === doc.id);
109
101
  const handleRemove = async (index) => {
110
102
  const updatedDocuments = documents?.filter((_, i) => i !== index) ?? [];
111
103
  const newValue = updatedDocuments.length === 0 ? undefined : updatedDocuments;
112
104
  try {
113
- handleChange?.(id, newValue);
105
+ handleChange && (await handleChange(id, newValue));
114
106
  }
115
107
  catch (error) {
116
108
  console.error('Failed to update field:', error);
@@ -132,7 +124,9 @@ export const DocumentList = (props) => {
132
124
  : savedDocuments?.find((savedDocument) => savedDocument.id === doc.id)?.contentType;
133
125
  if (!isFile(doc)) {
134
126
  try {
135
- const contentEndpoint = isFileType
127
+ // Determine property type to use the correct endpoint
128
+ const propertyType = object?.properties?.find((p) => p.id === id)?.type;
129
+ const contentEndpoint = propertyType === 'file'
136
130
  ? getPrefixedUrl(`/files/${doc.id}/content`)
137
131
  : getPrefixedUrl(`/objects/${object?.id}/instances/${instance?.id}/documents/${doc.id}/content`);
138
132
  const documentResponse = await apiServices.get(contentEndpoint, { responseType: 'blob' });
@@ -195,10 +189,10 @@ export const DocumentList = (props) => {
195
189
  } }, doc.name)),
196
190
  React.createElement(Grid, { item: true, xs: 12 },
197
191
  React.createElement(Typography, { sx: { fontSize: '12px', color: '#637381' } }, getDocumentSize(doc)))),
198
- (isUnsavedFile(doc) || (hasViewPermission && !isFile(doc) && fileExists(doc))) && (React.createElement(Grid, { item: true },
192
+ (isFile(doc) || (hasViewPermission && !isFile(doc) && fileExists(doc))) && (React.createElement(Grid, { item: true },
199
193
  React.createElement(IconButton, { "aria-label": "open document", sx: { ...styles.icon, marginRight: '16px' }, onClick: () => openDocument(index) },
200
194
  React.createElement(LaunchRounded, { sx: { color: '#637381', fontSize: '22px' } })))),
201
- !isFile(doc) && !isUnsavedFile(doc) && savedDocuments && !fileExists(doc) && (React.createElement(Chip, { label: "Deleted", sx: {
195
+ !isFile(doc) && savedDocuments && !fileExists(doc) && (React.createElement(Chip, { label: "Deleted", sx: {
202
196
  marginRight: '16px',
203
197
  backgroundColor: 'rgba(222, 48, 36, 0.16)',
204
198
  color: '#A91813',
@@ -16,7 +16,7 @@ const SearchField = (props) => {
16
16
  };
17
17
  const handleSearch = async (e) => {
18
18
  const searchProperties = searchableColumns.map((column) => {
19
- const columnId = column.id;
19
+ const columnId = column.type === 'object' ? `${column.id}.name` : column.id;
20
20
  return {
21
21
  [columnId]: {
22
22
  like: e.target.value,
@@ -126,7 +126,6 @@ function FormSections(props) {
126
126
  display: 'flex',
127
127
  flexDirection: 'column',
128
128
  justifyContent: section.entries && section?.entries.length > 0 ? 'space-between' : 'flex-end',
129
- height: '100%',
130
129
  } },
131
130
  React.createElement(Box, null, section.entries &&
132
131
  section.entries.map((sectionEntry, index) => {
@@ -7,6 +7,7 @@ export type HeaderProps = {
7
7
  hasAccordions: boolean;
8
8
  shouldShowValidationErrors?: boolean;
9
9
  validationContainerRef?: React.Ref<HTMLDivElement>;
10
+ hideTitle: boolean;
10
11
  title?: string;
11
12
  expandedSections?: ExpandedSection[];
12
13
  onExpandAll?: () => void;
@@ -7,7 +7,7 @@ import { Typography } from '../../../core/Typography';
7
7
  import Box from '../../../layout/Box/Box';
8
8
  import ValidationErrors from './ValidationFiles/ValidationErrors';
9
9
  const Header = (props) => {
10
- const { title, errors, hasAccordions, shouldShowValidationErrors, validationContainerRef, sx, autosaveEnabled } = props;
10
+ const { title, errors, hasAccordions, shouldShowValidationErrors, validationContainerRef, sx, autosaveEnabled, hideTitle, autosaving, } = props;
11
11
  const { width } = useFormContext();
12
12
  const { breakpoints, isBelow } = useWidgetSize({
13
13
  scroll: false,
@@ -17,23 +17,32 @@ const Header = (props) => {
17
17
  const { isXs, isSm } = breakpoints;
18
18
  const isSmall = isSm || isXs;
19
19
  const displayValidationErrors = shouldShowValidationErrors && !isEmpty(errors);
20
+ if (hideTitle && !autosaveEnabled && !hasAccordions && !displayValidationErrors) {
21
+ return null;
22
+ }
20
23
  return (React.createElement(Box, { sx: {
21
24
  paddingX: isSmallerThanMd ? 2 : 3,
22
25
  paddingTop: '0px',
23
26
  display: 'flex',
24
27
  alignItems: 'center',
28
+ justifyContent: 'flex-end',
25
29
  flexWrap: 'wrap',
26
30
  paddingY: isSm || isXs ? 2 : 3,
27
31
  borderBottom: '1px solid #e9ecef',
28
32
  gap: isSm || isXs ? 2 : 3,
33
+ minHeight: '24px',
29
34
  ...sx,
30
35
  }, ref: validationContainerRef },
31
- title && (React.createElement(Box, { sx: { flex: '1 1 auto', minWidth: 0, display: 'flex', alignItems: 'center', gap: 1 } },
32
- React.createElement(Title, { ...props }),
33
- props.autosaving && !isSmall && React.createElement(SavingIndicator, null))),
34
- hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center' } },
35
- React.createElement(Box, { sx: { display: 'flex', alignItems: 'center' } },
36
- React.createElement(AccordionActions, { ...props })),
36
+ ((!hideTitle && title) || (autosaving && !isSmall)) && (React.createElement(Box, { sx: {
37
+ flex: '1 1 auto',
38
+ minWidth: 0,
39
+ display: 'flex',
40
+ alignItems: 'center',
41
+ gap: 1,
42
+ } },
43
+ !hideTitle && title && React.createElement(Title, { ...props }),
44
+ autosaving && !isSmall ? React.createElement(SavingIndicator, null) : React.createElement(React.Fragment, null))),
45
+ hasAccordions && (React.createElement(Box, { sx: { flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'space-between' } },
37
46
  autosaveEnabled && (React.createElement(Box, { sx: {
38
47
  width: '96px',
39
48
  minWidth: '72px',
@@ -41,7 +50,9 @@ const Header = (props) => {
41
50
  justifyContent: 'flex-end',
42
51
  alignItems: 'center',
43
52
  marginLeft: 0.5,
44
- } }, props.autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(Box, { sx: { width: '100%' } }))))),
53
+ } }, autosaving && isSmall ? React.createElement(SavingIndicator, null) : React.createElement(React.Fragment, null))),
54
+ React.createElement(Box, { sx: { display: 'flex', alignItems: 'center', justifyContent: 'center' } },
55
+ React.createElement(AccordionActions, { ...props })))),
45
56
  displayValidationErrors ? React.createElement(ValidationErrors, { errors: errors }) : null));
46
57
  };
47
58
  // Default slot components for convenience
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import 'quill/dist/quill.snow.css';
3
+ import 'quill-table-up/index.css';
4
+ type EditorProps = {
5
+ /** HTML content to display in the read-only editor */
6
+ value: string;
7
+ };
8
+ declare const HtmlView: ({ value }: EditorProps) => React.JSX.Element;
9
+ export default HtmlView;
@@ -0,0 +1,42 @@
1
+ import Quill from 'quill';
2
+ import React, { useEffect, useRef } from 'react';
3
+ import 'quill/dist/quill.snow.css';
4
+ import TableUp from 'quill-table-up';
5
+ import 'quill-table-up/index.css';
6
+ import { Box } from '@mui/material';
7
+ import DOMPurify from 'dompurify';
8
+ Quill.register({ [`modules/${TableUp.moduleName}`]: TableUp }, true);
9
+ const HtmlView = ({ value }) => {
10
+ const containerRef = useRef(null);
11
+ const quillRef = useRef(null);
12
+ // Initialize ONCE
13
+ useEffect(() => {
14
+ if (!containerRef.current || quillRef.current)
15
+ return;
16
+ const quill = new Quill(containerRef.current, {
17
+ theme: 'snow',
18
+ readOnly: true,
19
+ modules: {
20
+ toolbar: false,
21
+ },
22
+ });
23
+ quillRef.current = quill;
24
+ }, []);
25
+ // Update content only
26
+ useEffect(() => {
27
+ if (!quillRef.current)
28
+ return;
29
+ quillRef.current.setContents([]);
30
+ quillRef.current.clipboard.dangerouslyPasteHTML(DOMPurify.sanitize(value));
31
+ }, [value]);
32
+ return (React.createElement(Box, { sx: {
33
+ width: '100%',
34
+ height: '100%',
35
+ '.ql-container': {
36
+ border: 'none',
37
+ minHeight: 20,
38
+ },
39
+ } },
40
+ React.createElement("div", { ref: containerRef })));
41
+ };
42
+ export default HtmlView;
@@ -1,6 +1,5 @@
1
1
  import { useApiServices, useAuthenticationContext, } from '@evoke-platform/context';
2
2
  import { WarningRounded } from '@mui/icons-material';
3
- import DOMPurify from 'dompurify';
4
3
  import { isEmpty } from 'lodash';
5
4
  import React, { useEffect, useMemo } from 'react';
6
5
  import useWidgetSize, { useFormContext } from '../../../../theme/hooks';
@@ -19,6 +18,7 @@ import ObjectPropertyInput from './FormFieldTypes/relatedObjectFiles/ObjectPrope
19
18
  import UserProperty from './FormFieldTypes/UserProperty';
20
19
  import FormSections from './FormSections';
21
20
  import { entryIsVisible, fetchCollectionData, filterEmptySections, getEntryId, getFieldDefinition, isAddressProperty, isOptionEqualToValue, updateCriteriaInputs, } from './utils';
21
+ import HtmlView from './HtmlView';
22
22
  function getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors, validation) {
23
23
  return {
24
24
  inputId: entryId,
@@ -70,9 +70,7 @@ export function RecursiveEntryRenderer(props) {
70
70
  return null;
71
71
  }
72
72
  if (entry.type === 'content') {
73
- return (React.createElement(Box, { dangerouslySetInnerHTML: { __html: DOMPurify.sanitize(entry.html) }, sx: {
74
- fontFamily: 'Roboto, Helvetica, Arial, sans-serif',
75
- } }));
73
+ return React.createElement(HtmlView, { value: entry.html });
76
74
  }
77
75
  else if ((entry.type === 'input' || entry.type === 'readonlyField' || entry.type === 'inputField') &&
78
76
  fieldDefinition) {
@@ -87,9 +85,7 @@ export function RecursiveEntryRenderer(props) {
87
85
  return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
88
86
  React.createElement(ObjectPropertyInput, { relatedObjectId: !fieldDefinition.objectId ? display?.relatedObjectId : fieldDefinition.objectId, fieldDefinition: fieldDefinition, id: entryId, mode: display?.mode || 'default', error: !!errors?.[entryId], displayOption: display?.relatedObjectDisplay || 'dialogBox', initialValue: fieldValue, readOnly: entry.type === 'readonlyField', filter: 'criteria' in validation && validation.criteria
89
87
  ? updateCriteriaInputs(validation.criteria, getValues(), userAccount, instance)
90
- : entry.display?.criteria
91
- ? updateCriteriaInputs(entry.display?.criteria, getValues(), userAccount, instance)
92
- : undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
88
+ : undefined, sortBy: typeof display?.defaultValue === 'object' && 'sortBy' in display.defaultValue
93
89
  ? display?.defaultValue.sortBy
94
90
  : undefined, orderBy: typeof display?.defaultValue === 'object' && 'orderBy' in display.defaultValue
95
91
  ? display?.defaultValue.orderBy
@@ -138,7 +134,7 @@ export function RecursiveEntryRenderer(props) {
138
134
  }
139
135
  else if (fieldDefinition.type === 'document' || fieldDefinition.type === 'file') {
140
136
  return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
141
- React.createElement(Document, { id: entryId, fieldType: fieldDefinition.type, error: !!errors?.[entryId], value: fieldValue, canUpdateProperty: !(entry.type === 'readonlyField'), hasDescription: !!display?.description, validate: validation })));
137
+ React.createElement(Document, { id: entryId, error: !!errors?.[entryId], value: fieldValue, canUpdateProperty: !(entry.type === 'readonlyField'), hasDescription: !!display?.description, validate: validation })));
142
138
  }
143
139
  else if (fieldDefinition.type === 'criteria') {
144
140
  return (React.createElement(FieldWrapper, { ...getFieldWrapperProps(fieldDefinition, entry, entryId, fieldValue, display, errors) },
@@ -12,10 +12,9 @@ export type FieldAddress = {
12
12
  export type AccessCheck = {
13
13
  result: boolean;
14
14
  };
15
- export type DocumentReference = {
15
+ export type SavedDocumentReference = {
16
16
  id: string;
17
17
  name: string;
18
- unsaved?: boolean;
19
18
  };
20
19
  export type Document = {
21
20
  id: string;
@@ -113,7 +112,3 @@ export type InstanceLink = {
113
112
  id: string;
114
113
  objectId: string;
115
114
  };
116
- export type FileUploadBatchResult = {
117
- errorMessage?: string;
118
- successfulUploads: DocumentReference[];
119
- };
@@ -4,7 +4,7 @@ import { LocalDateTime } from '@js-joda/core';
4
4
  import { FieldErrors, FieldValues } from 'react-hook-form';
5
5
  import { ObjectProperty } from '../../../../types';
6
6
  import { AutocompleteOption } from '../../../core';
7
- import { DocumentReference, FileUploadBatchResult, InstanceLink } from './types';
7
+ import { InstanceLink, SavedDocumentReference } from './types';
8
8
  export declare const scrollIntoViewWithOffset: (el: HTMLElement, offset: number, container?: HTMLElement) => void;
9
9
  export declare const normalizeDateTime: (dateTime: LocalDateTime) => string;
10
10
  export declare function isAddressProperty(key: string): boolean;
@@ -49,13 +49,16 @@ export declare const convertPropertiesToParams: (object: Obj) => InputParameter[
49
49
  export declare function getUnnestedEntries(entries: FormEntry[]): FormEntry[];
50
50
  export declare const isEmptyWithDefault: (fieldValue: unknown, entry: InputParameterReference | InputField, instance: Record<string, unknown> | object) => boolean | "" | 0 | undefined;
51
51
  export declare const docProperties: Property[];
52
- export declare const uploadFiles: (files: (File | DocumentReference)[], apiServices: ApiServices, actionId?: string, linkTo?: InstanceLink, shortCircuit?: boolean) => Promise<FileUploadBatchResult>;
52
+ /**
53
+ * Upload files using the POST /files endpoint for sys__file objects
54
+ */
55
+ export declare const uploadFiles: (files: (File | SavedDocumentReference)[], apiServices: ApiServices, actionId?: string, metadata?: Record<string, string>, linkTo?: InstanceLink) => Promise<SavedDocumentReference[]>;
53
56
  /**
54
57
  * Creates file links for uploaded files by calling the objects endpoint with sys__fileLink
55
58
  * This is used after instance creation when the instance ID becomes available
56
59
  */
57
- export declare const createFileLinks: (fileReferences: DocumentReference[], linkedInstance: InstanceLink, apiServices: ApiServices) => Promise<void>;
58
- export declare const uploadDocuments: (files: (File | DocumentReference)[], metadata: Record<string, string>, apiServices: ApiServices, instanceId: string, objectId: string) => Promise<DocumentReference[]>;
60
+ export declare const createFileLinks: (files: SavedDocumentReference[], linkedInstance: InstanceLink, apiServices: ApiServices) => Promise<void>;
61
+ export declare const uploadDocuments: (files: (File | SavedDocumentReference)[], metadata: Record<string, string>, apiServices: ApiServices, instanceId: string, objectId: string) => Promise<SavedDocumentReference[]>;
59
62
  export declare const deleteDocuments: (submittedFields: FieldValues, requestSuccess: boolean, apiServices: ApiServices, object: Obj, instance: FieldValues, action?: Action, setSnackbarError?: React.Dispatch<React.SetStateAction<{
60
63
  showAlert: boolean;
61
64
  message?: string;
@@ -81,7 +84,6 @@ export declare function formatSubmission(submission: FieldValues, apiServices?:
81
84
  }>>, associatedObject?: {
82
85
  instanceId: string;
83
86
  propertyId: string;
84
- objectId?: string;
85
87
  }, parameters?: InputParameter[]): Promise<FieldValues>;
86
88
  export declare function filterEmptySections(entry: Sections | Columns, instance?: FieldValues, formData?: FieldValues): Sections | Columns | null;
87
89
  export declare function assignIdsToSectionsAndRichText(entries: FormEntry[], object: Obj, parameters?: InputParameter[]): FormEntry[];