Skip to content
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

[EuiPopover] Focus on parent popover panel by default, instead of first tabbable child #5784

Merged
merged 10 commits into from
Apr 19, 2022

Conversation

cee-chen
Copy link
Member

@cee-chen cee-chen commented Apr 13, 2022

Summary

closes #5683
closes #4973

After extensive discussion between several designers/developers, we've decided to introduce a wide-spread breaking change on EuiPopover that removes the default behavior of focusing the first tabbable child element within the popover. Instead, the parent panel will receive a the initial focus.

Before:

After:

Why is this a better keyboard / screen reader user experience?

  • As noted in [EuiBasicTable] Actions popover should announce it is a popup #1546 (comment), the You are in a dialogue screen reader text gets interrupted/hijacked by focus moving from the panel to the first child. Removing this allows the You are in a dialogue text to read out fully (including keyboard instructions to close the dialogue).
  • As noted in [EuiDataGrid] Expansion popover initial focus #5683, since we do not control the content of EuiPopover, trying to auto target the first tabbable child may lead to unexpected placement of focus if there's a button or link very far down in the content. It's a more consistent user experience for screen readers to start them at the top and let them navigate through the popover content themselves.

What if a consumer has a very specific scenario where they want to auto-focus a child and know it makes sense?

This is still totally doable/customizable using the initialFocus prop. This PR simply changes the default behavior. I've added a new documentation example in this PR to make that option more evident.

What does this affect?

Many components, which we should likely do a QA pass on:

  • EuiTour (no change, tour popover is not tabbable to)
  • EuiComboBox (no change, prod already did not autofocus first option)
  • EuiColorPicker (no change, prod already did not autofocus child)
  • EuiDatePicker (no change, prod already did not autofocus child)
  • EuiSuperDatePicker (changed from prod - no longer auto focuses tabs)
  • EuiAutoRefresh (changed from prod - no longer auto focuses switch)
  • EuiSelectableTemplateSitewide (no change from prod - autofocuses first option if tabbed, does not autofocus first option if clicked)
  • EuiContextMenu (changed from prod - no longer auto focuses first item but instead the panel)
  • EuiDataGrid (changed from prod - no longer auto focuses first tabbable child)
  • Components using EuiInputPopover
    • EuiAutoRefresh (no change from prod - enter key still works immediately. worth noting this behaves weirdly on tab press but also already does this on prod)
    • EuiRange/EuiDualRange (no change from prod - up/down arrow keys still work immediately, tab does not focus into popover)
    • EuiSuperSelect (no change from prod - first non-disabled option is focused)
    • EuiSuggest (no change from prod - autofocuses first option if tabbed, does not autofocus first option if clicked)
  • Misc usages
    • EuiBreadcrumbs - collapsed toggles (changed from prod - no longer auto focuses first breadcrumb)
    • EuiHeader / EuiHeaderLinks on mobile (changed from prod - no longer auto focuses first link)
    • EuiBasicTable - collapsed items and sort toggle on mobile (changed from prod - no longer auto focuses first option)
    • EuiTablePagination - (changed from prod - no longer auto focuses first option)
    • EuiSearchBar - tags (on load - not changed from prod as the first tag does not autofocus anyway. if already loaded - changed from prod, user must now tab into selectable options)
    • EuiNotificationEvent - context menu (changed from prod - no longer auto focuses first option)
    • EuiMarkdownEditor - errors popover (no change from prod, did not contain focusable items)

(Feel free to add more to this list if I missed anything, and check items off!)

Checklist

- [ ] Checked in both light and dark modes
- [ ] Checked in mobile
- [ ] Props have proper autodocs and playground toggles
- [ ] Checked Code Sandbox works for any docs examples
- [ ] Updated the Figma library counterpart

  • Checked in Chrome, Safari, Edge, and Firefox
  • Added documentation
  • Added or updated **jest and cypress tests**
  • Checked for breaking changes and labeled appropriately
  • Checked for accessibility including keyboard-only and screenreader modes
  • A changelog entry exists and is marked appropriately

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

- because EuiPopover's `updateFocus` is called after the transition ends (350ms currently), it supercedes EuiContextMenu's `updateFocus`

- I'm not a super huge fan of this DOM-heavy workaround but I can't think of any other options - very open to more ideas
src-docs/src/views/popover/trap_focus.js Show resolved Hide resolved
Comment on lines +275 to +279
// If EuiContextMenu is used within an EuiPopover, EuiPopover's own
// `updateFocus()` method hijacks EuiContextMenuPanel's `updateFocus()`
// 350ms after the popover finishes transitioning in. This workaround
// reclaims focus from parent EuiPopovers that do not set an `initialFocus`
reclaimPopoverFocus() {
Copy link
Member Author

Choose a reason for hiding this comment

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

Just to add more context for this commit (4958d95), which attempts to resolve #4973:

Unfortunately after testing both #5783 and #5784 together, focus still wasn't working as expected - EuiPopover was still taking focus away from EuiContextMenuPanel, and I don't think we can realistically ask every single consumer to always set <EuiPopover initialFocus={false} /> in conjunction with <EuiContextMenu />. I mean we can, but I think they'll forget to do it, and I'd rather use a workaround to ensure keyboard users aren't SOL.

I'm not a super huge fan of this DOM-heavy workaround but I think it's better than adding a hard-coded 350ms setTimeout to EuiContextMenuPanel 😬 Definitely open to other ideas/alternatives if folks can think of any!

Copy link
Member Author

Choose a reason for hiding this comment

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

Also in hindisght I realize this comment is hilarious in light of #5760 (comment). EuiPopover and EuiContextMenuPanel, name a more iconic duo... for stealing focus from one another and then requiring separate workarounds 😂

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

@cee-chen
Copy link
Member Author

jenkins test this

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

Copy link
Contributor

@1Copenut 1Copenut left a comment

Choose a reason for hiding this comment

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

I had one 👍 comment and one small text suggestion to change. No need for another review. LGTM!

const focusId = useGeneratedHtmlId();
useEffect(() => {
if (isPopoverOpen) {
document.getElementById(focusId).focus({ preventScroll: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 on the { preventScroll: true } object here. It's a good UX buffer.

upcoming_changelogs/5784.md Outdated Show resolved Hide resolved
Co-authored-by: Trevor Pierce <1Copenut@users.noreply.github.com>
@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

@cee-chen
Copy link
Member Author

jenkins test this

@kibanamachine
Copy link

Preview documentation changes for this PR: https://eui.elastic.co/pr_5784/

@cee-chen
Copy link
Member Author

I've QA'd all the components listed in the PR description and they're all working either the same as prod or as expected given these changes.

Does anyone have any thoughts/more feedback? Should I wait for @zuhairmahd to chime in?

@1Copenut
Copy link
Contributor

@constancecchen If you're okay waiting to Monday to merge, that should give Zuhair a chance to weigh in. I feel we're right on point with this solution.

@cee-chen
Copy link
Member Author

Absolutely!

@zuhairmahd
Copy link

zuhairmahd commented Apr 15, 2022

@constancecchen I plan to take a look at it Monday morning my time if not sooner, so it would be great if you could wait til Monday before merging. I'm seven time zones ahead of eastern, so you should have my feedback by morning your time.

@zuhairmahd
Copy link

The behavior of the screen reader text being interrupted appears in my testing to happen only with VoiceOver. NVDA and JAWS exhibited different behaviour. Essentially, they read as expected when the focus was moved to the first element, and when it was moved to the parent, they read the entire contents of the popover.
Having said this, I do agree that, overall, this approach is better than setting the focus on the first item for the reasons you stated. I would suggest, however, adding context specific text (if possible) advising what the user needs to do to get to the first focusable element (like pressing the tab key, using the arrows etc.). If there are no focusable elements, then the text should naturally not be spoken. This should at least prompt the user to initiate action to interact with the popover, letting them know that they don't have to listen to the entire content.

@cee-chen
Copy link
Member Author

Thanks Zuhair! If no objection I'll address screen reader keyboard instructions in a follow-up PR, to keep this one contained for easier debugging / identification if something goes awry in the next release/upgrade. I've made a detailed follow-up issue here: #5813

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants