deshiknaves / caret-pos

Locate the current position of the caret. A plain JavaScript version of Caret.js.
MIT License
120 stars 15 forks source link

Offset not calculated correctly on contenteditable when caret is on an empty line #38

Open andreasdj opened 3 years ago

andreasdj commented 3 years ago

When using the caret-pos library to get the caret position with noShadowCaret set to true there is an issue with contenteditable elements.

When the caret is set to a blank line (i.e. no content) inside the contenteditable element the offset for the caret inside the element is not calculated which leads to a top value of NaN.

For now we have patched the getOffset method locally by doing two updates. We have updated the following check from: if (range.endOffset - 1 > 0 && range.endContainer !== element || hasCustomPos) { to: if (range.endContainer !== element || hasCustomPos) {

We have also set fixedPosition to 1 in this scenario (i.e. startOffset=0, endOffset=1). The reason is that there seems to be an additional issue within chrome (haven't tested other browsers) that leads to an incorrect bounding client rect calculation when startOffset and endOffset is set to 0 for the cloned RangeObject on a blank line.

This issue could be solved by passing the customPos option, but we don't want to pass the custom position in other scenarios other than when the caret is on a blank line.

For more information, see this fiddle: https://jsfiddle.net/khvcsw50/1 Use the arrow keys together with the console.log to see the different outputs from the position method on different lines.

andreasdj commented 3 years ago

Giving a follow up on this issue if anyone else would stumble across the same thing.

The reason we wanted to update the logic when determining the caret position without shadowCaret in the scenario of having a blank line was that the shadowCaret logic introduced another issue which caused the caret to jump back to the start of the contenteditable when entering text. The correct thing to do would probably be to fix that instead. Not sure I will have time to create a fiddle for it but we solved the issue we saw by normalize the parent dom node that the shadow caret was injected to.

We replaced: shadowCaret.parentNode.removeChild(shadowCaret);

with:

let parentNode = shadowCaret.parentNode; 
parentNode.removeChild(shadowCaret);
parentNode.normalize(); //glue broken text back together