types(defineComponent): support for GlobalComponents, typed Directives and respect `expose` on defineComponent (#3399)

close #3367
This commit is contained in:
Carlos Rodrigues 2024-04-25 09:04:03 +01:00 committed by GitHub
parent 0e6e3c7eb0
commit 4cc9ca870c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 533 additions and 86 deletions

View File

@ -1,4 +1,4 @@
import { defineComponent } from 'vue' import { type DefineComponent, type Directive, defineComponent } from 'vue'
import { expectType } from './utils' import { expectType } from './utils'
declare module 'vue' { declare module 'vue' {
@ -6,6 +6,14 @@ declare module 'vue' {
test?(n: number): void test?(n: number): void
} }
interface GlobalDirectives {
test: Directive
}
interface GlobalComponents {
RouterView: DefineComponent<{}>
}
interface ComponentCustomProperties { interface ComponentCustomProperties {
state?: 'stopped' | 'running' state?: 'stopped' | 'running'
} }
@ -46,6 +54,8 @@ export const Custom = defineComponent({
}, },
}) })
expectType<Directive>(Custom.directives!.test)
expectType<DefineComponent<{}>>(Custom.components!.RouterView)
expectType<JSX.Element>(<Custom baz={1} />) expectType<JSX.Element>(<Custom baz={1} />)
expectType<JSX.Element>(<Custom custom={1} baz={1} />) expectType<JSX.Element>(<Custom custom={1} baz={1} />)
expectType<JSX.Element>(<Custom bar="bar" baz={1} />) expectType<JSX.Element>(<Custom bar="bar" baz={1} />)

View File

@ -1501,18 +1501,108 @@ describe('should work when props type is incompatible with setup returned type '
describe('withKeys and withModifiers as pro', () => { describe('withKeys and withModifiers as pro', () => {
const onKeydown = withKeys(e => {}, ['']) const onKeydown = withKeys(e => {}, [''])
// @ts-expect-error invalid modifiers
const onClick = withModifiers(e => {}, ['']) const onClick = withModifiers(e => {}, [''])
;<input onKeydown={onKeydown} onClick={onClick} /> ;<input onKeydown={onKeydown} onClick={onClick} />
}) })
// #3367 expose components types
describe('expose component types', () => {
const child = defineComponent({
props: {
a: String,
},
})
const parent = defineComponent({
components: {
child,
child2: {
template: `<div></div>`,
},
},
})
expectType<typeof child>(parent.components!.child)
expectType<Component>(parent.components!.child2)
// global components
expectType<Readonly<KeepAliveProps>>(
new parent.components!.KeepAlive().$props,
)
expectType<Readonly<KeepAliveProps>>(new child.components!.KeepAlive().$props)
// runtime-dom components
expectType<Readonly<TransitionProps>>(
new parent.components!.Transition().$props,
)
expectType<Readonly<TransitionProps>>(
new child.components!.Transition().$props,
)
})
describe('directive typing', () => {
const customDirective: Directive = {
created(_) {},
}
const comp = defineComponent({
props: {
a: String,
},
directives: {
customDirective,
localDirective: {
created(_, { arg }) {
expectType<string | undefined>(arg)
},
},
},
})
expectType<typeof customDirective>(comp.directives!.customDirective)
expectType<Directive>(comp.directives!.localDirective)
// global directive
expectType<typeof vShow>(comp.directives!.vShow)
})
describe('expose typing', () => {
const Comp = defineComponent({
expose: ['a', 'b'],
props: {
some: String,
},
data() {
return { a: 1, b: '2', c: 1 }
},
})
expectType<Array<'a' | 'b'>>(Comp.expose!)
const vm = new Comp()
// internal should still be exposed
vm.$props
expectType<number>(vm.a)
expectType<string>(vm.b)
// @ts-expect-error shouldn't be exposed
vm.c
})
import type { import type {
AllowedComponentProps, AllowedComponentProps,
ComponentCustomProps, ComponentCustomProps,
ComponentOptionsMixin, ComponentOptionsMixin,
DefineComponent, DefineComponent,
Directive,
EmitsOptions, EmitsOptions,
ExtractPropTypes, ExtractPropTypes,
KeepAliveProps,
TransitionProps,
VNodeProps, VNodeProps,
vShow,
} from 'vue' } from 'vue'
// code generated by tsc / vue-tsc, make sure this continues to work // code generated by tsc / vue-tsc, make sure this continues to work

View File

@ -0,0 +1,58 @@
import { type Directive, type ObjectDirective, vModelText } from 'vue'
import { describe, expectType } from './utils'
type ExtractBinding<T> = T extends (
el: any,
binding: infer B,
vnode: any,
prev: any,
) => any
? B
: never
declare function testDirective<
Value,
Modifiers extends string = string,
Arg extends string = string,
>(): ExtractBinding<Directive<any, Value, Modifiers, Arg>>
describe('vmodel', () => {
expectType<ObjectDirective<any, any, 'trim' | 'number' | 'lazy', string>>(
vModelText,
)
// @ts-expect-error
expectType<ObjectDirective<any, any, 'not-valid', string>>(vModelText)
})
describe('custom', () => {
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
}>(testDirective<number, 'a' | 'b', 'Arg'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<number, 'a', 'Arg'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<number, 'a' | 'b', 'Argx'>())
expectType<{
value: number
oldValue: number | null
arg?: 'Arg'
modifiers: Record<'a' | 'b', boolean>
// @ts-expect-error
}>(testDirective<string, 'a' | 'b', 'Arg'>())
})

View File

@ -6,13 +6,17 @@ import type {
ComponentOptionsWithArrayProps, ComponentOptionsWithArrayProps,
ComponentOptionsWithObjectProps, ComponentOptionsWithObjectProps,
ComponentOptionsWithoutProps, ComponentOptionsWithoutProps,
ComponentProvideOptions,
ComputedOptions, ComputedOptions,
MethodOptions, MethodOptions,
RenderFunction, RenderFunction,
} from './componentOptions' } from './componentOptions'
import type { import type {
AllowedComponentProps, AllowedComponentProps,
Component,
ComponentCustomProps, ComponentCustomProps,
GlobalComponents,
GlobalDirectives,
SetupContext, SetupContext,
} from './component' } from './component'
import type { import type {
@ -29,6 +33,7 @@ import type {
CreateComponentPublicInstance, CreateComponentPublicInstance,
} from './componentPublicInstance' } from './componentPublicInstance'
import type { SlotsType } from './componentSlots' import type { SlotsType } from './componentSlots'
import type { Directive } from './directives'
export type PublicProps = VNodeProps & export type PublicProps = VNodeProps &
AllowedComponentProps & AllowedComponentProps &
@ -55,6 +60,10 @@ export type DefineComponent<
Props = ResolveProps<PropsOrPropOptions, E>, Props = ResolveProps<PropsOrPropOptions, E>,
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>, Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>,
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentPublicInstanceConstructor< > = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance< CreateComponentPublicInstance<
Props, Props,
@ -69,7 +78,10 @@ export type DefineComponent<
Defaults, Defaults,
true, true,
{}, {},
S S,
LC & GlobalComponents,
Directives & GlobalDirectives,
Exposed
> >
> & > &
ComponentOptionsBase< ComponentOptionsBase<
@ -85,7 +97,11 @@ export type DefineComponent<
Defaults, Defaults,
{}, {},
string, string,
S S,
LC & GlobalComponents,
Directives & GlobalDirectives,
Exposed,
Provide
> & > &
PP PP
@ -166,9 +182,13 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
>( >(
options: ComponentOptionsWithoutProps< options: ComponentOptionsWithoutProps<
Props, Props,
@ -182,7 +202,11 @@ export function defineComponent<
EE, EE,
I, I,
II, II,
S S,
LC,
Directives,
Exposed,
Provide
>, >,
): DefineComponent< ): DefineComponent<
Props, Props,
@ -197,7 +221,11 @@ export function defineComponent<
PublicProps, PublicProps,
ResolveProps<Props, E>, ResolveProps<Props, E>,
ExtractDefaultPropTypes<Props>, ExtractDefaultPropTypes<Props>,
S S,
LC,
Directives,
Exposed,
Provide
> >
// overload 3: object format with array props declaration // overload 3: object format with array props declaration
@ -216,6 +244,10 @@ export function defineComponent<
S extends SlotsType = {}, S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Readonly<{ [key in PropNames]?: any }>, Props = Readonly<{ [key in PropNames]?: any }>,
>( >(
options: ComponentOptionsWithArrayProps< options: ComponentOptionsWithArrayProps<
@ -230,7 +262,11 @@ export function defineComponent<
EE, EE,
I, I,
II, II,
S S,
LC,
Directives,
Exposed,
Provide
>, >,
): DefineComponent< ): DefineComponent<
Props, Props,
@ -245,7 +281,11 @@ export function defineComponent<
PublicProps, PublicProps,
ResolveProps<Props, E>, ResolveProps<Props, E>,
ExtractDefaultPropTypes<Props>, ExtractDefaultPropTypes<Props>,
S S,
LC,
Directives,
Exposed,
Provide
> >
// overload 4: object format with object props declaration // overload 4: object format with object props declaration
@ -262,9 +302,13 @@ export function defineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
EE extends string = string, EE extends string = string,
S extends SlotsType = {},
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
>( >(
options: ComponentOptionsWithObjectProps< options: ComponentOptionsWithObjectProps<
PropsOptions, PropsOptions,
@ -278,7 +322,11 @@ export function defineComponent<
EE, EE,
I, I,
II, II,
S S,
LC,
Directives,
Exposed,
Provide
>, >,
): DefineComponent< ): DefineComponent<
PropsOptions, PropsOptions,
@ -293,7 +341,11 @@ export function defineComponent<
PublicProps, PublicProps,
ResolveProps<PropsOptions, E>, ResolveProps<PropsOptions, E>,
ExtractDefaultPropTypes<PropsOptions>, ExtractDefaultPropTypes<PropsOptions>,
S S,
LC,
Directives,
Exposed,
Provide
> >
// implementation, close to no-op // implementation, close to no-op

View File

@ -86,6 +86,13 @@ import {
import type { SchedulerJob } from './scheduler' import type { SchedulerJob } from './scheduler'
import type { LifecycleHooks } from './enums' import type { LifecycleHooks } from './enums'
// Augment GlobalComponents
import type { TeleportProps } from './components/Teleport'
import type { SuspenseProps } from './components/Suspense'
import type { KeepAliveProps } from './components/KeepAlive'
import type { BaseTransitionProps } from './components/BaseTransition'
import type { DefineComponent } from './apiDefineComponent'
export type Data = Record<string, unknown> export type Data = Record<string, unknown>
/** /**
@ -126,6 +133,45 @@ export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
*/ */
export interface ComponentCustomProps {} export interface ComponentCustomProps {}
/**
* For globally defined Directives
* Here is an example of adding a directive `VTooltip` as global directive:
*
* @example
* ```ts
* import VTooltip from 'v-tooltip'
*
* declare module '@vue/runtime-core' {
* interface GlobalDirectives {
* VTooltip
* }
* }
* ```
*/
export interface GlobalDirectives extends Record<string, Directive> {}
/**
* For globally defined Components
* Here is an example of adding a component `RouterView` as global component:
*
* @example
* ```ts
* import { RouterView } from 'vue-router'
*
* declare module '@vue/runtime-core' {
* interface GlobalComponents {
* RouterView
* }
* }
* ```
*/
export interface GlobalComponents extends Record<string, Component> {
Teleport: DefineComponent<TeleportProps>
Suspense: DefineComponent<SuspenseProps>
KeepAlive: DefineComponent<KeepAliveProps>
BaseTransition: DefineComponent<BaseTransitionProps>
}
/** /**
* Default allowed non-declared props on component in TSX * Default allowed non-declared props on component in TSX
*/ */

View File

@ -112,7 +112,11 @@ export interface ComponentOptionsBase<
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {}, S extends SlotsType = {},
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II>, LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, I, II, Provide>,
ComponentInternalOptions, ComponentInternalOptions,
ComponentCustomOptions { ComponentCustomOptions {
setup?: ( setup?: (
@ -136,13 +140,16 @@ export interface ComponentOptionsBase<
// Luckily `render()` doesn't need any arguments nor does it care about return // Luckily `render()` doesn't need any arguments nor does it care about return
// type. // type.
render?: Function render?: Function
components?: Record<string, Component> // NOTE: extending both LC and Record<string, Component> allows objects to be forced
directives?: Record<string, Directive> // to be of type Component, while still inferring LC generic
components?: LC & Record<string, Component>
// NOTE: extending both Directives and Record<string, Directive> allows objects to be forced
// to be of type Directive, while still inferring Directives generic
directives?: Directives & Record<string, Directive>
inheritAttrs?: boolean inheritAttrs?: boolean
emits?: (E | EE[]) & ThisType<void> emits?: (E | EE[]) & ThisType<void>
slots?: S slots?: S
// TODO infer public instance type based on exposed keys expose?: Exposed[]
expose?: string[]
serverPrefetch?(): void | Promise<any> serverPrefetch?(): void | Promise<any>
// Runtime compiler only ----------------------------------------------------- // Runtime compiler only -----------------------------------------------------
@ -224,6 +231,10 @@ export type ComponentOptionsWithoutProps<
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
PE = Props & EmitsToProps<E>, PE = Props & EmitsToProps<E>,
> = ComponentOptionsBase< > = ComponentOptionsBase<
PE, PE,
@ -238,7 +249,11 @@ export type ComponentOptionsWithoutProps<
{}, {},
I, I,
II, II,
S S,
LC,
Directives,
Exposed,
Provide
> & { > & {
props?: undefined props?: undefined
} & ThisType< } & ThisType<
@ -255,7 +270,10 @@ export type ComponentOptionsWithoutProps<
{}, {},
false, false,
I, I,
S S,
LC,
Directives,
Exposed
> >
> >
@ -272,6 +290,10 @@ export type ComponentOptionsWithArrayProps<
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>, Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>,
> = ComponentOptionsBase< > = ComponentOptionsBase<
Props, Props,
@ -286,7 +308,11 @@ export type ComponentOptionsWithArrayProps<
{}, {},
I, I,
II, II,
S S,
LC,
Directives,
Exposed,
Provide
> & { > & {
props: PropNames[] props: PropNames[]
} & ThisType< } & ThisType<
@ -303,7 +329,10 @@ export type ComponentOptionsWithArrayProps<
{}, {},
false, false,
I, I,
S S,
LC,
Directives,
Exposed
> >
> >
@ -320,6 +349,10 @@ export type ComponentOptionsWithObjectProps<
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
II extends string = string, II extends string = string,
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>, Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
Defaults = ExtractDefaultPropTypes<PropsOptions>, Defaults = ExtractDefaultPropTypes<PropsOptions>,
> = ComponentOptionsBase< > = ComponentOptionsBase<
@ -335,7 +368,11 @@ export type ComponentOptionsWithObjectProps<
Defaults, Defaults,
I, I,
II, II,
S S,
LC,
Directives,
Exposed,
Provide
> & { > & {
props: PropsOptions & ThisType<void> props: PropsOptions & ThisType<void>
} & ThisType< } & ThisType<
@ -352,7 +389,9 @@ export type ComponentOptionsWithObjectProps<
Defaults, Defaults,
false, false,
I, I,
S S,
LC,
Directives
> >
> >
@ -365,7 +404,15 @@ export type ComponentOptions<
Mixin extends ComponentOptionsMixin = any, Mixin extends ComponentOptionsMixin = any,
Extends extends ComponentOptionsMixin = any, Extends extends ComponentOptionsMixin = any,
E extends EmitsOptions = any, E extends EmitsOptions = any,
S extends SlotsType = any, EE extends string = string,
Defaults = {},
I extends ComponentInjectOptions = {},
II extends string = string,
S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentOptionsBase< > = ComponentOptionsBase<
Props, Props,
RawBindings, RawBindings,
@ -375,8 +422,15 @@ export type ComponentOptions<
Mixin, Mixin,
Extends, Extends,
E, E,
string, EE,
S Defaults,
I,
II,
S,
LC,
Directives,
Exposed,
Provide
> & > &
ThisType< ThisType<
CreateComponentPublicInstance< CreateComponentPublicInstance<
@ -388,7 +442,13 @@ export type ComponentOptions<
Mixin, Mixin,
Extends, Extends,
E, E,
Readonly<Props> Readonly<Props>,
Defaults,
false,
I,
S,
LC,
Directives
> >
> >
@ -403,6 +463,12 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
any, any,
any, any,
any, any,
any,
any,
any,
any,
any,
any,
any any
> >
@ -464,6 +530,7 @@ interface LegacyOptions<
Extends extends ComponentOptionsMixin, Extends extends ComponentOptionsMixin,
I extends ComponentInjectOptions, I extends ComponentInjectOptions,
II extends string, II extends string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> { > {
compatConfig?: CompatConfig compatConfig?: CompatConfig
@ -497,7 +564,7 @@ interface LegacyOptions<
computed?: C computed?: C
methods?: M methods?: M
watch?: ComponentWatchOptions watch?: ComponentWatchOptions
provide?: ComponentProvideOptions provide?: Provide
inject?: I | II[] inject?: I | II[]
// assets // assets

View File

@ -1,4 +1,5 @@
import { import {
type Component,
type ComponentInternalInstance, type ComponentInternalInstance,
type Data, type Data,
getExposeProxy, getExposeProxy,
@ -35,6 +36,7 @@ import {
type ComponentInjectOptions, type ComponentInjectOptions,
type ComponentOptionsBase, type ComponentOptionsBase,
type ComponentOptionsMixin, type ComponentOptionsMixin,
type ComponentProvideOptions,
type ComputedOptions, type ComputedOptions,
type ExtractComputedReturns, type ExtractComputedReturns,
type InjectToObject, type InjectToObject,
@ -51,6 +53,7 @@ import { markAttrsAccessed } from './componentRenderUtils'
import { currentRenderingInstance } from './componentRenderContext' import { currentRenderingInstance } from './componentRenderContext'
import { warn } from './warning' import { warn } from './warning'
import { installCompatInstanceProperties } from './compat/instance' import { installCompatInstanceProperties } from './compat/instance'
import type { Directive } from './directives'
/** /**
* Custom properties added to component instances in any way and can be accessed through `this` * Custom properties added to component instances in any way and can be accessed through `this`
@ -99,6 +102,10 @@ type MixinToOptionTypes<T> =
infer Defaults, infer Defaults,
any, any,
any, any,
any,
any,
any,
any,
any any
> >
? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> & ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
@ -157,6 +164,9 @@ export type CreateComponentPublicInstance<
MakeDefaultsOptional extends boolean = false, MakeDefaultsOptional extends boolean = false,
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
S extends SlotsType = {}, S extends SlotsType = {},
LC extends Record<string, Component> = {},
Directives extends Record<string, Directive> = {},
Exposed extends string = string,
PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>, PublicMixin = IntersectionMixin<Mixin> & IntersectionMixin<Extends>,
PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>, PublicP = UnwrapMixinsType<PublicMixin, 'P'> & EnsureNonVoid<P>,
PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>, PublicB = UnwrapMixinsType<PublicMixin, 'B'> & EnsureNonVoid<B>,
@ -167,6 +177,7 @@ export type CreateComponentPublicInstance<
EnsureNonVoid<M>, EnsureNonVoid<M>,
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> & PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
EnsureNonVoid<Defaults>, EnsureNonVoid<Defaults>,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
> = ComponentPublicInstance< > = ComponentPublicInstance<
PublicP, PublicP,
PublicB, PublicB,
@ -190,11 +201,22 @@ export type CreateComponentPublicInstance<
Defaults, Defaults,
{}, {},
string, string,
S S,
LC,
Directives,
Exposed,
Provide
>, >,
I, I,
S S,
Exposed
> >
export type ExposedKeys<
T,
Exposed extends string & keyof T,
> = '' extends Exposed ? T : Pick<T, Exposed>
// public properties exposed on the proxy, which is used as the render context // public properties exposed on the proxy, which is used as the render context
// in templates (as `this` in the render option) // in templates (as `this` in the render option)
export type ComponentPublicInstance< export type ComponentPublicInstance<
@ -210,6 +232,7 @@ export type ComponentPublicInstance<
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>, Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>,
I extends ComponentInjectOptions = {}, I extends ComponentInjectOptions = {},
S extends SlotsType = {}, S extends SlotsType = {},
Exposed extends string = '',
> = { > = {
$: ComponentInternalInstance $: ComponentInternalInstance
$data: D $data: D
@ -233,13 +256,16 @@ export type ComponentPublicInstance<
: (...args: any) => any, : (...args: any) => any,
options?: WatchOptions, options?: WatchOptions,
): WatchStopHandle ): WatchStopHandle
} & IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> & } & ExposedKeys<
IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
ShallowUnwrapRef<B> & ShallowUnwrapRef<B> &
UnwrapNestedRefs<D> & UnwrapNestedRefs<D> &
ExtractComputedReturns<C> & ExtractComputedReturns<C> &
M & M &
ComponentCustomProperties & ComponentCustomProperties &
InjectToObject<I> InjectToObject<I>,
Exposed
>
export type PublicPropertiesMap = Record< export type PublicPropertiesMap = Record<
string, string,

View File

@ -26,19 +26,29 @@ import { mapCompatDirectiveHook } from './compat/customDirective'
import { pauseTracking, resetTracking } from '@vue/reactivity' import { pauseTracking, resetTracking } from '@vue/reactivity'
import { traverse } from './apiWatch' import { traverse } from './apiWatch'
export interface DirectiveBinding<V = any> { export interface DirectiveBinding<
Value = any,
Modifiers extends string = string,
Arg extends string = string,
> {
instance: ComponentPublicInstance | null instance: ComponentPublicInstance | null
value: V value: Value
oldValue: V | null oldValue: Value | null
arg?: string arg?: Arg
modifiers: DirectiveModifiers modifiers: DirectiveModifiers<Modifiers>
dir: ObjectDirective<any, V> dir: ObjectDirective<any, Value>
} }
export type DirectiveHook<T = any, Prev = VNode<any, T> | null, V = any> = ( export type DirectiveHook<
el: T, HostElement = any,
binding: DirectiveBinding<V>, Prev = VNode<any, HostElement> | null,
vnode: VNode<any, T>, Value = any,
Modifiers extends string = string,
Arg extends string = string,
> = (
el: HostElement,
binding: DirectiveBinding<Value, Modifiers, Arg>,
vnode: VNode<any, HostElement>,
prevVNode: Prev, prevVNode: Prev,
) => void ) => void
@ -47,25 +57,52 @@ export type SSRDirectiveHook = (
vnode: VNode, vnode: VNode,
) => Data | undefined ) => Data | undefined
export interface ObjectDirective<T = any, V = any> { export interface ObjectDirective<
created?: DirectiveHook<T, null, V> HostElement = any,
beforeMount?: DirectiveHook<T, null, V> Value = any,
mounted?: DirectiveHook<T, null, V> Modifiers extends string = string,
beforeUpdate?: DirectiveHook<T, VNode<any, T>, V> Arg extends string = string,
updated?: DirectiveHook<T, VNode<any, T>, V> > {
beforeUnmount?: DirectiveHook<T, null, V> created?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
unmounted?: DirectiveHook<T, null, V> beforeMount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
mounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
beforeUpdate?: DirectiveHook<
HostElement,
VNode<any, HostElement>,
Value,
Modifiers,
Arg
>
updated?: DirectiveHook<
HostElement,
VNode<any, HostElement>,
Value,
Modifiers,
Arg
>
beforeUnmount?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
unmounted?: DirectiveHook<HostElement, null, Value, Modifiers, Arg>
getSSRProps?: SSRDirectiveHook getSSRProps?: SSRDirectiveHook
deep?: boolean deep?: boolean
} }
export type FunctionDirective<T = any, V = any> = DirectiveHook<T, any, V> export type FunctionDirective<
HostElement = any,
V = any,
Modifiers extends string = string,
Arg extends string = string,
> = DirectiveHook<HostElement, any, V, Modifiers, Arg>
export type Directive<T = any, V = any> = export type Directive<
| ObjectDirective<T, V> HostElement = any,
| FunctionDirective<T, V> Value = any,
Modifiers extends string = string,
Arg extends string = string,
> =
| ObjectDirective<HostElement, Value, Modifiers, Arg>
| FunctionDirective<HostElement, Value, Modifiers, Arg>
export type DirectiveModifiers = Record<string, boolean> export type DirectiveModifiers<K extends string = string> = Record<K, boolean>
export function validateDirectiveName(name: string) { export function validateDirectiveName(name: string) {
if (isBuiltInDirective(name)) { if (isBuiltInDirective(name)) {

View File

@ -248,6 +248,8 @@ export type {
SetupContext, SetupContext,
ComponentCustomProps, ComponentCustomProps,
AllowedComponentProps, AllowedComponentProps,
GlobalComponents,
GlobalDirectives,
ComponentInstance, ComponentInstance,
} from './component' } from './component'
export type { export type {

View File

@ -0,0 +1,11 @@
// Note: this file is auto concatenated to the end of the bundled d.ts during
// build.
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Teleport: DefineComponent<TeleportProps>
Suspense: DefineComponent<SuspenseProps>
KeepAlive: DefineComponent<KeepAliveProps>
BaseTransition: DefineComponent<BaseTransitionProps>
}
}

View File

@ -43,7 +43,7 @@ describe('runtime-dom: v-on directive', () => {
}) })
test('it should support key modifiers and system modifiers', () => { test('it should support key modifiers and system modifiers', () => {
const keyNames = ['ctrl', 'shift', 'meta', 'alt'] const keyNames = ['ctrl', 'shift', 'meta', 'alt'] as const
keyNames.forEach(keyName => { keyNames.forEach(keyName => {
const el = document.createElement('div') const el = document.createElement('div')

View File

@ -39,14 +39,17 @@ function onCompositionEnd(e: Event) {
const assignKey = Symbol('_assign') const assignKey = Symbol('_assign')
type ModelDirective<T> = ObjectDirective< type ModelDirective<T, Modifiers extends string = string> = ObjectDirective<
T & { [assignKey]: AssignerFn; _assigning?: boolean } T & { [assignKey]: AssignerFn; _assigning?: boolean },
any,
Modifiers
> >
// We are exporting the v-model runtime directly as vnode hooks so that it can // We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used. // be tree-shaken in case v-model is never used.
export const vModelText: ModelDirective< export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement HTMLInputElement | HTMLTextAreaElement,
'trim' | 'number' | 'lazy'
> = { > = {
created(el, { modifiers: { lazy, trim, number } }, vnode) { created(el, { modifiers: { lazy, trim, number } }, vnode) {
el[assignKey] = getModelAssigner(vnode) el[assignKey] = getModelAssigner(vnode)
@ -183,7 +186,7 @@ export const vModelRadio: ModelDirective<HTMLInputElement> = {
}, },
} }
export const vModelSelect: ModelDirective<HTMLSelectElement> = { export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = {
// <select multiple> value need to be deep traversed // <select multiple> value need to be deep traversed
deep: true, deep: true,
created(el, { value, modifiers: { number } }, vnode) { created(el, { value, modifiers: { number } }, vnode) {
@ -363,3 +366,10 @@ export function initVModelForSSR() {
} }
} }
} }
export type VModelDirective =
| typeof vModelText
| typeof vModelCheckbox
| typeof vModelSelect
| typeof vModelRadio
| typeof vModelDynamic

View File

@ -1,33 +1,40 @@
import { import {
type ComponentInternalInstance, type ComponentInternalInstance,
DeprecationTypes, DeprecationTypes,
type Directive,
type LegacyConfig, type LegacyConfig,
compatUtils, compatUtils,
getCurrentInstance, getCurrentInstance,
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { hyphenate, isArray } from '@vue/shared' import { hyphenate, isArray } from '@vue/shared'
const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] const systemModifiers = ['ctrl', 'shift', 'alt', 'meta'] as const
type SystemModifiers = (typeof systemModifiers)[number]
type CompatModifiers = keyof typeof keyNames
export type VOnModifiers = SystemModifiers | ModifierGuards | CompatModifiers
type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent type KeyedEvent = KeyboardEvent | MouseEvent | TouchEvent
const modifierGuards: Record< const modifierGuards = {
string, stop: (e: Event) => e.stopPropagation(),
(e: Event, modifiers: string[]) => void | boolean prevent: (e: Event) => e.preventDefault(),
> = { self: (e: Event) => e.target !== e.currentTarget,
stop: e => e.stopPropagation(), ctrl: (e: Event) => !(e as KeyedEvent).ctrlKey,
prevent: e => e.preventDefault(), shift: (e: Event) => !(e as KeyedEvent).shiftKey,
self: e => e.target !== e.currentTarget, alt: (e: Event) => !(e as KeyedEvent).altKey,
ctrl: e => !(e as KeyedEvent).ctrlKey, meta: (e: Event) => !(e as KeyedEvent).metaKey,
shift: e => !(e as KeyedEvent).shiftKey, left: (e: Event) => 'button' in e && (e as MouseEvent).button !== 0,
alt: e => !(e as KeyedEvent).altKey, middle: (e: Event) => 'button' in e && (e as MouseEvent).button !== 1,
meta: e => !(e as KeyedEvent).metaKey, right: (e: Event) => 'button' in e && (e as MouseEvent).button !== 2,
left: e => 'button' in e && (e as MouseEvent).button !== 0,
middle: e => 'button' in e && (e as MouseEvent).button !== 1,
right: e => 'button' in e && (e as MouseEvent).button !== 2,
exact: (e, modifiers) => exact: (e, modifiers) =>
systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)), systemModifiers.some(m => (e as any)[`${m}Key`] && !modifiers.includes(m)),
} } satisfies Record<
string,
| ((e: Event) => void | boolean)
| ((e: Event, modifiers: string[]) => void | boolean)
>
type ModifierGuards = keyof typeof modifierGuards
/** /**
* @private * @private
@ -36,7 +43,7 @@ export const withModifiers = <
T extends (event: Event, ...args: unknown[]) => any, T extends (event: Event, ...args: unknown[]) => any,
>( >(
fn: T & { _withMods?: { [key: string]: T } }, fn: T & { _withMods?: { [key: string]: T } },
modifiers: string[], modifiers: VOnModifiers[],
) => { ) => {
const cache = fn._withMods || (fn._withMods = {}) const cache = fn._withMods || (fn._withMods = {})
const cacheKey = modifiers.join('.') const cacheKey = modifiers.join('.')
@ -44,7 +51,7 @@ export const withModifiers = <
cache[cacheKey] || cache[cacheKey] ||
(cache[cacheKey] = ((event, ...args) => { (cache[cacheKey] = ((event, ...args) => {
for (let i = 0; i < modifiers.length; i++) { for (let i = 0; i < modifiers.length; i++) {
const guard = modifierGuards[modifiers[i]] const guard = modifierGuards[modifiers[i] as ModifierGuards]
if (guard && guard(event, modifiers)) return if (guard && guard(event, modifiers)) return
} }
return fn(event, ...args) return fn(event, ...args)
@ -54,7 +61,7 @@ export const withModifiers = <
// Kept for 2.x compat. // Kept for 2.x compat.
// Note: IE11 compat for `spacebar` and `del` is removed for now. // Note: IE11 compat for `spacebar` and `del` is removed for now.
const keyNames: Record<string, string | string[]> = { const keyNames = {
esc: 'escape', esc: 'escape',
space: ' ', space: ' ',
up: 'arrow-up', up: 'arrow-up',
@ -62,7 +69,7 @@ const keyNames: Record<string, string | string[]> = {
right: 'arrow-right', right: 'arrow-right',
down: 'arrow-down', down: 'arrow-down',
delete: 'backspace', delete: 'backspace',
} } satisfies Record<string, string | string[]>
/** /**
* @private * @private
@ -101,7 +108,13 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
} }
const eventKey = hyphenate(event.key) const eventKey = hyphenate(event.key)
if (modifiers.some(k => k === eventKey || keyNames[k] === eventKey)) { if (
modifiers.some(
k =>
k === eventKey ||
keyNames[k as unknown as CompatModifiers] === eventKey,
)
) {
return fn(event) return fn(event)
} }
@ -133,3 +146,5 @@ export const withKeys = <T extends (event: KeyboardEvent) => any>(
}) as T) }) as T)
) )
} }
export type VOnDirective = Directive<any, any, VOnModifiers>

View File

@ -1,7 +1,9 @@
import { import {
type App, type App,
type CreateAppFunction, type CreateAppFunction,
type DefineComponent,
DeprecationTypes, DeprecationTypes,
type Directive,
type ElementNamespace, type ElementNamespace,
type HydrationRenderer, type HydrationRenderer,
type Renderer, type Renderer,
@ -25,6 +27,11 @@ import {
isSVGTag, isSVGTag,
isString, isString,
} from '@vue/shared' } from '@vue/shared'
import type { TransitionProps } from './components/Transition'
import type { TransitionGroupProps } from './components/TransitionGroup'
import type { vShow } from './directives/vShow'
import type { VOnDirective } from './directives/vOn'
import type { VModelDirective } from './directives/vModel'
declare module '@vue/reactivity' { declare module '@vue/reactivity' {
export interface RefUnwrapBailTypes { export interface RefUnwrapBailTypes {
@ -32,6 +39,22 @@ declare module '@vue/reactivity' {
} }
} }
declare module '@vue/runtime-core' {
interface GlobalComponents {
Transition: DefineComponent<TransitionProps>
TransitionGroup: DefineComponent<TransitionGroupProps>
}
interface GlobalDirectives {
vShow: typeof vShow
vOn: VOnDirective
vBind: VModelDirective
vIf: Directive<any, boolean>
VOnce: Directive
VSlot: Directive
}
}
const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps) const rendererOptions = /*#__PURE__*/ extend({ patchProp }, nodeOps)
// lazy create the renderer - this makes core renderer logic tree-shakable // lazy create the renderer - this makes core renderer logic tree-shakable

View File

@ -292,7 +292,7 @@ describe('INSTANCE_SCOPED_SLOTS', () => {
components: { components: {
child: { child: {
compatConfig: { RENDER_FUNCTION: false }, compatConfig: { RENDER_FUNCTION: false },
render() { render(this: LegacyPublicInstance) {
normalSlots = this.$slots normalSlots = this.$slots
scopedSlots = this.$scopedSlots scopedSlots = this.$scopedSlots
}, },