diff --git a/packages/runtime-core/__tests__/componentEmits.spec.ts b/packages/runtime-core/__tests__/componentEmits.spec.ts index f93d547ab19..53c8d324ea3 100644 --- a/packages/runtime-core/__tests__/componentEmits.spec.ts +++ b/packages/runtime-core/__tests__/componentEmits.spec.ts @@ -220,6 +220,68 @@ describe('component: emit', () => { expect(onFooOnce).toHaveBeenCalledTimes(1) }) + test('.number modifier should work with v-model on component', () => { + const Foo = defineComponent({ + render() {}, + created() { + this.$emit('update:modelValue', '1') + this.$emit('update:foo', '2') + } + }) + + const fn1 = jest.fn() + const fn2 = jest.fn() + + const Comp = () => + h(Foo, { + modelValue: null, + modelModifiers: { number: true }, + 'onUpdate:modelValue': fn1, + + foo: null, + fooModifiers: { number: true }, + 'onUpdate:foo': fn2 + }) + + render(h(Comp), nodeOps.createElement('div')) + + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn1).toHaveBeenCalledWith(1) + expect(fn2).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledWith(2) + }) + + test('.trim modifier should work with v-model on component', () => { + const Foo = defineComponent({ + render() {}, + created() { + this.$emit('update:modelValue', ' one ') + this.$emit('update:foo', ' two ') + } + }) + + const fn1 = jest.fn() + const fn2 = jest.fn() + + const Comp = () => + h(Foo, { + modelValue: null, + modelModifiers: { trim: true }, + 'onUpdate:modelValue': fn1, + + foo: null, + fooModifiers: { trim: true }, + 'onUpdate:foo': fn2 + }) + + render(h(Comp), nodeOps.createElement('div')) + + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn1).toHaveBeenCalledWith('one') + expect(fn2).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledWith('two') + }) + test('isEmitListener', () => { const options = { click: null } expect(isEmitListener(options, 'onClick')).toBe(true) diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index 726a16b8743..15c28e18706 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -7,7 +7,8 @@ import { hyphenate, isArray, isFunction, - isOn + isOn, + toNumber } from '@vue/shared' import { ComponentInternalInstance, @@ -45,7 +46,7 @@ export type EmitFn< export function emit( instance: ComponentInternalInstance, event: string, - ...args: any[] + ...rawArgs: any[] ) { const props = instance.vnode.props || EMPTY_OBJ @@ -65,7 +66,7 @@ export function emit( } else { const validator = emitsOptions[event] if (isFunction(validator)) { - const isValid = validator(...args) + const isValid = validator(...rawArgs) if (!isValid) { warn( `Invalid event arguments: event validation failed for event "${event}".` @@ -76,6 +77,22 @@ export function emit( } } + let args = rawArgs + + // for v-model update:xxx events, apply modifiers on args + const modelArg = event.startsWith('update:') && event.slice(7) + if (modelArg && modelArg in props) { + const modifiersKey = `${ + modelArg === 'modelValue' ? 'model' : modelArg + }Modifiers` + const { number, trim } = props[modifiersKey] || EMPTY_OBJ + if (trim) { + args = rawArgs.map(a => a.trim()) + } else if (number) { + args = rawArgs.map(toNumber) + } + } + if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { devtoolsComponentEmit(instance, event, args) }