events.ts 5.0 KB

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