diff --git a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts index 1c7a60ed..76d5ca68 100644 --- a/packages/compiler-dom/__tests__/transforms/vOn.spec.ts +++ b/packages/compiler-dom/__tests__/transforms/vOn.spec.ts @@ -77,13 +77,13 @@ describe('compiler-dom: transform v-on', () => { it('should support multiple modifiers and event options w/ prefixIdentifiers: true', () => { const { props: [prop] - } = parseWithVOn(`
`, { + } = parseWithVOn(`
`, { prefixIdentifiers: true }) expect(prop).toMatchObject({ type: NodeTypes.JS_PROPERTY, key: { - content: `onClick.capture.passive` + content: `onClickCaptureOnce` }, value: { callee: V_ON_WITH_MODIFIERS, @@ -101,7 +101,7 @@ describe('compiler-dom: transform v-on', () => { expect(prop).toMatchObject({ type: NodeTypes.JS_PROPERTY, key: { - content: `onKeydown.capture` + content: `onKeydownCapture` }, value: { callee: V_ON_WITH_KEYS, @@ -274,7 +274,7 @@ describe('compiler-dom: transform v-on', () => { ) expect(prop).toMatchObject({ key: { - content: `onKeyup.capture` + content: `onKeyupCapture` }, value: { type: NodeTypes.JS_CACHE_EXPRESSION, diff --git a/packages/compiler-dom/src/transforms/vOn.ts b/packages/compiler-dom/src/transforms/vOn.ts index c08d5c80..50c9bf75 100644 --- a/packages/compiler-dom/src/transforms/vOn.ts +++ b/packages/compiler-dom/src/transforms/vOn.ts @@ -11,7 +11,7 @@ import { isStaticExp } from '@vue/compiler-core' import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers' -import { makeMap } from '@vue/shared' +import { makeMap, capitalize } from '@vue/shared' const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`) const isNonKeyModifier = /*#__PURE__*/ makeMap( @@ -38,7 +38,8 @@ const resolveModifiers = (key: ExpressionNode, modifiers: string[]) => { const modifier = modifiers[i] if (isEventOptionModifier(modifier)) { - // eventOptionModifiers: modifiers for addEventListener() options, e.g. .passive & .capture + // eventOptionModifiers: modifiers for addEventListener() options, + // e.g. .passive & .capture eventOptionModifiers.push(modifier) } else { // runtimeModifiers: modifiers that needs runtime guards @@ -125,16 +126,10 @@ export const transformOn: DirectiveTransform = (dir, node, context) => { } if (eventOptionModifiers.length) { + const modifierPostfix = eventOptionModifiers.map(capitalize).join('') key = isStaticExp(key) - ? createSimpleExpression( - `${key.content}.${eventOptionModifiers.join(`.`)}`, - true - ) - : createCompoundExpression([ - `(`, - key, - `) + ".${eventOptionModifiers.join(`.`)}"` - ]) + ? createSimpleExpression(`${key.content}${modifierPostfix}`, true) + : createCompoundExpression([`(`, key, `) + "${modifierPostfix}"`]) } return { diff --git a/packages/runtime-core/__tests__/componentEmits.spec.ts b/packages/runtime-core/__tests__/componentEmits.spec.ts index 1e819b65..effe6571 100644 --- a/packages/runtime-core/__tests__/componentEmits.spec.ts +++ b/packages/runtime-core/__tests__/componentEmits.spec.ts @@ -174,7 +174,7 @@ describe('component: emit', () => { const fn = jest.fn() render( h(Foo, { - 'onFoo.once': fn + onFooOnce: fn }), nodeOps.createElement('div') ) @@ -213,8 +213,8 @@ describe('component: emit', () => { test('.once listeners', () => { const def2 = { emits: { click: null } } - expect(isEmitListener(def2, 'onClick.once')).toBe(true) - expect(isEmitListener(def2, 'onclick.once')).toBe(false) + expect(isEmitListener(def2, 'onClickOnce')).toBe(true) + expect(isEmitListener(def2, 'onclickOnce')).toBe(false) }) }) }) diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts index f8a419dd..cce4db0b 100644 --- a/packages/runtime-core/src/componentEmits.ts +++ b/packages/runtime-core/src/componentEmits.ts @@ -76,7 +76,7 @@ export function emit( handler = props[handlerName] } if (!handler) { - handler = props[handlerName + `.once`] + handler = props[handlerName + `Once`] if (!instance.emitted) { ;(instance.emitted = {} as Record)[handlerName] = true } else if (instance.emitted[handlerName]) { @@ -136,7 +136,7 @@ export function isEmitListener(comp: Component, key: string): boolean { if (!isOn(key) || !(emits = normalizeEmitsOptions(comp))) { return false } - key = key.replace(/\.once$/, '') + key = key.replace(/Once$/, '') return ( hasOwn(emits, key[2].toLowerCase() + key.slice(3)) || hasOwn(emits, key.slice(2)) diff --git a/packages/runtime-dom/__tests__/patchEvents.spec.ts b/packages/runtime-dom/__tests__/patchEvents.spec.ts index 8962dbf5..f926cc69 100644 --- a/packages/runtime-dom/__tests__/patchEvents.spec.ts +++ b/packages/runtime-dom/__tests__/patchEvents.spec.ts @@ -61,7 +61,7 @@ describe(`runtime-dom: events patching`, () => { const el = document.createElement('div') const event = new Event('click') const fn = jest.fn() - patchProp(el, 'onClick.once.capture', null, fn) + patchProp(el, 'onClickOnceCapture', null, fn) el.dispatchEvent(event) await timeout() el.dispatchEvent(event) @@ -73,13 +73,17 @@ describe(`runtime-dom: events patching`, () => { const el = document.createElement('div') const event = new Event('click') const fn = jest.fn() - patchProp(el, 'onClick.capture', null, fn) - patchProp(el, 'onClick.capture', fn, null) + patchProp(el, 'onClickCapture', null, fn) + el.dispatchEvent(event) + await timeout() + expect(fn).toHaveBeenCalledTimes(1) + + patchProp(el, 'onClickCapture', fn, null) el.dispatchEvent(event) await timeout() el.dispatchEvent(event) await timeout() - expect(fn).not.toHaveBeenCalled() + expect(fn).toHaveBeenCalledTimes(1) }) it('should support native onclick', async () => { diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index 755d5841..78199c64 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -82,23 +82,20 @@ export function patchEvent( } } -const optionsModifierRE = /\.(once|passive|capture)\b/g +const optionsModifierRE = /(?:Once|Passive|Capture)$/ function parseName(name: string): [string, EventListenerOptions | undefined] { - name = name.slice(2).toLowerCase() + let options: EventListenerOptions | undefined if (optionsModifierRE.test(name)) { - const options: EventListenerOptions = {} - name = name.replace( - optionsModifierRE, - (_, key: keyof EventListenerOptions) => { - options[key] = true - return '' - } - ) - return [name, options] - } else { - return [name, undefined] + options = {} + let m + while ((m = name.match(optionsModifierRE))) { + name = name.slice(0, name.length - m[0].length) + ;(options as any)[m[0].toLowerCase()] = true + options + } } + return [name.slice(2).toLowerCase(), options] } function createInvoker(