shelving
Version:
Toolkit for using data in JavaScript.
52 lines (51 loc) • 2.93 kB
JavaScript
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 });
}