import { ASTElementHandler, ASTElementHandlers } from 'types/compiler' const fnExpRE = /^([\w$_]+|\([^)]*?\))\s*=>|^function(?:\s+[\w$]+)?\s*\(/ const fnInvokeRE = /\([^)]*?\);*$/ const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/ // KeyboardEvent.keyCode aliases const keyCodes: { [key: string]: number | Array } = { esc: 27, tab: 9, enter: 13, space: 32, up: 38, left: 37, right: 39, down: 40, delete: [8, 46] } // KeyboardEvent.key aliases const keyNames: { [key: string]: string | Array } = { // #7880: IE11 and Edge use `Esc` for Escape key name. esc: ['Esc', 'Escape'], tab: 'Tab', enter: 'Enter', // #9112: IE11 uses `Spacebar` for Space key name. space: [' ', 'Spacebar'], // #7806: IE11 uses key names without `Arrow` prefix for arrow keys. up: ['Up', 'ArrowUp'], left: ['Left', 'ArrowLeft'], right: ['Right', 'ArrowRight'], down: ['Down', 'ArrowDown'], // #9112: IE11 uses `Del` for Delete key name. delete: ['Backspace', 'Delete', 'Del'] } // #4868: modifiers that prevent the execution of the listener // need to explicitly return null so that we can determine whether to remove // the listener for .once const genGuard = condition => `if(${condition})return null;` const modifierCode: { [key: string]: string } = { stop: '$event.stopPropagation();', prevent: '$event.preventDefault();', self: genGuard(`$event.target !== $event.currentTarget`), ctrl: genGuard(`!$event.ctrlKey`), shift: genGuard(`!$event.shiftKey`), alt: genGuard(`!$event.altKey`), meta: genGuard(`!$event.metaKey`), left: genGuard(`'button' in $event && $event.button !== 0`), middle: genGuard(`'button' in $event && $event.button !== 1`), right: genGuard(`'button' in $event && $event.button !== 2`) } export function genHandlers( events: ASTElementHandlers, isNative: boolean ): string { const prefix = isNative ? 'nativeOn:' : 'on:' let staticHandlers = `` let dynamicHandlers = `` for (const name in events) { const handlerCode = genHandler(events[name]) //@ts-expect-error if (events[name] && events[name].dynamic) { dynamicHandlers += `${name},${handlerCode},` } else { staticHandlers += `"${name}":${handlerCode},` } } staticHandlers = `{${staticHandlers.slice(0, -1)}}` if (dynamicHandlers) { return prefix + `_d(${staticHandlers},[${dynamicHandlers.slice(0, -1)}])` } else { return prefix + staticHandlers } } function genHandler( handler: ASTElementHandler | Array ): string { if (!handler) { return 'function(){}' } if (Array.isArray(handler)) { return `[${handler.map(handler => genHandler(handler)).join(',')}]` } const isMethodPath = simplePathRE.test(handler.value) const isFunctionExpression = fnExpRE.test(handler.value) const isFunctionInvocation = simplePathRE.test( handler.value.replace(fnInvokeRE, '') ) if (!handler.modifiers) { if (isMethodPath || isFunctionExpression) { return handler.value } return `function($event){${ isFunctionInvocation ? `return ${handler.value}` : handler.value }}` // inline statement } else { let code = '' let genModifierCode = '' const keys: string[] = [] for (const key in handler.modifiers) { if (modifierCode[key]) { genModifierCode += modifierCode[key] // left/right if (keyCodes[key]) { keys.push(key) } } else if (key === 'exact') { const modifiers = handler.modifiers genModifierCode += genGuard( ['ctrl', 'shift', 'alt', 'meta'] .filter(keyModifier => !modifiers[keyModifier]) .map(keyModifier => `$event.${keyModifier}Key`) .join('||') ) } else { keys.push(key) } } if (keys.length) { code += genKeyFilter(keys) } // Make sure modifiers like prevent and stop get executed after key filtering if (genModifierCode) { code += genModifierCode } const handlerCode = isMethodPath ? `return ${handler.value}.apply(null, arguments)` : isFunctionExpression ? `return (${handler.value}).apply(null, arguments)` : isFunctionInvocation ? `return ${handler.value}` : handler.value return `function($event){${code}${handlerCode}}` } } function genKeyFilter(keys: Array): string { return ( // make sure the key filters only apply to KeyboardEvents // #9441: can't use 'keyCode' in $event because Chrome autofill fires fake // key events that do not have keyCode property... `if(!$event.type.indexOf('key')&&` + `${keys.map(genFilterCode).join('&&')})return null;` ) } function genFilterCode(key: string): string { const keyVal = parseInt(key, 10) if (keyVal) { return `$event.keyCode!==${keyVal}` } const keyCode = keyCodes[key] const keyName = keyNames[key] return ( `_k($event.keyCode,` + `${JSON.stringify(key)},` + `${JSON.stringify(keyCode)},` + `$event.key,` + `${JSON.stringify(keyName)}` + `)` ) }