Skip to content

FocusScope not working when used inside shadowRoot #1472

@sventschui

Description

@sventschui

🐛 Bug Report

When used inside a shadowRoot the FocusScope currently has some issues:

  • Focused element will not be determined correctly as document.activeElement will refer to the parent node of the shadowRoot the currently focused element is in. The activeElement needs to be determined recursively traversing (open) shadowRoots (document.activeElement.shadowRoot.activeElement.shadowRoot.activeElement......). This leads to invalid detection whether an element is in scope and also restores focus wrongly.
  • e.target in focus/blur events is referring to the parent node of the shadowRoot of the focused element. e.composedPath()[0] can be used to determine the actually focused element inside the custom element. ('composedPath' in e ? e.composedPath()[0] : e.target)
  • Chrome: TypeError: Cannot read property 'focus' of null in the onBlur of useFocusContainment due to e.target being null inside the rAF (not sure this is really related to custom element / shadowRoot or a general bug
  • Safari: Focus is looped through focusable elements

I patched this in our libs using patch-package so I'm happy to file a PR if you'd like to support custom elements. I'm not sure how this affects other packages of the spectrum ecosystem and thus wanted to check in prior to opening a PR.

🤔 Expected Behavior

FocusRing can be used with custom elements / shadowRoots

😯 Current Behavior

See Bug Report above

💁 Possible Solution

  • Instead of document.activeElement use activeElement()
function activeElement() {
    let activeElement = document.activeElement;
    while (activeElement && activeElement.shadowRoot && activeElement.shadowRoot.activeElement) {
        activeElement = activeElement.shadowRoot.activeElement;
    }
    return activeElement;
}
  • Instead of e.target use 'composedPath' in e ? e.composedPath()[0] : e.target

🔦 Context

In our Microfrontend Framework we encapsulate modules inside custom elements. To escape them we use a portal-root custom element in the body where all modals will be rendered into. Inside this we use FocusScope to contain focus.

💻 Code Sample

https://codesandbox.io/s/vigilant-hofstadter-3wf4i?file=/src/index.js

🌍 Your Environment

Software Version(s)
@react-aria/focus 3.2.3
Browser Chrome 87.0.4280.141 / Safari 14.0.2
Operating System Mac OS 10.15

🧢 Your Company/Team

Undisclosed

🕷 Tracking Issue (optional)

n/a

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions