javascriptquillngx-quill

Quill editor inside Shadow DOM


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.


Solution

  • Done.

    This fix doesn't cover missing shadow.getSelection in Safari but all other browsers should work. In my case, I needed only Chrome.

    var quill = new Quill(editorControl, {
      modules: {
        toolbar: [
          [{ header: [1, 2, false] }],
          ['bold', 'italic', 'underline'],
          ['image', 'code-block']
        ]
      },
      placeholder: 'Compose an epic...',
      theme: 'snow' 
    });
    
    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 
    
    quill.selection.getNativeRange = () => {
      
      const dom = quill.root.getRootNode();
      const selection = dom.getSelection();
      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", (...args) => {
    
      // Update selection and some other properties
    
      quill.selection.update()
    });