Skip to content

Commit

Permalink
[time picker] Only update time when updating input in TimePicker (#4398)
Browse files Browse the repository at this point in the history
  • Loading branch information
flaviendelangle authored Apr 11, 2022
1 parent 5a7ebcf commit 279d8a9
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,37 @@ describe('<DesktopTimePicker />', () => {
);
});

it('should only update the time change editing through the input', () => {
const handleChange = spy();
render(
<DesktopTimePicker
ampm
onChange={handleChange}
open
renderInput={(params) => <TextField {...params} />}
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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ const valueManager: PickerStateValueManager<unknown, unknown> = {
parseInput: parsePickerInputValue,
areValuesEqual: (utils: MuiPickersAdapter<unknown>, a: unknown, b: unknown) =>
utils.isEqual(a, b),
valueReducer: (utils, prevValue, newValue) => {
if (prevValue == null) {
return newValue;
}

return utils.mergeDateAndTime(prevValue, newValue);
},
};

export interface DesktopTimePickerProps<TDate = unknown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ const valueManager: PickerStateValueManager<unknown, unknown> = {
parseInput: parsePickerInputValue,
areValuesEqual: (utils: MuiPickersAdapter<unknown>, a: unknown, b: unknown) =>
utils.isEqual(a, b),
valueReducer: (utils, prevValue, newValue) => {
if (prevValue == null) {
return newValue;
}

return utils.mergeDateAndTime(prevValue, newValue);
},
};

export interface MobileTimePickerProps<TDate = unknown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ export const useMaskedInput = ({
setInnerInputValue(finalString);

const date = finalString === null ? null : utils.parse(finalString, inputFormat);

if (ignoreInvalidInputs && !utils.isValid(date)) {
return;
}
Expand Down
35 changes: 32 additions & 3 deletions packages/x-date-pickers/src/internals/hooks/usePickerState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export interface PickerStateValueManager<TInputValue, TDateValue> {
) => boolean;
emptyValue: TDateValue;
parseInput: (utils: MuiPickersAdapter<TDateValue>, value: TInputValue) => TDateValue;
valueReducer?: (
utils: MuiPickersAdapter<TDateValue>,
prevValue: TDateValue | null,
value: TDateValue,
) => TDateValue;
}

export type PickerSelectionState = 'partial' | 'shallow' | 'finish';
Expand Down Expand Up @@ -49,7 +54,21 @@ export const usePickerState = <TInput, TDateValue>(
return { committed: date, draft: date };
}

const parsedDateValue = valueManager.parseInput(utils, value);
const parsedDateValue = React.useMemo(
() => valueManager.parseInput(utils, value),
[valueManager, utils, value],
);

const [lastValidDateValue, setLastValidDateValue] = React.useState<TDateValue | null>(
parsedDateValue,
);

React.useEffect(() => {
if (parsedDateValue != null) {
setLastValidDateValue(parsedDateValue);
}
}, [parsedDateValue]);

const [draftState, dispatch] = React.useReducer(
(state: Draftable<TDateValue>, action: DraftAction<TDateValue>): Draftable<TDateValue> => {
switch (action.type) {
Expand Down Expand Up @@ -141,14 +160,24 @@ export const usePickerState = <TInput, TDateValue>(
[acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, draftState.draft],
);

const handleInputChange = React.useCallback(
(date: TDateValue, keyboardInputValue?: string) => {
const cleanDate = valueManager.valueReducer
? valueManager.valueReducer(utils, lastValidDateValue, date)
: date;
onChange(cleanDate, keyboardInputValue);
},
[onChange, valueManager, lastValidDateValue, utils],
);

const inputProps = React.useMemo(
() => ({
onChange,
onChange: handleInputChange,
open: isOpen,
rawValue: value,
openPicker: () => setIsOpen(true),
}),
[onChange, isOpen, value, setIsOpen],
[handleInputChange, isOpen, value, setIsOpen],
);

const pickerState = { pickerProps, inputProps, wrapperProps };
Expand Down

0 comments on commit 279d8a9

Please sign in to comment.