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 <br/> tag or newline character, &#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/>',
' ',
'<div> </div>',
'<span> </span>',
'<div></div> <div></div>',
'<span></span> <span></span>',
'<div style="user-select:none"/> <div style="user-select:none"></div>',
'<span style="user-select:none"/> <span style="user-select:none"></span>',
'<span contenteditable="false"></span> ',
' <span contenteditable="false"></span>',
'( )',
' ',
'​ ​',
' ​',
'​ ',
];
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...