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

Modal: Fix loss of focus when clicking outside #52653

Merged
merged 4 commits into from
Jul 25, 2023
Merged

Conversation

stokesman
Copy link
Contributor

@stokesman stokesman commented Jul 14, 2023

What?

  • Ensures that return of focus from the Modal component works when it’s dismissed by clicking outside.
  • Adds a unit test to guard regressions.

Why?

Focus loss is troublesome in various ways. This fixes #51722.

How?

Uses a pointer event handler to preempt the onFocusOutside handling for dismissing the modal when clicking outside.

Testing Instructions

  1. Start in the Post Editor
  2. Open the Options menu
  3. Open the Preferences modal
  4. Click outside the Preferences modal to dismiss it
  5. Observe the Options menu is still open and focus returned to the “Preferences” button.

This can be generalized to any modal/confirmation dialog in any of the block editors.

Testing Instructions for Keyboard

After step 4 of the above testing instructions you can try using editor keyboard shortcuts and observe they do work.

Unit Test Testing Instructions

  1. Check out the first commit on this branch git checkout 1aaa194
  2. npm run test:unit -- modal/test/index.tsx
  3. Observe the new test fails
  4. git switch -
  5. Repeat step 2.
  6. Observe the new test passes

@stokesman stokesman added [Type] Bug An existing feature does not function as intended [Package] Components /packages/components [a11y] Keyboard & Focus labels Jul 14, 2023
@github-actions
Copy link

github-actions bot commented Jul 14, 2023

Flaky tests detected in 2750830.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/5660909745
📝 Reported issues:

Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

This works as described, but it leaves me wondering why useFocusOutside() was necessary in the first place. Do you know? Since tabbing is constrained to the modal, wouldn't a click on the overlay be the only way that focus would move outside the modal?

I've even noticed that only relying on useFocusOutside to detect clicks on the overlay doesn't even work reliably when iframes are involved. For example in the Storybook in Canvas mode, if you open the modal, click outside the story iframe, then click on the overlay, it doesn't close.

It seems like a click handler on the overlay like in this PR is better and sufficient.

@stokesman
Copy link
Contributor Author

stokesman commented Jul 22, 2023

Thanks for having a look Lena 🙇

it leaves me wondering why useFocusOutside() was necessary in the first place. Do you know?

🤓 As best I can tell it never was technically necessary. The component had an overlay element and constrained tabbing from the start #6261 so you'd think it might have used a simple click handler on the overlay. Instead it used a more generalized approach provided by an HOC of an external library. Later that was replaced by the withFocusOutside HOC #16878 and of course later the hook equivalent.

Since tabbing is constrained to the modal, wouldn't a click on the overlay be the only way that focus would move outside the modal?

Practically speaking yes. Focus can be moved programmatically but I don't think that was intentionally supported or relied upon.

It seems like a click handler on the overlay like in this PR is better and sufficient.

Well, yeah that’s #51602 which removes the hook from the Modal. That stalled mostly because there’s one case of unintentional reliance on closing the modal when focus moves outside programmatically. Also, there is some desire to update useFocusOutside so it could support iframes so that useDialog and its consumers would not have the iframe issue.

@priethor priethor added [Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). and removed [a11y] Keyboard & Focus labels Jul 24, 2023
Copy link
Member

@mirka mirka left a comment

Choose a reason for hiding this comment

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

Thanks for explaining how this relates to the other PRs 🙏 I think it makes sense to merge this to fix the immediate problem with the focus returning then!

@@ -179,6 +188,9 @@ function UnforwardedModal(
overlayClassName
) }
onKeyDown={ handleEscapeKeyDown }
onPointerDown={
Copy link
Member

Choose a reason for hiding this comment

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

Can we add a TODO comment here? Something to the effect of "Reconcile with useFocusOutside" and "Ideally this should be an onClick".

Copy link
Contributor Author

@stokesman stokesman Jul 24, 2023

Choose a reason for hiding this comment

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

I gave it my best and hopefully it captures what you had in mind. At least regarding the first part. I left out the bit about preferring onClick. That could provide some nuance to the interaction but I think handling pointerdown will still be necessary. That’s because if the default isn't prevented from pointerdown focus will move and be left unreturned by useFocusReturn.

Copy link
Member

Choose a reason for hiding this comment

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

It does 👍 I'm mostly concerned that we're treating a mere pointerdown as a click intent, which isn't technically true. For example I sometimes mousedown on a button but change my mind and drag away so it doesn't go through as a click.

But I guess we could even resolve that right now, if we just event.preventDefault() on the pointerdown, and wait until the click event to actually call onRequestClose().

Copy link
Contributor Author

@stokesman stokesman Jul 25, 2023

Choose a reason for hiding this comment

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

Yep, that's just what I meant about nuance in the interaction. I do the same sometimes. To keep current behavior and this PR focused, I'd left it as is.

Since you agree it should be resolved I went ahead and tried it. It's not quite as simple as we likely both thought. It's also an opportunity to try a bit of further nuance as suggested by Aki #51602 (comment). So I've got a branch for it and will make it a separate PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

…and that PR #52994

@stokesman stokesman enabled auto-merge (squash) July 25, 2023 19:25
@stokesman stokesman merged commit eea9e52 into trunk Jul 25, 2023
50 checks passed
@stokesman stokesman deleted the fix/modal-focus-return branch July 25, 2023 21:33
@github-actions github-actions bot added this to the Gutenberg 16.4 milestone Jul 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Focus] Accessibility (a11y) Changes that impact accessibility and need corresponding review (e.g. markup changes). [Package] Components /packages/components [Type] Bug An existing feature does not function as intended
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Botch of focus return when dismissing Modal by clicking outside
3 participants