Repository: vuejs/vue
Pull request: Support mutliple keys for keycode

An improvement for #4102 

1.  KeyCodes support array type.
2. Build-in keycodes alias could be overrided on the userland.

Changed files:
filename: flow/component.js
patch: @@ -104,8 +104,8 @@ declare interface Component {
   _t: (name: string, fallback: ?Array<VNode>, props: ?Object) => ?Array<VNode>;
   // apply v-bind object
   _b: (data: any, value: any, asProp?: boolean) => VNodeData;
-  // retrive custom keyCode
-  _k: (key: string) => ?number;
+  // check custom keyCode
+  _k: (eventKeyCode: number, key: string, buildinAlias: number | Array<number> | void) => boolean;
 
   // allow dynamic method registration
   [key: string]: any

filename: src/compiler/codegen/events.js
patch: @@ -74,20 +74,15 @@ function genHandler (
 }
 
 function genKeyFilter (keys: Array<string>): string {
-  const code = keys.length === 1
-    ? normalizeKeyCode(keys[0])
-    : Array.prototype.concat.apply([], keys.map(normalizeKeyCode))
-  if (Array.isArray(code)) {
-    return `if(${code.map(c => `$event.keyCode!==${c}`).join('&&')})return;`
-  } else {
-    return `if($event.keyCode!==${code})return;`
-  }
+  const code = keys.map(genFilterCode)
+  return `if(${code.join('&&')})return;`
 }
 
-function normalizeKeyCode (key) {
-  return (
-    parseInt(key, 10) || // number keyCode
-    keyCodes[key] || // built-in alias
-    `_k(${JSON.stringify(key)})` // custom alias
-  )
+function genFilterCode (key: number | string): string {
+  const keyVal = parseInt(key, 10)
+  if (keyVal) {
+    return `$event.keyCode!==${keyVal}`
+  }
+  const alias = keyCodes[key]
+  return `_k($event.keyCode,${JSON.stringify(key)}${alias ? ',' + JSON.stringify(alias) : ''})`
 }

filename: src/core/instance/render.js
patch: @@ -238,9 +238,18 @@ export function renderMixin (Vue: Class<Component>) {
     return data
   }
 
-  // expose v-on keyCodes
-  Vue.prototype._k = function getKeyCodes (key: string): any {
-    return config.keyCodes[key]
+  // check v-on keyCodes
+  Vue.prototype._k = function checkKeyCodes (
+    eventKeyCode: number,
+    key: string,
+    buildinAlias: number | Array<number> | void
+  ): boolean {
+    const keyCodes = config.keyCodes[key] || buildinAlias
+    if (Array.isArray(keyCodes)) {
+      return keyCodes.indexOf(eventKeyCode) === -1
+    } else {
+      return keyCodes !== eventKeyCode
+    }
   }
 }
 

filename: test/unit/features/directives/on.spec.js
patch: @@ -187,6 +187,28 @@ describe('Directive v-on', () => {
     expect(spy).toHaveBeenCalled()
   })
 
+  it('should override build-in keyCode', () => {
+    Vue.config.keyCodes.up = [1, 87]
+    vm = new Vue({
+      el,
+      template: `<input @keyup.up="foo" @keyup.down="foo">`,
+      methods: { foo: spy }
+    })
+    triggerEvent(vm.$el, 'keyup', e => {
+      e.keyCode = 87
+    })
+    expect(spy).toHaveBeenCalled()
+    triggerEvent(vm.$el, 'keyup', e => {
+      e.keyCode = 1
+    })
+    expect(spy).toHaveBeenCalledTimes(2)
+    // should not affect build-in down keycode
+    triggerEvent(vm.$el, 'keyup', e => {
+      e.keyCode = 40
+    })
+    expect(spy).toHaveBeenCalledTimes(3)
+  })
+
   it('should bind to a child component', () => {
     Vue.component('bar', {
       template: '<span>Hello</span>'

filename: test/unit/modules/compiler/codegen.spec.js
patch: @@ -224,17 +224,17 @@ describe('codegen', () => {
   it('generate events with keycode', () => {
     assertCodegen(
       '<input @input.enter="onInput">',
-      `with(this){return _h('input',{on:{"input":function($event){if($event.keyCode!==13)return;onInput($event)}}})}`
+      `with(this){return _h('input',{on:{"input":function($event){if(_k($event.keyCode,"enter",13))return;onInput($event)}}})}`
     )
     // multiple keycodes (delete)
     assertCodegen(
       '<input @input.delete="onInput">',
-      `with(this){return _h('input',{on:{"input":function($event){if($event.keyCode!==8&&$event.keyCode!==46)return;onInput($event)}}})}`
+      `with(this){return _h('input',{on:{"input":function($event){if(_k($event.keyCode,"delete",[8,46]))return;onInput($event)}}})}`
     )
     // multiple keycodes (chained)
     assertCodegen(
       '<input @keydown.enter.delete="onInput">',
-      `with(this){return _h('input',{on:{"keydown":function($event){if($event.keyCode!==13&&$event.keyCode!==8&&$event.keyCode!==46)return;onInput($event)}}})}`
+      `with(this){return _h('input',{on:{"keydown":function($event){if(_k($event.keyCode,"enter",13)&&_k($event.keyCode,"delete",[8,46]))return;onInput($event)}}})}`
     )
     // number keycode
     assertCodegen(
@@ -244,7 +244,7 @@ describe('codegen', () => {
     // custom keycode
     assertCodegen(
       '<input @input.custom="onInput">',
-      `with(this){return _h('input',{on:{"input":function($event){if($event.keyCode!==_k("custom"))return;onInput($event)}}})}`
+      `with(this){return _h('input',{on:{"input":function($event){if(_k($event.keyCode,"custom"))return;onInput($event)}}})}`
     )
   })