@@ -15,7 +15,7 @@ import { useLocale } from '@/composables/locale'
15
15
import { useProxiedModel } from '@/composables/proxiedModel'
16
16
17
17
// Utilities
18
- import { computed , nextTick , onMounted , ref , shallowRef , toRef , watch , watchEffect } from 'vue'
18
+ import { computed , nextTick , onMounted , ref , shallowRef , toRef , watch } from 'vue'
19
19
import { clamp , escapeForRegex , extractNumber , genericComponent , omit , propsFactory , useRender } from '@/util'
20
20
21
21
// Types
@@ -131,37 +131,53 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
131
131
)
132
132
133
133
const _inputText = shallowRef < string | null > ( null )
134
- watchEffect ( ( ) => {
134
+ const _lastParsedValue = shallowRef < number | null > ( null )
135
+
136
+ watch ( model , val => {
135
137
if (
136
138
isFocused . value &&
137
139
! controlsDisabled . value &&
138
- Number ( _inputText . value ) === model . value
140
+ Number ( _inputText . value ) === val
139
141
) {
140
142
// ignore external changes while typing
141
143
// e.g. 5.01{backspace}2 » should result in 5.02
142
144
// but we emit '5' in and want to preserve '5.0'
143
- } else if ( model . value == null ) {
145
+ } else if ( val == null ) {
144
146
_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 , '.' ) )
147
151
}
148
- } )
152
+ } , { immediate : true } )
153
+
149
154
const inputText = computed < string | null > ( {
150
155
get : ( ) => _inputText . value ,
151
156
set ( val ) {
152
157
if ( val === null || val === '' ) {
153
158
model . value = null
154
159
_inputText . value = null
160
+ _lastParsedValue . value = null
155
161
return
156
162
}
157
163
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 ) ) {
160
165
_inputText . value = val
166
+ _lastParsedValue . value = parsedValue
167
+
168
+ if ( parsedValue <= props . max && parsedValue >= props . min ) {
169
+ model . value = parsedValue
170
+ }
161
171
}
162
172
} ,
163
173
} )
164
174
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
+
165
181
const canIncrease = computed ( ( ) => {
166
182
if ( controlsDisabled . value ) return false
167
183
return ( model . value ?? 0 ) as number + props . step <= props . max
@@ -474,6 +490,7 @@ export const VNumberInput = genericComponent<VNumberInputSlots>()({
474
490
v-model = { inputText . value }
475
491
v-model :focused = { isFocused . value }
476
492
validationValue = { model . value }
493
+ error = { isOutOfRange . value || undefined }
477
494
onBeforeinput = { onBeforeinput }
478
495
onFocus = { onFocus }
479
496
onBlur = { onBlur }
0 commit comments