So now I have run into the classic problem of making changes to a contenteditable
from code resulting in the caret not knowing where it should be, so jumping back to the start of the line.
This can be solved with javascript that is specifically wrapped around the updates to the elements text contents:
The problem in Elm is that the updates to the text happen in the virtual dom code, so I cannot modify its behaviour to save and restore the caret position like this.
To work around this I had to do something a bit icky. Essentially, I capture the mutation event, and figure out from that what the users editing intention was, and insert the text in the right place in the Elm-side Model. When the view is re-rendered, the line is replaced entirely with the correct text. The contenteditable
caret is made invisible and my own custom caret is rendered in Elm. It does not really matter where on the line the browsers caret is, so long as it is on the right line.
I also ensure that when edits happen on a line, the whole line is forced to re-render. This is done with Html.Keyed
by bumping a counter on each edit, so that a unique key can be set against the edit line. Since this changes on each edit, the virtual dom forces a redraw of the line. This helps when syntax highlighting is triggered, since that makes structural changes to the DOM, in the view
code.
Iâm definitely getting into brittle code territory with this, so donât feel great about it - am I relying on specifics of the vdom implementation? and is this going to work nicely on all browsers? Canât really see another way though.
===
In that stack overflow question, the code to maintain the caret position whilst making changes to the text looks like:
var position = getCaretCharacterOffsetWithin(input.get(0));
var text = input.text();
text = text.replace(new RegExp('\\btest\\b', 'ig'), '<span style="background-color: yellow">test</span>');
input.html($.parseHTML(text));
setCaretPosition(input.get(0), position);
A pseudo-code outline is:
- Get the caret position.
- Update the text/html in the element.
- Set the caret back to the correct position.
The Elm virtual DOM just does step 2.
I also tried to complete the algorithm by storing the current caret position in my Model (step 1), and then passing that as an attribute into the elm-editor
custom element and have it do step 3.
The problem with that is that the attribute seems to get updated by the virtual-dom
first, and then the contents. So it gets steps 2 and 3 the wrong way around. The result is that the caret ends up at the start of the line.
I also tried using an animation frame event to do step 3. It kind of works, but you can see the caret ever so briefly appears at the start of the line before jumping back to the correct place. The result is, if you type fast or fat finger the keyboard, you often get characters appearing at the start of the line, instead of where they are meant to go. I wasnât really expecting this approach to workâŠ
===
Time to step back a bit I think and work out what a more robust solution is going to look like.