diff --git a/packages/runtime-vapor/__tests__/vdomInterop.spec.ts b/packages/runtime-vapor/__tests__/vdomInterop.spec.ts index 08326d4d5d9..8b26f5d0f00 100644 --- a/packages/runtime-vapor/__tests__/vdomInterop.spec.ts +++ b/packages/runtime-vapor/__tests__/vdomInterop.spec.ts @@ -7,7 +7,27 @@ const define = makeInteropRender() describe('vdomInterop', () => { describe.todo('props', () => {}) - describe.todo('emit', () => {}) + describe('emit', () => { + test('emit from vapor child to vdom parent', () => { + const VaporChild = defineVaporComponent({ + emits: ['click'], + setup(_, { emit }) { + emit('click') + return [] + }, + }) + + const fn = vi.fn() + define({ + setup() { + return () => h(VaporChild as any, { onClick: fn }) + }, + }).render() + + // fn should be called once + expect(fn).toHaveBeenCalledTimes(1) + }) + }) describe.todo('slots', () => {}) diff --git a/packages/runtime-vapor/src/componentEmits.ts b/packages/runtime-vapor/src/componentEmits.ts index 68b7cfbeb21..daee4d59fe7 100644 --- a/packages/runtime-vapor/src/componentEmits.ts +++ b/packages/runtime-vapor/src/componentEmits.ts @@ -1,7 +1,8 @@ import { type ObjectEmitsOptions, baseEmit } from '@vue/runtime-dom' import type { VaporComponent, VaporComponentInstance } from './component' import { EMPTY_OBJ, hasOwn, isArray } from '@vue/shared' -import { resolveSource } from './componentProps' +import { type RawProps, resolveSource } from './componentProps' +import { interopKey } from './vdomInterop' /** * The logic from core isn't too reusable so it's better to duplicate here @@ -40,13 +41,17 @@ export function emit( ) } -function propGetter(rawProps: Record, key: string) { +function propGetter(rawProps: RawProps, key: string) { const dynamicSources = rawProps.$ if (dynamicSources) { let i = dynamicSources.length while (i--) { const source = resolveSource(dynamicSources[i]) - if (hasOwn(source, key)) return resolveSource(source[key]) + if (hasOwn(source, key)) + // for props passed from VDOM component, no need to resolve + return dynamicSources[interopKey] + ? source[key] + : resolveSource(source[key]) } } return rawProps[key] && resolveSource(rawProps[key]) diff --git a/packages/runtime-vapor/src/componentProps.ts b/packages/runtime-vapor/src/componentProps.ts index 9cf65c57143..18296771266 100644 --- a/packages/runtime-vapor/src/componentProps.ts +++ b/packages/runtime-vapor/src/componentProps.ts @@ -24,10 +24,11 @@ import { import { ReactiveFlags } from '@vue/reactivity' import { normalizeEmitsOptions } from './componentEmits' import { renderEffect } from './renderEffect' +import type { interopKey } from './vdomInterop' export type RawProps = Record unknown> & { // generated by compiler for :[key]="x" or v-bind="x" - $?: DynamicPropsSource[] + $?: DynamicPropsSource[] & { [interopKey]?: boolean } } export type DynamicPropsSource = diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts index e277024d73b..3c96d513817 100644 --- a/packages/runtime-vapor/src/vdomInterop.ts +++ b/packages/runtime-vapor/src/vdomInterop.ts @@ -37,6 +37,8 @@ import { renderEffect } from './renderEffect' import { createTextNode } from './dom/node' import { optimizePropertyLookup } from './dom/prop' +export const interopKey: unique symbol = Symbol(`interop`) + // mounting vapor components and slots in vdom const vaporInteropImpl: Omit< VaporInteropInterface, @@ -51,11 +53,16 @@ const vaporInteropImpl: Omit< const propsRef = shallowRef(vnode.props) const slotsRef = shallowRef(vnode.children) + const dynamicPropSource: (() => any)[] & { [interopKey]?: boolean } = [ + () => propsRef.value, + ] + // mark as interop props + dynamicPropSource[interopKey] = true // @ts-expect-error const instance = (vnode.component = createComponent( vnode.type as any as VaporComponent, { - $: [() => propsRef.value], + $: dynamicPropSource, } as RawProps, { _: slotsRef, // pass the slots ref