Skip to content

Conversation

mahoneycm
Copy link
Contributor

@mahoneycm mahoneycm commented Feb 20, 2024

Summary

Fixed a bug that caused mouseover events to focus buttons and prevent keyboard navigation. Now, hovering the mouse over date picker buttons will only cause the hover state.

Breaking change

This is not a breaking change

Related issue

Closes #5752

Related pull requests

Changelog →

Preview link

Date Picker →

Date Range Picker →

Problem statement

Hovering the mouse over date picker calendar buttons cause them to gain focus. This is unexpected behavior which also prevents keyboard navigation through calendar buttons.

Solution

Enhance logic to check for a focused calendar button. The calendar will apply the correct focus on render while permitting mouseover behaviors to exist cohesively.

This solution:

  1. Follows expected mouseover behaviors.
  2. Does not affect date picker usage.

Additionally, I removed the mouseover event listeners and functions from month and year buttons. They did not appear to add any functionality beyond focusing the button on hover, which we want to prevent.

handleMousoverFromDate is maintained in order to properly render date range previews on hover.

Accessibility improvements

This solution improves accessibility interaction in two ways.

VoiceOver

In it’s current iteration, when using date picker with a mouse and VoiceOver, it would rapidly call out dates as your mouse moved over them. This, in my opinion, is unexpected behavior. VoiceOver should not read out other buttons or table content upon mouseover events.

With this update, VoiceOver will only call out the buttons when selected via the keyboard.

High zoom magnification behaviors

As depicted in #5752, using the date picker at a high magnification (400%, AA requirement) the mouse would sometimes jump to the selected button, causing a mouseover event, and locking the focus on that button. Keyboard interaction would be prevented by the hovered mouse. For users who rely on keyboard navigation, this would cause confusion as they would not move the mouse to identify the issue.

With this update, users will be able to use expected keyboard interactions without error.

Alternatives considered

blur

I tested calling blur() on the selected element during keyboard interactions, but this caused accessibility issues at high zoom (400%). The window would jump unexpectedly and cause the mouse to move, hovering a new button, which then stole focus once again.

Focus styles

To prevent visual regression, I also considered adding focus styles to hover. In my opinion, this causes confusion because the actual focused element and the hovered button look the same and it’s hard to distinguish which is actually focused.

Removing mouseover events altogether

Originally, I had removed mouseover events all together. This caused a visual regression when selecting a date range for the first time where the range preview would not show. You can view this in action via this draft PR and testing Date Range Picker

Additional concerns

Current date picker behaviors cause a new calendar to be rendered after every mouseover and keyboard event. It does so by cloning the existing calendar, setting appropriate classes and returning an HTML template literal. This pattern does not match modern USWDS practices and likely affects performance.

Major changes

  • Date picker no longer updates focus on mouseover events.
  • Behavior changes help to meet WCAG AA requirement

The intent of this Success Criterion is to support people with low vision who need to enlarge text and read it in a single column. When the browser zoom is used to scale content to 400%, it reflows - i.e., it is presented in one column so that scrolling in more than one direction is not necessary. 1

Testing and review

  1. Visit date picker.
  2. Test mouse interactions and confirm they work as expected.
  3. Test keyboard interactions and confirm they work as expected.
  4. Test VoiceOver and ensure callouts are expected.
  5. Zoom to 400% magnification and use the keyboard to interact with the date picker. There should be no errors navigating throughout the calendar.
  6. Repeat steps for all variants.
  7. Repeat steps date range picker and it’s variants.

Tip

When using keyboard navigation with a screenreader, use the screenreader’s Table Navigation controls (varies by screenreader). This can help prevent unexpected interactions. Use default arrow keys when jumping months via dates.

Testing checklist

  • Expected mouse behaviors work without error
  • Expected keyboard behaviors work without error
  • Screenreader behaviors work as expected without error
  • Component is usable at high magnification without error

Footnotes

  1. WCAG Reflow (Level AA) requirement

Copy link
Contributor

@amyleadem amyleadem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mahoneycm, I tested keyboard navigation on the calendar at 400% zoom and I noticed an error where the calendar would stop reacting to keyboard input if my mouse cursor was hovering over any of the calendar dates. The mouseover was not causing it to focus, but it still interrupted keyboard interaction. Can you take a look at that?

Comment on lines +1713 to +1716
if (!calendarEl.contains(focusedEl)) {
return;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Early return to prevent console error when focusedEl is undefined

Comment on lines -2233 to -2238
[CALENDAR_MONTH]() {
handleMouseoverFromMonth(this);
},
[CALENDAR_YEAR]() {
handleMouseoverFromYear(this);
},
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed unnecessary handleMousover functions to prevent unexpected focus on mouseover behaviors

Comment on lines +1817 to +1823
if (calendarEl.contains(focusedEl)) {
const focusElLabel = focusedEl.getAttribute("aria-label");
const newCalendar = renderCalendar(calendarEl, dateToDisplay);
newCalendar.querySelector(`[aria-label="${focusElLabel}"]`).focus();
} else {
renderCalendar(calendarEl, dateToDisplay);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From JS comments:

If the calendar contains a focused element, reapply focus after new calendar renders.
Targeted using aria-label because they are unique and prevents error when focus is in the table header.

Comment on lines -1804 to +1818
const newCalendar = renderCalendar(calendarEl, dateToDisplay);
newCalendar.querySelector(CALENDAR_DATE_FOCUSED).focus();
const focusedEl = document.activeElement;

// If the calendar contains a focused element, reapply focus after new calendar renders.
// Targeted using aria-label because they are unique and prevents error when
// focus is in the table header.
if (calendarEl.contains(focusedEl)) {
const focusElLabel = focusedEl.getAttribute("aria-label");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activeElement vs. --focused class

When troubleshooting this issue, I decided I needed a way to identify what was actually :focused and what was hovered. It made sense to use document.activeElement to select the focused button natively while maintaining the focused class that is used to identify date range caps.

This seemed like the path of least resistance though we should consider renaming the --focused class to --hovered or something similar for the sake of brevity

if (calendarEl.contains(focusedEl)) {
const focusElLabel = focusedEl.getAttribute("aria-label");
const newCalendar = renderCalendar(calendarEl, dateToDisplay);
newCalendar.querySelector(`[aria-label="${focusElLabel}"]`).focus();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aria-label vs. dataset.value

In order to refocus the correct button, I used aria-label because they were unique and included on all buttons on the calendar.

This also prevented an error when tabbing to navigation, month, and year buttons which did not include a data-value attribute

@mahoneycm
Copy link
Contributor Author

Moving to draft, testing alternative solution #5774

@mahoneycm mahoneycm marked this pull request as draft July 22, 2024 17:53
@mahoneycm
Copy link
Contributor Author

Closing in favor of #5774

@mahoneycm mahoneycm closed this Nov 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

USWDS - Bug: Date picker - mouse hover preventing keyboard interaction on high zoom magnification
2 participants