vue3-yuanma/packages/runtime-core/src/componentEmits.ts
Evan You 6eb3399311 fix(runtime-dom): fix patching for attributes starting with on
fix #949

BREAKING CHANGE: Only props starting with `on` followed by an uppercase
letter or a non-letter character are considered event listeners.
2020-04-10 11:57:07 -04:00

117 lines
2.9 KiB
TypeScript

import {
isArray,
isOn,
hasOwn,
EMPTY_OBJ,
capitalize,
hyphenate,
isFunction,
def
} from '@vue/shared'
import { ComponentInternalInstance } from './component'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { warn } from './warning'
export type ObjectEmitsOptions = Record<
string,
((...args: any[]) => any) | null
>
export type EmitsOptions = ObjectEmitsOptions | string[]
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends ((k: infer I) => void)
? I
: never
export type EmitFn<
Options = ObjectEmitsOptions,
Event extends keyof Options = keyof Options
> = Options extends any[]
? (event: Options[0], ...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: ComponentInternalInstance,
event: string,
...args: any[]
) {
const props = instance.vnode.props || EMPTY_OBJ
if (__DEV__) {
const options = normalizeEmitsOptions(instance.type.emits)
if (options) {
if (!(event in options)) {
warn(
`Component emitted event "${event}" but it is not declared in the ` +
`emits option.`
)
} else {
const validator = options[event]
if (isFunction(validator)) {
const isValid = validator(...args)
if (!isValid) {
warn(
`Invalid event arguments: event validation failed for event "${event}".`
)
}
}
}
}
}
let handler = props[`on${capitalize(event)}`]
// for v-model update:xxx events, also trigger kebab-case equivalent
// for props passed via kebab-case
if (!handler && event.indexOf('update:') === 0) {
event = hyphenate(event)
handler = props[`on${capitalize(event)}`]
}
if (handler) {
callWithAsyncErrorHandling(
handler,
instance,
ErrorCodes.COMPONENT_EVENT_HANDLER,
args
)
}
}
export function normalizeEmitsOptions(
options: EmitsOptions | undefined
): ObjectEmitsOptions | undefined {
if (!options) {
return
} else if (isArray(options)) {
if ((options as any)._n) {
return (options as any)._n
}
const normalized: ObjectEmitsOptions = {}
options.forEach(key => (normalized[key] = null))
def(options, '_n', normalized)
return normalized
} else {
return options
}
}
// 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.
export function isEmitListener(emits: EmitsOptions, key: string): boolean {
return (
isOn(key) &&
(hasOwn(
(emits = normalizeEmitsOptions(emits) as ObjectEmitsOptions),
key[2].toLowerCase() + key.slice(3)
) ||
hasOwn(emits, key.slice(2)))
)
}