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

In shadow DOM, keyboard not working for Select menu #34061

Open
2 tasks done
jacobweberbowery opened this issue Aug 24, 2022 · 6 comments
Open
2 tasks done

In shadow DOM, keyboard not working for Select menu #34061

jacobweberbowery opened this issue Aug 24, 2022 · 6 comments
Assignees
Labels
bug 🐛 Something doesn't work component: select This is the name of the generic UI component, not the React module! package: material-ui Specific to @mui/material

Comments

@jacobweberbowery
Copy link
Contributor

Duplicates

  • I have searched the existing issues

Latest version

  • I have tested the latest version

Current behavior 😯

When Select menu (and its popover) is displayed within a shadow DOM, arrow keys have no effect.

Expected behavior 🤔

Arrow keys should select menu items.

(I'm not sure to what extent shadow DOM is supported, but it seems to be mostly working otherwise.)

Steps to reproduce 🕹

Steps:

  1. Load https://codesandbox.io/s/hopeful-noether-88fukr
  2. This renders a React root into a shadow DOM, and also puts the Emotion styles there using an Emotion CacheProvider.
  3. Inside it is a Select menu with disablePortal=true (so it can use the styles).
  4. Click the Select menu so its popover appears.
  5. Press one of the arrow keys. The selection doesn't change.

Context 🔦

Rendering a MUI app entirely within a web component's shadow DOM.

Your environment 🌎

CodeSandbox

@jacobweberbowery jacobweberbowery added the status: waiting for maintainer These issues haven't been looked at yet by a maintainer label Aug 24, 2022
@michaldudak michaldudak self-assigned this Aug 25, 2022
@michaldudak michaldudak added component: select This is the name of the generic UI component, not the React module! bug 🐛 Something doesn't work package: material-ui Specific to @mui/material and removed status: waiting for maintainer These issues haven't been looked at yet by a maintainer labels Aug 25, 2022
@michaldudak
Copy link
Member

We haven't been testing the components in such context. It's not a popular use case after all.
If you'd like to investigate the problem, I'll be happy to assist you with a PR.

@yk-jemmic
Copy link

yk-jemmic commented Sep 13, 2022

@michaldudak I wouldn't say it's not a popular use case. Being able to use a MUI with a React app that can be wrapped with a web component using shadowDom will give you more potential clients with big finances who want to move from JSF/JSP to ReactJS and who is willing to pay for the pro version.

There are currently 2 ways to use a React app with JSF/JSP.

  • in the *.xhtml file, include the iframe tag and pass a link to index.html created with react-scripts or vite. As a result we have an isolated UI part and we can even mix ReactJS/VueJS/Angular on the same page for example to understand what is best for us, but the downside of this is that I can't pass some attribute values ​​during rendering by java on the server. Also, there is no easy way to communicate between JSF and my React app, plus we lose the concept of SAP.
  • second: define a custom html element that wraps the React app with a shadowDom to avoid css mixing. in this case, we can observe the attribute changes and pass them to the React app. Look at this simples example:
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

const webComponentName = 'my-superpuper-element'

export class MySuperpuperElement extends HTMLElement {

    connectedCallback() {
        const shadowElement = this.attachShadow({mode: 'closed'})
        const styleElement = document.getElementById(`${webComponentName}-style`)
        if (styleElement instanceof HTMLElement) {
            shadowElement.appendChild(styleElement)
        }
        const reactRoot = ReactDOM.createRoot(shadowElement);
        reactRoot.render(React.createElement(App, {}));
    }
}

if (!customElements.get(webComponentName)) {
    customElements.define(webComponentName, MySuperpuperElement);
}

And it would be fit my needs perfectly if MUI provided a clear picture how and when it adds its styles.
In my case, with Vite and the plugin 'vite-plugin-css-injected-by-js' all my css are bundled together with one js file and then when I define my custom element I can move <style id='my-superpuper-element-style-id'> tag from the <head> tag inside the shadowDom but I can not do it with MUI styles (actually I do not know, I am java back-end developer).

Hence I'd like to ask you pleas may you give an idea how to do this.
Or maybe we can request a future that would add a script that can create a custom element of react app that uses MUI (something like this react-to-webcomponent)

PS. I bet this will allow some managers to be more courageous in their decision to migrate their old banking interface to a more modern one that uses React rather than JSP/JSF. Because in this way it would not be necessary to migrate everything at once.

@tristanjasper
Copy link

Any suggestions on a workaround? This is still an issue. @yk-jemmic Did you figure a solution?

@yk-jemmic
Copy link

@tristanjasper I had a slightly different case, but yes, it's still relevant with an example in the topic as well as on their Demo (but it works partially) so keyboard navigation does not work correctly

@mikrotron
Copy link

I've managed to get this to work with a custom onKeyDown handler:

<Select 
  ref={selectRef}
  MenuProps={{
    PaperProps: {
      onKeyDown: ({ key }: React.KeyboardEvent<HTMLDivElement>) => {
        const validKeys = ["ArrowUp", "ArrowDown"];
        let newIndex = 0;
        if (validKeys.includes(key) && selectRef.current) {
          const selectedItem = 
            (selectRef.current.getElementsByClassName("Mui-focusVisible")[0] ||
            selectRef.current.getElementsByClassName("Mui-selected")[0]) as HTMLElement;
          const menuList = Array.from(
            selectRef.current.querySelectorAll("ul[role='listbox'] li") || [],
          );

          if (selectedItem && selectedItem.parentNode) {
            const selectedIndex = menuList.indexOf(selectedItem);
            if (key === "ArrowDown") {
              newIndex = selectedIndex + 1 < menuList.length ? selectedIndex + 1 : selectedIndex;
            } else if (key === "ArrowUp") {
              newIndex = selectedIndex - 1 >= 0 ? selectedIndex - 1 : selectedIndex;
            }
          }

          if (menuList.length > newIndex) {
            const newSelectedItem = menuList[newIndex] as HTMLElement;
            newSelectedItem.focus();
          }
        }
      },
    },
  }}
>

@LukasTy
Copy link
Member

LukasTy commented Oct 17, 2024

Based on preliminary testing, the root cause of the problem is here:

const currentFocus = ownerDocument(list).activeElement;

On mui-x we have also experienced this problem and are using the following utility to resolve the activeElement:
https://github.com/search?q=repo%3Amui%2Fmui-x+%22export+const+getActiveElement%22&type=code

Applying similar changes seems to fix the problem.
Is anyone more familiar with the code able to fix this problem?
It could be a great idea to check all cases, where the active element is resolved and use the shadowRoot compliant method in such cases. 💡

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug 🐛 Something doesn't work component: select This is the name of the generic UI component, not the React module! package: material-ui Specific to @mui/material
Projects
None yet
Development

No branches or pull requests

6 participants