/**
* `@nifrajs/web-react/router` - React routing bindings over the agnostic `@nifrajs/web` router:
* ``/``, `useNavigate`, `useParams`, `useLocation`, `useSearchParams`, and ``.
*
* These read the current route from a {@link RouterContext} that `compose` provides on BOTH the SSR
* render and the client mount (seeded from the request match / router state respectively), so the read
* hooks (`useParams`/`useLocation`/`useSearchParams`) are SSR-correct and hydrate with no mismatch.
* Navigation goes through `@nifrajs/web`'s DOM-free navigate bridge (`getBrowserNavigate`, populated by
* `installHistory`) - so this module imports only `react` (never `react-dom/*`), and a route component
* can use these on the server and the client without dragging a DOM build into the wrong bundle. No JSX
* (the package builds with plain `tsc`), so everything is `createElement`.
*/
import type { InferOutput, StandardSchemaV1 } from "@nifrajs/core/server";
import type { Blocker, BlockerFunction, BlockerState, NavigateFunction, NavigateOptions } from "@nifrajs/web";
import { type AnchorHTMLAttributes, type CSSProperties, type ReactNode } from "react";
export type { Blocker, BlockerFunction, BlockerState, NavigateFunction };
/** The current route the routing hooks read. Provided by `compose` on SSR + client mount alike. */
export interface RouterContextValue {
/** The matched route's decoded path params (`/users/:id` → `{ id: "7" }`). */
readonly params: Readonly>;
/** The current URL's `pathname + search` (no hash - the router never carries one). */
readonly path: string;
/** The route's typed, validated search params (the loader's `ctx.search`), derived from the URL via
* the shared `searchOf` on SSR + client alike, read by {@link useSearch}. `{}` when the route
* declares no `searchSchema` (then it is the raw parsed query) or outside a nifra route tree. */
readonly search: Record;
/** True while a client navigation (or revalidation) is in flight - the current route stays mounted
* until the new one is ready. Always `false` on SSR (loaders block before render). Drives loading UI
* via {@link useNavigation}. */
readonly pending: boolean;
/** The `pathname + search` a navigation is transitioning TO while `pending` (`undefined` when idle or
* during a same-route revalidation). Powers {@link NavLink}'s per-link `isPending`. */
readonly pendingPath?: string | undefined;
}
export declare const RouterContext: import("react").Context;
/**
* The matched route's decoded path params - `/users/:id` on `/users/7` → `{ id: "7" }`. SSR-correct:
* `compose` provides the same value server-side (from the request match) and client-side (from router
* state), so a param rendered into markup doesn't flash on hydration.
*/
export declare function useParams = Record>(): Readonly;
/**
* The route's typed, validated search params: the SAME value the loader received as `ctx.search`.
* SSR-correct: `compose` provides it from `searchOf(searchSchema, url.search)` server-side and from the
* identical derivation on the client mount, so a value rendered from it doesn't flash on hydration.
* Hostile input already failed closed to the schema's defaults at match time, so a component never
* parses `window.location.search` or guards against a bad query.
*
* Pass the route's `searchSchema` as the type argument to get its output type; bare, it's the raw parsed
* query (`Record`), which is also what a route without a `searchSchema` yields.
*
* ```tsx
* export const searchSchema = v.object({ page: v.optional(v.fallback(v.number(), 1), 1) })
* const { page } = useSearch() // page: number
* ```
*/
export declare function useSearch(): Schema extends StandardSchemaV1 ? InferOutput : Record;
/** The parsed current location. `hash` is always `""` - the fragment is client-only and never reaches
* the router state / server, so exposing a live hash would hydration-mismatch; read `window.location.hash`
* directly (in an effect) if you truly need it. */
export interface Location {
readonly pathname: string;
readonly search: string;
readonly hash: string;
}
/** The current {@link Location} (`pathname`/`search`/`hash`), derived from the router context. */
export declare function useLocation(): Location;
/** The current navigation state, mirroring the Remix `useNavigation()` shape for familiarity. */
export interface Navigation {
/** True while a client navigation (or revalidation) is in flight. The current route stays mounted
* until the new one is ready, so this drives a loading indicator, not a route swap. */
readonly pending: boolean;
/** `"loading"` while a navigation is in flight, else `"idle"`. */
readonly state: "idle" | "loading";
/** The `pathname + search` being navigated TO while `pending`; `undefined` when idle or during a
* same-route revalidation. Mirrors Remix's `useNavigation().location`. */
readonly location: string | undefined;
}
/**
* Observe client navigation to drive loading UI (a top-bar spinner, dimmed content, a skeleton). nifra
* navigates imperatively - it fetches the next route's chunk + loader data while the current route stays
* on screen, then swaps - so `pending` is the signal for "a transition is in flight," not a Suspense
* boundary. Always `{ pending: false, state: "idle" }` on the server (loaders block before render), so
* it is hydration-safe.
*
* ```tsx
* const { pending } = useNavigation()
* return