]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-vapor): should not fallthrough emit handlers to vdom child (#13500)
authoredison <daiwei521@126.com>
Fri, 20 Jun 2025 00:08:34 +0000 (08:08 +0800)
committerGitHub <noreply@github.com>
Fri, 20 Jun 2025 00:08:34 +0000 (08:08 +0800)
packages/runtime-core/src/index.ts
packages/runtime-vapor/__tests__/componentAttrs.spec.ts
packages/runtime-vapor/src/vdomInterop.ts

index e309554f2f6c3edd517c9b736d06ee1c72594698..1ed6f21df7769a695a0643a6c27c38eaeaf30eef 100644 (file)
@@ -557,3 +557,7 @@ export { startMeasure, endMeasure } from './profiling'
  * @internal
  */
 export { initFeatureFlags } from './featureFlags'
+/**
+ * @internal
+ */
+export { createInternalObject } from './internalObject'
index fc6fae5f0215cd4616c325c8a720000d857bc733..204dd599011fa310e7612531cdb1410fbe8d0a49 100644 (file)
@@ -1,4 +1,11 @@
-import { type Ref, nextTick, ref } from '@vue/runtime-dom'
+import {
+  type Ref,
+  createApp,
+  defineComponent,
+  h,
+  nextTick,
+  ref,
+} from '@vue/runtime-dom'
 import {
   createComponent,
   defineVaporComponent,
@@ -8,6 +15,7 @@ import {
   setProp,
   setStyle,
   template,
+  vaporInteropPlugin,
 } from '../src'
 import { makeRender } from './_utils'
 import { stringifyStyle } from '@vue/shared'
@@ -361,4 +369,42 @@ describe('attribute fallthrough', () => {
     const el = host.children[0]
     expect(el.classList.length).toBe(0)
   })
+
+  it('should not fallthrough emit handlers to vdom child', () => {
+    const VDomChild = defineComponent({
+      emits: ['click'],
+      setup(_, { emit }) {
+        return () => h('button', { onClick: () => emit('click') }, 'click me')
+      },
+    })
+
+    const fn = vi.fn()
+    const VaporChild = defineVaporComponent({
+      emits: ['click'],
+      setup() {
+        return createComponent(
+          VDomChild as any,
+          { onClick: () => fn },
+          null,
+          true,
+        )
+      },
+    })
+
+    const App = {
+      setup() {
+        return () => h(VaporChild as any)
+      },
+    }
+
+    const root = document.createElement('div')
+    createApp(App).use(vaporInteropPlugin).mount(root)
+
+    expect(root.innerHTML).toBe('<button>click me</button>')
+    const button = root.querySelector('button')!
+    button.dispatchEvent(new Event('click'))
+
+    // fn should be called once
+    expect(fn).toHaveBeenCalledTimes(1)
+  })
 })
index 77228fd72a02fe85a5496daf7d89bc37e197a4d2..b916a2c8ebb0106b1d7542db2a83c17005c2649e 100644 (file)
@@ -9,9 +9,11 @@ import {
   type Slots,
   type VNode,
   type VaporInteropInterface,
+  createInternalObject,
   createVNode,
   currentInstance,
   ensureRenderer,
+  isEmitListener,
   onScopeDispose,
   renderSlot,
   shallowRef,
@@ -162,7 +164,14 @@ function createVDOMComponent(
   // overwrite how the vdom instance handles props
   vnode.vi = (instance: ComponentInternalInstance) => {
     instance.props = wrapper.props
-    instance.attrs = wrapper.attrs
+
+    const attrs = (instance.attrs = createInternalObject())
+    for (const key in wrapper.attrs) {
+      if (!isEmitListener(instance.emitsOptions, key)) {
+        attrs[key] = wrapper.attrs[key]
+      }
+    }
+
     instance.slots =
       wrapper.slots === EMPTY_OBJ
         ? EMPTY_OBJ