Kaynağa Gözat

fix(custom-element): fix event listeners with capital letter event names on custom elements

close https://github.com/vuejs/docs/issues/1708
close https://github.com/vuejs/docs/pull/1890
Evan You 3 yıl önce
ebeveyn
işleme
0739f8909a

+ 33 - 1
packages/compiler-core/__tests__/transforms/transformElement.spec.ts

@@ -314,6 +314,37 @@ describe('compiler: element transform', () => {
     )
     expect(root.helpers).toContain(MERGE_PROPS)
 
+    expect(node.props).toMatchObject({
+      type: NodeTypes.JS_CALL_EXPRESSION,
+      callee: MERGE_PROPS,
+      arguments: [
+        createObjectMatcher({
+          id: 'foo'
+        }),
+        {
+          type: NodeTypes.JS_CALL_EXPRESSION,
+          callee: TO_HANDLERS,
+          arguments: [
+            {
+              type: NodeTypes.SIMPLE_EXPRESSION,
+              content: `obj`
+            },
+            `true`
+          ]
+        },
+        createObjectMatcher({
+          class: 'bar'
+        })
+      ]
+    })
+  })
+
+  test('v-on="obj" on component', () => {
+    const { root, node } = parseWithElementTransform(
+      `<Foo id="foo" v-on="obj" class="bar" />`
+    )
+    expect(root.helpers).toContain(MERGE_PROPS)
+
     expect(node.props).toMatchObject({
       type: NodeTypes.JS_CALL_EXPRESSION,
       callee: MERGE_PROPS,
@@ -358,7 +389,8 @@ describe('compiler: element transform', () => {
             {
               type: NodeTypes.SIMPLE_EXPRESSION,
               content: `handlers`
-            }
+            },
+            `true`
           ]
         },
         {

+ 1 - 1
packages/compiler-core/src/transforms/transformElement.ts

@@ -647,7 +647,7 @@ export function buildProps(
               type: NodeTypes.JS_CALL_EXPRESSION,
               loc,
               callee: context.helper(TO_HANDLERS),
-              arguments: [exp]
+              arguments: isComponent ? [exp] : [exp, `true`]
             })
           }
         } else {

+ 11 - 6
packages/compiler-core/src/transforms/vOn.ts

@@ -47,12 +47,17 @@ export const transformOn: DirectiveTransform = (
       if (rawName.startsWith('vue:')) {
         rawName = `vnode-${rawName.slice(4)}`
       }
-      // for all event listeners, auto convert it to camelCase. See issue #2249
-      eventName = createSimpleExpression(
-        toHandlerKey(camelize(rawName)),
-        true,
-        arg.loc
-      )
+      const eventString =
+        node.tagType === ElementTypes.COMPONENT ||
+        rawName.startsWith('vnode') ||
+        !/[A-Z]/.test(rawName)
+          ? // for component and vnode lifecycle event listeners, auto convert
+            // it to camelCase. See issue #2249
+            toHandlerKey(camelize(rawName))
+            // preserve case for plain element listeners that have uppercase
+            // letters, as these may be custom elements' custom events
+          : `on:${rawName}`
+      eventName = createSimpleExpression(eventString, true, arg.loc)
     } else {
       // #2388
       eventName = createCompoundExpression([

+ 9 - 2
packages/runtime-core/src/helpers/toHandlers.ts

@@ -5,14 +5,21 @@ import { warn } from '../warning'
  * For prefixing keys in v-on="obj" with "on"
  * @private
  */
-export function toHandlers(obj: Record<string, any>): Record<string, any> {
+export function toHandlers(
+  obj: Record<string, any>,
+  preserveCaseIfNecessary?: boolean
+): Record<string, any> {
   const ret: Record<string, any> = {}
   if (__DEV__ && !isObject(obj)) {
     warn(`v-on with no argument expects an object value.`)
     return ret
   }
   for (const key in obj) {
-    ret[toHandlerKey(key)] = obj[key]
+    ret[
+      preserveCaseIfNecessary && /[A-Z]/.test(key)
+        ? `on:${key}`
+        : toHandlerKey(key)
+    ] = obj[key]
   }
   return ret
 }

+ 2 - 1
packages/runtime-dom/src/modules/events.ts

@@ -101,7 +101,8 @@ function parseName(name: string): [string, EventListenerOptions | undefined] {
       ;(options as any)[m[0].toLowerCase()] = true
     }
   }
-  return [hyphenate(name.slice(2)), options]
+  const event = name[2] === ':' ? name.slice(3) : hyphenate(name.slice(2))
+  return [event, options]
 }
 
 function createInvoker(

+ 1 - 1
packages/runtime-test/src/patchProp.ts

@@ -16,7 +16,7 @@ export function patchProp(
   })
   el.props[key] = nextValue
   if (isOn(key)) {
-    const event = key.slice(2).toLowerCase()
+    const event = key[2] === ':' ? key.slice(3) : key.slice(2).toLowerCase()
     ;(el.eventListeners || (el.eventListeners = {}))[event] = nextValue
   }
 }

+ 42 - 0
packages/vue/__tests__/customElementCasing.spec.ts

@@ -0,0 +1,42 @@
+import { createApp } from '../src'
+
+// https://github.com/vuejs/docs/pull/1890
+// https://github.com/vuejs/core/issues/5401
+// https://github.com/vuejs/docs/issues/1708
+test('custom element event casing', () => {
+  customElements.define(
+    'custom-event-casing',
+    class Foo extends HTMLElement {
+      connectedCallback() {
+        this.dispatchEvent(new Event('camelCase'))
+        this.dispatchEvent(new Event('CAPScase'))
+        this.dispatchEvent(new Event('PascalCase'))
+      }
+    }
+  )
+
+  const container = document.createElement('div')
+  document.body.appendChild(container)
+
+  const handler = jest.fn()
+  const handler2 = jest.fn()
+  createApp({
+    template: `
+    <custom-event-casing
+      @camelCase="handler"
+      @CAPScase="handler"
+      @PascalCase="handler"
+      v-on="{
+        camelCase: handler2,
+        CAPScase: handler2,
+        PascalCase: handler2
+      }" />`,
+    methods: {
+      handler,
+      handler2
+    }
+  }).mount(container)
+
+  expect(handler).toHaveBeenCalledTimes(3)
+  expect(handler2).toHaveBeenCalledTimes(3)
+})