3

As I was reinventing the wheel for fun and profit, I isolated an interesting behavior that appears to only occur in Android Chrome.

I am formatting a text input as the user types, which means setting the input value, losing the caret position. No problem - just track the caret position, and set it back after you change the value (adjusting for added/removed characters) in the input event callback.

Works great! Except for Android Chrome (Android 7, Chrome 58.0.3029.83) - I set the caret position, observe the caret position where I expect - then on keyup event it is magically moved - after input event but before keyup event.

Tracking caret positions

You can see this for yourself in this (exceedingly simplified) fiddle. I even output the relevant data on screen so you don't have to hook up a debugger to see it.

Type "abcd" and get:
Numbers are caret positions from this.selectionStart
D = keyDown, I = Input, C = position Changed to, U = keyUp

D: 0 | I: 0 | C: 1 | U: 1
D: 1 | I: 1 | C: 2 | U: 2
D: 2 | I: 2 | C: 3 | U: 3
D: 3 | I: 3 | C: 5 | U: 4 //<- This 4 is the culprit

Where every other browser tested (IE11, iOS Safari, Desktop Chrome) gives you:

D: 0 | I: 1 | C: 1 | U: 1
D: 1 | I: 2 | C: 2 | U: 2
D: 2 | I: 3 | C: 3 | U: 3
D: 3 | I: 4 | C: 5 | U: 5

Is there an event in there I'm missing? It's not in the docs that I can find, though I know virtual keyboards get a little janky. I'd love to find out exactly where this is happening so I can nuke it.


Also tried

beforeinput, compositionstart, and compositionend events. The only one that is triggered is compositionstart, between the keydown and input events. It shows the caret position I would expect to see at that time.

Typing "abcd" then hitting backspace in Android Chrome gives you:

D: 4 | I: 3 | C: 5 | U: 5

Seeming to indicate that whatever is moving the caret did not do so in this case.


Not looking for a workaround. The workaround is to set the caret again in the keyup event. I'm asking to see if anyone knows a less-wrong answer.

Randy Hall
  • 7,716
  • 16
  • 73
  • 151
  • 1
    Are you quite sure the values you show for android chrome are what you actually see? You say you see `D: 0 | I: 1 | C: 1 | U: 1` but I see `D: 0 | I: 0 | C: 1 | U: 1` – Clonkex Jul 06 '17 at 03:39
  • @Clonkex quite certain. What version android and chrome are you using? And what are you getting for the fourth row? – Randy Hall Jul 06 '17 at 13:08
  • @Clonkex wow okay less certain now. Updating... – Randy Hall Jul 06 '17 at 13:11
  • Edited my answer with an attempt at a workaround. Hopefully that can be useful to you, but I think there must be better ways of doing phone number auto-formatting. – Clonkex Jul 07 '17 at 01:13

1 Answers1

1

As you can see, typing on soft keyboards in Android Chrome doesn't update the caret position in the input event as PC Chrome does. This is pretty typical of web development; there's always one case where it just doesn't work the same way.

D: 0 | I: 0 | C: 1 | U: 1

Based on my own testing, the caret position is updated in input when pasting, but when typing it's only updated in keyup. This means if you copy a single letter and paste that repeatedly, when you get to the fourth paste your textbox formatting code will work as expected (and as it does on PC).

This also means that if you want your code to work reliably you'll need to keep track of whether the caret is updated in input, but also preemptively update it in input since keyup won't fire if you're pasting. This could be difficult, however, because keydown also doesn't fire when pasting so you won't know the previous caret position.

You say you're not looking for a workaround, but based on the edit to your post I suspect you now need one. I'm not sure what a good workaround would be. Possibly tracking caret position changes and storing them manually (maybe in a data attribute on the textbox) would be your best bet. The real difficulty comes from the fact that when pasting, you could be pasting an arbitrary number of characters so you can't just assume that if input is fired when keydown isn't, use currentCaretPosition - 1.


After a bit of experimentation, this is the workaround I came up with:

https://jsfiddle.net/smfaenvb/10/

It's not perfect, but it does make it work on Android Chrome the same way your original test code worked on PC Chrome.

Clonkex
  • 3,373
  • 7
  • 38
  • 55
  • I might try to come up with a good workaround myself actually. I'll edit my post if I succeed. – Clonkex Jul 06 '17 at 23:15
  • Interestingly, adding `stopPropagation`, `preventDefault`, and `return false` to the keyup event still does not prevent the update. It really makes me think there's a missing event, but maybe it really is just the browser doing things between events – Randy Hall Jul 07 '17 at 15:27
  • Also see my working setup https://jsfiddle.net/freer4/uz9hr6b6/ - basically the same idea as your workaround - track the position, then set it again on keyup OR keydown, since concurrent keydowns can happen before the next keyup. – Randy Hall Jul 07 '17 at 15:29