diff --git a/packages/svelte/src/internal/client/custom-renderer/index.js b/packages/svelte/src/internal/client/custom-renderer/index.js index f01f9b66d6..728918b856 100644 --- a/packages/svelte/src/internal/client/custom-renderer/index.js +++ b/packages/svelte/src/internal/client/custom-renderer/index.js @@ -1,4 +1,5 @@ /** @import { ComponentContext } from '#client' */ +/** @import { Component, ComponentType, SvelteComponent } from '../../../index.js' */ import { boundary } from '../dom/blocks/boundary.js'; import { branch, effect_root } from '../reactivity/effects.js'; import { push, pop, component_context } from '../context.js'; @@ -38,26 +39,27 @@ import { push_renderer } from './state.js'; * @template {object} [TTextNode=object] * @template {object} [TComment=object] * @param {Renderer} renderer - * @returns {Renderer & { render: (Component: any, options: { target: TFragment | TElement | TTextNode | TComment, props?: any, context?: Map }) => () => void }} + * @returns {Renderer & { render: >(component: ComponentType> | Component, options: {} extends Props ? { target: TFragment | TElement | TTextNode | TComment, props?: Props, context?: Map } : { target: TFragment | TElement | TTextNode | TComment, props: Props, context?: Map }) => () => void }} */ export function createRenderer(renderer) { return { ...renderer, /** - * @param {*} Component - * @param {*} options + * @template {Record} Props + * @param {ComponentType> | Component} Component + * @param {{} extends Props ? { target: TFragment | TElement | TTextNode | TComment, props?: Props, context?: Map } : { target: TFragment | TElement | TTextNode | TComment, props: Props, context?: Map }} options */ render(Component, { target, props, context }) { var cleanup = push_renderer(renderer); const unmount = effect_root(() => { var anchor = renderer.createComment(''); - renderer.insert(target, anchor, null); + renderer.insert(/** @type {*} */ (target), anchor, null); boundary(/** @type {*} */ (anchor), { pending: () => {} }, (anchor) => { push({}); var ctx = /** @type {ComponentContext} */ (component_context); if (context) ctx.c = context; branch(() => { - Component(anchor, props); + /** @type {Function} */ (Component)(anchor, props); }); pop(); }); diff --git a/packages/svelte/types/index.d.ts b/packages/svelte/types/index.d.ts index 803f65181b..b455b92893 100644 --- a/packages/svelte/types/index.d.ts +++ b/packages/svelte/types/index.d.ts @@ -2569,9 +2569,14 @@ declare module 'svelte/reactivity/window' { declare module 'svelte/renderer' { export function createRenderer(renderer: Renderer): Renderer & { - render: (Component: any, options: { + render: >(component: ComponentType> | Component, options: {} extends Props ? { target: TFragment | TElement | TTextNode | TComment; - props?: any; + props?: Props; + context?: Map; + } : { + target: TFragment | TElement | TTextNode | TComment; + props: Props; + context?: Map; }) => () => void; }; export type Renderer = { @@ -2652,6 +2657,206 @@ declare module 'svelte/renderer' { */ removeEventListener: (target: TNode, type: string, handler: any, options?: any) => void; }; + /** + * @deprecated In Svelte 4, components are classes. In Svelte 5, they are functions. + * Use `mount` instead to instantiate components. + * See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) + * for more info. + */ + interface ComponentConstructorOptions< + Props extends Record = Record + > { + target: Element | Document | ShadowRoot; + anchor?: Element; + props?: Props; + context?: Map; + hydrate?: boolean; + intro?: boolean; + recover?: boolean; + sync?: boolean; + idPrefix?: string; + $$inline?: boolean; + transformError?: (error: unknown) => unknown; + } + + /** + * Utility type for ensuring backwards compatibility on a type level that if there's a default slot, add 'children' to the props + */ + 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 + // signatures work (they will always take precedence and make an impossible-to-satisfy children type). + Props extends Record + ? any + : { children?: any } + : {}); + + /** + * This was the base class for Svelte components in Svelte 4. Svelte 5+ components + * are completely different under the hood. For typing, use `Component` instead. + * To instantiate components, use `mount` instead. + * See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more info. + */ + class SvelteComponent< + Props extends Record = Record, + Events extends Record = any, + Slots extends Record = any + > { + /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ + static element?: typeof HTMLElement; + + [prop: string]: any; + /** + * @deprecated This constructor only exists when using the `asClassComponent` compatibility helper, which + * is a stop-gap solution. Migrate towards using `mount` instead. See + * [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) for more info. + */ + constructor(options: ComponentConstructorOptions>); + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + */ + $$prop_def: Props; // Without Properties: unnecessary, causes type bugs + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + */ + $$events_def: Events; + /** + * For type checking capabilities only. + * Does not exist at runtime. + * ### DO NOT USE! + */ + $$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 + * is a stop-gap solution. See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) + * for more info. + */ + $destroy(): void; + + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) + * for more info. + */ + $on>( + type: K, + callback: (e: Events[K]) => void + ): () => void; + + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) + * for more info. + */ + $set(props: Partial): void; + } + + const brand: unique symbol; + type Brand = { [brand]: B }; + type Branded = T & Brand; + + /** + * Internal implementation details that vary between environments + */ + type ComponentInternals = Branded<{}, 'ComponentInternals'>; + + /** + * Can be used to create strongly typed Svelte components. + * + * #### Example: + * + * You have component library on npm called `component-library`, from which + * you export a component called `MyComponent`. For Svelte+TypeScript users, + * you want to provide typings. Therefore you create a `index.d.ts`: + * ```ts + * import type { Component } from 'svelte'; + * export declare const MyComponent: Component<{ foo: string }> {} + * ``` + * Typing this makes it possible for IDEs like VS Code with the Svelte extension + * to provide intellisense and to use the component like this in a Svelte file + * with TypeScript: + * ```svelte + * + * + * ``` + */ + interface Component< + Props extends Record = {}, + Exports extends Record = {}, + Bindings extends keyof Props | '' = string + > { + /** + * @param internal An internal object used by Svelte. Do not use or modify. + * @param props The props passed to the component. + */ + ( + this: void, + internals: ComponentInternals, + props: Props + ): { + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) + * for more info. + */ + $on?(type: string, callback: (e: any) => void): () => void; + /** + * @deprecated This method only exists when using one of the legacy compatibility helpers, which + * is a stop-gap solution. See [migration guide](https://svelte.dev/docs/svelte/v5-migration-guide#Components-are-no-longer-classes) + * for more info. + */ + $set?(props: Partial): void; + } & Exports; + /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ + element?: typeof HTMLElement; + /** Does not exist at runtime, for typing capabilities only. DO NOT USE */ + z_$$bindings?: Bindings; + } + + /** + * @deprecated This type is obsolete when working with the new `Component` type. + * + * @description + * Convenience type to get the type of a Svelte component. Useful for example in combination with + * dynamic components using ``. + * + * Example: + * ```html + * + * + * + * + * ``` + */ + type ComponentType = (new ( + options: ComponentConstructorOptions< + Comp extends SvelteComponent ? Props : Record + > + ) => Comp) & { + /** The custom element version of the component. Only present if compiled with the `customElement` compiler option */ + element?: typeof HTMLElement; + }; export {}; }