]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(custom-element): fix event listeners with capital letter event names on custom...
authorEvan You <yyx990803@gmail.com>
Tue, 30 Aug 2022 06:07:35 +0000 (14:07 +0800)
committerEvan You <yyx990803@gmail.com>
Tue, 30 Aug 2022 06:07:35 +0000 (14:07 +0800)
close https://github.com/vuejs/docs/issues/1708
close https://github.com/vuejs/docs/pull/1890

packages/compiler-core/__tests__/transforms/transformElement.spec.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/vOn.ts
packages/runtime-core/src/helpers/toHandlers.ts
packages/runtime-dom/src/modules/events.ts
packages/runtime-test/src/patchProp.ts
packages/vue/__tests__/customElementCasing.spec.ts [new file with mode: 0644]

index bec7e119bd3d8209b5ff00ab9a0985eaab8d5e9c..43bd2589df1a2719d6ae41ee62b72377d40e5955 100644 (file)
@@ -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`
           ]
         },
         {
index 2d15227f70f6e038d85660fc6155b13b44e09a80..0eb3bb57628a3844d0fe0b7b5bae5265cf6bd16a 100644 (file)
@@ -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 {
index 060a7ef9097591ee15bf82135fa4e30c7e6599f2..a9dfe77eff7db9bc44c9cd7b37310da388a10ec6 100644 (file)
@@ -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([
index d366a9b76c90de2c273cbb37d2e0124b7e548766..78ad164d7c02017e35ea8e12572f416da0b274d6 100644 (file)
@@ -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
 }
index bd2279cf5f2bdbb9f7016d760bf4c6e02f2d1d77..d0f8d364a2983eee2079516344da6c6cb7a53fa8 100644 (file)
@@ -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(
index 9c4bfca583f7c7433c6132de18eaed75b18073a2..3ebc64d1b0472ef1824b388692061e555bb51059 100644 (file)
@@ -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
   }
 }
diff --git a/packages/vue/__tests__/customElementCasing.spec.ts b/packages/vue/__tests__/customElementCasing.spec.ts
new file mode 100644 (file)
index 0000000..90e4453
--- /dev/null
@@ -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)
+})