lexical: Bug: Binding to webcomponent shadow root fails

I’m working on converting lexical into an es6 module + webcomponent. I’ve been able to successfully bundle an es6 module (using webpack). Using it as a top-level component in the HTML document root works, but including it as a webcomponent doesn’t.

Lexical version: 0.25

Steps To Reproduce

  1. import a bundled lexical.bundle.min.js file
  2. bind Lexical to an editor in the root DOM (ie. document.getElementById(“editor”)), and observe that it works
  3. bind Lexical to an editor in the shadow DOM (this.shadowRoot.querySelector(“#editor”), and observe that it doesn’t work.
  • The shadow dom’s editor does receive attributes for data-lexical-editor=true among others
  • The shadow dom does not receive key inputs

Link to code example:

https://user-images.githubusercontent.com/9470098/167487085-048ee412-6564-47cc-91f4-285bd9342a58.mov

https://github.com/yuzuquats/lexical-web-component

Any help would be appreciated! I’m fairly new to npm so if there’s anything obvious I’m missing I’d love to learn!

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 5
  • Comments: 17 (1 by maintainers)

Most upvoted comments

Hopefully this might help others too and is just a patch for now. After, a lot of trial and error trying to make it work. Since my research project involved using text Editors. And the people on whose work I am basing my thesis on. They used lit-html and they too had issues trying to get their text editor working with the shadow DOM. https://github.com/PAIR-code/wordcraft

https://github.com/PAIR-code/wordcraft/blob/main/app/core/services/text_editor_service.ts

They ended up patching the current node in focus and the window.getSelection() which is what Lexical also seems to be using. So this solution works as a patch for now just call the getPatchSelection() before registering the different plugins and it should work even for components within the shadow DOM.

/**
 * We need to hack the window.getSelection method to use the shadow DOM,
 * since the mobiledoc editor internals need to get the selection to detect
 * cursor changes. First, we walk down into the shadow DOM to find the
 * actual focused element. Then, we get the root node of the active element
 * (either the shadow root or the document itself) and call that root's
 * getSelection method.
 */
export function patchGetSelection() {
    const oldGetSelection = window.getSelection.bind(window);
    window.getSelection = (useOld: boolean = false) => {
      const activeElement = findActiveElementWithinShadow();
      const shadowRootOrDocument: ShadowRoot | Document = activeElement
        ? (activeElement.getRootNode() as ShadowRoot | Document)
        : document;
      const selection = (shadowRootOrDocument as any).getSelection();
  
      if (!selection || useOld) return oldGetSelection();
      return selection;
    };
  }
  
  /**
   * Recursively walks down the DOM tree to find the active element within any
   * shadow DOM that it might be contained in.
   */
function findActiveElementWithinShadow(
    element: Element | null = document.activeElement
  ): Element | null {
    if (element?.shadowRoot) {
      return findActiveElementWithinShadow(element.shadowRoot.activeElement);
    }
    return element;
  }

All credits go to the wordcraft team contributers Andy Coen and G. Hussain Chinoy

FYI: seems like this might be a common problem and selection APIs across shadow DOM boundaries are being worked on

In the meantime, I’ve opted to work around webcomponents with a simple class shim. Feel free to close this out unless you think there are better ways to work around this!