import { camelize , EMPTY_OBJ , toHandlerKey , extend , hasOwn , hyphenate , isArray , isFunction , isOn , toNumber } from '@vue/shared' ; import { ComponentInternalInstance , ComponentOptions , ConcreteComponent , formatComponentName } from './component' ; import { callWithAsyncErrorHandling , ErrorCodes } from './errorHandling' ; import { warn } from './warning' ; import { UnionToIntersection } from './helpers/typeUtils' ; import { devtoolsComponentEmit } from './devtools' ; import { AppContext } from './apiCreateApp' ; import { emit as compatInstanceEmit } from './compat/instanceEventEmitter' ; import { compatModelEventPrefix , compatModelEmit } from './compat/componentVModel' ; export type ObjectEmitsOptions = Record < string , ( ( ... args : any [ ] ) => any ) | null > ; export type EmitsOptions = ObjectEmitsOptions | string [ ] ; export type EmitsToProps < T extends EmitsOptions > = T extends string [ ] ? { [ K in string & `on${ Capitalize < T [ number ] > }` ] ? : ( ... args : any [ ] ) => any ; } : T extends ObjectEmitsOptions ? { [ K in string & `on${ Capitalize < string & keyof T > }` ] ? : K extends `on${ infer C }` ? T [ Uncapitalize < C > ] extends null ? ( ... args : any [ ] ) => any : ( ... args : T [ Uncapitalize < C > ] extends ( ... args : infer P ) => any ? P : never ) => any : never ; } : { } ; export type EmitFn < Options = ObjectEmitsOptions , Event extends keyof Options = keyof Options > = Options extends Array < infer V > ? ( event : V , ... args : any [ ] ) => void : { } extends Options ? ( event : string , ... args : any [ ] ) => void : UnionToIntersection < { [ key in Event ] : Options [ key ] extends ( ... args : infer Args ) => any ? ( event : key , ... args : Args ) => void : ( event : key , ... args : any [ ] ) => void ; } [ Event ] > ; export function emit ( instance , event , ... rawArgs ) { const props = instance . vnode . props || EMPTY_OBJ ; if ( __DEV__ ) { const { emitsOptions , propsOptions : [ propsOptions ] } = instance ; if ( emitsOptions ) { if ( ! ( event in emitsOptions ) && ! ( __COMPAT__ && ( event . startsWith ( 'hook:' ) || event . startsWith ( compatModelEventPrefix ) ) ) ) { if ( ! propsOptions || ! ( toHandlerKey ( event ) in propsOptions ) ) { warn ( `Component emitted event "${ event }" but it is neither declared in ` + `the emits option nor as an "${ toHandlerKey ( event ) }" prop.` ) ; } } else { const validator = emitsOptions [ event ] ; if ( isFunction ( validator ) ) { const isValid = validator ( ... rawArgs ) ; if ( ! isValid ) { warn ( `Invalid event arguments: event validation failed for event "${ event }".` ) ; } } } } } let args = rawArgs ; const isModelListener = event . startsWith ( 'update:' ) ; const modelArg = isModelListener && event . slice ( 7 ) ; if ( modelArg && modelArg in props ) { const modifiersKey = `${ modelArg === 'modelValue' ? 'model' : modelArg }Modifiers` ; const { number , trim } = props [ modifiersKey ] || EMPTY_OBJ ; if ( trim ) { args = rawArgs . map ( a => a . trim ( ) ) ; } else if ( number ) { args = rawArgs . map ( toNumber ) ; } } if ( __DEV__ || __FEATURE_PROD_DEVTOOLS__ ) { devtoolsComponentEmit ( instance , event , args ) ; } if ( __DEV__ ) { const lowerCaseEvent = event . toLowerCase ( ) ; if ( lowerCaseEvent !== event && props [ toHandlerKey ( lowerCaseEvent ) ] ) { warn ( `Event "${ lowerCaseEvent }" is emitted in component ` + `${ formatComponentName ( instance , instance . type ) } but the handler is registered for "${ event }". ` + `Note that HTML attributes are case-insensitive and you cannot use ` + `v-on to listen to camelCase events when using in-DOM templates. ` + `You should probably use "${ hyphenate ( event ) }" instead of "${ event }".` ) ; } } let handlerName ; let handler = props [ ( handlerName = toHandlerKey ( event ) ) ] || props [ ( handlerName = toHandlerKey ( camelize ( event ) ) ) ] ; if ( ! handler && isModelListener ) { handler = props [ ( handlerName = toHandlerKey ( hyphenate ( event ) ) ) ] ; } if ( handler ) { callWithAsyncErrorHandling ( handler , instance , ErrorCodes . COMPONENT_EVENT_HANDLER , args ) ; } const onceHandler = props [ handlerName + `Once` ] ; if ( onceHandler ) { if ( ! instance . emitted ) { instance . emitted = { } as Record < any , boolean > ; } else if ( instance . emitted [ handlerName ] ) { return ; } instance . emitted [ handlerName ] = true ; callWithAsyncErrorHandling ( onceHandler , instance , ErrorCodes . COMPONENT_EVENT_HANDLER , args ) ; } if ( __COMPAT__ ) { compatModelEmit ( instance , event , args ) ; return compatInstanceEmit ( instance , event , args ) ; } } export function normalizeEmitsOptions ( comp , appContext , asMixin = false ) { const cache = appContext . emitsCache ; const cached = cache . get ( comp ) ; if ( cached !== undefined ) { return cached ; } const raw = comp . emits ; let normalized = { } ; let hasExtends = false ; if ( __FEATURE_OPTIONS_API__ && ! isFunction ( comp ) ) { const extendEmits = ( raw ) => { const normalizedFromExtend = normalizeEmitsOptions ( raw , appContext , true ) ; if ( normalizedFromExtend ) { hasExtends = true ; extend ( normalized , normalizedFromExtend ) ; } } ; if ( ! asMixin && appContext . mixins . length ) { appContext . mixins . forEach ( extendEmits ) ; } if ( comp . extends ) { extendEmits ( comp . extends ) ; } if ( comp . mixins ) { comp . mixins . forEach ( extendEmits ) ; } } if ( ! raw && ! hasExtends ) { cache . set ( comp , null ) ; return null ; } if ( isArray ( raw ) ) { raw . forEach ( key => ( normalized [ key ] = null ) ) ; } else { extend ( normalized , raw ) ; } cache . set ( comp , normalized ) ; return normalized ; } export function isEmitListener ( options , key ) { if ( ! options || ! isOn ( key ) ) { return false ; } if ( __COMPAT__ && key . startsWith ( compatModelEventPrefix ) ) { return true ; } key = key . slice ( 2 ) . replace ( /Once$/ , '' ) ; return ( hasOwn ( options , key [ 0 ] . toLowerCase ( ) + key . slice ( 1 ) ) || hasOwn ( options , hyphenate ( key ) ) || hasOwn ( options , key ) ) ; }