From aeddc0d26b154dd8cc6e774b36ef75a043d6106f Mon Sep 17 00:00:00 2001 From: flavien Date: Mon, 2 Dec 2024 17:11:59 +0100 Subject: [PATCH 1/7] [pickers] Add new method onToggleOpening to the picker public context --- .../date-pickers/calendar-systems/AdapterHijri.js | 10 +--------- .../date-pickers/calendar-systems/AdapterHijri.tsx | 10 +--------- .../custom-field/BrowserV7SingleInputRangeField.js | 9 +-------- .../custom-field/BrowserV7SingleInputRangeField.tsx | 9 +-------- .../custom-field/JoyV6SingleInputRangeField.js | 9 +-------- .../custom-field/JoyV6SingleInputRangeField.tsx | 9 +-------- .../behavior-button/MaterialDatePicker.js | 10 +--------- .../behavior-button/MaterialDatePicker.tsx | 10 +--------- .../behavior-button/MaterialDateRangePicker.js | 10 +--------- .../behavior-button/MaterialDateRangePicker.tsx | 10 +--------- .../MaterialDatePicker.js | 10 +--------- .../MaterialDatePicker.tsx | 10 +--------- .../components/overview/mainDemo/PickerButton.tsx | 10 +--------- .../src/internals/components/PickerProvider.tsx | 5 +++++ .../internals/hooks/usePicker/usePickerProvider.ts | 11 +++++++++++ 15 files changed, 29 insertions(+), 113 deletions(-) diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.js b/docs/data/date-pickers/calendar-systems/AdapterHijri.js index fa738ee1e6e3..f146aaa31ba3 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.js +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.js @@ -46,14 +46,6 @@ function ButtonDateTimeField(props) { props: internalProps, }); - const handleTogglePicker = (event) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const valueStr = value == null ? parsedFormat : value.format(format); return ( @@ -62,7 +54,7 @@ function ButtonDateTimeField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx index aa9b24d67f79..319f93f591ac 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx @@ -50,14 +50,6 @@ function ButtonDateTimeField(props: DateTimePickerFieldProps) { props: internalProps, }); - const handleTogglePicker = (event: React.UIEvent) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const valueStr = value == null ? parsedFormat : value.format(format); return ( @@ -66,7 +58,7 @@ function ButtonDateTimeField(props: DateTimePickerFieldProps) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js index a86335cd7dd7..7adc0f2e453a 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js @@ -87,13 +87,6 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { const { slots, slotProps, ...other } = props; const pickerContext = usePickerContext(); - const handleTogglePicker = (event) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; const textFieldProps = useSlotProps({ elementType: 'input', @@ -106,7 +99,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { ...textFieldProps.InputProps, endAdornment: ( - + diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx index 2c1c935104e7..e6e44e9656e4 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx @@ -115,13 +115,6 @@ const BrowserSingleInputDateRangeField = React.forwardRef( const { slots, slotProps, ...other } = props; const pickerContext = usePickerContext(); - const handleTogglePicker = (event: React.UIEvent) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; const textFieldProps: typeof props = useSlotProps({ elementType: 'input', @@ -134,7 +127,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef( ...textFieldProps.InputProps, endAdornment: ( - + diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js index dd5ee96d6f52..95be4b633756 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js @@ -72,13 +72,6 @@ const JoySingleInputDateRangeField = React.forwardRef((props, ref) => { const { slots, slotProps, ...other } = props; const pickerContext = usePickerContext(); - const handleTogglePicker = (event) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; const textFieldProps = useSlotProps({ elementType: FormControl, @@ -105,7 +98,7 @@ const JoySingleInputDateRangeField = React.forwardRef((props, ref) => { ref={ref} endDecorator={ { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; const textFieldProps: JoySingleInputDateRangeFieldProps = useSlotProps({ elementType: FormControl, @@ -136,7 +129,7 @@ const JoySingleInputDateRangeField = React.forwardRef( ref={ref} endDecorator={ { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const valueStr = value == null ? parsedFormat : value.format(format); return ( @@ -50,7 +42,7 @@ function ButtonDateField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx index ac64ebd3a5f0..d19c72fb95b0 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx @@ -38,14 +38,6 @@ function ButtonDateField(props: DatePickerFieldProps) { props: internalProps, }); - const handleTogglePicker = (event: React.UIEvent) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const valueStr = value == null ? parsedFormat : value.format(format); return ( @@ -54,7 +46,7 @@ function ButtonDateField(props: DatePickerFieldProps) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js index 7c56f7a2693a..d2e26d720904 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js @@ -36,14 +36,6 @@ function ButtonDateRangeField(props) { props: internalProps, }); - const handleTogglePicker = (event) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const formattedValue = (value ?? [null, null]) .map((date) => (date == null ? parsedFormat : date.format(format))) .join(' – '); @@ -54,7 +46,7 @@ function ButtonDateRangeField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${formattedValue}` : formattedValue} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx index fbc0cd45c51e..b4a39597513e 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx @@ -40,14 +40,6 @@ function ButtonDateRangeField(props: DateRangePickerFieldProps) { props: internalProps, }); - const handleTogglePicker = (event: React.UIEvent) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const formattedValue = (value ?? [null, null]) .map((date: Dayjs) => (date == null ? parsedFormat : date.format(format))) .join(' – '); @@ -58,7 +50,7 @@ function ButtonDateRangeField(props: DateRangePickerFieldProps) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${formattedValue}` : formattedValue} diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js index 1e7d773f6fdf..96e29f79763a 100644 --- a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js @@ -26,14 +26,6 @@ function ReadOnlyDateField(props) { props: internalProps, }); - const handleTogglePicker = (event) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - return ( ); } diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx index 1fd235a77819..cc2b1180fdf7 100644 --- a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx @@ -30,14 +30,6 @@ function ReadOnlyDateField(props: DatePickerFieldProps) { props: internalProps, }); - const handleTogglePicker = (event: React.UIEvent) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - return ( ); } diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index d47d045fb1c6..59043f6ea467 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -23,14 +23,6 @@ function ButtonDateField(props: DatePickerFieldProps) { props: internalProps, }); - const handleTogglePicker = (event: React.UIEvent) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - const valueStr = value == null ? parsedFormat : value.format(format); return ( @@ -43,7 +35,7 @@ function ButtonDateField(props: DatePickerFieldProps) { fullWidth color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + onClick={pickerContext.onToggleOpening} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx index 85eeaf1f04d8..4fa7eb07cbf1 100644 --- a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx @@ -54,6 +54,11 @@ export interface PickerContextValue { * @param {React.UIEvent} event The DOM event that triggered the change. */ onClose: (event: React.UIEvent) => void; + /** + * Close the picker if it's open, open it if it's closed. + * @param {React.UIEvent} event The DOM event that triggered the change. + */ + onToggleOpening: (event: React.UIEvent) => void; /** * `true` if the picker is open, `false` otherwise. */ diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts index 92964972c29a..6c4746dbc8b7 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -1,5 +1,6 @@ import * as React from 'react'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; +import useEventCallback from '@mui/utils/useEventCallback'; import { PickerOwnerState } from '../../../models'; import { PickerValueManager, UsePickerValueResponse } from './usePickerValue.types'; import { @@ -67,6 +68,14 @@ export function usePickerProvider( const utils = useUtils(); const orientation = usePickerOrientation(views, props.orientation); + const handleTogglePicker = useEventCallback((event: React.UIEvent) => { + if (pickerValueResponse.open) { + pickerValueResponse.actions.onClose(event); + } else { + pickerValueResponse.actions.onOpen(event); + } + }); + const ownerState = React.useMemo( () => ({ isPickerValueEmpty: valueManager.areValuesEqual( @@ -96,6 +105,7 @@ export function usePickerProvider( () => ({ onOpen: pickerValueResponse.actions.onOpen, onClose: pickerValueResponse.actions.onClose, + onToggleOpening: handleTogglePicker, open: pickerValueResponse.open, disabled: props.disabled ?? false, readOnly: props.readOnly ?? false, @@ -105,6 +115,7 @@ export function usePickerProvider( [ pickerValueResponse.actions.onOpen, pickerValueResponse.actions.onClose, + handleTogglePicker, pickerValueResponse.open, variant, orientation, From 26b4206a8524e5fd7662dd716b966eb66e552bc6 Mon Sep 17 00:00:00 2001 From: flavien Date: Wed, 4 Dec 2024 08:43:36 +0100 Subject: [PATCH 2/7] Replace all the opening methods with setOpen --- .../calendar-systems/AdapterHijri.js | 2 +- .../calendar-systems/AdapterHijri.tsx | 2 +- .../BrowserV7SingleInputRangeField.js | 2 +- .../BrowserV7SingleInputRangeField.tsx | 2 +- .../JoyV6SingleInputRangeField.js | 2 +- .../JoyV6SingleInputRangeField.tsx | 2 +- .../behavior-button/MaterialDatePicker.js | 2 +- .../behavior-button/MaterialDatePicker.tsx | 2 +- .../MaterialDateRangePicker.js | 2 +- .../MaterialDateRangePicker.tsx | 2 +- .../MaterialDatePicker.js | 2 +- .../MaterialDatePicker.tsx | 2 +- .../overview/mainDemo/PickerButton.tsx | 2 +- .../internals/components/PickerProvider.tsx | 22 +---------- .../src/internals/hooks/useOpenState.ts | 19 ++++----- .../internals/hooks/usePicker/usePicker.ts | 2 +- .../hooks/usePicker/usePicker.types.ts | 2 +- .../hooks/usePicker/usePickerProvider.ts | 39 +++++-------------- .../hooks/usePicker/usePickerValue.ts | 33 +++++++++++----- .../hooks/usePicker/usePickerValue.types.ts | 18 +++++++++ 20 files changed, 78 insertions(+), 83 deletions(-) diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.js b/docs/data/date-pickers/calendar-systems/AdapterHijri.js index f146aaa31ba3..377759e11375 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.js +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.js @@ -54,7 +54,7 @@ function ButtonDateTimeField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx index 319f93f591ac..704dbee32ae7 100644 --- a/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx +++ b/docs/data/date-pickers/calendar-systems/AdapterHijri.tsx @@ -58,7 +58,7 @@ function ButtonDateTimeField(props: DateTimePickerFieldProps) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js index 7adc0f2e453a..edfed553188e 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.js @@ -99,7 +99,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef((props, ref) => { ...textFieldProps.InputProps, endAdornment: ( - + pickerContext.setOpen((prev) => !prev)}> diff --git a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx index e6e44e9656e4..c19472a59405 100644 --- a/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/BrowserV7SingleInputRangeField.tsx @@ -127,7 +127,7 @@ const BrowserSingleInputDateRangeField = React.forwardRef( ...textFieldProps.InputProps, endAdornment: ( - + pickerContext.setOpen((prev) => !prev)}> diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js index 95be4b633756..35cf00fa9a7e 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.js @@ -98,7 +98,7 @@ const JoySingleInputDateRangeField = React.forwardRef((props, ref) => { ref={ref} endDecorator={ pickerContext.setOpen((prev) => !prev)} variant="plain" color="neutral" sx={{ marginLeft: 2.5 }} diff --git a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx index 55554a38e418..9cf3261ab611 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx @@ -129,7 +129,7 @@ const JoySingleInputDateRangeField = React.forwardRef( ref={ref} endDecorator={ pickerContext.setOpen((prev) => !prev)} variant="plain" color="neutral" sx={{ marginLeft: 2.5 }} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js index 9ed91020e8ec..0e30e8d2605a 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js @@ -42,7 +42,7 @@ function ButtonDateField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx index d19c72fb95b0..1da1b675decd 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.tsx @@ -46,7 +46,7 @@ function ButtonDateField(props: DatePickerFieldProps) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js index d2e26d720904..0884deadb8b4 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.js @@ -46,7 +46,7 @@ function ButtonDateRangeField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${formattedValue}` : formattedValue} diff --git a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx index b4a39597513e..9361a51c256a 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDateRangePicker.tsx @@ -50,7 +50,7 @@ function ButtonDateRangeField(props: DateRangePickerFieldProps) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${formattedValue}` : formattedValue} diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js index 96e29f79763a..c5637b320e0e 100644 --- a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.js @@ -38,7 +38,7 @@ function ReadOnlyDateField(props) { sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, }} error={hasValidationError} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} /> ); } diff --git a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx index cc2b1180fdf7..a42da5e10407 100644 --- a/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx +++ b/docs/data/date-pickers/custom-field/behavior-read-only-text-field/MaterialDatePicker.tsx @@ -42,7 +42,7 @@ function ReadOnlyDateField(props: DatePickerFieldProps) { sx: { cursor: 'pointer', '& *': { cursor: 'inherit' } }, }} error={hasValidationError} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} /> ); } diff --git a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index 59043f6ea467..fb531c0e5204 100644 --- a/docs/src/modules/components/overview/mainDemo/PickerButton.tsx +++ b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx @@ -35,7 +35,7 @@ function ButtonDateField(props: DatePickerFieldProps) { fullWidth color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={pickerContext.onToggleOpening} + onClick={() => pickerContext.setOpen((prev) => !prev)} > {label ? `${label}: ${valueStr}` : valueStr} diff --git a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx index 4fa7eb07cbf1..ec63075b5027 100644 --- a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx @@ -3,6 +3,7 @@ import { PickerOwnerState } from '../../models'; import { PickersInputLocaleText } from '../../locales'; import { LocalizationProvider } from '../../LocalizationProvider'; import { PickerOrientation, PickerVariant } from '../models'; +import { UsePickerValueContextValue } from '../hooks/usePicker/usePickerValue.types'; export const PickerContext = React.createContext(null); @@ -43,26 +44,7 @@ export interface PickerProviderProps { children: React.ReactNode; } -export interface PickerContextValue { - /** - * Open the picker. - * @param {React.UIEvent} event The DOM event that triggered the change. - */ - onOpen: (event: React.UIEvent) => void; - /** - * Close the picker. - * @param {React.UIEvent} event The DOM event that triggered the change. - */ - onClose: (event: React.UIEvent) => void; - /** - * Close the picker if it's open, open it if it's closed. - * @param {React.UIEvent} event The DOM event that triggered the change. - */ - onToggleOpening: (event: React.UIEvent) => void; - /** - * `true` if the picker is open, `false` otherwise. - */ - open: boolean; +export interface PickerContextValue extends UsePickerValueContextValue { /** * `true` if the picker is disabled, `false` otherwise. */ diff --git a/packages/x-date-pickers/src/internals/hooks/useOpenState.ts b/packages/x-date-pickers/src/internals/hooks/useOpenState.ts index 558814e5be26..93c9d7f733bc 100644 --- a/packages/x-date-pickers/src/internals/hooks/useOpenState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useOpenState.ts @@ -8,7 +8,7 @@ export interface OpenStateProps { export const useOpenState = ({ open, onOpen, onClose }: OpenStateProps) => { const isControllingOpenProp = React.useRef(typeof open === 'boolean').current; - const [openState, setIsOpenState] = React.useState(false); + const [openState, setOpenState] = React.useState(false); // It is required to update inner state in useEffect in order to avoid situation when // Our component is not mounted yet, but `open` state is set to `true` (for example initially opened) @@ -18,26 +18,27 @@ export const useOpenState = ({ open, onOpen, onClose }: OpenStateProps) => { throw new Error('You must not mix controlling and uncontrolled mode for `open` prop'); } - setIsOpenState(open); + setOpenState(open); } }, [isControllingOpenProp, open]); - const setIsOpen = React.useCallback( - (newIsOpen: boolean) => { + const setOpen = React.useCallback( + (action: React.SetStateAction) => { + const newOpen = typeof action === 'function' ? action(openState) : action; if (!isControllingOpenProp) { - setIsOpenState(newIsOpen); + setOpenState(newOpen); } - if (newIsOpen && onOpen) { + if (newOpen && onOpen) { onOpen(); } - if (!newIsOpen && onClose) { + if (!newOpen && onClose) { onClose(); } }, - [isControllingOpenProp, onOpen, onClose], + [isControllingOpenProp, onOpen, onClose, openState], ); - return { isOpen: openState, setIsOpen }; + return { open: openState, setOpen }; }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts index 30b80061a4af..09a7ee5dc06a 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.ts @@ -55,11 +55,11 @@ export const usePicker = < const providerProps = usePickerProvider({ props, - pickerValueResponse, localeText, valueManager, variant, views: pickerViewsResponse.views, + paramsFromUsePickerValue: pickerValueResponse.provider, }); return { diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts index 37bbb59b667c..ae01b284b29a 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePicker.types.ts @@ -62,7 +62,7 @@ export interface UsePickerResponse< TValue extends PickerValidValue, TView extends DateOrTimeViewWithMeridiem, TError, -> extends Omit, 'viewProps' | 'layoutProps'>, +> extends Pick, 'open' | 'actions' | 'fieldProps'>, Omit, 'layoutProps' | 'views'> { ownerState: PickerOwnerState; providerProps: UsePickerProviderReturnValue; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts index 6c4746dbc8b7..a0508b1546ea 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -1,8 +1,7 @@ import * as React from 'react'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; -import useEventCallback from '@mui/utils/useEventCallback'; import { PickerOwnerState } from '../../../models'; -import { PickerValueManager, UsePickerValueResponse } from './usePickerValue.types'; +import { PickerValueManager, UsePickerValueProviderParams } from './usePickerValue.types'; import { PickerProviderProps, PickerContextValue, @@ -63,27 +62,19 @@ export const usePickerOrientation = ( export function usePickerProvider( parameters: UsePickerProviderParameters, ): UsePickerProviderReturnValue { - const { props, pickerValueResponse, valueManager, localeText, variant, views } = parameters; + const { props, valueManager, localeText, variant, views, paramsFromUsePickerValue } = parameters; const utils = useUtils(); const orientation = usePickerOrientation(views, props.orientation); - const handleTogglePicker = useEventCallback((event: React.UIEvent) => { - if (pickerValueResponse.open) { - pickerValueResponse.actions.onClose(event); - } else { - pickerValueResponse.actions.onOpen(event); - } - }); - const ownerState = React.useMemo( () => ({ isPickerValueEmpty: valueManager.areValuesEqual( utils, - pickerValueResponse.viewProps.value, + paramsFromUsePickerValue.value, valueManager.emptyValue, ), - isPickerOpen: pickerValueResponse.open, + isPickerOpen: paramsFromUsePickerValue.contextValue.open, isPickerDisabled: props.disabled ?? false, isPickerReadOnly: props.readOnly ?? false, pickerOrientation: orientation, @@ -92,8 +83,8 @@ export function usePickerProvider( [ utils, valueManager, - pickerValueResponse.viewProps.value, - pickerValueResponse.open, + paramsFromUsePickerValue.value, + paramsFromUsePickerValue.contextValue.open, orientation, variant, props.disabled, @@ -103,25 +94,13 @@ export function usePickerProvider( const contextValue = React.useMemo( () => ({ - onOpen: pickerValueResponse.actions.onOpen, - onClose: pickerValueResponse.actions.onClose, - onToggleOpening: handleTogglePicker, - open: pickerValueResponse.open, + ...paramsFromUsePickerValue.contextValue, disabled: props.disabled ?? false, readOnly: props.readOnly ?? false, variant, orientation, }), - [ - pickerValueResponse.actions.onOpen, - pickerValueResponse.actions.onClose, - handleTogglePicker, - pickerValueResponse.open, - variant, - orientation, - props.disabled, - props.readOnly, - ], + [paramsFromUsePickerValue.contextValue, variant, orientation, props.disabled, props.readOnly], ); const privateContextValue = React.useMemo( @@ -139,10 +118,10 @@ export function usePickerProvider( export interface UsePickerProviderParameters extends Pick { props: UsePickerProps; - pickerValueResponse: UsePickerValueResponse; valueManager: PickerValueManager; variant: PickerVariant; views: readonly DateOrTimeViewWithMeridiem[]; + paramsFromUsePickerValue: UsePickerValueProviderParams; } export interface UsePickerProviderReturnValue extends Omit {} diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts index 6481132bd86f..e973b01eaba3 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts @@ -21,6 +21,8 @@ import { UsePickerValueActions, PickerSelectionState, PickerValueUpdaterParams, + UsePickerValueContextValue, + UsePickerValueProviderParams, } from './usePickerValue.types'; import { useValueWithTimezone } from '../useValueWithTimezone'; import { PickerValidValue } from '../../models'; @@ -208,7 +210,7 @@ export const usePickerValue = < const utils = useUtils(); const adapter = useLocalizationContext(); - const { isOpen, setIsOpen } = useOpenState(props); + const { open, setOpen } = useOpenState(props); const { timezone, @@ -312,7 +314,7 @@ export const usePickerValue = < } if (shouldClose) { - setIsOpen(false); + setOpen(false); } }); @@ -379,12 +381,12 @@ export const usePickerValue = < const handleOpen = useEventCallback((event: React.UIEvent) => { event.preventDefault(); - setIsOpen(true); + setOpen(true); }); const handleClose = useEventCallback((event?: React.UIEvent) => { event?.preventDefault(); - setIsOpen(false); + setOpen(false); }); const handleChange = useEventCallback( @@ -426,16 +428,16 @@ export const usePickerValue = < onChange: handleChangeFromField, }; - const viewValue = React.useMemo( + const valueWithoutError = React.useMemo( () => valueManager.cleanValue(utils, dateState.draft), [utils, valueManager, dateState.draft], ); const viewResponse: UsePickerValueViewsResponse = { - value: viewValue, + value: valueWithoutError, onChange: handleChange, onClose: handleClose, - open: isOpen, + open, }; const isValid = (testedValue: TValue) => { @@ -451,17 +453,30 @@ export const usePickerValue = < const layoutResponse: UsePickerValueLayoutResponse = { ...actions, - value: viewValue, + value: valueWithoutError, onChange: handleChange, onSelectShortcut: handleSelectShortcut, isValid, }; + const contextValue = React.useMemo(() => { + return { + open, + setOpen, + }; + }, [open, setOpen]); + + const providerParams: UsePickerValueProviderParams = { + value: valueWithoutError, + contextValue, + }; + return { - open: isOpen, + open, fieldProps: fieldResponse, viewProps: viewResponse, layoutProps: layoutResponse, actions, + provider: providerParams, }; }; diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 1b301efd05be..5550c7535fa6 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -17,6 +17,7 @@ import { PickersShortcutsItemContext, } from '../../../PickersShortcuts'; import { InferNonNullablePickerValue, PickerValidValue } from '../../models'; +import React from 'react'; export interface PickerValueManager { /** @@ -313,10 +314,27 @@ export interface UsePickerValueLayoutResponse isValid: (value: TValue) => boolean; } +/** + * Params passed to `usePickerProvider`. + */ +export interface UsePickerValueProviderParams { + value: TValue; + contextValue: UsePickerValueContextValue; +} + export interface UsePickerValueResponse { open: boolean; actions: UsePickerValueActions; viewProps: UsePickerValueViewsResponse; fieldProps: UsePickerValueFieldResponse; layoutProps: UsePickerValueLayoutResponse; + provider: UsePickerValueProviderParams; +} + +export interface UsePickerValueContextValue { + setOpen: React.Dispatch>; + /** + * `true` if the picker is open, `false` otherwise. + */ + open: boolean; } From c0e4cede5a5973ee54b73f018d7617ca33e4fd43 Mon Sep 17 00:00:00 2001 From: flavien Date: Wed, 4 Dec 2024 08:55:03 +0100 Subject: [PATCH 3/7] Add migration guide --- .../migration-pickers-v7.md | 40 +++++++++++++++++++ .../hooks/usePicker/usePickerValue.types.ts | 11 ++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md index 764f28cfb98a..30541674081d 100644 --- a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -404,6 +404,46 @@ The following variables and types have been renamed to have a coherent `Picker` +import { FieldRangeSection } from '@mui/x-date-pickers-pro'; ``` +## Hooks breaking changes + +### `usePickerContext` + +- The `onOpen` and `onClock` methods have been replaced with a single `setOpen` method. + This method no longer takes an event, which was used to prevent the browser default behavior: + + ```diff + const pickerContext = usePickerContext(); + + - + + + + - + + + + - + + + ``` + + If you want to prevent the default behavior, you now have to do it manually: + + ```diff +
{ + if (event.key === 'Escape') { + - pickerContext.onClose(); + + event.preventDefault(); + + pickerContext.setOpen(false); + } + }} + /> + ``` + ## Typing breaking changes ### Do not pass the date object as a generic diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 5550c7535fa6..60c022e497a9 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -1,3 +1,4 @@ +import * as React from 'react'; import { FieldChangeHandlerContext, UseFieldInternalProps } from '../useField'; import { Validator } from '../../../validation'; import { PickerVariant } from '../../models/common'; @@ -17,7 +18,6 @@ import { PickersShortcutsItemContext, } from '../../../PickersShortcuts'; import { InferNonNullablePickerValue, PickerValidValue } from '../../models'; -import React from 'react'; export interface PickerValueManager { /** @@ -332,6 +332,15 @@ export interface UsePickerValueResponse } export interface UsePickerValueContextValue { + /** + * Sets the current opening status of the picker. + * ```ts + * setOpen(true); // Opens the picker. + * setOpen(false); // Closes the picker. + * setOpen((prevOpen) => !prevOpen); // Toggles the opening status. + * ``` + * @param {React.SetStateAction} action The new opening status of the picker, it can be a function that will receive the previous opening status. + */ setOpen: React.Dispatch>; /** * `true` if the picker is open, `false` otherwise. From ef5387474c3eabc33926ef816e44bdf19dd256fe Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Wed, 4 Dec 2024 10:36:34 +0100 Subject: [PATCH 4/7] Update docs/data/migration/migration-pickers-v7/migration-pickers-v7.md Co-authored-by: Lukas Tyla Signed-off-by: Flavien DELANGLE --- .../data/migration/migration-pickers-v7/migration-pickers-v7.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md index 30541674081d..199fe9357457 100644 --- a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -408,7 +408,7 @@ The following variables and types have been renamed to have a coherent `Picker` ### `usePickerContext` -- The `onOpen` and `onClock` methods have been replaced with a single `setOpen` method. +- The `onOpen` and `onClose` methods have been replaced with a single `setOpen` method. This method no longer takes an event, which was used to prevent the browser default behavior: ```diff From c047749913c6745b15d7599d437c2c9bdd94f971 Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Wed, 4 Dec 2024 10:36:40 +0100 Subject: [PATCH 5/7] Update packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts Co-authored-by: Lukas Tyla Signed-off-by: Flavien DELANGLE --- .../src/internals/hooks/usePicker/usePickerValue.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 60c022e497a9..aaff1bd843e5 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -333,7 +333,7 @@ export interface UsePickerValueResponse export interface UsePickerValueContextValue { /** - * Sets the current opening status of the picker. + * Sets the current open state of the Picker. * ```ts * setOpen(true); // Opens the picker. * setOpen(false); // Closes the picker. From e00ff3a884f5dfa412388fe3b1dd88e2989b6c4a Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Wed, 4 Dec 2024 10:36:46 +0100 Subject: [PATCH 6/7] Update packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts Co-authored-by: Lukas Tyla Signed-off-by: Flavien DELANGLE --- .../src/internals/hooks/usePicker/usePickerValue.types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index aaff1bd843e5..05795702cd11 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -337,7 +337,7 @@ export interface UsePickerValueContextValue { * ```ts * setOpen(true); // Opens the picker. * setOpen(false); // Closes the picker. - * setOpen((prevOpen) => !prevOpen); // Toggles the opening status. + * setOpen((prevOpen) => !prevOpen); // Toggles the open state. * ``` * @param {React.SetStateAction} action The new opening status of the picker, it can be a function that will receive the previous opening status. */ From 58e722c7ab4fa139ea17f4630725c4a8ae760d7b Mon Sep 17 00:00:00 2001 From: Flavien DELANGLE Date: Wed, 4 Dec 2024 10:36:51 +0100 Subject: [PATCH 7/7] Update packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts Co-authored-by: Lukas Tyla Signed-off-by: Flavien DELANGLE --- .../src/internals/hooks/usePicker/usePickerValue.types.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index 05795702cd11..d58af8940c43 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -339,7 +339,8 @@ export interface UsePickerValueContextValue { * setOpen(false); // Closes the picker. * setOpen((prevOpen) => !prevOpen); // Toggles the open state. * ``` - * @param {React.SetStateAction} action The new opening status of the picker, it can be a function that will receive the previous opening status. + * @param {React.SetStateAction} action The new open state of the Picker. + * It can be a function that will receive the current open state. */ setOpen: React.Dispatch>; /**