UNPKG

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