)
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,
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handlers`
- }
+ },
+ `true`
]
},
{
type: NodeTypes.JS_CALL_EXPRESSION,
loc,
callee: context.helper(TO_HANDLERS),
- arguments: [exp]
+ arguments: isComponent ? [exp] : [exp, `true`]
})
}
} else {
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([
* 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
}
;(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(
})
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
}
}
--- /dev/null
+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)
+})