diff --git a/packages-private/dts-test/defineComponent.test-d.tsx b/packages-private/dts-test/defineComponent.test-d.tsx index 6188b102b31..fee684d2406 100644 --- a/packages-private/dts-test/defineComponent.test-d.tsx +++ b/packages-private/dts-test/defineComponent.test-d.tsx @@ -1565,7 +1565,7 @@ export default { } describe('slots', () => { - const comp1 = defineComponent({ + const Comp1 = defineComponent({ slots: Object as SlotsType<{ default: { foo: string; bar: number } optional?: { data: string } @@ -1613,18 +1613,38 @@ describe('slots', () => { // @ts-expect-error slots.optionalUndefinedScope?.('foo') - expectType(new comp1().$slots) + expectType(new Comp1().$slots) }, }) - - const comp2 = defineComponent({ + ; + {({ bar, foo }) => [ + <> + {bar} + {foo} + , + ]} + + ; + {{ + default: ({ bar, foo }) => [ + <> + {bar} + {foo} + , + ], + undefinedScope: () => [], + }} + + + const Comp2 = defineComponent({ setup(props, { slots }) { // unknown slots expectType(slots) expectType<((...args: any[]) => VNode[]) | undefined>(slots.default) }, }) - expectType(new comp2().$slots) + expectType(new Comp2().$slots) + expectType<{}>(new Comp2().$props) }) // #5885 diff --git a/packages-private/dts-test/utils.d.ts b/packages-private/dts-test/utils.d.ts index c478b30cb6f..c70a3bd5f00 100644 --- a/packages-private/dts-test/utils.d.ts +++ b/packages-private/dts-test/utils.d.ts @@ -4,6 +4,12 @@ // register global JSX import 'vue/jsx' +declare module 'vue' { + interface JSXElementChildrenAttribute { + 'v-slots': {} + } +} + export function describe(_name: string, _fn: () => void): void export function test(_name: string, _fn: () => any): void diff --git a/packages/runtime-core/src/apiDefineComponent.ts b/packages/runtime-core/src/apiDefineComponent.ts index 4865c3b4ea4..90b5a662daf 100644 --- a/packages/runtime-core/src/apiDefineComponent.ts +++ b/packages/runtime-core/src/apiDefineComponent.ts @@ -21,6 +21,7 @@ import type { ComponentPropsOptions, ExtractDefaultPropTypes, ExtractPropTypes, + SlotsToProps, } from './componentProps' import type { EmitsOptions, @@ -71,7 +72,7 @@ export type DefineComponent< TypeEl extends Element = any, > = ComponentPublicInstanceConstructor< CreateComponentPublicInstanceWithMixins< - Props, + Props & SlotsToProps, RawBindings, D, C, @@ -116,7 +117,7 @@ export type DefineSetupFnComponent< P extends Record, E extends EmitsOptions = {}, S extends SlotsType = SlotsType, - Props = P & EmitsToProps, + Props = P & EmitsToProps & SlotsToProps, PP = PublicProps, > = new ( props: Props & PP, diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 775eb8b6728..5150c4d4c72 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -39,6 +39,8 @@ import { isCompatEnabled, softAssertCompatEnabled } from './compat/compatConfig' import { DeprecationTypes } from './compat/compatConfig' import { shouldSkipAttr } from './compat/attrsFallthrough' import { createInternalObject } from './internalObject' +import type { SlotsType, UnwrapSlotsType } from './componentSlots' +import type { VNodeChild } from './vnode' export type ComponentPropsOptions

= | ComponentObjectPropsOptions

@@ -190,6 +192,38 @@ type NormalizedProp = PropOptions & { export type NormalizedProps = Record export type NormalizedPropsOptions = [NormalizedProps, string[]] | [] +/** + * Defines which prop name is used for JSX children (slots) type checking. + * + * This is not set by default. To enable it, add the following + * declaration in your vue-jsx project: + * + * ```ts + * declare module 'vue' { + * interface JSXElementChildrenAttribute { + * 'v-slots': {} + * } + * } + * ``` + * + * @see {@link https://www.typescriptlang.org/docs/handbook/jsx.html#children-type-checking} + */ +export interface JSXElementChildrenAttribute {} + +export type SlotsToProps< + RawSlots extends SlotsType | Record = Record, + Element = VNodeChild, + Slots = RawSlots extends SlotsType ? UnwrapSlotsType : RawSlots, +> = string extends keyof Slots + ? {} + : keyof JSXElementChildrenAttribute extends infer Key extends string + ? { + [K in Key]?: + | ('default' extends keyof Slots ? Slots['default'] | Slots : Slots) + | NoInfer + } + : {} + export function initProps( instance: ComponentInternalInstance, rawProps: Data | null, diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 792a28b2582..59fe7aa78ea 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -322,6 +322,8 @@ export type { ExtractPropTypes, ExtractPublicPropTypes, ExtractDefaultPropTypes, + JSXElementChildrenAttribute, + SlotsToProps, } from './componentProps' export type { Directive, diff --git a/packages/vue/jsx-runtime/index.d.ts b/packages/vue/jsx-runtime/index.d.ts index 28071b75afe..08a54c6d717 100644 --- a/packages/vue/jsx-runtime/index.d.ts +++ b/packages/vue/jsx-runtime/index.d.ts @@ -1,5 +1,10 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import type { NativeElements, ReservedProps, VNode } from '@vue/runtime-dom' +import type { + JSXElementChildrenAttribute, + NativeElements, + ReservedProps, + VNode, +} from '@vue/runtime-dom' /** * JSX namespace for usage with @jsxImportsSource directive @@ -16,6 +21,7 @@ export namespace JSX { export interface ElementAttributesProperty { $props: {} } + export interface ElementChildrenAttribute extends JSXElementChildrenAttribute {} export interface IntrinsicElements extends NativeElements { // allow arbitrary elements // @ts-ignore suppress ts:2374 = Duplicate string index signature. diff --git a/packages/vue/jsx.d.ts b/packages/vue/jsx.d.ts index cfea000826b..402120bc97e 100644 --- a/packages/vue/jsx.d.ts +++ b/packages/vue/jsx.d.ts @@ -1,7 +1,12 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ // global JSX namespace registration // somehow we have to copy=pase the jsx-runtime types here to make TypeScript happy -import type { NativeElements, ReservedProps, VNode } from '@vue/runtime-dom' +import type { + JSXElementChildrenAttribute, + NativeElements, + ReservedProps, + VNode, +} from '@vue/runtime-dom' declare global { namespace JSX { @@ -12,6 +17,7 @@ declare global { export interface ElementAttributesProperty { $props: {} } + export interface ElementChildrenAttribute extends JSXElementChildrenAttribute {} export interface IntrinsicElements extends NativeElements { // allow arbitrary elements // @ts-ignore suppress ts:2374 = Duplicate string index signature.