Material-ui: [ButtonBase] Shadow Root activeElement resolution

Created on 30 Oct 2018  路  12Comments  路  Source: mui-org/material-ui

When using custom elements with a shadowRoot, the focusVisibleClassName is applied to the outer-most custom element, instead of the actual element that is focused within the shadow DOM.

Expected Behavior

focusVisibleClassName should be added to deepest-level activeElement when a shadowRoot is present.

Current Behavior

focusVisibleClassName is applied to the outer-most custom element that is returned from document.activeElement.

Possible Solution

Traverse activeElement targets that have a shadowRoot to find the lowest level active element for a given component; or just return document.activeElement if no shadowRoot is present.

Following solution appears to work as expected:

diff --git a/packages/material-ui/src/ButtonBase/focusVisible.js b/packages/material-ui/src/ButtonBase/focusVisible.js
index d10c1a00d..462376f72 100644
--- a/packages/material-ui/src/ButtonBase/focusVisible.js
+++ b/packages/material-ui/src/ButtonBase/focusVisible.js
@@ -16,10 +16,11 @@ export function detectFocusVisible(instance, element, callback, attempt = 1) {

   instance.focusVisibleTimeout = setTimeout(() => {
     const doc = ownerDocument(element);
+    const activeElement = findActiveElement(doc);

     if (
       internal.focusKeyPressed &&
-      (doc.activeElement === element || element.contains(doc.activeElement))
+      (activeElement === element || element.contains(activeElement))
     ) {
       callback();
     } else if (attempt < instance.focusVisibleMaxCheckTimes) {
@@ -28,6 +29,18 @@ export function detectFocusVisible(instance, element, callback, attempt = 1) {
   }, instance.focusVisibleCheckTime);
 }

+function findActiveElement(doc) {
+  let activeElement = doc.activeElement;
+  while (
+    activeElement &&
+    activeElement.shadowRoot &&
+    activeElement.shadowRoot.activeElement
+  ) {
+    activeElement = activeElement.shadowRoot.activeElement;
+  }
+  return activeElement;
+}
+
 const FOCUS_KEYS = ['tab', 'enter', 'space', 'esc', 'up', 'down', 'left', 'right'];

 function isFocusKey(event) {
ButtonBase enhancement good first issue

Most helpful comment

Actually already got something working locally. I can just tidy it up and add some tests.

All 12 comments

Does :focus-visible also apply to elements within a shadow root?

Does :focus-visible also apply to elements within a shadow root?

This is potentially handled by delegatesFocus? http://w3c.github.io/webcomponents/spec/shadow/#widl-ShadowRootInit-delegatesFocus

Although, a similar issue to this one was raised on the WICG/focus-visible polyfill @ https://github.com/WICG/focus-visible/issues/28

Do you have a reproduction example? What the use case?

Sure. https://codesandbox.io/s/8lxr89x2p2
You can see that tabbing only applies focus to the elements not inside a shadow root.
With the above changes to the activeElement code, it works as expected.

@jaipe Well done with the JSS setup for shadow DOM. In the documentation, I wish we had a demo with an iframe, a popup, and a shadow dom.

The fix looks good enough to me. There is a bundle size overhead concern but the fact it's usable within a shadow DOM is a convincing argument. @eps1lon What do you think?

I wasn't very convinced at first but native buttons do gain a visible focus state within a shadow root so I'd say we improve the polyfill.

Can I take this one??

Actually already got something working locally. I can just tidy it up and add some tests.

@jaipe I'm using your codesandbox example example to render an the AppBar component

However, I cannot get the click events to work properly. Have you found any solution to this?

@jaipe I'm using your codesandbox example example to render an the AppBar component

However, I cannot get the click events to work properly. Have you found any solution to this?

Can you send an updated code sandbox example?

Hi, here is a fork of your project with the aforementioned component included. In the demo you can see the click's won't work. Not sure how to fix them. I read about it a little (shadow root blocks outer events) but not sure how to fix it.

Hey. You'll probably need something like this, so the events re-target and React knows about them. React listens at the document level for events (yet events don't bubble out of the shadow root by default).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mnajdova picture mnajdova  路  105Comments

Bessonov picture Bessonov  路  93Comments

HZooly picture HZooly  路  63Comments

tleunen picture tleunen  路  59Comments

sjstebbins picture sjstebbins  路  71Comments