From f9fe935563a6b7e82d61dfdf7c9c42214cfb0010 Mon Sep 17 00:00:00 2001 From: Todor Date: Thu, 9 Jul 2020 16:19:00 +0300 Subject: [PATCH 1/5] Don't run onChange if same date selected --- lib/src/DateRangePicker/DateRangePicker.tsx | 1 + lib/src/Picker/makePickerWithState.tsx | 2 ++ lib/src/_shared/hooks/usePickerState.ts | 12 +++++++++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/src/DateRangePicker/DateRangePicker.tsx b/lib/src/DateRangePicker/DateRangePicker.tsx index bed475ffd..15819bb7f 100644 --- a/lib/src/DateRangePicker/DateRangePicker.tsx +++ b/lib/src/DateRangePicker/DateRangePicker.tsx @@ -65,6 +65,7 @@ export function makeRangePicker(name: string, Wrap emptyValue: [null, null], parseInput: parseRangeInputValue, areValuesEqual: (utils, a, b) => utils.isEqual(a[0], b[0]) && utils.isEqual(a[1], b[1]), + areDaysEqual: (utils, a, b) => utils.isSameDay(a[0], b[0]) && utils.isSameDay(a[1], b[1]), }; function RangePickerWithStateAndWrapper({ diff --git a/lib/src/Picker/makePickerWithState.tsx b/lib/src/Picker/makePickerWithState.tsx index 5f4495d7b..9cc934323 100644 --- a/lib/src/Picker/makePickerWithState.tsx +++ b/lib/src/Picker/makePickerWithState.tsx @@ -38,6 +38,8 @@ const valueManager: PickerStateValueManager parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: MaterialUiPickersDate, b: MaterialUiPickersDate) => utils.isEqual(a, b), + areDaysEqual: (utils: MuiPickersAdapter, a: MaterialUiPickersDate, b: MaterialUiPickersDate) => + utils.isSameDay(a, b), }; export function makePickerWithStateAndWrapper< diff --git a/lib/src/_shared/hooks/usePickerState.ts b/lib/src/_shared/hooks/usePickerState.ts index 27184c90c..fb7da29e6 100644 --- a/lib/src/_shared/hooks/usePickerState.ts +++ b/lib/src/_shared/hooks/usePickerState.ts @@ -12,6 +12,11 @@ export interface PickerStateValueManager { valueLeft: TDateValue, valueRight: TDateValue ) => boolean; + areDaysEqual: ( + utils: MuiPickersAdapter, + valueLeft: TDateValue, + valueRight: TDateValue + ) => boolean; } export type PickerSelectionState = 'partial' | 'shallow' | 'finish'; @@ -103,8 +108,9 @@ export function usePickerState( wrapperVariant: WrapperVariant, selectionState: PickerSelectionState = 'partial' ) => { - setPickerDate(newDate); - + if (!valueManager.areDaysEqual(utils, newDate, pickerDate)) { + setPickerDate(newDate); + } if (selectionState === 'partial') { acceptDate(newDate, false); } @@ -117,7 +123,7 @@ export function usePickerState( // if selectionState === "shallow" do nothing (we already update picker state) }, }), - [acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, pickerDate] + [acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, pickerDate, utils, valueManager] ); const inputProps = React.useMemo( From 4ab07329a2d5e89717668d155facd34d1ff0ab5d Mon Sep 17 00:00:00 2001 From: Todor Date: Tue, 14 Jul 2020 10:43:54 +0300 Subject: [PATCH 2/5] Add test for selecting same date --- docs/prop-types.json | 24 +++++++++---------- lib/src/DateRangePicker/DateRangePicker.tsx | 1 - lib/src/Picker/makePickerWithState.tsx | 2 -- .../__tests__/DatePickerTestingLib.test.tsx | 18 ++++++++++++++ lib/src/_shared/hooks/usePickerState.ts | 11 ++------- lib/src/views/Calendar/Day.tsx | 2 +- 6 files changed, 33 insertions(+), 25 deletions(-) diff --git a/docs/prop-types.json b/docs/prop-types.json index 0ccc3a5d1..549957591 100644 --- a/docs/prop-types.json +++ b/docs/prop-types.json @@ -320,7 +320,7 @@ }, "onError": { "defaultValue": null, - "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\nThis can be used to render appropriate form error.\n\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", + "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\r\nThis can be used to render appropriate form error.\r\n\r\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", "name": "onError", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/hooks/useValidation.ts", @@ -879,7 +879,7 @@ }, "renderInput": { "defaultValue": null, - "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\nrenderInput={props => }\n````", + "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.\r\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\rrenderInput={props => }\r````", "name": "renderInput", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/PureDateInput.tsx", @@ -1004,7 +1004,7 @@ }, "dateAdapter": { "defaultValue": null, - "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\n```", + "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\r\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\r\n```", "name": "dateAdapter", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/withDateAdapterProp.tsx", @@ -1292,7 +1292,7 @@ }, "onError": { "defaultValue": null, - "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\nThis can be used to render appropriate form error.\n\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", + "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\r\nThis can be used to render appropriate form error.\r\n\r\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", "name": "onError", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/hooks/useValidation.ts", @@ -1571,7 +1571,7 @@ }, "renderInput": { "defaultValue": null, - "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\nrenderInput={props => }\n````", + "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.\r\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\rrenderInput={props => }\r````", "name": "renderInput", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/PureDateInput.tsx", @@ -1696,7 +1696,7 @@ }, "dateAdapter": { "defaultValue": null, - "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\n```", + "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\r\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\r\n```", "name": "dateAdapter", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/withDateAdapterProp.tsx", @@ -1974,7 +1974,7 @@ }, "onError": { "defaultValue": null, - "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\nThis can be used to render appropriate form error.\n\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", + "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\r\nThis can be used to render appropriate form error.\r\n\r\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", "name": "onError", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/hooks/useValidation.ts", @@ -2619,7 +2619,7 @@ }, "renderInput": { "defaultValue": null, - "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\nrenderInput={props => }\n````", + "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `props` argument of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api) that you need to forward.\r\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\rrenderInput={props => }\r````", "name": "renderInput", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/PureDateInput.tsx", @@ -2744,7 +2744,7 @@ }, "dateAdapter": { "defaultValue": null, - "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\n```", + "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\r\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\r\n```", "name": "dateAdapter", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/withDateAdapterProp.tsx", @@ -3472,7 +3472,7 @@ }, "onError": { "defaultValue": null, - "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\nThis can be used to render appropriate form error.\n\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", + "description": "Callback that fired when input value or new `value` prop validation returns **new** validation error (or value is valid after error).\nIn case of validation error detected `reason` prop return non-null value and `TextField` must be displayed in `error` state.\r\nThis can be used to render appropriate form error.\r\n\r\n[Read the guide](https://next.material-ui-pickers.dev/guides/forms) about form integration and error displaying.\n@DateIOType", "name": "onError", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/hooks/useValidation.ts", @@ -3485,7 +3485,7 @@ }, "renderInput": { "defaultValue": null, - "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `startProps` and `endProps` arguments of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api),\nthat you need to forward to the range start/end inputs respectively.\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\n (\n \n \n to \n \n ;\n)}\n/>\n````", + "description": "The `renderInput` prop allows you to customize the rendered input.\nThe `startProps` and `endProps` arguments of this render prop contains props of [TextField](https://material-ui.com/api/text-field/#textfield-api),\r\nthat you need to forward to the range start/end inputs respectively.\r\nPay specific attention to the `ref` and `inputProps` keys.\n@example ```jsx\r (\r \r \r to \r \r ;\r)}\r/>\r````", "name": "renderInput", "parent": { "fileName": "material-ui-pickers/lib/src/DateRangePicker/DateRangePickerInput.tsx", @@ -3649,7 +3649,7 @@ }, "dateAdapter": { "defaultValue": null, - "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\n```", + "description": "Allows to pass configured date-io adapter directly. More info [here](https://material-ui-pickers.dev/guides/date-adapter-passing)\n```jsx\r\ndateAdapter={new DateFnsAdapter({ locale: ruLocale })}\r\n```", "name": "dateAdapter", "parent": { "fileName": "material-ui-pickers/lib/src/_shared/withDateAdapterProp.tsx", diff --git a/lib/src/DateRangePicker/DateRangePicker.tsx b/lib/src/DateRangePicker/DateRangePicker.tsx index 15819bb7f..bed475ffd 100644 --- a/lib/src/DateRangePicker/DateRangePicker.tsx +++ b/lib/src/DateRangePicker/DateRangePicker.tsx @@ -65,7 +65,6 @@ export function makeRangePicker(name: string, Wrap emptyValue: [null, null], parseInput: parseRangeInputValue, areValuesEqual: (utils, a, b) => utils.isEqual(a[0], b[0]) && utils.isEqual(a[1], b[1]), - areDaysEqual: (utils, a, b) => utils.isSameDay(a[0], b[0]) && utils.isSameDay(a[1], b[1]), }; function RangePickerWithStateAndWrapper({ diff --git a/lib/src/Picker/makePickerWithState.tsx b/lib/src/Picker/makePickerWithState.tsx index 9cc934323..5f4495d7b 100644 --- a/lib/src/Picker/makePickerWithState.tsx +++ b/lib/src/Picker/makePickerWithState.tsx @@ -38,8 +38,6 @@ const valueManager: PickerStateValueManager parseInput: parsePickerInputValue, areValuesEqual: (utils: MuiPickersAdapter, a: MaterialUiPickersDate, b: MaterialUiPickersDate) => utils.isEqual(a, b), - areDaysEqual: (utils: MuiPickersAdapter, a: MaterialUiPickersDate, b: MaterialUiPickersDate) => - utils.isSameDay(a, b), }; export function makePickerWithStateAndWrapper< diff --git a/lib/src/__tests__/DatePickerTestingLib.test.tsx b/lib/src/__tests__/DatePickerTestingLib.test.tsx index bde3f4057..ad062a992 100644 --- a/lib/src/__tests__/DatePickerTestingLib.test.tsx +++ b/lib/src/__tests__/DatePickerTestingLib.test.tsx @@ -78,4 +78,22 @@ describe('', () => { expect(screen.queryByRole('dialog')).toBeInTheDocument(); }); + + it('Does not call onChange if same date selected', async () => { + render( + } + /> + ); + + fireEvent.click(screen.getByLabelText('Choose date, selected date is Jan 1, 2018')); + await waitFor(() => screen.getByRole('dialog')); + + fireEvent.click(screen.getByLabelText('Jan 1, 2018')); + expect(screen.queryByRole('dialog')).toBeInTheDocument(); + }); }); diff --git a/lib/src/_shared/hooks/usePickerState.ts b/lib/src/_shared/hooks/usePickerState.ts index fb7da29e6..0734212cb 100644 --- a/lib/src/_shared/hooks/usePickerState.ts +++ b/lib/src/_shared/hooks/usePickerState.ts @@ -12,11 +12,6 @@ export interface PickerStateValueManager { valueLeft: TDateValue, valueRight: TDateValue ) => boolean; - areDaysEqual: ( - utils: MuiPickersAdapter, - valueLeft: TDateValue, - valueRight: TDateValue - ) => boolean; } export type PickerSelectionState = 'partial' | 'shallow' | 'finish'; @@ -108,9 +103,7 @@ export function usePickerState( wrapperVariant: WrapperVariant, selectionState: PickerSelectionState = 'partial' ) => { - if (!valueManager.areDaysEqual(utils, newDate, pickerDate)) { - setPickerDate(newDate); - } + setPickerDate(newDate); if (selectionState === 'partial') { acceptDate(newDate, false); } @@ -123,7 +116,7 @@ export function usePickerState( // if selectionState === "shallow" do nothing (we already update picker state) }, }), - [acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, pickerDate, utils, valueManager] + [acceptDate, disableCloseOnSelect, isMobileKeyboardViewOpen, pickerDate] ); const inputProps = React.useMemo( diff --git a/lib/src/views/Calendar/Day.tsx b/lib/src/views/Calendar/Day.tsx index 58c5c9c55..6f79ca7f1 100644 --- a/lib/src/views/Calendar/Day.tsx +++ b/lib/src/views/Calendar/Day.tsx @@ -180,7 +180,7 @@ const PureDay: React.FC = ({ }; const handleClick = (event: React.MouseEvent) => { - if (!disabled) { + if (!disabled && !selected) { onDaySelect(day, 'finish'); } From 1e3528a9a0470bb68afc23f85f1c125e6e0adddf Mon Sep 17 00:00:00 2001 From: Todor Date: Tue, 14 Jul 2020 20:35:55 +0300 Subject: [PATCH 3/5] Add allowSameDateSelection --- docs/prop-types.json | 15 +++++++++++++++ lib/src/DateRangePicker/DateRangePickerDay.tsx | 1 + lib/src/__tests__/DatePickerTestingLib.test.tsx | 6 ++++-- lib/src/views/Calendar/Day.tsx | 10 +++++++++- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/prop-types.json b/docs/prop-types.json index 37536e23e..307246cbe 100644 --- a/docs/prop-types.json +++ b/docs/prop-types.json @@ -4110,6 +4110,21 @@ "type": { "name": "boolean" } + }, + "allowSameDateSelection": { + "defaultValue": { + "value": false + }, + "description": "Allow selecting the same date (fire onChange even if same date is selected).", + "name": "allowSameDateSelection", + "parent": { + "fileName": "material-ui-pickers/lib/src/views/Calendar/Day.tsx", + "name": "DayProps" + }, + "required": false, + "type": { + "name": "boolean" + } } }, "ClockView": { diff --git a/lib/src/DateRangePicker/DateRangePickerDay.tsx b/lib/src/DateRangePicker/DateRangePickerDay.tsx index 92d2cc434..cc14663e3 100644 --- a/lib/src/DateRangePicker/DateRangePickerDay.tsx +++ b/lib/src/DateRangePicker/DateRangePickerDay.tsx @@ -141,6 +141,7 @@ export const PureDateRangeDay = ({ {...other} day={day} selected={selected} + allowSameDateSelection={true} inCurrentMonth={inCurrentMonth} data-mui-test="DateRangeDay" className={clsx( diff --git a/lib/src/__tests__/DatePickerTestingLib.test.tsx b/lib/src/__tests__/DatePickerTestingLib.test.tsx index ad062a992..955b1d02c 100644 --- a/lib/src/__tests__/DatePickerTestingLib.test.tsx +++ b/lib/src/__tests__/DatePickerTestingLib.test.tsx @@ -80,12 +80,14 @@ describe('', () => { }); it('Does not call onChange if same date selected', async () => { + const onChangeMock = jest.fn(); + render( } /> ); @@ -94,6 +96,6 @@ describe('', () => { await waitFor(() => screen.getByRole('dialog')); fireEvent.click(screen.getByLabelText('Jan 1, 2018')); - expect(screen.queryByRole('dialog')).toBeInTheDocument(); + expect(onChangeMock).toBe(undefined); }); }); diff --git a/lib/src/views/Calendar/Day.tsx b/lib/src/views/Calendar/Day.tsx index d901d3144..517024174 100644 --- a/lib/src/views/Calendar/Day.tsx +++ b/lib/src/views/Calendar/Day.tsx @@ -123,6 +123,11 @@ export interface DayProps extends ExtendMui { * @default false */ disableHighlightToday?: boolean; + /** + * Allow selecting the same date (fire onChange even if same date is selected). + * @default false + */ + allowSameDateSelection?: boolean; onDayFocus: (day: unknown) => void; onDaySelect: (day: unknown, isFinish: PickerSelectionState) => void; } @@ -132,6 +137,7 @@ const PureDay: React.FC = ({ className, day, disabled, + allowSameDateSelection = false, disableHighlightToday = false, disableMargin = false, focusable = false, @@ -179,7 +185,9 @@ const PureDay: React.FC = ({ }; const handleClick = (event: React.MouseEvent) => { - if (!disabled && !selected) { + if (!allowSameDateSelection && selected) return; + + if (!disabled) { onDaySelect(day, 'finish'); } From b9aa6cc9790963f6593533717c15f2c2deaeb793 Mon Sep 17 00:00:00 2001 From: Todor Date: Tue, 14 Jul 2020 21:04:01 +0300 Subject: [PATCH 4/5] Change onChange not called test --- lib/src/__tests__/DatePickerTestingLib.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/__tests__/DatePickerTestingLib.test.tsx b/lib/src/__tests__/DatePickerTestingLib.test.tsx index 955b1d02c..47512ad14 100644 --- a/lib/src/__tests__/DatePickerTestingLib.test.tsx +++ b/lib/src/__tests__/DatePickerTestingLib.test.tsx @@ -96,6 +96,6 @@ describe('', () => { await waitFor(() => screen.getByRole('dialog')); fireEvent.click(screen.getByLabelText('Jan 1, 2018')); - expect(onChangeMock).toBe(undefined); + expect(onChangeMock).not.toHaveBeenCalled(); }); }); From db16c0b7bf878ba01946eab331584eab4e610d98 Mon Sep 17 00:00:00 2001 From: Todor Date: Tue, 14 Jul 2020 21:39:08 +0300 Subject: [PATCH 5/5] Address PR comments. --- lib/src/__tests__/DatePickerTestingLib.test.tsx | 3 +-- lib/src/views/Calendar/Day.tsx | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/__tests__/DatePickerTestingLib.test.tsx b/lib/src/__tests__/DatePickerTestingLib.test.tsx index 47512ad14..29c613423 100644 --- a/lib/src/__tests__/DatePickerTestingLib.test.tsx +++ b/lib/src/__tests__/DatePickerTestingLib.test.tsx @@ -79,13 +79,12 @@ describe('', () => { expect(screen.queryByRole('dialog')).toBeInTheDocument(); }); - it('Does not call onChange if same date selected', async () => { + it('does not call onChange if same date selected', async () => { const onChangeMock = jest.fn(); render( } diff --git a/lib/src/views/Calendar/Day.tsx b/lib/src/views/Calendar/Day.tsx index 517024174..bb75bcb13 100644 --- a/lib/src/views/Calendar/Day.tsx +++ b/lib/src/views/Calendar/Day.tsx @@ -134,10 +134,10 @@ export interface DayProps extends ExtendMui { const PureDay: React.FC = ({ allowKeyboardControl, + allowSameDateSelection = false, className, day, disabled, - allowSameDateSelection = false, disableHighlightToday = false, disableMargin = false, focusable = false,