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 1e51554e6311c..f47dae9a7f0fd 100644 --- a/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md +++ b/docs/data/migration/migration-pickers-v7/migration-pickers-v7.md @@ -438,7 +438,9 @@ This change causes a few breaking changes: +const { views } = usePickerContext(); -const { onViewChange } = props; - +const { onViewChange } = usePickerContext(); + -onViewChange('month'); + +const { setView } = usePickerContext(); + +setView('month'); ``` - The component passed to the `layout` slot no longer receives the `onClear`, `onSetToday`, `onAccept`, `onCancel`, `onOpen`, `onClose` `onDismiss`, `onChange` and `onSelectShortcut` props. diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx index 2b83f8a4c4932..75befc777bafd 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerTabs.tsx @@ -119,7 +119,7 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs( const translations = usePickerTranslations(); const { ownerState } = usePickerPrivateContext(); - const { view, onViewChange } = usePickerContext(); + const { view, setView } = usePickerContext(); const classes = useUtilityClasses(classesProp); const { rangePosition, onRangePositionChange } = usePickerRangePositionContext(); @@ -160,13 +160,13 @@ const DateTimeRangePickerTabs = function DateTimeRangePickerTabs( const changeToPreviousTab = useEventCallback(() => { const previousTab = value == null ? tabOptions[0] : tabOptions[tabOptions.indexOf(value) - 1]; - onViewChange(tabToView(previousTab)); + setView(tabToView(previousTab)); handleRangePositionChange(previousTab); }); const changeToNextTab = useEventCallback(() => { const nextTab = value == null ? tabOptions[0] : tabOptions[tabOptions.indexOf(value) + 1]; - onViewChange(tabToView(nextTab)); + setView(tabToView(nextTab)); handleRangePositionChange(nextTab); }); diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx index d421cc99d2359..2c2859863d024 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx @@ -99,7 +99,7 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker ...other } = props; - const { value, setValue, disabled, readOnly, view, onViewChange, views } = usePickerContext< + const { value, setValue, disabled, readOnly, view, setView, views } = usePickerContext< PickerRangeValue, DateTimeRangeViews >(); @@ -141,17 +141,17 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker if (rangePosition !== 'start') { onRangePositionChange('start'); } - onViewChange(newView); + setView(newView); }; return { value: value[0], setValue: wrappedSetValue, forceDesktopVariant: true, - onViewChange: handleStartRangeViewChange, + setView: handleStartRangeViewChange, view: rangePosition === 'start' ? view : null, }; - }, [value, wrappedSetValue, rangePosition, view, onRangePositionChange, onViewChange]); + }, [value, wrappedSetValue, rangePosition, view, onRangePositionChange, setView]); const endOverrides = React.useMemo(() => { const handleEndRangeViewChange = (newView: DateOrTimeViewWithMeridiem) => { @@ -161,17 +161,17 @@ const DateTimeRangePickerToolbar = React.forwardRef(function DateTimeRangePicker if (rangePosition !== 'end') { onRangePositionChange('end'); } - onViewChange(newView); + setView(newView); }; return { value: value[1], setValue: wrappedSetValue, forceDesktopVariant: true, - onViewChange: handleEndRangeViewChange, + setView: handleEndRangeViewChange, view: rangePosition === 'end' ? view : null, }; - }, [value, wrappedSetValue, rangePosition, view, onRangePositionChange, onViewChange]); + }, [value, wrappedSetValue, rangePosition, view, onRangePositionChange, setView]); if (hidden) { return null; diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx index beaaefb36f02f..c69f02d3fdd50 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/internals/hooks/useDesktopRangePicker/useDesktopRangePicker.tsx @@ -182,7 +182,6 @@ export const useDesktopRangePicker = < ? providerProps.contextValue.view : undefined, initialView: initialView.current ?? undefined, - onViewChange: providerProps.contextValue.onViewChange, ...rangePositionResponse, }); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts index a4f1ab1bfd620..a333f91a68266 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts @@ -111,7 +111,7 @@ export interface UseEnrichedRangePickerFieldPropsParams< anchorRef?: React.Ref; currentView?: TView | null; initialView?: TView; - onViewChange?: (view: TView) => void; + setView?: (view: TView) => void; startFieldRef: React.RefObject | null>; endFieldRef: React.RefObject | null>; singleInputFieldRef: React.RefObject | null>; @@ -138,7 +138,7 @@ const useMultiInputFieldSlotProps = < anchorRef, currentView, initialView, - onViewChange, + setView, startFieldRef, endFieldRef, }: UseEnrichedRangePickerFieldPropsParams< @@ -198,7 +198,7 @@ const useMultiInputFieldSlotProps = < if (open) { onRangePositionChange('start'); if (previousRangePosition.current !== 'start' && initialView) { - onViewChange?.(initialView); + setView?.(initialView); } } }; @@ -207,7 +207,7 @@ const useMultiInputFieldSlotProps = < if (open) { onRangePositionChange('end'); if (previousRangePosition.current !== 'end' && initialView) { - onViewChange?.(initialView); + setView?.(initialView); } } }; diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx index 2ae198b28f762..295d6097c6643 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerTabs.tsx @@ -105,11 +105,11 @@ const DateTimePickerTabs = function DateTimePickerTabs(inProps: DateTimePickerTa const translations = usePickerTranslations(); const { ownerState } = usePickerPrivateContext(); - const { view, onViewChange } = usePickerContext(); + const { view, setView } = usePickerContext(); const classes = useUtilityClasses(classesProp); const handleChange = (event: React.SyntheticEvent, value: TabValue) => { - onViewChange(tabToView(value)); + setView(tabToView(value)); }; if (hidden) { diff --git a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx index 0e45cc3584278..28ce3c21b7d12 100644 --- a/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx +++ b/packages/x-date-pickers/src/DateTimePicker/DateTimePickerToolbar.tsx @@ -243,7 +243,7 @@ export const DateTimePickerToolbarOverrideContext = React.createContext<{ value: PickerValue; setValue: (value: PickerValue, options?: SetValueActionOptions) => void; forceDesktopVariant: boolean; - onViewChange: (view: DateOrTimeViewWithMeridiem) => void; + setView: (view: DateOrTimeViewWithMeridiem) => void; view: DateOrTimeViewWithMeridiem | null; } | null>(null); @@ -278,7 +278,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { variant, orientation, view: viewContext, - onViewChange: onViewChangeContext, + setView: setViewContext, views, } = usePickerContext(); @@ -291,7 +291,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { const value = overrides ? overrides.value : valueContext; const setValue = overrides ? overrides.setValue : setValueContext; const view = overrides ? overrides.view : viewContext; - const onViewChange = overrides ? overrides.onViewChange : onViewChangeContext; + const setView = overrides ? overrides.setView : setViewContext; const { meridiemMode, handleMeridiemChange } = useMeridiemMode(value, ampm, (newValue) => setValue(newValue, { changeImportance: 'set' }), @@ -336,7 +336,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { tabIndex={-1} variant="subtitle1" data-testid="datetimepicker-toolbar-year" - onClick={() => onViewChange('year')} + onClick={() => setView('year')} selected={view === 'year'} value={formatSection('year', '–')} /> @@ -347,7 +347,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { tabIndex={-1} variant={isDesktop ? 'h5' : 'h4'} data-testid="datetimepicker-toolbar-day" - onClick={() => onViewChange('day')} + onClick={() => setView('day')} selected={view === 'day'} value={dateText} /> @@ -373,7 +373,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { : undefined } data-testid="hours" - onClick={() => onViewChange('hours')} + onClick={() => setView('hours')} selected={view === 'hours'} value={formatSection(ampm ? 'hours12h' : 'hours24h', '--')} /> @@ -392,7 +392,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { : undefined } data-testid="minutes" - onClick={() => onViewChange('minutes')} + onClick={() => setView('minutes')} selected={view === 'minutes' || (!views.includes('minutes') && view === 'hours')} value={formatSection('minutes', '--')} disabled={!views.includes('minutes')} @@ -417,7 +417,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { : undefined } data-testid="seconds" - onClick={() => onViewChange('seconds')} + onClick={() => setView('seconds')} selected={view === 'seconds'} value={formatSection('seconds', '--')} /> @@ -452,7 +452,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) { onViewChange('meridiem')} + onClick={() => setView('meridiem')} selected={view === 'meridiem'} value={value && meridiemMode ? formatMeridiem(utils, meridiemMode) : '--'} width={MULTI_SECTION_CLOCK_SECTION_WIDTH} diff --git a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx index 4c95cce547de9..d7ed44402e6aa 100644 --- a/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx +++ b/packages/x-date-pickers/src/PickersActionBar/PickersActionBar.test.tsx @@ -12,6 +12,7 @@ describe('', () => { const renderWithContext = (element: React.ReactElement) => { const spys = { setValue: spy(), + setView: spy(), setOpen: spy(), clearValue: spy(), setValueToToday: spy(), diff --git a/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx b/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx index 04a7ad695b7aa..7cecf4d09e3ab 100644 --- a/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx +++ b/packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx @@ -157,7 +157,7 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) { const translations = usePickerTranslations(); const ownerState = useToolbarOwnerState(); const classes = useUtilityClasses(classesProp, ownerState); - const { value, setValue, disabled, readOnly, view, onViewChange, views } = usePickerContext< + const { value, setValue, disabled, readOnly, view, setView, views } = usePickerContext< PickerValue, TimeViewWithMeridiem >(); @@ -199,7 +199,7 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) { data-testid="hours" tabIndex={-1} variant="h3" - onClick={() => onViewChange('hours')} + onClick={() => setView('hours')} selected={view === 'hours'} value={formatSection(ampm ? 'hours12h' : 'hours24h')} /> @@ -211,7 +211,7 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) { data-testid="minutes" tabIndex={-1} variant="h3" - onClick={() => onViewChange('minutes')} + onClick={() => setView('minutes')} selected={view === 'minutes'} value={formatSection('minutes')} /> @@ -222,7 +222,7 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) { onViewChange('seconds')} + onClick={() => setView('seconds')} selected={view === 'seconds'} value={formatSection('seconds')} /> diff --git a/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts b/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts index 50d1155790b9a..546ef7da1b1e8 100644 --- a/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts +++ b/packages/x-date-pickers/src/hooks/usePickerActionsContext.ts @@ -4,7 +4,7 @@ import { PickerActionsContext, PickerActionsContextValue, } from '../internals/components/PickerProvider'; -import { PickerValidValue, PickerValue } from '../internals/models'; +import { DateOrTimeViewWithMeridiem, PickerValidValue, PickerValue } from '../internals/models'; /** * Returns a subset of the context passed by the picker wrapping the current component. @@ -12,10 +12,12 @@ import { PickerValidValue, PickerValue } from '../internals/models'; */ export const usePickerActionsContext = < TValue extends PickerValidValue = PickerValue, + TView extends DateOrTimeViewWithMeridiem = DateOrTimeViewWithMeridiem, TError = string, >() => { const value = React.useContext(PickerActionsContext) as PickerActionsContextValue< TValue, + TView, TError > | null; if (value == null) { diff --git a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx index e3642b186fdb6..8baf47d8a8662 100644 --- a/packages/x-date-pickers/src/internals/components/PickerProvider.tsx +++ b/packages/x-date-pickers/src/internals/components/PickerProvider.tsx @@ -13,14 +13,19 @@ import type { UsePickerValueContextValue, UsePickerValuePrivateContextValue, } from '../hooks/usePicker/usePickerValue.types'; -import { UsePickerViewsContextValue } from '../hooks/usePicker/usePickerViews'; +import { + UsePickerViewsActionsContextValue, + UsePickerViewsContextValue, +} from '../hooks/usePicker/usePickerViews'; import { IsValidValueContext } from '../../hooks/useIsValidValue'; export const PickerContext = React.createContext | null>(null); -export const PickerActionsContext = React.createContext | null>( - null, -); +export const PickerActionsContext = React.createContext | null>(null); export const PickerPrivateContext = React.createContext({ ownerState: { @@ -68,7 +73,7 @@ export function PickerProvider( export interface PickerProviderProps { contextValue: PickerContextValue; - actionsContextValue: PickerActionsContextValue; + actionsContextValue: PickerActionsContextValue; privateContextValue: PickerPrivateContextValue; isValidContextValue: (value: TValue) => boolean; localeText: PickersInputLocaleText | undefined; @@ -108,8 +113,12 @@ export interface PickerContextValue< orientation: PickerOrientation; } -export interface PickerActionsContextValue - extends UsePickerValueActionsContextValue {} +export interface PickerActionsContextValue< + TValue extends PickerValidValue, + TView extends DateOrTimeViewWithMeridiem, + TError = string, +> extends UsePickerValueActionsContextValue, + UsePickerViewsActionsContextValue {} export interface PickerPrivateContextValue extends UsePickerValuePrivateContextValue { /** diff --git a/packages/x-date-pickers/src/internals/hooks/useOpenState.ts b/packages/x-date-pickers/src/internals/hooks/useOpenState.ts index 93c9d7f733bcc..c698f9f208f72 100644 --- a/packages/x-date-pickers/src/internals/hooks/useOpenState.ts +++ b/packages/x-date-pickers/src/internals/hooks/useOpenState.ts @@ -1,4 +1,5 @@ import * as React from 'react'; +import useEventCallback from '@mui/utils/useEventCallback'; export interface OpenStateProps { open?: boolean; @@ -22,23 +23,20 @@ export const useOpenState = ({ open, onOpen, onClose }: OpenStateProps) => { } }, [isControllingOpenProp, open]); - const setOpen = React.useCallback( - (action: React.SetStateAction) => { - const newOpen = typeof action === 'function' ? action(openState) : action; - if (!isControllingOpenProp) { - setOpenState(newOpen); - } + const setOpen = useEventCallback((action: React.SetStateAction) => { + const newOpen = typeof action === 'function' ? action(openState) : action; + if (!isControllingOpenProp) { + setOpenState(newOpen); + } - if (newOpen && onOpen) { - onOpen(); - } + if (newOpen && onOpen) { + onOpen(); + } - if (!newOpen && onClose) { - onClose(); - } - }, - [isControllingOpenProp, onOpen, onClose, openState], - ); + if (!newOpen && onClose) { + onClose(); + } + }); return { open: openState, setOpen }; }; 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 f907c589f527a..87845afcd6ef6 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerProvider.ts @@ -128,11 +128,19 @@ export function usePickerProvider< [paramsFromUsePickerValue, ownerState], ); + const actionsContextValue = React.useMemo( + () => ({ + ...paramsFromUsePickerValue.actionsContextValue, + ...paramsFromUsePickerViews.actionsContextValue, + }), + [paramsFromUsePickerValue.actionsContextValue, paramsFromUsePickerViews.actionsContextValue], + ); + return { localeText, contextValue, privateContextValue, - actionsContextValue: paramsFromUsePickerValue.actionsContextValue, + actionsContextValue, isValidContextValue: paramsFromUsePickerValue.isValidContextValue, }; } diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx index 93dc3c32399ce..f8769c0109b27 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerViews.tsx @@ -118,7 +118,17 @@ export interface UsePickerViewsResponse; } -export interface UsePickerViewsContextValue { +export interface UsePickerViewsActionsContextValue { + /** + * Set the current view. + * @template TView + * @param {TView} view The view to render + */ + setView: (view: TView) => void; +} + +export interface UsePickerViewsContextValue + extends UsePickerViewsActionsContextValue { /** * Available views. */ @@ -127,18 +137,13 @@ export interface UsePickerViewsContextValue void; } export interface UsePickerViewsProviderParams { hasUIView: boolean; views: readonly TView[]; contextValue: UsePickerViewsContextValue; + actionsContextValue: UsePickerViewsActionsContextValue; } /** @@ -256,19 +261,25 @@ export const usePickerViews = < setFocusedView(newView, true); }, [open]); // eslint-disable-line react-hooks/exhaustive-deps + const actionsContextValue = React.useMemo>( + () => ({ setView }), + [setView], + ); + const contextValue = React.useMemo>( () => ({ + ...actionsContextValue, views, view: popperView, - onViewChange: setView, }), - [views, popperView, setView], + [actionsContextValue, views, popperView], ); const providerParams: UsePickerViewsProviderParams = { hasUIView, views, contextValue, + actionsContextValue, }; return {