Skip welcome & menu and move to editor
Welcome to JS Bin
Load cached copy from
 
<!DOCTYPE html>
<html>
<head>
  <style>
  #editor {
    border: 1px solid black;
    min-height: 150px;
  }
  span {
    outline-color: red !important;
   outline-style: solid;
  }
  </style>
</head>
<body>
<p>DOM Changes in Editor: <span id="counter">0</span></p>
<div id="editor" contenteditable="true" >
  <h1>Composition in Shadow DOM</h1>
  <p>This is to try the capabilities <b>of</b> using the <i>Shadow</i> DOM for IME so that the
    DOM is not updated during character composition.</p>
  <p>To try this out:</p>
  <ol>
    <li>Click anywhere into this text.</li>
    <li>Start typing using keyboard input (not IME).</li>
    <li>Observe: For each letter, the "DOM Changes in Editor" will increase by 1.</li>
    <li>Switch to IME input.</li>
    <li>Click anywhere in this text.</li>
    <li>Construct an IME character inline.</li>
    <li>Observe: the "DOM CHanges in Editor" counter will only increase by 1 after construction has finished.</li>
  </ol>
</div>
<script>
var editor = document.getElementById('editor'),
  beforeCompositionRange,
  compositionShadowRoot,
  selection = window.getSelection(),
  getRange = function (range, element) {
    var selectedElement,
      pathElement,
      rangeDes = {
        start: {
          path: [],
          offset: range.startOffset
        },
        end: {
          path: [],
          offset: range.endOffset
        }
      };
    selectedElement = range.startContainer;
    pathElement = 0;
    while (selectedElement != element) {
      while (selectedElement.previousSibling) {
        pathElement += 1;
        selectedElement = selectedElement.previousSibling;
      }
      rangeDes.start.path.push(pathElement);
      selectedElement = selectedElement.parentElement;
      pathElement = 0;
    }
    selectedElement = range.endContainer;
    pathElement = 0;
    while (selectedElement != element) {
      while (selectedElement.previousSibling) {
        pathElement += 1;
        selectedElement = selectedElement.previousSibling;
      }
      rangeDes.end.path.push(pathElement);
      selectedElement = selectedElement.parentElement;
      pathElement = 0;
    }
    return rangeDes;
  },
  setRange = function (rangeDes, element) {
    var range = document.createRange(),
    selectedElement,
    findPath;
    selectedElement = element;
    findPath = rangeDes.start.path.slice();
    while(findPath.length > 0) {
      selectedElement = selectedElement.childNodes[findPath.pop()];
    }
    range.setStart(selectedElement, rangeDes.start.offset);
    selectedElement = element;
    findPath = rangeDes.end.path.slice();
    while(findPath.length > 0) {
      selectedElement = selectedElement.childNodes[findPath.pop()];
    }
    range.setEnd(selectedElement, rangeDes.end.offset);
    return range;
  },
  moveCaret = function () {
    var beforeCompositionRange = selection.getRangeAt(0),
    rangeDes,
    moveCaretBack = function () {
      shadow.innerHTML ='<content>';
      selection.removeAllRanges();
      selection.addRange(beforeCompositionRange);
    },
    compositionShadowRoot = beforeCompositionRange.commonAncestorContainer;
    if (compositionShadowRoot.nodeType===3) {
      // start and end is in the same text node, so we attach the Shadow DOM to the parent element instead
      compositionShadowRoot = compositionShadowRoot.parentElement;
    }
    rangeDes = getRange(beforeCompositionRange, compositionShadowRoot);
    shadow = compositionShadowRoot.createShadowRoot(),
    cEShadow = document.createElement('span');
    cEShadow.setAttribute('contentEditable','true');
    cEShadow.style.outlineColor = 'transparent';
    cEShadow.innerHTML=compositionShadowRoot.innerHTML;
    shadow.appendChild(cEShadow);
    newRange = setRange(rangeDes, cEShadow);
    selection.removeAllRanges();
    selection.addRange(newRange);
    cEShadow.addEventListener('compositionend',moveCaretBack);
  };
editor.addEventListener('compositionstart',moveCaret);
// Observe changes
var observer = new MutationObserver(function(mutations) {
  changeCounter += 1;
  changeCounterElement.innerHTML = changeCounter;
}), changeCounter = 0, changeCounterElement = document.getElementById('counter');
observer.observe(editor, { attributes: true, childList: true, characterData: true, subtree: true });
</script>
</body>
</html>
Output

You can jump to the latest bin by adding /latest to your URL

Dismiss x
public
Bin info
anonymouspro
0viewers