Skip to content

Commit

Permalink
fix(custom-element): fix event listeners with capital letter event na…
Browse files Browse the repository at this point in the history
…mes on custom elements

close vuejs/docs#1708
close vuejs/docs#1890
  • Loading branch information
yyx990803 committed Aug 30, 2022
1 parent 9f8f07e commit 0739f89
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 12 deletions.
Expand Up @@ -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,
Expand Down Expand Up @@ -358,7 +389,8 @@ describe('compiler: element transform', () => {
{
type: NodeTypes.SIMPLE_EXPRESSION,
content: `handlers`
}
},
`true`
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion packages/compiler-core/src/transforms/transformElement.ts
Expand Up @@ -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 {
Expand Down
17 changes: 11 additions & 6 deletions packages/compiler-core/src/transforms/vOn.ts
Expand Up @@ -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([
Expand Down
11 changes: 9 additions & 2 deletions packages/runtime-core/src/helpers/toHandlers.ts
Expand Up @@ -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
}
3 changes: 2 additions & 1 deletion packages/runtime-dom/src/modules/events.ts
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime-test/src/patchProp.ts
Expand Up @@ -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 changes: 42 additions & 0 deletions 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)
})

0 comments on commit 0739f89

Please sign in to comment.