Skip to content

Conversation

@majornista
Copy link
Collaborator

@majornista majornista commented Oct 13, 2021

Restore focus to previous nodeToRestore when expected nodeToRestore is no longer present, as with a Menu that opens a Dialog.

Closes #2444

✅ Pull Request Checklist:

📝 Test Instructions:

  1. Open https://reactspectrum.blob.core.windows.net/reactspectrum/4b09194340f607fab1c56739c71f99e8a7d58337/storybook/index.html?path=/story/dialogcontainer--in-a-menu

  2. Click or navigate to the "Open menu" button and press Enter or Space to expand the menu.

  3. Click or arrow down to and activate the "Open dialog..." menu item.

  4. Press Esc key to close the Dialog.

  5. Focus should be restored to the "Open menu" button.

  6. Open https://reactspectrum.blob.core.windows.net/reactspectrum/4b09194340f607fab1c56739c71f99e8a7d58337/storybook/index.html?path=/story/dialogcontainer--nested-dialog-containers

  7. Click or navigate to the "Open menu" button and press Enter or Space to expand the menu.

  8. Click or arrow down to and activate the "Do this..." or "Do that..." menu item.

  9. Click or activate the "Do that" or "Do this" button to replace the Dialog with the nested Dialog.

  10. Repeat step 4 a few times for good measure.

  11. Press Esc key, or use the "Dismiss" button to close the Dialog.

  12. Focus should be restored to the "Open menu" button.

Verify that https://reactspectrum.blob.core.windows.net/reactspectrum/4b09194340f607fab1c56739c71f99e8a7d58337/storybook/index.html?path=/story/focusscope--keyboard-navigation and https://reactspectrum.blob.core.windows.net/reactspectrum/4b09194340f607fab1c56739c71f99e8a7d58337/storybook/index.html?path=/story/focusscope--keyboard-navigation-inside-portal manage and restore focus appropriately.

🧢 Your Project:

Adobe/Accessibility

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

Per #2444:

Restore focus to previous nodeToRestore when expected nodeToRestore is no longer present, as with a Menu that opens a Dialog.

fix(#2444): allow restoreFocus prop to specify a ref to receive focus

fix(#2444): update Dialog to support restoreFocus prop, add stories and tests
@majornista majornista force-pushed the fix-useRestoreFocus-dialog-from-menu branch from 950b16d to a328b77 Compare March 4, 2022 22:19
@adobe-bot

This comment was marked as outdated.

@majornista majornista requested a review from LFDanLu March 4, 2022 22:28
@adobe-bot

This comment was marked as outdated.

@adobe-bot

This comment was marked as outdated.

@adobe-bot
Copy link

triggerPress(item3);
expect(onSelectionChange).toHaveBeenCalledTimes(1);
act(() => jest.runAllTimers());
act(() => jest.runAllTimers());
Copy link
Member

Choose a reason for hiding this comment

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

note: this test used to be technically wrong, after the first runAllTimers here, focus was on the body, we never ran the queued restore focus which is queued up as a result of the render contained in the act.

Because we now have a stack of nodes to restore focus to, we need to make sure to run our cleanup in the raf to clear that stack out, otherwise we have extra elements in the stack that will cause problems further down in the test

it's the same for all other tests here where it's runalltimersx2

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So to clarify, where I now have act(() => {jest.runAllTimers();}); twice is appropriate for this case? I saw instances where runAllTimers was called twice, in ActionBar.test and Picker.test, and figured I'd give it a try to fix some failing tests here. Sure enough, it worked.

<DialogTrigger type="popover" isOpen={isPopoverOpen} onOpenChange={setPopoverOpen}>
<ActionButton>Open popover</ActionButton>
<Dialog>
<Dialog isDismissable={false}>
Copy link
Member

Choose a reason for hiding this comment

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

these should already be false? what are these changes for?

Copy link
Collaborator Author

@majornista majornista Jun 8, 2022

Choose a reason for hiding this comment

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

The responsive Popover hides the footer containing the "Open modal" button when rendered as a Dialog which defaults to isDismissible={true}, making it difficult to test if your screen width is too narrow. I should probably open a separate issue for this.

}
return;
}
// Otherwise try to focus the nodeToRestore, and clean up
Copy link
Member

Choose a reason for hiding this comment

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

i don't quite understand this case, can you explain when we'd hit this?

Copy link
Collaborator Author

@majornista majornista Jun 8, 2022

Choose a reason for hiding this comment

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

We have to account for FocusScopes where contain={false}, as @LFDanLu pointed out in this comment: #2445 (review)

I'll need to dig into it a some more but the following story breaks in the following case:

  1. Open the dialog with you keyboard
  2. Open several layers of nested dialogs via the "open dialog" buttons in each new dialog
  3. Shift + Tab back to the first "dialog" you opened and hit the "close" button with the enter key
  4. Note that focus is lost instead of being restored to the first "open dialog" button

The above doesn't reproduce in an earlier version of this PR: https://reactspectrum.blob.core.windows.net/reactspectrum/c465126e8286623894aca7b692cb096121643211/storybook/index.html?path=/story/focusscope--keyboard-navigation-no-contain

EDIT:
Seems to be due to something in a2fad80

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We iterate backward through the nodeToRestoreArray using pop() only when the nodeToRestore for the closing scope is the last item in the array.

@adobe-bot

This comment was marked as outdated.

@snowystinger
Copy link
Member

I looked into this with @devongovett , we came to the conclusion a stack, while close, isn't quite right. We want to try refactoring our Map scopes (which is currently a poor man's tree) into a real, doubly linked, tree. This way we can have sibling focus scopes and they won't try to restore to each other depending on the order they were added/removed. We also noticed that our Map isn't always up to date currently.

@adobe-bot
Copy link

@majornista
Copy link
Collaborator Author

majornista commented Jun 9, 2022

I looked into this with @devongovett , we came to the conclusion a stack, while close, isn't quite right. We want to try refactoring our Map scopes (which is currently a poor man's tree) into a real, doubly linked, tree. This way we can have sibling focus scopes and they won't try to restore to each other depending on the order they were added/removed. We also noticed that our Map isn't always up to date currently.

@devongovett and @snowystinger While I agree that refactoring the restoreFocus behavior in FocusScopes around the composite pattern is desirable, please consider that this PR has been open since October 2021, ~7 months. Provided it fixes the problem and does not introduce breaking changes, I think it would be better to accept the PR and address the more significant refactoring as a separate issue.

@adobe-bot
Copy link

@adobe-bot
Copy link

@adobe-bot
Copy link

@snowystinger snowystinger mentioned this pull request Jul 20, 2022
5 tasks
@adobe-bot
Copy link

@adobe-bot
Copy link

@devongovett
Copy link
Member

Fixed by #3323

@devongovett devongovett closed this Aug 8, 2022
@devongovett devongovett deleted the fix-useRestoreFocus-dialog-from-menu branch August 8, 2022 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FocusScope fails to restore focus when the element to restore has unmounted

6 participants