yelouafi / petit-dom

minimalist virtual dom library
MIT License
506 stars 36 forks source link

keep input-cursor position between patches? #10

Closed zaceno closed 6 years ago

zaceno commented 6 years ago

Please see this example on codepen: https://codepen.io/zaceno/pen/BxaENr?editors=0010

I'm rendering a plain text-input, and expect to capture the value every oninput. Each time I update (patch) the ui. (A quite normal scenario, I think. For every input, you want to do some validation, helpful formatting, et c. Hence: update the ui).

It works fine except when you try to enter something in the middle or beginning of the input. In that case, the cursor jumps to the end of the input. Not entirely unexpected, since I'm rerendering and the previous cursor position isn't kept track of anywhere.

So, I could correct for this by using a custom component class where I can store the cursor position in the component state between renders. But that seems like overkill for something so trivial.

Is there some simpler way I could handle this, so I can still use a plain h('input', {...}) without making a component? If not, may I suggest keeping track of cursor position as a feature-request?

yelouafi commented 6 years ago

I wasn't able to reproduce the issue with the above example.

Since this line oninput: ev => setValue(ev.target.value) just sets the state value to the one already existing on the DOM input, the library will skip updating the DOM input (since in this case they are the same). See https://github.com/yelouafi/petit-dom/blob/5412b18ad26108762ebf5624ac1c5bd70c578e57/src/vdom.js#L124-L126.

So as long as the state value is equal to the DOM input value, the lib will not touch the DOM (so cursor jumps shouldn't occur here)

However I can reproduce the issue by setting the state value to something else than the one currently in the DOM input. For example

function setValue(x) {
    value = x + "o"
    /*
        In a real application, capturing the input
        might lead to validation or other reasons
        to update the ui. So we do:
    */
    updateUI()
}

In this case effectively the lib will change the DOM input value which causes the cursor position to jump to the end.

may I suggest keeping track of cursor position as a feature-request?

Yes of course, but (for the reasons below) I'd prefer to do this in the same way the lib is managing other interactive props (like 'value', 'checked'...). It keeps a map of those props here

https://github.com/yelouafi/petit-dom/blob/5412b18ad26108762ebf5624ac1c5bd70c578e57/src/vdom.js#L4

And those props are updated after attributes

We can simply add selectionStart and selectionEnd to that list; and let the developer choose if he wants to keep track of the cursor position on the state.

(A quite normal scenario, I think. For every input, you want to do some validation, helpful formatting, et c. Hence: update the ui).

IMHO I'm not really sure it's a good idea to change the DOM input value while the user is currently typing it. Sure I understand that we would like for example to format a numeric value, but this formatting typically should occur after the user has finished typing (e.g. blur event) to avoid any interference with the user typing process.

I'm not also sure restoring cursor position between updates will be sufficient to solve the UX issue. For example, formatting the numeric value while the user is typing, keeping the cursor position can lead to weird UX issues. Here is a forked example (https://codepen.io/anon/pen/KRwWdr?editors=0010) that restores the cursor position after each update : try typing '123' then observe what happens when typing '4'.

But perhaps there maybe some cases I didn't think of where this could be useful.

cjh9 commented 6 years ago

It works fine except when you try to enter something in the middle or beginning of the input. In that case, the cursor jumps to the end of the input.

I'm not able to reproduce this either...

zaceno commented 6 years ago

Hmm... guess what: in Chrome, I can't reproduce it either 😅 . Go figure. I was working in Safari when I noticed it and never bothered to check other browsers.... I wonder what the difference could be? Works fine in Firefox too btw.

@yelouafi Thank you for the thoughtful response! Your explanation makes sense. It should just work - and clearly does in most cases 🤷‍♂️. So I withdraw my feature request :)

Still... I wonder what's up with Safari?!

(It might not even be Safari-specific. I've noticed sometimes codepens on Safari exibit behavior that is not seen when the same code is run in a standalone web-page in Safari ... I should test that )

cjh9 commented 6 years ago

I just tried with IOS 11, the bug is both on Safari and Chrome (the later is just using a webview) But then I tried to upgrade to IOS 11.3 and the upgrade failed so I have to restore with Itunes. To bad my mac only has USB C ports. The day starts well 😂

zaceno commented 6 years ago

I've been able to confirm that it is not codepen related. The problem appears even when the script is loaded from a plain html page. Also, I disabled babel. same thing. All in Safari only (so far. Haven't been able to try Edge or IE11)