@mui/x-date-pickers 8.0.0-alpha.10 → 8.0.0-alpha.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,110 @@
5
5
  All notable changes to this project will be documented in this file.
6
6
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
7
7
 
8
+ ## **8.0.0-alpha.11**
9
+
10
+ _Feb 7, 2025_
11
+
12
+ We'd like to offer a big thanks to the 11 contributors who made this release possible. Here are some highlights ✨:
13
+
14
+ - ⚡ Mount and resize performance improvements for the Data Grid
15
+
16
+ Special thanks go out to the community contributors who have helped make this release possible:
17
+ @lauri865.
18
+ Following are all team members who have contributed to this release:
19
+ @alexfauquette, @arminmeh, @bernardobelchior, @flaviendelangle, @Janpot, @KenanYusuf, @LukasTy, @MBilalShafi, @noraleonte, @romgrk.
20
+
21
+ <!--/ HIGHLIGHT_ABOVE_SEPARATOR /-->
22
+
23
+ ### Data Grid
24
+
25
+ #### Breaking changes
26
+
27
+ - `createUseGridApiEventHandler()` is not exported anymore.
28
+ - The `filteredRowsLookup` object of the filter state does not contain `true` values anymore. If the row is filtered out, the value is `false`. Otherwise, the row id is not present in the object.
29
+ This change only impacts you if you relied on `filteredRowsLookup` to get ids of filtered rows. In this case,use `gridDataRowIdsSelector` selector to get row ids and check `filteredRowsLookup` for `false` values:
30
+
31
+ ```diff
32
+ const filteredRowsLookup = gridFilteredRowsLookupSelector(apiRef);
33
+ -const filteredRowIds = Object.keys(filteredRowsLookup).filter((rowId) => filteredRowsLookup[rowId] === true);
34
+ +const rowIds = gridDataRowIdsSelector(apiRef);
35
+ +const filteredRowIds = rowIds.filter((rowId) => filteredRowsLookup[rowId] !== false);
36
+ ```
37
+
38
+ - The `visibleRowsLookup` state does not contain `true` values anymore. If the row is not visible, the value is `false`. Otherwise, the row id is not present in the object:
39
+
40
+ ```diff
41
+ const visibleRowsLookup = gridVisibleRowsLookupSelector(apiRef);
42
+ -const isRowVisible = visibleRowsLookup[rowId] === true;
43
+ +const isRowVisible = visibleRowsLookup[rowId] !== false;
44
+ ```
45
+
46
+ #### `@mui/x-data-grid@8.0.0-alpha.11`
47
+
48
+ - [DataGrid] Avoid `<GridRoot />` double-render pass on mount in SPA mode (#15648) @lauri865
49
+ - [DataGrid] Fix loading overlay not in sync with scroll (#16437) @MBilalShafi
50
+ - [DataGrid] Refactor: remove material `MenuList` import (#16444) @romgrk
51
+ - [DataGrid] Refactor: simplify `useGridApiEventHandler()` (#16479) @romgrk
52
+
53
+ #### `@mui/x-data-grid-pro@8.0.0-alpha.11` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
54
+
55
+ Same changes as in `@mui/x-data-grid@8.0.0-alpha.11`, plus:
56
+
57
+ - [DataGridPro] Fix the return type of `useGridApiContext()` for Pro and Premium packages on React < 19 (#16441) @arminmeh
58
+
59
+ #### `@mui/x-data-grid-premium@8.0.0-alpha.11` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan')
60
+
61
+ Same changes as in `@mui/x-data-grid-pro@8.0.0-alpha.11`, plus:
62
+
63
+ - [DataGridPremium] Fix "no rows" overlay not showing with active aggregation (#16466) @KenanYusuf
64
+
65
+ ### Date and Time Pickers
66
+
67
+ #### `@mui/x-date-pickers@8.0.0-alpha.11`
68
+
69
+ Internal changes.
70
+
71
+ #### `@mui/x-date-pickers-pro@8.0.0-alpha.11` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
72
+
73
+ Same changes as in `@mui/x-date-pickers@8.0.0-alpha.11`, plus:
74
+
75
+ - [DateRangeCalendar] Support arrow navigation with multiple months rendered (#16363) @flaviendelangle
76
+ - [DateRangePicker] Fix `currentMonthCalendarPosition` prop behavior on mobile (#16455) @LukasTy
77
+ - [DateRangePicker] Fix vertical alignment for multi input fields (#16489) @noraleonte
78
+
79
+ ### Charts
80
+
81
+ #### `@mui/x-charts@8.0.0-alpha.11`
82
+
83
+ - [charts] Add `color` prop to `Sparkline` and deprecate `colors` (#16477) @bernardobelchior
84
+ - [charts] Make typescript more flexible about plugins and their params (#16478) @alexfauquette
85
+ - [charts] Remove component for axis event listener (#16314) @alexfauquette
86
+
87
+ #### `@mui/x-charts-pro@8.0.0-alpha.11` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
88
+
89
+ Same changes as in `@mui/x-charts@8.0.0-alpha.11`.
90
+
91
+ ### Tree View
92
+
93
+ #### `@mui/x-tree-view@8.0.0-alpha.11`
94
+
95
+ Internal changes.
96
+
97
+ #### `@mui/x-tree-view-pro@8.0.0-alpha.11` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan')
98
+
99
+ Same changes as in `@mui/x-tree-view@8.0.0-alpha.11`.
100
+
101
+ ### Docs
102
+
103
+ - [docs] Update charts colors default value (#16484) @bernardobelchior
104
+
105
+ ### Core
106
+
107
+ - [core] Fix corepack and pnpm installation in CircleCI (#16434) @flaviendelangle
108
+ - [code-infra] Update monorepo (#16112) @Janpot
109
+ - [test] Avoid test warning when running on React 18 (#16486) @LukasTy
110
+ - [test] Disable `react-transition-group` transitions in unit testing (#16288) @lauri865
111
+
8
112
  ## 8.0.0-alpha.10
9
113
 
10
114
  _Jan 30, 2025_
@@ -50,6 +154,7 @@ Following are all team members who have contributed to this release:
50
154
  + },
51
155
  });
52
156
  ```
157
+
53
158
  - The `detailPanels`, `pinnedColumns`, and `pinnedRowsRenderZone` classes have been removed.
54
159
  - Return type of the `useGridApiRef()` hook and the type of `apiRef` prop are updated to explicitly include the possibilty of `null`. In addition to this, `useGridApiRef()` returns a reference that is initialized with `null` instead of `{}`.
55
160
 
@@ -91,7 +196,7 @@ Same changes as in `@mui/x-data-grid-pro@8.0.0-alpha.10`.
91
196
 
92
197
  #### Breaking changes
93
198
 
94
- - The component passed to the `field` slot no longer receives the `ref`, `disabled`, `className`, `sx`, `label`, `name`, `formatDensity`, `enableAccessibleFieldDOMStructure`, `selectedSections`, `onSelectedSectionsChange` and `inputRef` props — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-field)
199
+ - The component passed to the `field` slot no longer receives the `ref`, `disabled`, `className`, `sx`, `label`, `name`, `formatDensity`, `enableAccessibleFieldDOMStructure`, `selectedSections`, `onSelectedSectionsChange` and `inputRef` props — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#slot-field)
95
200
  - The `MuiPickersPopper` theme entry have been renamed `MuiPickerPopper` and some of its props have been removed — [Learn more](https://next.mui.com/x/migration/migration-pickers-v7/#muipickerspopper)
96
201
 
97
202
  #### `@mui/x-date-pickers@8.0.0-alpha.10`
@@ -111,7 +216,7 @@ Same changes as in `@mui/x-date-pickers@8.0.0-alpha.10`.
111
216
 
112
217
  - Replace `legend.position.horizontal` from `"left" | "middle" | "right"` to `"start" | "center" | "end"`.
113
218
  This is to align with the CSS values and reflect the RTL ability of the legend component.
114
- - The default colors have changed. To keep using the old palette. It is possible to import `blueberryTwilightPalette` from `@mui/x-charts/colorPalettes` and set it on the `colors` property of charts.
219
+ - The default colors have changed. To keep using the old palette. It is possible to import `blueberryTwilightPalette` from `@mui/x-charts/colorPalettes` and set it on the `colors` property of charts.
115
220
  - The `id` property is now optional on the `Pie` and `Scatter` data types.
116
221
 
117
222
  #### `@mui/x-charts@8.0.0-alpha.10`
@@ -96,7 +96,6 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
96
96
  disableFuture,
97
97
  disablePast,
98
98
  onChange,
99
- onYearChange,
100
99
  onMonthChange,
101
100
  reduceAnimations,
102
101
  shouldDisableDate,
@@ -112,7 +111,7 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
112
111
  minDate,
113
112
  maxDate,
114
113
  disableHighlightToday,
115
- focusedView: inFocusedView,
114
+ focusedView: focusedViewProp,
116
115
  onFocusedViewChange,
117
116
  showDaysOutsideCurrentMonth,
118
117
  fixedWeekNumber,
@@ -155,15 +154,13 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
155
154
  onChange: handleValueChange,
156
155
  onViewChange,
157
156
  autoFocus,
158
- focusedView: inFocusedView,
157
+ focusedView: focusedViewProp,
159
158
  onFocusedViewChange
160
159
  });
161
160
  const {
162
161
  referenceDate,
163
162
  calendarState,
164
- changeFocusedDay,
165
- changeMonth,
166
- handleChangeMonth,
163
+ setVisibleDate,
167
164
  isDateDisabled,
168
165
  onMonthSwitchingAnimationEnd
169
166
  } = useCalendarState({
@@ -176,7 +173,13 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
176
173
  shouldDisableDate,
177
174
  disablePast,
178
175
  disableFuture,
179
- timezone
176
+ timezone,
177
+ getCurrentMonthFromVisibleDate: (visibleDate, prevMonth) => {
178
+ if (utils.isSameMonth(visibleDate, prevMonth)) {
179
+ return prevMonth;
180
+ }
181
+ return utils.startOfMonth(visibleDate);
182
+ }
180
183
  });
181
184
 
182
185
  // When disabled, limit the view to the selected date
@@ -193,9 +196,9 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
193
196
  view,
194
197
  currentMonth: calendarState.currentMonth,
195
198
  onViewChange: setView,
196
- onMonthChange: (newMonth, direction) => handleChangeMonth({
197
- newMonth,
198
- direction
199
+ onMonthChange: month => setVisibleDate({
200
+ target: month,
201
+ reason: 'header-navigation'
199
202
  }),
200
203
  minDate: minDateWithDisabled,
201
204
  maxDate: maxDateWithDisabled,
@@ -223,12 +226,17 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
223
226
  }) : newDate;
224
227
  if (closestEnabledDate) {
225
228
  setValueAndGoToNextView(closestEnabledDate, 'finish');
226
- onMonthChange?.(startOfMonth);
229
+ setVisibleDate({
230
+ target: closestEnabledDate,
231
+ reason: 'cell-interaction'
232
+ });
227
233
  } else {
228
234
  goToNextView();
229
- changeMonth(startOfMonth);
235
+ setVisibleDate({
236
+ target: startOfMonth,
237
+ reason: 'cell-interaction'
238
+ });
230
239
  }
231
- changeFocusedDay(closestEnabledDate, true);
232
240
  });
233
241
  const handleDateYearChange = useEventCallback(newDate => {
234
242
  const startOfYear = utils.startOfYear(newDate);
@@ -245,12 +253,17 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
245
253
  }) : newDate;
246
254
  if (closestEnabledDate) {
247
255
  setValueAndGoToNextView(closestEnabledDate, 'finish');
248
- onYearChange?.(closestEnabledDate);
256
+ setVisibleDate({
257
+ target: closestEnabledDate,
258
+ reason: 'cell-interaction'
259
+ });
249
260
  } else {
250
261
  goToNextView();
251
- changeMonth(startOfYear);
262
+ setVisibleDate({
263
+ target: startOfYear,
264
+ reason: 'cell-interaction'
265
+ });
252
266
  }
253
- changeFocusedDay(closestEnabledDate, true);
254
267
  });
255
268
  const handleSelectedDayChange = useEventCallback(day => {
256
269
  if (day) {
@@ -261,7 +274,10 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
261
274
  });
262
275
  React.useEffect(() => {
263
276
  if (utils.isValid(value)) {
264
- changeMonth(value);
277
+ setVisibleDate({
278
+ target: value,
279
+ reason: 'controlled-value-change'
280
+ });
265
281
  }
266
282
  }, [value]); // eslint-disable-line
267
283
 
@@ -328,7 +344,10 @@ export const DateCalendar = /*#__PURE__*/React.forwardRef(function DateCalendar(
328
344
  referenceDate: referenceDate
329
345
  })), view === 'day' && /*#__PURE__*/_jsx(DayCalendar, _extends({}, calendarState, baseDateValidationProps, commonViewProps, {
330
346
  onMonthSwitchingAnimationEnd: onMonthSwitchingAnimationEnd,
331
- onFocusedDayChange: changeFocusedDay,
347
+ onFocusedDayChange: focusedDate => setVisibleDate({
348
+ target: focusedDate,
349
+ reason: 'cell-interaction'
350
+ }),
332
351
  reduceAnimations: reduceAnimations,
333
352
  selectedDays: selectedDays,
334
353
  onSelectedDaysChange: handleSelectedDayChange,
@@ -54,7 +54,6 @@ export interface ExportedDayCalendarProps extends ExportedPickersDayProps {
54
54
  fixedWeekNumber?: number;
55
55
  }
56
56
  export interface DayCalendarProps extends ExportedDayCalendarProps, DayValidationProps, MonthValidationProps, YearValidationProps, Required<BaseDateValidationProps>, DefaultizedProps<TimezoneProps, 'timezone'>, FormProps {
57
- autoFocus?: boolean;
58
57
  className?: string;
59
58
  currentMonth: PickerValidDate;
60
59
  selectedDays: (PickerValidDate | null)[];
@@ -66,7 +65,7 @@ export interface DayCalendarProps extends ExportedDayCalendarProps, DayValidatio
66
65
  reduceAnimations: boolean;
67
66
  slideDirection: SlideDirection;
68
67
  TransitionProps?: Partial<SlideTransitionProps>;
69
- hasFocus?: boolean;
68
+ hasFocus: boolean;
70
69
  onFocusedViewChange?: (newHasFocus: boolean) => void;
71
70
  gridLabelId?: string;
72
71
  /**
@@ -2,7 +2,7 @@
2
2
 
3
3
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
4
4
  import _extends from "@babel/runtime/helpers/esm/extends";
5
- const _excluded = ["parentProps", "day", "focusableDay", "selectedDays", "isDateDisabled", "currentMonthNumber", "isViewFocused"],
5
+ const _excluded = ["parentProps", "day", "focusedDay", "selectedDays", "isDateDisabled", "currentMonthNumber", "isViewFocused"],
6
6
  _excluded2 = ["ownerState"];
7
7
  import * as React from 'react';
8
8
  import useEventCallback from '@mui/utils/useEventCallback';
@@ -10,7 +10,7 @@ import Typography from '@mui/material/Typography';
10
10
  import useSlotProps from '@mui/utils/useSlotProps';
11
11
  import { useRtl } from '@mui/system/RtlProvider';
12
12
  import { styled, useThemeProps } from '@mui/material/styles';
13
- import { unstable_composeClasses as composeClasses, unstable_useControlled as useControlled } from '@mui/utils';
13
+ import composeClasses from '@mui/utils/composeClasses';
14
14
  import clsx from 'clsx';
15
15
  import { PickersDay } from "../PickersDay/PickersDay.js";
16
16
  import { usePickerTranslations } from "../hooks/usePickerTranslations.js";
@@ -137,7 +137,7 @@ function WrappedDay(_ref) {
137
137
  let {
138
138
  parentProps,
139
139
  day,
140
- focusableDay,
140
+ focusedDay,
141
141
  selectedDays,
142
142
  isDateDisabled,
143
143
  currentMonthNumber,
@@ -158,7 +158,8 @@ function WrappedDay(_ref) {
158
158
  const {
159
159
  ownerState
160
160
  } = usePickerPrivateContext();
161
- const isFocusableDay = focusableDay !== null && utils.isSameDay(day, focusableDay);
161
+ const isFocusableDay = focusedDay != null && utils.isSameDay(day, focusedDay);
162
+ const isFocusedDay = isViewFocused && isFocusableDay;
162
163
  const isSelected = selectedDays.some(selectedDay => utils.isSameDay(selectedDay, day));
163
164
  const isToday = utils.isSameDay(day, now);
164
165
  const isDisabled = React.useMemo(() => disabled || isDateDisabled(day), [disabled, isDateDisabled, day]);
@@ -200,7 +201,7 @@ function WrappedDay(_ref) {
200
201
  return /*#__PURE__*/_jsx(Day, _extends({}, dayProps, {
201
202
  day: day,
202
203
  disabled: isDisabled,
203
- autoFocus: isViewFocused && isFocusableDay,
204
+ autoFocus: !outsideCurrentMonth && isFocusedDay,
204
205
  today: isToday,
205
206
  outsideCurrentMonth: outsideCurrentMonth,
206
207
  isFirstVisibleCell: isFirstVisibleCell,
@@ -251,7 +252,6 @@ export function DayCalendar(inProps) {
251
252
  gridLabelId,
252
253
  displayWeekNumber,
253
254
  fixedWeekNumber,
254
- autoFocus,
255
255
  timezone
256
256
  } = props;
257
257
  const now = useNow(timezone);
@@ -268,13 +268,6 @@ export function DayCalendar(inProps) {
268
268
  timezone
269
269
  });
270
270
  const translations = usePickerTranslations();
271
- const [internalHasFocus, setInternalHasFocus] = useControlled({
272
- name: 'DayCalendar',
273
- state: 'hasFocus',
274
- controlled: hasFocus,
275
- default: autoFocus ?? false
276
- });
277
- const [internalFocusedDay, setInternalFocusedDay] = React.useState(() => focusedDay || now);
278
271
  const handleDaySelect = useEventCallback(day => {
279
272
  if (readOnly) {
280
273
  return;
@@ -284,9 +277,7 @@ export function DayCalendar(inProps) {
284
277
  const focusDay = day => {
285
278
  if (!isDateDisabled(day)) {
286
279
  onFocusedDayChange(day);
287
- setInternalFocusedDay(day);
288
280
  onFocusedViewChange?.(true);
289
- setInternalHasFocus(true);
290
281
  }
291
282
  };
292
283
  const handleKeyDown = useEventCallback((event, day) => {
@@ -353,7 +344,7 @@ export function DayCalendar(inProps) {
353
344
  });
354
345
  const handleFocus = useEventCallback((event, day) => focusDay(day));
355
346
  const handleBlur = useEventCallback((event, day) => {
356
- if (internalHasFocus && utils.isSameDay(internalFocusedDay, day)) {
347
+ if (focusedDay != null && utils.isSameDay(focusedDay, day)) {
357
348
  onFocusedViewChange?.(false);
358
349
  }
359
350
  });
@@ -365,23 +356,6 @@ export function DayCalendar(inProps) {
365
356
  const transitionKey = `${currentYearNumber}-${currentMonthNumber}`;
366
357
  // eslint-disable-next-line react-hooks/exhaustive-deps
367
358
  const slideNodeRef = React.useMemo(() => /*#__PURE__*/React.createRef(), [transitionKey]);
368
- const focusableDay = React.useMemo(() => {
369
- const startOfMonth = utils.startOfMonth(currentMonth);
370
- const endOfMonth = utils.endOfMonth(currentMonth);
371
- if (isDateDisabled(internalFocusedDay) || utils.isAfterDay(internalFocusedDay, endOfMonth) || utils.isBeforeDay(internalFocusedDay, startOfMonth)) {
372
- return findClosestEnabledDate({
373
- utils,
374
- date: internalFocusedDay,
375
- minDate: startOfMonth,
376
- maxDate: endOfMonth,
377
- disablePast,
378
- disableFuture,
379
- isDateDisabled,
380
- timezone
381
- });
382
- }
383
- return internalFocusedDay;
384
- }, [currentMonth, disableFuture, disablePast, internalFocusedDay, isDateDisabled, utils, timezone]);
385
359
  const weeksToDisplay = React.useMemo(() => {
386
360
  const toDisplay = utils.getWeekArray(currentMonth);
387
361
  let nextMonth = utils.addMonths(currentMonth, 1);
@@ -448,14 +422,14 @@ export function DayCalendar(inProps) {
448
422
  parentProps: props,
449
423
  day: day,
450
424
  selectedDays: validSelectedDays,
451
- focusableDay: focusableDay,
425
+ isViewFocused: hasFocus,
426
+ focusedDay: focusedDay,
452
427
  onKeyDown: handleKeyDown,
453
428
  onFocus: handleFocus,
454
429
  onBlur: handleBlur,
455
430
  onDaySelect: handleDaySelect,
456
431
  isDateDisabled: isDateDisabled,
457
- currentMonthNumber: currentMonthNumber,
458
- isViewFocused: internalHasFocus
432
+ currentMonthNumber: currentMonthNumber
459
433
  // fix issue of announcing column 1 as column 2 when `displayWeekNumber` is enabled
460
434
  ,
461
435
  "aria-colindex": dayIndex + 1
@@ -1,5 +1,5 @@
1
1
  import { SlideDirection } from './PickersSlideTransition';
2
- import { MuiPickersAdapter, PickersTimezone, PickerValidDate } from '../models';
2
+ import { PickersTimezone, PickerValidDate } from '../models';
3
3
  import { DateCalendarDefaultizedProps } from './DateCalendar.types';
4
4
  interface CalendarState {
5
5
  currentMonth: PickerValidDate;
@@ -7,37 +7,21 @@ interface CalendarState {
7
7
  isMonthSwitchingAnimating: boolean;
8
8
  slideDirection: SlideDirection;
9
9
  }
10
- type ReducerAction<TType, TAdditional = {}> = {
11
- type: TType;
12
- } & TAdditional;
13
- interface ChangeMonthPayload {
14
- direction: SlideDirection;
15
- newMonth: PickerValidDate;
16
- }
17
- interface ChangeFocusedDayPayload {
18
- focusedDay: PickerValidDate | null;
19
- /**
20
- * The update does not trigger month switching animation.
21
- * For example: when selecting month from the month view.
22
- */
23
- withoutMonthSwitchingAnimation?: boolean;
24
- }
25
- export declare const createCalendarStateReducer: (reduceAnimations: boolean, disableSwitchToMonthOnDayFocus: boolean, utils: MuiPickersAdapter) => (state: CalendarState, action: ReducerAction<"finishMonthSwitchingAnimation"> | ReducerAction<"changeMonth", ChangeMonthPayload> | ReducerAction<"changeMonthTimezone", {
26
- newTimezone: string;
27
- }> | ReducerAction<"changeFocusedDay", ChangeFocusedDayPayload>) => CalendarState;
28
- interface UseCalendarStateParameters extends Pick<DateCalendarDefaultizedProps, 'referenceDate' | 'disableFuture' | 'disablePast' | 'minDate' | 'maxDate' | 'onMonthChange' | 'reduceAnimations' | 'shouldDisableDate'> {
10
+ interface UseCalendarStateParameters extends Pick<DateCalendarDefaultizedProps, 'referenceDate' | 'disableFuture' | 'disablePast' | 'minDate' | 'maxDate' | 'onMonthChange' | 'onYearChange' | 'reduceAnimations' | 'shouldDisableDate'> {
29
11
  value: PickerValidDate | null;
30
- disableSwitchToMonthOnDayFocus?: boolean;
31
12
  timezone: PickersTimezone;
13
+ getCurrentMonthFromVisibleDate: (focusedDay: PickerValidDate, prevMonth: PickerValidDate) => PickerValidDate;
32
14
  }
33
15
  interface UseCalendarStateReturnValue {
34
16
  referenceDate: PickerValidDate;
35
17
  calendarState: CalendarState;
36
- changeMonth: (newDate: PickerValidDate) => void;
37
- changeFocusedDay: (newFocusedDate: PickerValidDate | null, withoutMonthSwitchingAnimation?: boolean) => void;
18
+ setVisibleDate: (parameters: SetVisibleDateParameters) => void;
38
19
  isDateDisabled: (day: PickerValidDate | null) => boolean;
39
20
  onMonthSwitchingAnimationEnd: () => void;
40
- handleChangeMonth: (payload: ChangeMonthPayload) => void;
41
21
  }
42
22
  export declare const useCalendarState: (params: UseCalendarStateParameters) => UseCalendarStateReturnValue;
23
+ interface SetVisibleDateParameters {
24
+ target: PickerValidDate;
25
+ reason: 'header-navigation' | 'cell-interaction' | 'controlled-value-change';
26
+ }
43
27
  export {};
@@ -7,13 +7,15 @@ import { useIsDateDisabled } from "./useIsDateDisabled.js";
7
7
  import { useUtils } from "../internals/hooks/useUtils.js";
8
8
  import { singleItemValueManager } from "../internals/utils/valueManagers.js";
9
9
  import { SECTION_TYPE_GRANULARITY } from "../internals/utils/getDefaultReferenceDate.js";
10
- export const createCalendarStateReducer = (reduceAnimations, disableSwitchToMonthOnDayFocus, utils) => (state, action) => {
10
+ import { findClosestEnabledDate } from "../internals/utils/date-utils.js";
11
+ const createCalendarStateReducer = (reduceAnimations, utils) => (state, action) => {
11
12
  switch (action.type) {
12
- case 'changeMonth':
13
+ case 'setVisibleDate':
13
14
  return _extends({}, state, {
14
15
  slideDirection: action.direction,
15
- currentMonth: action.newMonth,
16
- isMonthSwitchingAnimating: !reduceAnimations
16
+ currentMonth: action.month,
17
+ isMonthSwitchingAnimating: !utils.isSameMonth(action.month, state.currentMonth) && !reduceAnimations && !action.skipAnimation,
18
+ focusedDay: action.focusedDay
17
19
  });
18
20
  case 'changeMonthTimezone':
19
21
  {
@@ -33,19 +35,6 @@ export const createCalendarStateReducer = (reduceAnimations, disableSwitchToMont
33
35
  return _extends({}, state, {
34
36
  isMonthSwitchingAnimating: false
35
37
  });
36
- case 'changeFocusedDay':
37
- {
38
- if (state.focusedDay != null && action.focusedDay != null && utils.isSameDay(action.focusedDay, state.focusedDay)) {
39
- return state;
40
- }
41
- const needMonthSwitch = action.focusedDay != null && !disableSwitchToMonthOnDayFocus && !utils.isSameMonth(state.currentMonth, action.focusedDay);
42
- return _extends({}, state, {
43
- focusedDay: action.focusedDay,
44
- isMonthSwitchingAnimating: needMonthSwitch && !reduceAnimations && !action.withoutMonthSwitchingAnimation,
45
- currentMonth: needMonthSwitch ? utils.startOfMonth(action.focusedDay) : state.currentMonth,
46
- slideDirection: action.focusedDay != null && utils.isAfterDay(action.focusedDay, state.currentMonth) ? 'left' : 'right'
47
- });
48
- }
49
38
  default:
50
39
  throw new Error('missing support');
51
40
  }
@@ -56,16 +45,17 @@ export const useCalendarState = params => {
56
45
  referenceDate: referenceDateProp,
57
46
  disableFuture,
58
47
  disablePast,
59
- disableSwitchToMonthOnDayFocus = false,
60
48
  maxDate,
61
49
  minDate,
62
50
  onMonthChange,
51
+ onYearChange,
63
52
  reduceAnimations,
64
53
  shouldDisableDate,
65
- timezone
54
+ timezone,
55
+ getCurrentMonthFromVisibleDate
66
56
  } = params;
67
57
  const utils = useUtils();
68
- const reducerFn = React.useRef(createCalendarStateReducer(Boolean(reduceAnimations), disableSwitchToMonthOnDayFocus, utils)).current;
58
+ const reducerFn = React.useRef(createCalendarStateReducer(Boolean(reduceAnimations), utils)).current;
69
59
  const referenceDate = React.useMemo(() => {
70
60
  return singleItemValueManager.getInitialReferenceValue({
71
61
  value,
@@ -85,6 +75,14 @@ export const useCalendarState = params => {
85
75
  currentMonth: utils.startOfMonth(referenceDate),
86
76
  slideDirection: 'left'
87
77
  });
78
+ const isDateDisabled = useIsDateDisabled({
79
+ shouldDisableDate,
80
+ minDate,
81
+ maxDate,
82
+ disableFuture,
83
+ disablePast,
84
+ timezone
85
+ });
88
86
 
89
87
  // Ensure that `calendarState.currentMonth` timezone is updated when `referenceDate` (or timezone changes)
90
88
  // https://github.com/mui/mui-x/issues/10804
@@ -94,53 +92,65 @@ export const useCalendarState = params => {
94
92
  newTimezone: utils.getTimezone(referenceDate)
95
93
  });
96
94
  }, [referenceDate, utils]);
97
- const handleChangeMonth = React.useCallback(payload => {
98
- dispatch(_extends({
99
- type: 'changeMonth'
100
- }, payload));
101
- if (onMonthChange) {
102
- onMonthChange(payload.newMonth);
103
- }
104
- }, [onMonthChange]);
105
- const changeMonth = React.useCallback(newDate => {
106
- const newDateRequested = newDate;
107
- if (utils.isSameMonth(newDateRequested, calendarState.currentMonth)) {
95
+ const setVisibleDate = useEventCallback(({
96
+ target,
97
+ reason
98
+ }) => {
99
+ if (reason === 'cell-interaction' && calendarState.focusedDay != null && utils.isSameDay(target, calendarState.focusedDay)) {
108
100
  return;
109
101
  }
110
- handleChangeMonth({
111
- newMonth: utils.startOfMonth(newDateRequested),
112
- direction: utils.isAfterDay(newDateRequested, calendarState.currentMonth) ? 'left' : 'right'
102
+ const skipAnimation = reason === 'cell-interaction';
103
+ let month;
104
+ let focusedDay;
105
+ if (reason === 'cell-interaction') {
106
+ month = getCurrentMonthFromVisibleDate(target, calendarState.currentMonth);
107
+ focusedDay = target;
108
+ } else {
109
+ month = utils.isSameMonth(target, calendarState.currentMonth) ? calendarState.currentMonth : utils.startOfMonth(target);
110
+ focusedDay = target;
111
+
112
+ // If the date is disabled, we try to find a non-disabled date inside the same month.
113
+ if (isDateDisabled(focusedDay)) {
114
+ const startOfMonth = utils.startOfMonth(target);
115
+ const endOfMonth = utils.endOfMonth(target);
116
+ focusedDay = findClosestEnabledDate({
117
+ utils,
118
+ date: focusedDay,
119
+ minDate: utils.isBefore(minDate, startOfMonth) ? startOfMonth : minDate,
120
+ maxDate: utils.isAfter(maxDate, endOfMonth) ? endOfMonth : maxDate,
121
+ disablePast,
122
+ disableFuture,
123
+ isDateDisabled,
124
+ timezone
125
+ });
126
+ }
127
+ }
128
+ const hasChangedMonth = !utils.isSameMonth(calendarState.currentMonth, month);
129
+ const hasChangedYear = !utils.isSameYear(calendarState.currentMonth, month);
130
+ if (hasChangedMonth) {
131
+ onMonthChange?.(month);
132
+ }
133
+ if (hasChangedYear) {
134
+ onYearChange?.(utils.startOfYear(month));
135
+ }
136
+ dispatch({
137
+ type: 'setVisibleDate',
138
+ month,
139
+ direction: utils.isAfterDay(month, calendarState.currentMonth) ? 'left' : 'right',
140
+ focusedDay: calendarState.focusedDay != null && focusedDay != null && utils.isSameDay(focusedDay, calendarState.focusedDay) ? calendarState.focusedDay : focusedDay,
141
+ skipAnimation
113
142
  });
114
- }, [calendarState.currentMonth, handleChangeMonth, utils]);
115
- const isDateDisabled = useIsDateDisabled({
116
- shouldDisableDate,
117
- minDate,
118
- maxDate,
119
- disableFuture,
120
- disablePast,
121
- timezone
122
143
  });
123
144
  const onMonthSwitchingAnimationEnd = React.useCallback(() => {
124
145
  dispatch({
125
146
  type: 'finishMonthSwitchingAnimation'
126
147
  });
127
148
  }, []);
128
- const changeFocusedDay = useEventCallback((newFocusedDate, withoutMonthSwitchingAnimation) => {
129
- if (!isDateDisabled(newFocusedDate)) {
130
- dispatch({
131
- type: 'changeFocusedDay',
132
- focusedDay: newFocusedDate,
133
- withoutMonthSwitchingAnimation
134
- });
135
- }
136
- });
137
149
  return {
138
150
  referenceDate,
139
151
  calendarState,
140
- changeMonth,
141
- changeFocusedDay,
152
+ setVisibleDate,
142
153
  isDateDisabled,
143
- onMonthSwitchingAnimationEnd,
144
- handleChangeMonth
154
+ onMonthSwitchingAnimationEnd
145
155
  };
146
156
  };
@@ -159,8 +159,8 @@ const PickersCalendarHeader = /*#__PURE__*/React.forwardRef(function PickersCale
159
159
  className: classes.switchViewIcon
160
160
  }),
161
161
  switchViewIconProps = _objectWithoutPropertiesLoose(_useSlotProps, _excluded2);
162
- const selectNextMonth = () => onMonthChange(utils.addMonths(month, 1), 'left');
163
- const selectPreviousMonth = () => onMonthChange(utils.addMonths(month, -1), 'right');
162
+ const selectNextMonth = () => onMonthChange(utils.addMonths(month, 1));
163
+ const selectPreviousMonth = () => onMonthChange(utils.addMonths(month, -1));
164
164
  const isNextMonthDisabled = useNextMonthDisabled(month, {
165
165
  disableFuture,
166
166
  maxDate,
@@ -6,7 +6,6 @@ import { SxProps, Theme } from '@mui/material/styles';
6
6
  import { ExportedPickersArrowSwitcherProps, PickersArrowSwitcherSlots, PickersArrowSwitcherSlotProps } from '../internals/components/PickersArrowSwitcher';
7
7
  import { MonthValidationOptions } from '../internals/hooks/date-helpers-hooks';
8
8
  import { PickerValidDate, DateView, PickerOwnerState } from '../models';
9
- import { SlideDirection } from '../DateCalendar/PickersSlideTransition';
10
9
  import { PickersCalendarHeaderClasses } from './pickersCalendarHeaderClasses';
11
10
  export interface PickersCalendarHeaderSlots extends PickersArrowSwitcherSlots {
12
11
  /**
@@ -40,7 +39,7 @@ export interface PickersCalendarHeaderProps extends ExportedPickersArrowSwitcher
40
39
  currentMonth: PickerValidDate;
41
40
  disabled?: boolean;
42
41
  views: readonly DateView[];
43
- onMonthChange: (date: PickerValidDate, slideDirection: SlideDirection) => void;
42
+ onMonthChange: (date: PickerValidDate) => void;
44
43
  view: DateView;
45
44
  reduceAnimations: boolean;
46
45
  onViewChange?: (view: DateView) => void;