From 3ec807bcd1d25fa43ed2a05bf7f3d8e1cb9a2ec7 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Wed, 31 Jan 2024 01:06:29 +0900 Subject: [PATCH 01/24] feat(runtime-vapor): component emits --- README.md | 1 + packages/runtime-vapor/src/component.ts | 22 ++++- packages/runtime-vapor/src/componentEmits.ts | 96 ++++++++++++++++++++ packages/runtime-vapor/src/render.ts | 2 +- 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 packages/runtime-vapor/src/componentEmits.ts diff --git a/README.md b/README.md index 143d7f371..028af1737 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ The code provided here is a duplicate from `runtime-core` as Vapor cannot import - packages/runtime-vapor/src/apiWatch.ts - packages/runtime-vapor/src/component.ts +- packages/runtime-vapor/src/componentEmits.ts - packages/runtime-vapor/src/componentProps.ts - packages/runtime-vapor/src/enums.ts - packages/runtime-vapor/src/errorHandling.ts diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 7ed267668..5493d7ab9 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -8,6 +8,12 @@ import { type NormalizedPropsOptions, normalizePropsOptions, } from './componentProps' +import { + type EmitFn, + type EmitsOptions, + type ObjectEmitsOptions, + normalizeEmitsOptions, +} from './componentEmits' import type { Data } from '@vue/shared' import { VaporLifecycleHooks } from './enums' @@ -17,10 +23,12 @@ export type Component = FunctionalComponent | ObjectComponent export type SetupFn = (props: any, ctx: any) => Block | Data export type FunctionalComponent = SetupFn & { props: ComponentPropsOptions + emits: EmitsOptions render(ctx: any): Block } export interface ObjectComponent { props: ComponentPropsOptions + emits: EmitsOptions setup?: SetupFn render(ctx: any): Block } @@ -37,13 +45,19 @@ export interface ComponentInternalInstance { block: Block | null scope: EffectScope component: FunctionalComponent | ObjectComponent + rawProps: Data + + // normalized options propsOptions: NormalizedPropsOptions + emitsOptions: ObjectEmitsOptions | null parent: ComponentInternalInstance | null // state props: Data setupState: Data + emit: EmitFn + emitted: Record | null refs: Data metadata: WeakMap @@ -139,6 +153,7 @@ export const unsetCurrentInstance = () => { let uid = 0 export const createComponentInstance = ( component: ObjectComponent | FunctionalComponent, + rawProps: Data, ): ComponentInternalInstance => { const instance: ComponentInternalInstance = { uid: uid++, @@ -146,13 +161,18 @@ export const createComponentInstance = ( container: null!, // set on mountComponent scope: new EffectScope(true /* detached */)!, component, + rawProps, // TODO: registory of parent parent: null, // resolved props and emits options propsOptions: normalizePropsOptions(component), - // emitsOptions: normalizeEmitsOptions(type, appContext), // TODO: + emitsOptions: normalizeEmitsOptions(component), + + // emit + emit: null!, // to be set immediately + emitted: null, // state props: EMPTY_OBJ, diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts new file mode 100644 index 000000000..fa7257a24 --- /dev/null +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -0,0 +1,96 @@ +// NOTE: runtime-core/src/componentEmits.ts + +import { + type UnionToIntersection, + camelize, + extend, + hyphenate, + isArray, + isFunction, + toHandlerKey, +} from '@vue/shared' +import type { Component, ComponentInternalInstance } from './component' + +export type ObjectEmitsOptions = Record< + string, + ((...args: any[]) => any) | null // TODO: call validation? +> + +export type EmitsOptions = ObjectEmitsOptions | string[] + +export type EmitFn< + Options = ObjectEmitsOptions, + Event extends keyof Options = keyof Options, +> = + Options extends Array + ? (event: V, ...args: any[]) => void + : {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function + ? (event: string, ...args: any[]) => void + : UnionToIntersection< + { + [key in Event]: Options[key] extends (...args: infer Args) => any + ? (event: key, ...args: Args) => void + : (event: key, ...args: any[]) => void + }[Event] + > + +export function emit( + instance: ComponentInternalInstance, + event: string, + ...rawArgs: any[] +) { + if (instance.isUnmounted) return + const props = instance.rawProps // TODO: raw props + + let args = rawArgs + + // TODO: modelListener + + let handlerName + let handler = + props[(handlerName = toHandlerKey(event))] || + // also try camelCase event handler (#2249) + props[(handlerName = toHandlerKey(camelize(event)))] + // for v-model update:xxx events, also trigger kebab-case equivalent + // for props passed via kebab-case + if (!handler) { + handler = props[(handlerName = toHandlerKey(hyphenate(event)))] + } + + if (handler && isFunction(handler)) { + // TODO: callWithAsyncErrorHandling + handler(...args) + } + + const onceHandler = props[handlerName + `Once`] + if (onceHandler) { + if (!instance.emitted) { + instance.emitted = {} + } else if (instance.emitted[handlerName]) { + return + } + + if (isFunction(onceHandler)) { + instance.emitted[handlerName] = true + // TODO: callWithAsyncErrorHandling + onceHandler(...args) + } + } +} + +export function normalizeEmitsOptions( + comp: Component, +): ObjectEmitsOptions | null { + // TODO: caching? + + const raw = comp.emits + let normalized: ObjectEmitsOptions = {} + + if (isArray(raw)) { + raw.forEach(key => (normalized[key] = null)) + } else { + extend(normalized, raw) + } + + return normalized +} diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index 8bae59953..b2f3d594c 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -27,7 +27,7 @@ export function render( props: Data, container: string | ParentNode, ): ComponentInternalInstance { - const instance = createComponentInstance(comp) + const instance = createComponentInstance(comp, props) initProps(instance, props) return mountComponent(instance, (container = normalizeContainer(container))) } From 41dafacd6ef81fa1edb724ca24491e4df2b91594 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 22:53:36 +0900 Subject: [PATCH 02/24] test(runtime-vapor): component emits (base) --- .../__tests__/componentEmits.spec.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 packages/runtime-vapor/__tests__/componentEmits.spec.ts diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts new file mode 100644 index 000000000..2a2e5db5a --- /dev/null +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -0,0 +1,62 @@ +// NOTE: this test cases are based on paclages/runtime-core/__tests__/componentEmits.spec.ts + +// Note: emits and listener fallthrough is tested in +// ./rendererAttrsFallthrough.spec.ts. + +describe('component: emit', () => { + test.todo('trigger handlers', () => {}) + + test.todo('trigger camelCase handler', () => {}) + + test.todo('trigger kebab-case handler', () => {}) + + // #3527 + test.todo('trigger mixed case handlers', () => {}) + + // for v-model:foo-bar usage in DOM templates + test.todo('trigger hyphenated events for update:xxx events', () => {}) + + test.todo('should trigger array of listeners', async () => {}) + + test.todo('warning for undeclared event (array)', () => {}) + + test.todo('warning for undeclared event (object)', () => {}) + + test.todo('should not warn if has equivalent onXXX prop', () => {}) + + test.todo('validator warning', () => {}) + + test.todo('merging from mixins', () => {}) + + // #2651 + test.todo( + 'should not attach normalized object when mixins do not contain emits', + () => {}, + ) + + test.todo('.once', () => {}) + + test.todo('.once with normal listener of the same name', () => {}) + + test.todo('.number modifier should work with v-model on component', () => {}) + + test.todo('.trim modifier should work with v-model on component', () => {}) + + test.todo( + '.trim and .number modifiers should work with v-model on component', + () => {}, + ) + + test.todo( + 'only trim string parameter when work with v-model on component', + () => {}, + ) + + test.todo('isEmitListener', () => {}) + + test.todo('does not emit after unmount', async () => {}) + + test.todo('merge string array emits', async () => {}) + + test.todo('merge object emits', async () => {}) +}) From d19625d8d4c12e721fb82c904e58dc65bdf601ee Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:37:06 +0900 Subject: [PATCH 03/24] feat(runtime-vapor): init component emits --- packages/runtime-vapor/src/component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 5493d7ab9..21b532f82 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -12,6 +12,7 @@ import { type EmitFn, type EmitsOptions, type ObjectEmitsOptions, + emit, normalizeEmitsOptions, } from './componentEmits' @@ -245,5 +246,8 @@ export const createComponentInstance = ( */ // [VaporLifecycleHooks.SERVER_PREFETCH]: null, } + + instance.emit = emit.bind(null, instance) + return instance } From 0af8ffe864162b5dc8178dee0b57dcc792d158d1 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:37:39 +0900 Subject: [PATCH 04/24] feat(runtime-vapor): provide component emits into setup fn --- packages/runtime-vapor/src/render.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime-vapor/src/render.ts b/packages/runtime-vapor/src/render.ts index 4795b6110..acda15cf3 100644 --- a/packages/runtime-vapor/src/render.ts +++ b/packages/runtime-vapor/src/render.ts @@ -46,8 +46,8 @@ export function mountComponent( const reset = setCurrentInstance(instance) const block = instance.scope.run(() => { - const { component, props } = instance - const ctx = { expose: () => {} } + const { component, props, emit } = instance + const ctx = { expose: () => {}, emit } const setupFn = typeof component === 'function' ? component : component.setup From c74236a86a59329eeba6fd781ab80ce7f86e4652 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:46:31 +0900 Subject: [PATCH 05/24] test(runtime-vapor): component emits (trigger handlers) --- .../__tests__/componentEmits.spec.ts | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 2a2e5db5a..7d153ab45 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -3,8 +3,51 @@ // Note: emits and listener fallthrough is tested in // ./rendererAttrsFallthrough.spec.ts. +import { defineComponent, render } from '../src' + +let host: HTMLElement + +const initHost = () => { + host = document.createElement('div') + host.setAttribute('id', 'host') + document.body.appendChild(host) +} +beforeEach(() => initHost()) +afterEach(() => host.remove()) + describe('component: emit', () => { - test.todo('trigger handlers', () => {}) + test('trigger handlers', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('foo') + emit('bar') + emit('!baz') + }, + }) + const onfoo = vi.fn() + const onBar = vi.fn() + const onBaz = vi.fn() + render( + Foo, + { + get onfoo() { + return onfoo + }, + get onBar() { + return onBar + }, + get ['on!baz']() { + return onBaz + }, + }, + '#host', + ) + + expect(onfoo).not.toHaveBeenCalled() + expect(onBar).toHaveBeenCalled() + expect(onBaz).toHaveBeenCalled() + }) test.todo('trigger camelCase handler', () => {}) From e5a77126e8e2f4ce4c6799e2e2e19d3009bec449 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:47:14 +0900 Subject: [PATCH 06/24] chore: comment out unimplemented code --- packages/runtime-vapor/src/componentEmits.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index fa7257a24..aa4b108e4 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -4,7 +4,6 @@ import { type UnionToIntersection, camelize, extend, - hyphenate, isArray, isFunction, toHandlerKey, @@ -40,7 +39,7 @@ export function emit( ...rawArgs: any[] ) { if (instance.isUnmounted) return - const props = instance.rawProps // TODO: raw props + const props = instance.rawProps let args = rawArgs @@ -53,9 +52,9 @@ export function emit( props[(handlerName = toHandlerKey(camelize(event)))] // for v-model update:xxx events, also trigger kebab-case equivalent // for props passed via kebab-case - if (!handler) { - handler = props[(handlerName = toHandlerKey(hyphenate(event)))] - } + // if (!handler && isModelListener) { + // handler = props[(handlerName = toHandlerKey(hyphenate(event)))] + // } if (handler && isFunction(handler)) { // TODO: callWithAsyncErrorHandling From 5eeffcc4f491956b86a8675211920febf41c4f8a Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:48:52 +0900 Subject: [PATCH 07/24] test(runtime-vapor): component emits (trigger camelCase handler) --- .../__tests__/componentEmits.spec.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 7d153ab45..3af876a83 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -49,7 +49,26 @@ describe('component: emit', () => { expect(onBaz).toHaveBeenCalled() }) - test.todo('trigger camelCase handler', () => {}) + test('trigger camelCase handler', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('test-event') + }, + }) + + const fooSpy = vi.fn() + render( + Foo, + { + get onTestEvent() { + return fooSpy + }, + }, + '#host', + ) + expect(fooSpy).toHaveBeenCalled() + }) test.todo('trigger kebab-case handler', () => {}) From c707e271c9e43fd56d1ca77150194fb709c466cf Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:51:06 +0900 Subject: [PATCH 08/24] test(runtime-vapor): component emits (trigger kebab-case handler) --- .../__tests__/componentEmits.spec.ts | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 3af876a83..7996d868f 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -70,7 +70,26 @@ describe('component: emit', () => { expect(fooSpy).toHaveBeenCalled() }) - test.todo('trigger kebab-case handler', () => {}) + test('trigger kebab-case handler', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('test-event') + }, + }) + + const fooSpy = vi.fn() + render( + Foo, + { + get ['onTest-event']() { + return fooSpy + }, + }, + '#host', + ) + expect(fooSpy).toHaveBeenCalledTimes(1) + }) // #3527 test.todo('trigger mixed case handlers', () => {}) From 8ed282f270aaac8f5c2a0056f79dfa2ae58d3ad5 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Thu, 1 Feb 2024 23:54:35 +0900 Subject: [PATCH 09/24] test(runtime-vapor): component emits (trigger mixed case handlers) --- .../__tests__/componentEmits.spec.ts | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 7996d868f..e5c0def5f 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -92,7 +92,33 @@ describe('component: emit', () => { }) // #3527 - test.todo('trigger mixed case handlers', () => {}) + test.todo('trigger mixed case handlers', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('test-event') + emit('testEvent') + }, + }) + + const fooSpy = vi.fn() + const barSpy = vi.fn() + render( + Foo, + // TODO: impl `toHandlers` + { + get ['onTest-Event']() { + return fooSpy + }, + get onTestEvent() { + return barSpy + }, + }, + '#host', + ) + expect(fooSpy).toHaveBeenCalledTimes(1) + expect(barSpy).toHaveBeenCalledTimes(1) + }) // for v-model:foo-bar usage in DOM templates test.todo('trigger hyphenated events for update:xxx events', () => {}) From d0e1ed74da0c6eef122438dd75509a0a1beabb7f Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:34:43 +0900 Subject: [PATCH 10/24] feat(runtime-vapor): trigger hyphenated events for update:xxx events --- .../__tests__/componentEmits.spec.ts | 28 +++++++++++++++++- packages/runtime-vapor/src/component.ts | 4 ++- packages/runtime-vapor/src/componentEmits.ts | 29 ++++++++++++++++--- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index e5c0def5f..e3caaab8d 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -121,7 +121,33 @@ describe('component: emit', () => { }) // for v-model:foo-bar usage in DOM templates - test.todo('trigger hyphenated events for update:xxx events', () => {}) + test('trigger hyphenated events for update:xxx events', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('update:fooProp') + emit('update:barProp') + }, + }) + + const fooSpy = vi.fn() + const barSpy = vi.fn() + render( + Foo, + { + get ['onUpdate:fooProp']() { + return fooSpy + }, + get ['onUpdate:bar-prop']() { + return barSpy + }, + }, + '#host', + ) + + expect(fooSpy).toHaveBeenCalled() + expect(barSpy).toHaveBeenCalled() + }) test.todo('should trigger array of listeners', async () => {}) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 21b532f82..6fb6e9774 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -46,7 +46,9 @@ export interface ComponentInternalInstance { block: Block | null scope: EffectScope component: FunctionalComponent | ObjectComponent - rawProps: Data + + // TODO: VNodeProps & ExtraProps (key, ref, ...) + rawProps: { [key: string]: any } // normalized options propsOptions: NormalizedPropsOptions diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index aa4b108e4..79d6ce4b4 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -1,11 +1,15 @@ // NOTE: runtime-core/src/componentEmits.ts import { + EMPTY_OBJ, type UnionToIntersection, camelize, extend, + hyphenate, isArray, isFunction, + isString, + looseToNumber, toHandlerKey, } from '@vue/shared' import type { Component, ComponentInternalInstance } from './component' @@ -42,8 +46,25 @@ export function emit( const props = instance.rawProps let args = rawArgs + const isModelListener = event.startsWith('update:') - // TODO: modelListener + // for v-model update:xxx events, apply modifiers on args + const modelArg = isModelListener && 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 => (isString(a) ? a.trim() : a)) + } + if (number) { + args = rawArgs.map(looseToNumber) + } + } + + // TODO: warn let handlerName let handler = @@ -52,9 +73,9 @@ export function emit( props[(handlerName = toHandlerKey(camelize(event)))] // for v-model update:xxx events, also trigger kebab-case equivalent // for props passed via kebab-case - // if (!handler && isModelListener) { - // handler = props[(handlerName = toHandlerKey(hyphenate(event)))] - // } + if (!handler && isModelListener) { + handler = props[(handlerName = toHandlerKey(hyphenate(event)))] + } if (handler && isFunction(handler)) { // TODO: callWithAsyncErrorHandling From bd3ffe5bc0295a18f1fd8352ddaf81dc87b1a71b Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:44:19 +0900 Subject: [PATCH 11/24] feat(runtime-vapor): call emit handler with async error handling --- packages/runtime-vapor/src/componentEmits.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index 79d6ce4b4..4345dbabf 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -13,6 +13,7 @@ import { toHandlerKey, } from '@vue/shared' import type { Component, ComponentInternalInstance } from './component' +import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling' export type ObjectEmitsOptions = Record< string, @@ -77,9 +78,13 @@ export function emit( handler = props[(handlerName = toHandlerKey(hyphenate(event)))] } - if (handler && isFunction(handler)) { - // TODO: callWithAsyncErrorHandling - handler(...args) + if (handler) { + callWithAsyncErrorHandling( + handler, + instance, + VaporErrorCodes.COMPONENT_EVENT_HANDLER, + args, + ) } const onceHandler = props[handlerName + `Once`] @@ -92,8 +97,12 @@ export function emit( if (isFunction(onceHandler)) { instance.emitted[handlerName] = true - // TODO: callWithAsyncErrorHandling - onceHandler(...args) + callWithAsyncErrorHandling( + handler, + instance, + VaporErrorCodes.COMPONENT_EVENT_HANDLER, + args, + ) } } } From d240d3d1470369f5c8040eec89da22b8b5b97b4c Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:44:41 +0900 Subject: [PATCH 12/24] test(runtime-vapor): component emits (should trigger array of listeners) --- .../__tests__/componentEmits.spec.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index e3caaab8d..c20a12da9 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -149,7 +149,31 @@ describe('component: emit', () => { expect(barSpy).toHaveBeenCalled() }) - test.todo('should trigger array of listeners', async () => {}) + test('should trigger array of listeners', async () => { + const App = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('foo', 1) + }, + }) + + const fn1 = vi.fn() + const fn2 = vi.fn() + + render( + App, + { + get onFoo() { + return [fn1, fn2] + }, + }, + '#host', + ) + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn1).toHaveBeenCalledWith(1) + expect(fn2).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledWith(1) + }) test.todo('warning for undeclared event (array)', () => {}) From 3852eeb15c7c5c8ea870b043cb774d1e32c3beec Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:48:03 +0900 Subject: [PATCH 13/24] test(runtime-vapor): component emits (should not warn if has equivalent onXXX prop) --- .../__tests__/componentEmits.spec.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index c20a12da9..1143f2373 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -179,7 +179,20 @@ describe('component: emit', () => { test.todo('warning for undeclared event (object)', () => {}) - test.todo('should not warn if has equivalent onXXX prop', () => {}) + test('should not warn if has equivalent onXXX prop', () => { + const Foo = defineComponent({ + props: ['onFoo'], + emits: [], + render() {}, + setup(_: any, { emit }: any) { + emit('foo') + }, + }) + render(Foo, {}, '#host') + expect( + `Component emitted event "foo" but it is neither declared`, + ).not.toHaveBeenWarned() + }) test.todo('validator warning', () => {}) From aaa2add8fc3469f540f3d2ec49ea468a27e9e568 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:54:24 +0900 Subject: [PATCH 14/24] test(runtime-vapor): component emits (.once) --- .../__tests__/componentEmits.spec.ts | 43 ++++++++++++++++--- packages/runtime-vapor/src/componentEmits.ts | 17 +++----- 2 files changed, 44 insertions(+), 16 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 1143f2373..ad76c454f 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -196,15 +196,46 @@ describe('component: emit', () => { test.todo('validator warning', () => {}) - test.todo('merging from mixins', () => {}) + // NOTE: not supported mixins + // test.todo('merging from mixins', () => {}) // #2651 - test.todo( - 'should not attach normalized object when mixins do not contain emits', - () => {}, - ) + // test.todo( + // 'should not attach normalized object when mixins do not contain emits', + // () => {}, + // ) - test.todo('.once', () => {}) + test('.once', () => { + const Foo = defineComponent({ + render() {}, + emits: { + foo: null, + bar: null, + }, + setup(_: any, { emit }: any) { + emit('foo') + emit('foo') + emit('bar') + emit('bar') + }, + }) + const fn = vi.fn() + const barFn = vi.fn() + render( + Foo, + { + get onFooOnce() { + return fn + }, + get onBarOnce() { + return barFn + }, + }, + '#host', + ) + expect(fn).toHaveBeenCalledTimes(1) + expect(barFn).toHaveBeenCalledTimes(1) + }) test.todo('.once with normal listener of the same name', () => {}) diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index 4345dbabf..beedabe03 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -94,16 +94,13 @@ export function emit( } else if (instance.emitted[handlerName]) { return } - - if (isFunction(onceHandler)) { - instance.emitted[handlerName] = true - callWithAsyncErrorHandling( - handler, - instance, - VaporErrorCodes.COMPONENT_EVENT_HANDLER, - args, - ) - } + instance.emitted[handlerName] = true + callWithAsyncErrorHandling( + onceHandler, + instance, + VaporErrorCodes.COMPONENT_EVENT_HANDLER, + args, + ) } } From 9adfff5009adf2ae12b8aa9485f44cbd742fadf4 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:56:11 +0900 Subject: [PATCH 15/24] test(runtime-vapor): component emits (.once with normal listener of the same name) --- .../__tests__/componentEmits.spec.ts | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index ad76c454f..7aa015123 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -237,7 +237,34 @@ describe('component: emit', () => { expect(barFn).toHaveBeenCalledTimes(1) }) - test.todo('.once with normal listener of the same name', () => {}) + test('.once with normal listener of the same name', () => { + const Foo = defineComponent({ + render() {}, + emits: { + foo: null, + }, + setup(_: any, { emit }: any) { + emit('foo') + emit('foo') + }, + }) + const onFoo = vi.fn() + const onFooOnce = vi.fn() + render( + Foo, + { + get onFoo() { + return onFoo + }, + get onFooOnce() { + return onFooOnce + }, + }, + '#host', + ) + expect(onFoo).toHaveBeenCalledTimes(2) + expect(onFooOnce).toHaveBeenCalledTimes(1) + }) test.todo('.number modifier should work with v-model on component', () => {}) From 51b801de73898d4f37e26904bbf9aa7e1d936a63 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 00:59:42 +0900 Subject: [PATCH 16/24] test(runtime-vapor): component emits (.number modifier should work with v-model on component) --- .../__tests__/componentEmits.spec.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 7aa015123..fe508f2a7 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -266,7 +266,45 @@ describe('component: emit', () => { expect(onFooOnce).toHaveBeenCalledTimes(1) }) - test.todo('.number modifier should work with v-model on component', () => {}) + test('.number modifier should work with v-model on component', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('update:modelValue', '1') + emit('update:foo', '2') + }, + }) + const fn1 = vi.fn() + const fn2 = vi.fn() + render( + Foo, + { + get modelValue() { + return null + }, + get modelModifiers() { + return { number: true } + }, + get ['onUpdate:modelValue']() { + return fn1 + }, + get foo() { + return null + }, + get fooModifiers() { + return { number: true } + }, + get ['onUpdate:foo']() { + return fn2 + }, + }, + '#host', + ) + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn1).toHaveBeenCalledWith(1) + expect(fn2).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledWith(2) + }) test.todo('.trim modifier should work with v-model on component', () => {}) From f73e7e788d0594a94bc29bae594a169e3bb3e081 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:00:03 +0900 Subject: [PATCH 17/24] chore: remove dead code --- packages/runtime-vapor/src/componentEmits.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index beedabe03..3a5634093 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -7,7 +7,6 @@ import { extend, hyphenate, isArray, - isFunction, isString, looseToNumber, toHandlerKey, From 31180b69eeb734197804a7a8083c43fcd4150cb9 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:03:18 +0900 Subject: [PATCH 18/24] test(runtime-vapor): component emits (.trim modifier should work with v-model on component) --- .../__tests__/componentEmits.spec.ts | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index fe508f2a7..09f282f60 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -306,7 +306,45 @@ describe('component: emit', () => { expect(fn2).toHaveBeenCalledWith(2) }) - test.todo('.trim modifier should work with v-model on component', () => {}) + test('.trim modifier should work with v-model on component', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('update:modelValue', ' one ') + emit('update:foo', ' two ') + }, + }) + const fn1 = vi.fn() + const fn2 = vi.fn() + render( + Foo, + { + get modelValue() { + return null + }, + get modelModifiers() { + return { trim: true } + }, + get ['onUpdate:modelValue']() { + return fn1 + }, + get foo() { + return null + }, + get fooModifiers() { + return { trim: true } + }, + get 'onUpdate:foo'() { + return fn2 + }, + }, + '#host', + ) + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn1).toHaveBeenCalledWith('one') + expect(fn2).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledWith('two') + }) test.todo( '.trim and .number modifiers should work with v-model on component', From 510f2b12bb70530db0e8a4ab89671ffe9380347e Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:05:08 +0900 Subject: [PATCH 19/24] test(runtime-vapor): component emits (.trim and .number modifiers should work with v-model on component) --- .../__tests__/componentEmits.spec.ts | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 09f282f60..64fa351ef 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -346,10 +346,45 @@ describe('component: emit', () => { expect(fn2).toHaveBeenCalledWith('two') }) - test.todo( - '.trim and .number modifiers should work with v-model on component', - () => {}, - ) + test('.trim and .number modifiers should work with v-model on component', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('update:modelValue', ' +01.2 ') + emit('update:foo', ' 1 ') + }, + }) + const fn1 = vi.fn() + const fn2 = vi.fn() + render( + Foo, + { + get modelValue() { + return null + }, + get modelModifiers() { + return { trim: true, number: true } + }, + get ['onUpdate:modelValue']() { + return fn1 + }, + get foo() { + return null + }, + get fooModifiers() { + return { trim: true, number: true } + }, + get ['onUpdate:foo']() { + return fn2 + }, + }, + '#host', + ) + expect(fn1).toHaveBeenCalledTimes(1) + expect(fn1).toHaveBeenCalledWith(1.2) + expect(fn2).toHaveBeenCalledTimes(1) + expect(fn2).toHaveBeenCalledWith(1) + }) test.todo( 'only trim string parameter when work with v-model on component', From acc0d98861d8d5b3d6743faa40d13034b100e64b Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:07:18 +0900 Subject: [PATCH 20/24] test(runtime-vapor): component emits (only trim string parameter when work with v-model on component) --- .../__tests__/componentEmits.spec.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 64fa351ef..2bee9c807 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -386,10 +386,32 @@ describe('component: emit', () => { expect(fn2).toHaveBeenCalledWith(1) }) - test.todo( - 'only trim string parameter when work with v-model on component', - () => {}, - ) + test('only trim string parameter when work with v-model on component', () => { + const Foo = defineComponent({ + render() {}, + setup(_: any, { emit }: any) { + emit('update:modelValue', ' foo ', { bar: ' bar ' }) + }, + }) + const fn = vi.fn() + render( + Foo, + { + get modelValue() { + return null + }, + get modelModifiers() { + return { trim: true } + }, + get ['onUpdate:modelValue']() { + return fn + }, + }, + '#host', + ) + expect(fn).toHaveBeenCalledTimes(1) + expect(fn).toHaveBeenCalledWith('foo', { bar: ' bar ' }) + }) test.todo('isEmitListener', () => {}) From 0d17ba7b582ad0898bcb17255f4a8a171196e9b3 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:09:05 +0900 Subject: [PATCH 21/24] feat(runtime-vapor): isEmitListener --- .../__tests__/componentEmits.spec.ts | 30 ++++++++++++++++++- packages/runtime-vapor/src/componentEmits.ts | 21 +++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index 2bee9c807..c5beb025b 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -4,6 +4,7 @@ // ./rendererAttrsFallthrough.spec.ts. import { defineComponent, render } from '../src' +import { isEmitListener } from '../src/componentEmits' let host: HTMLElement @@ -413,7 +414,34 @@ describe('component: emit', () => { expect(fn).toHaveBeenCalledWith('foo', { bar: ' bar ' }) }) - test.todo('isEmitListener', () => {}) + test('isEmitListener', () => { + const options = { + get click() { + return null + }, + get 'test-event'() { + return null + }, + get fooBar() { + return null + }, + get FooBaz() { + return null + }, + } + expect(isEmitListener(options, 'onClick')).toBe(true) + expect(isEmitListener(options, 'onclick')).toBe(false) + expect(isEmitListener(options, 'onBlick')).toBe(false) + // .once listeners + expect(isEmitListener(options, 'onClickOnce')).toBe(true) + expect(isEmitListener(options, 'onclickOnce')).toBe(false) + // kebab-case option + expect(isEmitListener(options, 'onTestEvent')).toBe(true) + // camelCase option + expect(isEmitListener(options, 'onFooBar')).toBe(true) + // PascalCase option + expect(isEmitListener(options, 'onFooBaz')).toBe(true) + }) test.todo('does not emit after unmount', async () => {}) diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index 3a5634093..c10ac7a89 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -5,8 +5,10 @@ import { type UnionToIntersection, camelize, extend, + hasOwn, hyphenate, isArray, + isOn, isString, looseToNumber, toHandlerKey, @@ -119,3 +121,22 @@ export function normalizeEmitsOptions( return normalized } + +// Check if an incoming prop key is a declared emit event listener. +// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are +// both considered matched listeners. +export function isEmitListener( + options: ObjectEmitsOptions | null, + key: string, +): boolean { + if (!options || !isOn(key)) { + return false + } + + key = key.slice(2).replace(/Once$/, '') + return ( + hasOwn(options, key[0].toLowerCase() + key.slice(1)) || + hasOwn(options, hyphenate(key)) || + hasOwn(options, key) + ) +} From feb813d52bd2b5397b11dd3ef6c99401482b05b1 Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:14:12 +0900 Subject: [PATCH 22/24] test(runtime-vapor): component emits (does not emit after unmount) --- .../__tests__/componentEmits.spec.ts | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index c5beb025b..f9096df68 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -3,7 +3,13 @@ // Note: emits and listener fallthrough is tested in // ./rendererAttrsFallthrough.spec.ts. -import { defineComponent, render } from '../src' +import { + defineComponent, + nextTick, + onBeforeUnmount, + render, + unmountComponent, +} from '../src' import { isEmitListener } from '../src/componentEmits' let host: HTMLElement @@ -443,7 +449,32 @@ describe('component: emit', () => { expect(isEmitListener(options, 'onFooBaz')).toBe(true) }) - test.todo('does not emit after unmount', async () => {}) + test('does not emit after unmount', async () => { + const fn = vi.fn() + const Foo = defineComponent({ + emits: ['closing'], + setup(_: any, { emit }: any) { + onBeforeUnmount(async () => { + await nextTick() + emit('closing', true) + }) + }, + render() {}, + }) + const i = render( + Foo, + { + get onClosing() { + return fn + }, + }, + '#host', + ) + await nextTick() + unmountComponent(i) + await nextTick() + expect(fn).not.toHaveBeenCalled() + }) test.todo('merge string array emits', async () => {}) From bc862779675407edc6c52822ec9b8fd65020a0bd Mon Sep 17 00:00:00 2001 From: Ubugeeei Date: Fri, 2 Feb 2024 01:15:43 +0900 Subject: [PATCH 23/24] chore: add comments --- .../__tests__/componentEmits.spec.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts index f9096df68..af301df95 100644 --- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts +++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts @@ -182,9 +182,13 @@ describe('component: emit', () => { expect(fn2).toHaveBeenCalledWith(1) }) - test.todo('warning for undeclared event (array)', () => {}) + test.todo('warning for undeclared event (array)', () => { + // TODO: warning + }) - test.todo('warning for undeclared event (object)', () => {}) + test.todo('warning for undeclared event (object)', () => { + // TODO: warning + }) test('should not warn if has equivalent onXXX prop', () => { const Foo = defineComponent({ @@ -201,7 +205,9 @@ describe('component: emit', () => { ).not.toHaveBeenWarned() }) - test.todo('validator warning', () => {}) + test.todo('validator warning', () => { + // TODO: warning validator + }) // NOTE: not supported mixins // test.todo('merging from mixins', () => {}) @@ -476,7 +482,7 @@ describe('component: emit', () => { expect(fn).not.toHaveBeenCalled() }) - test.todo('merge string array emits', async () => {}) - - test.todo('merge object emits', async () => {}) + // NOTE: not supported mixins + // test.todo('merge string array emits', async () => {}) + // test.todo('merge object emits', async () => {}) }) From 6efe75be57be773e3d32dbe3e9b97264890fb0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sun, 4 Feb 2024 21:16:31 +0800 Subject: [PATCH 24/24] refactor --- packages/runtime-vapor/src/component.ts | 2 +- packages/runtime-vapor/src/componentEmits.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts index 6fb6e9774..570834eac 100644 --- a/packages/runtime-vapor/src/component.ts +++ b/packages/runtime-vapor/src/component.ts @@ -47,7 +47,7 @@ export interface ComponentInternalInstance { scope: EffectScope component: FunctionalComponent | ObjectComponent - // TODO: VNodeProps & ExtraProps (key, ref, ...) + // TODO: ExtraProps: key, ref, ... rawProps: { [key: string]: any } // normalized options diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index c10ac7a89..138097c81 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -45,7 +45,7 @@ export function emit( ...rawArgs: any[] ) { if (instance.isUnmounted) return - const props = instance.rawProps + const { rawProps } = instance let args = rawArgs const isModelListener = event.startsWith('update:') @@ -53,11 +53,11 @@ export function emit( // for v-model update:xxx events, apply modifiers on args const modelArg = isModelListener && event.slice(7) - if (modelArg && modelArg in props) { + if (modelArg && modelArg in rawProps) { const modifiersKey = `${ modelArg === 'modelValue' ? 'model' : modelArg }Modifiers` - const { number, trim } = props[modifiersKey] || EMPTY_OBJ + const { number, trim } = rawProps[modifiersKey] || EMPTY_OBJ if (trim) { args = rawArgs.map(a => (isString(a) ? a.trim() : a)) } @@ -70,13 +70,13 @@ export function emit( let handlerName let handler = - props[(handlerName = toHandlerKey(event))] || + rawProps[(handlerName = toHandlerKey(event))] || // also try camelCase event handler (#2249) - props[(handlerName = toHandlerKey(camelize(event)))] + rawProps[(handlerName = toHandlerKey(camelize(event)))] // for v-model update:xxx events, also trigger kebab-case equivalent // for props passed via kebab-case if (!handler && isModelListener) { - handler = props[(handlerName = toHandlerKey(hyphenate(event)))] + handler = rawProps[(handlerName = toHandlerKey(hyphenate(event)))] } if (handler) { @@ -88,7 +88,7 @@ export function emit( ) } - const onceHandler = props[handlerName + `Once`] + const onceHandler = rawProps[`${handlerName}Once`] if (onceHandler) { if (!instance.emitted) { instance.emitted = {}