contenteditable whitespace-clobbering

A DOM content-editable seems to

by ecmanaut

HTML

<div id="editor" contenteditable>
</div>

<div>
  <h4>Q: How do you make a content-editable element preserve an existing single newline in it (either in the shape of an &lt;br/&gt; tag or newline character, &amp;#10; === \n – whichever works), when you insert another character, before or after that newline?</h4>
  <p>
    Click a radio below to populate the content-editable above with just the html shown, and as you type in the content-editable, its current innerHTML will be shown underneath, for each new character typed.
  </p>
    <ol id="radios"></ol>
</div>

<pre id="output"></pre>

CSS

#editor {
    width: 100%;
    white-space: pre-wrap;
    min-height: 100px;
    outline: 1px solid blue;
}

#radios {
    list-style: none;
}

span {
    background: #ccc;
    padding: 1px 5px 1px 5px;
    font-size: 12px;
    margin-right: 4px;
}

JavaScript

const div = document.querySelector('#editor');
const pre = document.querySelector('#output');
const ras = document.querySelector('#radios');
const bad = [0, 1, 2, 3, 4, 5, 6, 7];
const how =
[ '<br/>',
  '&#10;',
  '<div>&#10;</div>',
  '<span>&#10;</span>',
  '<div></div>&#10;<div></div>',
  '<span></span>&#10;<span></span>',
  '<div style="user-select:none"/>&#10;<div style="user-select:none"></div>',
  '<span style="user-select:none"/>&#10;<span style="user-select:none"></span>',
  '<span contenteditable="false"></span>&#10;',
  '&#10;<span contenteditable="false"></span>',
  '(&#10;)',
  '&nbsp;&#10;&nbsp;',
  '&#8203;&#10;&#8203;',
  '&#10;&#8203;',
  '&#8203;&#10;',
];

div.addEventListener('textinput', show);
div.addEventListener('input', show);
div.addEventListener('keydown', maybeShow); // compensate for IE/Edge?

for (let no = 0; no < how.length; no++) {
  const html = how[no];
  const li = document.createElement('li');
  const la = document.createElement('label');
  const ra = document.createElement('input');
  ra.name = 'content';
  ra.type = 'radio';
  ra.id = la.for =  'ra' + no;
  ra.value = la.title = no;
  ra.addEventListener('click', reset);
  li.appendChild(la);
  la.appendChild(ra);
  const s = bad.indexOf(no) === -1 ? la : la.appendChild(document.createElement('del'));
  s.appendChild(document.createTextNode(' ' + html));
  ras.appendChild(li);
}

setTimeout(reset, 1);

function reset(e) {
  const no = Number(e.target.value);
  pre.innerHTML = '';
  div.innerHTML = how[no];
  show();
  div.focus();
}

function js(s) {
  return JSON.stringify(s).replace(/\u200B/g, '\\u200B');
}

function show() {
  pre.textContent += (show.shown = js(div.innerHTML)) + '\n';
}

// the weird microsoft textinput event doesn't fire for delete, backspace,
// enter, and maybe other essential editing keystrokes; fire show() after
// each keystroke if the editor's innerHTML differs from what's last shown.
function maybeShow() {
  setTimeout(function() {
    if...