diff --git a/.changeset/few-tips-suffer.md b/.changeset/few-tips-suffer.md new file mode 100644 index 000000000000..232ace1f1531 --- /dev/null +++ b/.changeset/few-tips-suffer.md @@ -0,0 +1,5 @@ +--- +'svelte': major +--- + +improve types for props when using Client-side component API diff --git a/packages/svelte/src/runtime/internal/dev.js b/packages/svelte/src/runtime/internal/dev.js index b1d89add3341..65fedad293ae 100644 --- a/packages/svelte/src/runtime/internal/dev.js +++ b/packages/svelte/src/runtime/internal/dev.js @@ -292,9 +292,9 @@ export function construct_svelte_component_dev(component, props) { * * * ``` - * @template {Record} [Props=any] - * @template {Record} [Events=any] - * @template {Record} [Slots=any] + * @template {Record | never} [Props=Record] + * @template {Record} [Events=Record] + * @template {Record} [Slots=Record] * @extends {SvelteComponent} */ export class SvelteComponentDev extends SvelteComponent { @@ -346,9 +346,9 @@ export class SvelteComponentDev extends SvelteComponent { $inject_state() {} } /** - * @template {Record} [Props=any] - * @template {Record} [Events=any] - * @template {Record} [Slots=any] + * @template {Record | never} [Props=Record] + * @template {Record} [Events=Record] + * @template {Record} [Slots=Record] * @deprecated Use `SvelteComponent` instead. See PR for more information: https://github.com/sveltejs/svelte/pull/8512 * @extends {SvelteComponentDev} */ diff --git a/packages/svelte/src/runtime/internal/public.d.ts b/packages/svelte/src/runtime/internal/public.d.ts index 1f1011740d21..f47215320505 100644 --- a/packages/svelte/src/runtime/internal/public.d.ts +++ b/packages/svelte/src/runtime/internal/public.d.ts @@ -1,18 +1,34 @@ import { SvelteComponent } from './Component.js'; import { SvelteComponentDev } from './dev.js'; -export interface ComponentConstructorOptions< - Props extends Record = Record -> { +interface ComponentConstructorOptionsWithoutProps { target: Element | Document | ShadowRoot; anchor?: Element; - props?: Props; context?: Map; hydrate?: boolean; intro?: boolean; $$inline?: boolean; } +interface ComponentConstructorOptionsWithProps< + Props extends Record = Record +> extends ComponentConstructorOptionsWithoutProps { + props: Props; +} + +interface ComponentConstructorOptionsWithOptionalProps< + Props extends Record = Record +> extends ComponentConstructorOptionsWithoutProps { + props?: Props; +} + +export type ComponentConstructorOptions | null = null> = + Props extends null + ? ComponentConstructorOptionsWithoutProps + : Record extends Props + ? ComponentConstructorOptionsWithOptionalProps + : ComponentConstructorOptionsWithProps; + /** * Convenience type to get the events the given component expects. Example: * ```html diff --git a/packages/svelte/test/types/component-constructor-options.ts b/packages/svelte/test/types/component-constructor-options.ts new file mode 100644 index 000000000000..fb5e66f38104 --- /dev/null +++ b/packages/svelte/test/types/component-constructor-options.ts @@ -0,0 +1,60 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { ComponentConstructorOptions } from '$runtime/internal/public.js'; + +type NoProps = ComponentConstructorOptions; + +const n1: NoProps = { + target: document.body +}; + +const n2: NoProps = { + target: document.body, + // @ts-expect-error does not accept props + props: {} +}; + +const n3: NoProps = { + target: document.body, + // @ts-expect-error does not accept any props + props: { + img: '' + } +}; + +type Props = ComponentConstructorOptions<{ img: string }>; + +// @ts-expect-error +const p1: Props = { + target: document.body +}; + +const p2: Props = { + target: document.body, + // @ts-expect-error required prop is missing + props: {} +}; + +const p3: Props = { + target: document.body, + props: { + img: '' + } +}; + +type OptionalProps = ComponentConstructorOptions<{ img?: string }>; + +const o1: OptionalProps = { + target: document.body +}; + +const o2: OptionalProps = { + target: document.body, + props: {} +}; + +const o3: OptionalProps = { + target: document.body, + props: { + img: '' + } +};