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

[ButtonBase] Shadow Root activeElement resolution #13447

Closed
JaiPe opened this issue Oct 30, 2018 · 12 comments
Closed

[ButtonBase] Shadow Root activeElement resolution #13447

JaiPe opened this issue Oct 30, 2018 · 12 comments
Labels
component: ButtonBase The React component. good first issue Great for first contributions. Enable to learn the contribution process. new feature New feature or request

Comments

@JaiPe
Copy link
Contributor

JaiPe commented Oct 30, 2018

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) {
@eps1lon
Copy link
Member

eps1lon commented Oct 30, 2018

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

@JaiPe
Copy link
Contributor Author

JaiPe commented Oct 30, 2018

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 @ WICG/focus-visible#28

@oliviertassinari
Copy link
Member

oliviertassinari commented Oct 30, 2018

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

@JaiPe
Copy link
Contributor Author

JaiPe commented Oct 30, 2018

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.

@oliviertassinari
Copy link
Member

oliviertassinari commented Oct 30, 2018

@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?

@oliviertassinari oliviertassinari added new feature New feature or request component: ButtonBase The React component. labels Oct 30, 2018
@eps1lon
Copy link
Member

eps1lon commented Oct 30, 2018

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.

@oliviertassinari oliviertassinari added the good first issue Great for first contributions. Enable to learn the contribution process. label Oct 30, 2018
@logic0319
Copy link

Can I take this one??

@JaiPe
Copy link
Contributor Author

JaiPe commented Oct 31, 2018

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

JaiPe added a commit to JaiPe/material-ui that referenced this issue Oct 31, 2018
The focusVisible polyfill for ButtonBase components was not resolving
targets inside a shadowRoot. Now the activeElement for nested
shadowRoots is resolved to add focus visible classes to targets in the
shadow DOM.
JaiPe added a commit to JaiPe/material-ui that referenced this issue Nov 1, 2018
The focusVisible polyfill for ButtonBase components was not resolving
targets inside a shadowRoot. Now the activeElement for nested
shadowRoots is resolved to add focus visible classes to targets in the
shadow DOM.
JaiPe added a commit to JaiPe/material-ui that referenced this issue Nov 1, 2018
The focusVisible polyfill for ButtonBase components was not resolving
targets inside a shadowRoot. Now the activeElement for nested
shadowRoots is resolved to add focus visible classes to targets in the
shadow DOM.
JaiPe added a commit to JaiPe/material-ui that referenced this issue Nov 1, 2018
The focusVisible polyfill for ButtonBase components was not resolving
targets inside a shadowRoot. Now the activeElement for nested
shadowRoots is resolved to add focus visible classes to targets in the
shadow DOM.

Closes mui#13447
oliviertassinari pushed a commit that referenced this issue Nov 2, 2018
* [ButtonBase] Update focusVisible polyfill for shadowRoots (#13447)

The focusVisible polyfill for ButtonBase components was not resolving
targets inside a shadowRoot. Now the activeElement for nested
shadowRoots is resolved to add focus visible classes to targets in the
shadow DOM.

Closes #13447

* Modify test to check for shadowRoot behaviour and functionality before simulating it

* size-limit
@bboydflo
Copy link

bboydflo commented Feb 6, 2019

@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
Copy link
Contributor Author

JaiPe commented Feb 6, 2019

@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?

@bboydflo
Copy link

bboydflo commented Feb 6, 2019

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.

@JaiPe
Copy link
Contributor Author

JaiPe commented Feb 9, 2019

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).

rediska1114 added a commit to rediska1114/text-mask that referenced this issue Nov 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: ButtonBase The React component. good first issue Great for first contributions. Enable to learn the contribution process. new feature New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants