diff --git a/docs/data/date-pickers/calendar-systems/AdapterHijri.js b/docs/data/date-pickers/calendar-systems/AdapterHijri.js index fa738ee1e6e3..377759e11375 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.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 aa9b24d67f79..704dbee32ae7 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.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 a86335cd7dd7..edfed553188e 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: ( - + 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 2c1c935104e7..c19472a59405 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: ( - + 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 dd5ee96d6f52..35cf00fa9a7e 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={ 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 36d33af92f98..9cf3261ab611 100644 --- a/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx +++ b/docs/data/date-pickers/custom-field/JoyV6SingleInputRangeField.tsx @@ -103,13 +103,6 @@ const JoySingleInputDateRangeField = 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: JoySingleInputDateRangeFieldProps = useSlotProps({ elementType: FormControl, @@ -136,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 f9306981aac0..0e30e8d2605a 100644 --- a/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js +++ b/docs/data/date-pickers/custom-field/behavior-button/MaterialDatePicker.js @@ -34,14 +34,6 @@ function ButtonDateField(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 ( @@ -50,7 +42,7 @@ function ButtonDateField(props) { variant="outlined" color={hasValidationError ? 'error' : 'primary'} ref={InputProps?.ref} - onClick={handleTogglePicker} + 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 ac64ebd3a5f0..1da1b675decd 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.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 7c56f7a2693a..0884deadb8b4 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.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 fbc0cd45c51e..9361a51c256a 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.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 1e7d773f6fdf..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 @@ -26,14 +26,6 @@ function ReadOnlyDateField(props) { props: internalProps, }); - const handleTogglePicker = (event) => { - if (pickerContext.open) { - pickerContext.onClose(event); - } else { - pickerContext.onOpen(event); - } - }; - return ( 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 1fd235a77819..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 @@ -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 ( pickerContext.setOpen((prev) => !prev)} /> ); } 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..199fe9357457 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 `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 + 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/docs/src/modules/components/overview/mainDemo/PickerButton.tsx b/docs/src/modules/components/overview/mainDemo/PickerButton.tsx index d47d045fb1c6..fb531c0e5204 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.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 85eeaf1f04d8..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,21 +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; - /** - * `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 92964972c29a..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,7 +1,7 @@ import * as React from 'react'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { PickerOwnerState } from '../../../models'; -import { PickerValueManager, UsePickerValueResponse } from './usePickerValue.types'; +import { PickerValueManager, UsePickerValueProviderParams } from './usePickerValue.types'; import { PickerProviderProps, PickerContextValue, @@ -62,7 +62,7 @@ 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); @@ -71,10 +71,10 @@ export function usePickerProvider( () => ({ 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, @@ -83,8 +83,8 @@ export function usePickerProvider( [ utils, valueManager, - pickerValueResponse.viewProps.value, - pickerValueResponse.open, + paramsFromUsePickerValue.value, + paramsFromUsePickerValue.contextValue.open, orientation, variant, props.disabled, @@ -94,23 +94,13 @@ export function usePickerProvider( const contextValue = React.useMemo( () => ({ - onOpen: pickerValueResponse.actions.onOpen, - onClose: pickerValueResponse.actions.onClose, - open: pickerValueResponse.open, + ...paramsFromUsePickerValue.contextValue, disabled: props.disabled ?? false, readOnly: props.readOnly ?? false, variant, orientation, }), - [ - pickerValueResponse.actions.onOpen, - pickerValueResponse.actions.onClose, - pickerValueResponse.open, - variant, - orientation, - props.disabled, - props.readOnly, - ], + [paramsFromUsePickerValue.contextValue, variant, orientation, props.disabled, props.readOnly], ); const privateContextValue = React.useMemo( @@ -128,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..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 @@ -1,3 +1,4 @@ +import * as React from 'react'; import { FieldChangeHandlerContext, UseFieldInternalProps } from '../useField'; import { Validator } from '../../../validation'; import { PickerVariant } from '../../models/common'; @@ -313,10 +314,37 @@ 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 { + /** + * Sets the current open state of the Picker. + * ```ts + * setOpen(true); // Opens the picker. + * setOpen(false); // Closes the picker. + * setOpen((prevOpen) => !prevOpen); // Toggles the open state. + * ``` + * @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>; + /** + * `true` if the picker is open, `false` otherwise. + */ + open: boolean; }