From 388369a5d7967f9c68338882ad96f2dad143aa39 Mon Sep 17 00:00:00 2001 From: delangle Date: Fri, 8 Apr 2022 10:03:27 +0200 Subject: [PATCH 1/6] [time picker] Only update time when updating input in TimePicker --- .../src/DesktopTimePicker/DesktopTimePicker.tsx | 7 +++++++ .../src/internals/components/KeyboardDateInput.tsx | 2 +- .../src/internals/hooks/useMaskedInput.tsx | 1 + .../x-date-pickers/src/internals/hooks/usePickerState.ts | 9 ++++++++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index e73cacdf9bdef..78b7895e71475 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -18,6 +18,13 @@ const valueManager: PickerStateValueManager = { parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: unknown, b: unknown) => utils.isEqual(a, b), + updateValue: (utils, prevValue, newValue) => { + if (prevValue == null) { + return newValue + } + + return utils.mergeDateAndTime(prevValue, newValue) + } }; export interface DesktopTimePickerProps diff --git a/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx b/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx index 8b2e857057659..d3b2d3aee44ae 100644 --- a/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx +++ b/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx @@ -28,7 +28,7 @@ export const KeyboardDateInput = React.forwardRef(function KeyboardDateInput( const textFieldProps = useMaskedInput(other); const adornmentPosition = InputAdornmentProps?.position || 'end'; const OpenPickerIcon = components.OpenPickerIcon || Calendar; - + return renderInput({ ref, inputRef, diff --git a/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx b/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx index 4c9be132c8894..afeb66f5b29c0 100644 --- a/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx +++ b/packages/x-date-pickers/src/internals/hooks/useMaskedInput.tsx @@ -79,6 +79,7 @@ export const useMaskedInput = ({ setInnerInputValue(finalString); const date = finalString === null ? null : utils.parse(finalString, inputFormat); + if (ignoreInvalidInputs && !utils.isValid(date)) { return; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts index c91edd30dbecf..ba999ae1465ee 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts @@ -12,6 +12,7 @@ export interface PickerStateValueManager { ) => boolean; emptyValue: TDateValue; parseInput: (utils: MuiPickersAdapter, value: TInputValue) => TDateValue; + updateValue?: (utils: MuiPickersAdapter, prevValue: TDateValue | null, value: TDateValue) => TDateValue } export type PickerSelectionState = 'partial' | 'shallow' | 'finish'; @@ -141,9 +142,15 @@ export const usePickerState = ( [acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, draftState.draft], ); + const handleInputChange = (date: TDateValue, keyboardInputValue?: string) => { + console.log(parsedDateValue, date) + const cleanDate = valueManager.updateValue ? valueManager.updateValue(utils, parsedDateValue, date) : date + onChange(cleanDate, keyboardInputValue) + } + const inputProps = React.useMemo( () => ({ - onChange, + onChange: handleInputChange, open: isOpen, rawValue: value, openPicker: () => setIsOpen(true), From f3dd675166481695d4ac0615d9d1acd0f942b398 Mon Sep 17 00:00:00 2001 From: delangle Date: Fri, 8 Apr 2022 10:14:04 +0200 Subject: [PATCH 2/6] Work --- .../src/DesktopTimePicker/DesktopTimePicker.tsx | 6 +++--- .../internals/components/KeyboardDateInput.tsx | 2 +- .../src/internals/hooks/usePickerState.ts | 16 +++++++++++----- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index 78b7895e71475..adb82e1d33327 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -20,11 +20,11 @@ const valueManager: PickerStateValueManager = { utils.isEqual(a, b), updateValue: (utils, prevValue, newValue) => { if (prevValue == null) { - return newValue + return newValue; } - return utils.mergeDateAndTime(prevValue, newValue) - } + return utils.mergeDateAndTime(prevValue, newValue); + }, }; export interface DesktopTimePickerProps diff --git a/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx b/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx index d3b2d3aee44ae..8b2e857057659 100644 --- a/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx +++ b/packages/x-date-pickers/src/internals/components/KeyboardDateInput.tsx @@ -28,7 +28,7 @@ export const KeyboardDateInput = React.forwardRef(function KeyboardDateInput( const textFieldProps = useMaskedInput(other); const adornmentPosition = InputAdornmentProps?.position || 'end'; const OpenPickerIcon = components.OpenPickerIcon || Calendar; - + return renderInput({ ref, inputRef, diff --git a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts index ba999ae1465ee..524a3fa1f96d9 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts @@ -12,7 +12,11 @@ export interface PickerStateValueManager { ) => boolean; emptyValue: TDateValue; parseInput: (utils: MuiPickersAdapter, value: TInputValue) => TDateValue; - updateValue?: (utils: MuiPickersAdapter, prevValue: TDateValue | null, value: TDateValue) => TDateValue + updateValue?: ( + utils: MuiPickersAdapter, + prevValue: TDateValue | null, + value: TDateValue, + ) => TDateValue; } export type PickerSelectionState = 'partial' | 'shallow' | 'finish'; @@ -143,10 +147,12 @@ export const usePickerState = ( ); const handleInputChange = (date: TDateValue, keyboardInputValue?: string) => { - console.log(parsedDateValue, date) - const cleanDate = valueManager.updateValue ? valueManager.updateValue(utils, parsedDateValue, date) : date - onChange(cleanDate, keyboardInputValue) - } + console.log(parsedDateValue, date); + const cleanDate = valueManager.updateValue + ? valueManager.updateValue(utils, parsedDateValue, date) + : date; + onChange(cleanDate, keyboardInputValue); + }; const inputProps = React.useMemo( () => ({ From af8593f9eb49b0e30be4f5a0986dbd31eca2d21f Mon Sep 17 00:00:00 2001 From: delangle Date: Fri, 8 Apr 2022 10:49:28 +0200 Subject: [PATCH 3/6] Work --- .../src/internals/hooks/usePickerState.ts | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts index 524a3fa1f96d9..e1758d250343b 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts @@ -54,7 +54,11 @@ export const usePickerState = ( return { committed: date, draft: date }; } - const parsedDateValue = valueManager.parseInput(utils, value); + const parsedDateValue = React.useMemo( + () => valueManager.parseInput(utils, value), + [valueManager, utils, value], + ); + const [draftState, dispatch] = React.useReducer( (state: Draftable, action: DraftAction): Draftable => { switch (action.type) { @@ -146,13 +150,15 @@ export const usePickerState = ( [acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, draftState.draft], ); - const handleInputChange = (date: TDateValue, keyboardInputValue?: string) => { - console.log(parsedDateValue, date); - const cleanDate = valueManager.updateValue - ? valueManager.updateValue(utils, parsedDateValue, date) - : date; - onChange(cleanDate, keyboardInputValue); - }; + const handleInputChange = React.useCallback( + (date: TDateValue, keyboardInputValue?: string) => { + const cleanDate = valueManager.updateValue + ? valueManager.updateValue(utils, parsedDateValue, date) + : date; + onChange(cleanDate, keyboardInputValue); + }, + [onChange, valueManager, parsedDateValue, utils], + ); const inputProps = React.useMemo( () => ({ @@ -161,7 +167,7 @@ export const usePickerState = ( rawValue: value, openPicker: () => setIsOpen(true), }), - [onChange, isOpen, value, setIsOpen], + [handleInputChange, isOpen, value, setIsOpen], ); const pickerState = { pickerProps, inputProps, wrapperProps }; From 9b55a11309c327ff780f251f8d9e7335e8a49322 Mon Sep 17 00:00:00 2001 From: delangle Date: Fri, 8 Apr 2022 11:06:36 +0200 Subject: [PATCH 4/6] Work --- .../DesktopTimePicker.test.tsx | 31 +++++++++++++++++++ .../src/internals/hooks/usePickerState.ts | 14 +++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.test.tsx index f4cd69c097462..f155e52b10cc4 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.test.tsx @@ -219,6 +219,37 @@ describe('', () => { ); }); + it('should only update the time change editing through the input', () => { + const handleChange = spy(); + render( + } + value={adapterToUse.date('2019-01-01T04:20:00.000')} + />, + ); + + // call `onChange` with an invalid time + fireEvent.change(screen.getByRole('textbox'), { + target: { value: ':00 pm' }, + }); + + expect(handleChange.callCount).to.equal(1); + expect(adapterToUse.isValid(handleChange.lastCall.args[0])).to.equal(false); + + // call `onChange` with a valid time + fireEvent.change(screen.getByRole('textbox'), { + target: { value: '07:00 pm' }, + }); + + expect(handleChange.callCount).to.equal(2); + expect(handleChange.lastCall.args[0]).toEqualDateTime( + adapterToUse.date('2019-01-01T19:00:00.000'), + ); + }); + context('input validation', () => { const shouldDisableTime: TimePickerProps['shouldDisableTime'] = (value) => value === 10; diff --git a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts index e1758d250343b..83c02cb73632a 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts @@ -59,6 +59,16 @@ export const usePickerState = ( [valueManager, utils, value], ); + const [lastValidDateValue, setLastValidDateValue] = React.useState( + parsedDateValue, + ); + + React.useEffect(() => { + if (parsedDateValue != null) { + setLastValidDateValue(parsedDateValue); + } + }, [parsedDateValue]); + const [draftState, dispatch] = React.useReducer( (state: Draftable, action: DraftAction): Draftable => { switch (action.type) { @@ -153,11 +163,11 @@ export const usePickerState = ( const handleInputChange = React.useCallback( (date: TDateValue, keyboardInputValue?: string) => { const cleanDate = valueManager.updateValue - ? valueManager.updateValue(utils, parsedDateValue, date) + ? valueManager.updateValue(utils, lastValidDateValue, date) : date; onChange(cleanDate, keyboardInputValue); }, - [onChange, valueManager, parsedDateValue, utils], + [onChange, valueManager, lastValidDateValue, utils], ); const inputProps = React.useMemo( From d5491801e6d6afe1352694005d79e8e997814ae5 Mon Sep 17 00:00:00 2001 From: delangle Date: Fri, 8 Apr 2022 16:25:34 +0200 Subject: [PATCH 5/6] Work --- .../src/DesktopTimePicker/DesktopTimePicker.tsx | 2 +- .../src/MobileTimePicker/MobileTimePicker.tsx | 7 +++++++ .../x-date-pickers/src/internals/hooks/usePickerState.ts | 6 +++--- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index adb82e1d33327..83f2d670922d3 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -18,7 +18,7 @@ const valueManager: PickerStateValueManager = { parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: unknown, b: unknown) => utils.isEqual(a, b), - updateValue: (utils, prevValue, newValue) => { + mergeOldAndNewValues: (utils, prevValue, newValue) => { if (prevValue == null) { return newValue; } diff --git a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx index 7d7be63fd8f5a..8c8181a31bff7 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx @@ -15,6 +15,13 @@ const valueManager: PickerStateValueManager = { parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: unknown, b: unknown) => utils.isEqual(a, b), + mergeOldAndNewValues: (utils, prevValue, newValue) => { + if (prevValue == null) { + return newValue; + } + + return utils.mergeDateAndTime(prevValue, newValue); + }, }; export interface MobileTimePickerProps diff --git a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts index 83c02cb73632a..11d26707a7dea 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts @@ -12,7 +12,7 @@ export interface PickerStateValueManager { ) => boolean; emptyValue: TDateValue; parseInput: (utils: MuiPickersAdapter, value: TInputValue) => TDateValue; - updateValue?: ( + mergeOldAndNewValues?: ( utils: MuiPickersAdapter, prevValue: TDateValue | null, value: TDateValue, @@ -162,8 +162,8 @@ export const usePickerState = ( const handleInputChange = React.useCallback( (date: TDateValue, keyboardInputValue?: string) => { - const cleanDate = valueManager.updateValue - ? valueManager.updateValue(utils, lastValidDateValue, date) + const cleanDate = valueManager.mergeOldAndNewValues + ? valueManager.mergeOldAndNewValues(utils, lastValidDateValue, date) : date; onChange(cleanDate, keyboardInputValue); }, From 181ef5daef0c5eb6e4132731b0998d0709263e43 Mon Sep 17 00:00:00 2001 From: delangle Date: Mon, 11 Apr 2022 10:31:40 +0200 Subject: [PATCH 6/6] Code review: Alex --- .../src/DesktopTimePicker/DesktopTimePicker.tsx | 2 +- .../src/MobileTimePicker/MobileTimePicker.tsx | 2 +- .../x-date-pickers/src/internals/hooks/usePickerState.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx index 83f2d670922d3..3df067c28a226 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/DesktopTimePicker.tsx @@ -18,7 +18,7 @@ const valueManager: PickerStateValueManager = { parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: unknown, b: unknown) => utils.isEqual(a, b), - mergeOldAndNewValues: (utils, prevValue, newValue) => { + valueReducer: (utils, prevValue, newValue) => { if (prevValue == null) { return newValue; } diff --git a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx index 8c8181a31bff7..3d735ad2c9746 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/MobileTimePicker.tsx @@ -15,7 +15,7 @@ const valueManager: PickerStateValueManager = { parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: unknown, b: unknown) => utils.isEqual(a, b), - mergeOldAndNewValues: (utils, prevValue, newValue) => { + valueReducer: (utils, prevValue, newValue) => { if (prevValue == null) { return newValue; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts index 11d26707a7dea..d4f6322e5957b 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePickerState.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePickerState.ts @@ -12,7 +12,7 @@ export interface PickerStateValueManager { ) => boolean; emptyValue: TDateValue; parseInput: (utils: MuiPickersAdapter, value: TInputValue) => TDateValue; - mergeOldAndNewValues?: ( + valueReducer?: ( utils: MuiPickersAdapter, prevValue: TDateValue | null, value: TDateValue, @@ -162,8 +162,8 @@ export const usePickerState = ( const handleInputChange = React.useCallback( (date: TDateValue, keyboardInputValue?: string) => { - const cleanDate = valueManager.mergeOldAndNewValues - ? valueManager.mergeOldAndNewValues(utils, lastValidDateValue, date) + const cleanDate = valueManager.valueReducer + ? valueManager.valueReducer(utils, lastValidDateValue, date) : date; onChange(cleanDate, keyboardInputValue); },