@evoke-platform/ui-components 1.16.0 → 1.18.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 (37) hide show
  1. package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +3 -0
  2. package/dist/published/components/custom/Form/utils.d.ts +2 -2
  3. package/dist/published/components/custom/FormField/AddressFieldComponent/addressFieldComponent.js +1 -1
  4. package/dist/published/components/custom/FormField/BooleanSelect/BooleanSelect.js +15 -7
  5. package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +1 -1
  6. package/dist/published/components/custom/FormField/Select/Select.js +1 -1
  7. package/dist/published/components/custom/FormV2/FormRenderer.d.ts +1 -0
  8. package/dist/published/components/custom/FormV2/FormRenderer.js +12 -7
  9. package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +1 -0
  10. package/dist/published/components/custom/FormV2/FormRendererContainer.js +52 -31
  11. package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -0
  12. package/dist/published/components/custom/FormV2/components/Body.js +4 -2
  13. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/DropdownRepeatableFieldInput.js +3 -0
  14. package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +8 -6
  15. package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +1 -0
  16. package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +1 -0
  17. package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +2 -0
  18. package/dist/published/components/custom/FormV2/components/FormletRenderer.d.ts +7 -0
  19. package/dist/published/components/custom/FormV2/components/FormletRenderer.js +22 -0
  20. package/dist/published/components/custom/FormV2/components/HtmlView.js +16 -9
  21. package/dist/published/components/custom/FormV2/components/MisconfiguredErrorMessage.d.ts +2 -0
  22. package/dist/published/components/custom/FormV2/components/MisconfiguredErrorMessage.js +15 -0
  23. package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +17 -24
  24. package/dist/published/components/custom/FormV2/components/types.d.ts +2 -1
  25. package/dist/published/components/custom/FormV2/components/utils.d.ts +7 -2
  26. package/dist/published/components/custom/FormV2/components/utils.js +84 -4
  27. package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +228 -7
  28. package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +491 -35
  29. package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +20 -9
  30. package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +1 -0
  31. package/dist/published/stories/FormRenderer.stories.d.ts +3 -0
  32. package/dist/published/stories/FormRenderer.stories.js +1 -0
  33. package/dist/published/stories/FormRendererContainer.stories.d.ts +5 -0
  34. package/dist/published/stories/FormRendererData.d.ts +15 -0
  35. package/dist/published/stories/FormRendererData.js +63 -0
  36. package/dist/published/stories/sharedMswHandlers.js +4 -2
  37. package/package.json +1 -1
@@ -562,6 +562,9 @@ const CriteriaBuilder = (props) => {
562
562
  borderStyle: 'hidden',
563
563
  background: '#fff',
564
564
  },
565
+ '.ruleGroup:not(:has(.rule, .ruleGroup .ruleGroup))': {
566
+ backgroundColor: 'transparent',
567
+ },
565
568
  '.ruleGroup-header': {
566
569
  display: 'block',
567
570
  },
@@ -1,4 +1,4 @@
1
- import { ActionInput, ActionInputType, ApiServices, AxiosError, FormEntry, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, PropertyType, UserAccount } from '@evoke-platform/context';
1
+ import { ActionInput, ActionInputType, ApiServices, AxiosError, FormEntry, FormletReference, InputParameter, InputParameterReference, Obj, ObjectInstance, Property, PropertyType, UserAccount } from '@evoke-platform/context';
2
2
  import { ReactComponent } from '@formio/react';
3
3
  import { LocalDateTime } from '@js-joda/core';
4
4
  import { AutocompleteOption } from '../../core';
@@ -7,7 +7,7 @@ export declare function determineComponentType(properties: Property[], parameter
7
7
  export declare function determineParameterType(componentType: string): PropertyType;
8
8
  export declare function getFlattenEntries(entries: FormEntry[]): InputParameterReference[];
9
9
  export declare function convertFormToComponents(entries: FormEntry[], parameters: InputParameter[], object: Obj): ActionInput[];
10
- export declare function convertComponentsToForm(components: ActionInput[]): FormEntry[];
10
+ export declare function convertComponentsToForm(components: ActionInput[]): Exclude<FormEntry, FormletReference>[];
11
11
  export declare function getMiddleObjectFilter(property: Property, instance: ObjectInstance): {
12
12
  where: {
13
13
  [x: string]: string;
@@ -48,7 +48,7 @@ const AddressFieldComponent = (props) => {
48
48
  !mask ? (React.createElement(TextField, { id: id, inputRef: textFieldRef, onChange: !readOnly ? handleChange : undefined, error: error, errorMessage: errorMessage, value: value, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: readOnly ? undefined : placeholder, InputProps: {
49
49
  type: 'search',
50
50
  autoComplete: 'off',
51
- }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
51
+ }, required: required, readOnly: readOnly, multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}), sx: { backgroundColor: 'white' } })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, inputRef: textFieldRef, sx: readOnly
52
52
  ? {
53
53
  '& .MuiOutlinedInput-notchedOutline': {
54
54
  border: 'none',
@@ -4,11 +4,17 @@ import { CheckBox as CheckBoxIcon, CheckBoxOutlineBlank, Help, ToggleOff, Toggle
4
4
  import { defaultTheme } from '../../../../theme/defaultTheme';
5
5
  import { Autocomplete, Checkbox, FormControl, FormControlLabel, FormHelperText, IconButton, Switch, TextField, Tooltip, Typography, } from '../../../core';
6
6
  import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
7
- const descriptionStyles = {
8
- color: '#999 !important',
9
- whiteSpace: 'normal',
10
- paddingBottom: '4px',
11
- marginX: 0,
7
+ import { Box } from '../../../layout';
8
+ const styles = {
9
+ descriptionStyles: { color: '#999 !important', whiteSpace: 'normal', paddingBottom: '4px', marginX: 0 },
10
+ checkboxIconBox: {
11
+ backgroundColor: 'white',
12
+ width: '18px',
13
+ height: '18px',
14
+ display: 'flex',
15
+ alignItems: 'center',
16
+ justifyContent: 'center',
17
+ },
12
18
  };
13
19
  const BooleanSelect = (props) => {
14
20
  const { id, property, defaultValue, error, errorMessage, readOnly, size, displayOption, label, strictlyTrue, tooltip, description, placeholder, onBlur, additionalProps, } = props;
@@ -31,7 +37,7 @@ const BooleanSelect = (props) => {
31
37
  },
32
38
  ];
33
39
  const descriptionComponent = () => {
34
- return (description && (React.createElement(FormHelperText, { sx: descriptionStyles, component: Typography }, parse(description))));
40
+ return (description && (React.createElement(FormHelperText, { sx: styles.descriptionStyles, component: Typography }, parse(description))));
35
41
  };
36
42
  const labelComponent = () => {
37
43
  return (React.createElement(Typography, { component: "span", variant: "body2", sx: { wordWrap: 'break-word', ...defaultTheme.typography.body2 } },
@@ -58,7 +64,9 @@ const BooleanSelect = (props) => {
58
64
  return displayOption === 'dropdown' ? (React.createElement(Autocomplete, { renderInput: (params) => (React.createElement(TextField, { ...params, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, sx: { background: 'white' }, placeholder: placeholder, size: size ?? 'medium' })), value: booleanOptions.find((opt) => opt.value === value) ?? '', onChange: (e, selectedValue) => handleChange(selectedValue.value), isOptionEqualToValue: (option, val) => option?.value === val?.value, options: booleanOptions, disableClearable: true, sx: { background: 'white', borderRadius: '8px' }, ...(additionalProps ?? {}), sortBy: "NONE", required: strictlyTrue })) : (React.createElement(FormControl, { required: strictlyTrue, error: error, fullWidth: true },
59
65
  React.createElement(FormControlLabel, { labelPlacement: "end", label: labelComponent(), sx: { marginLeft: '-8px' }, control: displayOption === 'switch' ? (React.createElement(Switch, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', name: property.id, checked: value, onChange: (e) => handleChange(e.target.checked), sx: {
60
66
  alignSelf: 'start',
61
- }, ...(additionalProps ?? {}) })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), sx: {
67
+ }, ...(additionalProps ?? {}) })) : (React.createElement(Checkbox, { id: id, "aria-required": strictlyTrue, "aria-invalid": error, size: size ?? 'medium', checked: value, name: property.id, onChange: (e) => handleChange(e.target.checked), icon: React.createElement(Box, { sx: styles.checkboxIconBox },
68
+ React.createElement(CheckBoxOutlineBlank, { fontSize: size ?? 'medium' })), checkedIcon: React.createElement(Box, { sx: styles.checkboxIconBox },
69
+ React.createElement(CheckBoxIcon, { fontSize: size ?? 'medium' })), sx: {
62
70
  alignSelf: 'start',
63
71
  padding: '4px 9px 9px 9px',
64
72
  }, ...(additionalProps ?? {}) })) }),
@@ -56,7 +56,6 @@ const InputFieldComponent = (props) => {
56
56
  : property.enum, onChange: handleSelectChange, renderInput: (params) => (React.createElement(TextField, { ...params, value: value, error: error, errorMessage: errorMessage, fullWidth: true, onBlur: onBlur, size: size ?? 'medium', placeholder: placeholder })), disableClearable: true, value: value, isOptionEqualToValue: (option, value) => {
57
57
  return option.value === value;
58
58
  }, error: error, required: required, inputValue: inputValue ?? '', onInputChange: handleInputValueChange, ...(additionalProps ?? {}) })) : !mask || isValueProtected ? (React.createElement(TextField, { id: id, sx: {
59
- background: 'white',
60
59
  borderRadius: '8px',
61
60
  ...(readOnly && {
62
61
  '& .MuiOutlinedInput-notchedOutline': {
@@ -67,6 +66,7 @@ const InputFieldComponent = (props) => {
67
66
  backgroundColor: '#f4f6f8',
68
67
  },
69
68
  }),
69
+ backgroundColor: 'white',
70
70
  }, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, endAdornment, readOnly: readOnly }, required: required, fullWidth: true, onBlur: onBlur, placeholder: readOnly ? undefined : placeholder, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })) : (React.createElement(InputMask, { mask: mask, maskChar: inputMaskPlaceholderChar ?? '_', value: value, onChange: !readOnly ? handleChange : undefined, onBlur: onBlur, alwaysShowMask: true }, (() => (React.createElement(TextField, { id: id, sx: readOnly
71
71
  ? {
72
72
  '& .MuiOutlinedInput-notchedOutline': {
@@ -161,7 +161,7 @@ const Select = (props) => {
161
161
  React.createElement(Typography, { variant: "caption" }, "Clear Selection"))))) : (React.createElement(Autocomplete, { multiple: property?.type === 'array', id: id, sortBy: sortBy, renderInput: (params) => (React.createElement(TextField, { ...params, value: value, fullWidth: true, onBlur: onBlur, inputProps: {
162
162
  ...params.inputProps,
163
163
  'aria-describedby': isCombobox ? `${id}-instructions` : undefined,
164
- } })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, filterOptions: (options, params) => {
164
+ }, sx: { backgroundColor: 'white', borderRadius: '8px' } })), value: value ?? (property?.type === 'array' ? [] : undefined), onChange: handleChange, options: selectOptions ?? property?.enum ?? [], inputValue: inputValue ?? '', error: error, errorMessage: errorMessage, required: required, onInputChange: handleInputValueChange, size: size, filterOptions: (options, params) => {
165
165
  const filtered = filter(options, params);
166
166
  const { inputValue } = params;
167
167
  // Suggest to the user to add a new value.
@@ -26,6 +26,7 @@ export type FormRendererProps = BaseProps & {
26
26
  };
27
27
  renderHeader?: (props: HeaderProps) => React.ReactNode;
28
28
  renderBody?: (props: BodyProps) => React.ReactNode;
29
+ readOnly?: boolean;
29
30
  renderFooter?: (props: FooterProps) => React.ReactNode;
30
31
  };
31
32
  export declare const FormRenderer: ((props: FormRendererProps) => React.JSX.Element) & {
@@ -14,7 +14,7 @@ import { assignIdsToSectionsAndRichText, convertPropertiesToParams, entryIsVisib
14
14
  import { handleValidation } from './components/ValidationFiles/Validation';
15
15
  import ValidationErrors from './components/ValidationFiles/ValidationErrors';
16
16
  const FormRendererInternal = (props) => {
17
- const { onSubmit, onDiscardChanges, onSubmitError: onSubmitErrorOverride, value, hideTitle = false, fieldHeight, richTextEditor, form, instance, onChange, onAutosave, associatedObject, renderHeader, renderBody, renderFooter, } = props;
17
+ const { onSubmit, onDiscardChanges, onSubmitError: onSubmitErrorOverride, value, hideTitle = false, fieldHeight, richTextEditor, form, instance, onChange, onAutosave, associatedObject, renderHeader, renderBody, renderFooter, readOnly, } = props;
18
18
  const { entries, name: title, objectId, actionId, display } = form;
19
19
  const { register, unregister, setValue, reset, handleSubmit, formState: { errors, isSubmitted }, getValues, } = useForm({
20
20
  defaultValues: value,
@@ -54,17 +54,17 @@ const FormRendererInternal = (props) => {
54
54
  enabled: !!objectId,
55
55
  });
56
56
  const updatedEntries = useMemo(() => {
57
- return object ? assignIdsToSectionsAndRichText(entries, object, parameters) : [];
57
+ return assignIdsToSectionsAndRichText(entries, object, parameters);
58
58
  }, [entries, object, parameters]);
59
59
  useEffect(() => {
60
- if (!object)
60
+ if (objectId && !object)
61
61
  return;
62
- const action = object.actions?.find((a) => a.id === actionId);
62
+ const action = object?.actions?.find((a) => a.id === actionId);
63
63
  setAction(action);
64
64
  // if forms action is synced with object properties then convertPropertiesToParams
65
- setParameters(action?.parameters ?? convertPropertiesToParams(object));
65
+ setParameters(action?.parameters ?? (object ? convertPropertiesToParams(object) : []));
66
66
  setIsInitializing(false);
67
- }, [object, actionId]);
67
+ }, [object, actionId, objectId]);
68
68
  useEffect(() => {
69
69
  const currentValues = getValues();
70
70
  if (value) {
@@ -221,6 +221,7 @@ const FormRendererInternal = (props) => {
221
221
  onCollapseAll: handleCollapseAll,
222
222
  expandedSections,
223
223
  hasAccordions: hasSections && isSmallerThanMd,
224
+ readOnly,
224
225
  })) : (React.createElement(Body, { ...{
225
226
  isInitializing,
226
227
  entries: updatedEntries,
@@ -230,8 +231,12 @@ const FormRendererInternal = (props) => {
230
231
  onCollapseAll: handleCollapseAll,
231
232
  expandedSections,
232
233
  hasAccordions: hasSections && isSmallerThanMd,
234
+ readOnly,
233
235
  } })),
234
- action && onSubmit && (renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
236
+ readOnly !== true &&
237
+ action &&
238
+ onSubmit &&
239
+ (renderFooter ? renderFooter(footerProps) : React.createElement(Footer, { ...footerProps }))))));
235
240
  };
236
241
  export const FormRenderer = Object.assign(function FormRenderer(props) {
237
242
  return (React.createElement(ConditionalQueryClientProvider, null,
@@ -24,6 +24,7 @@ export type FormRendererContainerProps = BaseProps & {
24
24
  };
25
25
  display?: {
26
26
  fieldHeight?: 'small' | 'medium';
27
+ readOnly?: boolean;
27
28
  };
28
29
  actionId?: string;
29
30
  objectId: string;
@@ -1,6 +1,7 @@
1
1
  import { useApiServices, useApp, useAuthenticationContext, useNavigate, useObject, } from '@evoke-platform/context';
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
3
  import axios from 'axios';
4
+ import { DepGraph } from 'dependency-graph';
4
5
  import { cloneDeep, get, isArray, isEmpty, isEqual, isObject, pick, set } from 'lodash';
5
6
  import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
6
7
  import { Skeleton, Snackbar } from '../../core';
@@ -9,9 +10,8 @@ import ErrorComponent from '../ErrorComponent';
9
10
  import ConditionalQueryClientProvider from './components/ConditionalQueryClientProvider';
10
11
  import { evalDefaultVals, processValueUpdate } from './components/DefaultValues';
11
12
  import Header from './components/Header';
12
- import { convertPropertiesToParams, createFileLinks, deleteDocuments, encodePageSlug, extractAllCriteria, extractPresetValuesFromCriteria, extractPresetValuesFromDynamicDefaultValues, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, handleFileUpload, isAddressProperty, isEmptyWithDefault, plainTextToRtf, useFormById, } from './components/utils';
13
+ import { convertPropertiesToParams, createFileLinks, deleteDocuments, encodePageSlug, extractAllCriteria, extractPresetValuesFromCriteria, extractPresetValuesFromDynamicDefaultValues, formatSubmission, getEntryId, getPrefixedUrl, getUnnestedEntries, handleFileUpload, getVisibleEditableFieldIds, isAddressProperty, isEmptyWithDefault, plainTextToRtf, useFormById, } from './components/utils';
13
14
  import FormRenderer from './FormRenderer';
14
- import { DepGraph } from 'dependency-graph';
15
15
  // Wrapper to provide QueryClient context for FormRendererContainer if this is not a nested form
16
16
  function FormRendererContainer(props) {
17
17
  return (React.createElement(ConditionalQueryClientProvider, null,
@@ -110,10 +110,16 @@ function FormRendererContainerInner(props) {
110
110
  setError(code ?? err);
111
111
  };
112
112
  const { data: sanitizedObject, error: sanitizedObjectError } = useQuery({
113
- queryKey: [form?.objectId ?? objectId, ...(instanceId ? [instanceId] : []), 'sanitized'],
113
+ queryKey: [
114
+ form?.objectId ?? objectId,
115
+ ...(instanceId ? [instanceId] : []),
116
+ display?.readOnly ? 'unsanitized' : 'sanitized',
117
+ ],
114
118
  queryFn: () =>
115
119
  // form?.objectId is needed for subtype forms to get the correct object
116
- apiServices.get(getPrefixedUrl(`/objects/${form?.objectId ?? objectId}${instanceId ? `/instances/${instanceId}/object` : '/effective'}`), { params: { sanitizedVersion: true } }),
120
+ apiServices.get(getPrefixedUrl(`/objects/${form?.objectId ?? objectId}${instanceId ? `/instances/${instanceId}/object` : '/effective'}`),
121
+ // when the form is readonly the action is still needed, so we need to get the unsanitized object
122
+ { params: { sanitizedVersion: display?.readOnly ? false : true } }),
117
123
  staleTime: Infinity,
118
124
  enabled: !!(form?.objectId || objectId),
119
125
  });
@@ -164,7 +170,11 @@ function FormRendererContainerInner(props) {
164
170
  set(result, fieldId, fieldValue);
165
171
  }
166
172
  }
167
- else if (entry.type !== 'sections' && entry.type !== 'columns' && entry.type !== 'content') {
173
+ else if (entry.type !== 'sections' &&
174
+ entry.type !== 'columns' &&
175
+ entry.type !== 'content' &&
176
+ // there cannot be formlets here since the `form` is the effective form
177
+ entry.type !== 'formlet') {
168
178
  const parameter = parameters?.find((param) => param.id === fieldId);
169
179
  if (associatedObject &&
170
180
  'propertyId' in associatedObject &&
@@ -185,9 +195,6 @@ function FormRendererContainerInner(props) {
185
195
  console.error(error);
186
196
  }
187
197
  }
188
- else if (entry.type === 'formlet') {
189
- // TODO: this should eventually fetch the formletId then get the fields and default values of those fields
190
- }
191
198
  else if (entry.type !== 'readonlyField') {
192
199
  if (isEmptyWithDefault(fieldValue, entry, result)) {
193
200
  if (fieldId && parameters && parameters.length > 0) {
@@ -227,28 +234,39 @@ function FormRendererContainerInner(props) {
227
234
  }
228
235
  }
229
236
  return result;
230
- }, [action, parameters, associatedObject, uniquePresetValues, formDataRef, apiServices, userAccount]);
237
+ }, [action, associatedObject, uniquePresetValues, formDataRef, apiServices, userAccount]);
231
238
  useEffect(() => {
232
- if (!sanitizedObject)
239
+ if (!sanitizedObject) {
233
240
  return;
234
- const allCriterias = extractAllCriteria(flattenFormEntries, parameters || []);
235
- const uniquePresetValues = new Set();
236
- for (const criteria of allCriterias) {
237
- const presetValues = extractPresetValuesFromCriteria(criteria);
238
- presetValues.forEach((value) => uniquePresetValues.add(value));
239
241
  }
240
- extractPresetValuesFromDynamicDefaultValues(flattenFormEntries).map((value) => uniquePresetValues.add(value));
241
- setUniquePresetValues(Array.from(uniquePresetValues));
242
242
  const action = sanitizedObject.actions?.find((a) => a.id === (form?.actionId || actionId));
243
243
  if (action && (instanceId || action.type === 'create')) {
244
244
  setAction(action);
245
- // Clear error if action is found after being missing
246
245
  setError((prevError) => (prevError === 'Action could not be found' ? undefined : prevError));
246
+ const getParamsFromObject = !action.parameters;
247
+ const parameters = (getParamsFromObject ? convertPropertiesToParams(sanitizedObject) : action.parameters) ?? [];
248
+ setParameters(parameters.filter((param) => param.type !== 'collection' && !param.formula));
247
249
  }
248
250
  else {
249
251
  setError('Action could not be found');
252
+ setAction(undefined);
253
+ setParameters([]);
254
+ }
255
+ }, [sanitizedObject, actionId, form?.actionId, instanceId]);
256
+ useEffect(() => {
257
+ if (!flattenFormEntries.length || !parameters.length) {
258
+ setUniquePresetValues([]);
259
+ return;
250
260
  }
251
- }, [sanitizedObject, actionId, form?.actionId, instanceId, flattenFormEntries, parameters]);
261
+ const allCriterias = extractAllCriteria(flattenFormEntries, parameters);
262
+ const uniquePresetValues = new Set();
263
+ for (const criteria of allCriterias) {
264
+ const presetValues = extractPresetValuesFromCriteria(criteria);
265
+ presetValues.forEach((value) => uniquePresetValues.add(value));
266
+ }
267
+ extractPresetValuesFromDynamicDefaultValues(flattenFormEntries).forEach((value) => uniquePresetValues.add(value));
268
+ setUniquePresetValues(Array.from(uniquePresetValues));
269
+ }, [flattenFormEntries, parameters]);
252
270
  const { data: navigationSlug } = useQuery({
253
271
  queryKey: [appId, 'navigationSlug'],
254
272
  queryFn: () => apiServices.get(getPrefixedUrl(`/apps/${appId}/pages/${encodePageSlug(pageNavigation)}`)),
@@ -293,14 +311,6 @@ function FormRendererContainerInner(props) {
293
311
  if (error)
294
312
  onError(error);
295
313
  }, [sanitizedObjectError, fetchedFormError, instanceError]);
296
- useEffect(() => {
297
- if (!form || !action)
298
- return;
299
- // If no parameters are defined, then the action is synced with object properties
300
- const getParamsFromObject = sanitizedObject && !action.parameters;
301
- const parameters = (getParamsFromObject ? convertPropertiesToParams(sanitizedObject) : action.parameters) ?? [];
302
- setParameters(parameters.filter((param) => param.type !== 'collection' && !param.formula));
303
- }, [form, action?.parameters, sanitizedObject]);
304
314
  useEffect(() => {
305
315
  const getInitialValues = async () => {
306
316
  if (flattenFormEntries.length && (instance || !instanceId)) {
@@ -309,6 +319,10 @@ function FormRendererContainerInner(props) {
309
319
  // Deep clone to avoid reference issues
310
320
  setLastSavedData(cloneDeep(defaultValues));
311
321
  }
322
+ else {
323
+ // if there is a form with no entries
324
+ setFormData({});
325
+ }
312
326
  };
313
327
  getInitialValues();
314
328
  }, [instanceId, instance, flattenFormEntries, getDefaultValues]);
@@ -394,7 +408,7 @@ function FormRendererContainerInner(props) {
394
408
  }
395
409
  else if (instanceId && action) {
396
410
  let response = undefined;
397
- if ((await objectStore.get()).rootObjectId === 'sys__file') {
411
+ if ((await objectStore.get()).rootObjectId === 'sys__file' && action.type !== 'delete') {
398
412
  response = await handleFileUpload(apiServices, submission, action.id, objectId, instanceId);
399
413
  }
400
414
  else {
@@ -447,6 +461,12 @@ function FormRendererContainerInner(props) {
447
461
  if (!form?.autosaveActionId || !formDataRef.current) {
448
462
  return;
449
463
  }
464
+ const visibleEditableFieldIds = getVisibleEditableFieldIds(form.entries ?? [], instance, formDataRef.current);
465
+ const allowedParameterIds = parameters?.filter((parameter) => parameter.type !== 'collection').map((parameter) => parameter.id) ?? [];
466
+ const autosaveFieldIds = visibleEditableFieldIds.filter((id) => allowedParameterIds.includes(id));
467
+ if (!autosaveFieldIds.includes(fieldId)) {
468
+ return;
469
+ }
450
470
  const currentValue = get(formDataRef.current, fieldId);
451
471
  const lastValue = get(lastSavedData, fieldId);
452
472
  if (isEqual(currentValue, lastValue)) {
@@ -455,7 +475,8 @@ function FormRendererContainerInner(props) {
455
475
  try {
456
476
  setIsSaving(true);
457
477
  const cleanedData = removeUneditedProtectedValues(formDataRef.current);
458
- const submission = await formatSubmission(cleanedData, apiServices, objectId, instanceId, form, setSnackbarError, undefined, parameters);
478
+ const scopedData = pick(cleanedData, autosaveFieldIds);
479
+ const submission = await formatSubmission(scopedData, apiServices, objectId, instanceId, form, setSnackbarError, undefined, parameters);
459
480
  // Handle object instance autosave
460
481
  if (instanceId && action?.type === 'update') {
461
482
  const pickedSubmission = pick(submission, sanitizedObject?.properties
@@ -508,7 +529,7 @@ function FormRendererContainerInner(props) {
508
529
  setFormData(newData);
509
530
  }
510
531
  }
511
- const isLoading = (instanceId && !formDataRef.current) || !form || !sanitizedObject;
532
+ const isLoading = !form || !sanitizedObject || (instanceId && formDataRef.current === undefined);
512
533
  const status = error ? 'error' : isLoading ? 'loading' : 'ready';
513
534
  // Compose a header renderer that injects the saving indicator into the rendered header
514
535
  const composedRenderHeader = (props) => {
@@ -532,7 +553,7 @@ function FormRendererContainerInner(props) {
532
553
  border: !isLoading ? '1px solid #dbe0e4' : undefined,
533
554
  ...sx,
534
555
  } }, !isLoading ? (React.createElement(React.Fragment, null,
535
- React.createElement(FormRenderer, { onSubmit: onSubmit ? async (data) => await onSubmit(data, saveHandler) : saveHandler, onSubmitError: onSubmitError, onDiscardChanges: onDiscardChanges, richTextEditor: richTextEditor, hideTitle: title?.hidden, fieldHeight: display?.fieldHeight ?? 'medium', value: formDataRef.current, form: form, instance: instance, onChange: onChange, onAutosave: onAutosave, associatedObject: associatedObject && 'propertyId' in associatedObject ? associatedObject : undefined, renderHeader: composedRenderHeader, renderBody: renderBody, renderFooter: renderFooter }))) : (React.createElement(Box, { sx: { padding: '20px' } },
556
+ React.createElement(FormRenderer, { onSubmit: onSubmit ? async (data) => await onSubmit(data, saveHandler) : saveHandler, onSubmitError: onSubmitError, onDiscardChanges: onDiscardChanges, richTextEditor: richTextEditor, hideTitle: title?.hidden, fieldHeight: display?.fieldHeight ?? 'medium', value: formDataRef.current, form: form, instance: instance, onChange: onChange, onAutosave: onAutosave, associatedObject: associatedObject && 'propertyId' in associatedObject ? associatedObject : undefined, renderHeader: composedRenderHeader, renderBody: renderBody, renderFooter: renderFooter, readOnly: display?.readOnly ?? false }))) : (React.createElement(Box, { sx: { padding: '20px' } },
536
557
  React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
537
558
  React.createElement(Skeleton, { width: '78%', sx: { borderRadius: '8px', height: '40px' } }),
538
559
  React.createElement(Skeleton, { width: '20%', sx: { borderRadius: '8px', height: '40px' } })),
@@ -13,6 +13,7 @@ export type BodyProps = {
13
13
  onExpandAll?: () => void;
14
14
  onCollapseAll?: () => void;
15
15
  sx?: SxProps;
16
+ readOnly?: boolean;
16
17
  };
17
18
  export declare const Body: React.FC<BodyProps>;
18
19
  export default Body;
@@ -3,9 +3,11 @@ import { FormContext } from '../..';
3
3
  import useWidgetSize from '../../../../theme/hooks';
4
4
  import { Skeleton } from '../../../core';
5
5
  import Box from '../../../layout/Box/Box';
6
+ import ViewDetailsEntryRenderer from '../../ViewDetailsV2/InstanceEntryRenderer';
6
7
  import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
8
+ import { convertToReadOnly } from './utils';
7
9
  export const Body = (props) => {
8
- const { entries, isInitializing, sx } = props;
10
+ const { entries, isInitializing, sx, readOnly } = props;
9
11
  const { width } = useContext(FormContext);
10
12
  const { breakpoints } = useWidgetSize({
11
13
  scroll: false,
@@ -22,6 +24,6 @@ export const Body = (props) => {
22
24
  React.createElement(Skeleton, { width: '32%', sx: { borderRadius: '8px', height: '40px' } })),
23
25
  React.createElement(Box, { display: 'flex', width: '100%', justifyContent: 'space-between' },
24
26
  React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } }),
25
- React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))) : (React.createElement(Box, { sx: { paddingX: isSm || isXs ? 2 : 3, paddingY: isSm || isXs ? '6px' : '14px', ...sx } }, entries.map((entry, index) => (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry })))))));
27
+ React.createElement(Skeleton, { width: '49%', sx: { borderRadius: '8px', height: '40px' } })))) : (React.createElement(Box, { sx: { paddingX: isSm || isXs ? 2 : 3, paddingY: isSm || isXs ? '6px' : '14px', ...sx } }, entries.map((entry, index) => readOnly === true ? (React.createElement(ViewDetailsEntryRenderer, { key: index, entry: convertToReadOnly(entry) })) : (React.createElement(RecursiveEntryRenderer, { key: index, entry: entry })))))));
26
28
  };
27
29
  export default Body;
@@ -85,6 +85,9 @@ export const DropdownRepeatableFieldInput = (props) => {
85
85
  : undefined),
86
86
  }, onChange: (event) => {
87
87
  setSearchValue(event.target.value);
88
+ }, sx: {
89
+ backgroundColor: 'white',
90
+ borderRadius: '8px',
88
91
  } })),
89
92
  loading,
90
93
  sortBy: 'NONE',
@@ -1,6 +1,6 @@
1
1
  import { useApiServices, useNotification, } from '@evoke-platform/context';
2
2
  import { useQuery, useQueryClient } from '@tanstack/react-query';
3
- import { get, omit, startCase } from 'lodash';
3
+ import { get, pick, startCase } from 'lodash';
4
4
  import { DateTime } from 'luxon';
5
5
  import React, { useCallback, useEffect, useMemo, useState } from 'react';
6
6
  import sift from 'sift';
@@ -308,10 +308,12 @@ const RepeatableField = (props) => {
308
308
  const relatedObjectId = relatedObject?.id;
309
309
  try {
310
310
  let response = undefined;
311
- const submission = omit(input, relatedObject?.properties
312
- ?.filter((property) => property.formula || property.type === 'collection')
313
- .map((property) => property.id) ?? []);
314
- if (relatedObject?.rootObjectId === 'sys__file' && action?.id) {
311
+ const actionParameters = action?.parameters ?? (relatedObject ? convertPropertiesToParams(relatedObject) : undefined) ?? [];
312
+ const allowedFieldIds = actionParameters
313
+ .filter((param) => param.type !== 'collection')
314
+ .map((param) => param.id);
315
+ const submission = pick(input, allowedFieldIds);
316
+ if (relatedObject?.rootObjectId === 'sys__file' && action?.id && action?.type !== 'delete') {
315
317
  response = await handleFileUpload(apiServices, submission, action.id, relatedObjectId, selectedInstanceId);
316
318
  }
317
319
  else {
@@ -472,7 +474,7 @@ const RepeatableField = (props) => {
472
474
  React.createElement(TableRow, null,
473
475
  columns?.map((prop) => (React.createElement(TableCell, { sx: styles.tableCell }, prop.name))),
474
476
  canUpdateProperty && React.createElement(TableCell, { sx: { ...styles.tableCell, width: '80px' } }))),
475
- React.createElement(TableBody, null, relatedInstances?.map((relatedInstance, index) => (React.createElement(TableRow, { key: relatedInstance.id },
477
+ React.createElement(TableBody, { sx: { backgroundColor: 'white' } }, relatedInstances?.map((relatedInstance, index) => (React.createElement(TableRow, { key: relatedInstance.id },
476
478
  columns?.map((prop) => {
477
479
  return (React.createElement(TableCell, { sx: { fontSize: '16px' } }, prop.type === 'document' ? (React.createElement(DocumentViewerCell, { instance: relatedInstance, propertyId: prop.id, setSnackbarError: setSnackbarError })) : (React.createElement(Typography, { key: prop.id, sx: prop.id === 'name'
478
480
  ? {
@@ -126,6 +126,7 @@ export const Document = (props) => {
126
126
  border: `1px dashed ${error ? 'red' : uploadDisabled ? '#DFE3E8' : '#858585'}`,
127
127
  position: 'relative',
128
128
  cursor: uploadDisabled ? 'cursor' : 'pointer',
129
+ backgroundColor: 'white',
129
130
  }, ...getRootProps(), onClick: open },
130
131
  React.createElement("input", { ...getInputProps({ id }), disabled: uploadDisabled, ...(hasDescription ? { 'aria-describedby': `${id}-description` } : undefined) }),
131
132
  React.createElement(Grid, { container: true, sx: { width: '100%' } },
@@ -29,6 +29,7 @@ const styles = {
29
29
  border: '1px dashed #858585',
30
30
  position: 'relative',
31
31
  cursor: 'pointer',
32
+ backgroundColor: 'white',
32
33
  },
33
34
  icon: {
34
35
  color: '#fff',
@@ -141,6 +141,8 @@ const UserProperty = (props) => {
141
141
  },
142
142
  }
143
143
  : {}),
144
+ backgroundColor: 'white',
145
+ borderRadius: '8px',
144
146
  } })), size: fieldHeight ?? 'medium', readOnly: readOnly, error: error })));
145
147
  };
146
148
  export default UserProperty;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { FormletReference } from '@evoke-platform/context';
3
+ declare function FormletRenderer(props: {
4
+ entry: FormletReference;
5
+ readOnly?: boolean;
6
+ }): React.JSX.Element;
7
+ export default FormletRenderer;
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { useApiServices } from '@evoke-platform/context';
3
+ import { convertToReadOnly, getPrefixedUrl } from './utils';
4
+ import { useQuery } from '@tanstack/react-query';
5
+ import { RecursiveEntryRenderer } from './RecursiveEntryRenderer';
6
+ import { Skeleton } from '../../../core';
7
+ import ViewDetailsEntryRenderer from '../../ViewDetailsV2/InstanceEntryRenderer';
8
+ import MisconfiguredErrorMessage from './MisconfiguredErrorMessage';
9
+ function FormletRenderer(props) {
10
+ const { entry, readOnly } = props;
11
+ const apiServices = useApiServices();
12
+ const { data: formlet, isLoading } = useQuery({
13
+ queryKey: [entry.formletId, 'formlet'],
14
+ queryFn: () => apiServices.get(getPrefixedUrl(`/formlets/${entry.formletId}`)),
15
+ staleTime: Infinity,
16
+ enabled: !!entry.formletId,
17
+ });
18
+ if (isLoading)
19
+ return React.createElement(Skeleton, null);
20
+ return formlet ? (React.createElement(React.Fragment, null, formlet.entries?.map((formletEntry, index) => readOnly === true ? (React.createElement(ViewDetailsEntryRenderer, { key: index, entry: convertToReadOnly(formletEntry) })) : (React.createElement(RecursiveEntryRenderer, { key: index, entry: formletEntry }))))) : (React.createElement(MisconfiguredErrorMessage, null));
21
+ }
22
+ export default FormletRenderer;
@@ -33,14 +33,21 @@ const HtmlView = ({ value }) => {
33
33
  quillRef.current.setContents([]);
34
34
  quillRef.current.clipboard.dangerouslyPasteHTML(DOMPurify.sanitize(value));
35
35
  }, [value]);
36
- return (React.createElement(Box, { sx: {
37
- width: '100%',
38
- height: '100%',
39
- '.ql-container.ql-snow': {
40
- border: 'none',
41
- minHeight: 20,
42
- },
43
- } },
44
- React.createElement("div", { ref: containerRef })));
36
+ return (
37
+ // Needs to be wrapped in a Box to prevent quill from setting height: 100% on the container, which causes it to overflow its parent and ignore the maxHeight we set on the containerRef (only happens when in a grid item)
38
+ React.createElement(Box, null,
39
+ React.createElement(Box, { sx: {
40
+ width: '100%',
41
+ height: '100%',
42
+ '.ql-container.ql-snow': {
43
+ border: 'none',
44
+ minHeight: 20,
45
+ },
46
+ // Override Quill list markers for correct display
47
+ '.ql-editor ol li:before': {
48
+ content: '""',
49
+ },
50
+ } },
51
+ React.createElement("div", { ref: containerRef }))));
45
52
  };
46
53
  export default HtmlView;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export default function MisconfiguredErrorMessage(): React.JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { WarningRounded } from '../../../../icons';
2
+ import { Typography } from '../../../core';
3
+ import { Box } from '../../../layout';
4
+ import React from 'react';
5
+ export default function MisconfiguredErrorMessage() {
6
+ return (React.createElement(Box, { sx: {
7
+ display: 'flex',
8
+ backgroundColor: '#ffc1073b',
9
+ borderRadius: '8px',
10
+ padding: '16.5px 14px',
11
+ marginTop: '6px',
12
+ } },
13
+ React.createElement(WarningRounded, { sx: { paddingRight: '8px' }, color: "warning" }),
14
+ React.createElement(Typography, { variant: "body2", color: "textSecondary" }, "This field was not configured correctly")));
15
+ }