3

I am aware that there are a lot of existing questions about getting the caret position in ContentEditable elements. Almost all of those existing solutions provide the caret position with respect to the textContent. Some examples are this one or this one.

We are currently developing two WebExtensions to do autocorrection as the user types. For example, if the user types :), it could autocorrect it to . For the autocorrection to work, it needs to get the caret position with respect to the innerText. The textContent can include whitespace characters and other differences that do not actually appear when the text is rendered, which breaks the autocorrect feature.

Our current method is partially adapted from this answer: https://stackoverflow.com/a/29258657, which provides the caret position with respect to the innerHTML. It clones the element, inserts the null character, determines the index and then removes the null character:

// document.designMode is handled the same, see  https://github.com/rugk/unicodify/issues/54
if (target.isContentEditable || document.designMode === "on") { 
     target.focus(); 
     const _range = document.getSelection().getRangeAt(0); 
     if (!_range.collapsed) { 
         return null; 
     } 
     const range = _range.cloneRange(); 
     const temp = document.createTextNode("\0"); 
     range.insertNode(temp); 
     const caretposition = target.innerText.indexOf("\0"); 
     temp.parentNode.removeChild(temp); 
     return caretposition; 
}

See our original source code for the full context. This method seems to work fine on 99% of websites, but breaks on Twitter. The cursor is constantly reset to the beginning of the line as the user is typing, which scrambles the text (see the corresponding issue for more information). We are guessing that Twitter does not like the null character, but we tried with other nonprinting characters and had the same issue.

We are looking for another method to determine the caret position with respect to the innerText that will work on all websites, including on Twitter. It needs to support recent versions of both Firefox and Chrome, including Firefox ESR. It also needs to be performant, since it runs on every keypress.


Cross-posted on Mozilla's Discourse.

rugk
  • 4,755
  • 2
  • 28
  • 55

1 Answers1

0

Steps


I get the Selection and Range from the target element

const selection = document.getSelection();
const range = document.createRange();

After checking if the Range is collapsed, your base code to insert the null character and getting the caret's position

const temp = document.createTextNode("\0");
selection.getRangeAt(0).insertNode(temp);
caretPosition = target.innerText.indexOf("\0");
temp.parentNode.removeChild(temp);

I then use this snippet to set the caret's position, which should be the fix to Twitter

range.setStart(selection.focusNode, selection.focusOffset);
range.collapse(false);

selection.removeAllRanges();
selection.addRange(range);

const target = document.getElementById('text');

target.addEventListener('keyup', () => {
  let caretPosition = null;
  const selection = document.getSelection();
  const range = document.createRange();
  
  if (range.collapsed) {
    const temp = document.createTextNode("\0");
    selection.getRangeAt(0).insertNode(temp);
    caretPosition = target.innerText.indexOf("\0");
    temp.parentNode.removeChild(temp);

    range.setStart(selection.focusNode, selection.focusOffset);
    range.collapse(false);

    selection.removeAllRanges();
    selection.addRange(range);
  }
  
  console.log(JSON.stringify({ caretPosition }));
});
#text {
  border: 1px solid;
  padding: 1rem;
  width: 50vw;
  height: 50vh;
}
<h3>ContentEditable Div</h3>
<div id="text" contenteditable="true">This text can be edited by the user.<br> Some <strong>bold</strong> and <em>italic and <strong>bold</strong></em> text.</div>
a.mola
  • 3,883
  • 7
  • 23
  • [We have tried implementing it like this](https://github.com/rugk/unicodify/issues/62#issuecomment-916738896), but it does fail, i.e. it does not work. When this is used with a ContentEditable Div, there is an error for each keypress, because the caret position is wrong. [Tested here with the contenteditable.](https://rugk.github.io/unicodify/). – rugk Sep 10 '21 at 15:11
  • @rugk. Edited and fixed errors. Let me know if it's fine – a.mola Sep 10 '21 at 19:47
  • [Here is another JSFiddle to reproduce the error](https://jsfiddle.net/tcomh4zb/), Now also adjusted [for your edit](https://jsfiddle.net/3mskc146/) and besides the fact that JsFiddle shows some linting errors in your code it also unfortunately seems to fail for a line break now, as it seems. @a.mola – rugk Sep 11 '21 at 09:03
  • Thanks, the caret position now works correctly and is correctly returned, however contrary to your assumptions, it still breaks (on) Twitter. Here is a [test branch/Pull Request](https://github.com/rugk/unicodify/pull/69) you can use in your browser to test it. – rugk Sep 15 '21 at 22:10
  • Alright, how do I use the [test branch/Pull Request](https://github.com/rugk/unicodify/pull/69) to test the way it works with Twitter @rugk – a.mola Sep 16 '21 at 07:57
  • [That is described here](https://github.com/TinyWebEx/common/blob/master/CONTRIBUTING.md#coding). The TL;DR: basically is: Clone with submodules and load the `manifest.json` into your browser as a browser extension. – rugk Sep 17 '21 at 18:32
  • @rugk; I haven't found a way to solve this; Any luck? – a.mola Sep 24 '21 at 20:27