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
This commit is contained in:
parent
9f8f07ed38
commit
0739f8909a
@ -314,6 +314,37 @@ describe('compiler: element transform', () => {
|
|||||||
)
|
)
|
||||||
expect(root.helpers).toContain(MERGE_PROPS)
|
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({
|
expect(node.props).toMatchObject({
|
||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
callee: MERGE_PROPS,
|
callee: MERGE_PROPS,
|
||||||
@ -358,7 +389,8 @@ describe('compiler: element transform', () => {
|
|||||||
{
|
{
|
||||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
content: `handlers`
|
content: `handlers`
|
||||||
}
|
},
|
||||||
|
`true`
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -647,7 +647,7 @@ export function buildProps(
|
|||||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
loc,
|
loc,
|
||||||
callee: context.helper(TO_HANDLERS),
|
callee: context.helper(TO_HANDLERS),
|
||||||
arguments: [exp]
|
arguments: isComponent ? [exp] : [exp, `true`]
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -47,12 +47,17 @@ export const transformOn: DirectiveTransform = (
|
|||||||
if (rawName.startsWith('vue:')) {
|
if (rawName.startsWith('vue:')) {
|
||||||
rawName = `vnode-${rawName.slice(4)}`
|
rawName = `vnode-${rawName.slice(4)}`
|
||||||
}
|
}
|
||||||
// for all event listeners, auto convert it to camelCase. See issue #2249
|
const eventString =
|
||||||
eventName = createSimpleExpression(
|
node.tagType === ElementTypes.COMPONENT ||
|
||||||
toHandlerKey(camelize(rawName)),
|
rawName.startsWith('vnode') ||
|
||||||
true,
|
!/[A-Z]/.test(rawName)
|
||||||
arg.loc
|
? // 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 {
|
} else {
|
||||||
// #2388
|
// #2388
|
||||||
eventName = createCompoundExpression([
|
eventName = createCompoundExpression([
|
||||||
|
@ -5,14 +5,21 @@ import { warn } from '../warning'
|
|||||||
* For prefixing keys in v-on="obj" with "on"
|
* For prefixing keys in v-on="obj" with "on"
|
||||||
* @private
|
* @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> = {}
|
const ret: Record<string, any> = {}
|
||||||
if (__DEV__ && !isObject(obj)) {
|
if (__DEV__ && !isObject(obj)) {
|
||||||
warn(`v-on with no argument expects an object value.`)
|
warn(`v-on with no argument expects an object value.`)
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
for (const key in obj) {
|
for (const key in obj) {
|
||||||
ret[toHandlerKey(key)] = obj[key]
|
ret[
|
||||||
|
preserveCaseIfNecessary && /[A-Z]/.test(key)
|
||||||
|
? `on:${key}`
|
||||||
|
: toHandlerKey(key)
|
||||||
|
] = obj[key]
|
||||||
}
|
}
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,8 @@ function parseName(name: string): [string, EventListenerOptions | undefined] {
|
|||||||
;(options as any)[m[0].toLowerCase()] = true
|
;(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(
|
function createInvoker(
|
||||||
|
@ -16,7 +16,7 @@ export function patchProp(
|
|||||||
})
|
})
|
||||||
el.props[key] = nextValue
|
el.props[key] = nextValue
|
||||||
if (isOn(key)) {
|
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
|
;(el.eventListeners || (el.eventListeners = {}))[event] = nextValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
packages/vue/__tests__/customElementCasing.spec.ts
Normal file
42
packages/vue/__tests__/customElementCasing.spec.ts
Normal file
@ -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)
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user