Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
>();
Expand Down Expand Up @@ -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) => {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ export const useDesktopRangePicker = <
? providerProps.contextValue.view
: undefined,
initialView: initialView.current ?? undefined,
onViewChange: providerProps.contextValue.onViewChange,
...rangePositionResponse,
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export interface UseEnrichedRangePickerFieldPropsParams<
anchorRef?: React.Ref<HTMLDivElement>;
currentView?: TView | null;
initialView?: TView;
onViewChange?: (view: TView) => void;
setView?: (view: TView) => void;
startFieldRef: React.RefObject<FieldRef<PickerValue> | null>;
endFieldRef: React.RefObject<FieldRef<PickerValue> | null>;
singleInputFieldRef: React.RefObject<FieldRef<PickerRangeValue> | null>;
Expand All @@ -138,7 +138,7 @@ const useMultiInputFieldSlotProps = <
anchorRef,
currentView,
initialView,
onViewChange,
setView,
startFieldRef,
endFieldRef,
}: UseEnrichedRangePickerFieldPropsParams<
Expand Down Expand Up @@ -198,7 +198,7 @@ const useMultiInputFieldSlotProps = <
if (open) {
onRangePositionChange('start');
if (previousRangePosition.current !== 'start' && initialView) {
onViewChange?.(initialView);
setView?.(initialView);
}
}
};
Expand All @@ -207,7 +207,7 @@ const useMultiInputFieldSlotProps = <
if (open) {
onRangePositionChange('end');
if (previousRangePosition.current !== 'end' && initialView) {
onViewChange?.(initialView);
setView?.(initialView);
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export const DateTimePickerToolbarOverrideContext = React.createContext<{
value: PickerValue;
setValue: (value: PickerValue, options?: SetValueActionOptions<DateTimeValidationError>) => void;
forceDesktopVariant: boolean;
onViewChange: (view: DateOrTimeViewWithMeridiem) => void;
setView: (view: DateOrTimeViewWithMeridiem) => void;
view: DateOrTimeViewWithMeridiem | null;
} | null>(null);

Expand Down Expand Up @@ -278,7 +278,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) {
variant,
orientation,
view: viewContext,
onViewChange: onViewChangeContext,
setView: setViewContext,
views,
} = usePickerContext();

Expand All @@ -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' }),
Expand Down Expand Up @@ -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', '–')}
/>
Expand All @@ -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}
/>
Expand All @@ -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', '--')}
/>
Expand All @@ -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')}
Expand All @@ -417,7 +417,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) {
: undefined
}
data-testid="seconds"
onClick={() => onViewChange('seconds')}
onClick={() => setView('seconds')}
selected={view === 'seconds'}
value={formatSection('seconds', '--')}
/>
Expand Down Expand Up @@ -452,7 +452,7 @@ function DateTimePickerToolbar(inProps: DateTimePickerToolbarProps) {
<PickersToolbarButton
variant="h5"
data-testid="am-pm-view-button"
onClick={() => onViewChange('meridiem')}
onClick={() => setView('meridiem')}
selected={view === 'meridiem'}
value={value && meridiemMode ? formatMeridiem(utils, meridiemMode) : '--'}
width={MULTI_SECTION_CLOCK_SECTION_WIDTH}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('<PickersActionBar />', () => {
const renderWithContext = (element: React.ReactElement) => {
const spys = {
setValue: spy(),
setView: spy(),
setOpen: spy(),
clearValue: spy(),
setValueToToday: spy(),
Expand Down
8 changes: 4 additions & 4 deletions packages/x-date-pickers/src/TimePicker/TimePickerToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
>();
Expand Down Expand Up @@ -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')}
/>
Expand All @@ -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')}
/>
Expand All @@ -222,7 +222,7 @@ function TimePickerToolbar(inProps: TimePickerToolbarProps) {
<PickersToolbarButton
data-testid="seconds"
variant="h3"
onClick={() => onViewChange('seconds')}
onClick={() => setView('seconds')}
selected={view === 'seconds'}
value={formatSection('seconds')}
/>
Expand Down
4 changes: 3 additions & 1 deletion packages/x-date-pickers/src/hooks/usePickerActionsContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@ 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.
* It only contains the actions and never causes a re-render of the component using it.
*/
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<PickerContextValue<any, any, any> | null>(null);

export const PickerActionsContext = React.createContext<PickerActionsContextValue<any, any> | null>(
null,
);
export const PickerActionsContext = React.createContext<PickerActionsContextValue<
any,
any,
any
> | null>(null);

export const PickerPrivateContext = React.createContext<PickerPrivateContextValue>({
ownerState: {
Expand Down Expand Up @@ -68,7 +73,7 @@ export function PickerProvider<TValue extends PickerValidValue>(

export interface PickerProviderProps<TValue extends PickerValidValue> {
contextValue: PickerContextValue<any, any, any>;
actionsContextValue: PickerActionsContextValue<any, any>;
actionsContextValue: PickerActionsContextValue<any, any, any>;
privateContextValue: PickerPrivateContextValue;
isValidContextValue: (value: TValue) => boolean;
localeText: PickersInputLocaleText | undefined;
Expand Down Expand Up @@ -108,8 +113,12 @@ export interface PickerContextValue<
orientation: PickerOrientation;
}

export interface PickerActionsContextValue<TValue extends PickerValidValue, TError = string>
extends UsePickerValueActionsContextValue<TValue, TError> {}
export interface PickerActionsContextValue<
TValue extends PickerValidValue,
TView extends DateOrTimeViewWithMeridiem,
TError = string,
> extends UsePickerValueActionsContextValue<TValue, TError>,
UsePickerViewsActionsContextValue<TView> {}

export interface PickerPrivateContextValue extends UsePickerValuePrivateContextValue {
/**
Expand Down
28 changes: 13 additions & 15 deletions packages/x-date-pickers/src/internals/hooks/useOpenState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as React from 'react';
import useEventCallback from '@mui/utils/useEventCallback';

export interface OpenStateProps {
open?: boolean;
Expand All @@ -22,23 +23,20 @@ export const useOpenState = ({ open, onOpen, onClose }: OpenStateProps) => {
}
}, [isControllingOpenProp, open]);

const setOpen = React.useCallback(
(action: React.SetStateAction<boolean>) => {
const newOpen = typeof action === 'function' ? action(openState) : action;
if (!isControllingOpenProp) {
setOpenState(newOpen);
}
const setOpen = useEventCallback((action: React.SetStateAction<boolean>) => {
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure setOpen is stable so that the context never re-renders.

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 };
};
Loading