Documentation menu

@gyral/router

Gyral router: typed route tables, navigation as commands, History API baseline with Navigation API enhancement.

npm install @gyral/router

Writing code? Start from working code, not from this page. Routing. Adapt the closest example and check it, then look a name up here when you are about to call it.

Generated from the package's type declarations. Entry points: @gyral/router.

@gyral/router

Functions

Name Summary
listen Streams the current location, then every URL change, as messages until the component disconnects (a streaming command, ADR 0006/0009).
makeRouter A router driver.
navigate Navigates in-app (pushes a history entry, or replaces the current one).
routes
setHead Makes the document's head this Head (ADR 0019): the title, lang/dir, and every managed element (description, robots, canonical, meta, links, JSON-LD).

listen

function listen<M>(toMsg: (location: RouteLocation) => M | undefined): Command<M>;

Streams the current location, then every URL change, as messages until the component disconnects (a streaming command, ADR 0006/0009). Start it from subscriptions, which restarts it when a moved or re-attached component connects again (a command from init is not):

subscriptions: () => [listen((location) => ({ _tag: 'Routed', location }))]

makeRouter

function makeRouter(options?: RouterOptions): RouterDriver;

A router driver. Listeners are installed on first use, never at import time.

navigate

function navigate(url: string, options?: NavigateOptions): Command<never>;

Navigates in-app (pushes a history entry, or replaces the current one). In the browser, once the new page has rendered, a push scrolls to the #fragment target or the top and resets focus, as the browser does for a page load (ADR 0009 "Scroll and focus"); scroll: false or focusReset: false leaves that to the app. A replace (keeping the query in sync with a search box) leaves scroll and focus alone unless scroll: true / focusReset: true.

routes

function routes<const T extends RouteTable>(table: T, options?: {
    readonly matcher?: Matcher;
}): Routes<T>;

setHead

function setHead(head: Head): Command<never>;

Makes the document's head this Head (ADR 0019): the title, lang/dir, and every managed element (description, robots, canonical, meta, links, JSON-LD). Managed elements the new head doesn't name are removed; unchanged ones are not written; content from renderPage({ extraHead }) is never touched. The memory history records it instead. Build the head with one pure function and pass the same function's result to the server's renderPage():

Routed: (s, m) => [next, [setHead(pageHead(site.match(m.location.href), origin))]]

Constants

Name Summary
router The default router (global window, Navigation API when available).

router

const router: RouterDriver

The default router (global window, Navigation API when available).

Types and interfaces

You rarely write most of these yourself: TypeScript infers them from the functions above. Look one up when an example or a compiler error names it.

Name Summary
AfterNavigation What the browser history does once a navigation's render has committed (ADR 0009).
LocationLike What link capture and URL resolution need from a location (a URL qualifies).
Matcher
MemoryOptions
NavigateOptions Options of navigate().
Params Named required strings, optional strings and trailing catch-all arrays, inferred by path.
RouteLocation The document location, numbered in the order this router saw changes.
RouteMatch The route name discriminates its params.
RouterDriver
RouterInput
RouterOptions
RouterSnapshot The router's current URL and last head, for tests and server-side assertions.
Routes
RouteTable

AfterNavigation

interface AfterNavigation {
    /** Scroll to the `#fragment` target or the top (push), or restore (back/forward). */
    readonly scroll: boolean;
    /** Move focus to the first `[autofocus]` element, or reset it to the page start. */
    readonly focusReset: boolean;
}

What the browser history does once a navigation's render has committed (ADR 0009).

LocationLike

type LocationLike = Pick<Location, 'href' | 'origin' | 'pathname' | 'search'>;

What link capture and URL resolution need from a location (a URL qualifies).

Matcher

type Matcher = 'auto' | 'urlpattern' | 'fallback';

MemoryOptions

interface MemoryOptions {
    /** Starting URL, absolute or relative to `origin`. Default `'/'`. */
    readonly initial?: string;
    /** Origin that relative URLs resolve against. Default `'http://localhost'`. */
    readonly origin?: string;
    /**
     * Intercept same-origin link clicks. **Default `false`** (opt in): a router that captures
     * every link on the page breaks multi-page apps where only one component routes (ADR 0009).
     */
    readonly captureLinks?: boolean;
    /**
     * Where to capture link clicks when `captureLinks` is on. Default: the (global, or the
     * browser history's) `document`. Passing an element also turns capture on and limits it to
     * that subtree; `null` turns it off. Capture is only active while a component listens.
     */
    readonly linkRoot?: EventTarget | null;
}

NavigateOptions

interface NavigateOptions extends Partial<AfterNavigation> {
    /** Replace the current history entry instead of pushing one. Default `false`. */
    readonly replace?: boolean;
}

Options of navigate().

Params

type Params<Path extends string> = Path extends `${infer Head}/${infer Rest}` ? Param<Head> & Params<Rest> : Param<Path>;

Named required strings, optional strings and trailing catch-all arrays, inferred by path.

RouteLocation

interface RouteLocation {
    readonly href: string;
    readonly pathname: string;
    readonly search: string;
    readonly hash: string;
    /** Increases on every URL change seen by this router. */
    readonly seq: number;
}

The document location, numbered in the order this router saw changes.

RouteMatch

type RouteMatch<T extends RouteTable> = {
    [N in keyof T & string]: {
        readonly name: N;
        readonly params: Params<T[N]>;
        readonly path: string;
    };
}[keyof T & string];

The route name discriminates its params. path is the canonical href for those captures.

RouterDriver

interface RouterDriver extends Driver<RouterInput, RouteLocation | undefined> {
    /** The current URL and last head as this router sees them. */
    readonly snapshot: () => RouterSnapshot;
    /** Removes document listeners and ends running `listen` commands. */
    readonly dispose: () => void;
}

RouterInput

type RouterInput = ({
    readonly _tag: 'Navigate';
    readonly url: string;
    readonly replace: boolean;
} & Partial<AfterNavigation>) | {
    readonly _tag: 'Traverse';
    readonly delta: number;
} | {
    readonly _tag: 'Head';
    readonly head: Head;
} | {
    readonly _tag: 'Listen';
};

RouterOptions

interface RouterOptions extends MemoryOptions {
    /** Driver name used for substitution (`el.drivers`). Default `'router'`. */
    readonly name?: string;
    /**
     * `'browser'` (default) drives the real document. `'memory'` keeps entries in memory: no
     * global URL changes, no `window` needed (tests, servers). See `initial` and `origin`.
     * `captureLinks`/`linkRoot` apply to both histories.
     */
    readonly history?: 'browser' | 'memory';
    /** Browser history: default the global window, resolved on first use (safe on a server). */
    readonly window?: Window;
    /** Browser history: use the Navigation API when present. Default `true`. */
    readonly navigationApi?: boolean;
    /**
     * Browser history, once a navigation has rendered: scroll to the `#fragment` target or the
     * top (push) and restore the position on back/forward. Default `true`; `false` for apps that
     * manage scroll themselves. A replace never scrolls unless `navigate(url, { scroll: true })`;
     * `navigate(url, { scroll })` overrides it per call.
     */
    readonly scroll?: boolean;
    /**
     * Browser history, once a navigation has rendered: move focus to the first `[autofocus]`
     * element, or reset it to the page start, unless something took focus during the navigation
     * (a `focus('main h1')` command). Default `true` (a replace: only with `focusReset: true`).
     * `navigate(url, { focusReset })` per call.
     */
    readonly focusReset?: boolean;
}

RouterSnapshot

interface RouterSnapshot {
    readonly href: string;
    /** The last `setHead()` head, if any (the browser applies it to the document too). */
    readonly head: Head | undefined;
    /** Number of history entries (memory history; `history.length` in a browser). */
    readonly length: number;
}

The router's current URL and last head, for tests and server-side assertions.

Routes

interface Routes<T extends RouteTable> {
    readonly table: T;
    /** First route in table order matching the pathname. A string beginning with / is a path. */
    match(url: string | URL): RouteMatch<T> | undefined;
    /** Percent-encode each parameter or catch-all segment; omit absent optional parameters. */
    href<N extends keyof T & string>(name: N, params: Params<T[N]>): string;
}

RouteTable

type RouteTable = Readonly<Record<string, string>>;