diff --git a/packages/vee-validate/src/Field.ts b/packages/vee-validate/src/Field.ts index 8f92e8c75..83bc40139 100644 --- a/packages/vee-validate/src/Field.ts +++ b/packages/vee-validate/src/Field.ts @@ -1,8 +1,9 @@ import { h, defineComponent, toRef, SetupContext, resolveDynamicComponent, computed, watch } from 'vue'; import { getConfig } from './config'; import { useField } from './useField'; -import { normalizeChildren, hasCheckedAttr, shouldHaveValueBinding } from './utils'; +import { normalizeChildren, hasCheckedAttr, shouldHaveValueBinding, isPropPresent } from './utils'; import { toNumber } from '../../shared'; +import { EMPTY_VALUE } from './symbols'; interface ValidationTriggersProps { validateOnMount: boolean; @@ -62,6 +63,7 @@ export const Field = defineComponent({ }, modelValue: { type: null, + default: EMPTY_VALUE, }, modelModifiers: { type: null, @@ -93,13 +95,7 @@ export const Field = defineComponent({ validateOnMount: props.validateOnMount, bails: props.bails, type: ctx.attrs.type as string, - // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue) - // For checkboxes and radio buttons it will always be the model value not the `value` attribute - initialValue: hasCheckedAttr(ctx.attrs.type) - ? props.modelValue - : 'modelValue' in props - ? props.modelValue - : ctx.attrs.value, + initialValue: resolveInitialValue(props, ctx), // Only for checkboxes and radio buttons valueProp: ctx.attrs.value, uncheckedValue, @@ -108,21 +104,19 @@ export const Field = defineComponent({ }); // If there is a v-model applied on the component we need to emit the `update:modelValue` whenever the value binding changes - const onChangeHandler = - 'modelValue' in props - ? function handleChangeWithModel(e: any) { - handleChange(e); - ctx.emit('update:modelValue', value.value); - } - : handleChange; - - const onInputHandler = - 'modelValue' in props - ? function handleChangeWithModel(e: any) { - handleInput(e); - ctx.emit('update:modelValue', value.value); - } - : handleInput; + const onChangeHandler = isPropPresent(props, 'modelValue') + ? function handleChangeWithModel(e: any) { + handleChange(e); + ctx.emit('update:modelValue', value.value); + } + : handleChange; + + const onInputHandler = isPropPresent(props, 'modelValue') + ? function handleChangeWithModel(e: any) { + handleInput(e); + ctx.emit('update:modelValue', value.value); + } + : handleInput; const fieldProps = computed(() => { const { validateOnInput, validateOnChange, validateOnBlur, validateOnModelUpdate } = resolveValidationTriggers( @@ -161,7 +155,7 @@ export const Field = defineComponent({ return attrs; }); - if ('modelValue' in props) { + if (isPropPresent(props, 'modelValue')) { const modelValue = toRef(props, 'modelValue'); watch(modelValue, newModelValue => { if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) { @@ -237,3 +231,13 @@ function applyModifiers(value: unknown, modifiers: Record) { return value; } + +function resolveInitialValue(props: Record, ctx: SetupContext) { + // Gets the initial value either from `value` prop/attr or `v-model` binding (modelValue) + // For checkboxes and radio buttons it will always be the model value not the `value` attribute + if (!hasCheckedAttr(ctx.attrs.type)) { + return isPropPresent(props, 'modelValue') ? props.modelValue : ctx.attrs.value; + } + + return isPropPresent(props, 'modelValue') ? props.modelValue : undefined; +} diff --git a/packages/vee-validate/src/symbols.ts b/packages/vee-validate/src/symbols.ts index df543b113..cb883e3c1 100644 --- a/packages/vee-validate/src/symbols.ts +++ b/packages/vee-validate/src/symbols.ts @@ -12,3 +12,5 @@ export const FormInitialValuesSymbol: InjectionKey> = Symbol('vee-validate-field-instance'); + +export const EMPTY_VALUE = Symbol('Default empty value'); diff --git a/packages/vee-validate/src/utils/assertions.ts b/packages/vee-validate/src/utils/assertions.ts index f31d44120..88d201415 100644 --- a/packages/vee-validate/src/utils/assertions.ts +++ b/packages/vee-validate/src/utils/assertions.ts @@ -1,5 +1,6 @@ import { Locator, YupValidator } from '../types'; import { isCallable, isObject } from '../../../shared'; +import { EMPTY_VALUE } from '../symbols'; export function isLocator(value: unknown): value is Locator { return isCallable(value) && !!(value as Locator).__locatorRef; @@ -99,3 +100,7 @@ export function isEvent(evt: unknown): evt is Event { return false; } + +export function isPropPresent(obj: Record, prop: string) { + return prop in obj && obj[prop] !== EMPTY_VALUE; +}