diff --git a/packages/dts-test/defineComponent.test-d.tsx b/packages/dts-test/defineComponent.test-d.tsx
index 44a00d4e586..077f1abc075 100644
--- a/packages/dts-test/defineComponent.test-d.tsx
+++ b/packages/dts-test/defineComponent.test-d.tsx
@@ -15,7 +15,7 @@ import {
withKeys,
withModifiers,
} from 'vue'
-import { type IsUnion, describe, expectType } from './utils'
+import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => {
interface ExpectedProps {
@@ -1623,3 +1623,146 @@ declare const MyButton: DefineComponent<
{}
>
;
+
+describe('__typeProps backdoor for union type for conditional props', () => {
+ interface CommonProps {
+ size?: 'xl' | 'l' | 'm' | 's' | 'xs'
+ }
+
+ type ConditionalProps =
+ | {
+ color?: 'normal' | 'primary' | 'secondary'
+ appearance?: 'normal' | 'outline' | 'text'
+ }
+ | {
+ color: 'white'
+ appearance: 'outline'
+ }
+
+ type Props = CommonProps & ConditionalProps
+
+ const Comp = defineComponent({
+ __typeProps: {} as Props,
+ })
+ // @ts-expect-error
+ ;
+ // @ts-expect-error
+ ;
+ ;
+
+ const c = new Comp()
+
+ // @ts-expect-error
+ c.$props = { color: 'white' }
+ // @ts-expect-error
+ c.$props = { color: 'white', appearance: 'text' }
+ c.$props = { color: 'white', appearance: 'outline' }
+})
+
+describe('__typeEmits backdoor, 3.3+ object syntax', () => {
+ type Emits = {
+ change: [id: number]
+ update: [value: string]
+ }
+
+ const Comp = defineComponent({
+ __typeEmits: {} as Emits,
+ mounted() {
+ this.$props.onChange?.(123)
+ // @ts-expect-error
+ this.$props.onChange?.('123')
+ this.$props.onUpdate?.('foo')
+ // @ts-expect-error
+ this.$props.onUpdate?.(123)
+
+ // @ts-expect-error
+ this.$emit('foo')
+
+ this.$emit('change', 123)
+ // @ts-expect-error
+ this.$emit('change', '123')
+
+ this.$emit('update', 'test')
+ // @ts-expect-error
+ this.$emit('update', 123)
+ },
+ })
+
+ ; id.toFixed(2)} />
+ ; id.toUpperCase()} />
+ // @ts-expect-error
+ ; id.slice(1)} />
+ // @ts-expect-error
+ ; id.toFixed(2)} />
+
+ const c = new Comp()
+ // @ts-expect-error
+ c.$emit('foo')
+
+ c.$emit('change', 123)
+ // @ts-expect-error
+ c.$emit('change', '123')
+
+ c.$emit('update', 'test')
+ // @ts-expect-error
+ c.$emit('update', 123)
+})
+
+describe('__typeEmits backdoor, call signature syntax', () => {
+ type Emits = {
+ (e: 'change', id: number): void
+ (e: 'update', value: string): void
+ }
+
+ const Comp = defineComponent({
+ __typeEmits: {} as Emits,
+ mounted() {
+ this.$props.onChange?.(123)
+ // @ts-expect-error
+ this.$props.onChange?.('123')
+ this.$props.onUpdate?.('foo')
+ // @ts-expect-error
+ this.$props.onUpdate?.(123)
+
+ // @ts-expect-error
+ this.$emit('foo')
+
+ this.$emit('change', 123)
+ // @ts-expect-error
+ this.$emit('change', '123')
+
+ this.$emit('update', 'test')
+ // @ts-expect-error
+ this.$emit('update', 123)
+ },
+ })
+
+ ; id.toFixed(2)} />
+ ; id.toUpperCase()} />
+ // @ts-expect-error
+ ; id.slice(1)} />
+ // @ts-expect-error
+ ; id.toFixed(2)} />
+
+ const c = new Comp()
+ // @ts-expect-error
+ c.$emit('foo')
+
+ c.$emit('change', 123)
+ // @ts-expect-error
+ c.$emit('change', '123')
+
+ c.$emit('update', 'test')
+ // @ts-expect-error
+ c.$emit('update', 123)
+})
+
+defineComponent({
+ props: {
+ foo: [String, null],
+ },
+ setup(props) {
+ expectType>(false)
+ expectType(props.foo)
+ },
+})
diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts
index 46e1d59a8e7..7fce96586da 100644
--- a/packages/runtime-core/src/apiDefineComponent.ts
+++ b/packages/runtime-core/src/apiDefineComponent.ts
@@ -3,9 +3,6 @@ import type {
ComponentOptions,
ComponentOptionsBase,
ComponentOptionsMixin,
- ComponentOptionsWithArrayProps,
- ComponentOptionsWithObjectProps,
- ComponentOptionsWithoutProps,
ComponentProvideOptions,
ComputedOptions,
MethodOptions,
@@ -25,7 +22,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
-import type { EmitsOptions, EmitsToProps } from './componentEmits'
+import type {
+ EmitsOptions,
+ EmitsToProps,
+ TypeEmitsToOptions,
+} from './componentEmits'
import { extend, isFunction } from '@vue/shared'
import type { VNodeProps } from './vnode'
import type {
@@ -34,6 +35,7 @@ import type {
} from './componentPublicInstance'
import type { SlotsType } from './componentSlots'
import type { Directive } from './directives'
+import type { ComponentTypeEmits } from './apiSetupHelpers'
export type PublicProps = VNodeProps &
AllowedComponentProps &
@@ -64,6 +66,7 @@ export type DefineComponent<
Directives extends Record = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ MakeDefaultsOptional extends boolean = true,
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
@@ -76,7 +79,7 @@ export type DefineComponent<
E,
PP & Props,
Defaults,
- true,
+ MakeDefaultsOptional,
{},
S,
LC & GlobalComponents,
@@ -169,183 +172,114 @@ export function defineComponent<
},
): DefineSetupFnComponent
-// overload 2: object format with no props
-// (uses user defined props interface)
-// return type is for Vetur and TSX support
-export function defineComponent<
- Props = {},
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = {},
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
- LC extends Record = {},
- Directives extends Record = {},
- Exposed extends string = string,
- Provide extends ComponentProvideOptions = ComponentProvideOptions,
->(
- options: ComponentOptionsWithoutProps<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- I,
- II,
- S,
- LC,
- Directives,
- Exposed,
- Provide
- >,
-): DefineComponent<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- PublicProps,
- ResolveProps,
- ExtractDefaultPropTypes,
- S,
- LC,
- Directives,
- Exposed,
- Provide
->
-
-// overload 3: object format with array props declaration
-// props inferred as { [key in PropNames]?: any }
-// return type is for Vetur and TSX support
-export function defineComponent<
- PropNames extends string,
- RawBindings,
- D,
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = {},
- EE extends string = string,
- S extends SlotsType = {},
- I extends ComponentInjectOptions = {},
- II extends string = string,
- LC extends Record = {},
- Directives extends Record = {},
- Exposed extends string = string,
- Provide extends ComponentProvideOptions = ComponentProvideOptions,
- Props = Readonly<{ [key in PropNames]?: any }>,
->(
- options: ComponentOptionsWithArrayProps<
- PropNames,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- I,
- II,
- S,
- LC,
- Directives,
- Exposed,
- Provide
- >,
-): DefineComponent<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- PublicProps,
- ResolveProps,
- ExtractDefaultPropTypes,
- S,
- LC,
- Directives,
- Exposed,
- Provide
->
-
-// overload 4: object format with object props declaration
-// see `ExtractPropTypes` in ./componentProps.ts
+// overload 2: defineComponent with options object, infer props from options
export function defineComponent<
- // the Readonly constraint allows TS to treat the type of { required: true }
- // as constant instead of boolean.
- PropsOptions extends Readonly,
- RawBindings,
- D,
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
+ // props
+ TypeProps,
+ RuntimePropsOptions extends
+ ComponentObjectPropsOptions = ComponentObjectPropsOptions,
+ RuntimePropsKeys extends string = string,
+ // emits
+ TypeEmits extends ComponentTypeEmits = {},
+ RuntimeEmitsOptions extends EmitsOptions = {},
+ RuntimeEmitsKeys extends string = string,
+ // other options
+ Data = {},
+ SetupBindings = {},
+ Computed extends ComputedOptions = {},
+ Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = {},
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
- LC extends Record = {},
+ InjectOptions extends ComponentInjectOptions = {},
+ InjectKeys extends string = string,
+ Slots extends SlotsType = {},
+ LocalComponents extends Record = {},
Directives extends Record = {},
Exposed extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ // resolved types
+ ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
+ ? TypeEmitsToOptions
+ : RuntimeEmitsOptions,
+ InferredProps = unknown extends TypeProps
+ ? string extends RuntimePropsKeys
+ ? ComponentObjectPropsOptions extends RuntimePropsOptions
+ ? {}
+ : ExtractPropTypes
+ : { [key in RuntimePropsKeys]?: any }
+ : TypeProps,
+ ResolvedProps = Readonly>,
>(
- options: ComponentOptionsWithObjectProps<
- PropsOptions,
- RawBindings,
- D,
- C,
- M,
+ options: {
+ props?: (RuntimePropsOptions & ThisType) | RuntimePropsKeys[]
+ /**
+ * @private for language-tools use only
+ */
+ __typeProps?: TypeProps
+ /**
+ * @private for language-tools use only
+ */
+ __typeEmits?: TypeEmits
+ } & ComponentOptionsBase<
+ ResolvedProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
Mixin,
Extends,
- E,
- EE,
- I,
- II,
- S,
- LC,
+ RuntimeEmitsOptions,
+ RuntimeEmitsKeys,
+ {}, // Defaults
+ InjectOptions,
+ InjectKeys,
+ Slots,
+ LocalComponents,
Directives,
Exposed,
Provide
- >,
+ > &
+ ThisType<
+ CreateComponentPublicInstance<
+ ResolvedProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
+ Mixin,
+ Extends,
+ ResolvedEmits,
+ RuntimeEmitsKeys,
+ {},
+ false,
+ InjectOptions,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed
+ >
+ >,
): DefineComponent<
- PropsOptions,
- RawBindings,
- D,
- C,
- M,
+ InferredProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
Mixin,
Extends,
- E,
- EE,
+ ResolvedEmits,
+ RuntimeEmitsKeys,
PublicProps,
- ResolveProps,
- ExtractDefaultPropTypes,
- S,
- LC,
+ ResolvedProps,
+ ExtractDefaultPropTypes,
+ Slots,
+ LocalComponents,
Directives,
Exposed,
- Provide
+ Provide,
+ // MakeDefaultsOptional - if TypeProps is provided, set to false to use
+ // user props types verbatim
+ unknown extends TypeProps ? true : false
>
// implementation, close to no-op
diff --git a/packages/runtime-core/src/apiSetupHelpers.ts b/packages/runtime-core/src/apiSetupHelpers.ts
index 382bb30b365..dbe27dde48e 100644
--- a/packages/runtime-core/src/apiSetupHelpers.ts
+++ b/packages/runtime-core/src/apiSetupHelpers.ts
@@ -16,8 +16,8 @@ import {
} from './component'
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
import type {
+ ComponentOptionsBase,
ComponentOptionsMixin,
- ComponentOptionsWithoutProps,
ComputedOptions,
MethodOptions,
} from './componentOptions'
@@ -135,9 +135,11 @@ export function defineEmits(
export function defineEmits(
emitOptions: E,
): EmitFn
-export function defineEmits<
- T extends ((...args: any[]) => any) | Record,
->(): T extends (...args: any[]) => any ? T : ShortEmits
+export function defineEmits(): T extends (
+ ...args: any[]
+) => any
+ ? T
+ : ShortEmits
// implementation
export function defineEmits() {
if (__DEV__) {
@@ -146,6 +148,10 @@ export function defineEmits() {
return null as any
}
+export type ComponentTypeEmits =
+ | ((...args: any[]) => any)
+ | Record
+
type RecordToUnion> = T[keyof T]
type ShortEmits> = UnionToIntersection<
@@ -191,15 +197,33 @@ export function defineOptions<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
>(
- options?: ComponentOptionsWithoutProps<
+ options?: ComponentOptionsBase<
{},
RawBindings,
D,
C,
M,
Mixin,
- Extends
- > & { emits?: undefined; expose?: undefined; slots?: undefined },
+ Extends,
+ {}
+ > & {
+ /**
+ * props should be defined via defineProps().
+ */
+ props: never
+ /**
+ * emits should be defined via defineEmits().
+ */
+ emits?: never
+ /**
+ * expose should be defined via defineExpose().
+ */
+ expose?: never
+ /**
+ * slots should be defined via defineSlots().
+ */
+ slots?: never
+ },
): void {
if (__DEV__) {
warnRuntimeUsage(`defineOptions`)
diff --git a/packages/runtime-core/src/componentEmits.ts b/packages/runtime-core/src/componentEmits.ts
index 4551235bc5a..eb80d02afb6 100644
--- a/packages/runtime-core/src/componentEmits.ts
+++ b/packages/runtime-core/src/componentEmits.ts
@@ -1,5 +1,6 @@
import {
EMPTY_OBJ,
+ type OverloadParameters,
type UnionToIntersection,
camelize,
extend,
@@ -28,6 +29,7 @@ import {
compatModelEmit,
compatModelEventPrefix,
} from './compat/componentVModel'
+import type { ComponentTypeEmits } from './apiSetupHelpers'
export type ObjectEmitsOptions = Record<
string,
@@ -36,23 +38,41 @@ export type ObjectEmitsOptions = Record<
export type EmitsOptions = ObjectEmitsOptions | string[]
-export type EmitsToProps = T extends string[]
- ? {
- [K in `on${Capitalize}`]?: (...args: any[]) => any
- }
- : T extends ObjectEmitsOptions
+export type EmitsToProps =
+ T extends string[]
? {
- [K in `on${Capitalize}`]?: K extends `on${infer C}`
- ? (
- ...args: T[Uncapitalize] extends (...args: infer P) => any
- ? P
- : T[Uncapitalize] extends null
- ? any[]
- : never
- ) => any
- : never
+ [K in `on${Capitalize}`]?: (...args: any[]) => any
}
- : {}
+ : T extends ObjectEmitsOptions
+ ? {
+ [K in `on${Capitalize}`]?: K extends `on${infer C}`
+ ? (
+ ...args: T[Uncapitalize] extends (...args: infer P) => any
+ ? P
+ : T[Uncapitalize] extends null
+ ? any[]
+ : never
+ ) => any
+ : never
+ }
+ : {}
+
+export type TypeEmitsToOptions =
+ T extends Record
+ ? {
+ [K in keyof T]: T[K] extends [...args: infer Args]
+ ? (...args: Args) => any
+ : () => any
+ }
+ : T extends (...args: any[]) => any
+ ? ParametersToFns>
+ : {}
+
+type ParametersToFns = {
+ [K in T[0]]: K extends `${infer C}`
+ ? (...args: T extends [C, ...infer Args] ? Args : never) => any
+ : never
+}
export type ShortEmitsToObject =
E extends Record
diff --git a/packages/runtime-core/src/componentOptions.ts b/packages/runtime-core/src/componentOptions.ts
index da1dfcec0cc..ac1841edee9 100644
--- a/packages/runtime-core/src/componentOptions.ts
+++ b/packages/runtime-core/src/componentOptions.ts
@@ -54,7 +54,11 @@ import type {
ExtractDefaultPropTypes,
ExtractPropTypes,
} from './componentProps'
-import type { EmitsOptions, EmitsToProps } from './componentEmits'
+import type {
+ EmitsOptions,
+ EmitsToProps,
+ TypeEmitsToOptions,
+} from './componentEmits'
import type { Directive } from './directives'
import {
type ComponentPublicInstance,
@@ -76,7 +80,10 @@ import {
import type { OptionMergeFunction } from './apiCreateApp'
import { LifecycleHooks } from './enums'
import type { SlotsType } from './componentSlots'
-import { normalizePropsOrEmits } from './apiSetupHelpers'
+import {
+ type ComponentTypeEmits,
+ normalizePropsOrEmits,
+} from './apiSetupHelpers'
/**
* Interface for declaring custom options.
@@ -218,183 +225,6 @@ export interface RuntimeCompilerOptions {
delimiters?: [string, string]
}
-export type ComponentOptionsWithoutProps<
- Props = {},
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = EmitsOptions,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
- LC extends Record = {},
- Directives extends Record = {},
- Exposed extends string = string,
- Provide extends ComponentProvideOptions = ComponentProvideOptions,
- PE = Props & EmitsToProps,
-> = ComponentOptionsBase<
- PE,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- {},
- I,
- II,
- S,
- LC,
- Directives,
- Exposed,
- Provide
-> & {
- props?: undefined
-} & ThisType<
- CreateComponentPublicInstance<
- PE,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- PE,
- {},
- false,
- I,
- S,
- LC,
- Directives,
- Exposed
- >
- >
-
-export type ComponentOptionsWithArrayProps<
- PropNames extends string = string,
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = EmitsOptions,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
- LC extends Record = {},
- Directives extends Record = {},
- Exposed extends string = string,
- Provide extends ComponentProvideOptions = ComponentProvideOptions,
- Props = Prettify>>,
-> = ComponentOptionsBase<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- {},
- I,
- II,
- S,
- LC,
- Directives,
- Exposed,
- Provide
-> & {
- props: PropNames[]
-} & ThisType<
- CreateComponentPublicInstance<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- Props,
- {},
- false,
- I,
- S,
- LC,
- Directives,
- Exposed
- >
- >
-
-export type ComponentOptionsWithObjectProps<
- PropsOptions = ComponentObjectPropsOptions,
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = EmitsOptions,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
- LC extends Record = {},
- Directives extends Record = {},
- Exposed extends string = string,
- Provide extends ComponentProvideOptions = ComponentProvideOptions,
- Props = Prettify & EmitsToProps>>,
- Defaults = ExtractDefaultPropTypes,
-> = ComponentOptionsBase<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- Defaults,
- I,
- II,
- S,
- LC,
- Directives,
- Exposed,
- Provide
-> & {
- props: PropsOptions & ThisType
-} & ThisType<
- CreateComponentPublicInstance<
- Props,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- Props,
- Defaults,
- false,
- I,
- S,
- LC,
- Directives
- >
- >
-
export type ComponentOptions<
Props = {},
RawBindings = any,
@@ -1238,3 +1068,203 @@ function mergeWatchOptions(
}
return merged
}
+
+// Deprecated legacy types, kept because they were previously exported ---------
+
+/**
+ * @deprecated
+ */
+export type ComponentOptionsWithoutProps<
+ Props = {},
+ RawBindings = {},
+ D = {},
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+ Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+ E extends EmitsOptions = {},
+ EE extends string = string,
+ I extends ComponentInjectOptions = {},
+ II extends string = string,
+ S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ TE extends ComponentTypeEmits = {},
+ ResolvedEmits extends EmitsOptions = {} extends E
+ ? TypeEmitsToOptions
+ : E,
+ PE = Props & EmitsToProps,
+> = ComponentOptionsBase<
+ PE,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ EE,
+ {},
+ I,
+ II,
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
+> & {
+ props?: never
+ /**
+ * @private for language-tools use only
+ */
+ __typeProps?: Props
+ /**
+ * @private for language-tools use only
+ */
+ __typeEmits?: TE
+} & ThisType<
+ CreateComponentPublicInstance<
+ PE,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ ResolvedEmits,
+ EE,
+ {},
+ false,
+ I,
+ S,
+ LC,
+ Directives,
+ Exposed
+ >
+ >
+
+/**
+ * @deprecated
+ */
+export type ComponentOptionsWithArrayProps<
+ PropNames extends string = string,
+ RawBindings = {},
+ D = {},
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+ Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+ E extends EmitsOptions = EmitsOptions,
+ EE extends string = string,
+ I extends ComponentInjectOptions = {},
+ II extends string = string,
+ S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ Props = Prettify>>,
+> = ComponentOptionsBase<
+ Props,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ EE,
+ {},
+ I,
+ II,
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
+> & {
+ props: PropNames[]
+} & ThisType<
+ CreateComponentPublicInstance<
+ Props,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ Props,
+ {},
+ false,
+ I,
+ S,
+ LC,
+ Directives,
+ Exposed
+ >
+ >
+
+/**
+ * @deprecated
+ */
+export type ComponentOptionsWithObjectProps<
+ PropsOptions = ComponentObjectPropsOptions,
+ RawBindings = {},
+ D = {},
+ C extends ComputedOptions = {},
+ M extends MethodOptions = {},
+ Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
+ Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
+ E extends EmitsOptions = EmitsOptions,
+ EE extends string = string,
+ I extends ComponentInjectOptions = {},
+ II extends string = string,
+ S extends SlotsType = {},
+ LC extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ Props = Prettify & EmitsToProps>>,
+ Defaults = ExtractDefaultPropTypes,
+> = ComponentOptionsBase<
+ Props,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ EE,
+ Defaults,
+ I,
+ II,
+ S,
+ LC,
+ Directives,
+ Exposed,
+ Provide
+> & {
+ props: PropsOptions & ThisType
+} & ThisType<
+ CreateComponentPublicInstance<
+ Props,
+ RawBindings,
+ D,
+ C,
+ M,
+ Mixin,
+ Extends,
+ E,
+ Props,
+ Defaults,
+ false,
+ I,
+ S,
+ LC,
+ Directives
+ >
+ >
diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts
index 5a4292b6f36..597a1f1f285 100644
--- a/packages/runtime-core/src/componentProps.ts
+++ b/packages/runtime-core/src/componentProps.ts
@@ -67,7 +67,7 @@ export interface PropOptions {
skipFactory?: boolean
}
-export type PropType = PropConstructor | PropConstructor[]
+export type PropType = PropConstructor | (PropConstructor | null)[]
type PropConstructor =
| { new (...args: any[]): T & {} }
@@ -107,8 +107,10 @@ type DefaultKeys = {
: never
}[keyof T]
-type InferPropType = [T] extends [null]
- ? any // null & true would fail to infer
+type InferPropType = [T] extends [null]
+ ? NullAsAny extends true
+ ? any
+ : null
: [T] extends [{ type: null | true }]
? any // As TS issue https://github.com/Microsoft/TypeScript/issues/14829 // somehow `ObjectConstructor` when inferred from { (): T } becomes `any` // `BooleanConstructor` when inferred from PropConstructor(with PropMethod) becomes `Boolean`
: [T] extends [ObjectConstructor | { type: ObjectConstructor }]
@@ -119,8 +121,8 @@ type InferPropType = [T] extends [null]
? Date
: [T] extends [(infer U)[] | { type: (infer U)[] }]
? U extends DateConstructor
- ? Date | InferPropType
- : InferPropType
+ ? Date | InferPropType
+ : InferPropType
: [T] extends [Prop]
? unknown extends V
? IfAny
@@ -594,7 +596,7 @@ function validatePropName(key: string) {
// use function string name to check type constructors
// so that it works across vms / iframes.
-function getType(ctor: Prop): string {
+function getType(ctor: Prop | null): string {
// Early return for null to avoid unnecessary computations
if (ctor === null) {
return 'null'
@@ -614,7 +616,7 @@ function getType(ctor: Prop): string {
return ''
}
-function isSameType(a: Prop, b: Prop): boolean {
+function isSameType(a: Prop | null, b: Prop | null): boolean {
return getType(a) === getType(b)
}
@@ -707,24 +709,27 @@ type AssertionResult = {
/**
* dev only
*/
-function assertType(value: unknown, type: PropConstructor): AssertionResult {
+function assertType(
+ value: unknown,
+ type: PropConstructor | null,
+): AssertionResult {
let valid
const expectedType = getType(type)
- if (isSimpleType(expectedType)) {
+ if (expectedType === 'null') {
+ valid = value === null
+ } else if (isSimpleType(expectedType)) {
const t = typeof value
valid = t === expectedType.toLowerCase()
// for primitive wrapper objects
if (!valid && t === 'object') {
- valid = value instanceof type
+ valid = value instanceof (type as PropConstructor)
}
} else if (expectedType === 'Object') {
valid = isObject(value)
} else if (expectedType === 'Array') {
valid = isArray(value)
- } else if (expectedType === 'null') {
- valid = value === null
} else {
- valid = value instanceof type
+ valid = value instanceof (type as PropConstructor)
}
return {
valid,
diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts
index 5d36407ba6a..e4a9e53f29c 100644
--- a/packages/runtime-core/src/index.ts
+++ b/packages/runtime-core/src/index.ts
@@ -76,6 +76,7 @@ export {
withDefaults,
type DefineProps,
type ModelRef,
+ type ComponentTypeEmits,
} from './apiSetupHelpers'
/**
@@ -260,9 +261,6 @@ export type {
export type {
ComponentOptions,
ComponentOptionsMixin,
- ComponentOptionsWithoutProps,
- ComponentOptionsWithObjectProps,
- ComponentOptionsWithArrayProps,
ComponentCustomOptions,
ComponentOptionsBase,
ComponentProvideOptions,
@@ -272,7 +270,11 @@ export type {
RuntimeCompilerOptions,
ComponentInjectOptions,
} from './componentOptions'
-export type { EmitsOptions, ObjectEmitsOptions } from './componentEmits'
+export type {
+ EmitsOptions,
+ ObjectEmitsOptions,
+ EmitsToProps,
+} from './componentEmits'
export type {
ComponentPublicInstance,
ComponentCustomProperties,
diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts
index fb746f72c4a..cc56de2d685 100644
--- a/packages/runtime-dom/__tests__/customElement.spec.ts
+++ b/packages/runtime-dom/__tests__/customElement.spec.ts
@@ -88,10 +88,14 @@ describe('defineCustomElement', () => {
describe('props', () => {
const E = defineCustomElement({
- props: ['foo', 'bar', 'bazQux'],
+ props: {
+ foo: [String, null],
+ bar: Object,
+ bazQux: null,
+ },
render() {
return [
- h('div', null, this.foo),
+ h('div', null, this.foo || ''),
h('div', null, this.bazQux || (this.bar && this.bar.x)),
]
},
diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts
index 01ce2bad464..01728466241 100644
--- a/packages/runtime-dom/src/apiCustomElement.ts
+++ b/packages/runtime-dom/src/apiCustomElement.ts
@@ -1,16 +1,19 @@
import {
+ type Component,
type ComponentInjectOptions,
type ComponentInternalInstance,
+ type ComponentObjectPropsOptions,
type ComponentOptions,
+ type ComponentOptionsBase,
type ComponentOptionsMixin,
- type ComponentOptionsWithArrayProps,
- type ComponentOptionsWithObjectProps,
- type ComponentOptionsWithoutProps,
- type ComponentPropsOptions,
+ type ComponentProvideOptions,
type ComputedOptions,
type ConcreteComponent,
+ type CreateComponentPublicInstance,
type DefineComponent,
+ type Directive,
type EmitsOptions,
+ type EmitsToProps,
type ExtractPropTypes,
type MethodOptions,
type RenderFunction,
@@ -41,98 +44,79 @@ export function defineCustomElement(
) => RawBindings | RenderFunction,
): VueElementConstructor
-// overload 2: object format with no props
+// overload 2: defineCustomElement with options object, infer props from options
export function defineCustomElement<
- Props = {},
- RawBindings = {},
- D = {},
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
+ // props
+ RuntimePropsOptions extends
+ ComponentObjectPropsOptions = ComponentObjectPropsOptions,
+ PropsKeys extends string = string,
+ // emits
+ RuntimeEmitsOptions extends EmitsOptions = {},
+ EmitsKeys extends string = string,
+ // other options
+ Data = {},
+ SetupBindings = {},
+ Computed extends ComputedOptions = {},
+ Methods extends MethodOptions = {},
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = EmitsOptions,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
+ InjectOptions extends ComponentInjectOptions = {},
+ InjectKeys extends string = string,
+ Slots extends SlotsType = {},
+ LocalComponents extends Record = {},
+ Directives extends Record = {},
+ Exposed extends string = string,
+ Provide extends ComponentProvideOptions = ComponentProvideOptions,
+ // resolved types
+ InferredProps = string extends PropsKeys
+ ? ComponentObjectPropsOptions extends RuntimePropsOptions
+ ? {}
+ : ExtractPropTypes
+ : { [key in PropsKeys]?: any },
+ ResolvedProps = InferredProps & EmitsToProps,
>(
- options: ComponentOptionsWithoutProps<
- Props,
- RawBindings,
- D,
- C,
- M,
+ options: {
+ props?: (RuntimePropsOptions & ThisType) | PropsKeys[]
+ } & ComponentOptionsBase<
+ ResolvedProps,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
Mixin,
Extends,
- E,
- EE,
- I,
- II,
- S
- > & { styles?: string[] },
-): VueElementConstructor
-
-// overload 3: object format with array props declaration
-export function defineCustomElement<
- PropNames extends string,
- RawBindings,
- D,
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = Record,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
->(
- options: ComponentOptionsWithArrayProps<
- PropNames,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- I,
- II,
- S
- > & { styles?: string[] },
-): VueElementConstructor<{ [K in PropNames]: any }>
-
-// overload 4: object format with object props declaration
-export function defineCustomElement<
- PropsOptions extends Readonly,
- RawBindings,
- D,
- C extends ComputedOptions = {},
- M extends MethodOptions = {},
- Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
- Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
- E extends EmitsOptions = Record,
- EE extends string = string,
- I extends ComponentInjectOptions = {},
- II extends string = string,
- S extends SlotsType = {},
->(
- options: ComponentOptionsWithObjectProps<
- PropsOptions,
- RawBindings,
- D,
- C,
- M,
- Mixin,
- Extends,
- E,
- EE,
- I,
- II,
- S
- > & { styles?: string[] },
-): VueElementConstructor>
+ RuntimeEmitsOptions,
+ EmitsKeys,
+ {}, // Defaults
+ InjectOptions,
+ InjectKeys,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed,
+ Provide
+ > &
+ ThisType<
+ CreateComponentPublicInstance<
+ Readonly,
+ SetupBindings,
+ Data,
+ Computed,
+ Methods,
+ Mixin,
+ Extends,
+ RuntimeEmitsOptions,
+ EmitsKeys,
+ {},
+ false,
+ InjectOptions,
+ Slots,
+ LocalComponents,
+ Directives,
+ Exposed
+ >
+ >,
+): VueElementConstructor
// overload 5: defining a custom element from the returned value of
// `defineComponent`
diff --git a/packages/shared/src/typeUtils.ts b/packages/shared/src/typeUtils.ts
index 63372d82916..4846751b84e 100644
--- a/packages/shared/src/typeUtils.ts
+++ b/packages/shared/src/typeUtils.ts
@@ -21,3 +21,34 @@ export type Awaited = T extends null | undefined
? Awaited // recursively unwrap the value
: never // the argument to `then` was not callable
: T // non-object or non-thenable
+
+/**
+ * Utility for extracting the parameters from a function overload (for typed emits)
+ * https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
+ */
+export type OverloadParameters any> = Parameters<
+ OverloadUnion
+>
+
+type OverloadProps = Pick
+
+type OverloadUnionRecursive<
+ TOverload,
+ TPartialOverload = unknown,
+> = TOverload extends (...args: infer TArgs) => infer TReturn
+ ? TPartialOverload extends TOverload
+ ? never
+ :
+ | OverloadUnionRecursive<
+ TPartialOverload & TOverload,
+ TPartialOverload &
+ ((...args: TArgs) => TReturn) &
+ OverloadProps
+ >
+ | ((...args: TArgs) => TReturn)
+ : never
+
+type OverloadUnion any> = Exclude<
+ OverloadUnionRecursive<(() => never) & TOverload>,
+ TOverload extends () => never ? never : () => never
+>