{
+ 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;
}