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

WB-1638: Fix tabbing in popover #2159

Merged
merged 6 commits into from
Jan 5, 2024
Merged

WB-1638: Fix tabbing in popover #2159

merged 6 commits into from
Jan 5, 2024

Conversation

jandrade
Copy link
Member

@jandrade jandrade commented Jan 4, 2024

Summary:

There is a bug in the popover where tabbing out of the popover (while it
is still open) will cause the focus order to be incorrect. More
specifically, the focus will be returned to the trigger element, while
it should be returned to the next element in the tab order.

This is because the popover is not properly handling the case where the
trigger element is the last element in the tab order. This PR fixes that
by making all the focusable elements in the popover non-tabbable, and
then make them tabbable again when the popover gains focus.

For more reference on the actual error, see: https://khanacademy.atlassian.net/browse/CL-1259

TabTrapWhenPopupDisplayed.mp4

Issue: WB-1638

Test plan:

  • Navigate to /?path=/story/popover-popover--keyboard-navigation
  • Start tabbing through the elements in the story
  • Verify that the focus order is correct
  • Open the popover
  • Tab through the elements in the popover and verify that the focus
    order is still correct
  • Tab backwards through the elements in the popover and verify that the
    focus order is still correct (using shift+tab).
  • Close the popover and verify that the focus order is still correct
Screen.Recording.2024-01-04.at.2.30.37.PM.mov

There is a bug in the popover where tabbing out of the popover (while it
is still open) will cause the focus order to be incorrect. More
specifically, the focus will be returned to the trigger element, while
it should be returned to the next element in the tab order.

This is because the popover is not properly handling the case where the
trigger element is the last element in the tab order. This PR fixes that
by making all the focusable elements in the popover non-tabbable, and
then make them tabbable again when the popover gains focus.
@jandrade jandrade self-assigned this Jan 4, 2024
Copy link

changeset-bot bot commented Jan 4, 2024

🦋 Changeset detected

Latest commit: 4b70ebd

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@khanacademy/wonder-blocks-popover Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@khan-actions-bot
Copy link
Contributor

Gerald

Required Reviewers
  • @Khan/wonder-blocks for changes to .changeset/cyan-oranges-wait.md, __docs__/wonder-blocks-popover/popover.stories.tsx, packages/wonder-blocks-popover/src/components/focus-manager.tsx, packages/wonder-blocks-popover/src/components/__tests__/focus-manager.test.tsx, packages/wonder-blocks-popover/src/components/__tests__/popover.test.tsx

Don't want to be involved in this pull request? Comment #removeme and we won't notify you of further changes.

@khan-actions-bot khan-actions-bot requested a review from a team January 4, 2024 19:35
Copy link

codecov bot commented Jan 4, 2024

Codecov Report

Merging #2159 (4b70ebd) into main (f6f9a28) will increase coverage by 0.44%.
Report is 2 commits behind head on main.
The diff coverage is 98.71%.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2159      +/-   ##
==========================================
+ Coverage   94.91%   95.36%   +0.44%     
==========================================
  Files         245      245              
  Lines       28292    28442     +150     
  Branches     2335     2358      +23     
==========================================
+ Hits        26854    27124     +270     
+ Misses       1438     1318     -120     
Files Coverage Δ
...er-blocks-popover/src/components/focus-manager.tsx 96.86% <98.71%> (+1.39%) ⬆️

... and 32 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f6f9a28...4b70ebd. Read the comment docs.

Copy link
Contributor

github-actions bot commented Jan 4, 2024

Size Change: +556 B (+1%)

Total Size: 92.5 kB

Filename Size Change
packages/wonder-blocks-pill/dist/es/index.js 1.19 kB +159 B (+15%) ⚠️
packages/wonder-blocks-popover/dist/es/index.js 4.75 kB +374 B (+9%) 🔍
packages/wonder-blocks-theming/dist/es/index.js 1.23 kB +23 B (+2%)
ℹ️ View Unchanged
Filename Size
packages/wonder-blocks-accordion/dist/es/index.js 3.76 kB
packages/wonder-blocks-banner/dist/es/index.js 1.53 kB
packages/wonder-blocks-birthday-picker/dist/es/index.js 1.72 kB
packages/wonder-blocks-breadcrumbs/dist/es/index.js 1.13 kB
packages/wonder-blocks-button/dist/es/index.js 4.27 kB
packages/wonder-blocks-cell/dist/es/index.js 2.24 kB
packages/wonder-blocks-clickable/dist/es/index.js 3.24 kB
packages/wonder-blocks-color/dist/es/index.js 1.15 kB
packages/wonder-blocks-core/dist/es/index.js 3.7 kB
packages/wonder-blocks-data/dist/es/index.js 6.33 kB
packages/wonder-blocks-dropdown/dist/es/index.js 12.3 kB
packages/wonder-blocks-form/dist/es/index.js 5.34 kB
packages/wonder-blocks-grid/dist/es/index.js 1.36 kB
packages/wonder-blocks-i18n/dist/es/index.js 4.54 kB
packages/wonder-blocks-icon-button/dist/es/index.js 3.21 kB
packages/wonder-blocks-icon/dist/es/index.js 1.06 kB
packages/wonder-blocks-labeled-field/dist/es/index.js 72 B
packages/wonder-blocks-layout/dist/es/index.js 1.88 kB
packages/wonder-blocks-link/dist/es/index.js 2.54 kB
packages/wonder-blocks-modal/dist/es/index.js 5.53 kB
packages/wonder-blocks-progress-spinner/dist/es/index.js 1.51 kB
packages/wonder-blocks-search-field/dist/es/index.js 1.55 kB
packages/wonder-blocks-spacing/dist/es/index.js 158 B
packages/wonder-blocks-switch/dist/es/index.js 2.06 kB
packages/wonder-blocks-testing/dist/es/index.js 3.94 kB
packages/wonder-blocks-timing/dist/es/index.js 1.78 kB
packages/wonder-blocks-toolbar/dist/es/index.js 862 B
packages/wonder-blocks-tooltip/dist/es/index.js 5.05 kB
packages/wonder-blocks-typography/dist/es/index.js 1.49 kB

compressed-size-action

Copy link
Contributor

github-actions bot commented Jan 4, 2024

npm Snapshot: Published

🎉 Good news!! We've packaged up the latest commit from this PR (421b170) and published all packages with changesets to npm.

You can install the packages in webapp by running:

./services/static/dev/tools/deploy_wonder_blocks.js --tag="PR2159"

Packages can also be installed manually by running:

yarn add @khanacademy/wonder-blocks-<package-name>@PR2159

Copy link
Contributor

github-actions bot commented Jan 4, 2024

A new build was pushed to Chromatic! 🚀

https://5e1bf4b385e3fb0020b7073c-bnpynoxjar.chromatic.com/

Chromatic results:

Metric Total
Captured snapshots 319
Tests with visual changes 0
Total stories 403
Inherited (not captured) snapshots [TurboSnap] 0
Tests on the build 319

Copy link
Contributor

@kevinbarabash kevinbarabash left a comment

Choose a reason for hiding this comment

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

LGTM. A few minor feedbacks in the comments. Thanks for fixing this behavior. 🎉

Comment on lines +107 to +113
render(
<FocusManager anchorElement={anchorElementNode}>
<div>
<button>first focusable element inside</button>
</div>
</FocusManager>,
);
Copy link
Contributor

Choose a reason for hiding this comment

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

This is cool. I didn't realize we had tests that used multiple calls to render() in this way.

Comment on lines +169 to +190
// Act
// 1. focus on the previous element before the popover
userEvent.tab();

// 2. focus on the anchor element
userEvent.tab();

// 3. focus on focusable element inside the popover
userEvent.tab();

// 4. focus on the next focusable element outside the popover (this will
// be the first focusable element outside the popover)
userEvent.tab();

// The elements inside the focus manager should not be focusable anymore.
const focusableElementInside = screen.getByRole("button", {
name: "first focusable element inside",
});

// Assert
expect(focusableElementInside).toHaveAttribute("tabIndex", "-1");
});
Copy link
Contributor

Choose a reason for hiding this comment

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

praise: thanks for labelling what each userEvent method call is for.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does tabbing back into the popover making elements inside the popover focusable again?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's correct.... I make them focusable as soon as the focus goes back to the popover wrapper. One important thing to mention is that I just noticed an issue when the trigger element is the last focusable element in the DOM. I might need to completely change this approach if needed, so wanted to give a heads up.

Comment on lines 369 to 373
<PopoverContent
title="Popover title"
content="content"
actions={<Button>Button inside popover</Button>}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

question: are there multiple focusable items in this popover content? If not, could you add another button?

Comment on lines +464 to +477
// Focus on the first element outside the popover
userEvent.tab();
// open the popover by focusing on the trigger element
userEvent.tab();
userEvent.keyboard("{enter}");

// Wait for the popover to be open.
await screen.findByRole("dialog");

// Focus on the next element after the popover
userEvent.tab();

// Focus on the document body
userEvent.tab();
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (non-blocking): it might good to assert what some of these elements are in addition to the comments.

@@ -86,6 +87,11 @@ export default class FocusManager extends React.Component<Props> {
*/
focusableElementsInPopover: Array<HTMLElement> = [];
Copy link
Contributor

Choose a reason for hiding this comment

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

The name of this field is a little confusing because we're setting these elements to be non-focusable when the focus exits the popover or whatever.

suggestion: consider renaming this to something like elementsThatCanBeFocusable or something like that to avoid confusion as to whether the elements in the array are currently focusable or not.

@jandrade jandrade merged commit 163cfca into main Jan 5, 2024
16 checks passed
@jandrade jandrade deleted the popover-tab-fix branch January 5, 2024 22:25
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.

4 participants