From 74de9ae598d45a635aec21648b69757d09f1cea4 Mon Sep 17 00:00:00 2001 From: Lukas Boll Date: Tue, 24 Oct 2023 10:24:50 +0200 Subject: [PATCH] fix(material): Improve usability of date renderers Previously, data wasn't cleared when the input field was emptied. This commit will set the value to 'undefined' when the input is empty or invalid upon blurring. During editing, the data is only updated when the current input is valid. Closes #2183 --- .../src/controls/MaterialDateControl.tsx | 29 +++++++++++++++++-- .../src/controls/MaterialDateTimeControl.tsx | 28 ++++++++++++++++-- .../src/controls/MaterialTimeControl.tsx | 29 +++++++++++++++++-- .../material-renderers/src/util/datejs.tsx | 27 +++++++++++++++-- .../renderers/MaterialDateControl.test.tsx | 4 +-- .../MaterialDateTimeControl.test.tsx | 4 +-- .../renderers/MaterialTimeControl.test.tsx | 6 ++-- 7 files changed, 108 insertions(+), 19 deletions(-) diff --git a/packages/material-renderers/src/controls/MaterialDateControl.tsx b/packages/material-renderers/src/controls/MaterialDateControl.tsx index a551f0e6a..c3ce86ac8 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,17 +80,33 @@ export const MaterialDateControl = (props: ControlProps) => { ? errors : null; const secondFormHelperText = showDescription && !isValid ? errors : null; + + const updateChild = useCallback(() => setKey((key) => key + 1), [setKey]); + 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 09124c6c0..6ae11ab6c 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(); @@ -66,6 +71,8 @@ export const MaterialDateTimeControl = (props: ControlProps) => { const format = appliedUiSchemaOptions.dateTimeFormat ?? 'YYYY-MM-DD HH:mm'; const saveFormat = appliedUiSchemaOptions.dateTimeSaveFormat ?? undefined; + const [key, setKey] = useState(0); + const views = appliedUiSchemaOptions.views ?? [ 'year', 'day', @@ -80,17 +87,32 @@ export const MaterialDateTimeControl = (props: ControlProps) => { : null; const secondFormHelperText = showDescription && !isValid ? errors : null; + const updateChild = useCallback(() => setKey((key) => key + 1), [setKey]); + 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 cf898845f..26b991336 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,16 +82,32 @@ export const MaterialTimeControl = (props: ControlProps) => { : null; const secondFormHelperText = showDescription && !isValid ? errors : null; + const updateChild = useCallback(() => setKey((key) => key + 1), [setKey]); + 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 d3f019cc1..ced911721 100644 --- a/packages/material-renderers/src/util/datejs.tsx +++ b/packages/material-renderers/src/util/datejs.tsx @@ -13,10 +13,31 @@ export const createOnChangeHandler = (time: dayjs.Dayjs) => { if (!time) { handleChange(path, undefined); - return; + } else if (time.toString() !== 'Invalid Date') { + const result = dayjs(time).format(saveFormat); + handleChange(path, result); } - const result = dayjs(time).format(saveFormat); - handleChange(path, result); + }; + +export const createOnBlurHandler = + ( + path: string, + handleChange: (path: string, value: any) => void, + format: string | undefined, + saveFormat: string | undefined, + rerenderChild: () => void, + resetFocus: () => void + ) => + (e: React.FocusEvent) => { + const date = e.target.value; + const formatedValue = dayjs(date, format); + if (formatedValue.toString() === 'Invalid Date') { + handleChange(path, undefined); + rerenderChild(); + } else { + handleChange(path, dayjs(formatedValue).format(saveFormat)); + } + resetFocus(); }; export const getData = ( diff --git a/packages/material-renderers/test/renderers/MaterialDateControl.test.tsx b/packages/material-renderers/test/renderers/MaterialDateControl.test.tsx index 5c0f51d29..7f89dd761 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 3e61b0d1f..d56a451f6 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 b3b6b358e..d3430016d 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'); }); });