diff --git a/.changeset/yellow-pugs-raise.md b/.changeset/yellow-pugs-raise.md new file mode 100644 index 000000000000..3c43a7a74727 --- /dev/null +++ b/.changeset/yellow-pugs-raise.md @@ -0,0 +1,5 @@ +--- +"svelte": patch +--- + +fix: rework binding type-checking strategy diff --git a/packages/svelte/src/index.d.ts b/packages/svelte/src/index.d.ts index 2c1b5287d453..956e97ed8f88 100644 --- a/packages/svelte/src/index.d.ts +++ b/packages/svelte/src/index.d.ts @@ -1,7 +1,6 @@ // This should contain all the public interfaces (not all of them are actually importable, check current Svelte for which ones are). import './ambient.js'; -import type { RemoveBindable } from './internal/types.js'; /** * @deprecated Svelte components were classes in Svelte 4. In Svelte 5, thy are not anymore. @@ -21,29 +20,10 @@ export interface ComponentConstructorOptions< $$inline?: boolean; } -/** Tooling for types uses this for properties are being used with `bind:` */ -export type Binding = { 'bind:': T }; /** - * Tooling for types uses this for properties that may be bound to. - * Only use this if you author Svelte component type definition files by hand (we recommend using `@sveltejs/package` instead). - * Example: - * ```ts - * export class MyComponent extends SvelteComponent<{ readonly: string, bindable: Bindable }> {} - * ``` - * means you can now do `` + * Utility type for ensuring backwards compatibility on a type level that if there's a default slot, add 'children' to the props */ -export type Bindable = T | Binding; - -type WithBindings = { - [Key in keyof T]: Bindable; -}; - -/** - * Utility type for ensuring backwards compatibility on a type level: - * - If there's a default slot, add 'children' to the props - * - All props are bindable - */ -type PropsWithChildren = WithBindings & +type Properties = Props & (Slots extends { default: any } ? // This is unfortunate because it means "accepts no props" turns into "accepts any prop" // but the alternative is non-fixable type errors because of the way TypeScript index @@ -95,13 +75,13 @@ export class SvelteComponent< * is a stop-gap solution. Migrate towards using `mount` or `createRoot` instead. See * https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more info. */ - constructor(options: ComponentConstructorOptions>); + constructor(options: ComponentConstructorOptions>); /** * For type checking capabilities only. * Does not exist at runtime. * ### DO NOT USE! * */ - $$prop_def: RemoveBindable; // Without PropsWithChildren: unnecessary, causes type bugs + $$prop_def: Props; // Without Properties: unnecessary, causes type bugs /** * For type checking capabilities only. * Does not exist at runtime. @@ -116,6 +96,12 @@ export class SvelteComponent< * * */ $$slot_def: Slots; + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + * */ + $$bindings?: string; /** * @deprecated This method only exists when using one of the legacy compatibility helpers, which @@ -181,7 +167,7 @@ export type ComponentEvents = * ``` */ export type ComponentProps = - Comp extends SvelteComponent ? RemoveBindable : never; + Comp extends SvelteComponent ? Props : never; /** * Convenience type to get the type of a Svelte component. Useful for example in combination with diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index bafaadf2b8f2..1db4c9111be9 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -95,7 +95,7 @@ export function stringify(value) { * @param {{ * target: Document | Element | ShadowRoot; * anchor?: Node; - * props?: import('../types.js').RemoveBindable; + * props?: Props; * events?: { [Property in keyof Events]: (e: Events[Property]) => any }; * context?: Map; * intro?: boolean; @@ -121,7 +121,7 @@ export function mount(component, options) { * @param {import('../../index.js').ComponentType>} component * @param {{ * target: Document | Element | ShadowRoot; - * props?: import('../types.js').RemoveBindable; + * props?: Props; * events?: { [Property in keyof Events]: (e: Events[Property]) => any }; * context?: Map; * intro?: boolean; diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index aa2a3321b8f1..dc9672bc0dd0 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -1,4 +1,3 @@ -import type { Bindable, Binding } from '../../index.js'; import type { Store } from '#shared'; import { STATE_SYMBOL } from './constants.js'; import type { Effect, Source, Value } from './reactivity/types.js'; diff --git a/packages/svelte/src/internal/types.d.ts b/packages/svelte/src/internal/types.d.ts index 1508f2ac84a1..12b2e5d4fbab 100644 --- a/packages/svelte/src/internal/types.d.ts +++ b/packages/svelte/src/internal/types.d.ts @@ -1,8 +1,2 @@ -import type { Bindable } from '../index.js'; - /** Anything except a function */ export type NotFunction = T extends Function ? never : T; - -export type RemoveBindable> = { - [Key in keyof Props]: Props[Key] extends Bindable ? Value : Props[Key]; -}; diff --git a/packages/svelte/tests/types/component.ts b/packages/svelte/tests/types/component.ts index affb2f36fbf6..55477441bb17 100644 --- a/packages/svelte/tests/types/component.ts +++ b/packages/svelte/tests/types/component.ts @@ -5,10 +5,7 @@ import { type ComponentProps, type ComponentType, mount, - hydrate, - type Bindable, - type Binding, - type ComponentConstructorOptions + hydrate } from 'svelte'; SvelteComponent.element === HTMLElement; @@ -177,53 +174,3 @@ const x: typeof asLegacyComponent = createClassComponent({ hydrate: true, component: NewComponent }); - -// --------------------------------------------------------------------------- bindable - -// Test that -// - everything's bindable unless the component constructor is specifically set telling otherwise (for backwards compatibility) -// - when using mount etc the props are never bindable because this is language-tools only concept - -function binding(value: T): Binding { - return value as any; -} - -class Explicit extends SvelteComponent<{ - foo: string; - bar: Bindable; -}> { - constructor(options: ComponentConstructorOptions<{ foo: string; bar: Bindable }>) { - super(options); - } -} -new Explicit({ target: null as any, props: { foo: 'foo', bar: binding(true) } }); -new Explicit({ target: null as any, props: { foo: 'foo', bar: true } }); -new Explicit({ - target: null as any, - props: { - // @ts-expect-error - foo: binding(''), - bar: true - } -}); -mount(Explicit, { target: null as any, props: { foo: 'foo', bar: true } }); -mount(Explicit, { - target: null as any, - props: { - // @ts-expect-error - bar: binding(true) - } -}); - -class Implicit extends SvelteComponent<{ foo: string; bar: boolean }> {} -new Implicit({ target: null as any, props: { foo: 'foo', bar: true } }); -new Implicit({ target: null as any, props: { foo: binding(''), bar: binding(true) } }); -mount(Implicit, { target: null as any, props: { foo: 'foo', bar: true } }); -mount(Implicit, { - target: null as any, - props: { - foo: 'foo', - // @ts-expect-error - bar: binding(true) - } -}); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 07e5c1104e29..7bfdee575c43 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -17,29 +17,10 @@ declare module 'svelte' { $$inline?: boolean; } - /** Tooling for types uses this for properties are being used with `bind:` */ - export type Binding = { 'bind:': T }; /** - * Tooling for types uses this for properties that may be bound to. - * Only use this if you author Svelte component type definition files by hand (we recommend using `@sveltejs/package` instead). - * Example: - * ```ts - * export class MyComponent extends SvelteComponent<{ readonly: string, bindable: Bindable }> {} - * ``` - * means you can now do `` - */ - export type Bindable = T | Binding; - - type WithBindings = { - [Key in keyof T]: Bindable; - }; - - /** - * Utility type for ensuring backwards compatibility on a type level: - * - If there's a default slot, add 'children' to the props - * - All props are bindable + * Utility type for ensuring backwards compatibility on a type level that if there's a default slot, add 'children' to the props */ - type PropsWithChildren = WithBindings & + type Properties = Props & (Slots extends { default: any } ? // This is unfortunate because it means "accepts no props" turns into "accepts any prop" // but the alternative is non-fixable type errors because of the way TypeScript index @@ -91,13 +72,13 @@ declare module 'svelte' { * is a stop-gap solution. Migrate towards using `mount` or `createRoot` instead. See * https://svelte-5-preview.vercel.app/docs/breaking-changes#components-are-no-longer-classes for more info. */ - constructor(options: ComponentConstructorOptions>); + constructor(options: ComponentConstructorOptions>); /** * For type checking capabilities only. * Does not exist at runtime. * ### DO NOT USE! * */ - $$prop_def: RemoveBindable; // Without PropsWithChildren: unnecessary, causes type bugs + $$prop_def: Props; // Without Properties: unnecessary, causes type bugs /** * For type checking capabilities only. * Does not exist at runtime. @@ -112,6 +93,12 @@ declare module 'svelte' { * * */ $$slot_def: Slots; + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + * */ + $$bindings?: string; /** * @deprecated This method only exists when using one of the legacy compatibility helpers, which @@ -177,7 +164,7 @@ declare module 'svelte' { * ``` */ export type ComponentProps = - Comp extends SvelteComponent ? RemoveBindable : never; + Comp extends SvelteComponent ? Props : never; /** * Convenience type to get the type of a Svelte component. Useful for example in combination with @@ -247,12 +234,6 @@ declare module 'svelte' { : [type: Type, parameter: EventMap[Type], options?: DispatchOptions] ): boolean; } - /** Anything except a function */ - type NotFunction = T extends Function ? never : T; - - type RemoveBindable> = { - [Key in keyof Props]: Props[Key] extends Bindable ? Value : Props[Key]; - }; /** * The `onMount` function schedules a callback to run as soon as the component has been mounted to the DOM. * It must be called during the component's initialisation (but doesn't need to live *inside* the component; @@ -323,6 +304,8 @@ declare module 'svelte' { * Synchronously flushes any pending state changes and those that result from it. * */ export function flushSync(fn?: (() => void) | undefined): void; + /** Anything except a function */ + type NotFunction = T extends Function ? never : T; /** * Mounts a component to the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component * @@ -330,7 +313,7 @@ declare module 'svelte' { export function mount, Exports extends Record, Events extends Record>(component: ComponentType>, options: { target: Document | Element | ShadowRoot; anchor?: Node | undefined; - props?: RemoveBindable | undefined; + props?: Props | undefined; events?: { [Property in keyof Events]: (e: Events[Property]) => any; } | undefined; context?: Map | undefined; intro?: boolean | undefined; @@ -341,7 +324,7 @@ declare module 'svelte' { * */ export function hydrate, Exports extends Record, Events extends Record>(component: ComponentType>, options: { target: Document | Element | ShadowRoot; - props?: RemoveBindable | undefined; + props?: Props | undefined; events?: { [Property in keyof Events]: (e: Events[Property]) => any; } | undefined; context?: Map | undefined; intro?: boolean | undefined;