From 5e72852e80b971121d10422cf84085b07bb2d8fb Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Sun, 23 May 2021 22:18:39 +0200 Subject: [PATCH] fix: seperate model detection from event emitting closes #3312 --- packages/vee-validate/src/Field.ts | 28 ++++++++++++----------- packages/vee-validate/tests/Field.spec.ts | 24 ++++++++++++++++++- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/vee-validate/src/Field.ts b/packages/vee-validate/src/Field.ts index 1f85586af..969d2d58c 100644 --- a/packages/vee-validate/src/Field.ts +++ b/packages/vee-validate/src/Field.ts @@ -1,4 +1,4 @@ -import { h, defineComponent, toRef, SetupContext, resolveDynamicComponent, computed, watch } from 'vue'; +import { h, defineComponent, toRef, SetupContext, resolveDynamicComponent, computed, watch, PropType } from 'vue'; import { getConfig } from './config'; import { useField } from './useField'; import { normalizeChildren, hasCheckedAttr, shouldHaveValueBinding, isPropPresent } from './utils'; @@ -69,13 +69,17 @@ export const Field = defineComponent({ type: null, default: () => ({}), }, + 'onUpdate:modelValue': { + type: (null as unknown) as PropType<((e: any) => unknown) | undefined>, + default: undefined, + }, }, - emits: ['update:modelValue'], setup(props, ctx) { const rules = toRef(props, 'rules'); const name = toRef(props, 'name'); const label = toRef(props, 'label'); const uncheckedValue = toRef(props, 'uncheckedValue'); + const hasModelEvents = isPropPresent(props, 'onUpdate:modelValue'); const { errors, @@ -104,14 +108,14 @@ 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 = isPropPresent(props, 'modelValue') + const onChangeHandler = hasModelEvents ? function handleChangeWithModel(e: unknown, shouldValidate = true) { handleChange(e, shouldValidate); ctx.emit('update:modelValue', value.value); } : handleChange; - const onInputHandler = isPropPresent(props, 'modelValue') + const onInputHandler = hasModelEvents ? function handleChangeWithModel(e: any) { handleInput(e); ctx.emit('update:modelValue', value.value); @@ -151,15 +155,13 @@ export const Field = defineComponent({ return attrs; }); - if (isPropPresent(props, 'modelValue')) { - const modelValue = toRef(props, 'modelValue'); - watch(modelValue, newModelValue => { - if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) { - value.value = newModelValue; - validateField(); - } - }); - } + const modelValue = toRef(props, 'modelValue'); + watch(modelValue, newModelValue => { + if (newModelValue !== applyModifiers(value.value, props.modelModifiers)) { + value.value = newModelValue; + validateField(); + } + }); function slotProps() { return { diff --git a/packages/vee-validate/tests/Field.spec.ts b/packages/vee-validate/tests/Field.spec.ts index 5bfec06b8..49f3a41d9 100644 --- a/packages/vee-validate/tests/Field.spec.ts +++ b/packages/vee-validate/tests/Field.spec.ts @@ -2,7 +2,7 @@ import flushPromises from 'flush-promises'; import { defineRule, configure } from '@/vee-validate'; import { mountWithHoc, setValue, dispatchEvent, setChecked } from './helpers'; import * as yup from 'yup'; -import { ref, Ref } from 'vue'; +import { reactive, ref, Ref } from 'vue'; jest.useFakeTimers(); @@ -985,4 +985,26 @@ describe('', () => { await flushPromises(); expect(error.textContent).toBe(errorMessage); }); + + // #3312 + test('v-model on a non-existent nested prop should still emit model events', async () => { + const form = reactive({}); + const wrapper = mountWithHoc({ + setup() { + return { form }; + }, + template: ` +
+ +
+ `, + }); + + await flushPromises(); + const input = wrapper.$el.querySelector('input'); + input.value = 'hello'; + dispatchEvent(input, 'input'); + await flushPromises(); + expect((form as any).field).toBe('hello'); + }); });