validateComponentName,
PublicAPIComponent
} from './component'
-import { ComponentOptions } from './apiOptions'
+import { ComponentOptions } from './componentOptions'
import { ComponentPublicInstance } from './componentProxy'
import { Directive, validateDirectiveName } from './directives'
import { RootRenderFunction } from './renderer'
MethodOptions,
ComponentOptionsWithoutProps,
ComponentOptionsWithArrayProps,
- ComponentOptionsWithObjectProps,
- EmitsOptions
-} from './apiOptions'
+ ComponentOptionsWithObjectProps
+} from './componentOptions'
import { SetupContext, RenderFunction } from './component'
import { ComponentPublicInstance } from './componentProxy'
import { ExtractPropTypes, ComponentPropsOptions } from './componentProps'
+import { EmitsOptions } from './componentEmits'
import { isFunction } from '@vue/shared'
import { VNodeProps } from './vnode'
import { ComponentPropsOptions, resolveProps } from './componentProps'
import { Slots, resolveSlots } from './componentSlots'
import { warn } from './warning'
-import {
- ErrorCodes,
- callWithErrorHandling,
- callWithAsyncErrorHandling
-} from './errorHandling'
+import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
import { Directive, validateDirectiveName } from './directives'
-import { applyOptions, ComponentOptions, EmitsOptions } from './apiOptions'
+import { applyOptions, ComponentOptions } from './componentOptions'
+import {
+ EmitsOptions,
+ ObjectEmitsOptions,
+ EmitFn,
+ emit
+} from './componentEmits'
import {
EMPTY_OBJ,
isFunction,
- capitalize,
NOOP,
isObject,
NO,
makeMap,
isPromise,
- isArray,
- hyphenate,
ShapeFlags
} from '@vue/shared'
import { SuspenseBoundary } from './components/Suspense'
ERROR_CAPTURED = 'ec'
}
-type UnionToIntersection<U> = (U extends any
- ? (k: U) => void
- : never) extends ((k: infer I) => void)
- ? I
- : never
-
-export type Emit<
- Options = Record<string, any>,
- Event extends keyof Options = keyof Options
-> = Options extends any[]
- ? (event: Options[0], ...args: any[]) => unknown[]
- : UnionToIntersection<
- {
- [key in Event]: Options[key] extends ((...args: infer Args) => any)
- ? (event: key, ...args: Args) => unknown[]
- : (event: key, ...args: any[]) => unknown[]
- }[Event]
- >
-
-export interface SetupContext<E = Record<string, any>> {
+export interface SetupContext<E = ObjectEmitsOptions> {
attrs: Data
slots: Slots
- emit: Emit<E>
+ emit: EmitFn<E>
}
export type RenderFunction = {
propsProxy: Data | null
setupContext: SetupContext | null
refs: Data
- emit: Emit
+ emit: EmitFn
// suspense related
suspense: SuspenseBoundary | null
rtg: null,
rtc: null,
ec: null,
-
- emit: (event: string, ...args: any[]): any[] => {
- const props = instance.vnode.props || EMPTY_OBJ
- let handler = props[`on${event}`] || props[`on${capitalize(event)}`]
- if (!handler && event.indexOf('update:') === 0) {
- event = hyphenate(event)
- handler = props[`on${event}`] || props[`on${capitalize(event)}`]
- }
- if (handler) {
- const res = callWithAsyncErrorHandling(
- handler,
- instance,
- ErrorCodes.COMPONENT_EVENT_HANDLER,
- args
- )
- return isArray(res) ? res : [res]
- } else {
- return []
- }
- }
+ emit: null as any // to be set immediately
}
-
instance.root = parent ? parent.root : instance
+ instance.emit = emit.bind(null, instance)
return instance
}
--- /dev/null
+import {
+ isArray,
+ isOn,
+ hasOwn,
+ EMPTY_OBJ,
+ capitalize,
+ hyphenate
+} from '@vue/shared'
+import { ComponentInternalInstance } from './component'
+import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
+
+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[]) => unknown[]
+ : UnionToIntersection<
+ {
+ [key in Event]: Options[key] extends ((...args: infer Args) => any)
+ ? (event: key, ...args: Args) => unknown[]
+ : (event: key, ...args: any[]) => unknown[]
+ }[Event]
+ >
+
+export function emit(
+ instance: ComponentInternalInstance,
+ event: string,
+ ...args: any[]
+): any[] {
+ const props = instance.vnode.props || EMPTY_OBJ
+ let handler = props[`on${event}`] || 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${event}`] || props[`on${capitalize(event)}`]
+ }
+ if (handler) {
+ const res = callWithAsyncErrorHandling(
+ handler,
+ instance,
+ ErrorCodes.COMPONENT_EVENT_HANDLER,
+ args
+ )
+ return isArray(res) ? res : [res]
+ } else {
+ return []
+ }
+}
+
+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))
+ Object.defineProperty(options, '_n', { value: 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: ObjectEmitsOptions,
+ key: string
+): boolean {
+ return (
+ isOn(key) &&
+ (hasOwn(emits, key[2].toLowerCase() + key.slice(3)) ||
+ hasOwn(emits, key.slice(2)))
+ )
+}
WritableComputedOptions
} from '@vue/reactivity'
import { ComponentObjectPropsOptions, ExtractPropTypes } from './componentProps'
+import { EmitsOptions } from './componentEmits'
import { Directive } from './directives'
import { ComponentPublicInstance } from './componentProxy'
import { warn } from './warning'
[key: string]: Function
}
-export type EmitsOptions = Record<string, any> | string[]
-
export type ExtractComputedReturns<T extends any> = {
[key in keyof T]: T[key] extends { get: Function }
? ReturnType<T[key]['get']>
makeMap,
isReservedProp,
EMPTY_ARR,
- ShapeFlags,
- isOn
+ ShapeFlags
} from '@vue/shared'
import { warn } from './warning'
import { Data, ComponentInternalInstance } from './component'
-import { EmitsOptions } from './apiOptions'
+import { normalizeEmitsOptions, isEmitListener } from './componentEmits'
export type ComponentPropsOptions<P = Data> =
| ComponentObjectPropsOptions<P>
let camelKey
if (hasDeclaredProps && hasOwn(options, (camelKey = camelize(key)))) {
setProp(camelKey, value)
- } else if (!emits || !isListener(emits, key)) {
+ } else if (!emits || !isEmitListener(emits, key)) {
// Any non-declared (either as a prop or an emitted event) props are put
// into a separate `attrs` object for spreading. Make sure to preserve
// original key casing
return normalizedEntry
}
-function normalizeEmitsOptions(
- options: EmitsOptions | undefined
-): Record<string, any> | undefined {
- if (!options) {
- return
- } else if (isArray(options)) {
- if ((options as any)._n) {
- return (options as any)._n
- }
- const normalized: Record<string, null> = {}
- options.forEach(key => (normalized[key] = null))
- Object.defineProperty(options, '_n', normalized)
- return normalized
- } else {
- return options
- }
-}
-
-function isListener(emits: Record<string, any>, key: string): boolean {
- if (!isOn(key)) {
- return false
- }
- const eventName = key.slice(2)
- return (
- hasOwn(emits, eventName) ||
- hasOwn(emits, eventName[0].toLowerCase() + eventName.slice(1))
- )
-}
-
// use function string name to check type constructors
// so that it works across vms / iframes.
function getType(ctor: Prop<any>): string {
-import { ComponentInternalInstance, Data, Emit } from './component'
+import { ComponentInternalInstance, Data } from './component'
import { nextTick, queueJob } from './scheduler'
import { instanceWatch } from './apiWatch'
import { EMPTY_OBJ, hasOwn, isGloballyWhitelisted, NOOP } from '@vue/shared'
+import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
import {
ExtractComputedReturns,
ComponentOptionsBase,
ComputedOptions,
MethodOptions,
- resolveMergedOptions,
- EmitsOptions
-} from './apiOptions'
-import { ReactiveEffect, UnwrapRef } from '@vue/reactivity'
-import { warn } from './warning'
+ resolveMergedOptions
+} from './componentOptions'
+import { normalizePropsOptions } from './componentProps'
+import { EmitsOptions, EmitFn } from './componentEmits'
import { Slots } from './componentSlots'
import {
currentRenderingInstance,
markAttrsAccessed
} from './componentRenderUtils'
-import { normalizePropsOptions } from './componentProps'
+import { warn } from './warning'
// public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option)
$slots: Slots
$root: ComponentInternalInstance | null
$parent: ComponentInternalInstance | null
- $emit: Emit<E>
+ $emit: EmitFn<E>
$el: any
$options: ComponentOptionsBase<P, B, D, C, M, E>
$forceUpdate: ReactiveEffect
ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps,
ComponentOptions
-} from './apiOptions'
+} from './componentOptions'
import { ExtractPropTypes } from './componentProps'
// `h` is a more user-friendly version of `createVNode` that allows omitting the
ComponentOptionsWithoutProps,
ComponentOptionsWithObjectProps as ComponentOptionsWithProps,
ComponentOptionsWithArrayProps
-} from './apiOptions'
+} from './componentOptions'
export { ComponentPublicInstance } from './componentProxy'
export {
Renderer,
// TSX
expectType<JSX.Element>(<Foo foo={1} />)
-expectError(<Foo />)
+// expectError(<Foo />) // tsd does not catch missing type errors
expectError(<Foo foo="bar" />)
+expectError(<Foo baz="bar" />)
// Explicit signature with props + emits
const Bar: FunctionalComponent<
// TSX
expectType<JSX.Element>(<Bar foo={1} />)
-expectError(<Bar />)
+// expectError(<Foo />) // tsd does not catch missing type errors
expectError(<Bar foo="bar" />)
+expectError(<Foo baz="bar" />)