diff --git a/src/time.jsx b/src/time.jsx index a79f3b0bc..692d9bacb 100644 --- a/src/time.jsx +++ b/src/time.jsx @@ -134,6 +134,21 @@ export default class Time extends React.Component { event.key = "Enter"; } + if ( + (event.key === "ArrowUp" || event.key === "ArrowLeft") && + event.target.previousSibling + ) { + event.preventDefault(); + event.target.previousSibling.focus(); + } + if ( + (event.key === "ArrowDown" || event.key === "ArrowRight") && + event.target.nextSibling + ) { + event.preventDefault(); + event.target.nextSibling.focus(); + } + if (event.key === "Enter") { this.handleClick(time); } @@ -175,27 +190,39 @@ export default class Time extends React.Component { } } - return times.map((time, i) => ( -
  • { - if (isBefore(time, activeTime) || isEqual(time, activeTime)) { - this.centerLi = li; + // Determine which time to focus and scroll into view when component mounts + const timeToFocus = times.reduce((prev, time) => { + if (isBefore(time, activeTime) || isEqual(time, activeTime)) { + return time; + } else { + return prev; + } + }, times[0]); + + return times.map((time, i) => { + return ( +
  • { + if (time === timeToFocus) { + this.centerLi = li; + } + }} + onKeyDown={(ev) => { + this.handleOnKeyDown(ev, time); + }} + tabIndex={time === timeToFocus ? "0" : "-1"} + role="option" + aria-selected={ + this.isSelectedTime(time, currH, currM) ? "true" : undefined } - }} - onKeyDown={(ev) => { - this.handleOnKeyDown(ev, time); - }} - tabIndex="0" - aria-selected={ - this.isSelectedTime(time, currH, currM) ? "true" : undefined - } - > - {formatDate(time, format, this.props.locale)} -
  • - )); + > + {formatDate(time, format, this.props.locale)} + + ); + }); }; render() { @@ -231,7 +258,8 @@ export default class Time extends React.Component { this.list = list; }} style={height ? { height } : {}} - tabIndex="0" + role="listbox" + aria-label={this.props.timeCaption} > {this.renderTimes()} diff --git a/test/time_format_test.js b/test/time_format_test.js index 1d79ef390..e7a4e612a 100644 --- a/test/time_format_test.js +++ b/test/time_format_test.js @@ -110,6 +110,21 @@ describe("TimeComponent", () => { expect(timeListItem.at(0).prop("aria-selected")).to.eq("true"); }); + it("should enable keyboard focus on the selected item", () => { + var timeComponent = mount( + + ); + + var timeListItem = timeComponent.find( + ".react-datepicker__time-list-item--selected" + ); + expect(timeListItem.at(0).prop("tabIndex")).to.equal("0"); + }); + it("should not add the aria-selected property to a regular item", () => { var timeComponent = mount( { expect(timeListItem.at(0).prop("aria-selected")).to.be.undefined; }); + it("should disable keyboard focus on a regular item", () => { + var timeComponent = mount( + + ); + + var timeListItem = timeComponent.find( + ".react-datepicker__time-list-item" + ); + expect(timeListItem.at(0).prop("tabIndex")).to.equal("-1"); + }); + + it("when no selected time, should focus the time closest to the opened time", () => { + var timeComponent = mount( + + ); + + var timeListItem = timeComponent.find( + ".react-datepicker__time-list-item" + ); + expect( + timeListItem + .findWhere((node) => node.type() && node.text() === "09:00") + .prop("tabIndex") + ).to.equal("0"); + }); + it("when no selected time, should call calcCenterPosition with centerLi ref, closest to the opened time", () => { mount(