diff --git a/src/month_year_dropdown.tsx b/src/month_year_dropdown.tsx index 95b058fc3..e03f2086c 100644 --- a/src/month_year_dropdown.tsx +++ b/src/month_year_dropdown.tsx @@ -91,8 +91,9 @@ export default class MonthYearDropdown extends Component< ); return ( -
{yearMonth} -
+ ); }; diff --git a/src/month_year_dropdown_options.tsx b/src/month_year_dropdown_options.tsx index 173499258..ee8d7a395 100644 --- a/src/month_year_dropdown_options.tsx +++ b/src/month_year_dropdown_options.tsx @@ -72,9 +72,38 @@ export default class MonthYearDropdownOptions extends Component< }; } + monthYearOptionButtonsRef: Record = {}; + + handleOptionKeyDown = ( + i: number, + monthYearPoint: number, + e: React.KeyboardEvent, + ): void => { + switch (e.key) { + case "Enter": + e.preventDefault(); + this.onChange(monthYearPoint); + break; + case "Escape": + e.preventDefault(); + this.props.onCancel(); + break; + case "ArrowUp": + case "ArrowDown": { + e.preventDefault(); + const newIndex = i + (e.key === "ArrowUp" ? -1 : 1); + this.monthYearOptionButtonsRef[newIndex]?.focus(); + break; + } + } + }; + renderOptions = (): React.ReactElement[] => { + // Clear refs to prevent memory leaks on re-render + this.monthYearOptionButtonsRef = {}; + return this.state.monthYearsList.map( - (monthYear: Date): React.ReactElement => { + (monthYear: Date, i: number): React.ReactElement => { const monthYearPoint = getTime(monthYear); const isSameMonthYear = isSameYear(this.props.date, monthYear) && @@ -82,6 +111,14 @@ export default class MonthYearDropdownOptions extends Component< return (
{ + this.monthYearOptionButtonsRef[i] = el; + if (isSameMonthYear) { + el?.focus(); + } + }} + role="button" + tabIndex={0} className={ isSameMonthYear ? "react-datepicker__month-year-option--selected_month-year" @@ -89,6 +126,7 @@ export default class MonthYearDropdownOptions extends Component< } key={monthYearPoint} onClick={this.onChange.bind(this, monthYearPoint)} + onKeyDown={this.handleOptionKeyDown.bind(this, i, monthYearPoint)} aria-selected={isSameMonthYear ? "true" : undefined} > {isSameMonthYear ? ( diff --git a/src/test/month_year_dropdown_test.test.tsx b/src/test/month_year_dropdown_test.test.tsx index ea5067eed..7efddab7e 100644 --- a/src/test/month_year_dropdown_test.test.tsx +++ b/src/test/month_year_dropdown_test.test.tsx @@ -82,6 +82,14 @@ describe("MonthYearDropdown", () => { ); }); + it("renders the read view as a button so it is reachable via Tab, matching MonthDropdown/YearDropdown", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + expect(monthYearReadView.tagName).toBe("BUTTON"); + }); + it("opens a list when read view is clicked", () => { const monthYearReadView = safeQuerySelector( monthYearDropdown, @@ -201,6 +209,128 @@ describe("MonthYearDropdown", () => { expect(handleChangeResult?.toString()).toBe(expected_date.toString()); }); + it("focuses the selected month year option when the dropdown opens", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + fireEvent.click(monthYearReadView); + + const selectedOption = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-option--selected_month-year", + ); + expect(document.activeElement).toEqual(selectedOption); + }); + + it("calls the supplied onChange function when a month year is selected using arrows and enter key", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + fireEvent.click(monthYearReadView); + + const monthYearOptions = safeQuerySelectorAll( + monthYearDropdown, + '.react-datepicker__month-year-dropdown [role="button"]', + 12, + ); + + const selectedOption = monthYearOptions[6]!; + fireEvent.keyDown(selectedOption, { key: "ArrowDown" }); + + const nextOption = monthYearOptions[7]; + expect(document.activeElement).toEqual(nextOption); + + fireEvent.keyDown(document.activeElement!, { key: "Enter" }); + expect(handleChangeResult?.toString()).toBe( + newDate("2018-02").toString(), + ); + }); + + it("handles ArrowUp key navigation correctly", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + fireEvent.click(monthYearReadView); + + const monthYearOptions = safeQuerySelectorAll( + monthYearDropdown, + '.react-datepicker__month-year-dropdown [role="button"]', + 12, + ); + + const selectedOption = monthYearOptions[6]!; + fireEvent.keyDown(selectedOption, { key: "ArrowUp" }); + + const prevOption = monthYearOptions[5]; + expect(document.activeElement).toEqual(prevOption); + }); + + it("handles Escape key to cancel dropdown", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + fireEvent.click(monthYearReadView); + + const monthYearOptions = safeQuerySelectorAll( + monthYearDropdown, + '.react-datepicker__month-year-dropdown [role="button"]', + 12, + ); + + const selectedOption = monthYearOptions[6]!; + fireEvent.keyDown(selectedOption, { key: "Escape" }); + + expect( + monthYearDropdown?.querySelectorAll( + ".react-datepicker__month-year-dropdown", + ), + ).toHaveLength(0); + }); + + it("does not move focus past the last option when pressing ArrowDown", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + fireEvent.click(monthYearReadView); + + const monthYearOptions = safeQuerySelectorAll( + monthYearDropdown, + '.react-datepicker__month-year-dropdown [role="button"]', + 12, + ); + + const lastOption = monthYearOptions[11]!; + lastOption.focus(); + fireEvent.keyDown(lastOption, { key: "ArrowDown" }); + + expect(document.activeElement).toEqual(lastOption); + }); + + it("does not move focus before the first option when pressing ArrowUp", () => { + const monthYearReadView = safeQuerySelector( + monthYearDropdown, + ".react-datepicker__month-year-read-view", + ); + fireEvent.click(monthYearReadView); + + const monthYearOptions = safeQuerySelectorAll( + monthYearDropdown, + '.react-datepicker__month-year-dropdown [role="button"]', + 12, + ); + + const firstOption = monthYearOptions[0]!; + firstOption.focus(); + fireEvent.keyDown(firstOption, { key: "ArrowUp" }); + + expect(document.activeElement).toEqual(firstOption); + }); + it("should use dateFormat to display date in dropdown", () => { registerLocale("fi", fi); let dropdownDateFormat = getMonthYearDropdown({