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(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`
 | 
			
		||||
          ]
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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([
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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
									
								
								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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user