Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pickers] Support onMonthChange / onYearChange in day view #6954

Closed
wants to merge 4 commits into from
Closed
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 @@ -180,24 +180,19 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar<TDate>(
shouldDisableDate &&
((dayToTest: TDate) => shouldDisableDate?.(dayToTest, currentDatePosition));

const {
calendarState,
changeFocusedDay,
changeMonth,
handleChangeMonth,
onMonthSwitchingAnimationEnd,
} = useCalendarState<TDate>({
value: value[0] || value[1],
defaultCalendarMonth,
disableFuture,
disablePast,
disableSwitchToMonthOnDayFocus: true,
maxDate,
minDate,
onMonthChange,
reduceAnimations,
shouldDisableDate: wrappedShouldDisableDate,
});
const { calendarState, changeFocusedDay, slideToMonth, onMonthSwitchingAnimationEnd } =
useCalendarState<TDate>({
value: value[0] || value[1],
defaultCalendarMonth,
disableFuture,
disablePast,
disableSwitchToMonthOnDayFocus: true,
maxDate,
minDate,
onMonthChange,
reduceAnimations,
shouldDisableDate: wrappedShouldDisableDate,
});

const prevValue = React.useRef<DateRange<TDate> | null>(null);
React.useEffect(() => {
Expand Down Expand Up @@ -231,7 +226,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar<TDate>(
: // If need to focus end, scroll to the state when "end" is displaying in the last calendar
utils.addMonths(date, -displayingMonthRange);

changeMonth(newMonth);
slideToMonth(newMonth);
}
}, [currentDatePosition, value]); // eslint-disable-line

Expand All @@ -252,13 +247,13 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar<TDate>(
onChange?.(newRange, isFullRangeSelected ? 'finish' : 'partial');
});

const selectNextMonth = React.useCallback(() => {
changeMonth(utils.getNextMonth(calendarState.currentMonth));
}, [changeMonth, calendarState.currentMonth, utils]);
const selectNextMonth = useEventCallback(() =>
slideToMonth(utils.getNextMonth(calendarState.currentMonth)),
);

const selectPreviousMonth = React.useCallback(() => {
changeMonth(utils.getPreviousMonth(calendarState.currentMonth));
}, [changeMonth, calendarState.currentMonth, utils]);
const selectPreviousMonth = useEventCallback(() =>
slideToMonth(utils.getPreviousMonth(calendarState.currentMonth)),
);

const isNextMonthDisabled = useNextMonthDisabled(calendarState.currentMonth, {
disableFuture,
Expand Down Expand Up @@ -365,7 +360,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar<TDate>(
views={['day']}
openView={'day'}
currentMonth={calendarState.currentMonth}
onMonthChange={(newMonth, direction) => handleChangeMonth({ newMonth, direction })}
onMonthChange={slideToMonth}
minDate={minDateWithDisabled}
maxDate={maxDateWithDisabled}
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ function DateRangePickerViewRaw<TDate>(props: DateRangePickerViewProps<TDate>) {
((dayToTest: TDate) => shouldDisableDate?.(dayToTest, currentlySelectingRangeEnd));

const [start, end] = value;
const { changeMonth, calendarState, onMonthSwitchingAnimationEnd, changeFocusedDay } =
const { slideToMonth, calendarState, onMonthSwitchingAnimationEnd, changeFocusedDay } =
useCalendarState<TDate>({
value: start || end,
defaultCalendarMonth,
Expand Down Expand Up @@ -178,7 +178,7 @@ function DateRangePickerViewRaw<TDate>(props: DateRangePickerViewProps<TDate>) {
: // If need to focus end, scroll to the state when "end" is displaying in the last calendar
utils.addMonths(date, -displayingMonthRange);

changeMonth(newMonth);
slideToMonth(newMonth);
}
}, [currentlySelectingRangeEnd, value]); // eslint-disable-line

Expand Down Expand Up @@ -225,7 +225,7 @@ function DateRangePickerViewRaw<TDate>(props: DateRangePickerViewProps<TDate>) {
reduceAnimations,
disableHighlightToday,
onMonthSwitchingAnimationEnd,
changeMonth,
slideToMonth,
currentlySelectingRangeEnd,
disableFuture,
disablePast,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export interface DateRangePickerViewDesktopProps<TDate>
componentsProps?: DesktopDateRangeCalendarSlotsComponentsProps<TDate>;
calendars: 1 | 2 | 3;
value: DateRange<TDate>;
changeMonth: (date: TDate) => void;
slideToMonth: (date: TDate) => void;
currentlySelectingRangeEnd: 'start' | 'end';
classes?: Partial<DateRangePickerViewDesktopClasses>;
}
Expand Down Expand Up @@ -145,7 +145,7 @@ export function DateRangePickerViewDesktop<TDate>(inProps: DateRangePickerViewDe
const props = useThemeProps({ props: inProps, name: 'MuiDateRangePickerViewDesktop' });
const {
calendars,
changeMonth,
slideToMonth,
components,
componentsProps,
currentlySelectingRangeEnd,
Expand Down Expand Up @@ -218,12 +218,12 @@ export function DateRangePickerViewDesktop<TDate>(inProps: DateRangePickerViewDe
);

const selectNextMonth = React.useCallback(() => {
changeMonth(utils.getNextMonth(currentMonth));
}, [changeMonth, currentMonth, utils]);
slideToMonth(utils.getNextMonth(currentMonth));
}, [slideToMonth, currentMonth, utils]);
Comment on lines 220 to +222
Copy link
Member

Choose a reason for hiding this comment

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

Nitpick: Should we maybe replace these with useEventCallback? 🤔


const selectPreviousMonth = React.useCallback(() => {
changeMonth(utils.getPreviousMonth(currentMonth));
}, [changeMonth, currentMonth, utils]);
slideToMonth(utils.getPreviousMonth(currentMonth));
}, [slideToMonth, currentMonth, utils]);

const componentsForDayCalendar = {
Day: DateRangePickerDay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ interface DesktopDateRangeCalendarProps<TDate>
*/
componentsProps?: DateRangePickerViewMobileSlotsComponentsProps<TDate>;
value: DateRange<TDate>;
changeMonth: (date: TDate) => void;
slideToMonth: (date: TDate) => void;
}

const onlyDayView = ['day'] as const;
Expand All @@ -64,7 +64,7 @@ const onlyDayView = ['day'] as const;
*/
export function DateRangePickerViewMobile<TDate>(props: DesktopDateRangeCalendarProps<TDate>) {
const {
changeMonth,
slideToMonth,
components,
componentsProps,
value,
Expand Down Expand Up @@ -127,7 +127,7 @@ export function DateRangePickerViewMobile<TDate>(props: DesktopDateRangeCalendar
componentsProps={componentsProps}
maxDate={maxDateWithDisabled}
minDate={minDateWithDisabled}
onMonthChange={changeMonth as any}
onMonthChange={slideToMonth as any}
openView="day"
views={onlyDayView}
disabled={disabled}
Expand Down
50 changes: 28 additions & 22 deletions packages/x-date-pickers/src/DateCalendar/DateCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -296,26 +296,10 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate>(
default: defaultValue ?? null,
});

const handleValueChange = useEventCallback(
(newValue: TDate | null, selectionState?: PickerSelectionState) => {
setValue(newValue);
onChange?.(newValue, selectionState);
},
);

const { openView, setOpenView, openNext } = useViews({
view,
views,
openTo,
onChange: handleValueChange,
onViewChange,
});

const {
calendarState,
changeFocusedDay,
changeMonth,
handleChangeMonth,
slideToMonth,
isDateDisabled,
onMonthSwitchingAnimationEnd,
} = useCalendarState({
Expand All @@ -330,6 +314,29 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate>(
disableFuture,
});

const handleValueChange = useEventCallback(
(newValue: TDate | null, selectionState?: PickerSelectionState) => {
setValue(newValue);
onChange?.(newValue, selectionState);

if (newValue != null) {
Copy link
Member Author

@flaviendelangle flaviendelangle Nov 22, 2022

Choose a reason for hiding this comment

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

We now do the checks here to make sure that any value change causes a call to onMonthChange /onYearChange if needed

slideToMonth(newValue);

if (onYearChange && !utils.isSameYear(newValue, calendarState.currentMonth)) {
onYearChange(newValue);
}
}
},
);

const { openView, setOpenView, openNext } = useViews({
view,
views,
openTo,
onChange: handleValueChange,
onViewChange,
});

const handleDateMonthChange = useEventCallback((newDate: TDate) => {
const startOfMonth = utils.startOfMonth(newDate);
const endOfMonth = utils.endOfMonth(newDate);
Expand All @@ -351,7 +358,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate>(
onMonthChange?.(startOfMonth);
} else {
openNext();
changeMonth(startOfMonth);
slideToMonth(startOfMonth);
}

changeFocusedDay(closestEnabledDate, true);
Expand All @@ -375,10 +382,9 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate>(

if (closestEnabledDate) {
handleValueChange(closestEnabledDate, 'finish');
onYearChange?.(closestEnabledDate);
} else {
openNext();
changeMonth(startOfYear);
slideToMonth(startOfYear);
}

changeFocusedDay(closestEnabledDate, true);
Expand All @@ -397,7 +403,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate>(

React.useEffect(() => {
if (value != null && utils.isValid(value)) {
changeMonth(value);
slideToMonth(value);
}
}, [value]); // eslint-disable-line

Expand Down Expand Up @@ -474,7 +480,7 @@ export const DateCalendar = React.forwardRef(function DateCalendar<TDate>(
openView={openView}
currentMonth={calendarState.currentMonth}
onViewChange={setOpenView}
onMonthChange={(newMonth, direction) => handleChangeMonth({ newMonth, direction })}
onMonthChange={slideToMonth}
Copy link
Member Author

Choose a reason for hiding this comment

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

I now compute the direction inside useCalendarState to have a single month change method exposed

minDate={minDateWithDisabled}
maxDate={maxDateWithDisabled}
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { SlotComponentProps, useSlotProps } from '@mui/base/utils';
import { unstable_composeClasses as composeClasses } from '@mui/utils';
import IconButton from '@mui/material/IconButton';
import SvgIcon from '@mui/material/SvgIcon';
import { SlideDirection } from './PickersSlideTransition';
import { useLocaleText, useUtils } from '../internals/hooks/useUtils';
import { PickersFadeTransitionGroup } from './PickersFadeTransitionGroup';
import { DateComponentValidationProps } from '../internals/hooks/validation/useDateValidation';
Expand Down Expand Up @@ -77,7 +76,7 @@ export interface PickersCalendarHeaderProps<TDate>
currentMonth: TDate;
disabled?: boolean;
views: readonly DateView[];
onMonthChange: (date: TDate, slideDirection: SlideDirection) => void;
onMonthChange: (date: TDate) => void;
openView: DateView;
reduceAnimations: boolean;
onViewChange?: (view: DateView) => void;
Expand Down Expand Up @@ -218,8 +217,8 @@ export function PickersCalendarHeader<TDate>(inProps: PickersCalendarHeaderProps
className: classes.switchViewIcon,
});

const selectNextMonth = () => onMonthChange(utils.getNextMonth(month), 'left');
const selectPreviousMonth = () => onMonthChange(utils.getPreviousMonth(month), 'right');
const selectNextMonth = () => onMonthChange(utils.getNextMonth(month));
const selectPreviousMonth = () => onMonthChange(utils.getPreviousMonth(month));

const isNextMonthDisabled = useNextMonthDisabled(month, {
disableFuture,
Expand Down
64 changes: 31 additions & 33 deletions packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx
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';
import { SlideDirection } from './PickersSlideTransition';
import { useIsDateDisabled } from '../internals/hooks/validation/useDateValidation';
import { useUtils, useNow } from '../internals/hooks/useUtils';
Expand Down Expand Up @@ -71,18 +72,27 @@ export const createCalendarStateReducer =
!disableSwitchToMonthOnDayFocus &&
!utils.isSameMonth(state.currentMonth, action.focusedDay);

let slideDirection: SlideDirection;
if (!needMonthSwitch) {
slideDirection = state.slideDirection;
Copy link
Member Author

Choose a reason for hiding this comment

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

Before, the slide to the new month was caused by the useEffect tracking the external value.
So the focus of the clicked day was already done
But now it is the click itself which caused the slide (like when you click on the array left / right).

Without this check, the focus was resetting the slide direction, causing it to go on the wrong direction in some scenario.

} else if (
action.focusedDay != null &&
utils.isAfterDay(action.focusedDay, state.currentMonth)
) {
slideDirection = 'left';
} else {
slideDirection = 'right';
}

return {
...state,
slideDirection,
focusedDay: action.focusedDay,
isMonthSwitchingAnimating:
needMonthSwitch && !reduceAnimations && !action.withoutMonthSwitchingAnimation,
currentMonth: needMonthSwitch
? utils.startOfMonth(action.focusedDay!)
: state.currentMonth,
slideDirection:
action.focusedDay != null && utils.isAfterDay(action.focusedDay, state.currentMonth)
? 'left'
: 'right',
};
}

Expand Down Expand Up @@ -139,36 +149,25 @@ export const useCalendarState = <TDate extends unknown>({
slideDirection: 'left',
});

const handleChangeMonth = React.useCallback(
(payload: ChangeMonthPayload<TDate>) => {
dispatch({
type: 'changeMonth',
...payload,
});
const slideToMonth = useEventCallback((newDate: TDate) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed to make clear that we are not updating any value, just "meta data" used to handle the visually displayed month

Copy link
Member

Choose a reason for hiding this comment

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

If we are going this route, then maybe onYearChange should also be called when year changes when navigating months (not only when choosing a date in a different month—which is also in a different year)? 🤔

const newDateRequested = newDate;
if (utils.isSameMonth(newDateRequested, calendarState.currentMonth)) {
return;
}

if (onMonthChange) {
onMonthChange(payload.newMonth);
}
},
[onMonthChange],
);
const newMonth = utils.startOfMonth(newDateRequested);
const direction = utils.isAfterDay(newDateRequested, calendarState.currentMonth)
? 'left'
: 'right';

const changeMonth = React.useCallback(
(newDate: TDate) => {
const newDateRequested = newDate;
if (utils.isSameMonth(newDateRequested, calendarState.currentMonth)) {
return;
}
dispatch({
type: 'changeMonth',
newMonth,
direction,
});

handleChangeMonth({
newMonth: utils.startOfMonth(newDateRequested),
direction: utils.isAfterDay(newDateRequested, calendarState.currentMonth)
? 'left'
: 'right',
});
},
[calendarState.currentMonth, handleChangeMonth, utils],
);
onMonthChange?.(newMonth);
});

const isDateDisabled = useIsDateDisabled({
shouldDisableDate,
Expand Down Expand Up @@ -197,10 +196,9 @@ export const useCalendarState = <TDate extends unknown>({

return {
calendarState,
changeMonth,
slideToMonth,
changeFocusedDay,
isDateDisabled,
onMonthSwitchingAnimationEnd,
handleChangeMonth,
};
};