@evoke-platform/ui-components 1.10.0-testing.9 → 1.10.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/core/Autocomplete/Autocomplete.js +4 -2
- package/dist/published/components/core/Autocomplete/Autocomplete.test.js +112 -3
- package/dist/published/components/core/TextField/TextField.js +1 -1
- package/dist/published/components/core/TextField/TextField.test.js +0 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.js +24 -2
- package/dist/published/components/custom/CriteriaBuilder/CriteriaBuilder.test.js +45 -2
- package/dist/published/components/custom/Form/FormComponents/DocumentComponent/Document.js +2 -1
- package/dist/published/components/custom/Form/FormComponents/RepeatableFieldComponent/RepeatableField.js +1 -1
- package/dist/published/components/custom/Form/tests/Form.test.js +0 -2
- package/dist/published/components/custom/FormField/DatePickerSelect/DatePickerSelect.js +36 -7
- package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormField/FormField.d.ts +3 -1
- package/dist/published/components/custom/FormField/FormField.js +17 -5
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js +6 -4
- package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.test.js +0 -2
- package/dist/published/components/custom/FormField/Select/Select.test.js +0 -2
- package/dist/published/components/custom/FormField/TimePickerSelect/TimePickerSelect.js +14 -1
- package/dist/published/components/custom/FormV2/FormRenderer.d.ts +2 -1
- package/dist/published/components/custom/FormV2/FormRenderer.js +46 -8
- package/dist/published/components/custom/FormV2/FormRendererContainer.js +178 -153
- package/dist/published/components/custom/FormV2/components/AccordionSections.js +7 -2
- package/dist/published/components/custom/FormV2/components/Body.d.ts +1 -1
- package/dist/published/components/custom/FormV2/components/DefaultValues.d.ts +2 -2
- package/dist/published/components/custom/FormV2/components/DefaultValues.js +36 -28
- package/dist/published/components/custom/FormV2/components/FieldWrapper.js +1 -1
- package/dist/published/components/custom/FormV2/components/Footer.d.ts +1 -0
- package/dist/published/components/custom/FormV2/components/Footer.js +8 -5
- package/dist/published/components/custom/FormV2/components/FormContext.d.ts +3 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.d.ts +9 -0
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/AddressFields.js +32 -15
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/ActionDialog.js +2 -2
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/CollectionFiles/RepeatableField.js +6 -23
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Criteria.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/Document.js +22 -4
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.d.ts +2 -1
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/DocumentFiles/DocumentList.js +16 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/Image.js +31 -5
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/UserProperty.js +15 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/ObjectPropertyInput.js +115 -87
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.d.ts +2 -3
- package/dist/published/components/custom/FormV2/components/FormFieldTypes/relatedObjectFiles/RelatedObjectInstance.js +43 -20
- package/dist/published/components/custom/FormV2/components/Header.d.ts +5 -3
- package/dist/published/components/custom/FormV2/components/Header.js +47 -9
- package/dist/published/components/custom/FormV2/components/PropertyProtection.d.ts +16 -0
- package/dist/published/components/custom/FormV2/components/PropertyProtection.js +113 -0
- package/dist/published/components/custom/FormV2/components/RecursiveEntryRenderer.js +47 -24
- package/dist/published/components/custom/FormV2/components/ValidationFiles/ValidationErrors.js +1 -1
- package/dist/published/components/custom/FormV2/components/types.d.ts +2 -0
- package/dist/published/components/custom/FormV2/components/utils.d.ts +6 -4
- package/dist/published/components/custom/FormV2/components/utils.js +83 -13
- package/dist/published/components/custom/FormV2/tests/FormRenderer.test.js +411 -44
- package/dist/published/components/custom/FormV2/tests/FormRendererContainer.test.js +983 -16
- package/dist/published/components/custom/FormV2/tests/test-data.d.ts +1 -0
- package/dist/published/components/custom/FormV2/tests/test-data.js +138 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/InstanceEntryRenderer.js +165 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.d.ts +13 -0
- package/dist/published/components/custom/ViewDetailsV2/ViewDetailsV2Container.js +144 -0
- package/dist/published/components/custom/ViewDetailsV2/index.d.ts +3 -0
- package/dist/published/components/custom/ViewDetailsV2/index.js +2 -0
- package/dist/published/components/custom/index.d.ts +2 -0
- package/dist/published/components/custom/index.js +1 -0
- package/dist/published/index.d.ts +6 -6
- package/dist/published/index.js +1 -1
- package/dist/published/stories/CriteriaBuilder.stories.js +6 -0
- package/dist/published/stories/FormRenderer.stories.d.ts +8 -4
- package/dist/published/stories/FormRendererContainer.stories.d.ts +26 -0
- package/dist/published/stories/FormRendererContainer.stories.js +5 -0
- package/dist/published/stories/FormRendererData.d.ts +12 -0
- package/dist/published/stories/FormRendererData.js +26 -1
- package/dist/published/stories/ViewDetailsV2Container.stories.d.ts +26 -0
- package/dist/published/stories/ViewDetailsV2Container.stories.js +37 -0
- package/dist/published/stories/ViewDetailsV2Data.d.ts +4 -0
- package/dist/published/stories/ViewDetailsV2Data.js +203 -0
- package/dist/published/stories/sharedMswHandlers.js +49 -10
- package/dist/published/theme/hooks.d.ts +4 -3
- package/dist/published/types.d.ts +3 -0
- package/package.json +10 -8
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ExpandMore, InfoRounded } from '@mui/icons-material';
|
|
2
2
|
import { InputLabel, Autocomplete as MUIAutocomplete } from '@mui/material';
|
|
3
|
+
import { omit } from 'lodash';
|
|
3
4
|
import React from 'react';
|
|
4
5
|
import UIThemeProvider from '../../../theme';
|
|
5
6
|
import FieldError from '../FieldError';
|
|
@@ -37,6 +38,7 @@ const Autocomplete = (props) => {
|
|
|
37
38
|
return props.instructionText;
|
|
38
39
|
}
|
|
39
40
|
};
|
|
41
|
+
const muiAutocompleteProps = omit(props, 'sortBy', 'error', 'errorMessage', 'labelPlacement', 'instructionText');
|
|
40
42
|
if (!!props.label && props.labelPlacement === 'outside-top') {
|
|
41
43
|
return (React.createElement(UIThemeProvider, null,
|
|
42
44
|
React.createElement(InputLabel, { htmlFor: props.id ?? '', sx: { display: 'flex', paddingBottom: '0px', fontSize: '14px' } },
|
|
@@ -50,7 +52,7 @@ const Autocomplete = (props) => {
|
|
|
50
52
|
color: (theme) => theme.palette.text.secondary,
|
|
51
53
|
} })))),
|
|
52
54
|
renderInstructionText(),
|
|
53
|
-
React.createElement(MUIAutocomplete, { ...
|
|
55
|
+
React.createElement(MUIAutocomplete, { ...muiAutocompleteProps, sx: {
|
|
54
56
|
'& fieldset': { borderRadius: '8px', borderColor: props.error ? 'red' : undefined },
|
|
55
57
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
56
58
|
border: props.readOnly ? 'none' : 'auto',
|
|
@@ -67,7 +69,7 @@ const Autocomplete = (props) => {
|
|
|
67
69
|
}
|
|
68
70
|
else {
|
|
69
71
|
return (React.createElement(UIThemeProvider, null,
|
|
70
|
-
React.createElement(MUIAutocomplete, { ...
|
|
72
|
+
React.createElement(MUIAutocomplete, { ...muiAutocompleteProps, sx: {
|
|
71
73
|
'& fieldset': { borderRadius: '8px', borderColor: props.error ? 'red' : undefined },
|
|
72
74
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
73
75
|
border: props.readOnly ? 'none' : 'auto',
|
|
@@ -1,11 +1,120 @@
|
|
|
1
1
|
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
2
3
|
import React from 'react';
|
|
3
4
|
import { it } from 'vitest';
|
|
4
5
|
import TextField from '../TextField';
|
|
5
6
|
import Autocomplete from './Autocomplete';
|
|
6
|
-
const
|
|
7
|
+
const renderInputFactory = (label) => (params) => React.createElement(TextField, { ...params, label: label });
|
|
7
8
|
const options = [];
|
|
8
|
-
it('
|
|
9
|
-
render(React.createElement(Autocomplete, { id: "testinput", labelPlacement: "outside-top", label: "Title", renderInput:
|
|
9
|
+
it('should render with label outside outline', () => {
|
|
10
|
+
render(React.createElement(Autocomplete, { id: "testinput", labelPlacement: "outside-top", label: "Title", renderInput: renderInputFactory(), options: options }));
|
|
10
11
|
screen.getByRole('combobox', { name: 'Title' });
|
|
11
12
|
});
|
|
13
|
+
it('should render label outside outline with asterisk when required', () => {
|
|
14
|
+
render(React.createElement(Autocomplete, { id: "testinput", labelPlacement: "outside-top", label: "Title", renderInput: renderInputFactory(), options: options, required: true }));
|
|
15
|
+
screen.getByRole('combobox', { name: 'Title *' });
|
|
16
|
+
});
|
|
17
|
+
it('should render instruction text when provided when there is an outside-top label', () => {
|
|
18
|
+
render(React.createElement(Autocomplete, { id: "testinput", labelPlacement: "outside-top", label: "Title", instructionText: "This is an instruction", renderInput: renderInputFactory(), options: options }));
|
|
19
|
+
screen.getByText('This is an instruction');
|
|
20
|
+
});
|
|
21
|
+
it('should not render instruction text when there is not an outside-top label', () => {
|
|
22
|
+
render(React.createElement(Autocomplete, { id: "testinput", instructionText: "This is an instruction", renderInput: renderInputFactory(), options: options }));
|
|
23
|
+
const instructionText = screen.queryByText('This is an instruction');
|
|
24
|
+
expect(instructionText).not.toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
it('should render a tooltip if one is provided and there is an outside-top label', async () => {
|
|
27
|
+
const user = userEvent.setup();
|
|
28
|
+
render(React.createElement(Autocomplete, { id: "testinput", labelPlacement: "outside-top", label: "Title", tooltip: "This is a tooltip", renderInput: renderInputFactory(), options: options }));
|
|
29
|
+
expect(screen.queryByText('This is a tooltip')).not.toBeInTheDocument();
|
|
30
|
+
await user.hover(screen.getByLabelText('This is a tooltip'));
|
|
31
|
+
await screen.findByText('This is a tooltip');
|
|
32
|
+
});
|
|
33
|
+
it('should sort options in ascending order if sortBy is ASC', async () => {
|
|
34
|
+
const user = userEvent.setup();
|
|
35
|
+
const unsortedOptions = [
|
|
36
|
+
{ label: 'Banana', value: 1 },
|
|
37
|
+
{ label: 'Apple', value: 2 },
|
|
38
|
+
{ label: 'Cherry', value: 3 },
|
|
39
|
+
];
|
|
40
|
+
render(React.createElement(Autocomplete, { id: "testinput", sortBy: "ASC", renderInput: renderInputFactory('Fruits'), options: unsortedOptions }));
|
|
41
|
+
const comboBox = screen.getByRole('combobox', { name: 'Fruits' });
|
|
42
|
+
await user.click(comboBox);
|
|
43
|
+
const options = screen.getAllByRole('option');
|
|
44
|
+
const labels = options.map((option) => option.textContent);
|
|
45
|
+
expect(labels).toEqual(['Apple', 'Banana', 'Cherry']);
|
|
46
|
+
});
|
|
47
|
+
it('should sort options in descending order if sortBy is DESC', async () => {
|
|
48
|
+
const user = userEvent.setup();
|
|
49
|
+
const unsortedOptions = [
|
|
50
|
+
{ label: 'Banana', value: 1 },
|
|
51
|
+
{ label: 'Apple', value: 2 },
|
|
52
|
+
{ label: 'Cherry', value: 3 },
|
|
53
|
+
];
|
|
54
|
+
render(React.createElement(Autocomplete, { id: "testinput", sortBy: "DESC", renderInput: renderInputFactory('Fruits'), options: unsortedOptions }));
|
|
55
|
+
const comboBox = screen.getByRole('combobox', { name: 'Fruits' });
|
|
56
|
+
await user.click(comboBox);
|
|
57
|
+
const options = screen.getAllByRole('option');
|
|
58
|
+
const labels = options.map((option) => option.textContent);
|
|
59
|
+
expect(labels).toEqual(['Cherry', 'Banana', 'Apple']);
|
|
60
|
+
});
|
|
61
|
+
it('should not sort options if sortBy is NONE', async () => {
|
|
62
|
+
const user = userEvent.setup();
|
|
63
|
+
const unsortedOptions = [
|
|
64
|
+
{ label: 'Banana', value: 1 },
|
|
65
|
+
{ label: 'Apple', value: 2 },
|
|
66
|
+
{ label: 'Cherry', value: 3 },
|
|
67
|
+
];
|
|
68
|
+
render(React.createElement(Autocomplete, { id: "testinput", sortBy: "NONE", renderInput: renderInputFactory('Fruits'), options: unsortedOptions }));
|
|
69
|
+
const comboBox = screen.getByRole('combobox', { name: 'Fruits' });
|
|
70
|
+
await user.click(comboBox);
|
|
71
|
+
const options = screen.getAllByRole('option');
|
|
72
|
+
const labels = options.map((option) => option.textContent);
|
|
73
|
+
expect(labels).toEqual(['Banana', 'Apple', 'Cherry']);
|
|
74
|
+
});
|
|
75
|
+
it('should sort options in ascending order by default', async () => {
|
|
76
|
+
const user = userEvent.setup();
|
|
77
|
+
const unsortedOptions = [
|
|
78
|
+
{ label: 'Banana', value: 1 },
|
|
79
|
+
{ label: 'Apple', value: 2 },
|
|
80
|
+
{ label: 'Cherry', value: 3 },
|
|
81
|
+
];
|
|
82
|
+
render(React.createElement(Autocomplete, { id: "testinput", renderInput: renderInputFactory('Fruits'), options: unsortedOptions }));
|
|
83
|
+
const comboBox = screen.getByRole('combobox', { name: 'Fruits' });
|
|
84
|
+
await user.click(comboBox);
|
|
85
|
+
const options = screen.getAllByRole('option');
|
|
86
|
+
const labels = options.map((option) => option.textContent);
|
|
87
|
+
expect(labels).toEqual(['Apple', 'Banana', 'Cherry']);
|
|
88
|
+
});
|
|
89
|
+
it('should sort string options', async () => {
|
|
90
|
+
const user = userEvent.setup();
|
|
91
|
+
const unsortedOptions = ['Banana', 'Apple', 'Cherry'];
|
|
92
|
+
render(React.createElement(Autocomplete, { id: "testinput", sortBy: "ASC", renderInput: renderInputFactory('Fruit Options'), options: unsortedOptions }));
|
|
93
|
+
const comboBox = screen.getByRole('combobox', { name: 'Fruit Options' });
|
|
94
|
+
await user.click(comboBox);
|
|
95
|
+
const options = screen.getAllByRole('option');
|
|
96
|
+
const labels = options.map((option) => option.textContent);
|
|
97
|
+
expect(labels).toEqual(['Apple', 'Banana', 'Cherry']);
|
|
98
|
+
});
|
|
99
|
+
it('should render an error when error prop is true', () => {
|
|
100
|
+
render(React.createElement(Autocomplete, { id: "testinput", label: "Title", error: true, errorMessage: "This is an error", labelPlacement: "outside-top", renderInput: renderInputFactory(), options: options }));
|
|
101
|
+
screen.getByText('This is an error');
|
|
102
|
+
});
|
|
103
|
+
it('should show popupIcon when not disabled', () => {
|
|
104
|
+
render(React.createElement(Autocomplete, { id: "testinput", label: "Title", disabled: false, renderInput: renderInputFactory(), options: options }));
|
|
105
|
+
screen.getByTestId('ExpandMoreIcon');
|
|
106
|
+
});
|
|
107
|
+
it('should hide popupIcon when disabled', () => {
|
|
108
|
+
render(React.createElement(Autocomplete, { id: "testinput", label: "Title", disabled: true, renderInput: renderInputFactory(), options: options }));
|
|
109
|
+
const popupIcon = screen.queryByTestId('ExpandMoreIcon');
|
|
110
|
+
expect(popupIcon).not.toBeInTheDocument();
|
|
111
|
+
});
|
|
112
|
+
it('should hide popupIcon when readOnly', () => {
|
|
113
|
+
render(React.createElement(Autocomplete, { id: "testinput", label: "Title", readOnly: true, renderInput: renderInputFactory(), options: options }));
|
|
114
|
+
const popupIcon = screen.queryByTestId('ExpandMoreIcon');
|
|
115
|
+
expect(popupIcon).not.toBeInTheDocument();
|
|
116
|
+
});
|
|
117
|
+
it('should show custom popupIcon when provided', () => {
|
|
118
|
+
render(React.createElement(Autocomplete, { id: "testinput", label: "Title", popupIcon: React.createElement("span", { "data-testid": "custom-icon" }, "^"), renderInput: renderInputFactory(), options: options }));
|
|
119
|
+
screen.getByTestId('custom-icon');
|
|
120
|
+
});
|
|
@@ -36,7 +36,7 @@ const TextField = (props) => {
|
|
|
36
36
|
...props.sx,
|
|
37
37
|
} }),
|
|
38
38
|
error && React.createElement(FieldError, { required: required, label: errorMessage }))) : (React.createElement(React.Fragment, null,
|
|
39
|
-
React.createElement(MUITextField, { inputProps: { readOnly: readOnly, 'aria-readonly': !!readOnly, 'data-testid': 'label-inside' }, ...
|
|
39
|
+
React.createElement(MUITextField, { inputProps: { readOnly: readOnly, 'aria-readonly': !!readOnly, 'data-testid': 'label-inside' }, ...muiProps, sx: readOnly
|
|
40
40
|
? { ...readOnlyStyles, ...props.sx }
|
|
41
41
|
: { '& fieldset': { borderRadius: '8px' }, ...props.sx } }),
|
|
42
42
|
error && React.createElement(FieldError, { required: required, label: errorMessage })))));
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
1
|
import { render, screen } from '@testing-library/react';
|
|
3
2
|
import React from 'react';
|
|
4
3
|
import { expect, it } from 'vitest';
|
|
5
4
|
import TextField from './index';
|
|
6
|
-
expect.extend(matchers);
|
|
7
5
|
it('render TextField and check for data-testid === label-outside when labelPlacement === outside-top && variant === outlined', () => {
|
|
8
6
|
render(React.createElement(TextField, { id: "testinput", labelPlacement: "outside-top", variant: "outlined", label: "Title" }));
|
|
9
7
|
const textField = screen.getByRole('textbox', { name: 'Title' });
|
|
@@ -400,9 +400,31 @@ const CriteriaBuilder = (props) => {
|
|
|
400
400
|
const fields = useMemo(() => {
|
|
401
401
|
return properties
|
|
402
402
|
.filter(({ type }) => type !== 'collection')
|
|
403
|
-
.
|
|
403
|
+
.flatMap((property) => {
|
|
404
|
+
if (property.type === 'object') {
|
|
405
|
+
const result = [
|
|
406
|
+
{
|
|
407
|
+
name: `${property.id}.id`,
|
|
408
|
+
label: `${property.name} ID`,
|
|
409
|
+
inputType: property.type,
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
name: `${property.id}.name`,
|
|
413
|
+
label: `${property.name} Name`,
|
|
414
|
+
inputType: property.type,
|
|
415
|
+
},
|
|
416
|
+
];
|
|
417
|
+
if (!property.objectId) {
|
|
418
|
+
result.push({
|
|
419
|
+
name: `${property.id}.objectId`,
|
|
420
|
+
label: `${property.name} Object ID`,
|
|
421
|
+
inputType: property.type,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
404
426
|
return {
|
|
405
|
-
name: property.
|
|
427
|
+
name: property.id,
|
|
406
428
|
label: property.name,
|
|
407
429
|
inputType: property.type,
|
|
408
430
|
...(property.enum && {
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
1
|
import { render, screen } from '@testing-library/react';
|
|
3
2
|
import userEvent from '@testing-library/user-event';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { expect, it } from 'vitest';
|
|
6
5
|
import CriteriaBuilder from './CriteriaBuilder';
|
|
7
|
-
expect.extend(matchers);
|
|
8
6
|
const mockProperties = [
|
|
9
7
|
{
|
|
10
8
|
id: 'name',
|
|
@@ -58,6 +56,17 @@ const mockProperties = [
|
|
|
58
56
|
name: 'Boolean',
|
|
59
57
|
type: 'boolean',
|
|
60
58
|
},
|
|
59
|
+
{
|
|
60
|
+
id: 'regularRelatedObject',
|
|
61
|
+
name: 'Regular Related Object',
|
|
62
|
+
type: 'object',
|
|
63
|
+
objectId: 'relatedObjectId',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'dynamicRelatedObject',
|
|
67
|
+
name: 'Dynamic Related Object',
|
|
68
|
+
type: 'object',
|
|
69
|
+
},
|
|
61
70
|
];
|
|
62
71
|
describe('CriteriaBuilder', () => {
|
|
63
72
|
// Mock function for setCriteria
|
|
@@ -66,6 +75,40 @@ describe('CriteriaBuilder', () => {
|
|
|
66
75
|
// Reset the mock before each test
|
|
67
76
|
setCriteriaMock.mockReset();
|
|
68
77
|
});
|
|
78
|
+
describe('when passed regular related object fields', () => {
|
|
79
|
+
it('should render the field ID', () => {
|
|
80
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
81
|
+
'regularRelatedObject.id': 'relatedInstanceId',
|
|
82
|
+
}, setCriteria: setCriteriaMock }));
|
|
83
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Regular Related Object ID');
|
|
84
|
+
});
|
|
85
|
+
it('should render the field Name', () => {
|
|
86
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
87
|
+
'regularRelatedObject.name': 'relatedInstanceName',
|
|
88
|
+
}, setCriteria: setCriteriaMock }));
|
|
89
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Regular Related Object Name');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('when passed dynamic related object fields', () => {
|
|
93
|
+
it('should render the field ID', () => {
|
|
94
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
95
|
+
'dynamicRelatedObject.id': 'relatedInstanceId',
|
|
96
|
+
}, setCriteria: setCriteriaMock }));
|
|
97
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object ID');
|
|
98
|
+
});
|
|
99
|
+
it('should render the field Name', () => {
|
|
100
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
101
|
+
'dynamicRelatedObject.name': 'relatedInstanceName',
|
|
102
|
+
}, setCriteria: setCriteriaMock }));
|
|
103
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object Name');
|
|
104
|
+
});
|
|
105
|
+
it('should render the field Object ID', () => {
|
|
106
|
+
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
107
|
+
'dynamicRelatedObject.objectId': 'relatedInstanceObjectId',
|
|
108
|
+
}, setCriteria: setCriteriaMock }));
|
|
109
|
+
expect(screen.getByRole('combobox', { name: /select property/i })).toHaveValue('Dynamic Related Object Object ID');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
69
112
|
describe('when passed single-select fields', () => {
|
|
70
113
|
it('should render the field name', () => {
|
|
71
114
|
render(React.createElement(CriteriaBuilder, { properties: mockProperties, criteria: {
|
|
@@ -42,7 +42,8 @@ export const Document = (props) => {
|
|
|
42
42
|
if (canUpdateProperty) {
|
|
43
43
|
apiServices
|
|
44
44
|
.get(getPrefixedUrl(`/objects/${objectId}/instances/${instance.id}/documents/checkAccess?action=update`))
|
|
45
|
-
.then((accessCheck) => setHasUpdatePermission(accessCheck.result))
|
|
45
|
+
.then((accessCheck) => setHasUpdatePermission(accessCheck.result))
|
|
46
|
+
.catch(() => setHasUpdatePermission(false));
|
|
46
47
|
}
|
|
47
48
|
};
|
|
48
49
|
const handleUpload = async (files) => {
|
|
@@ -430,7 +430,7 @@ const RepeatableField = (props) => {
|
|
|
430
430
|
hasCreateAction && (React.createElement(Button, { variant: "contained", sx: styles.addButton, onClick: addRow }, "Add"))),
|
|
431
431
|
relatedObject && openDialog && (React.createElement(ActionDialog, { object: relatedObject, open: openDialog, apiServices: apiServices, onClose: () => setOpenDialog(false), instanceInput: dialogType === 'update' ? (relatedInstances.find((i) => i.id === selectedRow) ?? {}) : {}, handleSubmit: save,
|
|
432
432
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
433
|
-
objectInputCommonProps: { apiServices }, action: relatedObject?.actions?.find((a) => a.id ===
|
|
433
|
+
objectInputCommonProps: { apiServices, setSnackbarError }, 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
436
|
: undefined, richTextEditor: richTextEditor })),
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ApiServices } from '@evoke-platform/context';
|
|
2
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
3
2
|
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
4
3
|
import userEvent from '@testing-library/user-event';
|
|
5
4
|
import axios from 'axios';
|
|
@@ -10,7 +9,6 @@ import React from 'react';
|
|
|
10
9
|
import { expect, it } from 'vitest';
|
|
11
10
|
import Form from '../Common/Form';
|
|
12
11
|
import { accessibility508Object, licenseObject, npLicense, npSpecialtyType1, npSpecialtyType2, rnLicense, rnSpecialtyType1, rnSpecialtyType2, specialtyObject, specialtyTypeObject, users, } from './test-data';
|
|
13
|
-
expect.extend(matchers);
|
|
14
12
|
const removePoppers = () => {
|
|
15
13
|
const portalSelectors = ['.MuiAutocomplete-popper'];
|
|
16
14
|
portalSelectors.forEach((selector) => {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { DateTimeFormatter } from '@js-joda/core';
|
|
2
2
|
import { omit } from 'lodash';
|
|
3
|
-
import React, { useEffect, useState } from 'react';
|
|
3
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
4
|
+
import { useFormContext } from '../../../../theme/hooks';
|
|
4
5
|
import { InvalidDate, LocalDate, nativeJs } from '../../../../util';
|
|
5
6
|
import { DatePicker, LocalizationProvider, TextField } from '../../../core';
|
|
7
|
+
import { obfuscateValue } from '../../FormV2/components/utils';
|
|
6
8
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
7
9
|
function asCalendarDate(value) {
|
|
8
10
|
if (!value) {
|
|
@@ -27,21 +29,48 @@ const asMonthDayYearFormat = (date) => {
|
|
|
27
29
|
}
|
|
28
30
|
};
|
|
29
31
|
const DatePickerSelect = (props) => {
|
|
30
|
-
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, onChange, additionalProps, } = props;
|
|
31
|
-
const
|
|
32
|
+
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, onChange, additionalProps, endAdornment, protection, } = props;
|
|
33
|
+
const { onAutosave } = useFormContext();
|
|
34
|
+
const processValue = useCallback((val) => {
|
|
35
|
+
const isProtected = protection?.maskChar && val === obfuscateValue(val, { protection });
|
|
36
|
+
return isProtected && typeof val === 'string' ? val : asCalendarDate(val);
|
|
37
|
+
}, [protection]);
|
|
38
|
+
const [value, setValue] = useState(() => processValue(defaultValue));
|
|
32
39
|
useEffect(() => {
|
|
33
|
-
setValue(
|
|
34
|
-
}, [defaultValue]);
|
|
40
|
+
setValue(processValue(defaultValue));
|
|
41
|
+
}, [defaultValue, processValue]);
|
|
35
42
|
const handleChange = (date) => {
|
|
36
43
|
setValue(date);
|
|
37
44
|
onChange && onChange(property.id, date, property);
|
|
38
45
|
};
|
|
39
|
-
|
|
40
|
-
|
|
46
|
+
const handleAccept = async () => {
|
|
47
|
+
// Trigger autosave when date is accepted (picker closes after selection)
|
|
48
|
+
if (onAutosave) {
|
|
49
|
+
try {
|
|
50
|
+
await onAutosave(id);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error('Autosave failed:', error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
return readOnly ? (React.createElement(InputFieldComponent, { ...{
|
|
58
|
+
...props,
|
|
59
|
+
defaultValue: protection?.maskChar && value === obfuscateValue(value, { protection })
|
|
60
|
+
? value
|
|
61
|
+
: asMonthDayYearFormat(value),
|
|
62
|
+
endAdornment,
|
|
63
|
+
} })) : (React.createElement(LocalizationProvider, null,
|
|
64
|
+
React.createElement(DatePicker, { value: value, onChange: handleChange, onAccept: handleAccept, inputFormat: "MM/dd/yyyy", renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
41
65
|
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
42
66
|
inputProps: {
|
|
43
67
|
...params.inputProps,
|
|
44
68
|
...(additionalProps?.inputProps ?? {}),
|
|
69
|
+
}, InputProps: {
|
|
70
|
+
...params.InputProps,
|
|
71
|
+
endAdornment: (React.createElement(React.Fragment, null,
|
|
72
|
+
params.InputProps?.endAdornment,
|
|
73
|
+
endAdornment)),
|
|
45
74
|
}, ...omit(additionalProps, ['inputProps']) })) })));
|
|
46
75
|
};
|
|
47
76
|
export default DatePickerSelect;
|
package/dist/published/components/custom/FormField/DateTimePickerSelect/DateTimePickerSelect.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LocalDate, LocalDateTime, LocalTime, nativeJs } from '@js-joda/core';
|
|
2
2
|
import { omit } from 'lodash';
|
|
3
3
|
import React, { useEffect, useState } from 'react';
|
|
4
|
+
import { useFormContext } from '../../../../theme/hooks';
|
|
4
5
|
import { InvalidDate } from '../../../../util';
|
|
5
6
|
import { DateTimePicker, LocalizationProvider, TextField } from '../../../core';
|
|
6
7
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
@@ -30,6 +31,7 @@ const formatDateTime = (date) => {
|
|
|
30
31
|
const DateTimePickerSelect = (props) => {
|
|
31
32
|
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, additionalProps } = props;
|
|
32
33
|
const [value, setValue] = useState(asCalendarDate(defaultValue));
|
|
34
|
+
const { onAutosave } = useFormContext();
|
|
33
35
|
useEffect(() => {
|
|
34
36
|
setValue(asCalendarDate(defaultValue));
|
|
35
37
|
}, [defaultValue]);
|
|
@@ -43,8 +45,19 @@ const DateTimePickerSelect = (props) => {
|
|
|
43
45
|
setValue(date);
|
|
44
46
|
props.onChange && props.onChange(property.id, date, property);
|
|
45
47
|
};
|
|
48
|
+
const handleAccept = async () => {
|
|
49
|
+
// Trigger autosave when date/time is accepted (picker closes after selection)
|
|
50
|
+
if (onAutosave) {
|
|
51
|
+
try {
|
|
52
|
+
await onAutosave(id);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('Autosave failed:', error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
46
59
|
return readOnly ? (React.createElement(InputFieldComponent, { ...{ ...props, defaultValue: formatDateTime(value) } })) : (React.createElement(LocalizationProvider, null,
|
|
47
|
-
React.createElement(DateTimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
60
|
+
React.createElement(DateTimePicker, { value: value, onChange: handleChange, onAccept: handleAccept, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium',
|
|
48
61
|
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
49
62
|
inputProps: {
|
|
50
63
|
...params.inputProps,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SelectOption } from '@evoke-platform/context';
|
|
1
|
+
import { PropertyProtection as PropertyProtectionType, SelectOption } from '@evoke-platform/context';
|
|
2
2
|
import React, { FocusEventHandler, ReactNode } from 'react';
|
|
3
3
|
import { ObjectProperty } from '../../../types';
|
|
4
4
|
import { AutocompleteOption } from '../../core';
|
|
@@ -36,6 +36,8 @@ export type FormFieldProps = {
|
|
|
36
36
|
description?: string;
|
|
37
37
|
tooltip?: string;
|
|
38
38
|
isCombobox?: boolean;
|
|
39
|
+
endAdornment?: ReactNode;
|
|
40
|
+
protection?: PropertyProtectionType;
|
|
39
41
|
};
|
|
40
42
|
declare const FormField: (props: FormFieldProps) => React.JSX.Element;
|
|
41
43
|
export default FormField;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import PropertyProtection from '../FormV2/components/PropertyProtection';
|
|
2
3
|
import AddressFieldComponent from './AddressFieldComponent/addressFieldComponent';
|
|
3
4
|
import BooleanSelect from './BooleanSelect/BooleanSelect';
|
|
4
5
|
import DatePickerSelect from './DatePickerSelect/DatePickerSelect';
|
|
@@ -8,8 +9,16 @@ import InputFieldComponent from './InputFieldComponent/InputFieldComponent';
|
|
|
8
9
|
import Select from './Select/Select';
|
|
9
10
|
import TimePickerSelect from './TimePickerSelect/TimePickerSelect';
|
|
10
11
|
const FormField = (props) => {
|
|
11
|
-
const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, strictlyTrue, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, isCombobox, } = props;
|
|
12
|
-
|
|
12
|
+
const { id, defaultValue, error, onChange, property, readOnly, selectOptions, required, strictlyTrue, size, placeholder, errorMessage, onBlur, mask, max, min, isMultiLineText, rows, inputMaskPlaceholderChar, queryAddresses, isOptionEqualToValue, renderOption, disableCloseOnSelect, getOptionLabel, additionalProps, displayOption, sortBy, label, description, tooltip, isCombobox, protection, } = props;
|
|
13
|
+
const [currentDisplayValue, setCurrentDisplayValue] = useState(defaultValue);
|
|
14
|
+
const isProtectedProperty = !!protection?.maskChar;
|
|
15
|
+
const [protectionMode, setProtectionMode] = useState(isProtectedProperty ? (!currentDisplayValue ? 'edit' : 'mask') : 'full');
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (isProtectedProperty && protectionMode === 'edit') {
|
|
18
|
+
setCurrentDisplayValue(defaultValue);
|
|
19
|
+
}
|
|
20
|
+
}, [defaultValue]);
|
|
21
|
+
const protectionComponent = isProtectedProperty && !!defaultValue ? (React.createElement(PropertyProtection, { parameter: property, protection: protection, mask: mask, canEdit: !readOnly, value: defaultValue, handleChange: (value) => onChange?.(property.id, value, property), setCurrentDisplayValue: setCurrentDisplayValue, mode: protectionMode, setMode: setProtectionMode })) : null;
|
|
13
22
|
const commonProps = {
|
|
14
23
|
id: id ?? property.id,
|
|
15
24
|
property,
|
|
@@ -17,8 +26,8 @@ const FormField = (props) => {
|
|
|
17
26
|
onBlur,
|
|
18
27
|
error,
|
|
19
28
|
errorMessage,
|
|
20
|
-
readOnly,
|
|
21
|
-
defaultValue,
|
|
29
|
+
readOnly: readOnly || (!!isProtectedProperty && protectionMode !== 'edit'),
|
|
30
|
+
defaultValue: isProtectedProperty ? currentDisplayValue : defaultValue,
|
|
22
31
|
selectOptions,
|
|
23
32
|
required,
|
|
24
33
|
strictlyTrue,
|
|
@@ -37,7 +46,10 @@ const FormField = (props) => {
|
|
|
37
46
|
description,
|
|
38
47
|
tooltip,
|
|
39
48
|
isCombobox,
|
|
49
|
+
endAdornment: protectionComponent,
|
|
50
|
+
protection,
|
|
40
51
|
};
|
|
52
|
+
let control;
|
|
41
53
|
if (queryAddresses) {
|
|
42
54
|
control = (React.createElement(AddressFieldComponent, { ...commonProps, mask: mask, inputMaskPlaceholderChar: inputMaskPlaceholderChar, isMultiLineText: isMultiLineText, rows: rows, queryAddresses: queryAddresses }));
|
|
43
55
|
return control;
|
package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.js
CHANGED
|
@@ -3,6 +3,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
3
3
|
import InputMask from 'react-input-mask';
|
|
4
4
|
import NumberFormat from 'react-number-format';
|
|
5
5
|
import { Autocomplete, TextField } from '../../../core';
|
|
6
|
+
import { obfuscateValue } from '../../FormV2/components/utils';
|
|
6
7
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
8
|
export const NumericFormat = (props) => {
|
|
8
9
|
const { inputRef, onChange, defaultValue, ...other } = props;
|
|
@@ -15,7 +16,7 @@ export const NumericFormat = (props) => {
|
|
|
15
16
|
}, isNumericString: true, fixedDecimalScale: true, allowNegative: true }));
|
|
16
17
|
};
|
|
17
18
|
const InputFieldComponent = (props) => {
|
|
18
|
-
const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, required, size, placeholder, mask, min, max, isMultiLineText, rows, inputMaskPlaceholderChar, additionalProps, } = props;
|
|
19
|
+
const { id, property, defaultValue, error, errorMessage, onBlur, readOnly, required, size, placeholder, mask, min, max, isMultiLineText, rows, inputMaskPlaceholderChar, additionalProps, endAdornment, protection, } = props;
|
|
19
20
|
const [value, setValue] = useState(defaultValue ?? '');
|
|
20
21
|
const [inputValue, setInputValue] = useState('');
|
|
21
22
|
useEffect(() => {
|
|
@@ -43,6 +44,7 @@ const InputFieldComponent = (props) => {
|
|
|
43
44
|
: property.type === 'integer'
|
|
44
45
|
? { inputProps: { min, max, ...(additionalProps?.inputProps ?? {}) } }
|
|
45
46
|
: null;
|
|
47
|
+
const isValueProtected = protection?.maskChar && defaultValue === obfuscateValue(defaultValue, { protection, mask });
|
|
46
48
|
return property.enum && !readOnly ? (React.createElement(Autocomplete, { id: id,
|
|
47
49
|
// note: this is different between widgets and builder
|
|
48
50
|
// builder had select options being {label, value}
|
|
@@ -53,7 +55,7 @@ const InputFieldComponent = (props) => {
|
|
|
53
55
|
? [...property.enum, defaultValue]
|
|
54
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) => {
|
|
55
57
|
return option.value === value;
|
|
56
|
-
}, error: error, required: required, inputValue: inputValue ?? '', onInputChange: handleInputValueChange, ...(additionalProps ?? {}) })) : !mask ? (React.createElement(TextField, { id: id, sx: {
|
|
58
|
+
}, error: error, required: required, inputValue: inputValue ?? '', onInputChange: handleInputValueChange, ...(additionalProps ?? {}) })) : !mask || isValueProtected ? (React.createElement(TextField, { id: id, sx: {
|
|
57
59
|
background: 'white',
|
|
58
60
|
borderRadius: '8px',
|
|
59
61
|
...(readOnly && {
|
|
@@ -65,7 +67,7 @@ const InputFieldComponent = (props) => {
|
|
|
65
67
|
backgroundColor: '#f4f6f8',
|
|
66
68
|
},
|
|
67
69
|
}),
|
|
68
|
-
}, error: error, errorMessage: errorMessage, value: value, onChange: !readOnly ? handleChange : undefined, InputProps: { ...InputProps, 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
|
|
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
|
|
69
71
|
? {
|
|
70
72
|
'& .MuiOutlinedInput-notchedOutline': {
|
|
71
73
|
border: 'none',
|
|
@@ -75,7 +77,7 @@ const InputFieldComponent = (props) => {
|
|
|
75
77
|
backgroundColor: '#f4f6f8',
|
|
76
78
|
},
|
|
77
79
|
}
|
|
78
|
-
: undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...InputProps, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })
|
|
80
|
+
: undefined, required: required, error: error, errorMessage: errorMessage, InputProps: { ...InputProps, endAdornment, readOnly: readOnly }, fullWidth: true, size: size ?? 'medium', type: property.type === 'integer' ? 'number' : 'text', multiline: property.type === 'string' && !readOnly && isMultiLineText, rows: isMultiLineText ? (rows ? rows : 3) : undefined, ...(additionalProps ?? {}) })
|
|
79
81
|
// Casting to `React.ReactNode` is necessary to resolve TypeScript errors
|
|
80
82
|
// due to compatibility issues with the outdated `react-input-mask` version
|
|
81
83
|
// and the newer `@types/react` package.
|
package/dist/published/components/custom/FormField/InputFieldComponent/InputFieldComponent.test.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
1
|
import { render, screen } from '@testing-library/react';
|
|
3
2
|
import { userEvent } from '@testing-library/user-event';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
6
5
|
import InputField from './InputFieldComponent';
|
|
7
|
-
expect.extend(matchers);
|
|
8
6
|
describe('Free-text input', () => {
|
|
9
7
|
// Right now an object property is required for this to function, but eventually this should go
|
|
10
8
|
// away.
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import * as matchers from '@testing-library/jest-dom/matchers';
|
|
2
1
|
import { render, screen } from '@testing-library/react';
|
|
3
2
|
import { userEvent } from '@testing-library/user-event';
|
|
4
3
|
import React from 'react';
|
|
5
4
|
import { describe, expect, it, vi } from 'vitest';
|
|
6
5
|
import Select from './Select';
|
|
7
|
-
expect.extend(matchers);
|
|
8
6
|
describe('Single select', () => {
|
|
9
7
|
// Right now an object property is required for this to function, but eventually this should go
|
|
10
8
|
// away.
|
|
@@ -3,11 +3,13 @@ import { TimePicker } from '@mui/x-date-pickers';
|
|
|
3
3
|
import { isUndefined, omit, padStart } from 'lodash';
|
|
4
4
|
import { DateTime } from 'luxon';
|
|
5
5
|
import React, { useEffect, useState } from 'react';
|
|
6
|
+
import { useFormContext } from '../../../../theme/hooks';
|
|
6
7
|
import { InvalidDate } from '../../../../util';
|
|
7
8
|
import { LocalizationProvider, TextField } from '../../../core';
|
|
8
9
|
import InputFieldComponent from '../InputFieldComponent/InputFieldComponent';
|
|
9
10
|
const TimePickerSelect = (props) => {
|
|
10
11
|
const { id, property, defaultValue, error, errorMessage, readOnly, required, size, onBlur, placeholder, additionalProps, } = props;
|
|
12
|
+
const { onAutosave } = useFormContext();
|
|
11
13
|
const values = defaultValue ? defaultValue.split(':') : undefined;
|
|
12
14
|
const hour = values ? parseInt(values[0]) : undefined;
|
|
13
15
|
const minute = values ? parseInt(values[1]) : undefined;
|
|
@@ -41,11 +43,22 @@ const TimePickerSelect = (props) => {
|
|
|
41
43
|
props.onChange && props.onChange(property.id, date, property);
|
|
42
44
|
}
|
|
43
45
|
};
|
|
46
|
+
const handleAccept = async () => {
|
|
47
|
+
// Trigger autosave when time is accepted (picker closes after selection)
|
|
48
|
+
if (onAutosave) {
|
|
49
|
+
try {
|
|
50
|
+
await onAutosave(id);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
console.error('Autosave failed:', error);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
44
57
|
return readOnly ? (React.createElement(InputFieldComponent, { ...{
|
|
45
58
|
...props,
|
|
46
59
|
defaultValue: value instanceof LocalDateTime ? DateTime.fromISO(value.toString()).toFormat('hh:mm a') : '',
|
|
47
60
|
} })) : (React.createElement(LocalizationProvider, null,
|
|
48
|
-
React.createElement(TimePicker, { value: value, onChange: handleChange, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder,
|
|
61
|
+
React.createElement(TimePicker, { value: value, onChange: handleChange, onAccept: handleAccept, renderInput: (params) => (React.createElement(TextField, { ...params, id: id, error: error, errorMessage: errorMessage, onBlur: onBlur, fullWidth: true, required: required, sx: { background: 'white', borderRadius: '8px' }, size: size ?? 'medium', placeholder: placeholder,
|
|
49
62
|
// merges MUI inputProps with additionalProps.inputProps in a way that still shows the value
|
|
50
63
|
inputProps: {
|
|
51
64
|
...params.inputProps,
|
|
@@ -16,7 +16,8 @@ export type FormRendererProps = BaseProps & {
|
|
|
16
16
|
form: EvokeForm;
|
|
17
17
|
title?: string | React.ReactNode;
|
|
18
18
|
instance?: ObjectInstance | Document;
|
|
19
|
-
onChange: (id: string, value: unknown) => void
|
|
19
|
+
onChange: (id: string, value: unknown) => void | Promise<void>;
|
|
20
|
+
onAutosave?: (fieldId: string) => void | Promise<void>;
|
|
20
21
|
associatedObject?: {
|
|
21
22
|
instanceId?: string;
|
|
22
23
|
propertyId?: string;
|