diff --git a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx index b3f59b8078769..41042aaf6049f 100644 --- a/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx +++ b/packages/x-date-pickers/src/DateCalendar/tests/DateCalendar.test.tsx @@ -183,14 +183,17 @@ describe('', () => { render( , ); + // should make the reference day firstly focusable + expect(screen.getByRole('gridcell', { name: '17' })).to.have.attribute('tabindex', '0'); + userEvent.mousePress(screen.getByRole('gridcell', { name: '2' })); expect(onChange.callCount).to.equal(1); - expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 2, 12, 30)); + expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2022, 3, 2, 12, 30)); }); it('should not use `referenceDate` when a value is defined', () => { diff --git a/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx b/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx index 1754dc47f8ac6..440fa628f4dd6 100644 --- a/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx +++ b/packages/x-date-pickers/src/DateCalendar/useCalendarState.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import useEventCallback from '@mui/utils/useEventCallback'; import { SlideDirection } from './PickersSlideTransition'; import { useIsDateDisabled } from './useIsDateDisabled'; -import { useUtils, useNow } from '../internals/hooks/useUtils'; +import { useUtils } from '../internals/hooks/useUtils'; import { MuiPickersAdapter, PickersTimezone } from '../models'; import { DateCalendarDefaultizedProps } from './DateCalendar.types'; import { singleItemValueManager } from '../internals/utils/valueManagers'; @@ -127,7 +127,6 @@ export const useCalendarState = (params: UseCalendarState timezone, } = params; - const now = useNow(timezone); const utils = useUtils(); const reducerFn = React.useRef( @@ -162,7 +161,7 @@ export const useCalendarState = (params: UseCalendarState const [calendarState, dispatch] = React.useReducer(reducerFn, { isMonthSwitchingAnimating: false, - focusedDay: value || now, + focusedDay: referenceDate, currentMonth: utils.startOfMonth(referenceDate), slideDirection: 'left', }); diff --git a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx index 7ed56f2cf6156..7259992a7c8dd 100644 --- a/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx +++ b/packages/x-date-pickers/src/DesktopTimePicker/tests/describes.DesktopTimePicker.test.tsx @@ -10,6 +10,7 @@ import { describeValidation, describeValue, describePicker, + formatFullTimeValue, } from 'test/utils/pickers'; import { DesktopTimePicker } from '@mui/x-date-pickers/DesktopTimePicker'; @@ -68,9 +69,7 @@ describe(' - Describes', () => { } expectInputValue( input, - expectedValue - ? adapterToUse.format(expectedValue, hasMeridiem ? 'fullTime12h' : 'fullTime24h') - : '', + expectedValue ? formatFullTimeValue(adapterToUse, expectedValue) : '', ); }, setNewValue: (value, { isOpened, applySameValue, selectSection }) => { diff --git a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx index 67dda956330bb..720f52747c141 100644 --- a/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx +++ b/packages/x-date-pickers/src/DigitalClock/DigitalClock.tsx @@ -202,14 +202,17 @@ export const DigitalClock = React.forwardRef(function DigitalClock( - '[role="listbox"] [role="option"][aria-selected="true"]', + const activeItem = containerRef.current.querySelector( + '[role="listbox"] [role="option"][tabindex="0"], [role="listbox"] [role="option"][aria-selected="true"]', ); - if (!selectedItem) { + if (!activeItem) { return; } - const offsetTop = selectedItem.offsetTop; + const offsetTop = activeItem.offsetTop; + if (autoFocus || !!focusedView) { + activeItem.focus(); + } // Subtracting the 4px of extra margin intended for the first visible section item containerRef.current.scrollTop = offsetTop - 4; @@ -281,6 +284,10 @@ export const DigitalClock = React.forwardRef(function DigitalClock + utils.isEqual(option, valueOrReferenceDate), + ); + return ( - {timeOptions.map((option) => { + {timeOptions.map((option, index) => { if (skipDisabled && isTimeDisabled(option)) { return null; } const isSelected = utils.isEqual(option, value); + const tabIndex = + focusedOptionIndex === index || (focusedOptionIndex === -1 && index === 0) ? 0 : -1; return ( {utils.format(option, ampm ? 'fullTime12h' : 'fullTime24h')} diff --git a/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx index 0df3c3fee7472..6002e825f08ef 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/DigitalClock.test.tsx @@ -2,7 +2,13 @@ import * as React from 'react'; import { expect } from 'chai'; import { spy } from 'sinon'; import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; -import { adapterToUse, createPickerRenderer, digitalClockHandler } from 'test/utils/pickers'; +import { + adapterToUse, + createPickerRenderer, + digitalClockHandler, + formatFullTimeValue, +} from 'test/utils/pickers'; +import { screen } from '@mui-internal/test-utils'; describe('', () => { const { render } = createPickerRenderer(); @@ -10,13 +16,22 @@ describe('', () => { describe('Reference date', () => { it('should use `referenceDate` when no value defined', () => { const onChange = spy(); + const referenceDate = new Date(2018, 0, 1, 12, 30); - render( - , - ); + render(); + + // the first item should not be initially focusable when `referenceDate` is defined + expect( + screen.getByRole('option', { + name: formatFullTimeValue(adapterToUse, new Date(2018, 0, 1, 0, 0, 0)), + }), + ).to.have.attribute('tabindex', '-1'); + // check that the relevant time based on the `referenceDate` is focusable + expect( + screen.getByRole('option', { + name: formatFullTimeValue(adapterToUse, referenceDate), + }), + ).to.have.attribute('tabindex', '0'); digitalClockHandler.setViewValue( adapterToUse, @@ -26,6 +41,18 @@ describe('', () => { expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 1, 15, 30)); }); + it('should fallback to making the first entry focusable when `referenceDate` does not map to any option', () => { + const referenceDate = new Date(2018, 0, 1, 12, 33); + + render(); + + expect( + screen.getByRole('option', { + name: formatFullTimeValue(adapterToUse, new Date(2018, 0, 1, 0, 0, 0)), + }), + ).to.have.attribute('tabindex', '0'); + }); + it('should not use `referenceDate` when a value is defined', () => { const onChange = spy(); diff --git a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx index 32ef3633fe3b4..45050c55ae149 100644 --- a/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx +++ b/packages/x-date-pickers/src/DigitalClock/tests/describes.DigitalClock.test.tsx @@ -8,6 +8,7 @@ import { digitalClockHandler, describeValidation, describeValue, + formatFullTimeValue, } from 'test/utils/pickers'; import { DigitalClock } from '@mui/x-date-pickers/DigitalClock'; @@ -56,14 +57,11 @@ describe(' - Describes', () => { emptyValue: null, clock, assertRenderedValue: (expectedValue: any) => { - const hasMeridiem = adapterToUse.is12HourCycleInCurrentLocale(); const selectedItem = screen.queryByRole('option', { selected: true }); if (!expectedValue) { expect(selectedItem).to.equal(null); } else { - expect(selectedItem).to.have.text( - adapterToUse.format(expectedValue, hasMeridiem ? 'fullTime12h' : 'fullTime24h'), - ); + expect(selectedItem).to.have.text(formatFullTimeValue(adapterToUse, expectedValue)); } }, setNewValue: (value) => { diff --git a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx index 4ea3654013c26..7d06143444916 100644 --- a/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx +++ b/packages/x-date-pickers/src/MobileTimePicker/tests/describes.MobileTimePicker.test.tsx @@ -17,6 +17,7 @@ import { describeValidation, describeValue, describePicker, + formatFullTimeValue, } from 'test/utils/pickers'; import { MobileTimePicker } from '@mui/x-date-pickers/MobileTimePicker'; @@ -73,7 +74,7 @@ describe(' - Describes', () => { expectInputPlaceholder(input, hasMeridiem ? 'hh:mm aa' : 'hh:mm'); } const expectedValueStr = expectedValue - ? adapterToUse.format(expectedValue, hasMeridiem ? 'fullTime12h' : 'fullTime24h') + ? formatFullTimeValue(adapterToUse, expectedValue) : ''; expectInputValue(input, expectedValueStr); diff --git a/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx b/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx index a884d19452aa7..2ee5130f4226b 100644 --- a/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx +++ b/packages/x-date-pickers/src/MonthCalendar/MonthCalendar.tsx @@ -144,13 +144,11 @@ export const MonthCalendar = React.forwardRef(function MonthCalendar( return utils.getMonth(value); } - if (disableHighlightToday) { - return null; - } - - return utils.getMonth(referenceDate); - }, [value, utils, disableHighlightToday, referenceDate]); - const [focusedMonth, setFocusedMonth] = React.useState(() => selectedMonth || todayMonth); + return null; + }, [value, utils]); + const [focusedMonth, setFocusedMonth] = React.useState( + () => selectedMonth || utils.getMonth(referenceDate), + ); const [internalHasFocus, setInternalHasFocus] = useControlled({ name: 'MonthCalendar', diff --git a/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx b/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx index 398e6c51f78b2..4bcf96e6fbe47 100644 --- a/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx +++ b/packages/x-date-pickers/src/MonthCalendar/tests/MonthCalendar.test.tsx @@ -169,5 +169,11 @@ describe('', () => { expect(january).not.to.have.attribute('disabled'); expect(february).to.have.attribute('disabled'); }); + + it('should not mark the `referenceDate` month as selected', () => { + render(); + + expect(screen.getByRole('radio', { name: 'February', checked: false })).to.not.equal(null); + }); }); }); diff --git a/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx b/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx index 89ddd554b862e..8cd311f7422b9 100644 --- a/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx +++ b/packages/x-date-pickers/src/MonthCalendar/tests/describes.MonthCalendar.test.tsx @@ -8,11 +8,7 @@ import { describeValidation, describeValue, } from 'test/utils/pickers'; -import { - MonthCalendar, - monthCalendarClasses as classes, - pickersMonthClasses, -} from '@mui/x-date-pickers/MonthCalendar'; +import { MonthCalendar, monthCalendarClasses as classes } from '@mui/x-date-pickers/MonthCalendar'; describe(' - Describes', () => { const { render, clock } = createPickerRenderer({ clock: 'fake' }); @@ -42,17 +38,19 @@ describe(' - Describes', () => { emptyValue: null, clock, assertRenderedValue: (expectedValue: any) => { - const selectedCells = document.querySelectorAll(`.${pickersMonthClasses.selected}`); + const activeMonth = screen + .queryAllByRole('radio') + .find((cell) => cell.getAttribute('tabindex') === '0'); + expect(activeMonth).not.to.equal(null); if (expectedValue == null) { - expect(selectedCells).to.have.length(1); - expect(selectedCells[0]).to.have.text( + expect(activeMonth).to.have.text( adapterToUse.format(adapterToUse.date(), 'monthShort').toString(), ); } else { - expect(selectedCells).to.have.length(1); - expect(selectedCells[0]).to.have.text( + expect(activeMonth).to.have.text( adapterToUse.format(expectedValue, 'monthShort').toString(), ); + expect(activeMonth).to.have.attribute('aria-checked', 'true'); } }, setNewValue: (value) => { diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx index 30f2d2a3a190f..9f083eac67783 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.tsx @@ -302,6 +302,7 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi isDisabled: (hours) => disabled || isTimeDisabled(hours, 'hours'), timeStep: timeSteps.hours, resolveAriaLabel: localeText.hoursClockNumberText, + valueOrReferenceDate, }), }; } @@ -348,12 +349,14 @@ export const MultiSectionDigitalClock = React.forwardRef(function MultiSectionDi value: 'am', label: amLabel, isSelected: () => !!value && meridiemMode === 'am', + isFocused: () => !!valueOrReferenceDate && meridiemMode === 'am', ariaLabel: amLabel, }, { value: 'pm', label: pmLabel, isSelected: () => !!value && meridiemMode === 'pm', + isFocused: () => !!valueOrReferenceDate && meridiemMode === 'pm', ariaLabel: pmLabel, }, ], diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.types.ts b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.types.ts index 6c378405a841a..ac0b3e6aa4f2b 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.types.ts +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.types.ts @@ -12,6 +12,7 @@ import { TimeViewWithMeridiem } from '../internals/models'; export interface MultiSectionDigitalClockOption { isDisabled?: (value: TValue) => boolean; isSelected: (value: TValue) => boolean; + isFocused: (value: TValue) => boolean; label: string; value: TValue; ariaLabel: string; diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.utils.ts b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.utils.ts index da44fac419543..25ed52dfd2046 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.utils.ts +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClock.utils.ts @@ -9,6 +9,7 @@ interface IGetHoursSectionOptions { isDisabled: (value: number) => boolean; timeStep: number; resolveAriaLabel: (value: string) => string; + valueOrReferenceDate: TDate; } export const getHourSectionOptions = ({ @@ -19,25 +20,31 @@ export const getHourSectionOptions = ({ isDisabled, resolveAriaLabel, timeStep, + valueOrReferenceDate, }: IGetHoursSectionOptions): MultiSectionDigitalClockOption[] => { const currentHours = value ? utils.getHours(value) : null; const result: MultiSectionDigitalClockOption[] = []; - const isSelected = (hour: number) => { - if (currentHours === null) { + const isSelected = (hour: number, overriddenCurrentHours?: number) => { + const resolvedCurrentHours = overriddenCurrentHours ?? currentHours; + if (resolvedCurrentHours === null) { return false; } if (ampm) { if (hour === 12) { - return currentHours === 12 || currentHours === 0; + return resolvedCurrentHours === 12 || resolvedCurrentHours === 0; } - return currentHours === hour || currentHours - 12 === hour; + return resolvedCurrentHours === hour || resolvedCurrentHours - 12 === hour; } - return currentHours === hour; + return resolvedCurrentHours === hour; + }; + + const isFocused = (hour: number) => { + return isSelected(hour, utils.getHours(valueOrReferenceDate)); }; const endHour = ampm ? 11 : 23; @@ -52,6 +59,7 @@ export const getHourSectionOptions = ({ label, isSelected, isDisabled, + isFocused, ariaLabel, }); } @@ -83,6 +91,10 @@ export const getTimeSectionOptions = ({ return hasValue && value === timeValue; }; + const isFocused = (timeValue: number) => { + return value === timeValue; + }; + return [ ...Array.from({ length: Math.ceil(60 / timeStep) }, (_, index) => { const timeValue = timeStep * index; @@ -91,6 +103,7 @@ export const getTimeSectionOptions = ({ label: resolveLabel(timeValue), isDisabled, isSelected, + isFocused, ariaLabel: resolveAriaLabel(timeValue.toString()), }; }), diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx index a81d4bd167faf..becbdcf81de64 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/MultiSectionDigitalClockSection.tsx @@ -121,7 +121,7 @@ export const MultiSectionDigitalClockSection = React.forwardRef( ) { const containerRef = React.useRef(null); const handleRef = useForkRef(ref, containerRef); - const previousSelected = React.useRef(null); + const previousActive = React.useRef(null); const props = useThemeProps({ props: inProps, @@ -154,26 +154,28 @@ export const MultiSectionDigitalClockSection = React.forwardRef( if (containerRef.current === null) { return; } - const selectedItem = containerRef.current.querySelector( - '[role="option"][aria-selected="true"]', + const activeItem = containerRef.current.querySelector( + '[role="option"][tabindex="0"], [role="option"][aria-selected="true"]', ); - if (!selectedItem || previousSelected.current === selectedItem) { + if (!activeItem || previousActive.current === activeItem) { // Handle setting the ref to null if the selected item is ever reset via UI - if (previousSelected.current !== selectedItem) { - previousSelected.current = selectedItem; + if (previousActive.current !== activeItem) { + previousActive.current = activeItem; } return; } - previousSelected.current = selectedItem; + previousActive.current = activeItem; if (active && autoFocus) { - selectedItem.focus(); + activeItem.focus(); } - const offsetTop = selectedItem.offsetTop; + const offsetTop = activeItem.offsetTop; // Subtracting the 4px of extra margin intended for the first visible section item containerRef.current.scrollTop = offsetTop - 4; }); + const focusedOptionIndex = items.findIndex((item) => item.isFocused(item.value)); + return ( - {items.map((option) => { + {items.map((option, index) => { if (skipDisabled && option.isDisabled?.(option.value)) { return null; } const isSelected = option.isSelected(option.value); + const tabIndex = + focusedOptionIndex === index || (focusedOptionIndex === -1 && index === 0) ? 0 : -1; return ( {option.label} diff --git a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx index ec9da34386c2a..1b5fea603f38d 100644 --- a/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx +++ b/packages/x-date-pickers/src/MultiSectionDigitalClock/tests/MultiSectionDigitalClock.test.tsx @@ -10,6 +10,7 @@ import { adapterToUse, multiSectionDigitalClockHandler, } from 'test/utils/pickers'; +import { screen } from '@mui-internal/test-utils'; describe('', () => { const { render } = createPickerRenderer(); @@ -17,14 +18,24 @@ describe('', () => { describe('Reference date', () => { it('should use `referenceDate` when no value defined', () => { const onChange = spy(); + const referenceDate = new Date(2018, 0, 1, 13, 30); render( , ); + // the first section items should not be initially focusable when `referenceDate` is defined + expect(screen.getByRole('option', { name: '12 hours' })).to.have.attribute('tabindex', '-1'); + expect(screen.getByRole('option', { name: '0 minutes' })).to.have.attribute('tabindex', '-1'); + expect(screen.getByRole('option', { name: 'AM' })).to.have.attribute('tabindex', '-1'); + // check that the relevant time based on the `referenceDate` is focusable + expect(screen.getByRole('option', { name: '1 hours' })).to.have.attribute('tabindex', '0'); + expect(screen.getByRole('option', { name: '30 minutes' })).to.have.attribute('tabindex', '0'); + expect(screen.getByRole('option', { name: 'PM' })).to.have.attribute('tabindex', '0'); + multiSectionDigitalClockHandler.setViewValue( adapterToUse, adapterToUse.setMinutes(adapterToUse.setHours(adapterToUse.date(), 15), 30), @@ -33,6 +44,14 @@ describe('', () => { expect(onChange.lastCall.firstArg).toEqualDateTime(new Date(2018, 0, 1, 15, 30)); }); + it('should fallback to making the first entry focusable when `referenceDate` does not map to an option', () => { + const referenceDate = new Date(2018, 0, 1, 13, 33); + + render(); + + expect(screen.getByRole('option', { name: '0 minutes' })).to.have.attribute('tabindex', '0'); + }); + it('should not use `referenceDate` when a value is defined', () => { const onChange = spy(); diff --git a/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx b/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx index f114972a5fb5d..04b67aaa2975c 100644 --- a/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx +++ b/packages/x-date-pickers/src/TimeField/tests/describes.TimeField.test.tsx @@ -7,6 +7,7 @@ import { getTextbox, describeValidation, describeValue, + formatFullTimeValue, } from 'test/utils/pickers'; import { TimeField } from '@mui/x-date-pickers/TimeField'; @@ -33,7 +34,7 @@ describe(' - Describes', () => { expectInputPlaceholder(input, hasMeridiem ? 'hh:mm aa' : 'hh:mm'); } const expectedValueStr = expectedValue - ? adapterToUse.format(expectedValue, hasMeridiem ? 'fullTime12h' : 'fullTime24h') + ? formatFullTimeValue(adapterToUse, expectedValue) : ''; expectInputValue(input, expectedValueStr); }, diff --git a/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx b/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx index bf6b925ac9c38..38752aba87560 100644 --- a/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx +++ b/packages/x-date-pickers/src/YearCalendar/YearCalendar.tsx @@ -148,15 +148,12 @@ export const YearCalendar = React.forwardRef(function YearCalendar( if (value != null) { return utils.getYear(value); } + return null; + }, [value, utils]); - if (disableHighlightToday) { - return null; - } - - return utils.getYear(referenceDate); - }, [value, utils, disableHighlightToday, referenceDate]); - - const [focusedYear, setFocusedYear] = React.useState(() => selectedYear || todayYear); + const [focusedYear, setFocusedYear] = React.useState( + () => selectedYear || utils.getYear(referenceDate), + ); const [internalHasFocus, setInternalHasFocus] = useControlled({ name: 'YearCalendar', diff --git a/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx b/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx index 3d979bd7f2ce8..17528fb8c8bba 100644 --- a/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx +++ b/packages/x-date-pickers/src/YearCalendar/tests/YearCalendar.test.tsx @@ -169,4 +169,10 @@ describe('', () => { expect(year2019).not.to.have.attribute('disabled'); expect(year2020).to.have.attribute('disabled'); }); + + it('should not mark the `referenceDate` year as selected', () => { + render(); + + expect(screen.getByRole('radio', { name: '2018', checked: false })).to.not.equal(null); + }); }); diff --git a/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx b/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx index 7aaf18d0752a0..a46fee1e629b0 100644 --- a/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx +++ b/packages/x-date-pickers/src/YearCalendar/tests/describes.YearCalendar.test.tsx @@ -1,11 +1,7 @@ import * as React from 'react'; import { expect } from 'chai'; import { userEvent, screen, describeConformance } from '@mui-internal/test-utils'; -import { - pickersYearClasses, - YearCalendar, - yearCalendarClasses as classes, -} from '@mui/x-date-pickers/YearCalendar'; +import { YearCalendar, yearCalendarClasses as classes } from '@mui/x-date-pickers/YearCalendar'; import { wrapPickerMount, createPickerRenderer, @@ -44,13 +40,15 @@ describe(' - Describes', () => { emptyValue: null, clock, assertRenderedValue: (expectedValue: any) => { - const selectedCells = document.querySelectorAll(`.${pickersYearClasses.selected}`); + const activeYear = screen + .queryAllByRole('radio') + .find((cell) => cell.getAttribute('tabindex') === '0'); + expect(activeYear).not.to.equal(null); if (expectedValue == null) { - expect(selectedCells).to.have.length(1); - expect(selectedCells[0]).to.have.text(adapterToUse.getYear(adapterToUse.date()).toString()); + expect(activeYear).to.have.text(adapterToUse.getYear(adapterToUse.date()).toString()); } else { - expect(selectedCells).to.have.length(1); - expect(selectedCells[0]).to.have.text(adapterToUse.getYear(expectedValue).toString()); + expect(activeYear).to.have.text(adapterToUse.getYear(expectedValue).toString()); + expect(activeYear).to.have.attribute('aria-checked', 'true'); } }, setNewValue: (value) => { diff --git a/test/utils/pickers/misc.ts b/test/utils/pickers/misc.ts index 49f70d50991fd..97a08af557017 100644 --- a/test/utils/pickers/misc.ts +++ b/test/utils/pickers/misc.ts @@ -56,3 +56,11 @@ export const getDateOffset = ( const cleanUtcHour = utcHour > 12 ? 24 - utcHour : -utcHour; return cleanUtcHour * 60; }; + +export const formatFullTimeValue = ( + adapter: MuiPickersAdapter, + value: TDate, +) => { + const hasMeridiem = adapter.is12HourCycleInCurrentLocale(); + return adapter.format(value, hasMeridiem ? 'fullTime12h' : 'fullTime24h'); +}; diff --git a/test/utils/pickers/viewHandlers.ts b/test/utils/pickers/viewHandlers.ts index 0496eab0ef774..8a723c2bf2c7f 100644 --- a/test/utils/pickers/viewHandlers.ts +++ b/test/utils/pickers/viewHandlers.ts @@ -1,5 +1,5 @@ import { fireTouchChangedEvent, userEvent, screen } from '@mui-internal/test-utils'; -import { getClockTouchEvent } from 'test/utils/pickers'; +import { getClockTouchEvent, formatFullTimeValue } from 'test/utils/pickers'; import { MuiPickersAdapter, TimeView } from '@mui/x-date-pickers/models'; import { formatMeridiem } from '@mui/x-date-pickers/internals/utils/date-utils'; @@ -35,9 +35,7 @@ export const timeClockHandler: ViewHandler = { export const digitalClockHandler: ViewHandler = { setViewValue: (adapter, value) => { - const hasMeridiem = adapter.is12HourCycleInCurrentLocale(); - const formattedLabel = adapter.format(value, hasMeridiem ? 'fullTime12h' : 'fullTime24h'); - userEvent.mousePress(screen.getByRole('option', { name: formattedLabel })); + userEvent.mousePress(screen.getByRole('option', { name: formatFullTimeValue(adapter, value) })); }, };