501d98e1924c6cf5149369721e793cf3188672a471a3bd42b731e645d0bf08a427d76b12941b2be414379d7ce091d89e652b39cafa6105ea76cb2710d17d2b 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. /* @flow */
  2. const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/
  3. const fnInvokeRE = /\([^)]*?\);*$/
  4. const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/
  5. // KeyboardEvent.keyCode aliases
  6. const keyCodes: { [key: string]: number | Array<number> } = {
  7. esc: 27,
  8. tab: 9,
  9. enter: 13,
  10. space: 32,
  11. up: 38,
  12. left: 37,
  13. right: 39,
  14. down: 40,
  15. 'delete': [8, 46]
  16. }
  17. // KeyboardEvent.key aliases
  18. const keyNames: { [key: string]: string | Array<string> } = {
  19. // #7880: IE11 and Edge use `Esc` for Escape key name.
  20. esc: ['Esc', 'Escape'],
  21. tab: 'Tab',
  22. enter: 'Enter',
  23. // #9112: IE11 uses `Spacebar` for Space key name.
  24. space: [' ', 'Spacebar'],
  25. // #7806: IE11 uses key names without `Arrow` prefix for arrow keys.
  26. up: ['Up', 'ArrowUp'],
  27. left: ['Left', 'ArrowLeft'],
  28. right: ['Right', 'ArrowRight'],
  29. down: ['Down', 'ArrowDown'],
  30. // #9112: IE11 uses `Del` for Delete key name.
  31. 'delete': ['Backspace', 'Delete', 'Del']
  32. }
  33. // #4868: modifiers that prevent the execution of the listener
  34. // need to explicitly return null so that we can determine whether to remove
  35. // the listener for .once
  36. const genGuard = condition => `if(${condition})return null;`
  37. const modifierCode: { [key: string]: string } = {
  38. stop: '$event.stopPropagation();',
  39. prevent: '$event.preventDefault();',
  40. self: genGuard(`$event.target !== $event.currentTarget`),
  41. ctrl: genGuard(`!$event.ctrlKey`),
  42. shift: genGuard(`!$event.shiftKey`),
  43. alt: genGuard(`!$event.altKey`),
  44. meta: genGuard(`!$event.metaKey`),
  45. left: genGuard(`'button' in $event && $event.button !== 0`),
  46. middle: genGuard(`'button' in $event && $event.button !== 1`),
  47. right: genGuard(`'button' in $event && $event.button !== 2`)
  48. }
  49. export function genHandlers (
  50. events: ASTElementHandlers,
  51. isNative: boolean
  52. ): string {
  53. const prefix = isNative ? 'nativeOn:' : 'on:'
  54. let staticHandlers = ``
  55. let dynamicHandlers = ``
  56. for (const name in events) {
  57. const handlerCode = genHandler(events[name])
  58. if (events[name] && events[name].dynamic) {
  59. dynamicHandlers += `${name},${handlerCode},`
  60. } else {
  61. staticHandlers += `"${name}":${handlerCode},`
  62. }
  63. }
  64. staticHandlers = `{${staticHandlers.slice(0, -1)}}`
  65. if (dynamicHandlers) {
  66. return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])`
  67. } else {
  68. return prefix + staticHandlers
  69. }
  70. }
  71. // Generate handler code with binding params on Weex
  72. /* istanbul ignore next */
  73. function genWeexHandler (params: Array<any>, handlerCode: string) {
  74. let innerHandlerCode = handlerCode
  75. const exps = params.filter(exp => simplePathRE.test(exp) && exp !== '$event')
  76. const bindings = exps.map(exp => ({ '@binding': exp }))
  77. const args = exps.map((exp, i) => {
  78. const key = `$_${i + 1}`
  79. innerHandlerCode = innerHandlerCode.replace(exp, key)
  80. return key
  81. })
  82. args.push('$event')
  83. return '{\n' +
  84. `handler:function(${args.join(',')}){${innerHandlerCode}},\n` +
  85. `params:${JSON.stringify(bindings)}\n` +
  86. '}'
  87. }
  88. function genHandler (handler: ASTElementHandler | Array<ASTElementHandler>): string {
  89. if (!handler) {
  90. return 'function(){}'
  91. }
  92. if (Array.isArray(handler)) {
  93. return `[${handler.map(handler => genHandler(handler)).join(',')}]`
  94. }
  95. const isMethodPath = simplePathRE.test(handler.value)
  96. const isFunctionExpression = fnExpRE.test(handler.value)
  97. const isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, ''))
  98. if (!handler.modifiers) {
  99. if (isMethodPath || isFunctionExpression) {
  100. return handler.value
  101. }
  102. /* istanbul ignore if */
  103. if (__WEEX__ && handler.params) {
  104. return genWeexHandler(handler.params, handler.value)
  105. }
  106. return `function($event){${
  107. isFunctionInvocation ? `return ${handler.value}` : handler.value
  108. }}` // inline statement
  109. } else {
  110. let code = ''
  111. let genModifierCode = ''
  112. const keys = []
  113. for (const key in handler.modifiers) {
  114. if (modifierCode[key]) {
  115. genModifierCode += modifierCode[key]
  116. // left/right
  117. if (keyCodes[key]) {
  118. keys.push(key)
  119. }
  120. } else if (key === 'exact') {
  121. const modifiers: ASTModifiers = (handler.modifiers: any)
  122. genModifierCode += genGuard(
  123. ['ctrl', 'shift', 'alt', 'meta']
  124. .filter(keyModifier => !modifiers[keyModifier])
  125. .map(keyModifier => `$event.${keyModifier}Key`)
  126. .join('||')
  127. )
  128. } else {
  129. keys.push(key)
  130. }
  131. }
  132. if (keys.length) {
  133. code += genKeyFilter(keys)
  134. }
  135. // Make sure modifiers like prevent and stop get executed after key filtering
  136. if (genModifierCode) {
  137. code += genModifierCode
  138. }
  139. const handlerCode = isMethodPath
  140. ? `return ${handler.value}($event)`
  141. : isFunctionExpression
  142. ? `return (${handler.value})($event)`
  143. : isFunctionInvocation
  144. ? `return ${handler.value}`
  145. : handler.value
  146. /* istanbul ignore if */
  147. if (__WEEX__ && handler.params) {
  148. return genWeexHandler(handler.params, code + handlerCode)
  149. }
  150. return `function($event){${code}${handlerCode}}`
  151. }
  152. }
  153. function genKeyFilter (keys: Array<string>): string {
  154. return (
  155. // make sure the key filters only apply to KeyboardEvents
  156. // #9441: can't use 'keyCode' in $event because Chrome autofill fires fake
  157. // key events that do not have keyCode property...
  158. `if(!$event.type.indexOf('key')&&` +
  159. `${keys.map(genFilterCode).join('&&')})return null;`
  160. )
  161. }
  162. function genFilterCode (key: string): string {
  163. const keyVal = parseInt(key, 10)
  164. if (keyVal) {
  165. return `$event.keyCode!==${keyVal}`
  166. }
  167. const keyCode = keyCodes[key]
  168. const keyName = keyNames[key]
  169. return (
  170. `_k($event.keyCode,` +
  171. `${JSON.stringify(key)},` +
  172. `${JSON.stringify(keyCode)},` +
  173. `$event.key,` +
  174. `${JSON.stringify(keyName)}` +
  175. `)`
  176. )
  177. }