Search code examples
javascripthtmlcontenteditableweb-componentshadow-dom

Using Shadow DOM within ContentEditable to create protected blocks of text


Recently I've been developing a simple editor using ContentEditable. The requirements for the application are simple, with the one exception of being able to insert blocks of text which are protected from the normal editing actions.

These blocks of text cannot be edited and must behave as a single character for the purposes of moving the cursor through them or deleting them.

An example of the resulting HTML looks like this:

<div id="editor" contenteditable style="height: 400px; border: 1px solid black; margin: 4px; padding: 4px; overflow:auto;">
  "This is standard text with a "
    <span class="attrib">
      #shadow-root
        "PROTECTED"
      "_"
    </span>
  " block"
</div>

Whilst this provides the protected section of text I need, it has a couple of major problems that I cannot resolve:

  • The text after the shadow DOM element does not display.
  • The cursor does not move through the shadowDOM element at all.

Is there a better way to do this or is it just not possible to use the shadow DOM in this way?


Solution

  • Solution 1

    You can force the contenteditable attribute to false in your protected element:

    <div id="editor" contenteditable style="height: 100px; border: 1px solid black; margin: 4px; padding: 4px; overflow:auto;">
      "This is standard text with a 
      <span contenteditable="false">PROTECTED</span> 
       block"
    </div>

    Solution 2

    If you want to use Shadow DOM without contenteditable=false:

    You can observe the caret position using window.getSelection().anchorOffset, and check if the position have changed.

    If not, you'll have to move the caret to the next text node (if possible) with Selection's setBaseAndExtent().

    Here is a minimal example which works when the [Right Arrow] key is pressed:

    editor.querySelector( '.attrib' )
          .attachShadow({mode: 'open' } )
          .innerHTML = 'PROTECTED'
       
    editor.addEventListener( 'keydown', onkeydown )
    
    var position = 0 
    
    function onkeydown( ev )
    {
      if ( ev.key == "ArrowRight" )
      {
        setTimeout( function ()
        { 
          var sel = window.getSelection()
          if ( position == sel.anchorOffset )
          {
            var anchor = sel.anchorNode 
            if ( anchor.nextSibling && anchor.nextSibling.nextSibling )
            {
              console.warn( 'move next' ) 
              var next = anchor.nextSibling.nextSibling
              sel.setBaseAndExtent( next, 1, next, 1)
            }
          } 
          position = sel.anchorOffset
        } ) 
      }
    }
    .attrib {
        background: lightblue ;
    }
      
    #editor {
        height: 100px; width:400px; border: 1px solid black; margin: 4px; padding: 4px; overflow:auto
    }
    <div id="editor" contenteditable>"This 
    		is standard text with a <span class="attrib">TEST</span> block"
    </div>