Skip to content

Commit

Permalink
Merge branch 'master' into pr/3799
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnrusschen committed Jan 30, 2023
2 parents c570302 + 3070e4a commit 321084a
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 16 deletions.
2 changes: 1 addition & 1 deletion examples/hello-world/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"dependencies": {
"react": "^18.2.0",
"react-datepicker": "^4.8.0",
"react-datepicker": "^4.9.0",
"react-dom": "^18.2.0",
"react-scripts": "4.0.3"
},
Expand Down
28 changes: 14 additions & 14 deletions examples/hello-world/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10070,17 +10070,17 @@ react-app-polyfill@^2.0.0:
regenerator-runtime "^0.13.7"
whatwg-fetch "^3.4.1"

react-datepicker@^4.8.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.8.0.tgz#11b8918d085a1ce4781eee4c8e4641b3cd592010"
integrity sha512-u69zXGHMpxAa4LeYR83vucQoUCJQ6m/WBsSxmUMu/M8ahTSVMMyiyQzauHgZA2NUr9y0FUgOAix71hGYUb6tvg==
react-datepicker@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.9.0.tgz#e70cc7956e89e22d41dfdca06a6a8bf9676a0cc6"
integrity sha512-H2TphqnbFpBzbgcYN6B2lhiNt0us0OBdvMOuhmZMXvm7s1Cz7U5nYdORL7aCh7bc41ofPimf3ocg/uw2/xdRUA==
dependencies:
"@popperjs/core" "^2.9.2"
classnames "^2.2.6"
date-fns "^2.24.0"
prop-types "^15.7.2"
react-onclickoutside "^6.12.0"
react-popper "^2.2.5"
react-onclickoutside "^6.12.2"
react-popper "^2.3.0"

react-dev-utils@^11.0.3:
version "11.0.4"
Expand Down Expand Up @@ -10140,15 +10140,15 @@ react-is@^17.0.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==

react-onclickoutside@^6.12.0:
version "6.12.0"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.0.tgz#c63db2e3c2c852b288160cdb6cff443604e28db4"
integrity sha512-oPlOTYcISLHfpMog2lUZMFSbqOs4LFcA4+vo7fpfevB5v9Z0D5VBDBkfeO5lv+hpEcGoaGk67braLT+QT+eICA==
react-onclickoutside@^6.12.2:
version "6.12.2"
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.2.tgz#8e6cf80c7d17a79f2c908399918158a7b02dda01"
integrity sha512-NMXGa223OnsrGVp5dJHkuKxQ4czdLmXSp5jSV9OqiCky9LOpPATn3vLldc+q5fK3gKbEHvr7J1u0yhBh/xYkpA==

react-popper@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
integrity sha512-kxGkS80eQGtLl18+uig1UIf9MKixFSyPxglsgLBxlYnyDf65BiY9B3nZSc6C9XUNDgStROB0fMQlTEz1KxGddw==
react-popper@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
dependencies:
react-fast-compare "^3.0.1"
warning "^4.0.2"
Expand Down
37 changes: 37 additions & 0 deletions src/calendar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
isValid,
getYearsPeriod,
DEFAULT_YEAR_ITEM_NUMBER,
getMonthInLocale,
} from "./date_utils";

const DROPDOWN_FOCUS_CLASSNAMES = [
Expand Down Expand Up @@ -210,6 +211,7 @@ export default class Calendar extends React.Component {
date: this.getDateInView(),
selectingDate: null,
monthContainer: null,
isRenderAriaLiveMessage: false,
};
}

Expand Down Expand Up @@ -312,6 +314,7 @@ export default class Calendar extends React.Component {
handleYearChange = (date) => {
if (this.props.onYearChange) {
this.props.onYearChange(date);
this.setState({ isRenderAriaLiveMessage: true });
}
if (this.props.adjustDateOnChange) {
if (this.props.onSelect) {
Expand All @@ -328,6 +331,7 @@ export default class Calendar extends React.Component {
handleMonthChange = (date) => {
if (this.props.onMonthChange) {
this.props.onMonthChange(date);
this.setState({ isRenderAriaLiveMessage: true });
}
if (this.props.adjustDateOnChange) {
if (this.props.onSelect) {
Expand Down Expand Up @@ -984,6 +988,38 @@ export default class Calendar extends React.Component {
}
};

renderAriaLiveRegion = () => {
const { startPeriod, endPeriod } = getYearsPeriod(
this.state.date,
this.props.yearItemNumber
);
let ariaLiveMessage;

if (this.props.showYearPicker) {
ariaLiveMessage = `${startPeriod} - ${endPeriod}`;
} else if (
this.props.showMonthYearPicker ||
this.props.showQuarterYearPicker
) {
ariaLiveMessage = getYear(this.state.date);
} else {
ariaLiveMessage = `${getMonthInLocale(
getMonth(this.state.date),
this.props.locale
)} ${getYear(this.state.date)}`;
}

return (
<span
role="alert"
aria-live="polite"
className="react-datepicker__aria-live"
>
{this.state.isRenderAriaLiveMessage && ariaLiveMessage}
</span>
);
};

renderChildren = () => {
if (this.props.children) {
return (
Expand All @@ -1005,6 +1041,7 @@ export default class Calendar extends React.Component {
showPopperArrow={this.props.showPopperArrow}
arrowProps={this.props.arrowProps}
>
{this.renderAriaLiveRegion()}
{this.renderPreviousButton()}
{this.renderNextButton()}
{this.renderMonths()}
Expand Down
75 changes: 75 additions & 0 deletions src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ export default class DatePicker extends React.Component {
// used to focus day in inline version after month has changed, but not on
// initial render
shouldFocusDayInline: false,
isRenderAriaLiveMessage: false,
};
};

Expand Down Expand Up @@ -527,6 +528,7 @@ export default class DatePicker extends React.Component {
this.props.onChangeRaw(event);
}
this.setSelected(date, event, false, monthSelectedIn);
this.setState({ isRenderAriaLiveMessage: true });
if (!this.props.shouldCloseOnSelect || this.props.showTimeSelect) {
this.setPreSelection(date);
} else if (!this.props.inline) {
Expand Down Expand Up @@ -670,6 +672,9 @@ export default class DatePicker extends React.Component {
if (this.props.showTimeInput) {
this.setOpen(true);
}
if (this.props.showTimeSelectOnly || this.props.showTimeSelect) {
this.setState({ isRenderAriaLiveMessage: true });
}
this.setState({ inputValue: null });
};

Expand Down Expand Up @@ -1015,6 +1020,75 @@ export default class DatePicker extends React.Component {
);
};

renderAriaLiveRegion = () => {
const { dateFormat, locale } = this.props;
const isContainsTime =
this.props.showTimeInput || this.props.showTimeSelect;
const longDateFormat = isContainsTime ? "PPPPp" : "PPPP";
let ariaLiveMessage;

if (this.props.selectsRange) {
ariaLiveMessage = `Selected start date: ${safeDateFormat(
this.props.startDate,
{
dateFormat: longDateFormat,
locale,
}
)}. ${
this.props.endDate
? "End date: " +
safeDateFormat(this.props.endDate, {
dateFormat: longDateFormat,
locale,
})
: ""
}`;
} else {
if (this.props.showTimeSelectOnly) {
ariaLiveMessage = `Selected time: ${safeDateFormat(
this.props.selected,
{ dateFormat, locale }
)}`;
} else if (this.props.showYearPicker) {
ariaLiveMessage = `Selected year: ${safeDateFormat(
this.props.selected,
{ dateFormat: "yyyy", locale }
)}`;
} else if (this.props.showMonthYearPicker) {
ariaLiveMessage = `Selected month: ${safeDateFormat(
this.props.selected,
{ dateFormat: "MMMM yyyy", locale }
)}`;
} else if (this.props.showQuarterYearPicker) {
ariaLiveMessage = `Selected quarter: ${safeDateFormat(
this.props.selected,
{
dateFormat: "yyyy, QQQ",
locale,
}
)}`;
} else {
ariaLiveMessage = `Selected date: ${safeDateFormat(
this.props.selected,
{
dateFormat: longDateFormat,
locale,
}
)}`;
}
}

return (
<span
role="alert"
aria-live="polite"
className="react-datepicker__aria-live"
>
{this.state.isRenderAriaLiveMessage && ariaLiveMessage}
</span>
);
};

renderDateInput = () => {
const className = classnames(this.props.className, {
[outsideClickIgnoreClass]: this.state.open,
Expand Down Expand Up @@ -1096,6 +1170,7 @@ export default class DatePicker extends React.Component {
renderInputContainer() {
return (
<div className="react-datepicker__input-container">
{this.renderAriaLiveRegion()}
{this.renderDateInput()}
{this.renderClearButton()}
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/stylesheets/datepicker.scss
Original file line number Diff line number Diff line change
Expand Up @@ -689,3 +689,15 @@
padding-left: 0.2rem;
height: auto;
}

.react-datepicker__aria-live {
position: absolute;
clip-path: circle(0);
border: 0;
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
width: 1px;
white-space: nowrap;
}
70 changes: 70 additions & 0 deletions test/calendar_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1741,4 +1741,74 @@ describe("Calendar", function () {
expect(childrenContainer).to.have.length(0);
});
});

describe("should render aria live region after month/year change", () => {
it("should render aria live region after month change", () => {
const datePicker = TestUtils.renderIntoDocument(
<DatePicker selected={utils.newDate()} />
);
const dateInput = datePicker.input;

TestUtils.Simulate.focus(ReactDOM.findDOMNode(dateInput));

const calendar = TestUtils.scryRenderedComponentsWithType(
datePicker.calendar,
Calendar
)[0];
const nextNavigationButton = TestUtils.findRenderedDOMComponentWithClass(
calendar,
"react-datepicker__navigation--next"
);
nextNavigationButton.click();

const currentMonthText = TestUtils.findRenderedDOMComponentWithClass(
calendar,
"react-datepicker__current-month"
).textContent;

const ariaLiveMessage = TestUtils.findRenderedDOMComponentWithClass(
calendar,
"react-datepicker__aria-live"
).textContent;

expect(currentMonthText).to.equal(ariaLiveMessage);
});

it("should render aria live region after year change", () => {
const datePicker = TestUtils.renderIntoDocument(
<DatePicker showYearDropdown selected={utils.newDate()} />
);
const dateInput = datePicker.input;

TestUtils.Simulate.focus(ReactDOM.findDOMNode(dateInput));

const calendar = TestUtils.scryRenderedComponentsWithType(
datePicker.calendar,
Calendar
)[0];
const yearDropdown = TestUtils.findRenderedDOMComponentWithClass(
calendar,
"react-datepicker__year-read-view"
);
yearDropdown.click();

const option = TestUtils.scryRenderedDOMComponentsWithClass(
calendar,
"react-datepicker__year-option"
)[7];
option.click();

const ariaLiveMessage = TestUtils.findRenderedDOMComponentWithClass(
calendar,
"react-datepicker__aria-live"
).textContent;

expect(ariaLiveMessage).to.equal(
`${utils.getMonthInLocale(
utils.getMonth(calendar.state.date),
datePicker.props.locale
)} ${utils.getYear(calendar.state.date)}`
);
});
});
});

0 comments on commit 321084a

Please sign in to comment.