Skip to content

Commit d67b2ba

Browse files
Merge pull request #3726 from RoryStokes/master
Enable keyboard navigation of quarters (fixes #3464)
2 parents 90802d2 + c49fb8e commit d67b2ba

File tree

3 files changed

+133
-2
lines changed

3 files changed

+133
-2
lines changed

src/date_utils.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import addHours from "date-fns/addHours";
66
import addDays from "date-fns/addDays";
77
import addWeeks from "date-fns/addWeeks";
88
import addMonths from "date-fns/addMonths";
9+
import addQuarters from "date-fns/addQuarters";
910
import addYears from "date-fns/addYears";
1011
import subMinutes from "date-fns/subMinutes";
1112
import subHours from "date-fns/subHours";
1213
import subDays from "date-fns/subDays";
1314
import subWeeks from "date-fns/subWeeks";
1415
import subMonths from "date-fns/subMonths";
16+
import subQuarters from "date-fns/subQuarters";
1517
import subYears from "date-fns/subYears";
1618
import getSeconds from "date-fns/getSeconds";
1719
import getMinutes from "date-fns/getMinutes";
@@ -221,7 +223,7 @@ export function getEndOfMonth(date) {
221223

222224
// *** Addition ***
223225

224-
export { addMinutes, addDays, addWeeks, addMonths, addYears };
226+
export { addMinutes, addDays, addWeeks, addMonths, addQuarters, addYears };
225227

226228
// *** Subtraction ***
227229

@@ -232,6 +234,7 @@ export {
232234
subDays,
233235
subWeeks,
234236
subMonths,
237+
subQuarters,
235238
subYears,
236239
};
237240

src/month.jsx

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export default class Month extends React.Component {
7474
};
7575

7676
MONTH_REFS = [...Array(12)].map(() => React.createRef());
77+
QUARTER_REFS = [...Array(4)].map(() => React.createRef());
7778

7879
isDisabled = (date) => utils.isDayDisabled(date, this.props);
7980

@@ -141,6 +142,10 @@ export default class Month extends React.Component {
141142
utils.getYear(day) === utils.getYear(utils.newDate()) &&
142143
m === utils.getMonth(utils.newDate());
143144

145+
isCurrentQuarter = (day, q) =>
146+
utils.getYear(day) === utils.getYear(utils.newDate()) &&
147+
q === utils.getQuarter(utils.newDate());
148+
144149
isSelectedMonth = (day, m, selected) =>
145150
utils.getMonth(day) === m && utils.getYear(day) === utils.getYear(selected);
146151

@@ -290,6 +295,37 @@ export default class Month extends React.Component {
290295
);
291296
};
292297

298+
handleQuarterNavigation = (newQuarter, newDate) => {
299+
if (this.isDisabled(newDate) || this.isExcluded(newDate)) return;
300+
this.props.setPreSelection(newDate);
301+
this.QUARTER_REFS[newQuarter - 1].current &&
302+
this.QUARTER_REFS[newQuarter - 1].current.focus();
303+
};
304+
305+
onQuarterKeyDown = (event, quarter) => {
306+
const eventKey = event.key;
307+
if (!this.props.disabledKeyboardNavigation) {
308+
switch (eventKey) {
309+
case "Enter":
310+
this.onQuarterClick(event, quarter);
311+
this.props.setPreSelection(this.props.selected);
312+
break;
313+
case "ArrowRight":
314+
this.handleQuarterNavigation(
315+
quarter === 4 ? 1 : quarter + 1,
316+
utils.addQuarters(this.props.preSelection, 1)
317+
);
318+
break;
319+
case "ArrowLeft":
320+
this.handleQuarterNavigation(
321+
quarter === 1 ? 4 : quarter - 1,
322+
utils.subQuarters(this.props.preSelection, 1)
323+
);
324+
break;
325+
}
326+
}
327+
};
328+
293329
getMonthClassNames = (m) => {
294330
const {
295331
day,
@@ -343,6 +379,16 @@ export default class Month extends React.Component {
343379
return tabIndex;
344380
};
345381

382+
getQuarterTabIndex = (q) => {
383+
const preSelectedQuarter = utils.getQuarter(this.props.preSelection);
384+
const tabIndex =
385+
!this.props.disabledKeyboardNavigation && q === preSelectedQuarter
386+
? "0"
387+
: "-1";
388+
389+
return tabIndex;
390+
};
391+
346392
getAriaLabel = (month) => {
347393
const {
348394
chooseDayAriaLabelPrefix = "Choose",
@@ -360,7 +406,15 @@ export default class Month extends React.Component {
360406
};
361407

362408
getQuarterClassNames = (q) => {
363-
const { day, startDate, endDate, selected, minDate, maxDate } = this.props;
409+
const {
410+
day,
411+
startDate,
412+
endDate,
413+
selected,
414+
minDate,
415+
maxDate,
416+
preSelection,
417+
} = this.props;
364418
return classnames(
365419
"react-datepicker__quarter-text",
366420
`react-datepicker__quarter-${q}`,
@@ -373,6 +427,8 @@ export default class Month extends React.Component {
373427
q,
374428
selected
375429
),
430+
"react-datepicker__quarter-text--keyboard-selected":
431+
utils.getQuarter(preSelection) === q,
376432
"react-datepicker__quarter--in-range": utils.isQuarterInRange(
377433
startDate,
378434
endDate,
@@ -454,12 +510,18 @@ export default class Month extends React.Component {
454510
{quarters.map((q, j) => (
455511
<div
456512
key={j}
513+
ref={this.QUARTER_REFS[j]}
457514
role="option"
458515
onClick={(ev) => {
459516
this.onQuarterClick(ev, q);
460517
}}
518+
onKeyDown={(ev) => {
519+
this.onQuarterKeyDown(ev, q);
520+
}}
461521
className={this.getQuarterClassNames(q)}
462522
aria-selected={this.isSelectedQuarter(day, q, selected)}
523+
tabIndex={this.getQuarterTabIndex(q)}
524+
aria-current={this.isCurrentQuarter(day, q) ? "date" : undefined}
463525
>
464526
{utils.getQuarterShortInLocale(q, this.props.locale)}
465527
</div>

test/month_test.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,20 @@ describe("Month", () => {
550550
);
551551
});
552552

553+
it("should enable keyboard focus on the preselected component", () => {
554+
const monthComponent = mount(
555+
<Month
556+
preSelection={utils.newDate("2015-02-01")}
557+
day={utils.newDate("2015-02-01")}
558+
startDate={utils.newDate("2015-01-01")}
559+
endDate={utils.newDate("2015-08-01")}
560+
showQuarterYearPicker
561+
/>
562+
);
563+
const quarter = monthComponent.find(".react-datepicker__quarter-1");
564+
expect(quarter.prop("tabIndex")).to.equal("0");
565+
});
566+
553567
it("should render full month name", () => {
554568
const monthComponent = mount(
555569
<Month
@@ -572,6 +586,58 @@ describe("Month", () => {
572586
expect(month.text()).to.equal("Feb");
573587
});
574588

589+
describe("Keyboard navigation", () => {
590+
const renderQuarters = (props) =>
591+
shallow(<Month showQuarterYearPicker {...props} />);
592+
593+
it("should trigger setPreSelection and set Q3 as pre-selected on arrowRight", () => {
594+
let preSelected = false;
595+
const setPreSelection = (param) => {
596+
preSelected = param;
597+
};
598+
599+
const quartersComponent = renderQuarters({
600+
selected: utils.newDate("2015-04-01"),
601+
day: utils.newDate("2015-04-01"),
602+
setPreSelection: setPreSelection,
603+
preSelection: utils.newDate("2015-04-01"),
604+
});
605+
quartersComponent
606+
.find(".react-datepicker__quarter-2")
607+
.simulate("keydown", getKey("Tab"));
608+
quartersComponent
609+
.find(".react-datepicker__quarter-2")
610+
.simulate("keydown", getKey("ArrowRight"));
611+
612+
expect(preSelected.toString()).to.equal(
613+
utils.newDate("2015-07-01").toString()
614+
);
615+
});
616+
617+
it("should trigger setPreSelection and set Q1 as pre-selected on arrowLeft", () => {
618+
let preSelected = false;
619+
const setPreSelection = (param) => {
620+
preSelected = param;
621+
};
622+
const quartersComponent = renderQuarters({
623+
selected: utils.newDate("2015-04-01"),
624+
day: utils.newDate("2015-04-01"),
625+
setPreSelection: setPreSelection,
626+
preSelection: utils.newDate("2015-04-01"),
627+
});
628+
quartersComponent
629+
.find(".react-datepicker__quarter-2")
630+
.simulate("keydown", getKey("Tab"));
631+
quartersComponent
632+
.find(".react-datepicker__quarter-2")
633+
.simulate("keydown", getKey("ArrowLeft"));
634+
635+
expect(preSelected.toString()).to.equal(
636+
utils.newDate("2015-01-01").toString()
637+
);
638+
});
639+
});
640+
575641
describe("Keyboard navigation", () => {
576642
const renderMonth = (props) =>
577643
mount(<Month showMonthYearPicker {...props} />);

0 commit comments

Comments
 (0)