From c8ad40c73681461fd49524c73a64d4f2674f54fc Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Mon, 10 Jul 2023 13:12:00 +0200 Subject: [PATCH 1/3] improve types for props when using Client-side component API --- .changeset/few-tips-suffer.md | 5 ++ packages/svelte/src/runtime/internal/dev.js | 12 ++-- .../svelte/src/runtime/internal/public.d.ts | 25 ++++++-- .../types/component-constructor-options.ts | 59 +++++++++++++++++++ 4 files changed, 91 insertions(+), 10 deletions(-) create mode 100644 .changeset/few-tips-suffer.md create mode 100644 packages/svelte/test/types/component-constructor-options.ts diff --git a/.changeset/few-tips-suffer.md b/.changeset/few-tips-suffer.md new file mode 100644 index 000000000000..427dff855b0c --- /dev/null +++ b/.changeset/few-tips-suffer.md @@ -0,0 +1,5 @@ +--- +'svelte': minor +--- + +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..5d284802a573 100644 --- a/packages/svelte/src/runtime/internal/public.d.ts +++ b/packages/svelte/src/runtime/internal/public.d.ts @@ -1,18 +1,35 @@ 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 | never = never> = [ + Props +] extends [never] + ? 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..242c1197f25f --- /dev/null +++ b/packages/svelte/test/types/component-constructor-options.ts @@ -0,0 +1,59 @@ +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: '' + } +}; From 160abcd17a03955d3bd27ab62bc1a2527d8615bf Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Mon, 10 Jul 2023 13:58:03 +0200 Subject: [PATCH 2/3] use `null` instead of `never` --- packages/svelte/src/runtime/internal/public.d.ts | 13 ++++++------- .../test/types/component-constructor-options.ts | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/runtime/internal/public.d.ts b/packages/svelte/src/runtime/internal/public.d.ts index 5d284802a573..f47215320505 100644 --- a/packages/svelte/src/runtime/internal/public.d.ts +++ b/packages/svelte/src/runtime/internal/public.d.ts @@ -22,13 +22,12 @@ interface ComponentConstructorOptionsWithOptionalProps< props?: Props; } -export type ComponentConstructorOptions | never = never> = [ - Props -] extends [never] - ? ComponentConstructorOptionsWithoutProps - : Record extends Props - ? ComponentConstructorOptionsWithOptionalProps - : ComponentConstructorOptionsWithProps; +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: diff --git a/packages/svelte/test/types/component-constructor-options.ts b/packages/svelte/test/types/component-constructor-options.ts index 242c1197f25f..fb5e66f38104 100644 --- a/packages/svelte/test/types/component-constructor-options.ts +++ b/packages/svelte/test/types/component-constructor-options.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import { ComponentConstructorOptions } from '$runtime/internal/public.js'; type NoProps = ComponentConstructorOptions; From e15e76dd564b9b9f11dcfc09a02c4e7fa7f8bac8 Mon Sep 17 00:00:00 2001 From: Ivan Hofer Date: Mon, 10 Jul 2023 13:58:14 +0200 Subject: [PATCH 3/3] update changeset --- .changeset/few-tips-suffer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/few-tips-suffer.md b/.changeset/few-tips-suffer.md index 427dff855b0c..232ace1f1531 100644 --- a/.changeset/few-tips-suffer.md +++ b/.changeset/few-tips-suffer.md @@ -1,5 +1,5 @@ --- -'svelte': minor +'svelte': major --- improve types for props when using Client-side component API