import React$1 from "react"; import { History, Location } from "history"; //#region src/router/symbols.d.ts declare const CONTEXT: unique symbol; declare const LAYOUT: unique symbol; declare const WRAPPER: unique symbol; declare const LOAD: unique symbol; declare const GUARD: unique symbol; declare const PAGE: unique symbol; declare const ERROR: unique symbol; declare const LOADING: unique symbol; declare const SPLASH: unique symbol; declare const REDIRECT: unique symbol; //#endregion //#region src/router/types.d.ts type Component = React.FC; type LazyComponent = () => Promise; type Obj = Record; type Loader = (route: Route) => Promise; type Guard = (route: Route) => Promise; /** * Where a component sits in the matched route tree. Every component the * router renders in an outlet — `[WRAPPER]`, `[PAGE]`, `[LOADING]`, * `[ERROR]` — receives its own level alongside `route`, so route-level * metadata (breadcrumbs, sub-navigation, per-level analytics) can live in * the component rather than being hardcoded against a path the route tree * already knows. */ interface RouteLevel { /** 0-based index of this level in the matched chain. */ index: number; /** * The route-definition key for this level, with a dynamic segment * normalized to its path spelling (`$orgId` and `":orgId"` both read as * `:orgId`). `"index"` for an index page, `""` for the root level. * * A definition key, not necessarily a URL segment: on a group's level this * is the group key (`_list`), which appears in no path. */ segment: string; /** * This level's own route pattern, e.g. `/org/:orgId/surveys` — ready to * hand to `to=` or `router.navigate()` once its params are filled in. * * `undefined` when the level does not address a page of its own: a * nesting level with no `index` key is not navigable, and deriving a * pattern for it would produce a path that 404s. */ pattern?: RoutePath; } /** * The props every `[WRAPPER]` component receives. Unlike `[LOADING]` and * `[ERROR]`, a wrapper does receive `children` — it wraps the rest of the * chain below it. * * Name the path it sits on to get `route` typed — see {@link PageProps}. A wrapper's path * is a {@link RoutePrefix} rather than a `RoutePath`, because the level it wraps often addresses no * page of its own. */ interface WrapperProps

{ route: [P] extends [undefined] ? Route : RouteAtPrefix

; level: RouteLevel; children?: React.ReactNode; } /** * The props every `[PAGE]` component receives. * * Name the path the page sits on and `route` is typed against the route tree — `params` from the * path, `data` from every `[LOAD]` at or above it, `context` from every `[CONTEXT]`: * * ```tsx * export const StudyPage: FC> = ({ route }) => { * route.params.studyId; // string * route.data.study; // whatever that level's [LOAD] resolves to * route.data.org; // ...and the ancestor's, merged in * }; * ``` * * The path is given rather than inferred because the component cannot import the route tree that * imports it. A mistyped one is a compile error; leaving it off keeps the untyped `Route`. * * **Annotate the const, not the parameter.** `({ route }: PageProps<…>) => …` puts the component's * *inferred* type on the path that resolves through `MobxRouter["routes"]` and closes the cycle — * the route tree imports the component, the component's type reads the route tree. It can compile * in isolation and collapse the whole tree to `any` (TS7022) once several components use it. */ interface PageProps

{ route: [P] extends [undefined] ? Route : RouteAt

; level: RouteLevel; } /** The props every `[ERROR]` component receives. */ interface ErrorProps { route: Route; error: RouterError; /** * Absent only on a synthetic error route that never matched a level — * a `NOT_FOUND` on the very first segment, say. Present for in-slot * loader errors and anywhere a prefix of the tree did match. */ level?: RouteLevel; } /** * The props every `[LOADING]` component receives. Note the absence of * `children`: outlets in a chain load in parallel, so a descendant can * be ready while this slot is still loading — rendering it would paint * a page with incomplete `route.data`. */ interface LoadingProps { route: Route; level: RouteLevel; } /** * A matched view of where navigation is headed, published the moment the * matcher resolves a URL — before guards and loaders run. See * {@link MobxRouterConfig} consumers via `RouterStore.target`. * * Deliberately a plain value rather than the matched `Route`: at this point * the route's outlets have not loaded, so handing it out would invite reading * `route.data` before the loaders ran, and would keep outlets alive for a * navigation that may yet be abandoned. */ interface RouteTarget { /** The destination URL's pathname. */ pathname: string; /** * The matched pattern, e.g. `/org/:orgId/surveys`. Compare against this * instead of interpolating params into a path — that comparison is where * the two clocks get mixed. * * `undefined` only when carried over from a route that never matched a * pattern; see `RouterStore.target`. */ pattern?: RoutePath; params: Record; /** * The matched nesting levels — the same ones `[WRAPPER]`s render at. A * wrapper can compare its own `level` against these to ask whether the * destination is still inside it, without waiting for the swap. */ levels: RouteLevel[]; } /** @internal a guard together with the route level that declared it */ interface GuardEntry { guard: Guard; depth: number; } /** @internal per-level snapshot used to build synthetic error routes */ interface MatchLevel { level: RouteLevel; wrapper?: Component; layout?: Component; errorComponent?: Component; } interface RouteConfig { [CONTEXT]?: Obj; [LAYOUT]?: Component; [WRAPPER]?: Component; [GUARD]?: Guard; [LOAD]?: Loader; [ERROR]?: Component; [LOADING]?: Component; } /** * A route page definition. * * For eager (non-lazy) pages, pass the component directly: * * ```tsx * import { DashboardPage } from './routes/dashboard'; * * const routes = makeRoutes()({ * dashboard: { [PAGE]: DashboardPage }, * }); * ``` * * Holding the reference across an HMR update is fine: React Refresh * resolves a component through its family map, so an element created * from an older reference still renders the current implementation. * What this depends on is `Outlet` keeping `component` as a plain * field — a MobX-wrapped identity belongs to no family and would stop * resolving. See the note on `Outlet.component`. * * A thunk (`[PAGE]: () => `) also works but drops the * props the outlet passes, so the page will not receive `route`. * * Lazy pages use the `() => import('./Page')` form (detected by the * library) and follow the normal code-splitting flow. */ interface Page extends Omit { [PAGE]: Component | LazyComponent; } /** * Navigation options for a `[REDIRECT]`. * * Deliberately path-agnostic: `to` is a plain `string` and `params` is * always optional, so a dynamic target types the same as a static one. * See the note on {@link RedirectTarget} for why this cannot be `RoutePath`. */ type RedirectOptions = Omit, "params"> & { params?: Obj; }; /** * What `[REDIRECT]` accepts: a path, full navigation options, or a function * of the route the redirect matched. * * The function form is how a redirect reaches a dynamic path — it can read * `route.params` rather than borrowing a `[GUARD]` to do the same job. It * runs during matching, before guards and loaders, so `route.data` is empty; * `params`, `context` and `path` are what it has to work with. Throwing from * it fails the navigation as a `RouterError` of type `"REDIRECT"`. * * **Nothing reachable from `Routes` may reference `RoutePath`.** `RoutePath` * is derived from `MobxRouter["routes"]`, which is the very object being * inferred — so naming it here makes `makeRoutes()`'s `R extends Routes` * constraint depend on `typeof routes` while inferring `typeof routes`. The * route object then collapses to `any` with TS7022 in every app that * augments `MobxRouter`. That is why targets are checked structurally, and * why an unresolvable `to` is a runtime `RouterError` rather than a type * error. See `makeRoutes`' note on the same constraint. */ type RedirectTarget = string | RedirectOptions | ((route: Route) => string | RedirectOptions); interface Redirector { [REDIRECT]: RedirectTarget; } type Leaf = Page | Redirector | Component | LazyComponent; interface Routes extends RouteConfig { /** * Rendered while the very first navigation is still resolving — before * any route has matched, so before `[LAYOUT]`, `[LOADING]` and the outlet * chain exist. Covers app boot, most visibly when a root `[GUARD]` has to * await an auth check. * * Read from the **root** of the route definition only; the type permits * it on nested objects, where it is ignored. Receives no props — there is * no route yet to describe. */ [SPLASH]?: Component; [segment: string]: Leaf | Routes; } type HasParam = T extends `${string}:${string}` ? true : false; type WithToAndParams

= {} extends ExtractParams

? { to: P; params?: undefined; } & Omit : { to: P; params: ExtractParams

; } & Omit; type NavigateOptions

= { to: P; /** * Replace the current history entry instead of pushing a new one. * * Defaults to `false` for a direct `navigate()` or ``, and to `true` * everywhere the navigation is a *redirect* — a `[REDIRECT]` leaf or a * `redirect()` thrown from a guard or loader — where the origin URL * renders nothing and would trap Back if it stayed in history. */ replace?: boolean; state?: unknown; search?: Record | URLSearchParams; preserveSearch?: boolean; } & (HasParam

extends true ? { params: ExtractParams

; } : { params?: undefined; }); /** * The navigation a blocker is being asked about — where it would go, and how. * * Deliberately not a history `Location`: nothing has been handed to history yet, so there is no * `key` or `state` to report. Deliberately not a matched {@link RouteTarget} either — matching the * destination would run its `[REDIRECT]`s for a navigation that may never happen. A blocker that * wants to allow movement *within* its own subtree tests the pathname: * * ```ts * ({ pathname }) => pathname.startsWith("/designer/") || confirm("Discard changes?"); * ``` */ interface BlockedNavigation { /** * `"POP"` for the back and forward buttons, `"REPLACE"` for a navigation * that would replace the current history entry, `"PUSH"` otherwise. */ action: "PUSH" | "REPLACE" | "POP"; /** The destination pathname, e.g. `/org/1/surveys`. */ pathname: string; /** The destination's query string, leading `?` included, or `""` when there is none. */ search: string; /** `pathname` and `search` joined — the same string `router.resolveHref` returns. */ href: string; } /** * What to do about a navigation while a block is live. * * **Only `true` proceeds.** `false`, `undefined` and a thrown error all keep the user where they * are, so a handler that shows a dialog and forgets to report the answer fails safe rather than * discarding the work it was meant to protect. * * May be async — the navigation waits for it, so `await confirmLeave()` and `await save()` are * both fine. See `RouterStore.block`. */ type NavigationBlocker = (navigation: BlockedNavigation) => boolean | void | Promise; /********************************************************************************* */ interface MobxRouter {} /** * The shape of `route.context`, for an app that wants one. Augment it the way you augment * `MobxRouter`: * * ```ts * declare module "@jayalfredprufrock/mobx-toolbox/router" { * interface MobxRouterContext { * public: boolean; * } * } * ``` * * This exists because a `[GUARD]` or `[LOAD]` **cannot** name a path-derived type. Both live inside * the object `makeRoutes()` is inferring, and `RouteAt

` derives from `MobxRouter["routes"]` — * that same object. Annotating one collapses the whole route tree to `any` (TS7022), the same * self-reference {@link RedirectTarget} documents. * * A standalone interface has no such dependency, so it reaches the one place the computed types * can't. The trade is that it describes the context of the app rather than of a path: it is the * union of what any level may contribute, so declare a key optional if only some branches set it. * * Components outside the route tree don't need this — `PageProps<"/path">` computes the exact * context in force at that path, which is strictly more precise. */ interface MobxRouterContext {} /** * What `route.context` is typed as: the augmented shape if there is one, and the untyped `Obj` it * has always been if not — so an app that never augments is unaffected. */ type RouteContext = keyof MobxRouterContext extends never ? Obj : MobxRouterContext; type MobxRouterRoutes = MobxRouter extends { routes: infer R; } ? R : Routes; interface MobxRouterConfig { history?: History; /** * Wrap route swaps in `document.startViewTransition` where the browser * supports it. Defaults to `true`; set `false` to opt out globally. */ viewTransitions?: boolean; } type RoutePath = ExtractPaths extends undefined ? string : NormalizeRootPath>; type DynamicRoutePath = Extract; type StaticRoutePath = Exclude; /********************************************************************************* */ type JoinSegments = `/${S1 extends string ? S1 : ""}${S2 extends string ? S2 : ""}`; type SegmentName = S extends `$${infer Param}` ? `:${Param}` : S; type ExtractParam = P extends `:${infer Param}` ? Record & NextPart : NextPart; type ExtractParams

= P extends `${infer S1}/${infer Rest}` ? ExtractParam> : ExtractParam; type ExtractPaths = { [S in keyof R]: S extends `_${string}` ? ExtractPaths : S extends string ? S extends "index" ? "" : R[S] extends Leaf ? `/${SegmentName extends string ? SegmentName : ""}` : JoinSegments, ExtractPaths> : never }[keyof R]; type NormalizeRootPath

= P extends "" ? "/" : P; /********************************************************************************* */ /** * Whether the app has augmented `MobxRouter` with its route tree. Without it there is nothing to * resolve against, so the typed props degrade to the untyped ones rather than producing nonsense * out of `Routes`' index signature. */ type HasRoutes = MobxRouter extends { routes: any; } ? true : false; /** * Merge two loader payloads with the **deeper one winning**, which is what happens at runtime: * `route.data` is `Object.assign({}, ...outlets)`, so a key a child loader also returns overwrites * its ancestor's. An intersection would type such a key as `A & B`, which nothing ever holds. */ type MergeDeeper = keyof A extends never ? B : keyof B extends never ? A : Omit & B; type LoadData = N extends { [LOAD]: (...args: any[]) => Promise; } ? D extends object ? D : {} : {}; type ContextData = N extends { [CONTEXT]: infer C; } ? (C extends object ? C : {}) : {}; /** Group keys (`_list`) contribute no path segment, so the walk passes through them. */ type GroupKeys = Extract; /** * The definition key that spells path segment `S`. A `:param` segment is written `$param` as an * object key, or quoted as `":param"` — the second spelling is `S` itself, so it needs no branch. */ type SegmentKey = S extends keyof N ? S : S extends `:${infer Param}` ? `$${Param}` extends keyof N ? `$${Param}` : never : never; /** `/org/:orgId/studies` → `["org", ":orgId", "studies"]`; `/` → `[]`. */ type Segments

= P extends `/${infer Rest}` ? Segments : P extends `${infer Head}/${infer Tail}` ? [Head, ...Segments] : P extends "" ? [] : [P]; /** An `index` key addresses its parent's path, so it is part of that path's chain. */ type IndexTail = N extends { index: infer I; } ? [I] : []; /** Guards the spread: a failed branch is `never`, which must not be spread into a tuple. */ type Prepend = T extends readonly unknown[] ? [H, ...T] : never; /** * Every definition node passed through on the way to path `P`, in order — which is exactly the set * whose `[LOAD]` and `[CONTEXT]` are in force there. * * Branches that cannot consume the next segment resolve to `never` and drop out of the union, so a * well-formed tree leaves exactly one chain. */ type Chain = Segs extends readonly [infer S extends string, ...infer Rest extends readonly string[]] ? Prepend> : [N, ...IndexTail]; type Step = (SegmentKey extends infer K ? (K extends keyof N ? Chain : never) : never) | { [G in GroupKeys]: Chain }[GroupKeys]; type FoldData = C extends readonly [infer H, ...infer T] ? MergeDeeper, FoldData> : {}; type FoldContext = C extends readonly [infer H, ...infer T] ? MergeDeeper, FoldContext> : {}; /** * What `route.data` holds at path `P`: every `[LOAD]` at that path and above it, merged, with the * deeper one winning. * * Ancestors are included because `route.data` is chain-wide at runtime. Descendants are not — which * of them matched is not knowable from `P`, so only what is *guaranteed* present is typed. That is * also why this is correct for a `[WRAPPER]`, which renders for many descendant paths. */ type RouteDataAt

= HasRoutes extends true ? FoldData>> : Obj; /** What `route.context` holds at path `P` — every `[CONTEXT]` at or above it, deeper winning. */ type RouteContextAt

= HasRoutes extends true ? FoldContext>> : Obj; /** * The definition node addressed by `P` — the same walk as {@link Chain}, but returning where it * lands rather than everything passed through, and without following `index`: what is wanted here * is the level itself, so its children are still reachable. */ type NodeAt = Segs extends readonly [infer S extends string, ...infer Rest extends readonly string[]] ? (SegmentKey extends infer K ? K extends keyof N ? NodeAt : never : never) | { [G in GroupKeys]: NodeAt }[GroupKeys] : N; /** * Every `:param` name anywhere in the subtree below a node, in either key spelling. * * Components are skipped rather than recursed into: a function type's own keys (`call`, `apply`, * `prototype`) are not route segments, and walking them would be both wrong and unbounded. */ type ParamNamesIn = N extends ((...args: any[]) => any) ? never : { [K in Extract]: K extends `$${infer Param}` ? Param | ParamNamesIn : K extends `:${infer Param}` ? Param | ParamNamesIn : ParamNamesIn }[Extract]; /** * The `Route` a component at path `P` receives, with `params`, `data` and `context` resolved * against the route tree instead of left as `Obj`. */ type RouteAt

= Omit & { params: ExtractParams

; data: RouteDataAt

; context: RouteContextAt

; }; /** * {@link RouteAt} for a level that renders over its descendants — a `[WRAPPER]` or `[LAYOUT]`. * * Identical except for `params`, which also carries every `:param` a descendant could contribute, * as optional. That is sound where the same treatment of `data` would not be: params are strings * and the set of them is knowable from the tree, so `string | undefined` is exactly true at this * level — whereas two sibling loaders can both define `data.thing` with different types, and no * merge of them describes what actually arrives. * * It is also what a wrapper needs in practice: a shell on `/org/:orgId/segments` that renders over * `:segmentId` and reads it to highlight a row would otherwise have to assert the type by hand. */ type RouteAtPrefix

= Omit & { params: ExtractParams

& { [K in Exclude>>, keyof ExtractParams

>]?: string }; data: RouteDataAt

; context: RouteContextAt

; }; /** * Every prefix of every route path, including the ones that address no page of their own. * * The leading empty segment is normalized here rather than excluded afterwards, so the root comes * out as `/` and the union needs no second pass. */ type PrefixesOf

= P extends `${infer Head}/${infer Tail}` ? NormalizeRootPath | `${Head}/${PrefixesOf}` : P; /** * Where a `[WRAPPER]` or `[LAYOUT]` can sit: any *prefix* of a route path, not just the navigable * ones. A nesting level with no `index` addresses no page, so it never appears in `RoutePath` — but * it is exactly where wrappers live. */ type RoutePrefix = string extends RoutePath ? string : PrefixesOf; //#endregion //#region src/router/route.d.ts interface RouteConfig$1 { path: string; pattern?: RoutePath; outlets: Outlet[]; guards: GuardEntry[]; levels: MatchLevel[]; context?: Obj; layout?: Component; params: Obj; error?: RouterError; } declare class Route { readonly path: string; /** * This route's pattern, e.g. `/org/:orgId/surveys` — `path` with its * dynamic segments left unsubstituted. Ready to hand to `to=` or * `router.navigate()` alongside `params`. * * Comparing patterns is how you ask "which route is this" without * interpolating params into a path and matching strings. `RouteLevel.pattern` * is the same idea per level; this is the whole route's. * * `undefined` only on a synthetic error route, which by definition has no * matched pattern — nothing matched, or matching is what failed. */ readonly pattern?: RoutePath; readonly outlets: Outlet[]; readonly guards: Guard[]; readonly context: RouteContext; readonly params: Obj; readonly layout?: Component; /** set on synthetic error routes; the error being rendered */ readonly error?: RouterError; /** @internal */ readonly guardEntries: GuardEntry[]; /** @internal */ readonly levels: MatchLevel[]; get data(): Obj; /** * `true` once a pending outlet has crossed the debounce threshold and * its `[LOADING]` component is on screen. This is the signal to drive * layout-level indicators (a top progress bar, a dimmed shell) — it * stays `false` through the quiet window, so navigations that resolve * quickly never flash an indicator. */ get isLoading(): boolean; /** * `true` whenever any outlet is still resolving, including the quiet * window before `isLoading` flips. Use this to reason about whether * navigation has settled (tests, effects) — not to render indicators. */ get isPending(): boolean; constructor(def: RouteConfig$1); guard(): Promise; load(options?: LoadOptions): Promise; } //#endregion //#region src/router/outlet.d.ts interface OutletConfig { component?: Component | LazyComponent; loader?: Loader; errorComponent?: Component; loadingComponent?: Component; /** * The matched level this outlet renders at. Passed to whatever component * fills the slot — the outlet's own, or the `[LOADING]` / `[ERROR]` that * stands in for it — alongside `route`. */ level?: RouteLevel; } type RouteSegmentState = "preloading" | "loading" | "error" | "ready"; interface LoadOptions { /** * Hold the `[LOADING]` component on screen for * `LOADING_MIN_DURATION_MS` after the data arrives, so a just-shown * indicator can't vanish a frame later. Only worth paying for when the * indicator is actually rendered — i.e. a cold load. During a warm * navigation the previous page is still on screen and the pending * route's outlets aren't rendered at all, so holding would delay * content to hide an indicator nobody saw. */ hold?: boolean; } /** * How long an outlet stays `"preloading"` — rendering nothing — before * it shows its `[LOADING]` component. Loads that finish inside this * window never render an indicator at all. */ declare const LOADING_DELAY_MS = 300; /** * Once the `[LOADING]` component is on screen, how long it is held there * even if the data has already arrived. Only applies to loads that * already exceeded `LOADING_DELAY_MS`, and exists solely to keep a * just-shown indicator from vanishing a frame later. */ declare const LOADING_MIN_DURATION_MS = 300; declare const DefaultOutlet: Component; /** * Rendered in a pending outlet's slot when no `[LOADING]` component is * defined at or above that level. Deliberately minimal — define a * root-level `[LOADING]` to replace it. */ declare const DefaultLoadingPage: Component; declare class Outlet { readonly config: OutletConfig; state: RouteSegmentState; promise: Promise | undefined; data: unknown; error: RouterError | undefined; component: Component | undefined; readonly level: RouteLevel | undefined; get Component(): Component | undefined; constructor(config: OutletConfig); load(route: Route, options?: LoadOptions): Promise; setData(data: unknown): void; setError(error: RouterError): void; setState(state: RouteSegmentState): void; private loadData; private loadComponent; } //#endregion //#region src/router/make-routes.d.ts interface MatchState { segments: string[]; patternSegments: string[]; context: Obj; params: Obj; outlets: (Outlet | undefined)[]; guards: GuardEntry[]; levels: MatchLevel[]; layout?: Component; errorComponent?: Component; loadingComponent?: Component; } declare const makeRoute: (matchState: MatchState) => Route; /** * Builds the synthetic route rendered when navigation fails. Bubbles * from the failing level (`error.depth`, defaulting to the deepest * matched level) to the nearest `[ERROR]` component, preserving the * `[LAYOUT]` and `[WRAPPER]`s accumulated up to that level. Ancestor * `[LOAD]` loaders are intentionally not run — error routes never * fetch data. */ declare const makeErrorRoute: (error: RouterError, pathname: string, source?: { levels: MatchLevel[]; params: Obj; context: Obj; }) => Route; declare const matchRoute: (path: string, routeDef: Routes, matchState?: MatchState) => Route; declare const makeRoutes: () => (routes: R) => R; //#endregion //#region src/router/errors.d.ts type RouterErrorType = "NOT_FOUND" | "GUARD" | "LOAD" | "RENDER" | "REDIRECT"; interface RouterErrorOptions { message?: string; cause?: unknown; path?: string; } /** * The single error type surfaced to `[ERROR]` components. `type` * discriminates the failure source; when the router wraps an * application-level error (thrown by a guard or loader), the original * is preserved on the standard `cause` property. * * Guards and loaders may also throw `RouterError` directly — e.g. * `throw new RouterError("NOT_FOUND")` from a loader when an entity * doesn't exist — and it passes through unwrapped. */ declare class RouterError extends Error { readonly type: RouterErrorType; readonly path?: string; /** @internal matched-prefix state captured when the matcher throws NOT_FOUND */ state?: MatchState; /** @internal level index of the failing guard, for depth-aware bubbling */ depth?: number; constructor(type: RouterErrorType, options?: RouterErrorOptions); } /** * @internal Builds the `REDIRECT` error for a redirect that could not be * carried out — a `[REDIRECT]` function that threw, or navigation options * naming a path whose `:params` can't be filled. * * `from` carries whatever the `Redirect` knew about where it came from, so * the error route keeps the matched prefix's layout and wrappers and bubbles * to the same `[ERROR]` a failure at that level would have. */ declare const redirectFailed: (cause: unknown, path: string | undefined, from?: { state?: MatchState; depth?: number; }) => RouterError; //#endregion //#region src/router/components/error.d.ts /** * Rendered when an error occurs and no `[ERROR]` component is defined * on the matched prefix. Deliberately minimal and dependency-free — * define a root-level `[ERROR]` to replace it. */ declare const DefaultErrorPage: Component; interface RouteErrorBoundaryProps { route: Route; fallback: Component; children?: React$1.ReactNode; } interface RouteErrorBoundaryState { error?: RouterError; route?: Route; } /** * Catches render-time crashes in page and `[WRAPPER]` components and * renders the nearest `[ERROR]` component with `type: "RENDER"`. Mounted * inside the `[LAYOUT]` so the layout survives page crashes; crashes in * the layout itself (or in the fallback) propagate out of `` by * design — those are developer bugs that should stay loud. * * Deliberately NOT keyed by location: the boundary must be transparent * to reconciliation (a key would remount the entire subtree and re-fire * every effect on each navigation). Instead, a captured error is cleared * when a new Route object arrives. */ declare class RouteErrorBoundary extends React$1.Component { state: RouteErrorBoundaryState; static getDerivedStateFromError(cause: unknown): RouteErrorBoundaryState; static getDerivedStateFromProps(props: RouteErrorBoundaryProps, state: RouteErrorBoundaryState): Partial | null; render(): React$1.ReactNode; } //#endregion //#region src/router/components/link.d.ts /** * The host element's own props, minus the ones the link claims for itself — everything that passes * straight through to whatever `C` renders as. * * Deliberately not called `LinkProps`: these are the *element's* props, not the link's. The link's * own are {@link LinkPropsBase}, which is built from these. */ type PassthroughProps = Omit, "ref" | "exact" | "to" | "params" | "onClick" | "asChild" | "replace" | "state" | "search" | "preserveSearch">; type LinkPropsBase = PassthroughProps & Omit, "to" | "params"> & { exact?: boolean; ref?: React$1.Ref>; /** * Runs before the link navigates, and can cancel it with * `preventDefault()`. Declared here rather than inherited from `C` so the * signature stays the same whatever element the link renders as. */ onClick?: (event: React$1.MouseEvent) => void; }; interface LinkComponent {

(props: LinkPropsBase & { to: P; params?: undefined; }): React$1.ReactNode;

(props: LinkPropsBase & { to: P; params: ExtractParams

; }): React$1.ReactNode; } declare const makeLinkComponent: (C: C, baseProps?: Partial> & { as?: I; onClick?: (event: React$1.MouseEvent) => void; }) => LinkComponent; //#endregion //#region src/router/router.store.d.ts interface MobxRenderSegment { segment: string; component: Component; props?: Obj; } declare class RouterStore { readonly history: History; readonly viewTransitions: boolean; routesDef?: Routes; /** * The current URL. Updates the **instant** a navigation starts, before * guards and loaders run. * * `activeRoute` — and so `pathParams`, `activeSegments` and * `doesPathMatch` — commits only once the navigation lands. The two * therefore disagree for the whole duration of a navigation, and combining * them silently mixes clocks: interpolating `pathParams` (old) into a test * against `location.pathname` (new) is wrong for exactly as long as the * navigation takes. Use {@link target} for a matched view of the * destination that is available immediately. */ location: Location; /** * The route on screen. Commits after guards **and** loaders resolve, so it * lags `location` for the duration of a navigation — see the note there. */ activeRoute: Route | undefined; /** * The route being matched, guarded and loaded. Set for the duration of * a navigation and cleared when it lands. `activeRoute` keeps rendering * the previous page while this is set, so navigation never blanks the * screen — see {@link isNavigating}. * * Assigned only once guards have resolved, because it gates rendering. For * the destination as soon as it is *known*, use {@link target}. */ pendingRoute: Route | undefined; /** Backs {@link target}; written at match time, never cleared. */ private matchedTarget; /** * Navigation-scoped state, tracked from the first line of a navigation * rather than derived from `pendingRoute`, so both span the guard phase. * See `beginNavigation`. */ private navigating; private navigationSlow; private slowTimer; /** * Redirect hops taken since the last navigation landed. Reset on landing, * so it measures one chain rather than session history. */ private redirects; /** * Registered navigation blockers, in registration order (a `Set` iterates * by insertion). See {@link block}. */ private readonly blockers; /** * State of the single `history.block` blocker that covers pops — its * disposer while armed, the one-shot listener that re-arms it after a * transition has been let through, and whether a handler is currently * deciding about one. See {@link syncHistoryBlocker}. */ private unblockHistory; private stopRearm; private deciding; get search(): URLSearchParams; get query(): Record; get pathParams(): Record; get activeSegments(): string[]; /** * Where navigation is headed, as soon as the matcher knows — before guards * and loaders, and so well before `activeRoute` swaps. When nothing is in * flight this is the active route, so consumers never branch on navigation * state: `target.pattern` answers "which route is, or is about to be, * on screen". * * Compare `pattern`s rather than interpolating params into a path — that * is the comparison that mixes the `location` and `activeRoute` clocks. * * ```tsx * const active = tabs.find((tab) => tab.to === router.target?.pattern); * ``` * * Holds its previous value when a URL produces no match, rather than * blanking: a `[REDIRECT]` leaf throws instead of matching, and clearing * would flicker for exactly the one hop before the redirect's own match * lands. The same applies to a `NOT_FOUND` or a rejected guard — the error * route commits through `activeRoute`, and `target` keeps naming the last * route that matched. So this is not "the route on screen": after a failed * navigation the two differ until the next successful match. * * `undefined` only before the first successful match of the session. */ get target(): RouteTarget | undefined; private get targetSegments(); /** * `true` from the first moment of a navigation until it lands, guard * phase included — the honest answer to "is something in flight". * * Undebounced: it flips for every navigation however fast, so an * indicator rendered straight off it will flicker. Use it for logic, and * {@link isSlowNavigation} or {@link isLoading} for pixels. For the * narrower question "is a route currently loading", check `pendingRoute`, * which is only assigned once guards have resolved. */ get isNavigating(): boolean; /** * `true` whenever a loading indicator is warranted *anywhere*: a * navigation has been in flight longer than `LOADING_DELAY_MS` (guards * included), or a cold load's `[LOADING]` component is on screen * (including through the minimum-duration hold). Debounced, so quick * navigations never flip it. * * Use this for a layout progress bar that should stay visible alongside * a cold load's `[LOADING]` skeleton. For a bar that yields to the * skeleton instead, use {@link isSlowNavigation}. */ get isLoading(): boolean; /** * `true` when a navigation has been slow enough to be worth showing * *and* there is already a page on screen to show it over — the usual * signal for a layout-level progress bar. * * Measured from the start of the navigation, so a slow `[GUARD]` counts * toward it just as a slow `[LOAD]` does, and a navigation made slow by * both phases together still trips it. * * Excludes the cold load, where the pending route's `[LOADING]` * component is on screen instead, so a bar driven off this is mutually * exclusive with `[LOADING]`. Use {@link isLoading} if you want both at * once. */ get isSlowNavigation(): boolean; constructor(config?: MobxRouterConfig); /** * Wires the router to its history and starts the first navigation, * resolving when that navigation lands — the same guarantee * {@link navigate} gives, including any redirect the initial URL runs * through. Await it to hand off from a boot screen, or to hold a test * until there is a route to assert on; ignore it to let `[SPLASH]` and * `[LOADING]` cover the wait, which is the usual case. */ initialize(routesDef: Routes): Promise; /** * Whether `path` matches the route **on screen**. Lags a navigation in * flight, because it reads `activeSegments`; use {@link doesTargetMatch} for * the destination. A `:param` segment in `path` matches any value. */ doesPathMatch

(path: P, exact?: boolean): boolean; /** * {@link doesPathMatch} against {@link target} instead of the active route, * so it answers for the destination the moment a navigation starts. * * A separate method rather than an option on `doesPathMatch`: which clock a * call site means is worth stating at the call site. */ doesTargetMatch

(path: P, exact?: boolean): boolean; /** * Navigates to `options`, resolving `true` once the navigation has * **landed** — or `false` as soon as a {@link block}er declines it. * * Landing is the end of the whole chain, not this hop: guards, loaders, * any redirect they throw, and the `activeRoute` swap (view transition * included). Await it to run a side effect against the page that actually * ended up on screen. * * ```ts * await router.navigate({ to: "/orders/:id", params: { id } }); * announce(`Now viewing ${router.target?.pathname}`); * ``` * * Resolves rather than rejects when a navigation fails. A rejected guard, * a `NOT_FOUND` or a throwing loader commits the `[ERROR]` route, which is * a landing like any other — the caller's "after navigation" work usually * still wants to run. Read `activeRoute.error`, or compare `target.pattern` * against where you meant to go, when the distinction matters. A * navigation skipped as redundant (already at that URL, no `state`) * resolves immediately, and counts as landing. * * `false` means only that *this* call did not navigate. A blocker that * saves and then lets the user leave is expected to return `true` and land * normally; `false` is the "stay here" answer. See {@link block}. * * An unresolvable `to` — a `:param` left unfilled — still throws * *synchronously*, because that is a caller bug rather than a navigation * outcome, and because the redirect path below depends on catching it. * * A redirect loop resolves too. Guards that redirect to each other would * otherwise chain forever and leave this promise pending for the life of * the page — see {@link redirectLoop}, which cuts the chain and lands an * `[ERROR]` route instead. A guard that calls `navigate()` in a cycle * rather than throwing `redirect()` is *not* bounded: that is the app * driving navigation through the same public API a link click uses, and * the router cannot tell the two apart. * * What is awaited is "nothing is in flight" rather than this call * specifically, so a navigation superseded by another resolves when *that* * one lands. That is the only useful answer: once a redirect has replaced * the destination there is no separate completion for the original hop, * and a caller awaiting navigation wants the view it ends on. * * The router's state is committed when this resolves. React has re-rendered * too wherever a view transition ran, since the swap is flushed inside it; * without one the re-render is left on React's scheduler, so a test reading * the DOM still needs its usual `act` / `waitFor`. */ navigate

(options: NavigateOptions

): Promise; /** * {@link navigate} without consulting blockers — the navigation the router * itself performs rather than one the user asked for. * * That is the `redirect()` path: a `[REDIRECT]` leaf or a redirect thrown * by a guard is the app's own decision about where a URL leads, not the * user leaving a page, so prompting for it would ask about a destination * the user never chose. * * Deliberately not `async`: `_navigate` throws synchronously for an * unresolvable path, and the redirect handler in `setLocation` catches it * with a plain `try`/`catch`. An async method would turn that throw into * a rejection and the [ERROR] route would never render. */ private navigateNow; private settled; _navigate

(options: NavigateOptions

): void; /** * Registers a navigation blocker: `when` says whether the block is live, * and `blocker` decides what to do about a navigation while it is. * Returns the disposer. * * ```ts * const dispose = router.block( * () => designer.dirty, * async () => { * const choice = await confirmLeave(); // the app's own dialog * if (choice === "save") await designer.save(); * return choice !== "stay"; * }, * ); * ``` * * {@link useNavigationBlock} is this with the lifetime tied to a * component, which is the usual way to reach it. * * Only `true` proceeds — see {@link NavigationBlocker}. The handler may be * async, and `navigate()` stays pending until it settles, so a blocker * that saves before allowing the navigation still resolves the caller's * `await router.navigate(...)` once the destination lands. * * Covers every navigation that goes through {@link navigate} — `` * clicks, programmatic navigation, `search`-only navigations to another * path — the back and forward buttons, and closing or reloading the tab. * * **`when` must be derived from observables.** It is read at the moment a * navigation is proposed, but it also *drives registration*: the pop and * `beforeunload` halves of this depend on a `history.block` blocker being * armed exactly while the predicate holds, and that is kept in step by a * MobX reaction. A predicate reading something MobX cannot see — a ref, a * DOM query, a plain field — still blocks in-app navigation correctly, and * silently stops covering the back button. * * Does **not** cover: * - **The `redirect()` path.** See {@link navigateNow}. * - **A change that keeps the same pathname** — a query param, a history * state update, or navigating to the current URL. The route on screen * does not change, so there is nothing to leave; this is the same rule * `setLocation` applies when it declines to re-match, and it is what * keeps `setQueryParam` working while a block is live. * - **Writes straight to `router.history`.** A push there is let through * rather than prompted about — see {@link onTransition}. * * There is no "navigate anyway" option, because the predicate is one: a * "discard and leave" action resets what it guards and then navigates, by * which point `when` is false. * * Registering more than one blocker is allowed. They are consulted in * registration order and the first to decline ends it, so two dirty models * prompt one after the other rather than both at once. * * What blocking a pop cannot do anything about: the URL moves and comes * back, so the address bar can flicker — `router.location` does not, since * a blocked pop never reaches the history listeners — and popping to an * entry the history library did not create cannot be blocked and fails * silently in production. A cancelled pop is also invisible to * `navigate()`, which was never called for it. */ block(when: () => boolean, blocker: NavigationBlocker): () => void; /** * The blockers with a say in a navigation to `pathname`, in registration * order. Empty is the common case and costs one `Set` size check. */ private activeBlockers; private consultBlockers; /** * Arms a single `history.block` blocker while any predicate holds, and * disarms it when none does. * * One blocker rather than one per registration: `history.block` fans a * transition out to *every* blocker and gives each its own `retry()`, so * several would prompt at once and each retry would re-prompt the others. * Multiplexing here is what makes {@link consultBlockers} the only place * that decides. * * Called from the reaction in {@link block}, from disposal, and after a * transition has been let through. */ private syncHistoryBlocker; /** * The one blocker history sees. * * `history.block` declines *every* transition while a blocker is * registered and never reads what the blocker returned, so most of what * arrives here has already been decided: a push {@link navigate} approved, * a `redirect()`, `setQueryParam`, the trailing-slash normalization and a * write straight to `router.history` all need nothing but a retry. * Prompting for them would ask twice about one navigation, or ask about * one the user never made. * * A pop is the one transition nothing else sees, and the only one this * consults blockers about. */ private onTransition; /** * Lets a transition history has already declined through, and re-arms once * it lands. * * Standing down first is not optional: `retry()` re-enters the blocker, * and a pop's retry is a `go()` whose `popstate` arrives asynchronously — * so the blocker has to stay down until the retried navigation lands. The * one-shot listener is that "until", and re-arming through * {@link syncHistoryBlocker} means a predicate that went false in the * meantime leaves it down. * * If the retry never lands — a pop to an entry the history library did not * create cannot be blocked, and fails silently in production — the blocker * stays down, which is the same outcome as never having armed it. */ private passThrough; private standDown; /** * The URL a set of navigation options addresses, as a single string. * * This is what the link components put on `href`, so a cmd-click lands on * exactly where a plain click would have navigated — `search` and * `preserveSearch` included. Reads {@link search} when preserving, so it * re-derives as the current query changes. */ resolveHref

(options: NavigateOptions

): string; private resolveLocation; private isCurrentLocation; setQueryParam(param: string, value: string): void; removeQueryParam(param: string): string | undefined; setLocation(location: Location): Promise; /** * Marks a navigation as in flight, starts its debounce clock, and returns * the cleanup that ends both. * * Both are tracked here — before guards run — rather than derived from * `pendingRoute`, which is only assigned once guards resolve. That is * what lets `isNavigating` mean "in flight" and `isSlowNavigation` * measure how long the user has actually been waiting. An outlet-level * clock cannot do the latter: outlets only begin loading after guards, so * a 250ms guard followed by a 250ms loader would show no indicator at all * despite half a second of waiting. */ private beginNavigation; /** * Counts a redirect hop away from `pathname`, and reports the chain as a * loop once it has taken too many. * * `makeRoutes` rejects a static `[REDIRECT]` cycle at build time, but it * gives up on the function form and cannot see a `redirect()` thrown from * a guard or loader at all. Those only reveal themselves by running, and * left alone they spin forever: every hop begins a fresh navigation before * the previous one's `finally`, so the clock is handed on indefinitely, * `isNavigating` never drops, and an awaited `navigate()` never settles — * which would silently swallow everything after it in an `async` caller. * * Deliberately a count and not a cycle search. Tracking the pathnames * visited would name the exact cycle in the message and catch a ping-pong * on its second hop rather than its tenth, but it only helps a chain that * repeats itself — one that keeps inventing pathnames still needs the * count — so it buys a better message at the price of being the second * way to answer a question that already has one. * * Either way the loop becomes a `RouterError` and ends the chain the way * any other routing failure does: `[ERROR]` renders and the promise * resolves, instead of a hung tab. */ private redirectLoop; /** * Whether another navigation has taken over since this one started. * Compared by pathname rather than `Location` identity: a query-param or * history-state change during a pending navigation replaces `location` * without re-matching, and must not cancel the navigation in flight. */ private isStale; private applyRoute; } //#endregion //#region src/router/components/router.d.ts declare const PassThrough: Component; /** One rendered slot in the outlet chain: what fills it, and where it sits. */ interface OutletSlot { Component: Component | undefined; level: RouteLevel | undefined; } declare const RouterOutlet: React.FC<{ route: Route; slots: OutletSlot[]; }>; declare const routerContext: import("react").Context; declare const useRouter: () => RouterStore; interface RouterProps { store: RouterStore; } declare const Router: (({ store }: RouterProps) => import("react/jsx-runtime").JSX.Element | null) & { displayName: string; }; //#endregion //#region src/router/redirect.d.ts declare class Redirect

{ readonly options: NavigateOptions

; /** * @internal matched-prefix state, set when the matcher throws this for a * `[REDIRECT]` leaf. Used to render the error route if the redirect fails. */ state?: MatchState; /** @internal level index of the guard that threw, for depth-aware bubbling */ depth?: number; constructor(options: NavigateOptions

); } declare const redirect:

(options: NavigateOptions

) => Redirect

; //#endregion //#region src/router/use-confirm-leave.d.ts /** * Native `confirm()` protection against leaving the page, however the user * leaves it — links, programmatic navigation, back and forward, and closing * or reloading the tab. * * The deliberately dumb counterpart to `RouterStore.block`: no predicate, no * dialog of your own, nothing to keep in step. It protects for as long as it * is registered, and returns the disposer. * * ```ts * const dispose = confirmLeave(router); * ``` * * It *is* a `block`, so everything documented there applies unchanged. The * only thing this trades away is the dialog: the prompts are native chrome, * and both ignore `message` in some form — every browser ignores a custom * `beforeunload` string, and `confirm()` renders as the browser draws it. * * The always-true predicate means the browser's own reload prompt is live * for as long as this is registered, clean page included. That is the point * of the shortcut — there is no state for it to consult — but it is why * `useNavigationBlock` is the better default for a form. */ declare const confirmLeave: (router: RouterStore, message?: string) => (() => void); /** * {@link confirmLeave} for the lifetime of a component — basic unsaved-work * protection in one line, with no state to track and no dialog to write. * * ```tsx * useConfirmLeave("Discard your changes?"); * ``` * * Reach for `useNavigationBlock` instead when the block should follow a * predicate or show the app's own dialog — back button included. */ declare const useConfirmLeave: (message?: string) => void; //#endregion //#region src/router/use-navigation-block.d.ts /** * Blocks navigation away from this component while `when` returns `true`, * asking `blocker` what to do about each attempt. * * ```tsx * useNavigationBlock( * () => designer.dirty, * async () => { * const choice = await confirmLeave(); // the app's own dialog * if (choice === "save") await designer.save(); * return choice !== "stay"; * }, * ); * ``` * * Only `true` proceeds — `false`, nothing at all, and a throw each keep the * user where they are. `RouterStore.block` documents exactly what is and is * not covered; the short version is every in-app navigation plus closing the * tab, but not the back button (see `useConfirmLeave` for that). * * Both arguments are read through a ref, so neither has to be stable: * passing inline closures re-registers nothing, and the registration lasts * the component's lifetime. Blocking follows `when`, not the mount, so a * clean form is as good as no blocker at all — including for the * `beforeunload` prompt. */ declare const useNavigationBlock: (when: () => boolean, blocker: NavigationBlocker) => void; //#endregion //#region src/router/util.d.ts /** * Substitute a path pattern's `:params`. Throws when one has no value — * the right default for `navigate()` and `href`, where an unresolved path * is a bug rather than a state to render. * * Use {@link tryResolvePath} for a path you did not construct — resolving a * `level.pattern` against params that may not reach that deep, say. */ declare const resolvePath: (to: string, params?: Obj) => string; /** * {@link resolvePath} without the throw: `undefined` when any `:param` has * no value. The idiom for "link it if we can address it, otherwise render * it as plain text". */ declare const tryResolvePath: (to: string, params?: Obj) => string | undefined; declare const isComponent: (data: any) => data is Component; declare const isPage: (data: any) => data is Page; declare const isRedirect: (data: any) => data is Redirector; declare const isLeaf: (data: any) => data is Leaf; declare const isLazyComponent: (data: any) => data is LazyComponent; //#endregion export { BlockedNavigation, CONTEXT, Component, DefaultErrorPage, DefaultLoadingPage, DefaultOutlet, DynamicRoutePath, ERROR, ErrorProps, ExtractParam, ExtractParams, ExtractPaths, GUARD, Guard, GuardEntry, HasParam, JoinSegments, LAYOUT, LOAD, LOADING, LOADING_DELAY_MS, LOADING_MIN_DURATION_MS, LazyComponent, Leaf, LinkComponent, LinkPropsBase, LoadOptions, Loader, LoadingProps, MatchLevel, MatchState, MobxRenderSegment, MobxRouter, MobxRouterConfig, MobxRouterContext, MobxRouterRoutes, NavigateOptions, NavigationBlocker, NormalizeRootPath, Obj, Outlet, OutletConfig, OutletSlot, PAGE, Page, PageProps, PassThrough, REDIRECT, Redirect, RedirectOptions, RedirectTarget, Redirector, Route, RouteAt, RouteAtPrefix, RouteConfig, RouteContext, RouteContextAt, RouteDataAt, RouteErrorBoundary, RouteErrorBoundaryProps, RouteLevel, RoutePath, RoutePrefix, RouteSegmentState, RouteTarget, Router, RouterError, RouterErrorOptions, RouterErrorType, RouterOutlet, RouterProps, RouterStore, Routes, SPLASH, SegmentName, StaticRoutePath, WRAPPER, WithToAndParams, WrapperProps, confirmLeave, isComponent, isLazyComponent, isLeaf, isPage, isRedirect, makeErrorRoute, makeLinkComponent, makeRoute, makeRoutes, matchRoute, redirect, redirectFailed, resolvePath, routerContext, tryResolvePath, useConfirmLeave, useNavigationBlock, useRouter }; //# sourceMappingURL=router.d.mts.map