]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(types/slots): support slot presence / props type checks via `defineSlots` macro...
author三咲智子 Kevin Deng <sxzz@sxzz.moe>
Mon, 3 Apr 2023 08:49:16 +0000 (16:49 +0800)
committerGitHub <noreply@github.com>
Mon, 3 Apr 2023 08:49:16 +0000 (16:49 +0800)
16 files changed:
packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
packages/compiler-sfc/__tests__/compileScript.spec.ts
packages/compiler-sfc/src/compileScript.ts
packages/dts-test/defineComponent.test-d.tsx
packages/dts-test/functionalComponent.test-d.tsx
packages/dts-test/setupHelpers.test-d.ts
packages/runtime-core/src/apiDefineComponent.ts
packages/runtime-core/src/apiSetupHelpers.ts
packages/runtime-core/src/component.ts
packages/runtime-core/src/componentOptions.ts
packages/runtime-core/src/componentPublicInstance.ts
packages/runtime-core/src/componentSlots.ts
packages/runtime-core/src/h.ts
packages/runtime-core/src/index.ts
packages/runtime-core/types/scriptSetupHelpers.d.ts
packages/runtime-dom/src/apiCustomElement.ts

index f59a7407c25a462e1906c2a31325ce53d26d4278..ee00977c1312413f14de1491cfe95b6ccc63ea31 100644 (file)
@@ -1785,6 +1785,51 @@ return { props, emit }
 })"
 `;
 
+exports[`SFC compile <script setup> > with TypeScript > defineSlots() > basic usage 1`] = `
+"import { useSlots as _useSlots, defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+          const slots = _useSlots()
+          
+return { slots }
+}
+
+})"
+`;
+
+exports[`SFC compile <script setup> > with TypeScript > defineSlots() > w/o generic params 1`] = `
+"import { useSlots as _useSlots } from 'vue'
+
+export default {
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+          const slots = _useSlots()
+          
+return { slots }
+}
+
+}"
+`;
+
+exports[`SFC compile <script setup> > with TypeScript > defineSlots() > w/o return value 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+
+export default /*#__PURE__*/_defineComponent({
+  setup(__props, { expose: __expose }) {
+  __expose();
+
+          
+          
+return {  }
+}
+
+})"
+`;
+
 exports[`SFC compile <script setup> > with TypeScript > hoist type declarations 1`] = `
 "import { defineComponent as _defineComponent } from 'vue'
 export interface Foo {}
index b3907fa721111377763250554d0b4f2dc21b36b9..25bfce72bd2b125dc1e836e560ac189a16a2d12e 100644 (file)
@@ -1585,6 +1585,45 @@ const emit = defineEmits(['a', 'b'])
       assertCode(content)
     })
 
+    describe('defineSlots()', () => {
+      test('basic usage', () => {
+        const { content } = compile(`
+          <script setup lang="ts">
+          const slots = defineSlots<{
+            default: { msg: string }
+          }>()
+          </script>
+        `)
+        assertCode(content)
+        expect(content).toMatch(`const slots = _useSlots()`)
+        expect(content).not.toMatch('defineSlots')
+      })
+
+      test('w/o return value', () => {
+        const { content } = compile(`
+          <script setup lang="ts">
+          defineSlots<{
+            default: { msg: string }
+          }>()
+          </script>
+        `)
+        assertCode(content)
+        expect(content).not.toMatch('defineSlots')
+        expect(content).not.toMatch(`_useSlots`)
+      })
+
+      test('w/o generic params', () => {
+        const { content } = compile(`
+          <script setup>
+          const slots = defineSlots()
+          </script>
+        `)
+        assertCode(content)
+        expect(content).toMatch(`const slots = _useSlots()`)
+        expect(content).not.toMatch('defineSlots')
+      })
+    })
+
     test('runtime Enum', () => {
       const { content, bindings } = compile(
         `<script setup lang="ts">
index ec476c4ad16c0b0bd32e8b8cbda60fba1642775b..1f1385b25fde87f6c2cd169e9ceaa03764dba858 100644 (file)
@@ -67,6 +67,7 @@ const DEFINE_EMITS = 'defineEmits'
 const DEFINE_EXPOSE = 'defineExpose'
 const WITH_DEFAULTS = 'withDefaults'
 const DEFINE_OPTIONS = 'defineOptions'
+const DEFINE_SLOTS = 'defineSlots'
 
 const isBuiltInDir = makeMap(
   `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
@@ -312,6 +313,7 @@ export function compileScript(
   let hasDefaultExportName = false
   let hasDefaultExportRender = false
   let hasDefineOptionsCall = false
+  let hasDefineSlotsCall = false
   let propsRuntimeDecl: Node | undefined
   let propsRuntimeDefaults: Node | undefined
   let propsDestructureDecl: Node | undefined
@@ -590,6 +592,30 @@ export function compileScript(
     return true
   }
 
+  function processDefineSlots(node: Node, declId?: LVal): boolean {
+    if (!isCallOf(node, DEFINE_SLOTS)) {
+      return false
+    }
+    if (hasDefineSlotsCall) {
+      error(`duplicate ${DEFINE_SLOTS}() call`, node)
+    }
+    hasDefineSlotsCall = true
+
+    if (node.arguments.length > 0) {
+      error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
+    }
+
+    if (declId) {
+      s.overwrite(
+        startOffset + node.start!,
+        startOffset + node.end!,
+        `${helper('useSlots')}()`
+      )
+    }
+
+    return true
+  }
+
   function getAstBody(): Statement[] {
     return scriptAst
       ? [...scriptSetupAst.body, ...scriptAst.body]
@@ -683,6 +709,7 @@ export function compileScript(
     let propsOption = undefined
     let emitsOption = undefined
     let exposeOption = undefined
+    let slotsOption = undefined
     if (optionsRuntimeDecl.type === 'ObjectExpression') {
       for (const prop of optionsRuntimeDecl.properties) {
         if (
@@ -692,6 +719,7 @@ export function compileScript(
           if (prop.key.name === 'props') propsOption = prop
           if (prop.key.name === 'emits') emitsOption = prop
           if (prop.key.name === 'expose') exposeOption = prop
+          if (prop.key.name === 'slots') slotsOption = prop
         }
       }
     }
@@ -714,6 +742,12 @@ export function compileScript(
         exposeOption
       )
     }
+    if (slotsOption) {
+      error(
+        `${DEFINE_OPTIONS}() cannot be used to declare slots. Use ${DEFINE_SLOTS}() instead.`,
+        slotsOption
+      )
+    }
 
     return true
   }
@@ -1286,7 +1320,8 @@ export function compileScript(
         processDefineProps(expr) ||
         processDefineEmits(expr) ||
         processDefineOptions(expr) ||
-        processWithDefaults(expr)
+        processWithDefaults(expr) ||
+        processDefineSlots(expr)
       ) {
         s.remove(node.start! + startOffset, node.end! + startOffset)
       } else if (processDefineExpose(expr)) {
@@ -1320,7 +1355,10 @@ export function compileScript(
           const isDefineProps =
             processDefineProps(init, decl.id) ||
             processWithDefaults(init, decl.id)
-          const isDefineEmits = processDefineEmits(init, decl.id)
+          const isDefineEmits =
+            !isDefineProps && processDefineEmits(init, decl.id)
+          !isDefineEmits && processDefineSlots(init, decl.id)
+
           if (isDefineProps || isDefineEmits) {
             if (left === 1) {
               s.remove(node.start! + startOffset, node.end! + startOffset)
index 3aec37e97b6aa32bf2dafaddd569aaad1e3b881a..32912dd9a2a07393fe63c3e0bf99011911461cd6 100644 (file)
@@ -8,7 +8,10 @@ import {
   ComponentPublicInstance,
   ComponentOptions,
   SetupContext,
-  h
+  h,
+  SlotsType,
+  Slots,
+  VNode
 } from 'vue'
 import { describe, expectType, IsUnion } from './utils'
 
@@ -1406,6 +1409,69 @@ export default {
   })
 }
 
+describe('slots', () => {
+  const comp1 = defineComponent({
+    slots: Object as SlotsType<{
+      default: { foo: string; bar: number }
+      optional?: { data: string }
+      undefinedScope: undefined | { data: string }
+      optionalUndefinedScope?: undefined | { data: string }
+    }>,
+    setup(props, { slots }) {
+      expectType<(scope: { foo: string; bar: number }) => VNode[]>(
+        slots.default
+      )
+      expectType<((scope: { data: string }) => VNode[]) | undefined>(
+        slots.optional
+      )
+
+      slots.default({ foo: 'foo', bar: 1 })
+
+      // @ts-expect-error it's optional
+      slots.optional({ data: 'foo' })
+      slots.optional?.({ data: 'foo' })
+
+      expectType<{
+        (): VNode[]
+        (scope: undefined | { data: string }): VNode[]
+      }>(slots.undefinedScope)
+
+      expectType<
+        | { (): VNode[]; (scope: undefined | { data: string }): VNode[] }
+        | undefined
+      >(slots.optionalUndefinedScope)
+
+      slots.default({ foo: 'foo', bar: 1 })
+      // @ts-expect-error it's optional
+      slots.optional({ data: 'foo' })
+      slots.optional?.({ data: 'foo' })
+      slots.undefinedScope()
+      slots.undefinedScope(undefined)
+      // @ts-expect-error
+      slots.undefinedScope('foo')
+
+      slots.optionalUndefinedScope?.()
+      slots.optionalUndefinedScope?.(undefined)
+      slots.optionalUndefinedScope?.({ data: 'foo' })
+      // @ts-expect-error
+      slots.optionalUndefinedScope()
+      // @ts-expect-error
+      slots.optionalUndefinedScope?.('foo')
+
+      expectType<typeof slots | undefined>(new comp1().$slots)
+    }
+  })
+
+  const comp2 = defineComponent({
+    setup(props, { slots }) {
+      // unknown slots
+      expectType<Slots>(slots)
+      expectType<((...args: any[]) => VNode[]) | undefined>(slots.default)
+    }
+  })
+  expectType<Slots | undefined>(new comp2().$slots)
+})
+
 import {
   DefineComponent,
   ComponentOptionsMixin,
@@ -1428,6 +1494,7 @@ declare const MyButton: DefineComponent<
   ComponentOptionsMixin,
   EmitsOptions,
   string,
+  {},
   VNodeProps & AllowedComponentProps & ComponentCustomProps,
   Readonly<ExtractPropTypes<{}>>,
   {}
index 383debcb7fe10ade48a7432a17124edf297923e8..827e8d63fe74cdd3a570c4c18b1fc8fe767e1699 100644 (file)
@@ -1,4 +1,4 @@
-import { h, Text, FunctionalComponent, Component } from 'vue'
+import { h, Text, FunctionalComponent, Component, VNode } from 'vue'
 import { expectType } from './utils'
 
 // simple function signature
@@ -68,3 +68,29 @@ const Qux: FunctionalComponent<{}, ['foo', 'bar']> = (props, { emit }) => {
 }
 
 expectType<Component>(Qux)
+
+const Quux: FunctionalComponent<
+  {},
+  {},
+  {
+    default: { foo: number }
+    optional?: { foo: number }
+  }
+> = (props, { emit, slots }) => {
+  expectType<{
+    default: (scope: { foo: number }) => VNode[]
+    optional?: (scope: { foo: number }) => VNode[]
+  }>(slots)
+
+  slots.default({ foo: 123 })
+  // @ts-expect-error
+  slots.default({ foo: 'fesf' })
+
+  slots.optional?.({ foo: 123 })
+  // @ts-expect-error
+  slots.optional?.({ foo: 'fesf' })
+  // @ts-expect-error
+  slots.optional({ foo: 123 })
+}
+expectType<Component>(Quux)
+;<Quux />
index d5d332daa1bbefd29755232e84654f94cb69bccc..13d80947c83fb9f4631ba8eb7ef41160b9b8ccae 100644 (file)
@@ -4,7 +4,9 @@ import {
   useAttrs,
   useSlots,
   withDefaults,
-  Slots
+  Slots,
+  defineSlots,
+  VNode
 } from 'vue'
 import { describe, expectType } from './utils'
 
@@ -179,6 +181,27 @@ describe('defineEmits w/ runtime declaration', () => {
   emit2('baz')
 })
 
+describe('defineSlots', () => {
+  // short syntax
+  const slots = defineSlots<{
+    default: { foo: string; bar: number }
+    optional?: string
+  }>()
+  expectType<(scope: { foo: string; bar: number }) => VNode[]>(slots.default)
+  expectType<undefined | ((scope: string) => VNode[])>(slots.optional)
+
+  // literal fn syntax (allow for specifying return type)
+  const fnSlots = defineSlots<{
+    default(props: { foo: string; bar: number }): any
+    optional?(props: string): any
+  }>()
+  expectType<(scope: { foo: string; bar: number }) => VNode[]>(fnSlots.default)
+  expectType<undefined | ((scope: string) => VNode[])>(fnSlots.optional)
+
+  const slotsUntype = defineSlots()
+  expectType<Slots>(slotsUntype)
+})
+
 describe('useAttrs', () => {
   const attrs = useAttrs()
   expectType<Record<string, unknown>>(attrs)
index bf579e7557357d8ade31410bda74deac879b7381..0adc9d29387c98666b156ec1d4e8f60e1134e4e0 100644 (file)
@@ -28,6 +28,7 @@ import {
   CreateComponentPublicInstance,
   ComponentPublicInstanceConstructor
 } from './componentPublicInstance'
+import { SlotsType } from './componentSlots'
 
 export type PublicProps = VNodeProps &
   AllowedComponentProps &
@@ -43,6 +44,7 @@ export type DefineComponent<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = {},
   EE extends string = string,
+  S extends SlotsType = {},
   PP = PublicProps,
   Props = Readonly<
     PropsOrPropOptions extends ComponentPropsOptions
@@ -61,6 +63,7 @@ export type DefineComponent<
     Mixin,
     Extends,
     E,
+    S,
     PP & Props,
     Defaults,
     true
@@ -77,6 +80,7 @@ export type DefineComponent<
     Extends,
     E,
     EE,
+    S,
     Defaults
   > &
   PP
@@ -91,29 +95,33 @@ export type DefineComponent<
 export function defineComponent<
   Props extends Record<string, any>,
   E extends EmitsOptions = {},
-  EE extends string = string
+  EE extends string = string,
+  S extends SlotsType = {}
 >(
   setup: (
     props: Props,
-    ctx: SetupContext<E>
+    ctx: SetupContext<E, S>
   ) => RenderFunction | Promise<RenderFunction>,
   options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
     props?: (keyof Props)[]
     emits?: E | EE[]
+    slots?: S
   }
 ): (props: Props & EmitsToProps<E>) => any
 export function defineComponent<
   Props extends Record<string, any>,
   E extends EmitsOptions = {},
-  EE extends string = string
+  EE extends string = string,
+  S extends SlotsType = {}
 >(
   setup: (
     props: Props,
-    ctx: SetupContext<E>
+    ctx: SetupContext<E, S>
   ) => RenderFunction | Promise<RenderFunction>,
   options?: Pick<ComponentOptions, 'name' | 'inheritAttrs'> & {
     props?: ComponentObjectPropsOptions<Props>
     emits?: E | EE[]
+    slots?: S
   }
 ): (props: Props & EmitsToProps<E>) => any
 
@@ -130,6 +138,7 @@ export function defineComponent<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = {},
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
 >(
@@ -143,10 +152,11 @@ export function defineComponent<
     Extends,
     E,
     EE,
+    S,
     I,
     II
   >
-): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>
+): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE, S>
 
 // overload 3: object format with array props declaration
 // props inferred as { [key in PropNames]?: any }
@@ -161,6 +171,7 @@ export function defineComponent<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = {},
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
 >(
@@ -174,6 +185,7 @@ export function defineComponent<
     Extends,
     E,
     EE,
+    S,
     I,
     II
   >
@@ -186,7 +198,8 @@ export function defineComponent<
   Mixin,
   Extends,
   E,
-  EE
+  EE,
+  S
 >
 
 // overload 4: object format with object props declaration
@@ -203,6 +216,7 @@ export function defineComponent<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = {},
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
 >(
@@ -216,10 +230,11 @@ export function defineComponent<
     Extends,
     E,
     EE,
+    S,
     I,
     II
   >
-): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
+): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, S>
 
 // implementation, close to no-op
 export function defineComponent(
index 239246a33ead3595d87ece494e6191ae36ead320..1927d13bbd4da3dd59b9d64b466ae74fa816482b 100644 (file)
@@ -25,6 +25,7 @@ import {
   ExtractPropTypes
 } from './componentProps'
 import { warn } from './warning'
+import { SlotsType, TypedSlots } from './componentSlots'
 
 // dev only
 const warnRuntimeUsage = (method: string) =>
@@ -184,9 +185,7 @@ export function defineOptions<
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
   Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
-  Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
-  E extends EmitsOptions = EmitsOptions,
-  EE extends string = string
+  Extends extends ComponentOptionsMixin = ComponentOptionsMixin
 >(
   options?: ComponentOptionsWithoutProps<
     {},
@@ -195,16 +194,23 @@ export function defineOptions<
     C,
     M,
     Mixin,
-    Extends,
-    E,
-    EE
-  > & { emits?: undefined; expose?: undefined }
+    Extends
+  > & { emits?: undefined; expose?: undefined; slots?: undefined }
 ): void {
   if (__DEV__) {
     warnRuntimeUsage(`defineOptions`)
   }
 }
 
+export function defineSlots<
+  S extends Record<string, any> = Record<string, any>
+>(): // @ts-expect-error
+TypedSlots<SlotsType<S>> {
+  if (__DEV__) {
+    warnRuntimeUsage(`defineSlots`)
+  }
+}
+
 type NotUndefined<T> = T extends undefined ? never : T
 
 type InferDefaults<T> = {
index 3e184d352cfcc120bd62128f6824a71c98d9a2f0..941231b393dfbf217388dca6a1b48da5dc1d986d 100644 (file)
@@ -27,7 +27,13 @@ import {
   initProps,
   normalizePropsOptions
 } from './componentProps'
-import { Slots, initSlots, InternalSlots } from './componentSlots'
+import {
+  initSlots,
+  InternalSlots,
+  Slots,
+  SlotsType,
+  TypedSlots
+} from './componentSlots'
 import { warn } from './warning'
 import { ErrorCodes, callWithErrorHandling, handleError } from './errorHandling'
 import { AppContext, createAppContext, AppConfig } from './apiCreateApp'
@@ -57,7 +63,8 @@ import {
   isPromise,
   ShapeFlags,
   extend,
-  getGlobalThis
+  getGlobalThis,
+  IfAny
 } from '@vue/shared'
 import { SuspenseBoundary } from './components/Suspense'
 import { CompilerOptions } from '@vue/compiler-core'
@@ -117,12 +124,19 @@ export interface ComponentInternalOptions {
   __name?: string
 }
 
-export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
-  extends ComponentInternalOptions {
+export interface FunctionalComponent<
+  P = {},
+  E extends EmitsOptions = {},
+  S extends Record<string, any> = any
+> extends ComponentInternalOptions {
   // use of any here is intentional so it can be a valid JSX Element constructor
-  (props: P, ctx: Omit<SetupContext<E>, 'expose'>): any
+  (
+    props: P,
+    ctx: Omit<SetupContext<E, IfAny<S, {}, SlotsType<S>>>, 'expose'>
+  ): any
   props?: ComponentPropsOptions<P>
   emits?: E | (keyof E)[]
+  slots?: IfAny<S, Slots, SlotsType<S>>
   inheritAttrs?: boolean
   displayName?: string
   compatConfig?: CompatConfig
@@ -147,7 +161,7 @@ export type ConcreteComponent<
   M extends MethodOptions = MethodOptions
 > =
   | ComponentOptions<Props, RawBindings, D, C, M>
-  | FunctionalComponent<Props, any>
+  | FunctionalComponent<Props, any, any>
 
 /**
  * A type used in public APIs where a component type is expected.
@@ -168,10 +182,13 @@ export type { ComponentOptions }
 type LifecycleHook<TFn = Function> = TFn[] | null
 
 // use `E extends any` to force evaluating type to fix #2362
-export type SetupContext<E = EmitsOptions> = E extends any
+export type SetupContext<
+  E = EmitsOptions,
+  S extends SlotsType = {}
+> = E extends any
   ? {
       attrs: Data
-      slots: Slots
+      slots: TypedSlots<S>
       emit: EmitFn<E>
       expose: (exposed?: Record<string, any>) => void
     }
index b5324c80dd07e8c8df88645a81f7a25b3a1c89fa..04c3839dd1d34d3ff9ff9215f0b6d91d3ee74855 100644 (file)
@@ -74,6 +74,7 @@ import {
 } from './compat/compatConfig'
 import { OptionMergeFunction } from './apiCreateApp'
 import { LifecycleHooks } from './enums'
+import { SlotsType } from './componentSlots'
 
 /**
  * Interface for declaring custom options.
@@ -105,6 +106,7 @@ export interface ComponentOptionsBase<
   Extends extends ComponentOptionsMixin,
   E extends EmitsOptions,
   EE extends string = string,
+  S extends SlotsType = {},
   Defaults = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
@@ -122,7 +124,7 @@ export interface ComponentOptionsBase<
           >
         >
     >,
-    ctx: SetupContext<E>
+    ctx: SetupContext<E, S>
   ) => Promise<RawBindings> | RawBindings | RenderFunction | void
   name?: string
   template?: string | object // can be a direct DOM node
@@ -136,6 +138,7 @@ export interface ComponentOptionsBase<
   directives?: Record<string, Directive>
   inheritAttrs?: boolean
   emits?: (E | EE[]) & ThisType<void>
+  slots?: S
   // TODO infer public instance type based on exposed keys
   expose?: string[]
   serverPrefetch?(): void | Promise<any>
@@ -216,6 +219,7 @@ export type ComponentOptionsWithoutProps<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string,
   PE = Props & EmitsToProps<E>
@@ -229,6 +233,7 @@ export type ComponentOptionsWithoutProps<
   Extends,
   E,
   EE,
+  S,
   {},
   I,
   II
@@ -244,6 +249,7 @@ export type ComponentOptionsWithoutProps<
       Mixin,
       Extends,
       E,
+      S,
       PE,
       {},
       false,
@@ -261,6 +267,7 @@ export type ComponentOptionsWithArrayProps<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string,
   Props = Prettify<Readonly<{ [key in PropNames]?: any } & EmitsToProps<E>>>
@@ -274,6 +281,7 @@ export type ComponentOptionsWithArrayProps<
   Extends,
   E,
   EE,
+  S,
   {},
   I,
   II
@@ -289,6 +297,7 @@ export type ComponentOptionsWithArrayProps<
       Mixin,
       Extends,
       E,
+      S,
       Props,
       {},
       false,
@@ -306,6 +315,7 @@ export type ComponentOptionsWithObjectProps<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string,
   Props = Prettify<Readonly<ExtractPropTypes<PropsOptions> & EmitsToProps<E>>>,
@@ -320,6 +330,7 @@ export type ComponentOptionsWithObjectProps<
   Extends,
   E,
   EE,
+  S,
   Defaults,
   I,
   II
@@ -335,6 +346,7 @@ export type ComponentOptionsWithObjectProps<
       Mixin,
       Extends,
       E,
+      S,
       Props,
       Defaults,
       false,
@@ -350,8 +362,20 @@ export type ComponentOptions<
   M extends MethodOptions = any,
   Mixin extends ComponentOptionsMixin = any,
   Extends extends ComponentOptionsMixin = any,
-  E extends EmitsOptions = any
-> = ComponentOptionsBase<Props, RawBindings, D, C, M, Mixin, Extends, E> &
+  E extends EmitsOptions = any,
+  S extends SlotsType = any
+> = ComponentOptionsBase<
+  Props,
+  RawBindings,
+  D,
+  C,
+  M,
+  Mixin,
+  Extends,
+  E,
+  string,
+  S
+> &
   ThisType<
     CreateComponentPublicInstance<
       {},
@@ -376,6 +400,7 @@ export type ComponentOptionsMixin = ComponentOptionsBase<
   any,
   any,
   any,
+  any,
   any
 >
 
index f083f06205253a4e856b4596537d1d539082d1cc..9997296b641d706ae2d7f06ad608f96af7b6c70f 100644 (file)
@@ -40,7 +40,7 @@ import {
   ComponentInjectOptions
 } from './componentOptions'
 import { EmitsOptions, EmitFn } from './componentEmits'
-import { Slots } from './componentSlots'
+import { SlotsType, TypedSlots } from './componentSlots'
 import { markAttrsAccessed } from './componentRenderUtils'
 import { currentRenderingInstance } from './componentRenderContext'
 import { warn } from './warning'
@@ -89,6 +89,7 @@ type MixinToOptionTypes<T> = T extends ComponentOptionsBase<
   infer Extends,
   any,
   any,
+  any,
   infer Defaults
 >
   ? OptionTypesType<P & {}, B & {}, D & {}, C & {}, M & {}, Defaults & {}> &
@@ -141,6 +142,7 @@ export type CreateComponentPublicInstance<
   Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = {},
+  S extends SlotsType = {},
   PublicProps = P,
   Defaults = {},
   MakeDefaultsOptional extends boolean = false,
@@ -162,10 +164,11 @@ export type CreateComponentPublicInstance<
   PublicC,
   PublicM,
   E,
+  S,
   PublicProps,
   PublicDefaults,
   MakeDefaultsOptional,
-  ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults>,
+  ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, S, Defaults>,
   I
 >
 
@@ -178,6 +181,7 @@ export type ComponentPublicInstance<
   C extends ComputedOptions = {},
   M extends MethodOptions = {},
   E extends EmitsOptions = {},
+  S extends SlotsType = {},
   PublicProps = P,
   Defaults = {},
   MakeDefaultsOptional extends boolean = false,
@@ -193,7 +197,7 @@ export type ComponentPublicInstance<
   >
   $attrs: Data
   $refs: Data
-  $slots: Slots
+  $slots: TypedSlots<S>
   $root: ComponentPublicInstance | null
   $parent: ComponentPublicInstance | null
   $emit: EmitFn<E>
index 682a107e676f57392049ea1c1ff35d3559dace5c..8198859998187f569331a3913e4fc039708f0b30 100644 (file)
@@ -13,7 +13,9 @@ import {
   ShapeFlags,
   extend,
   def,
-  SlotFlags
+  SlotFlags,
+  Prettify,
+  IfAny
 } from '@vue/shared'
 import { warn } from './warning'
 import { isKeepAlive } from './components/KeepAlive'
@@ -22,7 +24,9 @@ import { isHmrUpdating } from './hmr'
 import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
 import { toRaw } from '@vue/reactivity'
 
-export type Slot = (...args: any[]) => VNode[]
+export type Slot<T extends any = any> = (
+  ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
+) => VNode[]
 
 export type InternalSlots = {
   [name: string]: Slot | undefined
@@ -30,6 +34,24 @@ export type InternalSlots = {
 
 export type Slots = Readonly<InternalSlots>
 
+declare const SlotSymbol: unique symbol
+export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
+  [SlotSymbol]?: T
+}
+
+export type TypedSlots<
+  S extends SlotsType,
+  T = NonNullable<S[typeof SlotSymbol]>
+> = [keyof S] extends [never]
+  ? Slots
+  : Readonly<
+      Prettify<{
+        [K in keyof T]: NonNullable<T[K]> extends (...args: any[]) => any
+          ? T[K]
+          : Slot<T[K]>
+      }>
+    >
+
 export type RawSlots = {
   [name: string]: unknown
   // manual render fn hint to skip forced children updates
index 520f568e7a80c67f146ec6a0500ccc61602dc18e..73b27107b8bb006133f11b462d7ee19287147e73 100644 (file)
@@ -120,8 +120,12 @@ export function h(
 ): VNode
 
 // functional component
-export function h<P, E extends EmitsOptions = {}>(
-  type: FunctionalComponent<P, E>,
+export function h<
+  P,
+  E extends EmitsOptions = {},
+  S extends Record<string, any> = {}
+>(
+  type: FunctionalComponent<P, E, S>,
   props?: (RawProps & P) | ({} extends P ? null : never),
   children?: RawChildren | RawSlots
 ): VNode
index 936d6ca3565275ce36f234c55c96943d7ef45741..428f3683d5aa8367136cdd374ab4d234802cc367 100644 (file)
@@ -70,6 +70,7 @@ export {
   defineEmits,
   defineExpose,
   defineOptions,
+  defineSlots,
   withDefaults,
   // internal
   mergeDefaults,
@@ -243,7 +244,7 @@ export type {
   RootRenderFunction
 } from './renderer'
 export type { RootHydrateFunction } from './hydration'
-export type { Slot, Slots } from './componentSlots'
+export type { Slot, Slots, SlotsType } from './componentSlots'
 export type {
   Prop,
   PropType,
index ba4ca79fc59a91fd3ce5cbf22a5445bdb8fe15b9..4ffc2f725aed0175b289c1d6d301610f09986c03 100644 (file)
@@ -4,6 +4,7 @@ type _defineProps = typeof defineProps
 type _defineEmits = typeof defineEmits
 type _defineExpose = typeof defineExpose
 type _defineOptions = typeof defineOptions
+type _defineSlots = typeof defineSlots
 type _withDefaults = typeof withDefaults
 
 declare global {
@@ -11,5 +12,6 @@ declare global {
   const defineEmits: _defineEmits
   const defineExpose: _defineExpose
   const defineOptions: _defineOptions
+  const defineSlots: _defineSlots
   const withDefaults: _withDefaults
 }
index b4bd143c1355c90fa57bfc6bb7e04d58c632760e..58bc48f64d64eacc67c7101e3be036948c98e097 100644 (file)
@@ -20,7 +20,8 @@ import {
   warn,
   ConcreteComponent,
   ComponentOptions,
-  ComponentInjectOptions
+  ComponentInjectOptions,
+  SlotsType
 } from '@vue/runtime-core'
 import { camelize, extend, hyphenate, isArray, toNumber } from '@vue/shared'
 import { hydrate, render } from '.'
@@ -51,6 +52,7 @@ export function defineCustomElement<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = EmitsOptions,
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
 >(
@@ -64,6 +66,7 @@ export function defineCustomElement<
     Extends,
     E,
     EE,
+    S,
     I,
     II
   > & { styles?: string[] }
@@ -80,6 +83,7 @@ export function defineCustomElement<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = Record<string, any>,
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
 >(
@@ -93,6 +97,7 @@ export function defineCustomElement<
     Extends,
     E,
     EE,
+    S,
     I,
     II
   > & { styles?: string[] }
@@ -109,6 +114,7 @@ export function defineCustomElement<
   Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
   E extends EmitsOptions = Record<string, any>,
   EE extends string = string,
+  S extends SlotsType = {},
   I extends ComponentInjectOptions = {},
   II extends string = string
 >(
@@ -122,6 +128,7 @@ export function defineCustomElement<
     Extends,
     E,
     EE,
+    S,
     I,
     II
   > & { styles?: string[] }