@evoke-platform/ui-components 1.8.0-testing.9 → 1.8.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.
- package/dist/published/components/custom/DataGrid/DataGrid.d.ts +1 -0
- package/dist/published/components/custom/DataGrid/DataGrid.js +2 -1
- package/dist/published/components/custom/DataGrid/Toolbar.d.ts +1 -0
- package/dist/published/components/custom/DataGrid/Toolbar.js +3 -2
- package/dist/published/components/custom/DataGrid/index.d.ts +1 -0
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js +47 -40
- package/dist/published/components/custom/Form/FormComponents/ObjectComponent/RelatedObjectInstance.js +1 -1
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.d.ts +2 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/ActionDialog.js +2 -2
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.d.ts +2 -0
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +2 -2
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.d.ts +1 -1
- package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.js +18 -6
- package/dist/published/components/custom/Form/tests/Form.test.js +192 -2
- package/dist/published/components/custom/Form/tests/test-data.d.ts +7 -0
- package/dist/published/components/custom/Form/tests/test-data.js +138 -0
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -2
- package/dist/published/components/custom/FormV2/FormRendererContainer.d.ts +3 -2
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +1 -1
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +1 -2
- package/dist/published/components/custom/FormV2/components/utils.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/utils.js +1 -1
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +173 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +96 -0
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +16 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +394 -0
- package/dist/published/components/custom/index.d.ts +1 -0
- package/dist/published/index.d.ts +1 -1
- package/dist/published/stories/FormRenderer.stories.d.ts +7 -0
- package/dist/published/stories/FormRenderer.stories.js +65 -0
- package/dist/published/stories/FormRendererContainer.stories.d.ts +7 -0
- package/dist/published/stories/FormRendererContainer.stories.js +56 -0
- package/dist/published/stories/FormRendererData.d.ts +116 -0
- package/dist/published/stories/FormRendererData.js +925 -0
- package/dist/published/stories/sharedMswHandlers.d.ts +1 -0
- package/dist/published/stories/sharedMswHandlers.js +100 -0
- package/package.json +10 -4
|
@@ -23,6 +23,7 @@ export type DataGridProps<T extends GridValidRowModel> = MuiDataGridProps<T> & {
|
|
|
23
23
|
hideSearchbar?: boolean;
|
|
24
24
|
loadingOptions?: LoadingOptions;
|
|
25
25
|
hideDownload?: boolean;
|
|
26
|
+
exportFileName?: string;
|
|
26
27
|
};
|
|
27
28
|
export default function <T extends GridValidRowModel>(props: DataGridProps<T>): React.JSX.Element;
|
|
28
29
|
export {};
|
|
@@ -6,7 +6,7 @@ import LinearProgress from '../../core/LinearProgress';
|
|
|
6
6
|
import { dateTimeBetweenOperator } from './DateTimeCustomOperator';
|
|
7
7
|
import Toolbar from './Toolbar';
|
|
8
8
|
export default function (props) {
|
|
9
|
-
const { onRefresh, loading, theme, title, bulkAction, filterSettings, columns, rows, hideSearchbar, loadingOptions, hideDownload, ...rest } = props;
|
|
9
|
+
const { onRefresh, loading, theme, title, bulkAction, filterSettings, columns, rows, hideSearchbar, loadingOptions, hideDownload, exportFileName, ...rest } = props;
|
|
10
10
|
const [anchorEl, setAnchorEl] = useState();
|
|
11
11
|
const [loadingMessageIndex, setLoadingMessageIndex] = useState(0);
|
|
12
12
|
const addColumnFilterOperators = (columns) => {
|
|
@@ -93,6 +93,7 @@ export default function (props) {
|
|
|
93
93
|
bulkAction,
|
|
94
94
|
hideSearchbar,
|
|
95
95
|
hideDownload,
|
|
96
|
+
exportFileName,
|
|
96
97
|
},
|
|
97
98
|
panel: {
|
|
98
99
|
anchorEl: anchorEl,
|
|
@@ -11,6 +11,7 @@ export type GridToolbarProps = MuiGridToolbarProps & {
|
|
|
11
11
|
bulkAction?: BulkAction;
|
|
12
12
|
hideSearchbar?: boolean;
|
|
13
13
|
hideDownload?: boolean;
|
|
14
|
+
exportFileName?: string;
|
|
14
15
|
};
|
|
15
16
|
declare function Toolbar(props: GridToolbarProps): React.JSX.Element;
|
|
16
17
|
export default Toolbar;
|
|
@@ -6,7 +6,8 @@ import UIThemeProvider from '../../../theme';
|
|
|
6
6
|
import { Button, IconButton } from '../../core';
|
|
7
7
|
import { Grid } from '../../layout';
|
|
8
8
|
function Toolbar(props) {
|
|
9
|
-
const { onRefresh, setAnchorEl, loading, theme, title, bulkAction, hideSearchbar, hideDownload } = props;
|
|
9
|
+
const { onRefresh, setAnchorEl, loading, theme, title, bulkAction, hideSearchbar, hideDownload, exportFileName } = props;
|
|
10
|
+
const resolvedFileName = exportFileName ?? 'data';
|
|
10
11
|
const styles = {
|
|
11
12
|
container: { display: 'flex', justifyContent: 'space-between', margin: '0 0 15px 0' },
|
|
12
13
|
iconButton: {
|
|
@@ -57,7 +58,7 @@ function Toolbar(props) {
|
|
|
57
58
|
'&:hover': { backgroundColor: '#f2f3f5' },
|
|
58
59
|
paddingLeft: '10px',
|
|
59
60
|
paddingRight: '10px',
|
|
60
|
-
}, printOptions: { disableToolbarButton: true }, startIcon: React.createElement(FileDownloadRounded, null) }))),
|
|
61
|
+
}, printOptions: { disableToolbarButton: true }, csvOptions: { fileName: resolvedFileName }, startIcon: React.createElement(FileDownloadRounded, null) }))),
|
|
61
62
|
React.createElement(Grid, { item: true },
|
|
62
63
|
React.createElement(GridToolbarFilterButton, { componentsProps: {
|
|
63
64
|
button: {
|
package/dist/published/components/custom/Form/FormComponents/ObjectComponent/ObjectPropertyInput.js
CHANGED
|
@@ -3,7 +3,7 @@ import { cloneDeep, debounce, isEmpty, isNil } from 'lodash';
|
|
|
3
3
|
import Handlebars from 'no-eval-handlebars';
|
|
4
4
|
import React, { useCallback, useEffect, useState } from 'react';
|
|
5
5
|
import { Close } from '../../../../../icons';
|
|
6
|
-
import { Autocomplete, Button, Dialog, IconButton, Link, Paper, TextField, Tooltip, Typography, } from '../../../../core';
|
|
6
|
+
import { Autocomplete, Button, Dialog, IconButton, Link, ListItem, Paper, TextField, Tooltip, Typography, } from '../../../../core';
|
|
7
7
|
import { Box } from '../../../../layout';
|
|
8
8
|
import { getPrefixedUrl, normalizeDates, transformToWhere } from '../../utils';
|
|
9
9
|
import { RelatedObjectInstance } from './RelatedObjectInstance';
|
|
@@ -146,9 +146,27 @@ export const ObjectPropertyInput = (props) => {
|
|
|
146
146
|
return instance ? template(instance) : undefined;
|
|
147
147
|
};
|
|
148
148
|
const navigationSlug = defaultPages && property.objectId && defaultPages[property.objectId];
|
|
149
|
+
const dropdownOptions = [
|
|
150
|
+
...options.map((o) => ({ label: o.name, value: o.id })),
|
|
151
|
+
...(mode !== 'existingOnly' && relatedObject?.actions?.some((a) => a.id === DEFAULT_CREATE_ACTION)
|
|
152
|
+
? [
|
|
153
|
+
{
|
|
154
|
+
value: '__new__',
|
|
155
|
+
label: '+ Add New',
|
|
156
|
+
},
|
|
157
|
+
]
|
|
158
|
+
: []),
|
|
159
|
+
];
|
|
149
160
|
return (React.createElement(React.Fragment, null,
|
|
150
161
|
displayOption === 'dropdown' ? (React.createElement(React.Fragment, null,
|
|
151
|
-
React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions,
|
|
162
|
+
React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, clearIcon: React.createElement(IconButton, { size: "small", disableRipple: true, onKeyDown: (e) => {
|
|
163
|
+
if (e.key === 'Enter') {
|
|
164
|
+
e.stopPropagation();
|
|
165
|
+
}
|
|
166
|
+
}, onClick: (e) => {
|
|
167
|
+
setOpenOptions(false);
|
|
168
|
+
}, "aria-label": "Clear selection", sx: { padding: 0 } },
|
|
169
|
+
React.createElement(Close, { sx: { fontSize: '20px' } })), componentsProps: {
|
|
152
170
|
popper: {
|
|
153
171
|
modifiers: [
|
|
154
172
|
{
|
|
@@ -163,6 +181,7 @@ export const ObjectPropertyInput = (props) => {
|
|
|
163
181
|
boxShadow: '0px 24px 48px 0px rgba(145, 158, 171, 0.2)',
|
|
164
182
|
'& .MuiAutocomplete-listbox': {
|
|
165
183
|
maxHeight: '25vh',
|
|
184
|
+
paddingBottom: '0px',
|
|
166
185
|
},
|
|
167
186
|
'& .MuiAutocomplete-noOptions': {
|
|
168
187
|
fontFamily: 'sans-serif',
|
|
@@ -176,20 +195,7 @@ export const ObjectPropertyInput = (props) => {
|
|
|
176
195
|
paddingLeft: '24px',
|
|
177
196
|
color: 'rgba(145, 158, 171, 1)',
|
|
178
197
|
},
|
|
179
|
-
} },
|
|
180
|
-
children,
|
|
181
|
-
mode !== 'existingOnly' &&
|
|
182
|
-
relatedObject?.actions?.some((a) => a.id === DEFAULT_CREATE_ACTION) && (React.createElement(Button, { fullWidth: true, sx: {
|
|
183
|
-
justifyContent: 'flex-start',
|
|
184
|
-
pl: 2,
|
|
185
|
-
minHeight: '48px',
|
|
186
|
-
borderTop: '1px solid rgba(145, 158, 171, 0.24)',
|
|
187
|
-
borderRadius: '0p 0pc 6px 6px',
|
|
188
|
-
paddingLeft: '22px',
|
|
189
|
-
fontWeight: 400,
|
|
190
|
-
}, onMouseDown: (e) => {
|
|
191
|
-
setOpenCreateDialog(true);
|
|
192
|
-
}, color: 'inherit' }, "+ Add New"))));
|
|
198
|
+
} }, children));
|
|
193
199
|
}, sx: {
|
|
194
200
|
'& button.MuiButtonBase-root': {
|
|
195
201
|
...(!loadingOptions &&
|
|
@@ -200,18 +206,14 @@ export const ObjectPropertyInput = (props) => {
|
|
|
200
206
|
: {}),
|
|
201
207
|
},
|
|
202
208
|
}, noOptionsText: 'No options available', renderOption: (props, option) => {
|
|
203
|
-
|
|
209
|
+
const isAddNew = option.value === '__new__';
|
|
210
|
+
return (React.createElement(ListItem, { ...props, key: props.id, sx: {
|
|
211
|
+
...(isAddNew ? { borderTop: '1px solid rgba(145, 158, 171, 0.24)' } : {}),
|
|
212
|
+
} },
|
|
204
213
|
React.createElement(Box, null,
|
|
205
214
|
React.createElement(Typography, { sx: { marginLeft: '8px', fontSize: '14px' } }, option.label),
|
|
206
215
|
layout?.secondaryTextExpression ? (React.createElement(Typography, { sx: { marginLeft: '8px', fontSize: '14px', color: '#637381' } }, compileExpression(layout?.secondaryTextExpression, options.find((o) => o.id === option.value)))) : null)));
|
|
207
|
-
}, onOpen: () =>
|
|
208
|
-
if (instance?.[property.id]?.id || selectedInstance?.id) {
|
|
209
|
-
setOpenOptions(false);
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
-
setOpenOptions(true);
|
|
213
|
-
}
|
|
214
|
-
}, onClose: () => setOpenOptions(false), value: instance?.[property.id]?.id || selectedInstance?.id
|
|
216
|
+
}, onOpen: () => setOpenOptions(true), onClose: () => setOpenOptions(false), value: instance?.[property.id]?.id || selectedInstance?.id
|
|
215
217
|
? {
|
|
216
218
|
value: instance?.[property.id]?.id ??
|
|
217
219
|
selectedInstance?.id ??
|
|
@@ -225,15 +227,20 @@ export const ObjectPropertyInput = (props) => {
|
|
|
225
227
|
return option.value === value;
|
|
226
228
|
}
|
|
227
229
|
return option.value === value?.value;
|
|
228
|
-
}, options: options
|
|
230
|
+
}, options: dropdownOptions, filterOptions: (options) => options, getOptionLabel: (option) => {
|
|
229
231
|
return typeof option === 'string'
|
|
230
232
|
? (options.find((o) => o.id === option)?.name ?? '')
|
|
231
233
|
: option.label;
|
|
232
|
-
},
|
|
234
|
+
}, onKeyDown: (e) => {
|
|
233
235
|
// prevents keyboard trap
|
|
234
236
|
if (e.key === 'Tab') {
|
|
235
237
|
return;
|
|
236
238
|
}
|
|
239
|
+
if (e.key === 'Enter') {
|
|
240
|
+
setOpenOptions(true);
|
|
241
|
+
setDropdownInput(undefined);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
237
244
|
if (instance?.[property.id]?.id || selectedInstance?.id) {
|
|
238
245
|
e.preventDefault();
|
|
239
246
|
}
|
|
@@ -244,31 +251,31 @@ export const ObjectPropertyInput = (props) => {
|
|
|
244
251
|
handleChangeObjectProperty(property.id, null);
|
|
245
252
|
instance[property.id] = null;
|
|
246
253
|
}
|
|
254
|
+
else if (value?.value === '__new__') {
|
|
255
|
+
setOpenCreateDialog(true);
|
|
256
|
+
}
|
|
247
257
|
else {
|
|
248
258
|
const selectedInstance = options.find((o) => o.id === value?.value);
|
|
249
259
|
setSelectedInstance(selectedInstance);
|
|
250
260
|
handleChangeObjectProperty(property.id, selectedInstance);
|
|
251
261
|
}
|
|
252
|
-
}, selectOnFocus: false, onBlur: () =>
|
|
262
|
+
}, selectOnFocus: false, onBlur: () => {
|
|
263
|
+
if (dropdownInput) {
|
|
264
|
+
getDropdownOptions();
|
|
265
|
+
}
|
|
266
|
+
}, renderInput: (params) => (React.createElement(TextField, { ...params, placeholder: 'Select', readOnly: !loadingOptions &&
|
|
253
267
|
!!(instance?.[property.id]?.id ||
|
|
254
268
|
selectedInstance?.id), onChange: (event) => setDropdownInput(event.target.value), onClick: (e) => {
|
|
255
269
|
if (navigationSlug &&
|
|
256
270
|
['DIV', 'INPUT'].includes(e.target?.nodeName) &&
|
|
257
271
|
(instance?.[property.id]?.id ||
|
|
258
272
|
selectedInstance?.id)) {
|
|
259
|
-
navigateTo
|
|
273
|
+
if (navigateTo) {
|
|
274
|
+
setOpenOptions(false);
|
|
260
275
|
navigateTo(navigationSlug.replace(':instanceId', instance?.[property.id]?.id ??
|
|
261
276
|
selectedInstance?.id ??
|
|
262
277
|
''));
|
|
263
|
-
|
|
264
|
-
if (openOptions &&
|
|
265
|
-
e.target?.nodeName === 'svg') {
|
|
266
|
-
setOpenOptions(false);
|
|
267
|
-
}
|
|
268
|
-
else if (!['DIV', 'INPUT'].includes(e.target?.nodeName) &&
|
|
269
|
-
!selectedInstance) {
|
|
270
|
-
setOptions(options);
|
|
271
|
-
setOpenOptions(true);
|
|
278
|
+
}
|
|
272
279
|
}
|
|
273
280
|
}, sx: {
|
|
274
281
|
...(!loadingOptions &&
|
|
@@ -349,8 +356,8 @@ export const ObjectPropertyInput = (props) => {
|
|
|
349
356
|
event.stopPropagation();
|
|
350
357
|
setOpenCreateDialog(true);
|
|
351
358
|
}, "aria-label": `Add`, disabled: !canUpdateProperty }, "Add")))),
|
|
352
|
-
openCreateDialog && (React.createElement(React.Fragment, null, nestedFieldsView ? (React.createElement(RelatedObjectInstance, { apiServices: apiServices, handleClose: handleClose, handleChangeObjectProperty: handleChangeObjectProperty, instance: instance, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, property: property, nestedFieldsView: nestedFieldsView, mode: mode, setSnackbarError: setSnackbarError, displayOption: displayOption, setOptions: setOptions, options: options, filter: filter, user: user, layout: layout, richTextEditor: richTextEditor })) : (React.createElement(Dialog, { fullWidth: true, maxWidth: "md", open: openCreateDialog, onClose: (e, reason) => reason !== 'backdropClick' && handleClose },
|
|
353
|
-
React.createElement(Typography, { sx: {
|
|
359
|
+
openCreateDialog && (React.createElement(React.Fragment, null, nestedFieldsView ? (React.createElement(RelatedObjectInstance, { apiServices: apiServices, handleClose: handleClose, handleChangeObjectProperty: handleChangeObjectProperty, instance: instance, setSelectedInstance: setSelectedInstance, relatedObject: relatedObject, property: property, nestedFieldsView: nestedFieldsView, mode: mode, setSnackbarError: setSnackbarError, displayOption: displayOption, setOptions: setOptions, options: options, filter: filter, user: user, layout: layout, richTextEditor: richTextEditor })) : (React.createElement(Dialog, { fullWidth: true, maxWidth: "md", open: openCreateDialog, onClose: (e, reason) => reason !== 'backdropClick' && handleClose, "aria-labelledby": "add-dialog-title" },
|
|
360
|
+
React.createElement(Typography, { id: "add-dialog-title", sx: {
|
|
354
361
|
marginTop: '28px',
|
|
355
362
|
fontSize: '22px',
|
|
356
363
|
fontWeight: 700,
|
|
@@ -5,7 +5,7 @@ import React, { useState } from 'react';
|
|
|
5
5
|
import { InfoRounded } from '../../../../../icons';
|
|
6
6
|
import { Alert, Button, FormControlLabel, Radio, RadioGroup } from '../../../../core';
|
|
7
7
|
import { Box, Grid } from '../../../../layout';
|
|
8
|
-
import { Form } from '
|
|
8
|
+
import { Form } from '../../Common/Form';
|
|
9
9
|
import { getPrefixedUrl, normalizeDateTime, normalizeDates } from '../../utils';
|
|
10
10
|
import { InstanceLookup } from './InstanceLookup';
|
|
11
11
|
const styles = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Action, ApiServices, Obj, UserAccount } from '@evoke-platform/context';
|
|
2
|
+
import { ReactComponent } from '@formio/react';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import { Address, ObjectPropertyInputProps } from '../../types';
|
|
4
5
|
export type ActionDialogProps = {
|
|
@@ -20,5 +21,6 @@ export type ActionDialogProps = {
|
|
|
20
21
|
instanceId: string;
|
|
21
22
|
propertyId: string;
|
|
22
23
|
};
|
|
24
|
+
richTextEditor?: typeof ReactComponent;
|
|
23
25
|
};
|
|
24
26
|
export declare const ActionDialog: (props: ActionDialogProps) => React.JSX.Element;
|
|
@@ -36,7 +36,7 @@ const styles = {
|
|
|
36
36
|
},
|
|
37
37
|
};
|
|
38
38
|
export const ActionDialog = (props) => {
|
|
39
|
-
const { open, onClose, action, instanceInput, handleSubmit, apiServices, object, instanceId, objectInputCommonProps, queryAddresses, associatedObject, user, } = props;
|
|
39
|
+
const { open, onClose, action, instanceInput, handleSubmit, apiServices, object, instanceId, objectInputCommonProps, queryAddresses, associatedObject, user, richTextEditor, } = props;
|
|
40
40
|
const [updatedObject, setUpdatedObject] = useState();
|
|
41
41
|
const [hasAccess, setHasAccess] = useState(false);
|
|
42
42
|
const [loading, setLoading] = useState(false);
|
|
@@ -76,7 +76,7 @@ export const ActionDialog = (props) => {
|
|
|
76
76
|
React.createElement(IconButton, { sx: styles.closeIcon, onClick: onClose },
|
|
77
77
|
React.createElement(Close, { fontSize: "small" })),
|
|
78
78
|
action && hasAccess && !loading ? action?.name : ''),
|
|
79
|
-
React.createElement(DialogContent, null, hasAccess ? (React.createElement(Box, { sx: { width: '100%', marginTop: '10px' } }, (updatedObject || isDeleteAction) && (React.createElement(Form, { actionId: action.id, actionType: action.type, apiServices: objectInputCommonProps.apiServices, object: !isDeleteAction ? updatedObject : object, instance: instanceInput, onSave: async (data, setSubmitting) => handleSubmit(action.type, data, instanceId, setSubmitting), objectInputCommonProps: objectInputCommonProps, closeModal: onClose, queryAddresses: queryAddresses, user: user, submitButtonLabel: isDeleteAction ? 'Delete' : undefined, associatedObject: associatedObject })))) : (React.createElement(React.Fragment, null, loading ? (React.createElement(React.Fragment, null,
|
|
79
|
+
React.createElement(DialogContent, null, hasAccess ? (React.createElement(Box, { sx: { width: '100%', marginTop: '10px' } }, (updatedObject || isDeleteAction) && (React.createElement(Form, { actionId: action.id, actionType: action.type, apiServices: objectInputCommonProps.apiServices, object: !isDeleteAction ? updatedObject : object, instance: instanceInput, onSave: async (data, setSubmitting) => handleSubmit(action.type, data, instanceId, setSubmitting), objectInputCommonProps: objectInputCommonProps, closeModal: onClose, queryAddresses: queryAddresses, user: user, submitButtonLabel: isDeleteAction ? 'Delete' : undefined, associatedObject: associatedObject, richTextEditor: richTextEditor })))) : (React.createElement(React.Fragment, null, loading ? (React.createElement(React.Fragment, null,
|
|
80
80
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }),
|
|
81
81
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }),
|
|
82
82
|
React.createElement(Skeleton, { height: '30px', animation: 'wave' }))) : (React.createElement(ErrorComponent, { code: 'AccessDenied', message: 'You do not have permission to perform this action.', styles: { boxShadow: 'none' } })))))));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiServices, ObjectInstance, Property, UserAccount, ViewLayoutEntityReference } from '@evoke-platform/context';
|
|
2
|
+
import { ReactComponent } from '@formio/react';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import { Address } from '../../types';
|
|
4
5
|
export type ObjectPropertyInputProps = {
|
|
@@ -9,6 +10,7 @@ export type ObjectPropertyInputProps = {
|
|
|
9
10
|
queryAddresses?: (query: string) => Promise<Address[]>;
|
|
10
11
|
user?: UserAccount;
|
|
11
12
|
viewLayout?: ViewLayoutEntityReference;
|
|
13
|
+
richTextEditor?: typeof ReactComponent;
|
|
12
14
|
};
|
|
13
15
|
declare const RepeatableField: (props: ObjectPropertyInputProps) => React.JSX.Element;
|
|
14
16
|
export default RepeatableField;
|
|
@@ -33,7 +33,7 @@ const styles = {
|
|
|
33
33
|
},
|
|
34
34
|
};
|
|
35
35
|
const RepeatableField = (props) => {
|
|
36
|
-
const { property, instance, canUpdateProperty, apiServices, queryAddresses, user, viewLayout } = props;
|
|
36
|
+
const { property, instance, canUpdateProperty, apiServices, queryAddresses, user, viewLayout, richTextEditor } = props;
|
|
37
37
|
const [relatedInstances, setRelatedInstances] = useState([]);
|
|
38
38
|
const [relatedObject, setRelatedObject] = useState();
|
|
39
39
|
const [hasCreateAction, setHasCreateAction] = useState(false);
|
|
@@ -433,7 +433,7 @@ const RepeatableField = (props) => {
|
|
|
433
433
|
objectInputCommonProps: { apiServices }, action: relatedObject?.actions?.find((a) => a.id ===
|
|
434
434
|
(dialogType === 'create' ? '_create' : dialogType === 'update' ? '_update' : '_delete')), instanceId: selectedRow, queryAddresses: queryAddresses, user: user, associatedObject: instance.id && property.relatedPropertyId
|
|
435
435
|
? { instanceId: instance.id, propertyId: property.relatedPropertyId }
|
|
436
|
-
: undefined })),
|
|
436
|
+
: undefined, richTextEditor: richTextEditor })),
|
|
437
437
|
React.createElement(Snackbar, { open: snackbarError.showAlert, handleClose: () => setSnackbarError({ isError: snackbarError.isError, showAlert: false }), message: snackbarError.message, error: snackbarError.isError })));
|
|
438
438
|
};
|
|
439
439
|
export default RepeatableField;
|
package/dist/published/components/custom/Form/FormComponents/UserComponent/UserProperty.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export type UserPropertyProps = {
|
|
|
6
6
|
property: Property;
|
|
7
7
|
apiServices: ApiServices;
|
|
8
8
|
user?: UserAccount;
|
|
9
|
-
handleChangeUserProperty: (user: AutocompleteOption) => void;
|
|
9
|
+
handleChangeUserProperty: (user: AutocompleteOption | null) => void;
|
|
10
10
|
error?: boolean;
|
|
11
11
|
setSnackbarError?: (snackbarError: {
|
|
12
12
|
showAlert: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ExpandMore } from '@mui/icons-material';
|
|
2
2
|
import React, { useEffect, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { Close } from '../../../../../icons';
|
|
4
|
+
import { Autocomplete, IconButton, Paper, TextField, Typography } from '../../../../core';
|
|
4
5
|
import { getPrefixedUrl } from '../../utils';
|
|
5
6
|
export const UserProperty = (props) => {
|
|
6
7
|
const { id, property, apiServices, handleChangeUserProperty, error, filter, value, user, fieldHeight } = props;
|
|
@@ -37,7 +38,16 @@ export const UserProperty = (props) => {
|
|
|
37
38
|
}
|
|
38
39
|
}, [property, filter]);
|
|
39
40
|
return (options && (React.createElement(React.Fragment, null,
|
|
40
|
-
React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue ? '' : React.createElement(ExpandMore, null),
|
|
41
|
+
React.createElement(Autocomplete, { id: id, fullWidth: true, open: openOptions, popupIcon: userValue ? '' : React.createElement(ExpandMore, null), clearIcon: !loadingOptions && userValue ? (React.createElement(IconButton, { size: "small", disableRipple: true, onKeyDown: (e) => {
|
|
42
|
+
if (e.key === 'Enter') {
|
|
43
|
+
e.stopPropagation();
|
|
44
|
+
}
|
|
45
|
+
}, onClick: (e) => {
|
|
46
|
+
setOpenOptions(false);
|
|
47
|
+
}, "aria-label": "Clear selection", sx: {
|
|
48
|
+
padding: 0,
|
|
49
|
+
} },
|
|
50
|
+
React.createElement(Close, { sx: { fontSize: '20px' } }))) : null, PaperComponent: ({ children }) => {
|
|
41
51
|
return (React.createElement(Paper, { sx: {
|
|
42
52
|
borderRadius: '12px',
|
|
43
53
|
boxShadow: '0px 24px 48px 0px rgba(145, 158, 171, 0.2)',
|
|
@@ -72,9 +82,7 @@ export const UserProperty = (props) => {
|
|
|
72
82
|
" ",
|
|
73
83
|
'',
|
|
74
84
|
users?.find((user) => option.value === user.id)?.status === 'Inactive' ? (React.createElement("span", null, "(Inactive)")) : (''))));
|
|
75
|
-
}, onOpen: () => {
|
|
76
|
-
setOpenOptions(true);
|
|
77
|
-
}, onClose: () => setOpenOptions(false), value: userValue ?? '', options: options, getOptionLabel: (option) => {
|
|
85
|
+
}, onOpen: () => setOpenOptions(true), onClose: () => setOpenOptions(false), value: userValue ?? '', options: options, getOptionLabel: (option) => {
|
|
78
86
|
if (typeof option === 'string') {
|
|
79
87
|
return options.find((o) => o.value === option)?.label ?? '';
|
|
80
88
|
}
|
|
@@ -88,11 +96,15 @@ export const UserProperty = (props) => {
|
|
|
88
96
|
}
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
|
-
},
|
|
99
|
+
}, onKeyDown: (e) => {
|
|
92
100
|
// prevents keyboard trap
|
|
93
101
|
if (e.key === 'Tab') {
|
|
94
102
|
return;
|
|
95
103
|
}
|
|
104
|
+
if (e.key === 'Enter') {
|
|
105
|
+
setOpenOptions(true);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
96
108
|
if (value) {
|
|
97
109
|
e.preventDefault();
|
|
98
110
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApiServices } from '@evoke-platform/context';
|
|
2
|
+
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
3
|
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
3
4
|
import userEvent from '@testing-library/user-event';
|
|
4
5
|
import axios from 'axios';
|
|
@@ -8,7 +9,8 @@ import { setupServer } from 'msw/node';
|
|
|
8
9
|
import React from 'react';
|
|
9
10
|
import { expect, it } from 'vitest';
|
|
10
11
|
import Form from '../Common/Form';
|
|
11
|
-
import { licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, } from './test-data';
|
|
12
|
+
import { accessibility508Object, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, users, } from './test-data';
|
|
13
|
+
expect.extend(matchers);
|
|
12
14
|
const removePoppers = () => {
|
|
13
15
|
const portalSelectors = ['.MuiAutocomplete-popper'];
|
|
14
16
|
portalSelectors.forEach((selector) => {
|
|
@@ -20,7 +22,7 @@ describe('Form component', () => {
|
|
|
20
22
|
let server;
|
|
21
23
|
let apiServices;
|
|
22
24
|
beforeAll(() => {
|
|
23
|
-
server = setupServer(http.get('/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/data/objects/license/effective', () => HttpResponse.json(licenseObject)), http.get('/data/objects/license/instances', () => {
|
|
25
|
+
server = setupServer(http.get('/data/objects/specialtyType/effective', () => HttpResponse.json(specialtyTypeObject)), http.get('/data/objects/license/effective', () => HttpResponse.json(licenseObject)), http.get('accessManagement/users', () => HttpResponse.json(users)), http.get('/data/objects/license/instances', () => {
|
|
24
26
|
return HttpResponse.json([rnLicense, npLicense]);
|
|
25
27
|
}), http.get('/data/objects/specialtyType/instances', (req) => {
|
|
26
28
|
const filter = new URL(req.request.url).searchParams.get('filter');
|
|
@@ -155,4 +157,192 @@ describe('Form component', () => {
|
|
|
155
157
|
expect(screen.queryByRole('combobox', { name: 'Specialty Type' })).to.be.null;
|
|
156
158
|
});
|
|
157
159
|
});
|
|
160
|
+
describe('508 accessibility compliance', () => {
|
|
161
|
+
it('supports keyboard navigation back and forth through Related Object dropdowns', async () => {
|
|
162
|
+
const user = userEvent.setup();
|
|
163
|
+
render(React.createElement(Form, { actionId: '_update1', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
164
|
+
await waitFor(() => {
|
|
165
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
166
|
+
});
|
|
167
|
+
await user.tab();
|
|
168
|
+
// Name field should be focused
|
|
169
|
+
expect(screen.getByLabelText('Name')).toHaveFocus();
|
|
170
|
+
await user.tab();
|
|
171
|
+
// License should be focused
|
|
172
|
+
expect(screen.getByLabelText('License')).toHaveFocus();
|
|
173
|
+
// Check reverse tabbing
|
|
174
|
+
await user.tab({ shift: true });
|
|
175
|
+
expect(screen.getByLabelText('Name')).toHaveFocus();
|
|
176
|
+
});
|
|
177
|
+
it('supports keyboard navigation back and forth through User dropdowns', async () => {
|
|
178
|
+
const user = userEvent.setup();
|
|
179
|
+
render(React.createElement(Form, { actionId: '_update2', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
await user.tab();
|
|
184
|
+
// Name field should be focused
|
|
185
|
+
await waitFor(() => {
|
|
186
|
+
expect(screen.getByLabelText('Name')).toHaveFocus();
|
|
187
|
+
});
|
|
188
|
+
await user.tab();
|
|
189
|
+
// User should be focused
|
|
190
|
+
expect(screen.getByLabelText('User')).toHaveFocus();
|
|
191
|
+
// Check reverse tabbing
|
|
192
|
+
await user.tab({ shift: true });
|
|
193
|
+
expect(screen.getByLabelText('Name')).toHaveFocus();
|
|
194
|
+
});
|
|
195
|
+
it('supports keyboard selection of dropdown values using Enter key on Related Objects', async () => {
|
|
196
|
+
const user = userEvent.setup();
|
|
197
|
+
render(React.createElement(Form, { actionId: '_update1', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
200
|
+
});
|
|
201
|
+
// Navigate to License field
|
|
202
|
+
await user.tab();
|
|
203
|
+
await user.tab();
|
|
204
|
+
// Open dropdown with Enter
|
|
205
|
+
await user.keyboard('{Enter}');
|
|
206
|
+
// Navigate to first option
|
|
207
|
+
await user.keyboard('{ArrowDown}');
|
|
208
|
+
// Select option with Enter
|
|
209
|
+
await user.keyboard('{Enter}');
|
|
210
|
+
await waitFor(() => {
|
|
211
|
+
const input = screen.getByRole('combobox', { name: 'License' });
|
|
212
|
+
expect(input).toHaveValue('RN License');
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
it('supports keyboard selection of dropdown values using Enter key on Users', async () => {
|
|
216
|
+
const user = userEvent.setup();
|
|
217
|
+
render(React.createElement(Form, { actionId: '_update2', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
218
|
+
await waitFor(() => {
|
|
219
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
220
|
+
});
|
|
221
|
+
// Navigate to License field
|
|
222
|
+
await user.tab();
|
|
223
|
+
await user.tab();
|
|
224
|
+
// Open dropdown with Enter
|
|
225
|
+
await user.keyboard('{Enter}');
|
|
226
|
+
// Navigate to first option
|
|
227
|
+
await user.keyboard('{ArrowDown}');
|
|
228
|
+
// Select option with Enter
|
|
229
|
+
await user.keyboard('{Enter}');
|
|
230
|
+
await waitFor(() => {
|
|
231
|
+
const input = screen.getByRole('combobox', { name: 'User' });
|
|
232
|
+
expect(input).toHaveValue('User 1');
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
it('supports navigating between dropdown options using arrow keys on Related Objects', async () => {
|
|
236
|
+
const user = userEvent.setup();
|
|
237
|
+
render(React.createElement(Form, { actionId: '_update1', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
240
|
+
});
|
|
241
|
+
// Navigate to and open dropdown
|
|
242
|
+
await user.tab();
|
|
243
|
+
await user.tab();
|
|
244
|
+
await user.keyboard('{ArrowDown}'); // Open dropdown
|
|
245
|
+
await user.keyboard('{ArrowDown}'); // First option
|
|
246
|
+
await user.keyboard('{Enter}');
|
|
247
|
+
// Verify first selection
|
|
248
|
+
await waitFor(() => {
|
|
249
|
+
const input = screen.getByRole('combobox', { name: 'License' });
|
|
250
|
+
expect(input).toHaveValue('RN License');
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
it('supports navigating between dropdown options using arrow keys on User dropdowns', async () => {
|
|
254
|
+
const user = userEvent.setup();
|
|
255
|
+
render(React.createElement(Form, { actionId: '_update2', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
256
|
+
await waitFor(() => {
|
|
257
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
258
|
+
});
|
|
259
|
+
// Navigate to and open dropdown
|
|
260
|
+
await user.tab();
|
|
261
|
+
await user.tab();
|
|
262
|
+
await user.keyboard('{ArrowDown}'); // Open dropdown
|
|
263
|
+
await user.keyboard('{ArrowDown}'); // First option
|
|
264
|
+
await user.keyboard('{Enter}');
|
|
265
|
+
// Verify first selection
|
|
266
|
+
await waitFor(() => {
|
|
267
|
+
const input = screen.getByRole('combobox', { name: 'User' });
|
|
268
|
+
expect(input).toHaveValue('User 1');
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
it('supports clearing selection with the clear button on Related Objects', async () => {
|
|
272
|
+
const user = userEvent.setup();
|
|
273
|
+
render(React.createElement(Form, { actionId: '_update1', actionType: 'update', object: accessibility508Object, apiServices: apiServices, instance: {
|
|
274
|
+
id: '123',
|
|
275
|
+
objectId: 'accessibility508',
|
|
276
|
+
name: 'Test Accessibility 508 Object Instance',
|
|
277
|
+
license: {
|
|
278
|
+
id: 'rnLicense',
|
|
279
|
+
name: 'RN License',
|
|
280
|
+
},
|
|
281
|
+
} }));
|
|
282
|
+
// Set up a selection first
|
|
283
|
+
await waitFor(() => {
|
|
284
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
285
|
+
});
|
|
286
|
+
await waitFor(() => {
|
|
287
|
+
const input = screen.getByRole('combobox', { name: 'License' });
|
|
288
|
+
expect(input).toHaveValue('RN License');
|
|
289
|
+
});
|
|
290
|
+
// Manually focus the clear button since test environment can't reach it via tab,
|
|
291
|
+
// even though tabbing works correctly in the actual form
|
|
292
|
+
const clearButton = screen.getByRole('button', { name: 'Clear selection' });
|
|
293
|
+
clearButton.focus();
|
|
294
|
+
expect(clearButton).toHaveFocus();
|
|
295
|
+
await user.keyboard('{Enter}');
|
|
296
|
+
await waitFor(() => {
|
|
297
|
+
const input = screen.getByRole('combobox', { name: 'License' });
|
|
298
|
+
expect(input).toHaveValue('');
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
it('supports clearing selection with the clear button on User dropdowns', async () => {
|
|
302
|
+
const user = userEvent.setup();
|
|
303
|
+
render(React.createElement(Form, { actionId: '_update2', actionType: 'update', object: accessibility508Object, apiServices: apiServices, instance: {
|
|
304
|
+
id: '123',
|
|
305
|
+
objectId: 'accessibility508',
|
|
306
|
+
name: 'Test Accessibility 508 Object Instance',
|
|
307
|
+
user: {
|
|
308
|
+
id: 'user1',
|
|
309
|
+
name: 'User 1',
|
|
310
|
+
},
|
|
311
|
+
} }));
|
|
312
|
+
// Set up a selection first
|
|
313
|
+
await waitFor(() => {
|
|
314
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
315
|
+
});
|
|
316
|
+
await waitFor(() => {
|
|
317
|
+
const input = screen.getByRole('combobox', { name: 'User' });
|
|
318
|
+
expect(input).toHaveValue('User 1');
|
|
319
|
+
});
|
|
320
|
+
// Manually focus the clear button since test environment can't reach it via tab,
|
|
321
|
+
// even though tabbing works correctly in the actual form
|
|
322
|
+
const clearButton = screen.getByRole('button', { name: 'Clear selection' });
|
|
323
|
+
clearButton.focus();
|
|
324
|
+
expect(clearButton).toHaveFocus();
|
|
325
|
+
await user.keyboard('{Enter}');
|
|
326
|
+
await waitFor(() => {
|
|
327
|
+
const input = screen.getByRole('combobox', { name: 'User' });
|
|
328
|
+
expect(input).toHaveValue('');
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
it('supports navigating to Add New option with arrow keys and opens modal', async () => {
|
|
332
|
+
const user = userEvent.setup();
|
|
333
|
+
render(React.createElement(Form, { actionId: '_update1', actionType: 'update', object: accessibility508Object, apiServices: apiServices }));
|
|
334
|
+
await waitFor(() => {
|
|
335
|
+
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
336
|
+
});
|
|
337
|
+
// Navigate to and open dropdown
|
|
338
|
+
await user.tab();
|
|
339
|
+
await user.tab();
|
|
340
|
+
await user.keyboard('{Enter}');
|
|
341
|
+
await user.keyboard('{ArrowUp}'); // Navigate to "Add New" option
|
|
342
|
+
await user.keyboard('{Enter}');
|
|
343
|
+
await waitFor(() => {
|
|
344
|
+
expect(screen.getByRole('dialog', { name: /add license/i })).toBeInTheDocument();
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
});
|
|
158
348
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Obj, ObjectInstance } from '@evoke-platform/context';
|
|
2
2
|
export declare const licenseObject: Obj;
|
|
3
3
|
export declare const licenseTypeObject: Obj;
|
|
4
|
+
export declare const accessibility508Object: Obj;
|
|
4
5
|
export declare const specialtyObject: Obj;
|
|
5
6
|
export declare const specialtyTypeObject: Obj;
|
|
6
7
|
export declare const rnLicense: ObjectInstance;
|
|
@@ -11,3 +12,9 @@ export declare const rnSpecialtyType1: ObjectInstance;
|
|
|
11
12
|
export declare const rnSpecialtyType2: ObjectInstance;
|
|
12
13
|
export declare const npSpecialtyType1: ObjectInstance;
|
|
13
14
|
export declare const npSpecialtyType2: ObjectInstance;
|
|
15
|
+
export declare const users: {
|
|
16
|
+
id: string;
|
|
17
|
+
status: string;
|
|
18
|
+
email: string;
|
|
19
|
+
name: string;
|
|
20
|
+
}[];
|