From 158afc3de5dca1db090d44c0a9682e827138382d Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 19 Jun 2024 10:49:19 +0300 Subject: [PATCH 1/5] [DateRangePicker] Add accessible name to calendar grid --- .../src/DateRangeCalendar/DateRangeCalendar.tsx | 4 ++++ .../PickersRangeCalendarHeader.tsx | 7 +++++-- .../PickersArrowSwitcher/PickersArrowSwitcher.tsx | 3 ++- .../PickersArrowSwitcher/PickersArrowSwitcher.types.tsx | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 1d8bbc4daa79..5723ffc5c114 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -25,6 +25,7 @@ import { useViews, } from '@mui/x-date-pickers/internals'; import { PickerValidDate } from '@mui/x-date-pickers/models'; +import useId from '@mui/utils/useId'; import { getReleaseInfo } from '../internals/utils/releaseInfo'; import { dateRangeCalendarClasses, @@ -231,6 +232,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< const utils = useUtils(); const now = useNow(timezone); + const id = useId(); const { rangePosition, onRangePositionChange } = useRangePosition({ rangePosition: rangePositionProp, @@ -368,6 +370,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< timezone, slots, slotProps, + labelId: `${id}-grid`, }, ownerState: props, }); @@ -575,6 +578,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< fixedWeekNumber={fixedWeekNumber} displayWeekNumber={displayWeekNumber} timezone={timezone} + gridLabelId={`${id}-grid-${monthIndex}-label`} /> ); diff --git a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx index faf5ab4b228a..3fad4b2c4d97 100644 --- a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx +++ b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx @@ -29,7 +29,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda const utils = useUtils(); const localeText = useLocaleText(); - const { calendars, month, monthIndex, ...other } = props; + const { calendars, month, monthIndex, labelId, ...other } = props; const { format, slots, @@ -56,7 +56,9 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda }); if (calendars === 1) { - return ; + return ( + + ); } const selectNextMonth = () => onMonthChange(utils.addMonths(currentMonth, 1), 'left'); @@ -76,6 +78,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda nextLabel={localeText.nextMonth} slots={slots} slotProps={slotProps} + labelId={`${labelId}-${monthIndex}-label`} > {utils.formatByString(month, format ?? `${utils.formats.month} ${utils.formats.year}`)} diff --git a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx index 617a71eea6fd..2ecae8493f14 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.tsx @@ -79,6 +79,7 @@ export const PickersArrowSwitcher = React.forwardRef(function PickersArrowSwitch isPreviousHidden, onGoToPrevious, previousLabel, + labelId, ...other } = props; @@ -169,7 +170,7 @@ export const PickersArrowSwitcher = React.forwardRef(function PickersArrowSwitch )} {children ? ( - + {children} ) : ( diff --git a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx index aaa5552a8c65..0998b6ded18b 100644 --- a/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx +++ b/packages/x-date-pickers/src/internals/components/PickersArrowSwitcher/PickersArrowSwitcher.types.tsx @@ -38,6 +38,7 @@ export interface PickersArrowSwitcherProps isNextHidden?: boolean; onGoToNext: () => void; nextLabel: string; + labelId?: string; } export type PickersArrowSwitcherOwnerState = PickersArrowSwitcherProps; From b521e008dbf7a14d73c54fc9bc3f5144060e90af Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 19 Jun 2024 10:49:29 +0300 Subject: [PATCH 2/5] Update tests to rely on labelled `DateRangeCalendar` grid --- .../src/DateRangeCalendar/DateRangeCalendar.test.tsx | 2 +- .../tests/DesktopDateRangePicker.test.tsx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx index 1947567216c9..ffa81a05f51b 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.test.tsx @@ -25,7 +25,7 @@ import { describeConformance } from 'test/utils/describeConformance'; import { RangePosition } from '../models'; const getPickerDay = (name: string, picker = 'January 2018') => - getByRole(screen.getByText(picker)?.parentElement?.parentElement!, 'gridcell', { name }); + getByRole(screen.getByRole('grid', { name: picker }), 'gridcell', { name }); const dynamicShouldDisableDate = (date, position: RangePosition) => { if (position === 'end') { diff --git a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx index 98032d863607..4c4476eda2eb 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateRangePicker/tests/DesktopDateRangePicker.test.tsx @@ -18,8 +18,8 @@ import { const isJSDOM = /jsdom/.test(window.navigator.userAgent); -const getPickerDay = (name: string, picker = 'January 2018'): HTMLButtonElement => - getByRole(screen.getByText(picker)?.parentElement?.parentElement!, 'gridcell', { name }); +const getPickerDay = (name: string, picker = 'January 2018') => + getByRole(screen.getByRole('grid', { name: picker }), 'gridcell', { name }); describe('', () => { const { render, clock } = createPickerRenderer({ From cf5c4e8086ffe09aee3045eed2cf2c2541780709 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 19 Jun 2024 10:52:31 +0300 Subject: [PATCH 3/5] adjust imports --- .../src/DateRangeCalendar/DateRangeCalendar.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 5723ffc5c114..933a32dd37da 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -5,7 +5,8 @@ import useEventCallback from '@mui/utils/useEventCallback'; import useMediaQuery from '@mui/material/useMediaQuery'; import { resolveComponentProps, useSlotProps } from '@mui/base/utils'; import { styled, useThemeProps } from '@mui/material/styles'; -import { unstable_composeClasses as composeClasses } from '@mui/utils'; +import composeClasses from '@mui/utils/composeClasses'; +import useId from '@mui/utils/useId'; import { Watermark } from '@mui/x-license'; import { applyDefaultDate, @@ -25,7 +26,6 @@ import { useViews, } from '@mui/x-date-pickers/internals'; import { PickerValidDate } from '@mui/x-date-pickers/models'; -import useId from '@mui/utils/useId'; import { getReleaseInfo } from '../internals/utils/releaseInfo'; import { dateRangeCalendarClasses, From db9bc95658619553abeb35bad984e456bae03e27 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 19 Jun 2024 11:51:22 +0300 Subject: [PATCH 4/5] Code review: Flavien Better support for custom calendar header components --- .../src/DateRangeCalendar/DateRangeCalendar.tsx | 11 ++++++++--- .../PickersRangeCalendarHeader.tsx | 6 ++---- .../PickersCalendarHeader.types.ts | 4 ++++ 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx index 933a32dd37da..4d4e418bea87 100644 --- a/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx +++ b/packages/x-date-pickers-pro/src/DateRangeCalendar/DateRangeCalendar.tsx @@ -370,7 +370,6 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< timezone, slots, slotProps, - labelId: `${id}-grid`, }, ownerState: props, }); @@ -551,10 +550,16 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< {calendarMonths.map((monthIndex) => { const month = visibleMonths[monthIndex]; + const labelId = `${id}-grid-${monthIndex}-label`; return ( - {...calendarHeaderProps} month={month} monthIndex={monthIndex} /> + + {...calendarHeaderProps} + month={month} + monthIndex={monthIndex} + labelId={labelId} + /> className={classes.dayCalendar} {...calendarState} @@ -578,7 +583,7 @@ const DateRangeCalendar = React.forwardRef(function DateRangeCalendar< fixedWeekNumber={fixedWeekNumber} displayWeekNumber={displayWeekNumber} timezone={timezone} - gridLabelId={`${id}-grid-${monthIndex}-label`} + gridLabelId={labelId} /> ); diff --git a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx index 3fad4b2c4d97..8ad7ab368873 100644 --- a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx +++ b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx @@ -56,9 +56,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda }); if (calendars === 1) { - return ( - - ); + return ; } const selectNextMonth = () => onMonthChange(utils.addMonths(currentMonth, 1), 'left'); @@ -78,7 +76,7 @@ const PickersRangeCalendarHeader = React.forwardRef(function PickersRangeCalenda nextLabel={localeText.nextMonth} slots={slots} slotProps={slotProps} - labelId={`${labelId}-${monthIndex}-label`} + labelId={labelId} > {utils.formatByString(month, format ?? `${utils.formats.month} ${utils.formats.year}`)} diff --git a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts index 06cbb152849a..a679a4fe9b6f 100644 --- a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts +++ b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.types.ts @@ -67,6 +67,10 @@ export interface PickersCalendarHeaderProps view: DateView; reduceAnimations: boolean; onViewChange?: (view: DateView) => void; + /** + * Id of the calendar text element. + * It is used to establish an `aria-labelledby` relationship with the calendar `grid` element. + */ labelId?: string; /** * Override or extend the styles applied to the component. From 9d24724e8473f51f811113f19c453205ac41e8b4 Mon Sep 17 00:00:00 2001 From: Lukas Date: Wed, 19 Jun 2024 11:55:05 +0300 Subject: [PATCH 5/5] scripts --- docs/pages/x/api/date-pickers/pickers-calendar-header.json | 1 + .../x/api/date-pickers/pickers-range-calendar-header.json | 1 + .../pickers-calendar-header/pickers-calendar-header.json | 3 +++ .../pickers-range-calendar-header.json | 3 +++ .../PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx | 4 ++++ .../src/PickersCalendarHeader/PickersCalendarHeader.tsx | 4 ++++ 6 files changed, 16 insertions(+) diff --git a/docs/pages/x/api/date-pickers/pickers-calendar-header.json b/docs/pages/x/api/date-pickers/pickers-calendar-header.json index 94aa338f027f..b2a50f1ad029 100644 --- a/docs/pages/x/api/date-pickers/pickers-calendar-header.json +++ b/docs/pages/x/api/date-pickers/pickers-calendar-header.json @@ -5,6 +5,7 @@ "type": { "name": "string" }, "default": "`${adapter.formats.month} ${adapter.formats.year}`" }, + "labelId": { "type": { "name": "string" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, diff --git a/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json b/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json index fbf2f5e605c2..d3c101a847ab 100644 --- a/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json +++ b/docs/pages/x/api/date-pickers/pickers-range-calendar-header.json @@ -11,6 +11,7 @@ "type": { "name": "string" }, "default": "`${adapter.formats.month} ${adapter.formats.year}`" }, + "labelId": { "type": { "name": "string" } }, "slotProps": { "type": { "name": "object" }, "default": "{}" }, "slots": { "type": { "name": "object" }, diff --git a/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json b/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json index 6095d04c8b30..14b1549d337d 100644 --- a/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json +++ b/docs/translations/api-docs/date-pickers/pickers-calendar-header/pickers-calendar-header.json @@ -3,6 +3,9 @@ "propDescriptions": { "classes": { "description": "Override or extend the styles applied to the component." }, "format": { "description": "Format used to display the date." }, + "labelId": { + "description": "Id of the calendar text element. It is used to establish an aria-labelledby relationship with the calendar grid element." + }, "slotProps": { "description": "The props used for each component slot." }, "slots": { "description": "Overridable component slots." }, "sx": { diff --git a/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json b/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json index a152174c7d95..9affb9834e6d 100644 --- a/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json +++ b/docs/translations/api-docs/date-pickers/pickers-range-calendar-header/pickers-range-calendar-header.json @@ -4,6 +4,9 @@ "calendars": { "description": "The number of calendars rendered." }, "classes": { "description": "Override or extend the styles applied to the component." }, "format": { "description": "Format used to display the date." }, + "labelId": { + "description": "Id of the calendar text element. It is used to establish an aria-labelledby relationship with the calendar grid element." + }, "month": { "description": "Month used for this header." }, "monthIndex": { "description": "Index of the month used for this header." }, "slotProps": { "description": "The props used for each component slot." }, diff --git a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx index 8ad7ab368873..6657941e84d6 100644 --- a/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx +++ b/packages/x-date-pickers-pro/src/PickersRangeCalendarHeader/PickersRangeCalendarHeader.tsx @@ -106,6 +106,10 @@ PickersRangeCalendarHeader.propTypes = { * @default `${adapter.formats.month} ${adapter.formats.year}` */ format: PropTypes.string, + /** + * Id of the calendar text element. + * It is used to establish an `aria-labelledby` relationship with the calendar `grid` element. + */ labelId: PropTypes.string, maxDate: PropTypes.object.isRequired, minDate: PropTypes.object.isRequired, diff --git a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx index a172fa35fee2..d49252d9c441 100644 --- a/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx +++ b/packages/x-date-pickers/src/PickersCalendarHeader/PickersCalendarHeader.tsx @@ -281,6 +281,10 @@ PickersCalendarHeader.propTypes = { * @default `${adapter.formats.month} ${adapter.formats.year}` */ format: PropTypes.string, + /** + * Id of the calendar text element. + * It is used to establish an `aria-labelledby` relationship with the calendar `grid` element. + */ labelId: PropTypes.string, maxDate: PropTypes.object.isRequired, minDate: PropTypes.object.isRequired,