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

feat(react): remove focus-trap-react replacing with internally managed focus traps #1730

Merged
merged 26 commits into from
Dec 18, 2024

Conversation

scurker
Copy link
Member

@scurker scurker commented Oct 18, 2024

closes #1703

Any existing components that were utilizing focus-trap-react should now be using the internal useFocusTrap hook. Some small refactors had to take place to accommodate the new hook, but otherwise the functionality should be identical.

Copy link

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-1730.d15792l1n26ww3.amplifyapp.com

@scurker scurker marked this pull request as ready for review November 22, 2024 20:22
@scurker scurker requested review from a team as code owners November 22, 2024 20:22
@scurker scurker changed the title refactor(react): remove focus-trap-react replacing with internally managed focus traps feat(react): remove focus-trap-react replacing with internally managed focus traps Nov 22, 2024
if (!show || !dialogRef.current) return;

isolatorRef.current = new AriaIsolate(dialogRef.current);
setTimeout(focusHeading);
Copy link
Member

Choose a reason for hiding this comment

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

nit - this use of setTimeout could probably benefit from a comment (I assume this is to wait for the dialog to be rendered, if so there may be a way to avoid setTimeout use)

Copy link
Member Author

Choose a reason for hiding this comment

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

I agree, but I wish I remembered why as it's been like this since it's initial implementation:

this.attachEventListeners();
this.attachIsolator(() => setTimeout(this.focusHeading));

I'm taking an educated guess that it's queuing the focus to be on the next tick to prevent focus conflicts. We can add that as a comment if you feel that's useful?

schne324
schne324 previously approved these changes Dec 10, 2024
Copy link
Member

@schne324 schne324 left a comment

Choose a reason for hiding this comment

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

Looks good to me. I left a couple of nits. I'm also wondering if we should document anything about not using iframes within things that need focus traps as it could mess with the focus trap code (if, for example, the first tab stop in a dialog is within an iframe)

Co-authored-by: Harris Schneiderman <schne324@gmail.com>
schne324
schne324 previously approved these changes Dec 10, 2024
@scurker
Copy link
Member Author

scurker commented Dec 10, 2024

Looks good to me. I left a couple of nits. I'm also wondering if we should document anything about not using iframes within things that need focus traps as it could mess with the focus trap code (if, for example, the first tab stop in a dialog is within an iframe)

I'm not following 100%.

I just tested an alert with an iframe inside of it and it works as expected (since we focus the header by default) and focus works exactly as you would expect. Is there a particular edge case scenario you would be looking to cover?

@schne324
Copy link
Member

schne324 commented Dec 18, 2024

I'm not following 100%.

I just tested an alert with an iframe inside of it and it works as expected (since we focus the header by default) and focus works exactly as you would expect. Is there a particular edge case scenario you would be looking to cover?

I was making an assumption that funky things could happen because we have this code:

    const focusableElements = Array.from(
      targetElement?.querySelectorAll(focusable) || []
    ) as HTMLElement[];

and any focusable elements that are within an iframe would not be captured in that focusableElements array.

For example: https://github.com/dequelabs/cauldron/pull/1730/files#diff-8527069e35f9293283f4a8860691c088d4423c2addf12b5670303aceeff92c96R111-R117

   if (focusableElements.length && eventTarget === startGuard) {
      focusableElements.reverse()[0]?.focus();
      return;
    } else if (focusableElements.length && eventTarget === endGuard) {
      focusableElements[0]?.focus();
      return;
    }

the first and last elements in focusable could be, in reality, within an iframe but in this array they are not captured at all

schne324
schne324 previously approved these changes Dec 18, 2024
@scurker
Copy link
Member Author

scurker commented Dec 18, 2024

I was making an assumption that funky things could happen because we have this code:

[...]

and any focusable elements that are within an iframe would not be captured in that focusableElements array.

[...]

the first and last elements in focusable could be, in reality, within an iframe but in this array they are not captured at all

Ah right. With that code there, the focusable elements would only really be applicable for the first or last element (to determine where the next focus needs to go). In my test case I still had surrounding elements within the iframe and it works as expected. So this would be a bit of an edge case where the last or first focusable element was within an iframe.

I feel that's an acceptable outcome for us, but I'm not sure how we want to take note of it. The majoritive use case would be working within a single document context.

@scurker scurker merged commit 775bed0 into develop Dec 18, 2024
8 checks passed
@scurker scurker deleted the focus-trap-hook branch December 18, 2024 21:11
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.

Replace react-focus-trap with internally managed Focus Management
2 participants