-
Notifications
You must be signed in to change notification settings - Fork 1.1k
USWDS - Date picker: Prevent focus on mouseover
#5778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this 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?
if (!calendarEl.contains(focusedEl)) { | ||
return; | ||
} | ||
|
There was a problem hiding this comment.
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
[CALENDAR_MONTH]() { | ||
handleMouseoverFromMonth(this); | ||
}, | ||
[CALENDAR_YEAR]() { | ||
handleMouseoverFromYear(this); | ||
}, |
There was a problem hiding this comment.
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
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); | ||
} |
There was a problem hiding this comment.
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.
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"); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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
Moving to draft, testing alternative solution #5774 |
Closing in favor of #5774 |
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 thehover
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:
mouseover
behaviors.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 altogetherOriginally, 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
mouseover
events.Testing and review
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
Footnotes
WCAG Reflow (Level AA) requirement ↩