feat: Reinitialize frontend with SvelteKit and TypeScript

- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
This commit is contained in:
2026-02-17 16:19:59 -05:00
parent 54df6018f5
commit de2d83092e
28274 changed files with 3816354 additions and 90 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Ben Monro <ben.monro@gmail.com> (https://github.com/benmonro)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,184 @@
# @testing-library/svelte-core
Do you want to build your own Svelte testing library? You may want to use our
rendering core, which abstracts away differences in Svelte versions to provide a
simple API to render Svelte components into the document and clean them up
afterwards.
## Table of Contents
- [Example Usage](#example-usage)
- [API](#api)
- [`render`](#render)
- [`setup`](#setup)
- [`mount`](#mount)
- [`cleanup`](#cleanup)
- [`addCleanupTask`](#addcleanuptask)
- [`removeCleanupTask`](#removecleanuptask)
- [Utility types](#utility-types)
## Example Usage
```ts
import { beforeEach } from 'vitest'
import * as SvelteCore from '@testing-library/svelte-core'
import type {
Component,
Exports,
Rerender,
} from '@testing-library/svelte-core/types'
import { bindQueries, type Queries } from './bring-your-own-queries.js'
beforeEach(() => {
SvelteCore.cleanup()
})
export interface RenderResult<C extends Component> extends Queries {
container: HTMLElement
component: Exports<C>
rerender: Rerender<C>
unmount: () => void
}
export const render = <C extends SvelteCore.Component>(
Component: SvelteCore.ComponentImport<C>,
options: SvelteCore.ComponentOptions<C>
): RenderResult<C> => {
const { baseElement, component, container, rerender, unmount } =
SvelteCore.render(Component, options)
const queries = bindQueries(baseElement)
return { component, container, rerender, unmount, ...queries }
}
```
## API
### `render`
Set up the document and mount a component into that document.
```ts
const { baseElement, container, component, rerender, unmount } = render(
Component,
componentOptions,
setupOptions
)
```
| Argument | Type | Description |
| ------------------ | ------------------------------------------------------- | --------------------------------------------- |
| `Component` | [Svelte component][svelte-component-docs] | An imported Svelte component |
| `componentOptions` | `Props` or partial [`mount` options][svelte-mount-docs] | Options for how the component will be mounted |
| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` |
| Result | Type | Description | Default |
| ------------- | ------------------------------------------ | ---------------------------------------- | ----------------------------------- |
| `baseElement` | `HTMLElement` | The base element | `document.body` |
| `container` | `HTMLElement` | The component's immediate parent element | `<div>` appended to `document.body` |
| `component` | [component exports][svelte-mount-docs] | The component's exports from `mount` | N/A |
| `rerender` | `(props: Partial<Props>) => Promise<void>` | Update the component's props | N/A |
| `unmount` | `() => void` | Unmount the component from the document | N/A |
> \[!TIP]
> Calling `render` is equivalent to calling [`setup`](#setup) followed by [`mount`](#mount)
>
> ```ts
> const { baseElement, container, mountOptions } = setup(
> componentOptions,
> setupOptions
> )
> const { component, rerender, unmount } = mount(Component, mountOptions)
> ```
[svelte-component-docs]: https://svelte.dev/docs/svelte-components
[svelte-mount-docs]: https://svelte.dev/docs/svelte/imperative-component-api#mount
### `setup`
Validate options and prepare document elements for rendering.
```ts
const { baseElement, container, mountOptions } = setup(
componentOptions,
setupOptions
)
```
| Argument | Type | Description |
| ------------------ | ------------------------------------------------------- | --------------------------------------------- |
| `componentOptions` | `Props` or partial [`mount` options][svelte-mount-docs] | Options for how the component will be mounted |
| `setupOptions` | `{ baseElement?: HTMLElement }` | Optionally override `baseElement` |
| Result | Type | Description | Default |
| -------------- | ------------------------------------ | ---------------------------------------- | ----------------------------------- |
| `baseElement` | `HTMLElement` | The base element | `document.body` |
| `container` | `HTMLElement` | The component's immediate parent element | `<div>` appended to `document.body` |
| `mountOptions` | [`mount` options][svelte-mount-docs] | Validated options to pass to `mount` | `{ target, props: {} }` |
### `mount`
Mount a Svelte component into the document.
```ts
const { component, unmount, rerender } = mount(Component, mountOptions)
```
| Argument | Type | Description |
| -------------- | ----------------------------------------- | -------------------------------------------- |
| `Component` | [Svelte component][svelte-component-docs] | An imported Svelte component |
| `mountOptions` | [component options][svelte-mount-docs] | Options to pass to Svelte's `mount` function |
| Result | Type | Description |
| ----------- | ------------------------------------------ | --------------------------------------- |
| `component` | [component exports][svelte-mount-docs] | The component's exports from `mount` |
| `unmount` | `() => void` | Unmount the component from the document |
| `rerender` | `(props: Partial<Props>) => Promise<void>` | Update the component's props |
### `cleanup`
Cleanup rendered components and added elements. Call this when your tests are
over.
```ts
cleanup()
```
### `addCleanupTask`
Add a custom cleanup task to be called with `cleanup()`
```ts
addCleanupTask(() => {
// ...reset something
})
```
### `removeCleanupTask`
Remove a cleanup task from `cleanup()`. Useful if a cleanup task can only be run
once and may be run outside of `cleanup`
```ts
const customCleanup = () => {
// ...reset something
}
addCleanupTask(customCleanup)
const manuallyCleanupEarly = () => {
customCleanup()
removeCleanupTask(customCleanup)
}
```
### Utility types
This module exports various utility types from
`@testing-library/svelte-core/types`. They adapt to whatever Svelte version is
installed, and can be used to get type signatures for imported components,
props, exports, etc.
See [`./types.d.ts`](./types.d.ts) for the full list of available types.

View File

@@ -0,0 +1,15 @@
/**
* Register later cleanup task
*
* @param {() => void} onCleanup
*/
export function addCleanupTask(onCleanup: () => void): () => void;
/** Clean up all components and elements added to the document. */
export function cleanup(): void;
/**
* Remove a cleanup task without running it.
*
* @param {() => void} onCleanup
*/
export function removeCleanupTask(onCleanup: () => void): void;
//# sourceMappingURL=cleanup.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"cleanup.d.ts","sourceRoot":"","sources":["../src/cleanup.js"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,0CAFW,MAAM,IAAI,SAAJ,IAAI,CAKpB;AAWD,kEAAkE;AAClE,gCAMC;AAhBD;;;;GAIG;AACH,6CAFW,MAAM,IAAI,QAIpB"}

View File

@@ -0,0 +1,5 @@
export * from "./cleanup.js";
export * from "./mount.js";
export * from "./render.js";
export * from "./setup.js";
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""}

View File

@@ -0,0 +1,10 @@
/**
* Render a Svelte component into the document.
*
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentImport<C>} Component
* @param {import('../types.js').MountOptions<C>} options
* @returns {import('../types.js').MountResult<C>}
*/
export function mount<C extends import("../types.js").Component>(Component: import("../types.js").ComponentImport<C>, options: import("../types.js").MountOptions<C>): import("../types.js").MountResult<C>;
//# sourceMappingURL=mount.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../src/mount.js"],"names":[],"mappings":"AA2EA;;;;;;;GAOG;AACH,sBAL+C,CAAC,SAAnC,OAAQ,aAAa,EAAE,SAAU,aACnC,OAAO,aAAa,EAAE,eAAe,CAAC,CAAC,CAAC,WACxC,OAAO,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,GACnC,OAAO,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,CAwBhD"}

View File

@@ -0,0 +1,12 @@
/**
* Create a shallowly reactive props object.
*
* This allows us to update props on `rerender`
* without turing `props` into a deep set of Proxy objects
*
* @template {Record<string, unknown>} Props
* @param {Props} initialProps
* @returns {[Props, (nextProps: Partial<Props>) => void]}
*/
export function createProps<Props extends Record<string, unknown>>(initialProps?: Props): [Props, (nextProps: Partial<Props>) => void];
//# sourceMappingURL=props.svelte.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"props.svelte.d.ts","sourceRoot":"","sources":["../src/props.svelte.js"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,4BAJuC,KAAK,SAA9B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAE,iBAC3B,KAAK,GACH,CAAC,KAAK,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CA0BxD"}

View File

@@ -0,0 +1,12 @@
/**
* Render a component into the document.
*
* @template {import('../types.js').Component} C
*
* @param {import('../types.js').ComponentImport<C>} Component - The component to render.
* @param {import('../types.js').ComponentOptions<C>} componentOptions - Customize how Svelte renders the component.
* @param {import('../types.js').SetupOptions} setupOptions - Customize how the document is set up.
* @returns {import('../types.js').RenderResult<C>} The rendered component.
*/
export function render<C extends import("../types.js").Component>(Component: import("../types.js").ComponentImport<C>, componentOptions: import("../types.js").ComponentOptions<C>, setupOptions?: import("../types.js").SetupOptions): import("../types.js").RenderResult<C>;
//# sourceMappingURL=render.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,uBAP+C,CAAC,SAAnC,OAAQ,aAAa,EAAE,SAAU,aAEnC,OAAO,aAAa,EAAE,eAAe,CAAC,CAAC,CAAC,oBACxC,OAAO,aAAa,EAAE,gBAAgB,CAAC,CAAC,CAAC,iBACzC,OAAO,aAAa,EAAE,YAAY,GAChC,OAAO,aAAa,EAAE,YAAY,CAAC,CAAC,CAAC,CAOjD"}

View File

@@ -0,0 +1,13 @@
/**
* Set up the document to render a component.
*
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentOptions<C>} componentOptions - props or mount options
* @param {import('../types.js').SetupOptions} setupOptions - base element of the document to bind any queries
* @returns {import('../types.js').SetupResult<C>}
*/
export function setup<C extends import("../types.js").Component>(componentOptions: import("../types.js").ComponentOptions<C>, setupOptions?: import("../types.js").SetupOptions): import("../types.js").SetupResult<C>;
export class UnknownSvelteOptionsError extends TypeError {
constructor(unknownOptions: any);
}
//# sourceMappingURL=setup.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.js"],"names":[],"mappings":"AAsDA;;;;;;;GAOG;AACH,sBAL+C,CAAC,SAAnC,OAAQ,aAAa,EAAE,SAAU,oBACnC,OAAO,aAAa,EAAE,gBAAgB,CAAC,CAAC,CAAC,iBACzC,OAAO,aAAa,EAAE,YAAY,GAChC,OAAO,aAAa,EAAE,WAAW,CAAC,CAAC,CAAC,CAuBhD;AA1ED;IACE,iCAaC;CACF"}

View File

@@ -0,0 +1,3 @@
/** Whether we're using Svelte >= 5. */
export const IS_MODERN_SVELTE: boolean;
//# sourceMappingURL=svelte-version.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"svelte-version.d.ts","sourceRoot":"","sources":["../src/svelte-version.js"],"names":[],"mappings":"AAGA,uCAAuC;AACvC,uCAA2D"}

View File

@@ -0,0 +1,48 @@
{
"name": "@testing-library/svelte-core",
"version": "1.0.0",
"description": "Core rendering and cleanup logic for Svelte testing utilities.",
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./src/index.js"
},
"./types": {
"types": "./types.d.ts"
}
},
"type": "module",
"license": "MIT",
"homepage": "https://github.com/testing-library/svelte-testing-library#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/testing-library/svelte-testing-library.git",
"directory": "packages/svelte-core"
},
"bugs": {
"url": "https://github.com/testing-library/svelte-testing-library/issues"
},
"engines": {
"node": ">=16"
},
"keywords": [
"tdd",
"svelte",
"ui",
"unit",
"component",
"functional"
],
"files": [
"dist",
"src",
"types.d.ts"
],
"peerDependencies": {
"svelte": "^3 || ^4 || ^5 || ^5.0.0-next.0"
},
"publishConfig": {
"access": "public",
"provenance": true
}
}

View File

@@ -0,0 +1,32 @@
/** @type {Set<() => void>} */
const cleanupTasks = new Set()
/**
* Register later cleanup task
*
* @param {() => void} onCleanup
*/
const addCleanupTask = (onCleanup) => {
cleanupTasks.add(onCleanup)
return onCleanup
}
/**
* Remove a cleanup task without running it.
*
* @param {() => void} onCleanup
*/
const removeCleanupTask = (onCleanup) => {
cleanupTasks.delete(onCleanup)
}
/** Clean up all components and elements added to the document. */
const cleanup = () => {
for (const handleCleanup of cleanupTasks.values()) {
handleCleanup()
}
cleanupTasks.clear()
}
export { addCleanupTask, cleanup, removeCleanupTask }

View File

@@ -0,0 +1,11 @@
/**
* Rendering core for svelte-testing-library.
*
* Defines how components are added to and removed from the DOM.
* Will switch to legacy, class-based mounting logic
* if it looks like we're in a Svelte <= 4 environment.
*/
export * from './cleanup.js'
export * from './mount.js'
export * from './render.js'
export * from './setup.js'

View File

@@ -0,0 +1,108 @@
/**
* Component rendering core, with support for Svelte 3, 4, and 5
*/
import * as Svelte from 'svelte'
import { addCleanupTask, removeCleanupTask } from './cleanup.js'
import { createProps } from './props.svelte.js'
import { IS_MODERN_SVELTE } from './svelte-version.js'
/**
* Mount a modern Svelte 5 component into the DOM.
*
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentType<C>} Component
* @param {import('../types.js').MountOptions<C>} options
* @returns {import('../types.js').MountResult<C>}
*/
const mountModern = (Component, options) => {
const [props, updateProps] = createProps(options.props)
const component = Svelte.mount(Component, { ...options, props })
/** Remove the component from the DOM. */
const unmount = () => {
Svelte.flushSync(() => Svelte.unmount(component))
removeCleanupTask(unmount)
}
/** Update the component's props. */
const rerender = (nextProps) => {
Svelte.flushSync(() => updateProps(nextProps))
}
addCleanupTask(unmount)
Svelte.flushSync()
return { component, unmount, rerender }
}
/**
* Mount a legacy Svelte 3 or 4 component into the DOM.
*
* @template {import('../types.js').LegacyComponent} C
* @param {import('../types.js').ComponentType<C>} Component
* @param {import('../types.js').MountOptions<C>} options
* @returns {import('../types.js').MountResult<C>}
*/
const mountLegacy = (Component, options) => {
const component = new Component(options)
/** Remove the component from the DOM. */
const unmount = () => {
component.$destroy()
removeCleanupTask(unmount)
}
/** Update the component's props. */
const rerender = (nextProps) => {
component.$set(nextProps)
}
// This `$$.on_destroy` listener is included for strict backwards compatibility
// with previous versions of `@testing-library/svelte`.
// It's unnecessary and will be removed in a future major version.
component.$$.on_destroy.push(() => {
removeCleanupTask(unmount)
})
addCleanupTask(unmount)
return { component, unmount, rerender }
}
/** The mount method in use. */
const mountComponent = IS_MODERN_SVELTE ? mountModern : mountLegacy
/**
* Render a Svelte component into the document.
*
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentImport<C>} Component
* @param {import('../types.js').MountOptions<C>} options
* @returns {import('../types.js').MountResult<C>}
*/
const mount = (Component, options) => {
const { component, unmount, rerender } = mountComponent(
'default' in Component ? Component.default : Component,
options
)
return {
component,
unmount,
rerender: async (props) => {
if ('props' in props) {
console.warn(
'rerender({ props: { ... } }) deprecated, use rerender({ ... }) instead'
)
props = props.props
}
rerender(props)
// Await the next tick for Svelte 3/4, which cannot flush changes synchronously
await Svelte.tick()
},
}
}
export { mount }

View File

@@ -0,0 +1,37 @@
/**
* Create a shallowly reactive props object.
*
* This allows us to update props on `rerender`
* without turing `props` into a deep set of Proxy objects
*
* @template {Record<string, unknown>} Props
* @param {Props} initialProps
* @returns {[Props, (nextProps: Partial<Props>) => void]}
*/
const createProps = (initialProps = {}) => {
let currentProps = $state.raw(initialProps)
const props = new Proxy(initialProps, {
get(_, key) {
return currentProps[key]
},
set(_, key, value) {
currentProps[key] = value
return true
},
has(_, key) {
return Reflect.has(currentProps, key)
},
ownKeys() {
return Reflect.ownKeys(currentProps)
},
})
const update = (nextProps) => {
currentProps = { ...currentProps, ...nextProps }
}
return [props, update]
}
export { createProps }

View File

@@ -0,0 +1,21 @@
import { mount } from './mount.js'
import { setup } from './setup.js'
/**
* Render a component into the document.
*
* @template {import('../types.js').Component} C
*
* @param {import('../types.js').ComponentImport<C>} Component - The component to render.
* @param {import('../types.js').ComponentOptions<C>} componentOptions - Customize how Svelte renders the component.
* @param {import('../types.js').SetupOptions} setupOptions - Customize how the document is set up.
* @returns {import('../types.js').RenderResult<C>} The rendered component.
*/
const render = (Component, componentOptions, setupOptions = {}) => {
const { mountOptions, ...setupResult } = setup(componentOptions, setupOptions)
const mountResult = mount(Component, mountOptions)
return { ...setupResult, ...mountResult }
}
export { render }

View File

@@ -0,0 +1,86 @@
/** Set up the document to render a component. */
import { addCleanupTask } from './cleanup.js'
import { IS_MODERN_SVELTE } from './svelte-version.js'
/** Allowed options to the `mount` call or legacy component constructor. */
const ALLOWED_MOUNT_OPTIONS = IS_MODERN_SVELTE
? ['target', 'anchor', 'props', 'events', 'context', 'intro']
: ['target', 'accessors', 'anchor', 'props', 'hydrate', 'intro', 'context']
class UnknownSvelteOptionsError extends TypeError {
constructor(unknownOptions) {
super(`Unknown options.
Unknown: [ ${unknownOptions.join(', ')} ]
Allowed: [ ${ALLOWED_MOUNT_OPTIONS.join(', ')} ]
To pass both Svelte options and props to a component,
or to use props that share a name with a Svelte option,
you must place all your props under the \`props\` key:
render(Component, { props: { /** props here **/ } })
`)
this.name = 'UnknownSvelteOptionsError'
}
}
/**
* Validate a component's mount options.
*
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentOptions<C>} options - props or mount options
* @returns {Partial<import('../types.js').MountOptions<C>>}
*/
const validateOptions = (options) => {
const isProps = !Object.keys(options).some((option) =>
ALLOWED_MOUNT_OPTIONS.includes(option)
)
if (isProps) {
return { props: options }
}
// Check if any props and Svelte options were accidentally mixed.
const unknownOptions = Object.keys(options).filter(
(option) => !ALLOWED_MOUNT_OPTIONS.includes(option)
)
if (unknownOptions.length > 0) {
throw new UnknownSvelteOptionsError(unknownOptions)
}
return options
}
/**
* Set up the document to render a component.
*
* @template {import('../types.js').Component} C
* @param {import('../types.js').ComponentOptions<C>} componentOptions - props or mount options
* @param {import('../types.js').SetupOptions} setupOptions - base element of the document to bind any queries
* @returns {import('../types.js').SetupResult<C>}
*/
const setup = (componentOptions, setupOptions = {}) => {
const mountOptions = validateOptions(componentOptions)
const baseElement =
setupOptions.baseElement ?? mountOptions.target ?? document.body
const container =
mountOptions.target ??
baseElement.appendChild(document.createElement('div'))
addCleanupTask(() => {
if (container.parentNode === document.body) {
container.remove()
}
})
return {
baseElement,
container,
mountOptions: { ...mountOptions, target: container },
}
}
export { setup, UnknownSvelteOptionsError }

View File

@@ -0,0 +1,7 @@
/** Detect which version of Svelte we're using */
import * as Svelte from 'svelte'
/** Whether we're using Svelte >= 5. */
const IS_MODERN_SVELTE = typeof Svelte.mount === 'function'
export { IS_MODERN_SVELTE }

View File

@@ -0,0 +1,131 @@
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-deprecated */
/**
* Component and utility types.
*
* Supports components from Svelte 3, 4, and 5.
*/
import type {
Component as ModernComponent,
ComponentConstructorOptions as LegacyConstructorOptions,
ComponentProps,
EventDispatcher,
mount,
SvelteComponent as Svelte4LegacyComponent,
SvelteComponentTyped as Svelte3LegacyComponent,
} from 'svelte'
type IS_MODERN_SVELTE = ModernComponent extends (...args: any[]) => any
? true
: false
type IS_LEGACY_SVELTE_4 =
EventDispatcher<any> extends (...args: any[]) => any ? true : false
/** A compiled, imported Svelte component. */
export type Component<
P extends Record<string, any> = any,
E extends Record<string, any> = any,
> = IS_MODERN_SVELTE extends true
? ModernComponent<P, E> | LegacyComponent<P>
: LegacyComponent<P>
/** A compiled, imported Svelte 3 or 4 component. */
export type LegacyComponent<P extends Record<string, any> = any> =
IS_LEGACY_SVELTE_4 extends true
? Svelte4LegacyComponent<P>
: Svelte3LegacyComponent<P>
/**
* The "type" of an imported, compiled Svelte component.
*
* In Svelte 5, there is no difference between the
* imported component and its "type" - it's just a function.
* In Svelte 3/4, the imported component is a class.
*/
export type ComponentType<C> = C extends LegacyComponent
? new (...args: any[]) => C
: C
/**
* A component import.
*
* A convenience type to allow dynamically `import(...)`'d
* components to be passed directly to `mount.`
*/
export type ComponentImport<C> =
| ComponentType<C>
| { default: ComponentType<C> }
/** The props of a component. */
export type Props<C extends Component> = ComponentProps<C>
/**
* The exported fields of a component.
*
* In Svelte 5, this is the set of variables marked as `export`'d.
* In Svelte 4, this is simply the instance of the component class.
*/
export type Exports<C> = IS_MODERN_SVELTE extends true
? C extends ModernComponent<any, infer E>
? E
: C & { $set: never; $on: never; $destroy: never }
: C
/**
* Options that may be passed to `mount` when rendering the component.
*
* In Svelte 4, these are the options passed to the component constructor.
*/
export type MountOptions<C extends Component> = IS_MODERN_SVELTE extends true
? Parameters<typeof mount<Props<C>, Exports<C>>>[1]
: LegacyConstructorOptions<Props<C>>
/** A component's props or some of its mount options. */
export type ComponentOptions<C extends Component> =
| Props<C>
| Partial<MountOptions<C>>
/** Update a component's props and trigger updates to the DOM. */
export type Rerender<C extends Component> = (
props: Partial<Props<C>>
) => Promise<void>
/** The result of mounting a component into the document. */
export interface MountResult<C extends Component> {
/** The mounted component's exports. */
component: Exports<C>
/** Unmount the component. */
unmount: () => void
/** Rerender the component. */
rerender: Rerender<C>
}
/** Options for configuring the document. */
export interface SetupOptions {
/** The base document element, `document.body` if unspecified. */
baseElement?: HTMLElement
}
/** The result of setting up the document for rendering. */
export interface SetupResult<C extends Component> {
/** The base document element, usually `document.body`. */
baseElement: HTMLElement
/** The component's immediate container element, usually a `<div>` appended to `document.body`. */
container: HTMLElement
/** Options to pass to `mount`. */
mountOptions: MountOptions<C>
}
/** The result of setting up the document and rendering the component. */
export interface RenderResult<C extends Component> {
/** The base document element, usually `document.body`. */
baseElement: HTMLElement
/** The component's immediate container element, usually a `<div>` appended to `document.body`. */
container: HTMLElement
/** The mounted component's exports. */
component: Exports<C>
/** Unmount the component. */
unmount: () => void
/** Rerender the component. */
rerender: Rerender<C>
}