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: ''
+ }
+};