make validation smart, show invalid after first onBlur

This commit is contained in:
einhornimmond 2025-07-25 12:29:43 +02:00
parent 7f5569439d
commit f1052409c0
2 changed files with 38 additions and 4 deletions

View File

@ -178,7 +178,7 @@ const validationSchema = computed(() => {
amount: number()
.required()
.transform((value, originalValue) => {
if (typeof originalValue === 'string' && originalValue !== '') {
if (typeof originalValue === 'string') {
return Number(originalValue.replace(',', '.'))
}
return value

View File

@ -9,7 +9,8 @@
:required="!isOptional"
:label="label"
:name="name"
:state="valid"
:state="smartValidState"
@blur="afterFirstInput = true"
@update:modelValue="updateValue"
>
<BFormInvalidFeedback v-if="errorMessage">
@ -19,7 +20,7 @@
</template>
<script setup>
import { computed, ref, watch } from 'vue'
import { computed, ref, watch, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import LabeledInput from './LabeledInput'
import { translateYupErrorString } from '@/validationSchemas'
@ -42,9 +43,29 @@ const props = defineProps({
const { t } = useI18n()
const model = ref(props.modelValue)
const model = ref(props.modelValue !== 0 ? props.modelValue : '')
// change to true after user leave the input field the first time
// prevent showing errors on form init
const afterFirstInput = ref(false)
const valid = computed(() => props.rules.isValidSync(props.modelValue))
// smartValidState controls the visual validation feedback for the input field.
// The goal is to avoid showing red (invalid) borders too early, creating a smoother UX:
//
// - On initial form open, the field is neutral (no validation state shown).
// - If the user enters a value that passes validation, we show a green (valid) state immediately.
// - We only show red (invalid) feedback *after* the user has blurred the field for the first time.
//
// Before first blur:
// - show green if valid, otherwise neutral (null)
// After first blur:
// - show true or false according to the validation result
const smartValidState = computed(() => {
if (afterFirstInput.value) {
return valid.value
}
return valid.value ? true : null
})
const errorMessage = computed(() => {
if (props.modelValue === undefined || props.modelValue === '' || props.modelValue === null) {
return undefined
@ -79,4 +100,17 @@ const minValue = computed(() => getTestParameter('min'))
const maxValue = computed(() => getTestParameter('max'))
const resetValue = computed(() => schemaDescription.value.default)
const isOptional = computed(() => schemaDescription.value.optional)
// reset on mount
onMounted(() => {
afterFirstInput.value = false
})
</script>
<!-- disable animation on invalid input -->
<style>
.form-control {
transition: none !important;
transform: none !important;
animation: none !important;
}
</style>