Skip to content

Commit d95fc18

Browse files
authored
fix(runtime-vapor): component emits vdom interop (#13498)
1 parent 5ab938d commit d95fc18

File tree

4 files changed

+39
-6
lines changed

4 files changed

+39
-6
lines changed

packages/runtime-vapor/__tests__/vdomInterop.spec.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,27 @@ describe('vdomInterop', () => {
2626
})
2727
})
2828

29-
describe.todo('emit', () => {})
29+
describe('emit', () => {
30+
test('emit from vapor child to vdom parent', () => {
31+
const VaporChild = defineVaporComponent({
32+
emits: ['click'],
33+
setup(_, { emit }) {
34+
emit('click')
35+
return []
36+
},
37+
})
38+
39+
const fn = vi.fn()
40+
define({
41+
setup() {
42+
return () => h(VaporChild as any, { onClick: fn })
43+
},
44+
}).render()
45+
46+
// fn should be called once
47+
expect(fn).toHaveBeenCalledTimes(1)
48+
})
49+
})
3050

3151
describe('slots', () => {
3252
test('basic', () => {

packages/runtime-vapor/src/componentEmits.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { type ObjectEmitsOptions, baseEmit } from '@vue/runtime-dom'
22
import type { VaporComponent, VaporComponentInstance } from './component'
33
import { EMPTY_OBJ, hasOwn, isArray } from '@vue/shared'
4-
import { resolveSource } from './componentProps'
4+
import { type RawProps, resolveSource } from './componentProps'
5+
import { interopKey } from './vdomInterop'
56

67
/**
78
* The logic from core isn't too reusable so it's better to duplicate here
@@ -40,13 +41,17 @@ export function emit(
4041
)
4142
}
4243

43-
function propGetter(rawProps: Record<string, any>, key: string) {
44+
function propGetter(rawProps: RawProps, key: string) {
4445
const dynamicSources = rawProps.$
4546
if (dynamicSources) {
4647
let i = dynamicSources.length
4748
while (i--) {
4849
const source = resolveSource(dynamicSources[i])
49-
if (hasOwn(source, key)) return resolveSource(source[key])
50+
if (hasOwn(source, key))
51+
// for props passed from VDOM component, no need to resolve
52+
return dynamicSources[interopKey]
53+
? source[key]
54+
: resolveSource(source[key])
5055
}
5156
}
5257
return rawProps[key] && resolveSource(rawProps[key])

packages/runtime-vapor/src/componentProps.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ import {
2323
import { ReactiveFlags } from '@vue/reactivity'
2424
import { normalizeEmitsOptions } from './componentEmits'
2525
import { renderEffect } from './renderEffect'
26+
import type { interopKey } from './vdomInterop'
2627

2728
export type RawProps = Record<string, () => unknown> & {
2829
// generated by compiler for :[key]="x" or v-bind="x"
29-
$?: DynamicPropsSource[]
30+
$?: DynamicPropsSource[] & { [interopKey]?: boolean }
3031
}
3132

3233
export type DynamicPropsSource =

packages/runtime-vapor/src/vdomInterop.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import { renderEffect } from './renderEffect'
3737
import { createTextNode } from './dom/node'
3838
import { optimizePropertyLookup } from './dom/prop'
3939

40+
export const interopKey: unique symbol = Symbol(`interop`)
41+
4042
// mounting vapor components and slots in vdom
4143
const vaporInteropImpl: Omit<
4244
VaporInteropInterface,
@@ -51,11 +53,16 @@ const vaporInteropImpl: Omit<
5153
const propsRef = shallowRef(vnode.props)
5254
const slotsRef = shallowRef(vnode.children)
5355

56+
const dynamicPropSource: (() => any)[] & { [interopKey]?: boolean } = [
57+
() => propsRef.value,
58+
]
59+
// mark as interop props
60+
dynamicPropSource[interopKey] = true
5461
// @ts-expect-error
5562
const instance = (vnode.component = createComponent(
5663
vnode.type as any as VaporComponent,
5764
{
58-
$: [() => propsRef.value],
65+
$: dynamicPropSource,
5966
} as RawProps,
6067
{
6168
_: slotsRef, // pass the slots ref

0 commit comments

Comments
 (0)