quill
quill copied to clipboard
Shadow DOM support - continued
Follow up of issues https://github.com/quilljs/quill/issues/2021 and https://github.com/quilljs/quill/issues/1472 to add support for Shadow DOM.
More and more apps are being built with Micro Front End architecture in mind, which means components including editor experience are delivered through as a web component, which means, Shadow DOM.
Cisco had been leveraging quill in Contact Center cloud enterprise solution, and now is moving to MFE architecture.
I will be opening a simple PR on behalf of Cisco to address it, please let me know if quill team would have bandwidth to review it within the next 3-4 months, it would help greatly!
@yananym all power to you. It's a shame this Shadow DOM supporting implementation wasn't merged before becoming stale: https://github.com/web-padawan/quill But I'm sure something similar can be created for Quill 2.x.x
Any update on this issue?
Any update on this? I think as a community we must encourage the web component standard and we cannot be a bottle neck.
I'm glad to see a relatively new issue addressing shadow DOM support. I'd be happy to help in any way. Will integrating the changes from https://github.com/web-padawan/quill into a fresh fork of quill be useful?
This is actually exactly what one of my engineers had done. He had reported that some Jasmine unit tests are failing and he was not able to resole them, i had not been able to find time to look into it as of yet. @kr05 Kevin would you be able to take a look at tests failures? i would be grateful! https://github.com/yananym/quill
Of course, I'll see what I can do!
@yananym it still not work on Safari (my safari version: 13.1). But it's work on Chrome & Firefox, although I see some errors on the console
Has anyone tried setting up the dev environment on WSL? I'm getting some issues with installing nokogiri. I'll keep trying, but if anyone has any suggestions I would appreciate it. I can provide more info as requested.

I try the https://github.com/yananym/quill, I didn't see setup issue on mac.
@kr05 I think the error is realtively clear. you'll need to install pkg-config (program that helps C compiler discover libraries to link - it essentially generates -l* falgs to cc). You might also need to install libxml2 and other dependencies. Last time I used windows was 10 years ago so I can't provide more specific help but perhaps you can just apt-get install it in WSL?
I'm also interested seeing some progress here. Shadow dom support is not a deal breaker for me but I would still rather use the shadow dom if I could. Is anyone working on this at the moment? And if not are PRs implementing this accepted?
@kr05 actually I think you find answers to your questions in this post https://davemateer.com/2020/10/20/running-jekyll-on-wsl2
Any updates on this? I am using react-quill in shadow dom and I have strange issues with it.
@dman777 yes that's what I also experienced just with my hand-coded web-component embedded in elm application. For time being I would recommend you to abandon shadow DOM and just namespace things as best as you can. It seems to me that quill depends on some of the global stuff (event handlers on window and such) to work properly. Luckily these things are not a major problem if you're careful™. The limitation is probably the biggest problem for folks who need to integrate quill to code that itself is used in shadow dom like the micro frontend oriented apps. It seems there is no interest in maintainers on this so the options seems to be either to fork quill or to live with it as is.
I'm another person who wants to use Quill but needs it inside a web component context. I can get Quill to import and render and it kind of works, but the issue preventing me from using it is that it can get into a state where every keypress sends the cursor back to the start and inserts a newline, so trying to type a word will produce each character on its own line and in reverse order. Clicking inside the text box while it's empty toggles this state somewhat reliably.
It only happens when it's inside a web component, but that's my only lead.
@yujiri8 isn't it possible that you have quill in some vdom based FW (like React) and that every keypress causes react to render Quill again (insert after v dom diff) so the problem is not the quill itself but the fact you initialize it again after every key?
I'm 100% certain it's possible to embed quill as web-component (without shadow DOM). I think the problem is somewhere in your approach.
The FW I'm using is Lit-Element. It only re-renders a component when an attribute changes, so keypresses should not trigger that.
On 3/9/21, Marek Fajkus [email protected] wrote:
@yujiri8 isn't it possible that you have quill in some vdom based FW (like React) and that every keypress causes react to render Quill again (insert after v dom diff) so the problem is not the quill itself but the fact you initialize it again after every key?
I'm 100% certain it's possible to embed quill as web-component (without shadow DOM). I think the problem is somewhere in your approach.
-- You are receiving this because you were mentioned. Reply to this email directly or view it on GitHub: https://github.com/quilljs/quill/issues/2961#issuecomment-793507929
I would start by checking your data flow to see what causes it but it's definitely not because of a web component. For instance call to setContent and some other resets the position of cursor. My approach is to let quill manage it's own state and just hook into events but never to call these methods that sets the content on quill instance unless I need to (I'm loading a new data). This approach works for me though I admit it involved some trickery to wrap this stateful interface so it can work within declarative ui.
I'm not calling setContent or any other post-constructor API. This is a minimal element that experiences it:
import {LitElement, html, css} from 'lit-element';
import * as Quill from 'quill';
customElements.define('test-component', class extends LitElement {
static get properties() {
return {}
}
static get styles() {
return [styles, css`
@import 'https://cdn.quilljs.com/1.3.7/quill.snow.css';
:host([hidden]) { display: none; }
:host { display: block; }
`];
}
render() {
return html`
<div id="quill"></div>
`;
}
firstUpdated() {
this.quill = new Quill(this.shadowRoot.getElementById('quill'), {theme: 'snow'});
}
});
This is the vanila JS (ES2015) custom elment wrapper that works:
const toolbarOptions = [
[{ 'header': [1, 2, false] }],
['bold', 'underline', 'italic', 'strike'], // toggled buttons
[{ 'background': [] }, { 'color': [] }], // dropdown with defaults from theme
[{ 'script': 'super' }, { 'script': 'sub'}], // superscript/subscript
[{ 'list': 'bullet' }, { 'list': 'ordered'}],
[{ 'indent': '+1'}, { 'indent': '-1' }], // outdent/indent
['code-block', 'blockquote'],
['clean'] // remove formatting button
];
class MyQuill extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
// Setup container
this.editorContainer = document.createElement('div');
}
connectedCallback() {
this.prepend(this.editorContainer);
const quill = new Quill(this.editorContainer, {
modules: {
toolbar: toolbarOptions
},
theme: 'snow' // or 'bubble'
});
this._quill = quill;
}
}
customElements.define("my-quill", MyQuill);
Your example is using shadow dom as it says in code:
this.shadowRoot.getElementById('quill')
as I said:
web-component (without shadow DOM).
as I said:
web-component (without shadow DOM).
Oh, I missed that part of your reply. But yes I need it inside shadow DOM. And that is the title of this issue, after all.
In this example, editor is created inside Shadow Root.
https://codepen.io/artemiusgreat/pen/XWMPdWG
The main concern so far is that inline formatting doesn't work when initiated from Toolbar module by clicking Bold or Italic buttons on the panel.
The reason is that window.getSelection always returns empty selection inside the Shadow Root.
The good thing is that it somehow works when inline formatting is initiated from Keyboard module by pressing CTRL + B or CTRL + I.
I'm digging into the code, but if somebody already resolved this I would appreciate some guidance.
Kind of fixed. https://stackoverflow.com/a/67944380/437393
Kind of fixed. https://stackoverflow.com/a/67944380/437393
thank you very mush!
Wanted to note a couple of interesting details which I saw mentioned elsewhere
- web-padawan's personal quill fork has been moved to vaadin/quill
- some selection-api-related issues on safari are purportedly improved via a different polyfill
Happy new year everyone. Anyone else still eagerly awaiting some traction here? Please feel free to provide some comments/thoughts on my branch - https://github.com/robrez/quill/pull/1
@yananym all power to you. It's a shame this Shadow DOM supporting implementation wasn't merged before becoming stale: https://github.com/web-padawan/quill But I'm sure something similar can be created for Quill 2.x.x
parse not work.
Kind of fixed. https://stackoverflow.com/a/67944380/437393
I noticed that copy-pasting text is not working with this approach, which can be observed here. Does anyone have a workaround for this?
We are also using Quill and WebComponents ( Lit Element ) as part of our rich / fully featured text editor. We are currently still suffering from the fact Selection API doesn't understand the shadow DOM in Safari / Firefox this polyfil: https://github.com/GoogleChromeLabs/shadow-selection-polyfill together with this function: https://stackoverflow.com/questions/67914657/quill-editor-inside-shadow-dom/67944380#67944380 solved the issue on firefox (the entire toolbar and selection of text and trying to apply the toolbar functionality on the selected text didn't work without this polyfil. some issues were not resolved by the polyfil. for example: same issue of @yujiri8 https://github.com/quilljs/quill/issues/2961#issuecomment-793999566 HOWEVER - the biggest problem is with Safari -> not even one polyfil worked for us. anyone knows what other companies that use LitElement and ShadowDOM with rich text editors do with Safari ?
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:
- Getting Selection.Range:
document.getSelectiondoesn't work in Native Shadow and each browser has a different implementation - Checking Focus:
document.activeElementneeds to be replaced byshadowRoot.activeElement - Setting Selection.Range:
Selection.addRangedoes not work in Safari in Native Shadow but can be replaced withSelection.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);
@jhefferman-sfdc Thank you for this! I went in circles for hours trying to find a fix for this and finally found your comment, and you wrote it only a couple days ago! Great timing. Thanks again.