JSFiddle - React, Tailwind, and code Playground
by RichieHindle
HTML
<div contenteditable='true' id='e'><div>Content</div></div>
CSS
#e {
font-family: "Courier New";
}
JavaScript
function getCursorPosition($elt) {
// Remember the orignal range, so we can put it back.
var selection = window.getSelection();
var origRange = selection.getRangeAt(0);
// Create a range that we can manipulate.
var r = origRange.cloneRange();
selection.removeAllRanges();
selection.addRange(r);
// Where a line starts with spaces, Firefox uses multiple adjacent text
// nodes to represent that, so we need to add their widths to r.endOffset.
var prefixWidth = 0;
if (r.endContainer.nodeType == document.TEXT_NODE) {
var node = r.endContainer.previousSibling;
while (node && node.nodeType == document.TEXT_NODE) {
prefixWidth += node.length;
node = node.previousSibling;
}
}
// We now know the column number.
var column = r.endOffset + prefixWidth;
// To get the row number, we must get the HTML of the content as far as
// the cursor, then count the line breaks.
r.setStart($elt[0].firstChild, 0); // Start of first <div>.
var fragment = r.cloneContents(); // No innerHTML on fragments,
var tmp = $("<div></div>"); // hence we need to poke it into.
tmp.html(fragment); // an element to get its HTML.
var s = tmp[0].innerHTML.toLowerCase();
// We've finished with our range - put back the original.
selection.removeAllRanges();
selection.addRange(origRange);
// Count the newlines in the HTML.
s = s.replace(/&[a-z0-9]+;/g, "x"); // Entities are one character.
s = s.replace(/<\/[^>]+>/g, ""); // Remove closing tags.
s = s.replace(/<div[^>]*>\s*<br[^>]*>/g, "\n"); // <div><br> is one newline.
s = s.replace(/<br[^>]*>/g, "\n"); // <br> means newline.
s = s.replace(/<div\b[^>]*>/g, "\n"); // <div> means newline.
s = s.replace(/<[^>]+>/g, ""); // Remove all other tags.
s = s.replace(/^\n/, ""); // The opening <div>...