- [ ] `v-on`
- [x] simple expression
- [ ] compound expression
- - [ ] modifiers
+ - [x] modifiers
- [ ] `v-bind`
- [x] simple expression
- [ ] compound expression
DOMErrorCodes,
DOMErrorMessages
} from './errors'
+export { resolveModifiers } from './transforms/vOn'
export * from '@vue/compiler-core'
NodeTypes,
createCompoundExpression,
ExpressionNode,
- SimpleExpressionNode,
isStaticExp,
CompilerDeprecationTypes,
TransformContext,
checkCompatEnabled
} from '@vue/compiler-core'
import { V_ON_WITH_MODIFIERS, V_ON_WITH_KEYS } from '../runtimeHelpers'
-import { makeMap, capitalize } from '@vue/shared'
+import { makeMap, capitalize, isString } from '@vue/shared'
const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`)
const isNonKeyModifier = /*#__PURE__*/ makeMap(
true
)
-const resolveModifiers = (
- key: ExpressionNode,
+export const resolveModifiers = (
+ key: ExpressionNode | string,
modifiers: string[],
- context: TransformContext,
+ context: TransformContext | null,
loc: SourceLocation
) => {
const keyModifiers = []
if (
__COMPAT__ &&
modifier === 'native' &&
+ context &&
checkCompatEnabled(
CompilerDeprecationTypes.COMPILER_V_ON_NATIVE,
context,
// e.g. .passive & .capture
eventOptionModifiers.push(modifier)
} else {
+ const keyString = isString(key)
+ ? key
+ : isStaticExp(key)
+ ? key.content
+ : null
+
// runtimeModifiers: modifiers that needs runtime guards
if (maybeKeyModifier(modifier)) {
- if (isStaticExp(key)) {
- if (isKeyboardEvent((key as SimpleExpressionNode).content)) {
+ if (keyString) {
+ if (isKeyboardEvent(keyString)) {
keyModifiers.push(modifier)
} else {
nonKeyModifiers.push(modifier)
} else {
if (isNonKeyModifier(modifier)) {
nonKeyModifiers.push(modifier)
- } else {
+ } else if (!keyString || isKeyboardEvent(keyString)) {
keyModifiers.push(modifier)
}
}
`;
exports[`compile > directives > v-on > event modifier 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
export function render(_ctx) {
- const t0 = _template(\\"<div></div>\\")
+ const t0 = _template(\\"<a></a><form></form><a></a><div></div><div></div><a></a><div></div><input><input><input><input><input><input><input><input><input><input><input><input><input><input><input>\\")
const n0 = t0()
- const { 0: [n1],} = _children(n0)
- _on(n1, \\"click\\", _withModifiers(handleClick, [\\"prevent\\", \\"stop\\"]))
+ const { 0: [n1], 1: [n2], 2: [n3], 3: [n4], 4: [n5], 5: [n6], 6: [n7], 7: [n8], 8: [n9], 9: [n10], 10: [n11], 11: [n12], 12: [n13], 13: [n14], 14: [n15], 15: [n16], 16: [n17], 17: [n18], 18: [n19], 19: [n20], 20: [n21], 21: [n22],} = _children(n0)
+ _on(n1, \\"click\\", _withModifiers(handleEvent, [\\"stop\\"]))
+ _on(n2, \\"submit\\", _withModifiers(handleEvent, [\\"prevent\\"]))
+ _on(n3, \\"click\\", _withModifiers(handleEvent, [\\"stop\\", \\"prevent\\"]))
+ _on(n4, \\"click\\", _withModifiers(handleEvent, [\\"self\\"]))
+ _on(n5, \\"click\\", handleEvent, { capture: true })
+ _on(n6, \\"click\\", handleEvent, { once: true })
+ _on(n7, \\"scroll\\", handleEvent, { passive: true })
+ _on(n8, \\"contextmenu\\", _withModifiers(handleEvent, [\\"right\\"]))
+ _on(n9, \\"click\\", _withModifiers(handleEvent, [\\"left\\"]))
+ _on(n10, \\"mouseup\\", _withModifiers(handleEvent, [\\"middle\\"]))
+ _on(n11, \\"contextmenu\\", _withModifiers(handleEvent, [\\"right\\"]))
+ _on(n12, \\"keyup\\", _withKeys(handleEvent, [\\"enter\\"]))
+ _on(n13, \\"keyup\\", _withKeys(handleEvent, [\\"tab\\"]))
+ _on(n14, \\"keyup\\", _withKeys(handleEvent, [\\"delete\\"]))
+ _on(n15, \\"keyup\\", _withKeys(handleEvent, [\\"esc\\"]))
+ _on(n16, \\"keyup\\", _withKeys(handleEvent, [\\"space\\"]))
+ _on(n17, \\"keyup\\", _withKeys(handleEvent, [\\"up\\"]))
+ _on(n18, \\"keyup\\", _withKeys(handleEvent, [\\"down\\"]))
+ _on(n19, \\"keyup\\", _withKeys(handleEvent, [\\"left\\"]))
+ _on(n20, \\"keyup\\", _withModifiers(submit, [\\"middle\\"]))
+ _on(n21, \\"keyup\\", _withModifiers(submit, [\\"middle\\", \\"self\\"]))
+ _on(n22, \\"keyup\\", _withKeys(_withModifiers(handleEvent, [\\"self\\"]), [\\"enter\\"]))
return n0
}"
`;
test('event modifier', async () => {
const code = await compile(
- `<div @click.prevent.stop="handleClick"></div>`,
+ `<a @click.stop="handleEvent"></a>
+ <form @submit.prevent="handleEvent"></form>
+ <a @click.stop.prevent="handleEvent"></a>
+ <div @click.self="handleEvent"></div>
+ <div @click.capture="handleEvent"></div>
+ <a @click.once="handleEvent"></a>
+ <div @scroll.passive="handleEvent"></div>
+ <input @click.right="handleEvent" />
+ <input @click.left="handleEvent" />
+ <input @click.middle="handleEvent" />
+ <input @click.enter.right="handleEvent" />
+ <input @keyup.enter="handleEvent" />
+ <input @keyup.tab="handleEvent" />
+ <input @keyup.delete="handleEvent" />
+ <input @keyup.esc="handleEvent" />
+ <input @keyup.space="handleEvent" />
+ <input @keyup.up="handleEvent" />
+ <input @keyup.down="handleEvent" />
+ <input @keyup.left="handleEvent" />
+ <input @keyup.middle="submit" />
+ <input @keyup.middle.self="submit" />
+ <input @keyup.self.enter="handleEvent" />`,
{
bindingMetadata: {
- handleClick: BindingTypes.SETUP_CONST,
+ handleEvent: BindingTypes.SETUP_CONST,
},
},
)
OperationNode,
VaporHelper,
IRExpression,
+ SetEventIRNode,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
import { isString } from '@vue/shared'
}
case IRNodeTypes.SET_EVENT: {
- pushWithNewline(`${vaporHelper('on')}(n${oper.element}, `)
- genExpression(oper.name, context)
- push(', ')
-
- const hasModifiers = oper.modifiers.length
- hasModifiers && push(`${vaporHelper('withModifiers')}(`)
- genExpression(oper.value, context)
- hasModifiers && push(`, ${genArrayExpression(oper.modifiers)})`)
-
- push(')')
- return
+ return genSetEvent(oper, context)
}
case IRNodeTypes.SET_HTML: {
push(content, NewlineType.None, exp.loc, name)
}
+
+function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
+ const { vaporHelper, push, pushWithNewline } = context
+
+ pushWithNewline(`${vaporHelper('on')}(n${oper.element}, `)
+ // second arg: event name
+ genExpression(oper.name, context)
+ push(', ')
+
+ const { keys, nonKeys, options } = oper.modifiers
+ if (keys.length) {
+ push(`${vaporHelper('withKeys')}(`)
+ }
+ if (nonKeys.length) {
+ push(`${vaporHelper('withModifiers')}(`)
+ }
+ genExpression(oper.value, context)
+ if (nonKeys.length) {
+ push(`, ${genArrayExpression(nonKeys)})`)
+ }
+ if (keys.length) {
+ push(`, ${genArrayExpression(keys)})`)
+ }
+ if (options.length) {
+ push(`, { ${options.map((v) => `${v}: true`).join(', ')} }`)
+ }
+
+ push(')')
+}
element: number
name: IRExpression
value: IRExpression
- modifiers: string[]
+ modifiers: {
+ // modifiers for addEventListener() options, e.g. .passive & .capture
+ options: string[]
+ // modifiers that needs runtime guards, withKeys
+ keys: string[]
+ // modifiers that needs runtime guards, withModifiers
+ nonKeys: string[]
+ }
}
export interface SetHtmlIRNode extends BaseIRNode {
export type DirectiveTransform = (
dir: DirectiveNode,
node: ElementNode,
- context: TransformContext,
+ context: TransformContext<ElementNode>,
// a platform specific compiler can import the base transform and augment
// it by passing in this optional argument.
// augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,
import { isVoidTag } from '@vue/shared'
import { NodeTransform, TransformContext } from '../transform'
import { IRNodeTypes } from '../ir'
+import { transformVOn } from './vOn'
export const transformElement: NodeTransform = (node, ctx) => {
return function postTransformElement() {
return
}
- const { arg, exp, loc, modifiers } = prop
+ const { arg, exp, loc } = prop
const directiveTransform = context.options.directiveTransforms[name]
if (directiveTransform) {
directiveTransform(prop, node, context)
break
}
case 'on': {
- if (!exp && !modifiers.length) {
- context.options.onError(
- createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
- )
- return
- }
-
- if (!arg) {
- // TODO support v-on="{}"
- return
- } else if (exp === undefined) {
- // TODO: support @foo
- // https://github.com/vuejs/core/pull/9451
- return
- }
-
- // TODO reactive
- context.registerOperation({
- type: IRNodeTypes.SET_EVENT,
- loc: node.loc,
- element: context.reference(),
- name: arg,
- value: exp,
- modifiers,
- })
+ transformVOn(prop, node, context)
break
}
}
--- /dev/null
+import {
+ createCompilerError,
+ createSimpleExpression,
+ ErrorCodes,
+ ExpressionNode,
+ isStaticExp,
+ NodeTypes,
+} from '@vue/compiler-core'
+import type { DirectiveTransform } from '../transform'
+import { IRNodeTypes } from '../ir'
+import { resolveModifiers } from '@vue/compiler-dom'
+
+export const transformVOn: DirectiveTransform = (dir, node, context) => {
+ const { arg, exp, loc, modifiers } = dir
+ if (!exp && !modifiers.length) {
+ context.options.onError(
+ createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
+ )
+ return
+ }
+
+ if (!arg) {
+ // TODO support v-on="{}"
+ return
+ } else if (exp === undefined) {
+ // TODO X_V_ON_NO_EXPRESSION error
+ return
+ } else if (arg.type === NodeTypes.COMPOUND_EXPRESSION) {
+ // TODO
+ return
+ }
+
+ const handlerKey = `on${arg.content}`
+ const { keyModifiers, nonKeyModifiers, eventOptionModifiers } =
+ resolveModifiers(handlerKey, modifiers, null, loc)
+
+ // normalize click.right and click.middle since they don't actually fire
+ let name = arg.content
+ if (nonKeyModifiers.includes('right')) {
+ name = transformClick(arg, 'contextmenu')
+ }
+ if (nonKeyModifiers.includes('middle')) {
+ name = transformClick(arg, 'mouseup')
+ }
+
+ // TODO reactive
+ context.registerOperation({
+ type: IRNodeTypes.SET_EVENT,
+ loc,
+ element: context.reference(),
+ name: createSimpleExpression(name, true, arg.loc),
+ value: exp,
+ modifiers: {
+ keys: keyModifiers,
+ nonKeys: nonKeyModifiers,
+ options: eventOptionModifiers,
+ },
+ })
+}
+
+function transformClick(key: ExpressionNode, event: string) {
+ const isStaticClick =
+ isStaticExp(key) && key.content.toLowerCase() === 'click'
+
+ if (isStaticClick) {
+ return event
+ } else if (key.type !== NodeTypes.SIMPLE_EXPRESSION) {
+ // TODO: handle CompoundExpression
+ return 'TODO'
+ } else {
+ return key.content.toLowerCase()
+ }
+}
export * from './render'
export * from './template'
export * from './scheduler'
-export { withModifiers } from '@vue/runtime-dom'
+export { withModifiers, withKeys } from '@vue/runtime-dom'
export function on(
- el: any,
+ el: HTMLElement,
event: string,
handler: () => any,
- options?: EventListenerOptions,
+ options?: AddEventListenerOptions,
) {
el.addEventListener(event, handler, options)
}