Skip to content
This repository has been archived by the owner on Dec 19, 2024. It is now read-only.

Focus trapping logic doesn't work with non-Polymer elements #282

Open
1 of 8 tasks
galanovnick opened this issue Jun 7, 2018 · 5 comments
Open
1 of 8 tasks

Focus trapping logic doesn't work with non-Polymer elements #282

galanovnick opened this issue Jun 7, 2018 · 5 comments

Comments

@galanovnick
Copy link

galanovnick commented Jun 7, 2018

Description

Focus traping logic is not compatible with elements that are not extending PolymerElement. Moreover, it won't work properly even if at least one of parent shadow roots is not a Polymer element.

As far as I can tell the main reason for that is because of the way deepActiveElement getter of the iron-overlay-manager is implemented. It expects that the shadow root is accessible via root property.
LitElement, for example, contains the shadow root in the _root property, so the deepActiveElement won't get a proper active element for it.

Suggestion: why not use shadowRoot to access the shadow root instead?

Live demo

https://glitch.com/edit/#!/focus-trap-issue

Browsers Affected

  • Chrome
  • Firefox
  • Safari 9
  • Safari 8
  • Safari 7
  • Edge
  • IE 11
  • IE 10
@valdrinkoshi
Copy link
Member

valdrinkoshi commented Jun 8, 2018

Yeah, using active.root || active.shadowRoot in deepActiveElement should be fine.

In any case, beware that focus trapping doesn't work well when there are nodes with tabindex > 0 - see more details in #241 and in this comment in the code

// In ShadowDOM v1, tab order is affected by the order of distrubution.
// E.g. getTabbableNodes(#root) in ShadowDOM v1 should return [#A, #B];
// in ShadowDOM v0 tab order is not affected by the distrubution order,
// in fact getTabbableNodes(#root) returns [#B, #A].
// <div id="root">
// <!-- shadow -->
// <slot name="a">
// <slot name="b">
// <!-- /shadow -->
// <input id="A" slot="a">
// <input id="B" slot="b" tabindex="1">
// </div>

@web-padawan
Copy link

@valdrinkoshi there is one more place where using .root strikes for vanilla Web Components:

children = dom(element.root || element).children;

I'm up to submit a PR but we would need this fix for both Polymer 2 and 3 (so there will be 2 PRs)

@yosilevy
Copy link

Is this fixed/coming?
I think that line should read:
children = dom(element.root || element.shadowRoot || element).children;

@yosilevy
Copy link

Submitted PR #292

@KeithHenry
Copy link

I have a workaround for this... it is horrible, but I doubt they're ever going to fix this given the age of this issue.

Basically:

  • Add a getSpoofRootCollection that checks .children and .shadowRoot.
  • Wrap everything it returns in a Proxy that replaces the root getter.
  • Use Object.defineProperty to add a root property to the content added to the <paper-dialog> (or whatever).
/** Looking for focus elements we want any shadow DOM children and any light DOM childen
 *  Wrap all of them in the proxy so Polymer's recursion reaches nested web components */
function getSpoofRootCollection(
    parent: Element,
    proxyHandler: ProxyHandler<Element>) {
    const children = parent.shadowRoot ?
        [...parent.shadowRoot.children, ...parent.children] :
        [...parent.children];

    return children.map(c => new Proxy<Element>(c, proxyHandler));
}

/** Proxy that echoes everything except .root or .children, which it gets from the shadow root and wraps in the same proxy */
class SpoofPolymerRoot
    implements ProxyHandler<Element> {

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    get?<K extends keyof Element>(target: Element, p: K): any {
        if (typeof p === 'symbol')
            return target[p];

        if (p === 'children' ||
            p as any === 'root')
            return getSpoofRootCollection(target, this);

        return target[p];
    }
}

/** Hack fix workaround https://github.com/PolymerElements/iron-overlay-behavior/issues/282
 *  Use a proxy to dummy the root property, which Polymer's deepActiveElement uses to find shadow focus inputs */
function polymerRoot(content: HTMLElement) {
    // Ensure that we only get into this proxy if the recursion down the tree starts with .root
    Object.defineProperty(content, 'root', { get: () => getSpoofRootCollection(content, new SpoofPolymerRoot()) });
    return content; 
}

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants