UNPKG

shelving

Version:

Toolkit for using data in JavaScript.

52 lines (51 loc) 2.93 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { NotFoundError } from "../../error/RequestError.js"; import { UnexpectedError } from "../../error/UnexpectedError.js"; import { getProps } from "../../util/index.js"; import { matchPathTemplate, renderPathTemplate } from "../../util/template.js"; import { MetaContext, requireMetaURL } from "../misc/MetaContext.js"; /** * Match the current URL against `routes` and render the matched element. * - Reads `url` and `base` from the surrounding `<Meta>` context (override via props). * - When `base` is set, the effective path is the URL after stripping the base prefix. * - Nest by putting another `<Router>` inside a route's value; pass `base="/section"` (or wrap in `<Meta>`) to scope. * - Route `{placeholders}` are passed as props to function/component route values along with merged URL `?query` params. They are not published into context — descendants of a `ReactElement`-valued route can't see them automatically. * - Returns `null` when there's no URL in context or the URL is outside the base. * * @throws {NotFoundError} if no route matches and `fallback` is `undefined` */ export function Router({ routes, fallback, ...meta }) { const combined = requireMetaURL(meta); const path = combined; const route = _matchRoute(routes, fallback, combined); if (route) return _jsx(MetaContext, { value: combined, children: route }); throw new NotFoundError("Tree route not found", { received: path }); } function _matchRoute(routes, fallback, meta, path = meta.path, depth = 0) { for (const [route, Route] of getProps(routes)) { // Try to match this path. const placeholders = matchPathTemplate(route, path); if (!placeholders) continue; // Skip falsy. Allows a route to be conditionally disabled by setting its value to `null` or `false`. if (!Route) continue; // String value is a redirect; re-run matching with the new path. Guard against infinite redirect loops by limiting depth. if (typeof Route === "string") { if (depth > 10) throw new UnexpectedError("Infinite redirect loop", { received: route, expected: path, caller: _matchRoute }); return _matchRoute(routes, fallback, meta, renderPathTemplate(Route, placeholders), depth + 1); } // React element — render as-is. if (typeof Route !== "function") return Route; // Component — render with merged URL query params and route placeholders as props (placeholders win on conflict). return _jsx(Route, { ...meta.params, ...placeholders }, path); } // No match, try the fallback. if (fallback !== undefined) return fallback; // Fallback is undefined, throw a `NotFoundError` throw new NotFoundError("No matching route found", { received: path, caller: _matchRoute }); }