diff --git a/packages/material-renderers/src/controls/MaterialDateControl.tsx b/packages/material-renderers/src/controls/MaterialDateControl.tsx index a551f0e6a6..952e7c8786 100644 --- a/packages/material-renderers/src/controls/MaterialDateControl.tsx +++ b/packages/material-renderers/src/controls/MaterialDateControl.tsx @@ -23,7 +23,7 @@ THE SOFTWARE. */ import merge from 'lodash/merge'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import { ControlProps, isDateControl, @@ -35,7 +35,12 @@ import { withJsonFormsControlProps } from '@jsonforms/react'; import { FormHelperText, Hidden } from '@mui/material'; import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { createOnChangeHandler, getData, useFocus } from '../util'; +import { + createOnBlurHandler, + createOnChangeHandler, + getData, + useFocus, +} from '../util'; export const MaterialDateControl = (props: ControlProps) => { const [focused, onFocus, onBlur] = useFocus(); @@ -62,6 +67,8 @@ export const MaterialDateControl = (props: ControlProps) => { appliedUiSchemaOptions.showUnfocusedDescription ); + const [key, setKey] = useState(0); + const format = appliedUiSchemaOptions.dateFormat ?? 'YYYY-MM-DD'; const saveFormat = appliedUiSchemaOptions.dateSaveFormat ?? 'YYYY-MM-DD'; @@ -73,20 +80,36 @@ export const MaterialDateControl = (props: ControlProps) => { ? errors : null; const secondFormHelperText = showDescription && !isValid ? errors : null; + + const updateChild = useCallback(() => setKey((key) => key + 1), []); + const onChange = useMemo( () => createOnChangeHandler(path, handleChange, saveFormat), [path, handleChange, saveFormat] ); + const onBlurHandler = useMemo( + () => + createOnBlurHandler( + path, + handleChange, + format, + saveFormat, + updateChild, + onBlur + ), + [path, handleChange, format, saveFormat, updateChild] + ); const value = getData(data, saveFormat); return ( { }, InputLabelProps: data ? { shrink: true } : undefined, onFocus: onFocus, - onBlur: onBlur, + onBlur: onBlurHandler, }, }} /> diff --git a/packages/material-renderers/src/controls/MaterialDateTimeControl.tsx b/packages/material-renderers/src/controls/MaterialDateTimeControl.tsx index 09124c6c03..a68f3143ad 100644 --- a/packages/material-renderers/src/controls/MaterialDateTimeControl.tsx +++ b/packages/material-renderers/src/controls/MaterialDateTimeControl.tsx @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import merge from 'lodash/merge'; import { ControlProps, @@ -35,7 +35,12 @@ import { withJsonFormsControlProps } from '@jsonforms/react'; import { FormHelperText, Hidden } from '@mui/material'; import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { createOnChangeHandler, getData, useFocus } from '../util'; +import { + createOnBlurHandler, + createOnChangeHandler, + getData, + useFocus, +} from '../util'; export const MaterialDateTimeControl = (props: ControlProps) => { const [focused, onFocus, onBlur] = useFocus(); @@ -64,7 +69,9 @@ export const MaterialDateTimeControl = (props: ControlProps) => { ); const format = appliedUiSchemaOptions.dateTimeFormat ?? 'YYYY-MM-DD HH:mm'; - const saveFormat = appliedUiSchemaOptions.dateTimeSaveFormat ?? undefined; + const saveFormat = appliedUiSchemaOptions.dateTimeSaveFormat ?? undefined; //'YYYY-MM-DDTHH:mm:Z'; + + const [key, setKey] = useState(0); const views = appliedUiSchemaOptions.views ?? [ 'year', @@ -80,20 +87,35 @@ export const MaterialDateTimeControl = (props: ControlProps) => { : null; const secondFormHelperText = showDescription && !isValid ? errors : null; + const updateChild = useCallback(() => setKey((key) => key + 1), []); + const onChange = useMemo( () => createOnChangeHandler(path, handleChange, saveFormat), [path, handleChange, saveFormat] ); + const onBlurHandler = useMemo( + () => + createOnBlurHandler( + path, + handleChange, + format, + saveFormat, + updateChild, + onBlur + ), + [path, handleChange, format, saveFormat, updateChild] + ); const value = getData(data, saveFormat); return ( { }, InputLabelProps: data ? { shrink: true } : undefined, onFocus: onFocus, - onBlur: onBlur, + onBlur: onBlurHandler, }, }} /> diff --git a/packages/material-renderers/src/controls/MaterialTimeControl.tsx b/packages/material-renderers/src/controls/MaterialTimeControl.tsx index cf898845f4..35308f91ab 100644 --- a/packages/material-renderers/src/controls/MaterialTimeControl.tsx +++ b/packages/material-renderers/src/controls/MaterialTimeControl.tsx @@ -22,7 +22,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import merge from 'lodash/merge'; import { ControlProps, @@ -35,7 +35,12 @@ import { withJsonFormsControlProps } from '@jsonforms/react'; import { FormHelperText, Hidden } from '@mui/material'; import { TimePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -import { createOnChangeHandler, getData, useFocus } from '../util'; +import { + createOnBlurHandler, + createOnChangeHandler, + getData, + useFocus, +} from '../util'; export const MaterialTimeControl = (props: ControlProps) => { const [focused, onFocus, onBlur] = useFocus(); @@ -56,6 +61,8 @@ export const MaterialTimeControl = (props: ControlProps) => { const appliedUiSchemaOptions = merge({}, config, uischema.options); const isValid = errors.length === 0; + const [key, setKey] = useState(0); + const showDescription = !isDescriptionHidden( visible, description, @@ -75,19 +82,35 @@ export const MaterialTimeControl = (props: ControlProps) => { : null; const secondFormHelperText = showDescription && !isValid ? errors : null; + const updateChild = useCallback(() => setKey((key) => key + 1), []); + const onChange = useMemo( () => createOnChangeHandler(path, handleChange, saveFormat), [path, handleChange, saveFormat] ); + const onBlurHandler = useMemo( + () => + createOnBlurHandler( + path, + handleChange, + format, + saveFormat, + updateChild, + onBlur + ), + [path, handleChange, format, saveFormat, updateChild] + ); const value = getData(data, saveFormat); + return ( { }, InputLabelProps: data ? { shrink: true } : undefined, onFocus: onFocus, - onBlur: onBlur, + onBlur: onBlurHandler, }, }} /> diff --git a/packages/material-renderers/src/util/datejs.tsx b/packages/material-renderers/src/util/datejs.tsx index d3f019cc16..f7226a416c 100644 --- a/packages/material-renderers/src/util/datejs.tsx +++ b/packages/material-renderers/src/util/datejs.tsx @@ -8,17 +8,58 @@ export const createOnChangeHandler = ( path: string, handleChange: (path: string, value: any) => void, - saveFormat: string | undefined + saveFormat: string ) => - (time: dayjs.Dayjs) => { - if (!time) { + (value: dayjs.Dayjs) => { + if (!value) { handleChange(path, undefined); - return; + } else if (value.toString() !== 'Invalid Date') { + const formatedDate = formatDate(value, saveFormat); + handleChange(path, formatedDate); } - const result = dayjs(time).format(saveFormat); - handleChange(path, result); }; +export const createOnBlurHandler = + ( + path: string, + handleChange: (path: string, value: any) => void, + format: string, + saveFormat: string, + rerenderChild: () => void, + onBlur: () => void + ) => + (e: React.FocusEvent) => { + const date = dayjs(e.target.value, format); + const formatedDate = formatDate(date, saveFormat); + if (formatedDate.toString() === 'Invalid Date') { + handleChange(path, undefined); + rerenderChild(); + } else { + handleChange(path, formatedDate); + } + onBlur(); + }; + +const formatDate = (date: dayjs.Dayjs, saveFormat: string) => { + let formatedDate = date.format(saveFormat); + // Workaround to address a bug in Dayjs (https://github.com/iamkun/dayjs/issues/1849) + if ( + date.year() < 1000 && + date.year() > 100 && + saveFormat.length > formatedDate.length + ) { + const indexOfYear = saveFormat.indexOf('YYYY'); + if (indexOfYear !== -1) { + formatedDate = [ + formatedDate.slice(0, indexOfYear), + 0, + formatedDate.slice(indexOfYear), + ].join(''); + } + } + return formatedDate; +}; + export const getData = ( data: any, saveFormat: string | undefined diff --git a/packages/material-renderers/test/renderers/MaterialDateControl.test.tsx b/packages/material-renderers/test/renderers/MaterialDateControl.test.tsx index 5c0f51d299..7f89dd7618 100644 --- a/packages/material-renderers/test/renderers/MaterialDateControl.test.tsx +++ b/packages/material-renderers/test/renderers/MaterialDateControl.test.tsx @@ -225,7 +225,7 @@ describe('Material date control', () => { ); const input = wrapper.find('input').first(); (input.getDOMNode() as HTMLInputElement).value = '1961-04-12'; - input.simulate('change', input); + input.simulate('blur', input); expect(onChangeData.data.foo).toBe('1961-04-12'); }); @@ -421,7 +421,7 @@ describe('Material date control', () => { expect(input.props().value).toBe('1980/06'); (input.getDOMNode() as HTMLInputElement).value = '1961/04'; - input.simulate('change', input); + input.simulate('blur', input); expect(onChangeData.data.foo).toBe('04---1961'); }); }); diff --git a/packages/material-renderers/test/renderers/MaterialDateTimeControl.test.tsx b/packages/material-renderers/test/renderers/MaterialDateTimeControl.test.tsx index 3e61b0d1f4..d56a451f68 100644 --- a/packages/material-renderers/test/renderers/MaterialDateTimeControl.test.tsx +++ b/packages/material-renderers/test/renderers/MaterialDateTimeControl.test.tsx @@ -228,7 +228,7 @@ describe('Material date time control', () => { ); const input = wrapper.find('input').first(); (input.getDOMNode() as HTMLInputElement).value = '1961-12-12 20:15'; - input.simulate('change', input); + input.simulate('blur', input); expect(onChangeData.data.foo).toBe(dayjs('1961-12-12 20:15').format()); }); @@ -427,7 +427,7 @@ describe('Material date time control', () => { expect(input.props().value).toBe('23-04-80 01:37:pm'); (input.getDOMNode() as HTMLInputElement).value = '10-12-05 11:22:am'; - input.simulate('change', input); + input.simulate('blur', input); expect(onChangeData.data.foo).toBe('2005/12/10 11:22 am'); }); }); diff --git a/packages/material-renderers/test/renderers/MaterialTimeControl.test.tsx b/packages/material-renderers/test/renderers/MaterialTimeControl.test.tsx index b3b6b358e9..d3430016db 100644 --- a/packages/material-renderers/test/renderers/MaterialTimeControl.test.tsx +++ b/packages/material-renderers/test/renderers/MaterialTimeControl.test.tsx @@ -225,8 +225,8 @@ describe('Material time control', () => { ); const input = wrapper.find('input').first(); (input.getDOMNode() as HTMLInputElement).value = '08:40'; - input.simulate('change', input); - expect(onChangeData.data.foo).toBe('08:40:05'); + input.simulate('blur', input); + expect(onChangeData.data.foo).toBe('08:40:00'); }); it('should update via action', () => { @@ -421,7 +421,7 @@ describe('Material time control', () => { expect(input.props().value).toBe('02-13'); (input.getDOMNode() as HTMLInputElement).value = '12-01'; - input.simulate('change', input); + input.simulate('blur', input); expect(onChangeData.data.foo).toBe('1//12 am'); }); });