@@ -15,7 +15,7 @@ import { useLocale } from '@/composables/locale'
1515import { useProxiedModel } from '@/composables/proxiedModel'
1616
1717// Utilities
18- import { computed , nextTick , onMounted , ref , shallowRef , toRef , watch , watchEffect } from 'vue'
18+ import { computed , nextTick , onMounted , ref , shallowRef , toRef , watch } from 'vue'
1919import { clamp , escapeForRegex , extractNumber , genericComponent , omit , propsFactory , useRender } from '@/util'
2020
2121// Types
@@ -131,37 +131,53 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
131131 )
132132
133133 const _inputText = shallowRef < string | null > ( null )
134- watchEffect ( ( ) => {
134+ const _lastParsedValue = shallowRef < number | null > ( null )
135+
136+ watch ( model , val => {
135137 if (
136138 isFocused . value &&
137139 ! controlsDisabled . value &&
138- Number ( _inputText . value ) === model . value
140+ Number ( _inputText . value ) === val
139141 ) {
140142 // ignore external changes while typing
141143 // e.g. 5.01{backspace}2 » should result in 5.02
142144 // but we emit '5' in and want to preserve '5.0'
143- } else if ( model . value == null ) {
145+ } else if ( val == null ) {
144146 _inputText . value = null
145- } else if ( ! isNaN ( model . value ) ) {
146- _inputText . value = correctPrecision ( model . value )
147+ _lastParsedValue . value = null
148+ } else if ( ! isNaN ( val ) ) {
149+ _inputText . value = correctPrecision ( val )
150+ _lastParsedValue . value = Number ( _inputText . value . replace ( decimalSeparator . value , '.' ) )
147151 }
148- } )
152+ } , { immediate : true } )
153+
149154 const inputText = computed < string | null > ( {
150155 get : ( ) => _inputText . value ,
151156 set ( val ) {
152157 if ( val === null || val === '' ) {
153158 model . value = null
154159 _inputText . value = null
160+ _lastParsedValue . value = null
155161 return
156162 }
157163 const parsedValue = Number ( val . replace ( decimalSeparator . value , '.' ) )
158- if ( ! isNaN ( parsedValue ) && parsedValue <= props . max && parsedValue >= props . min ) {
159- model . value = parsedValue
164+ if ( ! isNaN ( parsedValue ) ) {
160165 _inputText . value = val
166+ _lastParsedValue . value = parsedValue
167+
168+ if ( parsedValue <= props . max && parsedValue >= props . min ) {
169+ model . value = parsedValue
170+ }
161171 }
162172 } ,
163173 } )
164174
175+ const isOutOfRange = computed ( ( ) => {
176+ if ( _lastParsedValue . value === null ) return false
177+ const numberFromText = Number ( _inputText . value )
178+ return numberFromText !== clamp ( numberFromText , props . min , props . max )
179+ } )
180+
165181 const canIncrease = computed ( ( ) => {
166182 if ( controlsDisabled . value ) return false
167183 return ( model . value ?? 0 ) as number + props . step <= props . max
@@ -474,6 +490,7 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
474490 v-model = { inputText . value }
475491 v-model :focused = { isFocused . value }
476492 validationValue = { model . value }
493+ error = { isOutOfRange . value || undefined }
477494 onBeforeinput = { onBeforeinput }
478495 onFocus = { onFocus }
479496 onBlur = { onBlur }
0 commit comments