Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

[terra-date-picker] [terra-date-time-picker] Fixed DatePicker focus issues #2167

Merged
merged 7 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/terra-date-picker/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

* Fixed
* Focus is now not moving to the calendar button when navigating using `tab` key.
* Focus is now retained on the calendar button when the calendar is closed in mobile view.

## 4.110.1 - (May 20, 2024)

* Fixed
Expand Down
56 changes: 36 additions & 20 deletions packages/terra-date-picker/src/DatePicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ const propTypes = {
* String that labels the current element. 'aria-label' must be present for accessibility.
*/
ariaLabel: PropTypes.string,
/**
* @private
* Whether or not to disable focus on the calendar button when the calendar picker dismisses.
*/
disableButtonFocusOnClose: PropTypes.bool,
/**
* Whether the date input should be disabled.
*/
Expand Down Expand Up @@ -150,7 +145,6 @@ const propTypes = {
};

const defaultProps = {
disableButtonFocusOnClose: false,
disabled: false,
excludeDates: undefined,
filterDate: undefined,
Expand Down Expand Up @@ -188,6 +182,7 @@ class DatePicker extends React.Component {
this.datePickerContainer = React.createRef();
this.isDefaultDateAcceptable = props.isDefaultDateAcceptable;
this.containerHasFocus = false;
this.inputChanged = false;
this.handleBlur = this.handleBlur.bind(this);
this.handleBreakpointChange = this.handleBreakpointChange.bind(this);
this.handleChange = this.handleChange.bind(this);
Expand Down Expand Up @@ -226,6 +221,12 @@ class DatePicker extends React.Component {
this.isDefaultDateAcceptable = (this.props.isDefaultDateAcceptable) ? this.props.isDefaultDateAcceptable : this.validateDefaultDate();
}

componentWillUnmount() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}

handleBreakpointChange(activeBreakpoint) {
const showPortalPicker = !this.props.isInline && (activeBreakpoint === 'tiny' || activeBreakpoint === 'small');

Expand All @@ -248,7 +249,7 @@ class DatePicker extends React.Component {
// as well as manually entering a valid date or clearing the date,
// Until a fix is made, we need to return if the event type is 'change' or 'keydown' indicating that onSelect was
// invoked from a manual change. See https://github.com/Hacker0x01/react-datepicker/issues/990
if (event.type === 'change' || event.type === 'keydown' || !selectedDate || !selectedDate.isValid()) {
if (event.type === 'change' || event.type === 'keydown' || !selectedDate || !selectedDate.isValid() || event.type === 'blur') {
return;
}

Expand All @@ -259,25 +260,17 @@ class DatePicker extends React.Component {
this.props.onSelect(event, selectedDate.format(DateUtil.ISO_EXTENDED_DATE_FORMAT));
}

if (!this.props.disableButtonFocusOnClose) {
// Allows time for focus-trap to release focus on the picker before returning focus to the calendar button.
setTimeout(() => {
/*
* Make sure the reference to calendarButton still exists before calling focus because it is possible that it is now
* nullified after the 100 ms timeout due to a force remount of this component with a new `key` prop value.
* Reference https://github.com/cerner/terra-framework/issues/1086
*/
if (this.calendarButton) {
this.calendarButton.focus();
}
}, 100);
saket2403 marked this conversation as resolved.
Show resolved Hide resolved
// If the portal picker is displayed, focus on the calendar button on selecting a date
if (this.state.showPortalPicker) {
this.focusCalendarButton();
}
}

handleOnClickOutside(event) {
if (this.props.onClickOutside) {
this.props.onClickOutside(event);
}
this.focusCalendarButton();
saket2403 marked this conversation as resolved.
Show resolved Hide resolved
}

handleBlur(event) {
Expand All @@ -287,6 +280,9 @@ class DatePicker extends React.Component {

// Handle blur only if focus has moved out of the entire date picker component.
if (!this.datePickerContainer.current.contains(activeTarget)) {
this.focusCalendarButton();
this.inputChanged = false;

if (this.props.onBlur) {
const metadata = this.getMetadata();
this.props.onBlur(event, metadata);
Expand All @@ -297,6 +293,8 @@ class DatePicker extends React.Component {
}

handleChange(date, event, value) {
this.inputChanged = true;

if (event.type === 'change') {
this.dateValue = value;
}
Expand Down Expand Up @@ -407,10 +405,28 @@ class DatePicker extends React.Component {
return isAcceptable;
}

focusCalendarButton() {
// Clear any existing timeout before setting a new one
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
// Allows time for focus-trap to release focus on the picker before returning focus to the calendar button.
this.timeoutId = setTimeout(() => {
/*
* Make sure the reference to calendarButton still exists before calling focus because it is possible that it is now
* nullified after the 100 ms timeout due to a force remount of this component with a new `key` prop value.
* Reference https://github.com/cerner/terra-framework/issues/1086
*/

if (this.calendarButton && this.inputChanged) {
this.calendarButton.focus();
}
}, 100);
}

render() {
const {
ariaLabel,
disableButtonFocusOnClose,
excludeDates,
filterDate,
includeDates,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
exports[`should render a calendar filter with default date 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
intl={
Expand Down Expand Up @@ -49,7 +48,6 @@ exports[`should render a calendar filter with default date 1`] = `
exports[`should render a calendar filter with excluded dates 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
excludeDates={
Expand Down Expand Up @@ -99,7 +97,6 @@ exports[`should render a calendar filter with excluded dates 1`] = `
exports[`should render a calendar filter with filtered dates 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
filterDate={[Function]}
Expand Down Expand Up @@ -145,7 +142,6 @@ exports[`should render a calendar filter with filtered dates 1`] = `
exports[`should render a calendar filter with included dates 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
includeDates={
Expand Down Expand Up @@ -195,7 +191,6 @@ exports[`should render a calendar filter with included dates 1`] = `
exports[`should render a calendar filter with min and max dates 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
intl={
Expand Down Expand Up @@ -240,7 +235,6 @@ exports[`should render a calendar filter with min and max dates 1`] = `
exports[`should render a calendar filter with onChange 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
intl={
Expand Down Expand Up @@ -286,7 +280,6 @@ exports[`should render a calendar filter with onChange 1`] = `
exports[`should render a controlled calendar filter 1`] = `
<DatePicker
calendarClassName="calendar-filter"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
intl={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ exports[`correctly applies the theme context className 1`] = `
name="date-input"
>
<DatePicker
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
intl={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ exports[`should render a DatePickerField with props 1`] = `
>
<DatePicker
ariaLabel="Label Test"
disableButtonFocusOnClose={false}
disabled={true}
errorId=""
excludeDates={
Expand Down Expand Up @@ -1195,7 +1194,6 @@ exports[`should render a default DatePickerField component 1`] = `
>
<DatePicker
ariaLabel="Label"
disableButtonFocusOnClose={false}
disabled={false}
errorId=""
id="test-date-picker"
Expand Down Expand Up @@ -2174,7 +2172,6 @@ exports[`should render a valid DatePickerField with props 1`] = `
>
<DatePicker
ariaLabel="Label Test"
disableButtonFocusOnClose={false}
disabled={true}
errorId=""
excludeDates={
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions packages/terra-date-time-picker/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Removed
* Removed passing `disableButtonFocusOnClose` prop to the `DatePicker` Component.

## 4.118.1 - (May 20, 2024)

* Changed
Expand Down
1 change: 0 additions & 1 deletion packages/terra-date-time-picker/src/DateTimePicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -811,7 +811,6 @@ class DateTimePicker extends React.Component {
value={dateValue}
name="input"
disabled={disabled}
disableButtonFocusOnClose
isIncomplete={isIncomplete}
isInvalid={isInvalid}
required={required}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading