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

[base] Removes invisible tabbable elements from TrapFocus component #33543

Merged
merged 6 commits into from Aug 10, 2022

Conversation

EthanStandel
Copy link
Contributor

@EthanStandel EthanStandel commented Jul 17, 2022

Problem description

Fix #33380

Solution

Setting tabIndex={open ? 0 : -1} on sentinelStart and sentinelEnd elements so that they're only tabbable when the TrapFocus open prop is true. Thus removing the awkward experience where there are invisible tabbable elements.

Preview: https://deploy-preview-33543--material-ui.netlify.app/base/react-trap-focus/

@EthanStandel EthanStandel changed the title [base] Removes invisible tabbable elements from TrapFocus component (fixes #33380) [base] Removes invisible tabbable elements from TrapFocus component Jul 17, 2022
@mui-bot
Copy link

mui-bot commented Jul 17, 2022

Details of bundle changes

Generated by 🚫 dangerJS against bf6dd7d

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Jul 22, 2022
@@ -283,6 +283,28 @@ describe('<TrapFocus />', () => {
expect(screen.getByTestId('root')).toHaveFocus();
});

it('does not allow sentinelStart or sentinelEnd to be tabbable until open={true}', () => {
Copy link
Member

Choose a reason for hiding this comment

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

Instead of testing the tabindex value, I would suggest testing the behavior, as the implementation may change in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mnajdova done! However, to add real tabbability testing, I needed to add @testing-library/user-event which has an awkward naming conflict with test/utils/userEvent.ts and I don't know if you have a preferred solution for that situation.

@mnajdova
Copy link
Member

cc @michaldudak would be great to have a second opinion on the changes. @samuelsycamore for reviewing the copywriting.

@michaldudak
Copy link
Member

I haven't thought of such a use case before, but these changes look OK to me.
As a follow-up, I think we'd have to rename the open prop. enabled would make more sense, especially for such cases.

@EthanStandel
Copy link
Contributor Author

EthanStandel commented Jul 28, 2022

When I tried to resync the master branch of my fork the PR autoclosed! Reopening!

@EthanStandel EthanStandel reopened this Jul 28, 2022
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Jul 28, 2022
@EthanStandel
Copy link
Contributor Author

EthanStandel commented Jul 28, 2022

@michaldudak I really like the idea of the prop being enabled, but I also know that change could be wide reaching. You're not recommending that change for this PR, are you?

@michaldudak
Copy link
Member

No, not at all, this should be a separate PR that we can handle on our own.

Copy link
Member

@samuelsycamore samuelsycamore left a comment

Choose a reason for hiding this comment

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

Solid addition! I just have a couple suggestions for grammar and style in the docs.

docs/data/base/components/trap-focus/trap-focus.md Outdated Show resolved Hide resolved

## Using a toggle inside the trap

The most common use case for the `TrapFocus` component is to maintain focus within a [`Modal`](/base/react-modal/) component which is entire separete from the element which would open the modal. However, there are use cases where you may want to have a toggle button for the `open` prop of the `TrapFocus` component which is stored inside that component.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
The most common use case for the `TrapFocus` component is to maintain focus within a [`Modal`](/base/react-modal/) component which is entire separete from the element which would open the modal. However, there are use cases where you may want to have a toggle button for the `open` prop of the `TrapFocus` component which is stored inside that component.
The most common use case for the `TrapFocus` component is to maintain focus within a [modal](/base/react-modal/) component that is entirely separate from the element that opens the modal.
But you can also create a toggle button for the `open` prop of the `TrapFocus` component that is stored inside of the component itself, as shown in the following demo:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done, except for one thing. I think it should be "Modal" and not "modal" because directly above this section, the Portal component is referenced in this exact same way. 😄

@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged label Aug 1, 2022
@EthanStandel
Copy link
Contributor Author

Same deal as before. Next time I won't open a PR off a fork from master. I'd change to another branch now but it seems like I'd have to open a new PR.

@EthanStandel EthanStandel reopened this Aug 3, 2022
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged label Aug 3, 2022
@EthanStandel
Copy link
Contributor Author

Requesting a re-review from @samuelsycamore & @mnajdova ! 😄

Is that how I should do that? I legitimately don't know.

Copy link
Member

@mnajdova mnajdova left a comment

Choose a reason for hiding this comment

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

This is a really great first contribution to MUI @EthanStandel 👌 I look forward for more of these 😄

@mnajdova mnajdova merged commit f703696 into mui:master Aug 10, 2022
@oliviertassinari oliviertassinari added component: FocusTrap The React component. bug 🐛 Something doesn't work labels Aug 17, 2022
Copy link
Member

@oliviertassinari oliviertassinari left a comment

Choose a reason for hiding this comment

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

@EthanStandel Thanks for working on this. Should we close #33380? I assume it's fixed.

@@ -4,6 +4,7 @@ import { expect } from 'chai';
import { act, createRenderer, screen } from 'test/utils';
import TrapFocus from '@mui/base/TrapFocus';
import Portal from '@mui/base/Portal';
import userEvent from '@testing-library/user-event';
Copy link
Member

Choose a reason for hiding this comment

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

AFAIK @eps1lon has been pushing toward not using this plugin, I would be in favor of doing such as well. The logic that implements userEvent.tab(); looks brittle, I don't think that we should trust it for tests. Instead, we could continue to use the real browser tab as the existing tests are doing in:

describe('<TrapFocus />', () => {

How about we do a follow-up to remove it?

Suggested change
import userEvent from '@testing-library/user-event';

Copy link
Member

@eps1lon eps1lon Aug 17, 2022

Choose a reason for hiding this comment

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

Didn't we already have Playwright tests for TrapFocus or any other more involved user-interaction tests? I would still recomend using that approach over using user-event. Especiallly for a component library. Unless you have good reason for using JSDOM + replaying events in which case I'd be curious to hear why you'd prefer JSDOm + replaying events over browser tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd be fine with moving the meaning of this test to Playwright and removing the new dependency. If I'm to be honest, I didn't notice the e2e tests initially 😅

import Stack from '@mui/material/Stack';
import TrapFocus from '@mui/base/TrapFocus';

export default function BasicTrapFocus() {
Copy link
Member

Choose a reason for hiding this comment

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

The name doesn't match

Suggested change
export default function BasicTrapFocus() {
export default function ContainedToggleTrappedFocus() {

Comment on lines +10 to +16
<Box
sx={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
}}
>
Copy link
Member

Choose a reason for hiding this comment

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

Unnecessary, we can simplify the demo without

Suggested change
<Box
sx={{
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
}}
>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was going for consistency with the other demos

e.g. DisableEnforceFocus, LazyTrapFocus, PortalTrapFocus

Would you suggest that they all be changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, nevermind I went to do that and then immediately saw why it's necessary in those cases and not this one!

Comment on lines +18 to +19
<Stack>
<Stack alignItems="center">
Copy link
Member

Choose a reason for hiding this comment

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

It would have been simpler like this, we don't need two Stack.

Suggested change
<Stack>
<Stack alignItems="center">
<Stack alignItems="center" spacing={2}>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That makes sense!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: FocusTrap The React component.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[FocusTrap] sentinelStart & sentinelEnd create an awkward experience
7 participants