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

Support Shadow DOM v1 #2021

Open
ergo opened this issue Mar 14, 2018 · 20 comments
Open

Support Shadow DOM v1 #2021

ergo opened this issue Mar 14, 2018 · 20 comments
Assignees
Labels

Comments

@ergo
Copy link

ergo commented Mar 14, 2018

Hello,

#1472 can we get this issue reopened or at least have a new one on the subject?

The stable spec is over a year old, chrome, safari and other webkit based browsers have full support, firefox nightly ships Shadow Dom behind a flag already and stable will probably land with support at the end of march.

@olegomon
Copy link

olegomon commented May 7, 2018

@jhchen Shadow DOM is becoming reality and many frameworks are taking advantage of it. I think it would be a great feature for 2.0 version of the modern Quill editor. Do you think version 2.0 will support it?

@kaseyhinton
Copy link

Any news on this?

petersooley pushed a commit to elmdash/quill that referenced this issue May 31, 2018
@chwzr
Copy link

chwzr commented Aug 5, 2018

bump. PLEASE add shadow dom support. this editor is so nice.

@ergo
Copy link
Author

ergo commented Sep 7, 2018

Any news on this?
GitHub is also using this, https://githubengineering.com/removing-jquery-from-github-frontend/.
Firefox beta already has web components enabled, next stable will have them on. Chrome and Safari both desktop and mobile support them for a year now.

@kr05
Copy link

kr05 commented Apr 2, 2019

Hello, I'm revisiting quill after about 4 months after not being able to use it due to this issue. Is it actively being worked on or is it simply not realistic...

@raphaelrauwolf
Copy link

We've now reached 88.66% global browser support for Shadow DOM (V1). We can ignore IE or Edge at this point, since libs like Lit are poly-filling and use Light DOM. Any updates or plans to implement support for 2019?

@ronnyroeller
Copy link

This would be really helpful! I love quill but we can't really use it today in our Polymer/LitElement application... :(

@kjantzer
Copy link

kjantzer commented Sep 26, 2019

@ronnyroeller Try this fork https://github.com/web-padawan/quill/tree/shadow
I've been using it with lit-element and shadow dom for a few months now

@ergo
Copy link
Author

ergo commented Jan 25, 2020

Interesting, @web-padawan - any chance this can get merged back into master? Maybe now when Edge is also Chromium based it will get accepted.

@Arosner
Copy link

Arosner commented Feb 20, 2020

I have quill working in shadowdom almost perfectly without any changes to quill itself, however I have an issue in Firefox where the cursor will not display in the quill editor, but does know my cursors position.
If I change tabs and tab back to my app the cursor will become visible. the same happens if I click into the inspector then back into the quill editor.

@aanavaneeth
Copy link

If I change tabs and tab back to my app the cursor will become visible. the same happens if I click into the inspector then back into the quill editor.

how? do you have demo?

@Arosner
Copy link

Arosner commented Feb 28, 2020

@aanavaneeth Demo of quill almost working in shadow Dom or the tabbing thing? I'm not sure why the tabbing thing happens the way it does and am currently trying to fix that. Only happens in Firefox.

Regardless this weekend I can make a simple example repo and link it here. The hill I am dying on is this Firefox issue though.

@aanavaneeth
Copy link

Demo of working in shadow DOM. Frankly, for now I am ok if it doesn't work perfectly in FF but hoping for it i n near future.

@tirithen
Copy link

I'm using the following polyfill workaround if it helps anyone, it is not perfect, but makes the editor fairly usable in Firefox.

It also has a small memory leak because of the event listener in the end that is not cleaned up if the quill instance is thrown away.

It is a mix of the shadow-selection-polyfill npm package and a script from stackoverflow (link in code comment):

import { getRange } from 'shadow-selection-polyfill';

// Shadow DOM fix based on https://stackoverflow.com/questions/67914657/quill-editor-inside-shadow-dom/67944380#67944380
export function quillSelectionFix(quill) {
  const normalizeNative = (nativeRange) => {

    // document.getSelection model has properties startContainer and endContainer
    // shadow.getSelection model has baseNode and focusNode
    // Unify formats to always look like document.getSelection

    if (nativeRange) {
      const range = nativeRange;

      if (range.baseNode) {
        range.startContainer = nativeRange.baseNode;
        range.endContainer = nativeRange.focusNode;
        range.startOffset = nativeRange.baseOffset;
        range.endOffset = nativeRange.focusOffset;

        if (range.endOffset < range.startOffset) {
          range.startContainer = nativeRange.focusNode;
          range.endContainer = nativeRange.baseNode;
          range.startOffset = nativeRange.focusOffset;
          range.endOffset = nativeRange.baseOffset;
        }
      }

      if (range.startContainer) {
        return {
          start: { node: range.startContainer, offset: range.startOffset },
          end: { node: range.endContainer, offset: range.endOffset },
          native: range
        };
      }
    }

    return null
  };

  // Hack Quill and replace document.getSelection with shadow.getSelection

  // eslint-disable-next-line no-param-reassign
  quill.selection.getNativeRange = () => {
    const dom = quill.root.getRootNode();
    // const selection = dom.getSelection instanceof Function ? dom.getSelection() : document.getSelection();
    const selection = getRange(dom);
    const range = normalizeNative(selection);

    return range;
  };

  // Subscribe to selection change separately,
  // because emitter in Quill doesn't catch this event in Shadow DOM

  document.addEventListener("selectionchange", () => {
    // Update selection and some other properties
    quill.selection.update();
  });
}

@hojjat-afsharan
Copy link

@tirithen with this approach, there is a problem of copy/pasting in chrome

@jhefferman-sfdc
Copy link

In case anyone is still trying to solve this, here's what I have (seems to work ok in Chromium, Gecko, Webkit browsers, including copy/paste, selection, keyboard shortcuts, etc. Note: for Safari it requires > Safari 17 which has the new Selection API

All issues seemed to boil down to three problems:

  1. Getting Selection.Range: document.getSelection doesn't work in Native Shadow and each browser has a different implementation
  2. Checking Focus: document.activeElement needs to be replaced by shadowRoot.activeElement
  3. Setting Selection.Range: Selection.addRange does not work in Safari in Native Shadow but can be replaced with Selection.setBaseAndExtent

Here's a codepen example: https://codepen.io/John-Hefferman/pen/yLZygKo?editors=1000
Here's the monkeypatch:

   const hasShadowRootSelection = !!(document.createElement('div').attachShadow({ mode: 'open' }).getSelection);
    // Each browser engine has a different implementation for retrieving the Range
    const getNativeRange = (rootNode) => {
        try {
            if (hasShadowRootSelection) {
                // In Chromium, the shadow root has a getSelection function which returns the range
                return rootNode.getSelection().getRangeAt(0);
            } else {
                const selection = window.getSelection();
                if (selection.getComposedRanges) {
                    // Webkit range retrieval is done with getComposedRanges (see: https://bugs.webkit.org/show_bug.cgi?id=163921)
                    return selection.getComposedRanges(rootNode)[0];
                } else {
                    // Gecko implements the range API properly in Native Shadow: https://developer.mozilla.org/en-US/docs/Web/API/Selection/getRangeAt
                    return selection.getRangeAt(0);
                }
            }
        } catch {
            return null;
        }
    }

    /** 
     * Original implementation uses document.active element which does not work in Native Shadow.
     * Replace document.activeElement with shadowRoot.activeElement
     **/
    quill.selection.hasFocus = function () {
        const rootNode = quill.root.getRootNode();
        return rootNode.activeElement === quill.root;
    }

    /** 
     * Original implementation uses document.getSelection which does not work in Native Shadow. 
     * Replace document.getSelection with shadow dom equivalent (different for each browser)
     **/
    quill.selection.getNativeRange = function () {
        const rootNode = quill.root.getRootNode();
        const nativeRange = getNativeRange(rootNode);
        return !!nativeRange ? quill.selection.normalizeNative(nativeRange) : null;
    };

    /**
     * Original implementation relies on Selection.addRange to programatically set the range, which does not work
     * in Webkit with Native Shadow. Selection.addRange works fine in Chromium and Gecko.
     **/
        quill.selection.setNativeRange = function (startNode, startOffset) {
            var endNode = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : startNode;
            var endOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : startOffset;
            var force = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
            if (startNode != null && (quill.selection.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
                return;
            }
            var selection = document.getSelection();
            if (selection == null) return;
            if (startNode != null) {
                if (!quill.selection.hasFocus()) quill.selection.root.focus();
                var native = (quill.selection.getNativeRange() || {}).native;
                if (native == null || force || startNode !== native.startContainer || startOffset !== native.startOffset || endNode !== native.endContainer || endOffset !== native.endOffset) {
                    if (startNode.tagName == "BR") {
                        startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
                        startNode = startNode.parentNode;
                    }
                    if (endNode.tagName == "BR") {
                        endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
                        endNode = endNode.parentNode;
                    }
                    selection.setBaseAndExtent(startNode, startOffset, endNode, endOffset);
                }
            } else {
                selection.removeAllRanges();
                quill.selection.root.blur();
                document.body.focus();
            }
        }

    /**
     * Subscribe to selection change separately, because emitter in Quill doesn't catch this event in Shadow DOM
     **/
    const handleSelectionChange = function () {
        quill.selection.update();
    };

    document.addEventListener("selectionchange", handleSelectionChange);

@varminas
Copy link

Any news on this issue?

@luin luin self-assigned this Jan 31, 2024
@justinbhopper
Copy link
Contributor

@luin Now that Quill 2.0 has released, are we interested in a PR for this based on @jhefferman-sfdc's snippet above? I can submit one if so.

I've tested their changes on v2 within a shadow root and its working very well, so it shows promise.

@TechQuery
Copy link

Quill 2.0.0 has same bugs with Web components: EasyWebApp/quill-cell@8bf3c6d

Reproduce

  1. open the test page code in Cloud IDE: https://gitpod.io/?autostart=true#https://github.com/EasyWebApp/quill-cell/pull/1/commits/8bf3c6d8da477c71f6cbb5be1140460141b30f33

  2. run pnpm i && npm start to open the test page

@web-padawan
Copy link

Please note that the code snippet above isn't really the only thing that should be applied. This part also need an update:

https://github.com/quilljs/quill/blob/6590aa45ac6a60a64b59bccf7badba9667692f61/packages/quill/src/core/emitter.ts#L10

See also #1805 where shadow DOM support was originally prototyped for some other places where getRootNode() might be used instead of document (it's mostly about activeElement and getSelection() calls though).

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

No branches or pull requests