Last active
January 3, 2022 21:00
-
-
Save mzechmeister/d0333d6e46746efc2f06e61547d69047 to your computer and use it in GitHub Desktop.
pseudo editable field (code mirror lite)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"/> | |
<style> | |
#cursor { | |
display: inline; | |
} | |
#cursor.collapsed { | |
animation: blink 1s; | |
outline: 1px solid red; | |
animation-iteration-count: infinite; | |
} | |
span { | |
color: green; | |
padding: 5px; | |
} | |
span:before { | |
content: "5"; | |
vertical-align: super; | |
font-size: smaller; | |
} | |
@keyframes blink { | |
50% { outline-color: #ffffff00; } | |
} | |
button { | |
font-size: large; | |
font-weight: bold; | |
} | |
</style> | |
</head> | |
<body> | |
<textarea id="ta" style="height:150px">$The top element is a textarea and the div at the bottom a pseudo editable field.$ Sometime mouse selection does not work.$</textarea> | |
<button onclick="ta.focus(); document.execCommand('undo', false, null)">↶</button> | |
<button onclick="ta.focus(); document.execCommand('redo', false, null)">↷</button> | |
<div id="cursor"></div> | |
<div id="mydiv" contenteditable></div> | |
<script> | |
cursor = document.getElementById('cursor') | |
function parse(t) { | |
return t.replace(/\$/g, '<span>$</span>') | |
} | |
function pseudoCursor() { | |
collapsed = ta.selectionStart == ta.selectionEnd | |
pointA = getPoint(ta.selectionStart) | |
cursor.classList.toggle('collapsed', collapsed) | |
if (collapsed) { | |
// indicate the ta position in editable div | |
let range = document.createRange() | |
range.setStart(...pointA) | |
range.insertNode(cursor) | |
mydiv.normalize() // triggers again selection change, if new cursor after old cursor | |
} else if (!block) { | |
// hand over the ta selection to the editable div | |
point = getPoint(ta.selectionEnd) | |
if (ta.selectionDirection == "backward") sel.setBaseAndExtent(...point, ...pointA) | |
else sel.setBaseAndExtent(...pointA, ...point) | |
} | |
} | |
function getPos(node, offset) { | |
let range = document.createRange() | |
range.setStart(mydiv, 0) | |
range.setEnd(node, offset) | |
pos = range.toString().length | |
return pos | |
} | |
function getPoint(offset) { | |
mydiv.after(cursor) // avoid miscounting with inline cursor, normalisation can be skipped | |
for (node of [...mydiv.childNodes]) { | |
len = (node.innerText || node).length | |
if (offset <= len) break | |
offset -= len | |
} | |
return [node, offset] | |
} | |
sel = window.getSelection() | |
block = false | |
document.addEventListener("selectionchange", function(e) { | |
cursor.classList.remove("collapsed") | |
if (document.activeElement == ta) pseudoCursor() | |
prev_sel = [getPos(sel.anchorNode, sel.anchorOffset), getPos(sel.focusNode, sel.focusOffset)] | |
if (!block && document.activeElement == mydiv && prev_sel[1] == prev_sel[0]) { | |
ta.setSelectionRange(...prev_sel) | |
} | |
}) | |
// Firefox ~68 + enable dom.select_events.textcontrols.enabled | |
ta.addEventListener("selectionchange", function(e) { | |
pseudoCursor() | |
e.stopPropagation() | |
}) | |
ta.oninput = e => { | |
mydiv.innerHTML = parse(ta.value) | |
if (e.inputType == 'deleteContentForward') | |
pseudoCursor() | |
// other keys call pseudoCursor via selectionchange | |
} | |
mydiv.ondrop = function(e) { | |
edata = null | |
if (document.caretRangeFromPoint) { | |
range = document.caretRangeFromPoint(e.clientX, e.clientY) | |
droppos = getPos(range.startContainer, range.startOffset) | |
edata = e.dataTransfer.getData('Text') | |
} else | |
droppos = getPos(e.rangeParent, e.rangeOffset) | |
} | |
mydiv.onpaste = function (e) { | |
// at least for edge, not firefox | |
edata = (e.clipboardData || window.clipboardData).getData('Text') | |
} | |
mydiv.onmouseup = function (e) { | |
sel.isCollapsed && ta.focus() | |
} | |
mydiv.oninput = function(e) { | |
console.log(e.inputType) | |
block = true | |
ta.focus() | |
if (prev_sel[0] > prev_sel[1]) | |
prev_sel = prev_sel.reverse() | |
if ("deleteByCut deleteByDrag deleteContentBackward deleteContentForward".includes(e.inputType)) { | |
ta.setSelectionRange(...prev_sel) | |
document.execCommand('delete') | |
} else { | |
data = e.data || (e.dataTransfer && e.dataTransfer.getData('Text')) || edata | |
edata = null | |
if ("insertFromPaste insertText".includes(e.inputType)) { | |
ta.setSelectionRange(...prev_sel) | |
} else if (e.inputType == "insertFromDrop") { | |
if (droppos > prev_sel[0]) droppos -= prev_sel[1] -prev_sel[0] | |
ta.setSelectionRange(droppos, droppos) | |
droppos = null | |
} | |
document.execCommand('insertText', false, data) | |
} | |
block = false | |
} | |
mydiv.innerHTML = parse(ta.value) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To inspect codemirror search for the textarea element and inspect the attached events.
https://github.com/codemirror/CodeMirror/blob/c1941628cdda92c32977b3a26b0a0b70c9a8f9b5/src/input/TextareaInput.js#L224