import type { Component, Snippet } from 'svelte'; import type { Action } from 'svelte/action'; import type { Attachment } from 'svelte/attachments'; /** * A Svelte attachment that will add a class to the anchor if its `href` matches the current route. * It can have an optional `className` parameter to specify the class to add, otherwise it will * default to `is-active`, and an optional `startsWith` parameter. * * ```svelte * * ``` */ export const isActiveLink: IsActiveLink; /** Create a search string from the search object that is provided in hooks. */ export function serializeSearch(search: Search): string | undefined; /** * Setup a new router instance with the given routes. * * ```js * export const { p, navigate, isActive, route } = createRouter({ * '/': Home, * '/about': About, * ... * }); * ``` * * A base path can be provided as an option: * * ```js * export const { p, ... } = createRouter({ ... }, { base: 'my-app' }); * ``` */ export function createRouter(r: T, options?: CreateRouterOptions): RouterApi; export type CreateRouterOptions = { /** The base path that is prepended to every URL. Use `'#'` to enable hash-based routing. */ base?: string; }; /** * Blocks navigation as long as the callback returns `false`. * * Returns a function that clears the navigation block. * * ```js * $effect(() => blockNavigation(() => confirm('Are you sure you want to leave?'))); * ``` * * The callback can also be async: * * ```js * $effect(() => blockNavigation(async () => await showConfirmModal())); * ``` * * If you also need to block tab close, use the object form to handle blocking for navigation and * site unloading separately (site unloading cannot be blocked asynchronously): * * ```js * $effect(() => * blockNavigation({ * beforeUnload() { * return false; * }, * async onNavigate() { * return await askInModal(); * }, * }), * ); * ``` */ export function blockNavigation( callback: | (() => boolean | Promise) | { beforeUnload?(): boolean; onNavigate(): boolean | Promise }, ): () => void; /** The component that will render the current route. */ export const Router: Component<{ /** * The base path that is prepended to every URL. * * @deprecated Use the `base` option of `createRouter` (or of the Vite plugin for file-based * routing) instead. */ base?: string; }>; /** * The reactive search params of the URL. It is just a wrapper around `SvelteURLSearchParam` that * will update the url on change. */ export const searchParams: SearchParams; // eslint-disable-next-line @typescript-eslint/no-empty-object-type type BaseProps = {}; export type LazyRouteComponent = () => Promise<{ default: Component | Snippet<[Props]>; }>; export type RouteComponent = | Component | Snippet<[Props]> | LazyRouteComponent; export type LayoutComponent = RouteComponent<{ children: Snippet }>; export type Hooks = { /** * A function that will be called before the route is loaded. If it returns a promise, the route * will wait for it to resolve before loading. * * You can throw a `navigate` call to redirect to another route. * * ```js * async beforeLoad({ pathname }) { * await ... * throw navigate('/home'); * } * ``` * * Returning `false` cancels the navigation and stays on the current route. * * ```js * beforeLoad({ meta }) { * if (meta.requiresAuth && !user) { * return false; * } * } * ``` */ beforeLoad?(context: HooksContext): void | boolean | Promise; /** A function that will be called after the route is loaded. */ afterLoad?(context: HooksContext): void | Promise; /** A function that will be called when the route is preloaded. */ onPreload?(context: HooksContext): void | Promise; /** A function that will be called when the route fails to load. */ onError?(error: unknown, context: HooksContext): void | Promise; }; export type Routes = { [_: `/${string}`]: RouteComponent | Routes; [_: `*${string}` | `(*${string})`]: RouteComponent | undefined; layout?: LayoutComponent; hooks?: Hooks; meta?: RouteMeta; }; export type IsActiveLink = (options?: { className?: string; startsWith?: boolean; }) => Attachment; export type IsActiveLinkAction = Action< HTMLAnchorElement, | { className?: string; startsWith?: boolean; } | undefined >; /** * Route metadata that can be extended via module augmentation. * * @example * declare module 'sv-router' { * interface RouteMeta { * public?: boolean; * requiresAuth?: boolean; * } * } */ // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface RouteMeta {} export class Navigation extends Error { constructor(target: string); } export type RouterApi = { /** * Construct a path while ensuring type safety. * * ```js * p('/users'); * // With parameters * p('/users/:id', { id: 1 }); * ``` * * @param route The route to navigate to. * @param params The parameters to replace in the route. */ p>(...args: ConstructPathArgs): string; /** * Navigate programmatically to a route. * * ```js * navigate('/users'); * // With parameters * navigate('/users/:id', { * params: { * id: 1, * }, * }); * // Back and forward * navigate(-1); * navigate(2); * ``` * * @param route The route to navigate to. * @param options The navigation options. * @returns {@link Navigation} For use with `throw navigate(...)` inside hooks. */ navigate>(...args: NavigateArgs): Promise; /** * Will return `true` if the given path is active. * * Can be used with params to check the exact path, or without to check for any params in the * path. * * @param path The route to check. * @param params The optional parameters to replace in the route. */ isActive: { >(...args: IsActiveArgs): boolean; startsWith>(...args: IsActiveArgs): boolean; }; /** * Preloads the given route. * * @param path The route to preload. */ preload>(path: U): Promise; /** * Resolves the metadata for the given route path by merging meta from all ancestor route levels. * * @param path The route to resolve meta for. */ resolveMeta>(path: U): RouteMeta; route: { /** * An object containing the parameters of the current route. * * For example, given the route `/posts/:slug/comments/:commentId` and the URL * `http://localhost:5173/posts/hello-world/comments/123`, the `params` object would be `{ slug: * 'hello-world', commentId: '123' }`. */ params: AllParams; /** * Extract parameters from the given pathname. Will throw if the pathname does not match the * current route. * * ```ts * route.getParams('/posts/:slug').slug; * ``` * * @param pathname */ getParams>(pathname: U): Record, string>; /** The reactive pathname of the URL. */ pathname: (Path & {}) | (string & {}); /** The reactive query string part of the URL. */ search: Record; /** The reactive history state that can be passed to the `navigate` function. */ state: unknown; /** The reactive hash part of the URL. */ hash: string; /** Arbitrary metadata associated with the route. */ meta: RouteMeta; }; }; export type Path = RemoveParenthesis< RemoveLastSlash, '', AnyParam>> >; export type ConstructPathArgs = { [Path in TPath]: PathParams extends never ? [Path] | [Path, ConstructUrlOptions] : [Path, ConstructUrlOptions & { params: PathParams }]; }[TPath]; export type IsActiveArgs< TPath extends string, StartsWith extends boolean = false, > = StartsWith extends true ? { [Path in TPath]: PathParams extends never ? [PathPrefixes] : [PathPrefixes] | [PathPrefixes, PathParams]; }[TPath] : { [Path in TPath]: PathParams extends never ? [Path] : [Path] | [Path, PathParams]; }[TPath]; export type PathParams = ExtractParams> extends never ? never : Record>, string>; export type AllParams = Partial< Record>>, string> >; export type Search = string | Record; export type HooksContext = { hash?: string; meta: RouteMeta; params: Record; pathname: string; replace?: boolean; search: Record; state?: unknown; }; export type NavigateOptions = | { replace?: boolean; search?: Search; state?: unknown; hash?: string; scrollToTop?: ScrollBehavior | false; viewTransition?: boolean; } | undefined; export type ConstructUrlOptions = | { search?: Search; hash?: string; } | undefined; export type SearchParams = Omit< URLSearchParams, 'append' | 'delete' | 'entries' | 'get' | 'getAll' | 'set' | 'sort' | 'values' > & { append(name: string, value: string | number | boolean, options?: { replace?: boolean }): void; delete(name: string, value?: string | number | boolean, options?: { replace?: boolean }): void; entries(): [string, string | number | boolean][]; get(name: string): string | number | boolean | null; getAll(name: string): (string | number | boolean)[]; set(name: string, value: string | number | boolean, options?: { replace?: boolean }): void; sort(options?: { replace?: boolean }): void; toURLSearchParams(): URLSearchParams; values(): (string | number | boolean)[]; }; type NavigateArgs = | (PathParams extends never ? [T] | [T, NavigateOptions] : [T, NavigateOptions & { params: PathParams }]) | [number]; type StripNonRoutes = { [ K in keyof T as K extends `*${string}` ? never : K extends `(*${string})` ? never : K extends 'layout' ? never : K extends 'hooks' ? never : K extends 'meta' ? never : K ]: T[K] extends Routes ? StripNonRoutes : T[K]; }; type NormalizeSlashes = T extends `${infer A}//${infer B}` ? NormalizeSlashes<`${A}/${B}`> : T; type RecursiveKeys< T extends Routes, Prefix extends string = '', AnyParam extends boolean = false, > = { [K in keyof T]: K extends string ? T[K] extends Routes ? RecursiveKeys< T[K], NormalizeSlashes<`${Prefix}${AnyParam extends true ? ReplaceParamWithString : K}`>, AnyParam > : NormalizeSlashes<`${Prefix}${AnyParam extends true ? ReplaceParamWithString : K}`> : never; }[keyof T]; type ReplaceParamWithString = T extends `/:${string}` ? `/${string}` : T extends `/(:${string})` ? `/${string}` : T; type RemoveLastSlash = T extends '/' ? T : T extends `${infer R}/` ? R : T; type RemoveParenthesis = T extends `${infer A}(${infer B})${infer C}` ? RemoveParenthesis<`${A}${B}${C}`> : T; type ExtractParams = T extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractParams<`/${Rest}`> : T extends `${string}:${infer Param}` ? Param : T extends `${string}*${infer Param}` ? Param extends '' ? never : Param : never; type PathPrefixes = T extends '/' ? '/' : T extends `/${infer Segment}/${infer Rest}` ? Acc extends '' ? PathPrefixes<`/${Rest}`, `/${Segment}`> | `/${Segment}` : PathPrefixes<`/${Rest}`, `${Acc}/${Segment}`> | `${Acc}/${Segment}` | Acc : T extends `/${infer Segment}` ? Acc extends '' ? `/${Segment}` : `${Acc}/${Segment}` | Acc : Acc;