Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix(VNumberInput): prevent NaN & properly handle js number quirks #20211

Merged
merged 14 commits into from
Jul 30, 2024

Conversation

yuwu9145
Copy link
Member

@yuwu9145 yuwu9145 commented Jul 22, 2024

fixes #19798
fixes #20171

Description

Markup:

<template>
  <v-app>
    <v-container>
      <h1>{{ typed }}</h1>
      <v-number-input
        label=""
        clearable 
        v-model="typed"
        :hideInput="false"
        :inset="false"
        :min="5"
        :max="125"
        :step="0.32"
        validate-on="lazy blur"
        :messages="`What I typed: ${typed}`"
      ></v-number-input>
    </v-container>
  </v-app>
</template>

<script setup>
  import { ref } from 'vue'
  // function onKeydown(event) {
  //   if (event.key === 'Backspace') {
  //     typed.value = typed.value.slice(0, -1)
  //   } else {
  //     typed.value += event.key
  //   }
  // }
  const typed = ref(-5)
</script>




@J-Sek
Copy link
Contributor

J-Sek commented Jul 22, 2024

I can see number of test cases failing. In particular min/max enforcement. Did you mean to move it inside onBeforeinput? If so, the tests need to be updated as they check range enforcement even without user interaction.

There is also a bit of inconsistency in the emitted value type. Up/Down emit number while typing emits string. I am not sure which way is intended for this component.

  • string model makes it somehow consistent with <input type='number' /> when interacting through vanilla JS
  • once native <input type='number' /> is used with Vue v-model the value is of type number and that's what developers may expect when binding to v-number-input

The trajectory of the changes suggests we cannot go back to emitting number.

  • users can use v-model.number="..." to transform values
    • It is not ideal though, as I get - as string out of it despite the modifier
  • some onBlur action to clear leading and trailing zeros could be welcomed

Issues:

  • - gets removed after typing 0 right after (when I use v-model.number=...)
  • leading and trailing 0 get removed if model changes (when I use v-model.number=...) (maybe irrelevant, but caught my eye regardless, because it interrupts typing)
  • 0 is required to type . (maybe irrelevant)
  • user cannot paste any value that has even the smallest divergence from underlying regex, so numbers copied with locale formatting, or spaces surrounding the value - may lead to "this input is broken" from user's perspective

@J-Sek
Copy link
Contributor

J-Sek commented Jul 24, 2024

I am glad to see some progress :) We don't need the .number modifier, but it is a bit of a trade nonetheless.

Couple of weird behaviors I think are not exactly obvious, but may be important:

  • (observation) component does not emit while typing - only on blur or when using up/down arrows
  • (bug) null values get replaced with 0 - clearable does not work
  • (bug) when I change value and use up/down arrows right after, the step is modifying last emitted value (e.g. 55{up} » 1)

@yuwu9145 yuwu9145 marked this pull request as ready for review July 25, 2024 05:21
@yuwu9145 yuwu9145 enabled auto-merge (squash) July 25, 2024 05:22
@yuwu9145 yuwu9145 self-assigned this Jul 25, 2024
@yuwu9145 yuwu9145 requested review from johnleider and KaelWD July 25, 2024 05:23
@yuwu9145 yuwu9145 added this to the v3.6.x milestone Jul 25, 2024
@yuwu9145 yuwu9145 disabled auto-merge July 25, 2024 07:01
@J-Sek
Copy link
Contributor

J-Sek commented Jul 25, 2024

<v-number-input v-model="value" clearable />
<code>value: {{ value ?? "(empty)" }} | {{ typeof(value) }}</code>
---
const value = ref<number | null>(null)

Clearable works, but I still get initial 0 instead of empty field.

There is one new bug

  • typing emits string and if I use up arrow (keyboard), the action has no effect, while down decrements normally

While can see it is very easy to patch with a single + to cast model before adding step, I am curious if emits with string type should be allowed... Developers might be tempted to use v-model.number= and will get problems mentioned earlier (especially problems when typing small negative float values).

@yuwu9145
Copy link
Member Author

typing emits string

Fixed, it should only emit number or null

@J-Sek
Copy link
Contributor

J-Sek commented Jul 26, 2024

All VTextField's v-model={ model.value } emits are strings, so the setter for computed model seems rather pointless. The intent could be more clear with going back to modelValue={ model.value }, because only onChange handler and toggleUpDown actually emit numbers.

Regardless, I think internals will change yet again, but I don't want to stress about uncovered use case that bounces between broken and working states. Could you include following test scenarios? 😃

{ text: '123{uparrow}', expected: '124' },
{ text: '521{downarrow}', expected: '520' },

@yuwu9145
Copy link
Member Author

yuwu9145 commented Jul 26, 2024

All VTextField's v-model={ model.value } emits are strings, so the setter for computed model seems rather pointless. The intent could be more clear with going back to modelValue={ model.value }, because only onChange handler and toggleUpDown actually emit numbers.

Regardless, I think internals will change yet again, but I don't want to stress about uncovered use case that bounces between broken and working states. Could you include following test scenarios? 😃

{ text: '123{uparrow}', expected: '124' },
{ text: '521{downarrow}', expected: '520' },

The reason of double way binding is: in the case of v-model=5, min=5 & max=10 and they type "-1", I want input to be enforced back to 5. Single way binding lose that enforcement because external v-model isn't mutated

Could be a nice feature in useProxiedModel

https://play.vuetifyjs.com/#eNpdUEFugzAQ/MrIlyRSKOIakaj9Q3sqPbiwBKvGtsyaNkL8vTYkUZTLyjs7np3Zz0kMvs7fnHsZA4mDKJl6pyXTqTJAOWbSueW5NLU1LJUhf4Ui2BWn94sjSHPhTpkzLHfkwZ00KPZQjF+lNVoZy7esf8AWRZnHb3eJMWP646xVpBscetuQzkapAx0r0Q/nSuA1uCZ6ep6l7mOZRE5+s5k/+0zIGqPMH+LFdqi9coyBOKwpVe+sZ0zw1GJG622PTbzMJtGBqDswoiccE2Nb7Fa8DaZmZQ0eLG13mOa0cl0S5cW8N0HrpXz9A1y2f1E=

@J-Sek
Copy link
Contributor

J-Sek commented Jul 26, 2024

The reason of double way binding is: in the case of v-model=5, min=5 & max=10 and they type "-1", I want input to be enforced back to 5.

And you achieve this with onChange handler. Check the example below. By removing @change='toNumber' you will get back to the same behavior as :model-value='...', proving it is not v-model that helps you run clampModel, but onChange handler.

https://play.vuetifyjs.com/#eNpdkMFy4jAQRH+l4wumAri4spjd/YDltLeQg2LGoIosqeSxkhTlf8/ICgnJRR71jHqe++FS9KGp/nq/igMVm2LL1HmjmHYHC2zjUnk/ldOlcZaVthQ+JBHP693/N09Q9o3P2p7g+EwBfFYW6wU040Ubg1bJ8aSaZ7DDelvJs0+LuGR65WWryRwRl507kqkPRdef/qXyUOBPI3YnEpHdfuieKIhYXbmqn2BJydzb6uZ/5No3QXtGTzx4GPFMlv2hmJ7pzrvAuCBQu0DjOj8wHTGiDa7DTAKaJRNIy/YM4UOdZsv1/Js6UUvr6lBeMuiJeINyjnqXplZRmYEWuSVAZZzjY1BQWpQssboWEXd1jVnPQdKd3czgy0WWxas85mKcnEchS992sA1rZyX9nF9JkT+9MnkUF1FXrIKQZuPcv91zp/u92pf3Qvsb9xEb2MGYX9OylHdOWPIsxkXqTMfjO2KHxeo=

@yuwu9145
Copy link
Member Author

@J-Sek feel free to make a tweaking PR to fix-19798-origin, I will check it out tomorrow in my time

@J-Sek J-Sek mentioned this pull request Jul 27, 2024
@yuwu9145 yuwu9145 requested a review from KaelWD July 28, 2024 02:35
@yuwu9145
Copy link
Member Author

If no more comments, ready to merge in

@J-Sek
Copy link
Contributor

J-Sek commented Jul 28, 2024

Edit: I checked out the wrong branch, sorry min and max are not enforced in some scenarios because model can become out-of-sync with VTextField's value

There is still a problem of using up/down while typing. 510{uparrow} does not become 511. I hope it can be addressed later.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
4 participants