2020-04-03 23:08:17 +00:00
|
|
|
import {
|
|
|
|
isArray,
|
|
|
|
isOn,
|
|
|
|
hasOwn,
|
|
|
|
EMPTY_OBJ,
|
|
|
|
capitalize,
|
2020-04-04 00:40:34 +00:00
|
|
|
hyphenate,
|
2020-04-06 21:57:27 +00:00
|
|
|
isFunction,
|
2020-07-13 15:55:46 +00:00
|
|
|
extend
|
2020-04-03 23:08:17 +00:00
|
|
|
} from '@vue/shared'
|
2020-08-19 20:11:29 +00:00
|
|
|
import { ComponentInternalInstance, ConcreteComponent } from './component'
|
2020-04-03 23:08:17 +00:00
|
|
|
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
|
2020-04-04 00:40:34 +00:00
|
|
|
import { warn } from './warning'
|
2020-04-16 15:27:52 +00:00
|
|
|
import { normalizePropsOptions } from './componentProps'
|
2020-06-09 14:37:00 +00:00
|
|
|
import { UnionToIntersection } from './helpers/typeUtils'
|
2020-08-23 23:31:32 +00:00
|
|
|
import { devtoolsComponentEmit } from './devtools'
|
2020-04-03 23:08:17 +00:00
|
|
|
|
|
|
|
export type ObjectEmitsOptions = Record<
|
|
|
|
string,
|
|
|
|
((...args: any[]) => any) | null
|
|
|
|
>
|
|
|
|
export type EmitsOptions = ObjectEmitsOptions | string[]
|
|
|
|
|
|
|
|
export type EmitFn<
|
|
|
|
Options = ObjectEmitsOptions,
|
|
|
|
Event extends keyof Options = keyof Options
|
|
|
|
> = Options extends any[]
|
2020-04-10 14:59:46 +00:00
|
|
|
? (event: Options[0], ...args: any[]) => void
|
2020-07-08 15:51:03 +00:00
|
|
|
: {} extends Options // if the emit is empty object (usually the default value for emit) should be converted to function
|
|
|
|
? (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]
|
|
|
|
>
|
2020-04-03 23:08:17 +00:00
|
|
|
|
|
|
|
export function emit(
|
|
|
|
instance: ComponentInternalInstance,
|
|
|
|
event: string,
|
|
|
|
...args: any[]
|
2020-04-10 14:59:46 +00:00
|
|
|
) {
|
2020-04-03 23:08:17 +00:00
|
|
|
const props = instance.vnode.props || EMPTY_OBJ
|
2020-04-04 00:40:34 +00:00
|
|
|
|
|
|
|
if (__DEV__) {
|
2020-07-13 15:55:46 +00:00
|
|
|
const options = normalizeEmitsOptions(instance.type)
|
2020-04-04 00:40:34 +00:00
|
|
|
if (options) {
|
|
|
|
if (!(event in options)) {
|
2020-06-09 15:27:40 +00:00
|
|
|
const propsOptions = normalizePropsOptions(instance.type)[0]
|
2020-04-16 15:27:52 +00:00
|
|
|
if (!propsOptions || !(`on` + capitalize(event) in propsOptions)) {
|
|
|
|
warn(
|
|
|
|
`Component emitted event "${event}" but it is neither declared in ` +
|
|
|
|
`the emits option nor as an "on${capitalize(event)}" prop.`
|
|
|
|
)
|
|
|
|
}
|
2020-04-04 00:40:34 +00:00
|
|
|
} else {
|
|
|
|
const validator = options[event]
|
|
|
|
if (isFunction(validator)) {
|
|
|
|
const isValid = validator(...args)
|
|
|
|
if (!isValid) {
|
|
|
|
warn(
|
|
|
|
`Invalid event arguments: event validation failed for event "${event}".`
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-23 23:31:32 +00:00
|
|
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
|
|
|
devtoolsComponentEmit(instance, event, args)
|
|
|
|
}
|
|
|
|
|
2020-07-14 15:48:05 +00:00
|
|
|
let handlerName = `on${capitalize(event)}`
|
|
|
|
let handler = props[handlerName]
|
2020-04-03 23:08:17 +00:00
|
|
|
// for v-model update:xxx events, also trigger kebab-case equivalent
|
|
|
|
// for props passed via kebab-case
|
2020-04-20 19:44:20 +00:00
|
|
|
if (!handler && event.startsWith('update:')) {
|
2020-07-14 15:48:05 +00:00
|
|
|
handlerName = `on${capitalize(hyphenate(event))}`
|
|
|
|
handler = props[handlerName]
|
|
|
|
}
|
|
|
|
if (!handler) {
|
2020-07-14 17:20:59 +00:00
|
|
|
handler = props[handlerName + `Once`]
|
2020-07-14 15:48:05 +00:00
|
|
|
if (!instance.emitted) {
|
|
|
|
;(instance.emitted = {} as Record<string, boolean>)[handlerName] = true
|
|
|
|
} else if (instance.emitted[handlerName]) {
|
|
|
|
return
|
|
|
|
}
|
2020-04-03 23:08:17 +00:00
|
|
|
}
|
|
|
|
if (handler) {
|
2020-04-10 14:59:46 +00:00
|
|
|
callWithAsyncErrorHandling(
|
2020-04-03 23:08:17 +00:00
|
|
|
handler,
|
|
|
|
instance,
|
|
|
|
ErrorCodes.COMPONENT_EVENT_HANDLER,
|
|
|
|
args
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-13 15:55:46 +00:00
|
|
|
function normalizeEmitsOptions(
|
2020-08-19 20:11:29 +00:00
|
|
|
comp: ConcreteComponent
|
2020-04-03 23:08:17 +00:00
|
|
|
): ObjectEmitsOptions | undefined {
|
2020-07-13 15:55:46 +00:00
|
|
|
if (hasOwn(comp, '__emits')) {
|
|
|
|
return comp.__emits
|
|
|
|
}
|
|
|
|
|
|
|
|
const raw = comp.emits
|
|
|
|
let normalized: ObjectEmitsOptions = {}
|
|
|
|
|
|
|
|
// apply mixin/extends props
|
|
|
|
let hasExtends = false
|
2020-07-21 01:51:30 +00:00
|
|
|
if (__FEATURE_OPTIONS_API__ && !isFunction(comp)) {
|
2020-07-13 15:55:46 +00:00
|
|
|
if (comp.extends) {
|
|
|
|
hasExtends = true
|
|
|
|
extend(normalized, normalizeEmitsOptions(comp.extends))
|
2020-04-03 23:08:17 +00:00
|
|
|
}
|
2020-07-13 15:55:46 +00:00
|
|
|
if (comp.mixins) {
|
|
|
|
hasExtends = true
|
|
|
|
comp.mixins.forEach(m => extend(normalized, normalizeEmitsOptions(m)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!raw && !hasExtends) {
|
|
|
|
return (comp.__emits = undefined)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isArray(raw)) {
|
|
|
|
raw.forEach(key => (normalized[key] = null))
|
2020-04-03 23:08:17 +00:00
|
|
|
} else {
|
2020-07-13 15:55:46 +00:00
|
|
|
extend(normalized, raw)
|
2020-04-03 23:08:17 +00:00
|
|
|
}
|
2020-07-13 15:55:46 +00:00
|
|
|
return (comp.__emits = normalized)
|
2020-04-03 23:08:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Check if an incoming prop key is a declared emit event listener.
|
|
|
|
// e.g. With `emits: { click: null }`, props named `onClick` and `onclick` are
|
|
|
|
// both considered matched listeners.
|
2020-08-19 20:11:29 +00:00
|
|
|
export function isEmitListener(comp: ConcreteComponent, key: string): boolean {
|
2020-07-14 15:48:05 +00:00
|
|
|
let emits: ObjectEmitsOptions | undefined
|
|
|
|
if (!isOn(key) || !(emits = normalizeEmitsOptions(comp))) {
|
2020-07-13 15:55:46 +00:00
|
|
|
return false
|
|
|
|
}
|
2020-07-14 17:20:59 +00:00
|
|
|
key = key.replace(/Once$/, '')
|
2020-04-03 23:08:17 +00:00
|
|
|
return (
|
2020-07-14 15:48:05 +00:00
|
|
|
hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
|
|
|
|
hasOwn(emits, key.slice(2))
|
2020-04-03 23:08:17 +00:00
|
|
|
)
|
|
|
|
}
|