alpine: Regression in handleAttributeBindingDirective: el.setSelectionRange is applied naively

Please bear with me as this is the first issue I’ve ever opened…

Unfortunately the fix from PR #367 has introduced a bug when binding a value to a subset of input types.

According to the MDN page on setSelectionRange():

Note that accordingly to the WHATWG forms spec selectionStart, selectionEnd properties and setSelectionRange method apply only to inputs of types text, search, URL, tel and password. Chrome, starting from version 33, throws an exception while accessing those properties and method on the rest of input types. For example, on input of type number: “Failed to read the ‘selectionStart’ property from ‘HTMLInputElement’: The input element’s type (‘number’) does not support selection”.

I was playing with some code on Firefox, updated to v2.3.0, and then started getting their version of the above Chrome error:

InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable

which is just ridiculously cryptic and took me a while to realize the problem!

https://github.com/alpinejs/alpine/blob/49de05c3278556cac5477a4ca668e9daf280d703/src/directives/bind.js#L45-L51

At the very least L49 should check that the type is not any of those types before attempting to set the selection.

Something like

(el === document.activeElement && ['text', 'search', 'url', 'password'].includes(el.type)) 

should fix it, but I don’t know if the developers would prefer to hoist this type check to the parent if/else block and only declare const cursorPosition for those types, and not also for everything else. Or at least move that const declaration inside the L49 if to optimize memory a bit.

Thoughts?

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 35 (16 by maintainers)

Commits related to this issue

Most upvoted comments

This is how i made my first contribution by @SimoTod 's invite. I think i will be more happy if you send your first PR 😃

@03juan Exactly, you found the issue and coded most of the solution (including the suggestion about selctionEnd and selectionDirection to preserve the selection). Muzaffer is more then happy to leave this with you if you have time to work on it so you can get the “inestimable” contributor badge. Welcome to the club. 👍

Yep, not a blocker but another reason we add tests is to avoid regressions. Your code works okay now, you saw it and the test won’t really add value niw but we’ll stop someone else from breaking it again in the future if they refractor that piece of code. For example, if we had had that test before, the regression would have not made into the release. I hope it makes sense.

To be honest, i did not test it because I’ve been busy lately. It’s probably less edge case than we think. I would expect it to break for checkboxes, radio buttons, etc. However, it’s an open source project so nobody has to feel guilty, regressions do happen sometimes expecially when a project doesn’t have a QA phase. Something to improve. Thanks everyone we can leave it with Caleb now, hopefully he’ll be able to tag a new release soon.

For example, if we had had that test before, the regression would have not made into the release. I hope it makes sense.

That’s how feel. Not sure if i should feel guilty about it. But this is a very sneaky bug that’s only happens in specific condition.

Probably more of a chain-of-review issue, where all PRs should be proven by tests before merging? Either way always a learning experience 😌

Thanks for the kind comments. I am having an issue getting my test to pass, though. I’ll post the PR as a draft and would appreciate your input on what I might be doing wrong.

@SimoTod
Update: I tried quickly and adding the fix in bind.js seems to work without using timeout. I’m happy for you to send a PR for this so you get recognised as contributor since you provided the initial solution.

This is how i made my first contribution by @SimoTod 's invite. I think i will be more happy if you send your first PR 😃

Happy to let @muzafferdede update his code with a new PR, the null test is a more efficient solution than instantiating and looping through an array on every bound value that hits the else.

Yes! @03juan appreciated for the bug hunt. 😃

It’s good enough for me. A comment mentioning that it’s due to a Safari issue when setting the value via code would probably help in the future so we remember why we are reinventing the wheel. 😃 (and it will help Caleb when he’s reviewing the PR)

Since initially we are storing selectionStart on cursorPosition, wouldn’t it be easier if we just check as:

if(el === document.activeElement && cursorPosition !== null) {

Great, thanks for the comments. I will give this PR a shot.

That looks like a good fix, would you mind submitting a PR (ideally with a test for this case)?