use `mount(...)` with `renderer` instead of introducing a new API

pull/18317/head
Rich Harris 2 months ago
parent 516973c320
commit 48665293bd

@ -316,15 +316,52 @@ export interface EventDispatcher<EventMap extends Record<string, any>> {
/**
* Defines the options accepted by the `mount()` function.
*/
export type MountOptions<Props extends Record<string, any> = Record<string, any>> = {
/**
* Target element where the component will be mounted.
*/
target: Document | Element | ShadowRoot;
/**
* Optional node inside `target`. When specified, it is used to render the component immediately before it.
*/
anchor?: Node;
type MountRenderer = {
createFragment(): object;
createElement(name: string): object;
createTextNode(data: string): object;
createComment(data: string): object;
};
type MountRendererTarget<Renderer> = Renderer extends {
createFragment(): infer TFragment;
createElement(name: string): infer TElement;
}
? TFragment | TElement
: never;
type MountRendererAnchor<Renderer> = Renderer extends {
createElement(name: string): infer TElement;
createTextNode(data: string): infer TTextNode;
createComment(data: string): infer TComment;
}
? TElement | TTextNode | TComment
: never;
export type MountOptions<
Props extends Record<string, any> = Record<string, any>,
Renderer = undefined
> = (Renderer extends MountRenderer
? {
/** Custom renderer to use instead of the DOM. */
renderer: Renderer;
/** Target node where the component will be mounted. */
target: MountRendererTarget<Renderer>;
/** Optional node inside `target`. When specified, it is used to render the component immediately before it. */
anchor?: MountRendererAnchor<Renderer>;
}
: {
/**
* Target element where the component will be mounted.
*/
target: Document | Element | ShadowRoot;
/**
* Optional node inside `target`. When specified, it is used to render the component immediately before it.
*/
anchor?: Node;
/** Custom renderer to use instead of the DOM. */
renderer?: undefined;
}) & {
/**
* Allows the specification of events.
* @deprecated Use callback props instead.
@ -345,18 +382,18 @@ export type MountOptions<Props extends Record<string, any> = Record<string, any>
*/
transformError?: (error: unknown) => unknown | Promise<unknown>;
} & ({} extends Props
? {
/**
* Component properties.
*/
props?: Props;
}
: {
/**
* Component properties.
*/
props: Props;
});
? {
/**
* Component properties.
*/
props?: Props;
}
: {
/**
* Component properties.
*/
props: Props;
});
/**
* Represents work that is happening off-screen, such as data being preloaded

@ -1,11 +1,4 @@
/** @import { ComponentContext } from '#client' */
/** @import { Renderer, RendererNodes, DefaultNodes } from "./types.js" */
/** @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';
import { push_renderer } from './state.js';
import { get_parent_node, remove_child } from '../dom/operations.js';
/**
* @template {RendererNodes<object, object, object, object>} [T=DefaultNodes]
@ -13,49 +6,10 @@ import { get_parent_node, remove_child } from '../dom/operations.js';
* @template {object} [TElement=T extends DefaultNodes ? object : T['element']]
* @template {object} [TTextNode=T extends DefaultNodes ? object : T['text']]
* @template {object} [TComment=T extends DefaultNodes ? object : T['comment']]
* @param {Renderer<TFragment, TElement, TTextNode, TComment>} renderer
* @returns {Renderer<TFragment, TElement, TTextNode, TComment> & { render: <Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? { target: TFragment | TElement | TTextNode | TComment, props?: Props, context?: Map<any, any> } : { target: TFragment | TElement | TTextNode | TComment, props: Props, context?: Map<any, any> }) => { component: Exports, unmount: () => void } }}
* @template {Renderer<TFragment, TElement, TTextNode, TComment>} [R=Renderer<TFragment, TElement, TTextNode, TComment>]
* @param {R} renderer
* @returns {R}
*/
export function createRenderer(renderer) {
const compound_renderer = {
...renderer,
/**
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @param {ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>} Component
* @param {{} extends Props ? { target: TFragment | TElement | TTextNode | TComment, props?: Props, context?: Map<any, any> } : { target: TFragment | TElement | TTextNode | TComment, props: Props, context?: Map<any, any> }} options
*/
render(Component, { target, props, context }) {
var pop_renderer = push_renderer(compound_renderer);
try {
/** @type {Exports} */
// @ts-expect-error will be defined because the render effect runs synchronously
var component = undefined;
const unmount = effect_root(() => {
var anchor = compound_renderer.createComment('');
compound_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 = /** @type {Function} */ (Component)(anchor, props ?? {}) || {};
});
pop();
});
return () => {
var parent = get_parent_node(/** @type {*} */ (anchor));
if (parent) remove_child(parent, /** @type {*} */ (anchor));
};
});
return { component, unmount };
} finally {
pop_renderer();
}
}
};
return compound_renderer;
return renderer;
}

@ -1,5 +1,6 @@
/** @import { ComponentContext, Effect, EffectNodes, TemplateNode } from '#client' */
/** @import { Component, ComponentType, SvelteComponent, MountOptions } from '../../index.js' */
/** @import { Renderer } from './custom-renderer/types.js' */
import { DEV } from 'esm-env';
import {
clear_text_content,
@ -32,6 +33,7 @@ import { assign_nodes } from './dom/template.js';
import { is_passive_event } from '../../utils.js';
import { COMMENT_NODE, STATE_SYMBOL, TEXT_CACHE } from './constants.js';
import { boundary } from './dom/blocks/boundary.js';
import { push_renderer } from './custom-renderer/state.js';
/**
* This is normally true block effects should run their intro transitions
@ -66,8 +68,9 @@ export function set_text(text, value) {
*
* @template {Record<string, any>} Props
* @template {Record<string, any>} Exports
* @template [Renderer=undefined]
* @param {ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>} component
* @param {MountOptions<Props>} options
* @param {MountOptions<Props, Renderer>} options
* @returns {Exports}
*/
export function mount(component, options) {
@ -160,135 +163,150 @@ const listeners = new Map();
/**
* @template {Record<string, any>} Exports
* @param {ComponentType<SvelteComponent<any>> | Component<any>} Component
* @param {MountOptions} options
* @param {MountOptions<any, any> & { renderer?: Renderer }} options
* @returns {Exports}
*/
function _mount(
Component,
{ target, anchor, props = {}, events, context, intro = true, transformError }
{ target, anchor, props = {}, events, context, intro = true, transformError, renderer }
) {
var pop_renderer = renderer ? push_renderer(renderer) : null;
/** @type {Exports} */
// @ts-expect-error will be defined because the render effect runs synchronously
var component = undefined;
var unmount = component_root(() => {
var anchor_node = anchor ?? /** @type {Text} */ (append_child(target, create_text()));
boundary(
/** @type {TemplateNode} */ (anchor_node),
{
pending: () => {}
},
(anchor_node) => {
push({});
var ctx = /** @type {ComponentContext} */ (component_context);
if (context) ctx.c = context;
if (events) {
// We can't spread the object or else we'd lose the state proxy stuff, if it is one
/** @type {any} */ (props).$$events = events;
}
if (hydrating) {
assign_nodes(/** @type {TemplateNode} */ (anchor_node), null);
}
should_intro = intro;
// @ts-expect-error the public typings are not what the actual function looks like
component = Component(anchor_node, props) || {};
should_intro = true;
if (hydrating) {
/** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = hydrate_node;
if (
hydrate_node === null ||
node_type(hydrate_node) !== COMMENT_NODE ||
get_node_value(hydrate_node) !== HYDRATION_END
) {
w.hydration_mismatch();
throw HYDRATION_ERROR;
try {
var unmount = component_root(() => {
var anchor_node =
anchor ?? /** @type {Text} */ (append_child(/** @type {Node} */ (target), create_text()));
boundary(
/** @type {TemplateNode} */ (anchor_node),
{
pending: () => {}
},
(anchor_node) => {
push({});
var ctx = /** @type {ComponentContext} */ (component_context);
if (context) ctx.c = context;
if (events) {
// We can't spread the object or else we'd lose the state proxy stuff, if it is one
/** @type {any} */ (props).$$events = events;
}
}
pop();
},
transformError
);
// Setup event delegation _after_ component is mounted - if an error would happen during mount, it would otherwise not be cleaned up
/** @type {Set<string>} */
var registered_events = new Set();
/** @param {Array<string>} events */
var event_handle = (events) => {
for (var i = 0; i < events.length; i++) {
var event_name = events[i];
if (registered_events.has(event_name)) continue;
registered_events.add(event_name);
var passive = is_passive_event(event_name);
// Add the event listener to both the container and the document.
// The container listener ensures we catch events from within in case
// the outer content stops propagation of the event.
//
// The document listener ensures we catch events that originate from elements that were
// manually moved outside of the container (e.g. via manual portals).
for (const node of [target, document]) {
var counts = listeners.get(node);
if (counts === undefined) {
counts = new Map();
listeners.set(node, counts);
if (hydrating) {
assign_nodes(/** @type {TemplateNode} */ (anchor_node), null);
}
var count = counts.get(event_name);
if (count === undefined) {
add_event_listener(node, event_name, handle_event_propagation, { passive });
counts.set(event_name, 1);
} else {
counts.set(event_name, count + 1);
should_intro = intro;
// @ts-expect-error the public typings are not what the actual function looks like
component = Component(anchor_node, props) || {};
should_intro = true;
if (hydrating) {
/** @type {Effect & { nodes: EffectNodes }} */ (active_effect).nodes.end = hydrate_node;
if (
hydrate_node === null ||
node_type(hydrate_node) !== COMMENT_NODE ||
get_node_value(hydrate_node) !== HYDRATION_END
) {
w.hydration_mismatch();
throw HYDRATION_ERROR;
}
}
}
}
};
event_handle(array_from(all_registered_events));
root_event_handles.add(event_handle);
return () => {
for (var event_name of registered_events) {
for (const node of [target, document]) {
var counts = /** @type {Map<string, number>} */ (listeners.get(node));
var count = /** @type {number} */ (counts.get(event_name));
pop();
},
transformError
);
// Setup event delegation _after_ component is mounted - if an error would happen during mount, it would otherwise not be cleaned up
/** @type {Set<string>} */
var registered_events = new Set();
/** @type {null | ((events: Array<string>) => void)} */
var event_handle = null;
if (!renderer) {
var dom_target = /** @type {EventTarget} */ (target);
/** @param {Array<string>} events */
event_handle = (events) => {
for (var i = 0; i < events.length; i++) {
var event_name = events[i];
if (registered_events.has(event_name)) continue;
registered_events.add(event_name);
var passive = is_passive_event(event_name);
// Add the event listener to both the container and the document.
// The container listener ensures we catch events from within in case
// the outer content stops propagation of the event.
//
// The document listener ensures we catch events that originate from elements that were
// manually moved outside of the container (e.g. via manual portals).
for (const node of [dom_target, document]) {
var counts = listeners.get(node);
if (counts === undefined) {
counts = new Map();
listeners.set(node, counts);
}
var count = counts.get(event_name);
if (count === undefined) {
add_event_listener(node, event_name, handle_event_propagation, { passive });
counts.set(event_name, 1);
} else {
counts.set(event_name, count + 1);
}
}
}
};
if (--count == 0) {
remove_event_listener(node, event_name, handle_event_propagation);
counts.delete(event_name);
event_handle(array_from(all_registered_events));
root_event_handles.add(event_handle);
}
if (counts.size === 0) {
listeners.delete(node);
return () => {
if (event_handle !== null) {
for (var event_name of registered_events) {
for (const node of [/** @type {EventTarget} */ (target), document]) {
var counts = /** @type {Map<string, number>} */ (listeners.get(node));
var count = /** @type {number} */ (counts.get(event_name));
if (--count == 0) {
remove_event_listener(node, event_name, handle_event_propagation);
counts.delete(event_name);
if (counts.size === 0) {
listeners.delete(node);
}
} else {
counts.set(event_name, count);
}
}
} else {
counts.set(event_name, count);
}
}
}
root_event_handles.delete(event_handle);
root_event_handles.delete(event_handle);
}
if (anchor_node !== anchor) {
var parent = get_parent_node(anchor_node);
if (parent) remove_child(parent, /** @type {ChildNode} */ (anchor_node));
}
};
});
if (anchor_node !== anchor) {
var parent = get_parent_node(/** @type {Node} */ (anchor_node));
if (parent) remove_child(parent, /** @type {ChildNode} */ (anchor_node));
}
};
});
mounted_components.set(component, unmount);
return component;
mounted_components.set(component, unmount);
return component;
} finally {
pop_renderer?.();
}
}
/**

@ -113,7 +113,7 @@ class Svelte4Component {
}
);
this.#instance = (options.hydrate ? hydrate : mount)(options.component, {
const render_options = {
target: options.target,
anchor: options.anchor,
props,
@ -121,7 +121,11 @@ class Svelte4Component {
intro: options.intro ?? false,
recover: options.recover,
transformError: options.transformError
});
};
this.#instance = options.hydrate
? hydrate(options.component, render_options)
: mount(options.component, render_options);
// We don't flushSync for custom element wrappers or if the user doesn't want it,
// or if we're in async mode since `flushSync()` will fail

@ -6,6 +6,7 @@ import { compile_directory } from '../helpers.js';
import { suite_with_variants, type BaseTest } from '../suite.js';
import type { CompileOptions } from '#compiler';
import renderer, { create_root, serialize, dispatch_event, dom_elements } from './renderer.js';
import { mount, unmount as unmount_component } from '../../src/index-client.js';
import { writeFile } from 'node:fs/promises';
import { globSync } from 'tinyglobby';
@ -150,17 +151,15 @@ async function run_test(cwd: string, config: CustomRendererTest, compile_options
const mod = await import(`${cwd}/_output/client/main.svelte.js`);
const target = create_root();
let unmount: (() => void) | undefined;
let component: Record<string, any> | undefined;
try {
dom_elements.length = 0;
const result = renderer.render(mod.default, {
component = mount(mod.default, {
renderer,
target,
props: config.props ?? {},
context: config.context
});
unmount = result.unmount;
component = result.component;
} catch (err) {
if (config.error) {
assert.include((err as Error).message, config.error);
@ -212,7 +211,9 @@ async function run_test(cwd: string, config: CustomRendererTest, compile_options
assert.fail('Expected a runtime error');
}
} finally {
unmount?.();
if (component) {
await unmount_component(component);
}
if (config.warnings) {
assert.deepEqual(warnings, config.warnings);

@ -470,14 +470,16 @@ async function run_test_variant(
};
} else {
run_hydratables_init();
const render = variant === 'hydrate' ? hydrate : mount;
instance = render(mod.default, {
const options = {
target,
props,
intro: config.intro,
recover: config.recover ?? false,
transformError: config.transformError
});
};
instance =
variant === 'hydrate' ? hydrate(mod.default, options) : mount(mod.default, options);
}
} else {
run_hydratables_init();

@ -9,6 +9,7 @@ import {
type Component,
type ComponentInternals
} from 'svelte';
import { createRenderer } from 'svelte/renderer';
import { render } from 'svelte/server';
SvelteComponent.element === HTMLElement;
@ -291,6 +292,52 @@ mount(
// if component receives no args, props can be omitted
mount(null as any as Component<{}>, { target: null as any });
type CustomFragment = { kind: 'fragment' };
type CustomElement = { kind: 'element' };
type CustomText = { kind: 'text' };
type CustomComment = { kind: 'comment' };
const custom_renderer = createRenderer<{
fragment: CustomFragment;
element: CustomElement;
text: CustomText;
comment: CustomComment;
}>(null as any);
mount(functionComponent, {
renderer: custom_renderer,
target: null as any as CustomFragment,
anchor: null as any as CustomComment,
props: {
binding: true,
readonly: 'foo'
}
});
// @ts-expect-error target must match the renderer
mount(functionComponent, {
renderer: custom_renderer,
target: null as any as HTMLElement,
props: {
binding: true,
readonly: 'foo'
}
});
// @ts-expect-error anchor must match the renderer
mount(functionComponent, {
renderer: custom_renderer,
target: null as any as CustomElement,
anchor: null as any as CustomFragment,
props: {
binding: true,
readonly: 'foo'
}
});
// @ts-expect-error createRenderer returns the renderer directly
custom_renderer.render;
createRenderer({ ...custom_renderer, extra: true }).extra === true;
hydrate(functionComponent, {
target: null as any as Document | Element | ShadowRoot,
props: {

@ -315,15 +315,52 @@ declare module 'svelte' {
/**
* Defines the options accepted by the `mount()` function.
*/
export type MountOptions<Props extends Record<string, any> = Record<string, any>> = {
/**
* Target element where the component will be mounted.
*/
target: Document | Element | ShadowRoot;
/**
* Optional node inside `target`. When specified, it is used to render the component immediately before it.
*/
anchor?: Node;
type MountRenderer = {
createFragment(): object;
createElement(name: string): object;
createTextNode(data: string): object;
createComment(data: string): object;
};
type MountRendererTarget<Renderer> = Renderer extends {
createFragment(): infer TFragment;
createElement(name: string): infer TElement;
}
? TFragment | TElement
: never;
type MountRendererAnchor<Renderer> = Renderer extends {
createElement(name: string): infer TElement;
createTextNode(data: string): infer TTextNode;
createComment(data: string): infer TComment;
}
? TElement | TTextNode | TComment
: never;
export type MountOptions<
Props extends Record<string, any> = Record<string, any>,
Renderer = undefined
> = (Renderer extends MountRenderer
? {
/** Custom renderer to use instead of the DOM. */
renderer: Renderer;
/** Target node where the component will be mounted. */
target: MountRendererTarget<Renderer>;
/** Optional node inside `target`. When specified, it is used to render the component immediately before it. */
anchor?: MountRendererAnchor<Renderer>;
}
: {
/**
* Target element where the component will be mounted.
*/
target: Document | Element | ShadowRoot;
/**
* Optional node inside `target`. When specified, it is used to render the component immediately before it.
*/
anchor?: Node;
/** Custom renderer to use instead of the DOM. */
renderer?: undefined;
}) & {
/**
* Allows the specification of events.
* @deprecated Use callback props instead.
@ -344,18 +381,18 @@ declare module 'svelte' {
*/
transformError?: (error: unknown) => unknown | Promise<unknown>;
} & ({} extends Props
? {
/**
* Component properties.
*/
props?: Props;
}
: {
/**
* Component properties.
*/
props: Props;
});
? {
/**
* Component properties.
*/
props?: Props;
}
: {
/**
* Component properties.
*/
props: Props;
});
/**
* Represents work that is happening off-screen, such as data being preloaded
@ -536,7 +573,7 @@ declare module 'svelte' {
* Transitions will play during the initial render unless the `intro` option is set to `false`.
*
* */
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props>): Exports;
export function mount<Props extends Record<string, any>, Exports extends Record<string, any>, Renderer = undefined>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: MountOptions<Props, Renderer>): Exports;
/**
* Hydrates a component on the given target and returns the exports and potentially the props (if compiled with `accessors: true`) of the component
*
@ -2568,20 +2605,7 @@ declare module 'svelte/reactivity/window' {
}
declare module 'svelte/renderer' {
export function createRenderer<T extends RendererNodes<object, object, object, object> = DefaultNodes, TFragment extends object = T extends DefaultNodes ? object : T["fragment"], TElement extends object = T extends DefaultNodes ? object : T["element"], TTextNode extends object = T extends DefaultNodes ? object : T["text"], TComment extends object = T extends DefaultNodes ? object : T["comment"]>(renderer: Renderer<TFragment, TElement, TTextNode, TComment>): Renderer<TFragment, TElement, TTextNode, TComment> & {
render: <Props extends Record<string, any>, Exports extends Record<string, any>>(component: ComponentType<SvelteComponent<Props>> | Component<Props, Exports, any>, options: {} extends Props ? {
target: TFragment | TElement | TTextNode | TComment;
props?: Props;
context?: Map<any, any>;
} : {
target: TFragment | TElement | TTextNode | TComment;
props: Props;
context?: Map<any, any>;
}) => {
component: Exports;
unmount: () => void;
};
};
export function createRenderer<T extends RendererNodes<object, object, object, object> = DefaultNodes, TFragment extends object = T extends DefaultNodes ? object : T["fragment"], TElement extends object = T extends DefaultNodes ? object : T["element"], TTextNode extends object = T extends DefaultNodes ? object : T["text"], TComment extends object = T extends DefaultNodes ? object : T["comment"], R extends Renderer<TFragment, TElement, TTextNode, TComment> = Renderer<TFragment, TElement, TTextNode, TComment>>(renderer: R): R;
type Renderer<
TFragment extends object = object,
TElement extends object = object,
@ -2690,206 +2714,6 @@ declare module 'svelte/renderer' {
// for the other arguments (TFragment, TElement, TTextNode, TComment)
type UnsetObject = object & { readonly __unset: unique symbol };
type DefaultNodes = RendererNodes<UnsetObject, UnsetObject, UnsetObject, UnsetObject>;
/**
* @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<string, any> = Record<string, any>
> {
target: Element | Document | ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map<any, any>;
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> = 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<string, never>
? 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<string, any> = Record<string, any>,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = 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<Properties<Props, Slots>>);
/**
* 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<K extends Extract<keyof Events, string>>(
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<Props>): void;
}
const brand: unique symbol;
type Brand<B> = { [brand]: B };
type Branded<T, B> = T & Brand<B>;
/**
* 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
* <script lang="ts">
* import { MyComponent } from "component-library";
* </script>
* <MyComponent foo={'bar'} />
* ```
*/
interface Component<
Props extends Record<string, any> = {},
Exports extends Record<string, any> = {},
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<Props>): 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 `<svelte:component>`.
*
* Example:
* ```html
* <script lang="ts">
* import type { ComponentType, SvelteComponent } from 'svelte';
* import Component1 from './Component1.svelte';
* import Component2 from './Component2.svelte';
*
* const component: ComponentType = someLogic() ? Component1 : Component2;
* const componentOfCertainSubType: ComponentType<SvelteComponent<{ needsThisProp: string }>> = someLogic() ? Component1 : Component2;
* </script>
*
* <svelte:component this={component} />
* <svelte:component this={componentOfCertainSubType} needsThisProp="hello" />
* ```
*/
type ComponentType<Comp extends SvelteComponent = SvelteComponent> = (new (
options: ComponentConstructorOptions<
Comp extends SvelteComponent<infer Props> ? Props : Record<string, any>
>
) => Comp) & {
/** The custom element version of the component. Only present if compiled with the `customElement` compiler option */
element?: typeof HTMLElement;
};
export {};
}

Loading…
Cancel
Save