@pyreon/router 0.12.14 → 0.12.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/analysis/index.js.html +1 -1
- package/lib/index.js +7 -2
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/loader.ts +4 -1
- package/src/router.ts +5 -2
- package/src/scroll.ts +13 -0
- package/src/tests/loader.test.ts +18 -0
- package/src/tests/scroll.test.ts +31 -0
|
@@ -5386,7 +5386,7 @@ var drawChart = (function (exports) {
|
|
|
5386
5386
|
</script>
|
|
5387
5387
|
<script>
|
|
5388
5388
|
/*<!--*/
|
|
5389
|
-
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"
|
|
5389
|
+
const data = {"version":2,"tree":{"name":"root","children":[{"name":"index.js","children":[{"name":"src","children":[{"uid":"792f9763-1","name":"loader.ts"},{"uid":"792f9763-3","name":"match.ts"},{"uid":"792f9763-5","name":"scroll.ts"},{"uid":"792f9763-7","name":"types.ts"},{"uid":"792f9763-9","name":"router.ts"},{"uid":"792f9763-11","name":"components.tsx"},{"uid":"792f9763-13","name":"index.ts"}]}]}],"isRoot":true},"nodeParts":{"792f9763-1":{"renderedLength":2824,"gzipLength":1233,"brotliLength":0,"metaUid":"792f9763-0"},"792f9763-3":{"renderedLength":12203,"gzipLength":3691,"brotliLength":0,"metaUid":"792f9763-2"},"792f9763-5":{"renderedLength":2194,"gzipLength":899,"brotliLength":0,"metaUid":"792f9763-4"},"792f9763-7":{"renderedLength":385,"gzipLength":246,"brotliLength":0,"metaUid":"792f9763-6"},"792f9763-9":{"renderedLength":23157,"gzipLength":6355,"brotliLength":0,"metaUid":"792f9763-8"},"792f9763-11":{"renderedLength":7106,"gzipLength":2622,"brotliLength":0,"metaUid":"792f9763-10"},"792f9763-13":{"renderedLength":0,"gzipLength":0,"brotliLength":0,"metaUid":"792f9763-12"}},"nodeMetas":{"792f9763-0":{"id":"/src/loader.ts","moduleParts":{"index.js":"792f9763-1"},"imported":[{"uid":"792f9763-14"}],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-10"}]},"792f9763-2":{"id":"/src/match.ts","moduleParts":{"index.js":"792f9763-3"},"imported":[],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-8"}]},"792f9763-4":{"id":"/src/scroll.ts","moduleParts":{"index.js":"792f9763-5"},"imported":[],"importedBy":[{"uid":"792f9763-8"}]},"792f9763-6":{"id":"/src/types.ts","moduleParts":{"index.js":"792f9763-7"},"imported":[],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-8"}]},"792f9763-8":{"id":"/src/router.ts","moduleParts":{"index.js":"792f9763-9"},"imported":[{"uid":"792f9763-14"},{"uid":"792f9763-15"},{"uid":"792f9763-2"},{"uid":"792f9763-4"},{"uid":"792f9763-6"}],"importedBy":[{"uid":"792f9763-12"},{"uid":"792f9763-10"}]},"792f9763-10":{"id":"/src/components.tsx","moduleParts":{"index.js":"792f9763-11"},"imported":[{"uid":"792f9763-14"},{"uid":"792f9763-0"},{"uid":"792f9763-8"}],"importedBy":[{"uid":"792f9763-12"}]},"792f9763-12":{"id":"/src/index.ts","moduleParts":{"index.js":"792f9763-13"},"imported":[{"uid":"792f9763-10"},{"uid":"792f9763-0"},{"uid":"792f9763-2"},{"uid":"792f9763-8"},{"uid":"792f9763-6"}],"importedBy":[],"isEntry":true},"792f9763-14":{"id":"@pyreon/core","moduleParts":{},"imported":[],"importedBy":[{"uid":"792f9763-10"},{"uid":"792f9763-0"},{"uid":"792f9763-8"}]},"792f9763-15":{"id":"@pyreon/reactivity","moduleParts":{},"imported":[],"importedBy":[{"uid":"792f9763-8"}]}},"env":{"rollup":"4.23.0"},"options":{"gzip":true,"brotli":false,"sourcemap":false}};
|
|
5390
5390
|
|
|
5391
5391
|
const run = () => {
|
|
5392
5392
|
const width = window.innerWidth;
|
package/lib/index.js
CHANGED
|
@@ -34,7 +34,6 @@ function useLoaderData() {
|
|
|
34
34
|
async function prefetchLoaderData(router, path) {
|
|
35
35
|
const route = router._resolve(path);
|
|
36
36
|
const ac = new AbortController();
|
|
37
|
-
router._abortController = ac;
|
|
38
37
|
await Promise.all(route.matched.filter((r) => r.loader).map(async (r) => {
|
|
39
38
|
const data = await r.loader?.({
|
|
40
39
|
params: route.params,
|
|
@@ -498,6 +497,7 @@ function buildNameIndex(routes) {
|
|
|
498
497
|
* Saves scroll position before each navigation and restores it when
|
|
499
498
|
* navigating back to a previously visited path.
|
|
500
499
|
*/
|
|
500
|
+
const MAX_SCROLL_POSITIONS = 100;
|
|
501
501
|
var ScrollManager = class {
|
|
502
502
|
_positions = /* @__PURE__ */ new Map();
|
|
503
503
|
_behavior;
|
|
@@ -507,7 +507,13 @@ var ScrollManager = class {
|
|
|
507
507
|
/** Call before navigating away — saves current scroll position for `fromPath` */
|
|
508
508
|
save(fromPath) {
|
|
509
509
|
if (typeof window === "undefined") return;
|
|
510
|
+
if (this._positions.has(fromPath)) this._positions.delete(fromPath);
|
|
510
511
|
this._positions.set(fromPath, window.scrollY);
|
|
512
|
+
while (this._positions.size > MAX_SCROLL_POSITIONS) {
|
|
513
|
+
const oldest = this._positions.keys().next().value;
|
|
514
|
+
if (oldest === void 0) break;
|
|
515
|
+
this._positions.delete(oldest);
|
|
516
|
+
}
|
|
511
517
|
}
|
|
512
518
|
/** Call after navigation is committed — applies scroll behavior */
|
|
513
519
|
restore(to, from) {
|
|
@@ -1152,7 +1158,6 @@ function createRouter(options) {
|
|
|
1152
1158
|
componentCache.set(record, comp);
|
|
1153
1159
|
}));
|
|
1154
1160
|
const ac = new AbortController();
|
|
1155
|
-
router._abortController = ac;
|
|
1156
1161
|
await Promise.all(resolved.matched.filter((r) => r.loader).map(async (r) => {
|
|
1157
1162
|
const data = await r.loader?.({
|
|
1158
1163
|
params: resolved.params,
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/loader.ts","../src/match.ts","../src/scroll.ts","../src/types.ts","../src/router.ts","../src/components.tsx"],"sourcesContent":["import type { Context } from '@pyreon/core'\nimport { createContext, useContext } from '@pyreon/core'\nimport type { RouterInstance } from './types'\n\n/**\n * Context frame that holds the loader data for the currently rendered route record.\n * Pushed by RouterView's withLoaderData wrapper before invoking the route component.\n */\nexport const LoaderDataContext: Context<unknown> = createContext<unknown>(undefined)\n\n/**\n * Returns the data resolved by the current route's `loader` function.\n * Must be called inside a route component rendered by <RouterView />.\n *\n * @example\n * const routes = [{ path: \"/users\", component: Users, loader: fetchUsers }]\n *\n * function Users() {\n * const users = useLoaderData<User[]>()\n * return h(\"ul\", null, users.map(u => h(\"li\", null, u.name)))\n * }\n */\nexport function useLoaderData<T = unknown>(): T {\n return useContext(LoaderDataContext) as T\n}\n\n/**\n * SSR helper: pre-run all loaders for the given path before rendering.\n * Call this before `renderToString` so route components can read data via `useLoaderData()`.\n *\n * @example\n * const router = createRouter({ routes, url: req.url })\n * await prefetchLoaderData(router, req.url)\n * const html = await renderToString(h(App, { router }))\n */\nexport async function prefetchLoaderData(router: RouterInstance, path: string): Promise<void> {\n const route = router._resolve(path)\n const ac = new AbortController()\n router._abortController = ac\n await Promise.all(\n route.matched\n .filter((r) => r.loader)\n .map(async (r) => {\n const data = await r.loader?.({\n params: route.params,\n query: route.query,\n signal: ac.signal,\n })\n router._loaderData.set(r, data)\n }),\n )\n}\n\n/**\n * Serialize loader data to a JSON-safe plain object for embedding in SSR HTML.\n * Keys are route path patterns (stable across server and client).\n *\n * @example — SSR handler:\n * await prefetchLoaderData(router, req.url)\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `...${head}\n * <script>window.__PYREON_LOADER_DATA__=${JSON.stringify(serializeLoaderData(router))}</script>\n * ...${html}...`\n */\nexport function serializeLoaderData(router: RouterInstance): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const [record, data] of router._loaderData) {\n result[record.path] = data\n }\n return result\n}\n\n/**\n * Hydrate loader data from a serialized object (e.g. `window.__PYREON_LOADER_DATA__`).\n * Populates the router's internal `_loaderData` map so the initial render uses\n * server-fetched data without re-running loaders on the client.\n *\n * Call this before `mount()`, after `createRouter()`.\n *\n * @example — client entry:\n * import { hydrateLoaderData } from \"@pyreon/router\"\n * const router = createRouter({ routes })\n * hydrateLoaderData(router, window.__PYREON_LOADER_DATA__ ?? {})\n * mount(h(App, null), document.getElementById(\"app\")!)\n */\nexport function hydrateLoaderData(\n router: RouterInstance,\n serialized: Record<string, unknown>,\n): void {\n if (!serialized || typeof serialized !== 'object') return\n const route = router._resolve(router.currentRoute().path)\n for (const record of route.matched) {\n if (Object.hasOwn(serialized, record.path)) {\n router._loaderData.set(record, serialized[record.path])\n }\n }\n}\n","import type { ResolvedRoute, RouteMeta, RouteRecord } from './types'\n\n// ─── Query string ─────────────────────────────────────────────────────────────\n\n/**\n * Parse a query string into key-value pairs. Duplicate keys are overwritten\n * (last value wins). Use `parseQueryMulti` to preserve duplicates as arrays.\n */\nexport function parseQuery(qs: string): Record<string, string> {\n if (!qs) return {}\n const result: Record<string, string> = {}\n for (const part of qs.split('&')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx < 0) {\n const key = decodeURIComponent(part)\n if (key) result[key] = ''\n } else {\n const key = decodeURIComponent(part.slice(0, eqIdx))\n const val = decodeURIComponent(part.slice(eqIdx + 1))\n if (key) result[key] = val\n }\n }\n return result\n}\n\n/**\n * Parse a query string preserving duplicate keys as arrays.\n *\n * @example\n * parseQueryMulti(\"color=red&color=blue&size=lg\")\n * // → { color: [\"red\", \"blue\"], size: \"lg\" }\n */\nexport function parseQueryMulti(qs: string): Record<string, string | string[]> {\n if (!qs) return {}\n const result: Record<string, string | string[]> = {}\n for (const part of qs.split('&')) {\n const eqIdx = part.indexOf('=')\n let key: string\n let val: string\n if (eqIdx < 0) {\n key = decodeURIComponent(part)\n val = ''\n } else {\n key = decodeURIComponent(part.slice(0, eqIdx))\n val = decodeURIComponent(part.slice(eqIdx + 1))\n }\n if (!key) continue\n const existing = result[key]\n if (existing === undefined) {\n result[key] = val\n } else if (Array.isArray(existing)) {\n existing.push(val)\n } else {\n result[key] = [existing, val]\n }\n }\n return result\n}\n\nexport function stringifyQuery(query: Record<string, string>): string {\n const parts: string[] = []\n for (const [k, v] of Object.entries(query)) {\n parts.push(v ? `${encodeURIComponent(k)}=${encodeURIComponent(v)}` : encodeURIComponent(k))\n }\n return parts.length ? `?${parts.join('&')}` : ''\n}\n\n// ─── Compiled route structures ───────────────────────────────────────────────\n\n/**\n * Pre-compiled segment info — computed once per route, reused on every match.\n * Avoids repeated split()/filter()/startsWith(\":\") on hot paths.\n */\ninterface CompiledSegment {\n /** Original segment string */\n raw: string\n /** true if this segment is a `:param` */\n isParam: boolean\n /** true if this segment is a `:param*` splat */\n isSplat: boolean\n /** true if this segment is a `:param?` optional */\n isOptional: boolean\n /** Param name (without leading `:` and trailing `*`/`?`) — empty for static segments */\n paramName: string\n}\n\ninterface CompiledRoute {\n route: RouteRecord\n /** true for wildcard patterns: `(.*)` or `*` */\n isWildcard: boolean\n /** Pre-split and classified segments */\n segments: CompiledSegment[]\n /** Number of segments */\n segmentCount: number\n /** true if route has no dynamic segments (pure static) */\n isStatic: boolean\n /** For static routes: the normalized path (e.g. \"/about\") for Map lookup */\n staticPath: string | null\n /** Compiled children (lazily populated) */\n children: CompiledRoute[] | null\n /** First static segment (for dispatch index), null if first segment is dynamic or route is wildcard */\n firstSegment: string | null\n}\n\n/**\n * A flattened route entry — pre-joins parent+child segments at compile time\n * so nested routes can be matched in a single pass without recursion.\n */\ninterface FlattenedRoute {\n /** All segments from root to leaf, concatenated */\n segments: CompiledSegment[]\n segmentCount: number\n /** The full matched chain from root to leaf (e.g. [adminLayout, usersPage]) */\n matchedChain: RouteRecord[]\n /** true if all segments are static */\n isStatic: boolean\n /** For static flattened routes: the full joined path */\n staticPath: string | null\n /** Pre-merged meta from all routes in the chain */\n meta: RouteMeta\n /** First static segment for dispatch index */\n firstSegment: string | null\n /** true if any segment is a splat */\n hasSplat: boolean\n /** true if this is a wildcard catch-all route (`*` or `(.*)`) */\n isWildcard: boolean\n /** true if any segment is optional (`:param?`) */\n hasOptional: boolean\n /** Minimum number of segments that must be present (excluding trailing optionals) */\n minSegments: number\n}\n\n/** WeakMap cache: compile each RouteRecord[] once */\nconst _compiledCache = new WeakMap<RouteRecord[], CompiledRoute[]>()\n\nfunction compileSegment(raw: string): CompiledSegment {\n if (raw.endsWith('*') && raw.startsWith(':')) {\n return { raw, isParam: true, isSplat: true, isOptional: false, paramName: raw.slice(1, -1) }\n }\n if (raw.endsWith('?') && raw.startsWith(':')) {\n return { raw, isParam: true, isSplat: false, isOptional: true, paramName: raw.slice(1, -1) }\n }\n if (raw.startsWith(':')) {\n return { raw, isParam: true, isSplat: false, isOptional: false, paramName: raw.slice(1) }\n }\n return { raw, isParam: false, isSplat: false, isOptional: false, paramName: '' }\n}\n\nfunction compileRoute(route: RouteRecord): CompiledRoute {\n const pattern = route.path\n const isWildcard = pattern === '(.*)' || pattern === '*'\n\n if (isWildcard) {\n return {\n route,\n isWildcard: true,\n segments: [],\n segmentCount: 0,\n isStatic: false,\n staticPath: null,\n children: null,\n firstSegment: null,\n }\n }\n\n const segments = pattern.split('/').filter(Boolean).map(compileSegment)\n const isStatic = segments.every((s) => !s.isParam)\n const staticPath = isStatic ? `/${segments.map((s) => s.raw).join('/')}` : null\n const first = segments.length > 0 ? segments[0] : undefined\n const firstSegment = first && !first.isParam ? first.raw : null\n\n return {\n route,\n isWildcard: false,\n segments,\n segmentCount: segments.length,\n isStatic,\n staticPath,\n children: null,\n firstSegment,\n }\n}\n\n/** Expand alias paths into additional compiled entries sharing the original RouteRecord */\nfunction expandAliases(r: RouteRecord, c: CompiledRoute): CompiledRoute[] {\n if (!r.alias) return []\n const aliases = Array.isArray(r.alias) ? r.alias : [r.alias]\n return aliases.map((aliasPath) => {\n const { alias: _, ...withoutAlias } = r\n const ac = compileRoute({ ...withoutAlias, path: aliasPath })\n ac.children = c.children\n ac.route = r\n return ac\n })\n}\n\nfunction compileRoutes(routes: RouteRecord[]): CompiledRoute[] {\n const cached = _compiledCache.get(routes)\n if (cached) return cached\n\n const compiled: CompiledRoute[] = []\n for (const r of routes) {\n const c = compileRoute(r)\n if (r.children && r.children.length > 0) {\n c.children = compileRoutes(r.children)\n }\n compiled.push(c)\n compiled.push(...expandAliases(r, c))\n }\n _compiledCache.set(routes, compiled)\n return compiled\n}\n\n// ─── Route flattening ────────────────────────────────────────────────────────\n\n/** Extract first static segment from a segment list, or null if dynamic/empty */\nfunction getFirstSegment(segments: CompiledSegment[]): string | null {\n const first = segments[0]\n if (first && !first.isParam) return first.raw\n return null\n}\n\n/** Build a FlattenedRoute from segments + metadata */\nfunction makeFlatEntry(\n segments: CompiledSegment[],\n chain: RouteRecord[],\n meta: RouteMeta,\n isWildcard: boolean,\n): FlattenedRoute {\n const isStatic = !isWildcard && segments.every((s) => !s.isParam)\n const hasOptional = segments.some((s) => s.isOptional)\n // minSegments: count of segments up to and not including trailing optionals\n let minSegs = segments.length\n if (hasOptional) {\n while (minSegs > 0 && segments[minSegs - 1]?.isOptional) minSegs--\n }\n return {\n segments,\n segmentCount: segments.length,\n matchedChain: chain,\n isStatic,\n staticPath: isStatic ? `/${segments.map((s) => s.raw).join('/')}` : null,\n meta,\n firstSegment: getFirstSegment(segments),\n hasSplat: segments.some((s) => s.isSplat),\n isWildcard,\n hasOptional,\n minSegments: minSegs,\n }\n}\n\n/**\n * Flatten nested routes into leaf entries with pre-joined segments.\n * This eliminates recursion during matching for the common case.\n */\nfunction flattenRoutes(compiled: CompiledRoute[]): FlattenedRoute[] {\n const result: FlattenedRoute[] = []\n flattenWalk(result, compiled, [], [], {})\n return result\n}\n\nfunction flattenWalk(\n result: FlattenedRoute[],\n routes: CompiledRoute[],\n parentSegments: CompiledSegment[],\n parentChain: RouteRecord[],\n parentMeta: RouteMeta,\n): void {\n for (const c of routes) {\n const chain = [...parentChain, c.route]\n const meta = c.route.meta ? { ...parentMeta, ...c.route.meta } : { ...parentMeta }\n flattenOne(result, c, parentSegments, chain, meta)\n }\n}\n\nfunction flattenOne(\n result: FlattenedRoute[],\n c: CompiledRoute,\n parentSegments: CompiledSegment[],\n chain: RouteRecord[],\n meta: RouteMeta,\n): void {\n if (c.isWildcard) {\n result.push(makeFlatEntry(parentSegments, chain, meta, true))\n if (c.children && c.children.length > 0) {\n flattenWalk(result, c.children, parentSegments, chain, meta)\n }\n return\n }\n\n const joined = [...parentSegments, ...c.segments]\n if (c.children && c.children.length > 0) {\n flattenWalk(result, c.children, joined, chain, meta)\n }\n result.push(makeFlatEntry(joined, chain, meta, false))\n}\n\n// ─── Combined index ─────────────────────────────────────────────────────────\n\ninterface RouteIndex {\n /** O(1) lookup for fully static paths (including nested) */\n staticMap: Map<string, FlattenedRoute>\n /** First-segment dispatch: maps first path segment → candidate routes */\n segmentMap: Map<string, FlattenedRoute[]>\n /** Routes whose first segment is dynamic (fallback) */\n dynamicFirst: FlattenedRoute[]\n /** Wildcard/catch-all routes */\n wildcards: FlattenedRoute[]\n}\n\nconst _indexCache = new WeakMap<RouteRecord[], RouteIndex>()\n\n/** Classify a single flattened route into the appropriate index bucket */\nfunction indexFlatRoute(\n f: FlattenedRoute,\n staticMap: Map<string, FlattenedRoute>,\n segmentMap: Map<string, FlattenedRoute[]>,\n dynamicFirst: FlattenedRoute[],\n wildcards: FlattenedRoute[],\n): void {\n // Static map: first static entry wins (preserves definition order)\n if (f.isStatic && f.staticPath && !staticMap.has(f.staticPath)) {\n staticMap.set(f.staticPath, f)\n }\n\n if (f.isWildcard) {\n wildcards.push(f)\n return\n }\n\n // Root route \"/\" has 0 segments — already in static map\n if (f.segmentCount === 0) return\n\n // First-segment dispatch\n if (f.firstSegment) {\n let bucket = segmentMap.get(f.firstSegment)\n if (!bucket) {\n bucket = []\n segmentMap.set(f.firstSegment, bucket)\n }\n bucket.push(f)\n } else {\n dynamicFirst.push(f)\n }\n}\n\nfunction buildRouteIndex(routes: RouteRecord[], compiled: CompiledRoute[]): RouteIndex {\n const cached = _indexCache.get(routes)\n if (cached) return cached\n\n const flattened = flattenRoutes(compiled)\n\n const staticMap = new Map<string, FlattenedRoute>()\n const segmentMap = new Map<string, FlattenedRoute[]>()\n const dynamicFirst: FlattenedRoute[] = []\n const wildcards: FlattenedRoute[] = []\n\n for (const f of flattened) {\n indexFlatRoute(f, staticMap, segmentMap, dynamicFirst, wildcards)\n }\n\n const index: RouteIndex = { staticMap, segmentMap, dynamicFirst, wildcards }\n _indexCache.set(routes, index)\n return index\n}\n\n// ─── Fast path splitting ─────────────────────────────────────────────────────\n\n/** Split path into segments without allocating a filtered array */\nfunction splitPath(path: string): string[] {\n // Fast path for common cases\n if (path === '/') return []\n // Remove leading slash, split, no filter needed if path is clean\n const start = path.charCodeAt(0) === 47 /* / */ ? 1 : 0\n const end = path.length\n if (start >= end) return []\n\n const parts: string[] = []\n let segStart = start\n for (let i = start; i <= end; i++) {\n if (i === end || path.charCodeAt(i) === 47 /* / */) {\n if (i > segStart) {\n parts.push(path.substring(segStart, i))\n }\n segStart = i + 1\n }\n }\n return parts\n}\n\n/** Decode only if the segment contains a `%` character */\nfunction decodeSafe(s: string): string {\n return s.indexOf('%') >= 0 ? decodeURIComponent(s) : s\n}\n\n// ─── Path matching (compiled) ────────────────────────────────────────────────\n\n/**\n * Match a single route pattern against a path segment.\n * Returns extracted params or null if no match.\n *\n * Supports:\n * - Exact segments: \"/about\"\n * - Param segments: \"/user/:id\"\n * - Wildcard: \"(.*)\" matches everything\n */\n/** Match a single pattern segment against a path segment, extracting params. Returns false on mismatch. */\nfunction matchPatternSegment(\n pp: string,\n pt: string | undefined,\n params: Record<string, string>,\n pathParts: string[],\n i: number,\n): 'splat' | 'continue' | 'fail' {\n if (pp.endsWith('*') && pp.startsWith(':')) {\n params[pp.slice(1, -1)] = pathParts.slice(i).map(decodeURIComponent).join('/')\n return 'splat'\n }\n if (pp.endsWith('?') && pp.startsWith(':')) {\n if (pt !== undefined) params[pp.slice(1, -1)] = decodeURIComponent(pt)\n return 'continue'\n }\n if (pt === undefined) return 'fail'\n if (pp.startsWith(':')) {\n params[pp.slice(1)] = decodeURIComponent(pt)\n return 'continue'\n }\n return pp === pt ? 'continue' : 'fail'\n}\n\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n if (pattern === '(.*)' || pattern === '*') return {}\n\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const result = matchPatternSegment(\n patternParts[i] as string,\n pathParts[i],\n params,\n pathParts,\n i,\n )\n if (result === 'splat') return params\n if (result === 'fail') return null\n }\n\n if (pathParts.length > patternParts.length) return null\n return params\n}\n\n// ─── Compiled matching helpers ────────────────────────────────────────────────\n\n/** Collect remaining path segments as a decoded splat value */\nfunction captureSplat(pathParts: string[], from: number, pathLen: number): string {\n const remaining: string[] = []\n for (let j = from; j < pathLen; j++) {\n const p = pathParts[j]\n if (p !== undefined) remaining.push(decodeSafe(p))\n }\n return remaining.join('/')\n}\n\n// ─── Flattened route matching ─────────────────────────────────────────────────\n\n/** Check whether a flattened route's segment count is compatible with the path length */\nfunction isSegmentCountCompatible(f: FlattenedRoute, pathLen: number): boolean {\n if (f.segmentCount === pathLen) return true\n if (f.hasSplat && pathLen >= f.segmentCount) return true\n if (f.hasOptional && pathLen >= f.minSegments && pathLen <= f.segmentCount) return true\n return false\n}\n\n/** Try to match a flattened route against path parts */\nfunction matchFlattened(\n f: FlattenedRoute,\n pathParts: string[],\n pathLen: number,\n): Record<string, string> | null {\n if (!isSegmentCountCompatible(f, pathLen)) return null\n\n const params: Record<string, string> = {}\n const segments = f.segments\n const count = f.segmentCount\n for (let i = 0; i < count; i++) {\n const seg = segments[i]\n const pt = pathParts[i]\n if (!seg) return null\n if (seg.isSplat) {\n params[seg.paramName] = captureSplat(pathParts, i, pathLen)\n return params\n }\n if (pt === undefined) {\n if (!seg.isOptional) return null\n continue\n }\n if (seg.isParam) {\n params[seg.paramName] = decodeSafe(pt)\n } else if (seg.raw !== pt) {\n return null\n }\n }\n return params\n}\n\n/** Search a list of flattened candidates for a match */\nfunction searchCandidates(\n candidates: FlattenedRoute[],\n pathParts: string[],\n pathLen: number,\n): MatchResult | null {\n for (let i = 0; i < candidates.length; i++) {\n const f = candidates[i]\n if (!f) continue\n const params = matchFlattened(f, pathParts, pathLen)\n if (params) {\n return { params, matched: f.matchedChain }\n }\n }\n return null\n}\n\n// ─── Route resolution ─────────────────────────────────────────────────────────\n\ninterface MatchResult {\n params: Record<string, string>\n matched: RouteRecord[]\n}\n\n/**\n * Resolve a raw path (including query string and hash) against the route tree.\n * Uses flattened index for O(1) static lookup and first-segment dispatch.\n */\nexport function resolveRoute(rawPath: string, routes: RouteRecord[]): ResolvedRoute {\n const qIdx = rawPath.indexOf('?')\n const pathAndHash = qIdx >= 0 ? rawPath.slice(0, qIdx) : rawPath\n const queryPart = qIdx >= 0 ? rawPath.slice(qIdx + 1) : ''\n\n const hIdx = pathAndHash.indexOf('#')\n const cleanPath = hIdx >= 0 ? pathAndHash.slice(0, hIdx) : pathAndHash\n const hash = hIdx >= 0 ? pathAndHash.slice(hIdx + 1) : ''\n\n const query = parseQuery(queryPart)\n\n // Build index (cached after first call)\n const compiled = compileRoutes(routes)\n const index = buildRouteIndex(routes, compiled)\n\n // Fast path 1: O(1) static Map lookup (covers nested static too)\n const staticMatch = index.staticMap.get(cleanPath)\n if (staticMatch) {\n return {\n path: cleanPath,\n params: {},\n query,\n hash,\n matched: staticMatch.matchedChain,\n meta: staticMatch.meta,\n }\n }\n\n // Split path for segment-based matching\n const pathParts = splitPath(cleanPath)\n const pathLen = pathParts.length\n\n // Fast path 2: first-segment dispatch (O(1) bucket lookup + small scan)\n if (pathLen > 0) {\n const first = pathParts[0] as string\n const bucket = index.segmentMap.get(first)\n if (bucket) {\n const match = searchCandidates(bucket, pathParts, pathLen)\n if (match) {\n return {\n path: cleanPath,\n params: match.params,\n query,\n hash,\n matched: match.matched,\n meta: mergeMeta(match.matched),\n }\n }\n }\n }\n\n // Fallback: dynamic-first-segment routes\n const dynMatch = searchCandidates(index.dynamicFirst, pathParts, pathLen)\n if (dynMatch) {\n return {\n path: cleanPath,\n params: dynMatch.params,\n query,\n hash,\n matched: dynMatch.matched,\n meta: mergeMeta(dynMatch.matched),\n }\n }\n\n // Fallback: wildcard/catch-all routes\n const w = index.wildcards[0]\n if (w) {\n return {\n path: cleanPath,\n params: {},\n query,\n hash,\n matched: w.matchedChain,\n meta: w.meta,\n }\n }\n\n return { path: cleanPath, params: {}, query, hash, matched: [], meta: {} }\n}\n\n/** Merge meta from matched routes (leaf takes precedence) */\nfunction mergeMeta(matched: RouteRecord[]): RouteMeta {\n const meta: RouteMeta = {}\n for (const record of matched) {\n if (record.meta) Object.assign(meta, record.meta)\n }\n return meta\n}\n\n/** Build a path string from a named route's pattern and params */\nexport function buildPath(pattern: string, params: Record<string, string>): string {\n const built = pattern.replace(/\\/:([^/]+)\\?/g, (_match, key) => {\n const val = params[key]\n // Optional param — omit the entire segment if no value provided\n if (!val) return ''\n return `/${encodeURIComponent(val)}`\n })\n return built.replace(/:([^/]+)\\*?/g, (match, key) => {\n const val = params[key] ?? ''\n // Splat params contain slashes — don't encode them\n if (match.endsWith('*')) return val.split('/').map(encodeURIComponent).join('/')\n return encodeURIComponent(val)\n })\n}\n\n/** Find a route record by name (recursive, O(n)). Prefer buildNameIndex for repeated lookups. */\nexport function findRouteByName(name: string, routes: RouteRecord[]): RouteRecord | null {\n for (const route of routes) {\n if (route.name === name) return route\n if (route.children) {\n const found = findRouteByName(name, route.children)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Pre-build a name → RouteRecord index from a route tree for O(1) named navigation.\n * Called once at router creation time; avoids O(n) depth-first search per push({ name }).\n */\nexport function buildNameIndex(routes: RouteRecord[]): Map<string, RouteRecord> {\n const index = new Map<string, RouteRecord>()\n function walk(list: RouteRecord[]): void {\n for (const route of list) {\n if (route.name) index.set(route.name, route)\n if (route.children) walk(route.children)\n }\n }\n walk(routes)\n return index\n}\n","import type { ResolvedRoute, RouterOptions } from './types'\n\n/**\n * Scroll restoration manager.\n *\n * Saves scroll position before each navigation and restores it when\n * navigating back to a previously visited path.\n */\nexport class ScrollManager {\n private readonly _positions = new Map<string, number>()\n private readonly _behavior: RouterOptions['scrollBehavior']\n\n constructor(behavior: RouterOptions['scrollBehavior'] = 'top') {\n this._behavior = behavior\n }\n\n /** Call before navigating away — saves current scroll position for `fromPath` */\n save(fromPath: string): void {\n // ScrollManager methods are only invoked from browser navigation paths,\n // but an explicit early-return documents the SSR-safety contract at the\n // callsite (the `no-window-in-ssr` lint rule can't AST-trace indirect\n // calls from router setup).\n if (typeof window === 'undefined') return\n this._positions.set(fromPath, window.scrollY)\n }\n\n /** Call after navigation is committed — applies scroll behavior */\n restore(to: ResolvedRoute, from: ResolvedRoute): void {\n const behavior = (to.meta.scrollBehavior as typeof this._behavior) ?? this._behavior ?? 'top'\n\n if (typeof behavior === 'function') {\n const saved = this._positions.get(to.path) ?? null\n const result = behavior(to, from, saved)\n this._applyResult(result, to.path)\n return\n }\n\n this._applyResult(behavior, to.path)\n }\n\n private _applyResult(result: 'top' | 'restore' | 'none' | number, toPath: string): void {\n if (typeof window === 'undefined') return\n // Hash scrolling: if the path contains #, scroll to the element\n const hashIdx = toPath.indexOf('#')\n if (hashIdx >= 0) {\n const id = toPath.slice(hashIdx + 1)\n if (id) {\n // Use requestAnimationFrame to ensure DOM is updated before scrolling\n requestAnimationFrame(() => {\n const el = document.getElementById(id)\n if (el) {\n el.scrollIntoView({ behavior: 'smooth' })\n return\n }\n // Fallback: try name attribute (for anchors)\n const namedEl = document.querySelector(`[name=\"${CSS.escape(id)}\"]`)\n if (namedEl) namedEl.scrollIntoView({ behavior: 'smooth' })\n })\n return\n }\n }\n\n if (result === 'none') return\n if (result === 'top' || result === undefined) {\n window.scrollTo({ top: 0, behavior: 'instant' as ScrollBehavior })\n return\n }\n if (result === 'restore') {\n const saved = this._positions.get(toPath) ?? 0\n window.scrollTo({ top: saved, behavior: 'instant' as ScrollBehavior })\n return\n }\n // At this point result must be a number (all string cases handled above)\n window.scrollTo({ top: result as number, behavior: 'instant' as ScrollBehavior })\n }\n\n getSavedPosition(path: string): number | null {\n return this._positions.get(path) ?? null\n }\n}\n","import type { ComponentFn } from '@pyreon/core'\n\nexport type { ComponentFn }\n\n// ─── Path param extraction ────────────────────────────────────────────────────\n\n/**\n * Extracts typed params from a path string at compile time.\n * Supports optional params via `:param?` — their type is `string | undefined`.\n *\n * @example\n * ExtractParams<'/user/:id/posts/:postId'>\n * // → { id: string; postId: string }\n *\n * ExtractParams<'/user/:id?'>\n * // → { id?: string | undefined }\n */\nexport type ExtractParams<T extends string> = T extends `${string}:${infer Param}*/${infer Rest}`\n ? { [K in Param]: string } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}*`\n ? { [K in Param]: string }\n : T extends `${string}:${infer Param}?/${infer Rest}`\n ? { [K in Param]?: string | undefined } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}?`\n ? { [K in Param]?: string | undefined }\n : T extends `${string}:${infer Param}/${infer Rest}`\n ? { [K in Param]: string } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? { [K in Param]: string }\n : Record<never, never>\n\n// ─── Route meta ───────────────────────────────────────────────────────────────\n\n/**\n * Route metadata interface. Extend it via module augmentation to add custom fields:\n *\n * @example\n * // globals.d.ts\n * declare module \"@pyreon/router\" {\n * interface RouteMeta {\n * requiresRole?: \"admin\" | \"user\"\n * pageTitle?: string\n * }\n * }\n */\nexport interface RouteMeta {\n /** Sets document.title on navigation */\n title?: string\n /** Page description (for meta tags) */\n description?: string\n /** If true, guards can redirect to login */\n requiresAuth?: boolean\n /** Scroll behavior for this route */\n scrollBehavior?: 'top' | 'restore' | 'none'\n /** Set to false to disable View Transitions API for this route. Default: true */\n viewTransition?: boolean\n}\n\n// ─── Resolved route ───────────────────────────────────────────────────────────\n\nexport interface ResolvedRoute<\n P extends Record<string, string | undefined> = Record<string, string>,\n Q extends Record<string, string> = Record<string, string>,\n> {\n path: string\n params: P\n query: Q\n hash: string\n /** All matched records from root to leaf (one per nesting level) */\n matched: RouteRecord[]\n meta: RouteMeta\n}\n\n// ─── Lazy component ───────────────────────────────────────────────────────────\n\nexport const LAZY_SYMBOL = Symbol('pyreon.lazy')\n\nexport interface LazyComponent {\n readonly [LAZY_SYMBOL]: true\n readonly loader: () => Promise<ComponentFn | { default: ComponentFn }>\n /** Optional component shown while the lazy chunk is loading */\n readonly loadingComponent?: ComponentFn\n /** Optional component shown after all retries have failed */\n readonly errorComponent?: ComponentFn\n}\n\nexport function lazy(\n loader: () => Promise<ComponentFn | { default: ComponentFn }>,\n options?: { loading?: ComponentFn; error?: ComponentFn },\n): LazyComponent {\n return {\n [LAZY_SYMBOL]: true,\n loader,\n ...(options?.loading ? { loadingComponent: options.loading } : {}),\n ...(options?.error ? { errorComponent: options.error } : {}),\n }\n}\n\nexport function isLazy(c: RouteComponent): c is LazyComponent {\n return typeof c === 'object' && c !== null && (c as LazyComponent)[LAZY_SYMBOL] === true\n}\n\nexport type RouteComponent = ComponentFn | LazyComponent\n\n// ─── Navigation guard ─────────────────────────────────────────────────────────\n\nexport type NavigationGuardResult = boolean | string | undefined\nexport type NavigationGuard = (\n to: ResolvedRoute,\n from: ResolvedRoute,\n) => NavigationGuardResult | Promise<NavigationGuardResult>\n\nexport type AfterEachHook = (to: ResolvedRoute, from: ResolvedRoute) => void\n\n// ─── Route middleware ────────────────────────────────────────────────────────\n\n/**\n * Context object passed through the middleware chain.\n * Middleware can read/write arbitrary data on `ctx.data`.\n */\nexport interface RouteMiddlewareContext {\n /** The route being navigated to. */\n to: ResolvedRoute\n /** The route being navigated from. */\n from: ResolvedRoute\n /** Shared data — middleware can accumulate state here for downstream middleware/components. */\n data: Record<string, unknown>\n}\n\n/**\n * Route middleware function. Called before guards.\n * - Return nothing/undefined to continue\n * - Return `false` to cancel navigation\n * - Return a string to redirect\n */\nexport type RouteMiddleware = (\n ctx: RouteMiddlewareContext,\n) => void | false | string | Promise<void | false | string>\n\n// ─── Navigation blockers ──────────────────────────────────────────────────────\n\n/**\n * Called before each navigation. Return `true` to block, `false` to allow.\n * Async blockers are supported (e.g. to show a confirmation dialog).\n */\nexport type BlockerFn = (to: ResolvedRoute, from: ResolvedRoute) => boolean | Promise<boolean>\n\nexport interface Blocker {\n /** Unregister this blocker so future navigations proceed freely. */\n remove(): void\n}\n\n// ─── Route loaders ────────────────────────────────────────────────────────────\n\nexport interface LoaderContext {\n params: Record<string, string>\n query: Record<string, string>\n /** Aborted when a newer navigation supersedes this one */\n signal: AbortSignal\n}\n\nexport type RouteLoaderFn = (ctx: LoaderContext) => Promise<unknown>\n\n// ─── Route record ─────────────────────────────────────────────────────────────\n\nexport interface RouteRecord<TPath extends string = string> {\n /** Path pattern — supports `:param` segments and `(.*)` wildcard */\n path: TPath\n component: RouteComponent\n /** Optional route name for named navigation */\n name?: string\n /** Metadata attached to this route */\n meta?: RouteMeta\n /**\n * Redirect target. Evaluated before guards.\n * String: redirect to that path.\n * Function: called with the resolved route, return path string.\n */\n redirect?: string | ((to: ResolvedRoute) => string)\n /** Guard(s) run only for this route, before global beforeEach guards */\n beforeEnter?: NavigationGuard | NavigationGuard[]\n /** Guard(s) run before leaving this route. Return false to cancel. */\n beforeLeave?: NavigationGuard | NavigationGuard[]\n /**\n * Alternative path(s) for this route. Alias paths render the same component\n * and share guards, loaders, and metadata with the primary path.\n *\n * @example\n * { path: \"/user/:id\", alias: [\"/profile/:id\"], component: UserPage }\n */\n alias?: string | string[]\n /** Child routes rendered inside this route's component via <RouterView /> */\n children?: RouteRecord[]\n /**\n * Data loader — runs before navigation commits, in parallel with sibling loaders.\n * The result is accessible via `useLoaderData()` inside the route component.\n * Receives an AbortSignal that fires if a newer navigation supersedes this one.\n */\n loader?: RouteLoaderFn\n /**\n * When true, the router shows cached loader data immediately (stale) and\n * revalidates in the background. The component re-renders once fresh data arrives.\n * Only applies when navigating to a route that already has cached loader data.\n */\n staleWhileRevalidate?: boolean\n /** Component rendered when this route's loader throws an error */\n errorComponent?: ComponentFn\n /** Per-route middleware — runs before guards, can accumulate context data. */\n middleware?: RouteMiddleware | RouteMiddleware[]\n}\n\n// ─── Router options ───────────────────────────────────────────────────────────\n\nexport type ScrollBehaviorFn = (\n to: ResolvedRoute,\n from: ResolvedRoute,\n savedPosition: number | null,\n) => 'top' | 'restore' | 'none' | number\n\nexport interface RouterOptions {\n routes: RouteRecord[]\n /** \"hash\" (default) uses location.hash; \"history\" uses pushState */\n mode?: 'hash' | 'history'\n /**\n * Base path for the application. Used when deploying to a sub-path\n * (e.g. `\"/app\"` for `https://example.com/app/`).\n * Only applies in history mode. Must start with `/`.\n * Default: `\"\"` (no base path).\n */\n base?: string\n /**\n * Global scroll behavior. Per-route meta.scrollBehavior takes precedence.\n * Default: \"top\"\n */\n scrollBehavior?: ScrollBehaviorFn | 'top' | 'restore' | 'none'\n /**\n * Initial URL for SSR. On the server, window.location is unavailable;\n * pass the request URL here so the router resolves the correct route.\n *\n * @example\n * // In your SSR handler:\n * const router = createRouter({ routes, url: req.url })\n */\n url?: string\n /**\n * Called when a route loader throws. If not provided, errors are logged\n * and the navigation continues with `undefined` data for the failed loader.\n * Return `false` to cancel the navigation.\n */\n onError?: (err: unknown, route: ResolvedRoute) => undefined | false\n /**\n * Maximum number of resolved lazy components to cache.\n * When exceeded, the oldest entry is evicted.\n * Default: 100.\n */\n maxCacheSize?: number\n /**\n * Trailing slash handling:\n * - `\"strip\"` — removes trailing slashes before matching (default)\n * - `\"add\"` — ensures paths always end with `/`\n * - `\"ignore\"` — no normalization\n */\n trailingSlash?: 'strip' | 'add' | 'ignore'\n}\n\n// ─── Router interface ─────────────────────────────────────────────────────────\n\n/**\n * Router interface. Parameterized by route name union for type-safe named navigation.\n *\n * @example\n * ```ts\n * type MyRoutes = 'home' | 'user' | 'settings'\n * const router: Router<MyRoutes> = createRouter({ routes })\n * router.push({ name: 'user', params: { id: '42' } }) // ✓\n * router.push({ name: 'typo' }) // TS error\n * ```\n */\nexport interface Router<TNames extends string = string> {\n /** Navigate to a path */\n push(path: string): Promise<void>\n /** Navigate to a named route */\n push(location: {\n name: TNames\n params?: Record<string, string>\n query?: Record<string, string>\n }): Promise<void>\n /** Replace current history entry */\n replace(path: string): Promise<void>\n /** Replace current history entry using a named route */\n replace(location: {\n name: TNames\n params?: Record<string, string>\n query?: Record<string, string>\n }): Promise<void>\n /** Go back one step in history */\n back(): void\n /** Go forward one step in history */\n forward(): void\n /** Navigate forward or backward by `delta` steps in the history stack */\n go(delta: number): void\n /** Register a global before-navigation guard. Returns an unregister function. */\n beforeEach(guard: NavigationGuard): () => void\n /** Register a global after-navigation hook. Returns an unregister function. */\n afterEach(hook: AfterEachHook): () => void\n /** Current resolved route (reactive signal) */\n readonly currentRoute: () => ResolvedRoute\n /** True while a navigation (guards + loaders) is in flight */\n readonly loading: () => boolean\n /**\n * Promise that resolves once the initial navigation is complete.\n * Useful for SSR and for delaying rendering until the first route is resolved.\n */\n isReady(): Promise<void>\n /**\n * Resolve `path` and prepare everything needed to render it: load any lazy\n * route components into the router's cache and run the matched routes'\n * loaders. After this resolves, a `RouterView` rendered against this router\n * for `path` will produce final HTML synchronously — no loading fallbacks,\n * no `useLoaderData()` returning `undefined`.\n *\n * Used by SSR/SSG to hydrate the route tree before `renderToString`.\n * The router's `currentRoute` is NOT changed by `preload` — pass the path\n * separately when creating the router (`createRouter({ url, ... })`) or\n * call this for the same `url` you initialised the router with.\n */\n preload(path: string): Promise<void>\n /** Remove all event listeners, clear caches, and abort in-flight navigations. */\n destroy(): void\n}\n\n// ─── Internal router instance ─────────────────────────────────────────────────\n\nimport type { Computed, Signal } from '@pyreon/reactivity'\n\nexport interface RouterInstance extends Router {\n routes: RouteRecord[]\n mode: 'hash' | 'history'\n /** Normalized base path (e.g. \"/app\"), empty string if none */\n _base: string\n _currentPath: Signal<string>\n _currentRoute: Computed<ResolvedRoute>\n _componentCache: Map<RouteRecord, ComponentFn>\n _loadingSignal: Signal<number>\n _resolve(rawPath: string): ResolvedRoute\n _scrollPositions: Map<string, number>\n _scrollBehavior: RouterOptions['scrollBehavior']\n _onError: RouterOptions['onError']\n _maxCacheSize: number\n /**\n * Current RouterView nesting depth. Incremented by each RouterView as it\n * mounts (in tree order = depth-first), so each view knows which level of\n * `matched[]` to render. Reset to 0 by RouterProvider.\n */\n _viewDepth: number\n /** Route records whose lazy chunk permanently failed (all retries exhausted) */\n _erroredChunks: Set<RouteRecord>\n /** Loader data keyed by route record — populated before each navigation commits */\n _loaderData: Map<RouteRecord, unknown>\n /** AbortController for the in-flight loader batch — aborted when a newer navigation starts */\n _abortController: AbortController | null\n /** Registered navigation blockers */\n _blockers: Set<BlockerFn>\n /** Resolves the isReady() promise after initial navigation completes */\n _readyResolve: (() => void) | null\n /** The isReady() promise instance */\n _readyPromise: Promise<void>\n}\n","import { createContext, onUnmount, useContext } from '@pyreon/core'\nimport { computed, signal } from '@pyreon/reactivity'\nimport { buildNameIndex, buildPath, resolveRoute, stringifyQuery } from './match'\nimport { ScrollManager } from './scroll'\nimport {\n type AfterEachHook,\n type Blocker,\n type BlockerFn,\n type ComponentFn,\n isLazy,\n type LoaderContext,\n type NavigationGuard,\n type NavigationGuardResult,\n type ResolvedRoute,\n type RouteMiddleware,\n type RouteMiddlewareContext,\n type RouteRecord,\n type Router,\n type RouterInstance,\n type RouterOptions,\n} from './types'\n\n// Evaluated once at module load — collapses to `true` in browser / happy-dom,\n// `false` on the server. Using a constant avoids per-call `typeof` branches\n// that are uncoverable in test environments.\nconst _isBrowser = typeof window !== 'undefined'\n// Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this\n// uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.\n// @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time\nconst __DEV__ = import.meta.env?.DEV === true\n\n// ─── Router context ───────────────────────────────────────────────────────────\n// Context-based access: isolated per request in SSR (ALS-backed via\n// @pyreon/runtime-server), isolated per component tree in CSR.\n// Falls back to the module-level singleton for code running outside a component\n// tree (e.g. programmatic navigation from event handlers).\n\nexport const RouterContext = createContext<RouterInstance | null>(null)\n\n// Module-level fallback — safe for CSR (single-threaded), not for concurrent SSR.\n// RouterProvider also sets this so legacy useRouter() calls outside the tree work.\nlet _activeRouter: RouterInstance | null = null\n\nexport function getActiveRouter(): RouterInstance | null {\n return useContext(RouterContext) ?? _activeRouter\n}\n\nexport function setActiveRouter(router: RouterInstance | null): void {\n if (router) router._viewDepth = 0\n _activeRouter = router\n}\n\n// ─── Hooks ────────────────────────────────────────────────────────────────────\n\nexport function useRouter(): Router {\n const router = useContext(RouterContext) ?? _activeRouter\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return router\n}\n\nexport function useRoute<TPath extends string = string>(): () => ResolvedRoute<\n import('./types').ExtractParams<TPath> & Record<string, string>,\n Record<string, string>\n> {\n const router = useContext(RouterContext) ?? _activeRouter\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return router.currentRoute as never\n}\n\n/**\n * In-component guard: called before the component's route is left.\n * Return `false` to cancel, a string to redirect, or `undefined`/`true` to proceed.\n * Automatically removed on component unmount.\n *\n * @example\n * onBeforeRouteLeave((to, from) => {\n * if (hasUnsavedChanges()) return false\n * })\n */\nexport function onBeforeRouteLeave(guard: NavigationGuard): () => void {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n // Register as a global guard that only fires when leaving the current route\n const currentMatched = router.currentRoute().matched\n const wrappedGuard: NavigationGuard = (to, from) => {\n // Only fire if we're actually leaving one of the matched routes\n const isLeaving = from.matched.some((r) => currentMatched.includes(r))\n if (!isLeaving) return undefined\n return guard(to, from)\n }\n const remove = router.beforeEach(wrappedGuard)\n onUnmount(() => remove())\n return remove\n}\n\n/**\n * In-component guard: called when the route changes but the component is reused\n * (e.g. `/user/1` → `/user/2`). Useful for reacting to param changes.\n * Automatically removed on component unmount.\n *\n * @example\n * onBeforeRouteUpdate((to, from) => {\n * if (!isValidId(to.params.id)) return false\n * })\n */\nexport function onBeforeRouteUpdate(guard: NavigationGuard): () => void {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n const currentMatched = router.currentRoute().matched\n const wrappedGuard: NavigationGuard = (to, from) => {\n // Only fire when the same component is reused (matched routes overlap)\n const isReused = to.matched.some((r) => currentMatched.includes(r))\n if (!isReused) return undefined\n return guard(to, from)\n }\n const remove = router.beforeEach(wrappedGuard)\n onUnmount(() => remove())\n return remove\n}\n\n/**\n * Register a navigation blocker. The `fn` callback is called before each\n * navigation — return `true` (or resolve to `true`) to block it.\n *\n * Automatically removed on component unmount if called during component setup.\n * Also installs a `beforeunload` handler so the browser shows a confirmation\n * dialog when the user tries to close the tab while a blocker is active.\n *\n * @example\n * const blocker = useBlocker((to, from) => {\n * return hasUnsavedChanges() && !confirm(\"Discard changes?\")\n * })\n * // later: blocker.remove()\n */\nexport function useBlocker(fn: BlockerFn): Blocker {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n router._blockers.add(fn)\n\n // Warn before tab/window close while this blocker is registered\n const beforeUnloadHandler = _isBrowser\n ? (e: BeforeUnloadEvent) => {\n e.preventDefault()\n }\n : null\n if (beforeUnloadHandler) {\n window.addEventListener('beforeunload', beforeUnloadHandler)\n }\n\n const remove = () => {\n router._blockers.delete(fn)\n if (beforeUnloadHandler) {\n window.removeEventListener('beforeunload', beforeUnloadHandler)\n }\n }\n\n // Auto-remove when the component that called useBlocker unmounts\n onUnmount(() => remove())\n\n return { remove }\n}\n\n/**\n * Reactive read/write access to the current route's query parameters.\n *\n * Returns `[get, set]` where `get` is a reactive signal producing the merged\n * query object and `set` navigates to the current path with updated params.\n *\n * @example\n * const [params, setParams] = useSearchParams({ page: \"1\", sort: \"name\" })\n * params().page // \"1\" if not in URL\n * setParams({ page: \"2\" }) // navigates to ?page=2&sort=name\n */\n/**\n * Check if a path is active (matches the current route).\n * Returns a reactive boolean signal.\n *\n * - Exact mode: `/admin` matches only `/admin`\n * - Partial mode (default): `/admin` matches `/admin`, `/admin/users`, `/admin/settings`\n * Uses segment-aware prefix matching — `/admin` does NOT match `/admin-panel`\n *\n * @example\n * ```tsx\n * const isAdmin = useIsActive(\"/admin\") // partial — matches /admin/*\n * const isExact = useIsActive(\"/admin\", true) // exact — only /admin\n *\n * <div class={isAdmin() ? \"active\" : \"\"}>Admin</div>\n * <Show when={isAdmin()}><Badge>Active</Badge></Show>\n * ```\n */\nexport function useIsActive(path: string, exact = false): () => boolean {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return () => {\n const current = router.currentRoute().path\n if (exact) {\n return matchSegments(current, path, true)\n }\n if (path === '/') return current === '/'\n // Segment-aware prefix: /admin matches /admin/users but NOT /admin-panel\n return matchSegments(current, path, false)\n }\n}\n\n/** Match current path segments against a pattern that may contain `:param` segments. */\nfunction matchSegments(current: string, pattern: string, exact: boolean): boolean {\n const cs = current.split('/').filter(Boolean)\n const ps = pattern.split('/').filter(Boolean)\n if (exact) {\n if (cs.length !== ps.length) return false\n return ps.every((seg, i) => seg.startsWith(':') || seg === cs[i])\n }\n if (ps.length > cs.length) return false\n return ps.every((seg, i) => seg.startsWith(':') || seg === cs[i])\n}\n\n/** Schema entry for typed search params. */\nexport type SearchParamSchema = {\n [key: string]: 'string' | 'number' | 'boolean'\n}\n\n/** Infer the typed result from a search param schema. */\ntype InferSearchParams<T extends SearchParamSchema> = {\n [K in keyof T]: T[K] extends 'number' ? number\n : T[K] extends 'boolean' ? boolean\n : string\n}\n\n/**\n * Read and write URL search params reactively.\n *\n * @example Basic (untyped)\n * ```ts\n * const [params, setParams] = useSearchParams({ page: \"1\" })\n * params().page // \"1\"\n * setParams({ page: \"2\" }) // updates URL\n * ```\n *\n * @example Typed with schema\n * ```ts\n * const [params, setParams] = useSearchParams({\n * page: 'number',\n * sort: 'string',\n * desc: 'boolean',\n * })\n * params().page // number (auto-coerced)\n * params().desc // boolean\n * ```\n */\nexport function useSearchParams<T extends Record<string, string>>(\n defaults?: T,\n): [get: () => T, set: (updates: Partial<T>) => Promise<void>] {\n const router = _getRouter()\n const get = (): T => {\n const query = router.currentRoute().query\n if (!defaults) return query as T\n return { ...defaults, ...query } as T\n }\n const set = (updates: Partial<T>): Promise<void> => {\n const merged = { ...get(), ...updates }\n const path = router.currentRoute().path + stringifyQuery(merged as Record<string, string>)\n return router.replace(path)\n }\n return [get, set]\n}\n\n/**\n * Typed search params with auto-coercion.\n *\n * Schema values define the type: `'string'`, `'number'`, or `'boolean'`.\n * Query string values are automatically coerced to the declared type.\n *\n * @example\n * ```ts\n * const [params, setParams] = useTypedSearchParams({\n * page: 'number',\n * sort: 'string',\n * desc: 'boolean',\n * })\n * params().page // number (coerced from \"3\" → 3)\n * params().desc // boolean (coerced from \"true\" → true)\n * setParams({ page: 2 }) // updates URL with ?page=2\n * ```\n */\nexport function useTypedSearchParams<T extends SearchParamSchema>(\n schema: T,\n): [get: () => InferSearchParams<T>, set: (updates: Partial<InferSearchParams<T>>) => Promise<void>] {\n const router = _getRouter()\n const get = (): InferSearchParams<T> => {\n const query = router.currentRoute().query\n const result: Record<string, unknown> = {}\n for (const [key, type] of Object.entries(schema)) {\n const raw = query[key]\n if (type === 'number') result[key] = raw !== undefined ? Number(raw) : 0\n else if (type === 'boolean') result[key] = raw === 'true' || raw === '1'\n else result[key] = raw ?? ''\n }\n return result as InferSearchParams<T>\n }\n const set = (updates: Partial<InferSearchParams<T>>): Promise<void> => {\n const current = get()\n const merged: Record<string, string> = {}\n for (const [k, v] of Object.entries({ ...current, ...updates })) {\n merged[k] = String(v)\n }\n const path = router.currentRoute().path + stringifyQuery(merged)\n return router.replace(path)\n }\n return [get, set]\n}\n\nfunction _getRouter(): RouterInstance {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return router\n}\n\n/**\n * Returns true while a navigation is in progress (guards + loaders running).\n * Use this to show loading indicators during route transitions.\n *\n * @example\n * ```tsx\n * const isNavigating = useTransition()\n * <Show when={isNavigating}>\n * <LoadingBar />\n * </Show>\n * ```\n */\nexport function useTransition(): () => boolean {\n const router = _getRouter()\n return () => router._loadingSignal() > 0\n}\n\n/**\n * Read data accumulated by route middleware.\n *\n * @example\n * ```ts\n * // In middleware:\n * const authMiddleware: RouteMiddleware = async (ctx) => {\n * ctx.data.user = await getUser(ctx.to)\n * if (!ctx.data.user) return '/login'\n * }\n *\n * // In component:\n * const data = useMiddlewareData()\n * const user = () => data().user as User\n * ```\n */\nexport function useMiddlewareData(): () => Record<string, unknown> {\n const router = _getRouter()\n return () => (router.currentRoute() as any)._middlewareData ?? {}\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createRouter(options: RouterOptions | RouteRecord[]): Router {\n const opts: RouterOptions = Array.isArray(options) ? { routes: options } : options\n const {\n routes,\n mode = 'hash',\n scrollBehavior,\n onError,\n maxCacheSize = 100,\n trailingSlash = 'strip',\n } = opts\n\n // Base path only applies to history mode — hash-based routing already namespaces via #\n const base = mode === 'history' ? normalizeBase(opts.base ?? '') : ''\n\n // Pre-built O(1) name → record index. Computed once at startup.\n const nameIndex = buildNameIndex(routes)\n\n const guards: NavigationGuard[] = []\n const afterHooks: AfterEachHook[] = []\n const scrollManager = new ScrollManager(scrollBehavior)\n\n // Navigation generation counter — cancels in-flight navigations when a newer\n // one starts. Prevents out-of-order completion from stale async guards.\n let _navGen = 0\n\n // ── Initial location ──────────────────────────────────────────────────────\n\n const getInitialLocation = (): string => {\n // SSR: use explicitly provided url (strip base if present)\n if (opts.url) return stripBase(opts.url, base)\n if (!_isBrowser) return '/'\n if (mode === 'history') {\n return stripBase(window.location.pathname, base) + window.location.search\n }\n const hash = window.location.hash\n return hash.startsWith('#') ? hash.slice(1) || '/' : '/'\n }\n\n const getCurrentLocation = (): string => {\n if (!_isBrowser) return currentPath()\n if (mode === 'history') {\n return stripBase(window.location.pathname, base) + window.location.search\n }\n const hash = window.location.hash\n return hash.startsWith('#') ? hash.slice(1) || '/' : '/'\n }\n\n // ── Signals ───────────────────────────────────────────────────────────────\n\n const currentPath = signal(normalizeTrailingSlash(getInitialLocation(), trailingSlash))\n const currentRoute = computed<ResolvedRoute>(() => resolveRoute(currentPath(), routes))\n\n // Browser event listeners — stored so destroy() can remove them.\n // Ternary-bound on `_isBrowser` (a typeof-derived const) so the lint rule\n // can trace these to an SSR-safe shape without needing `if (_isBrowser &&\n // handler)` contortions at every use site.\n const _popstateHandler: (() => void) | null =\n _isBrowser && mode === 'history' ? () => currentPath.set(getCurrentLocation()) : null\n const _hashchangeHandler: (() => void) | null =\n _isBrowser && mode !== 'history' ? () => currentPath.set(getCurrentLocation()) : null\n\n if (_popstateHandler) window.addEventListener('popstate', _popstateHandler)\n if (_hashchangeHandler) window.addEventListener('hashchange', _hashchangeHandler)\n\n const componentCache = new Map<RouteRecord, ComponentFn>()\n const loadingSignal = signal(0)\n\n // ── Navigation ────────────────────────────────────────────────────────────\n\n type GuardOutcome =\n | { action: 'continue' }\n | { action: 'cancel' }\n | { action: 'redirect'; target: string }\n\n async function evaluateGuard(\n guard: NavigationGuard,\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n const result = await runGuard(guard, to, from)\n if (gen !== _navGen) return { action: 'cancel' }\n if (result === false) return { action: 'cancel' }\n if (typeof result === 'string') return { action: 'redirect', target: result }\n return { action: 'continue' }\n }\n\n async function runRouteGuards(\n records: RouteRecord[],\n guardKey: 'beforeLeave' | 'beforeEnter',\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n for (const record of records) {\n const raw = record[guardKey]\n if (!raw) continue\n const routeGuards = Array.isArray(raw) ? raw : [raw]\n for (const guard of routeGuards) {\n const outcome = await evaluateGuard(guard, to, from, gen)\n if (outcome.action !== 'continue') return outcome\n }\n }\n return { action: 'continue' }\n }\n\n async function runGlobalGuards(\n globalGuards: NavigationGuard[],\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n for (const guard of globalGuards) {\n const outcome = await evaluateGuard(guard, to, from, gen)\n if (outcome.action !== 'continue') return outcome\n }\n return { action: 'continue' }\n }\n\n function processLoaderResult(\n result: PromiseSettledResult<unknown>,\n record: RouteRecord,\n ac: AbortController,\n to: ResolvedRoute,\n ): boolean {\n if (result.status === 'fulfilled') {\n router._loaderData.set(record, result.value)\n return true\n }\n if (ac.signal.aborted) return true\n if (router._onError) {\n const cancel = router._onError(result.reason, to)\n if (cancel === false) return false\n }\n router._loaderData.set(record, undefined)\n return true\n }\n\n function syncBrowserUrl(path: string, replace: boolean): void {\n if (!_isBrowser) return\n const url = mode === 'history' ? `${base}${path}` : `#${path}`\n if (replace) {\n window.history.replaceState(null, '', url)\n } else {\n window.history.pushState(null, '', url)\n }\n }\n\n function resolveRedirect(to: ResolvedRoute): string | null {\n const leaf = to.matched[to.matched.length - 1]\n if (!leaf?.redirect) return null\n return sanitizePath(typeof leaf.redirect === 'function' ? leaf.redirect(to) : leaf.redirect)\n }\n\n async function runAllGuards(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n const leaveOutcome = await runRouteGuards(from.matched, 'beforeLeave', to, from, gen)\n if (leaveOutcome.action !== 'continue') return leaveOutcome\n\n const enterOutcome = await runRouteGuards(to.matched, 'beforeEnter', to, from, gen)\n if (enterOutcome.action !== 'continue') return enterOutcome\n\n return runGlobalGuards(guards, to, from, gen)\n }\n\n async function runBlockingLoaders(\n records: RouteRecord[],\n to: ResolvedRoute,\n gen: number,\n ac: AbortController,\n ): Promise<boolean> {\n const loaderCtx: LoaderContext = { params: to.params, query: to.query, signal: ac.signal }\n const results = await Promise.allSettled(\n records.map((r) => (r.loader ? r.loader(loaderCtx) : Promise.resolve(undefined))),\n )\n if (gen !== _navGen) return false\n for (let i = 0; i < records.length; i++) {\n const result = results[i]\n const record = records[i]\n if (!result || !record) continue\n if (!processLoaderResult(result, record, ac, to)) return false\n }\n return true\n }\n\n /** Fire-and-forget background revalidation for stale-while-revalidate routes. */\n function revalidateSwrLoaders(records: RouteRecord[], to: ResolvedRoute, ac: AbortController) {\n const loaderCtx: LoaderContext = { params: to.params, query: to.query, signal: ac.signal }\n for (const r of records) {\n if (!r.loader) continue\n r.loader(loaderCtx)\n .then((data) => {\n if (!ac.signal.aborted) {\n router._loaderData.set(r, data)\n // Bump loadingSignal to trigger reactive re-render with fresh data\n loadingSignal.update((n) => n + 1)\n loadingSignal.update((n) => n - 1)\n }\n })\n .catch(() => {\n /* Background revalidation failure — stale data remains valid */\n })\n }\n }\n\n async function runLoaders(to: ResolvedRoute, gen: number, ac: AbortController): Promise<boolean> {\n const loadableRecords = to.matched.filter((r) => r.loader)\n if (loadableRecords.length === 0) return true\n\n const blocking: RouteRecord[] = []\n const swr: RouteRecord[] = []\n for (const r of loadableRecords) {\n if (r.staleWhileRevalidate && router._loaderData.has(r)) {\n swr.push(r)\n } else {\n blocking.push(r)\n }\n }\n\n if (blocking.length > 0) {\n const ok = await runBlockingLoaders(blocking, to, gen, ac)\n if (!ok) return false\n }\n if (swr.length > 0) revalidateSwrLoaders(swr, to, ac)\n return true\n }\n\n async function commitNavigation(\n path: string,\n replace: boolean,\n to: ResolvedRoute,\n from: ResolvedRoute,\n ): Promise<void> {\n scrollManager.save(from.path)\n\n const doCommit = () => {\n currentPath.set(path)\n syncBrowserUrl(path, replace)\n\n if (_isBrowser && to.meta.title) {\n document.title = to.meta.title\n }\n\n for (const record of router._loaderData.keys()) {\n if (!to.matched.includes(record)) {\n router._loaderData.delete(record)\n }\n }\n }\n\n // Use View Transitions API when available and not explicitly disabled.\n // Route meta can opt out: meta: { viewTransition: false }\n const useVT = _isBrowser\n && to.meta.viewTransition !== false\n && typeof (document as any).startViewTransition === 'function'\n\n if (useVT) {\n // `startViewTransition(cb)` runs `cb` inside an async transition. Its\n // `.updateCallbackDone` promise resolves as soon as the callback\n // finishes — DOM has swapped, state is live, but the fade/slide\n // animation is still running. That's what `await router.push()`\n // should wait for: callers need the new route live before they act\n // (e.g. focus an element, inspect `location`, query a new DOM node);\n // they don't want to block on the full animation (`.finished`),\n // which would add 200-300ms to every programmatic navigation.\n //\n // Before this await, `commitNavigation` was sync: the transition\n // callback ran in a later microtask, so `await router.push()`\n // resolved BEFORE the DOM swap. Browser smoke tests had to opt out\n // of View Transitions per-route via `meta: { viewTransition: false }`\n // to stay deterministic — a flag whose only purpose was to paper\n // over this bug.\n type ViewTransitionLike = {\n updateCallbackDone?: Promise<void>\n ready?: Promise<void>\n finished?: Promise<void>\n }\n const vt = (document as { startViewTransition?: (cb: () => void) => ViewTransitionLike | undefined })\n .startViewTransition!(() => {\n doCommit()\n })\n // `startViewTransition` may return `undefined` in test doubles\n // that shim it with a bare `(cb) => cb()`. Guard accordingly.\n if (vt) {\n // The ViewTransition object exposes THREE promises —\n // `updateCallbackDone`, `ready`, `finished`. When a newer\n // `startViewTransition()` starts while this one is in flight,\n // `ready` and `finished` reject with `AbortError: Transition\n // was skipped`. We only need to wait on `updateCallbackDone`\n // (the DOM-commit signal), but the other two MUST still be\n // handled or the rejection surfaces as an unhandled promise\n // rejection that breaks test runners and CI dashboards.\n vt.ready?.catch(() => {})\n vt.finished?.catch(() => {})\n if (vt.updateCallbackDone) {\n try {\n await vt.updateCallbackDone\n } catch {\n // `updateCallbackDone` rejects if the callback itself throws.\n // The DOM may be in a partial-commit state; the newer\n // navigation (if any) will re-commit. Swallow so the\n // navigation chain never hangs on a transition error.\n }\n }\n }\n } else {\n doCommit()\n }\n\n for (const hook of afterHooks) {\n try {\n hook(to, from)\n } catch (err) {\n if (__DEV__) {\n console.warn(`[Pyreon Router] afterEach hook threw an error:`, err)\n }\n }\n }\n\n if (_isBrowser) {\n queueMicrotask(() => scrollManager.restore(to, from))\n }\n }\n\n async function checkBlockers(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<'continue' | 'cancel'> {\n for (const blocker of router._blockers) {\n const blocked = await blocker(to, from)\n if (gen !== _navGen || blocked) return 'cancel'\n }\n return 'continue'\n }\n\n /** Run per-route middleware chain. Middleware from all matched routes execute in order. */\n async function runMiddleware(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<{ action: 'continue' } | { action: 'cancel' } | { action: 'redirect'; target: string }> {\n const ctx: RouteMiddlewareContext = { to, from, data: {} }\n\n for (const record of to.matched) {\n if (!record.middleware) continue\n const mws = Array.isArray(record.middleware) ? record.middleware : [record.middleware]\n for (const mw of mws) {\n if (gen !== _navGen) return { action: 'cancel' }\n const result = await mw(ctx)\n if (result === false) return { action: 'cancel' }\n if (typeof result === 'string') return { action: 'redirect', target: result }\n }\n }\n\n // Store middleware data on the resolved route for component access\n ;(to as any)._middlewareData = ctx.data\n return { action: 'continue' }\n }\n\n async function navigate(rawPath: string, replace: boolean, redirectDepth = 0): Promise<void> {\n if (redirectDepth > 10) {\n if (__DEV__) {\n // oxlint-disable-next-line no-console\n console.warn(\n `[Pyreon] Navigation to \"${rawPath}\" aborted: redirect depth exceeded 10 levels. ` +\n 'This likely indicates a redirect loop in your route configuration.',\n )\n }\n return\n }\n\n const path = normalizeTrailingSlash(rawPath, trailingSlash)\n const gen = ++_navGen\n loadingSignal.update((n) => n + 1)\n\n const to = resolveRoute(path, routes)\n const from = currentRoute()\n\n const redirectTarget = resolveRedirect(to)\n if (redirectTarget !== null) {\n loadingSignal.update((n) => n - 1)\n return navigate(redirectTarget, replace, redirectDepth + 1)\n }\n\n const blockerResult = await checkBlockers(to, from, gen)\n if (blockerResult !== 'continue') {\n loadingSignal.update((n) => n - 1)\n return\n }\n\n // Run per-route middleware chain (before guards)\n const mwResult = await runMiddleware(to, from, gen)\n if (mwResult.action !== 'continue') {\n loadingSignal.update((n) => n - 1)\n if (mwResult.action === 'redirect') {\n return navigate(sanitizePath(mwResult.target), replace, redirectDepth + 1)\n }\n return\n }\n\n const guardOutcome = await runAllGuards(to, from, gen)\n if (guardOutcome.action !== 'continue') {\n loadingSignal.update((n) => n - 1)\n if (guardOutcome.action === 'redirect') {\n return navigate(sanitizePath(guardOutcome.target), replace, redirectDepth + 1)\n }\n return\n }\n\n router._abortController?.abort()\n const ac = new AbortController()\n router._abortController = ac\n\n const loadersOk = await runLoaders(to, gen, ac)\n if (!loadersOk) {\n loadingSignal.update((n) => n - 1)\n return\n }\n\n await commitNavigation(path, replace, to, from)\n loadingSignal.update((n) => n - 1)\n }\n\n // ── isReady promise ─────────────────────────────────────────────────────\n // Resolves after the first navigation (including guards + loaders) completes.\n\n let _readyResolve: (() => void) | null = null\n const _readyPromise = new Promise<void>((resolve) => {\n _readyResolve = resolve\n })\n\n // ── Public router object ──────────────────────────────────────────────────\n\n const router: RouterInstance = {\n routes,\n mode,\n _base: base,\n currentRoute,\n _currentPath: currentPath,\n _currentRoute: currentRoute,\n _componentCache: componentCache,\n _loadingSignal: loadingSignal,\n _scrollPositions: new Map(),\n _scrollBehavior: scrollBehavior,\n _viewDepth: 0,\n _erroredChunks: new Set(),\n _loaderData: new Map(),\n _abortController: null,\n _blockers: new Set(),\n _readyResolve,\n _readyPromise,\n _onError: onError,\n _maxCacheSize: maxCacheSize,\n\n async push(\n location:\n | string\n | { name: string; params?: Record<string, string>; query?: Record<string, string> },\n ) {\n if (typeof location === 'string') {\n const resolved = resolveRelativePath(location, currentPath())\n return navigate(sanitizePath(resolved), false)\n }\n const path = resolveNamedPath(\n location.name,\n location.params ?? {},\n location.query ?? {},\n nameIndex,\n )\n return navigate(path, false)\n },\n\n async replace(\n location:\n | string\n | { name: string; params?: Record<string, string>; query?: Record<string, string> },\n ) {\n if (typeof location === 'string') {\n const resolved = resolveRelativePath(location, currentPath())\n return navigate(sanitizePath(resolved), true)\n }\n const path = resolveNamedPath(\n location.name,\n location.params ?? {},\n location.query ?? {},\n nameIndex,\n )\n return navigate(path, true)\n },\n\n back() {\n if (_isBrowser) window.history.back()\n },\n\n forward() {\n if (_isBrowser) window.history.forward()\n },\n\n go(delta: number) {\n if (_isBrowser) window.history.go(delta)\n },\n\n beforeEach(guard: NavigationGuard) {\n guards.push(guard)\n return () => {\n const idx = guards.indexOf(guard)\n if (idx >= 0) guards.splice(idx, 1)\n }\n },\n\n afterEach(hook: AfterEachHook) {\n afterHooks.push(hook)\n return () => {\n const idx = afterHooks.indexOf(hook)\n if (idx >= 0) afterHooks.splice(idx, 1)\n }\n },\n\n loading: () => loadingSignal() > 0,\n\n isReady() {\n return router._readyPromise\n },\n\n async preload(path: string) {\n const resolved = resolveRoute(path, routes)\n // Load lazy components in parallel and populate the component cache so\n // the synchronous render pass finds ready components instead of kicking\n // off async imports (which would fall back to loadingComponent).\n await Promise.all(\n resolved.matched.map(async (record) => {\n if (componentCache.has(record)) return\n const raw = record.component\n if (!isLazy(raw)) {\n componentCache.set(record, raw)\n return\n }\n const mod = await raw.loader()\n const comp = typeof mod === 'function' ? mod : mod.default\n componentCache.set(record, comp)\n }),\n )\n // Run loaders for the matched path — uses the same code path SSR\n // already relied on, so loader data ends up in `_loaderData` under the\n // matched route records.\n const ac = new AbortController()\n router._abortController = ac\n await Promise.all(\n resolved.matched\n .filter((r) => r.loader)\n .map(async (r) => {\n const data = await r.loader?.({\n params: resolved.params,\n query: resolved.query,\n signal: ac.signal,\n })\n router._loaderData.set(r, data)\n }),\n )\n },\n\n destroy() {\n if (_popstateHandler) window.removeEventListener('popstate', _popstateHandler)\n if (_hashchangeHandler) window.removeEventListener('hashchange', _hashchangeHandler)\n guards.length = 0\n afterHooks.length = 0\n router._blockers.clear()\n componentCache.clear()\n router._loaderData.clear()\n router._abortController?.abort()\n router._abortController = null\n },\n\n _resolve: (rawPath: string) => resolveRoute(rawPath, routes),\n }\n\n // Initial route is resolved synchronously — mark ready on next microtask\n // so consumers can await isReady() before the first render.\n queueMicrotask(() => {\n if (router._readyResolve) {\n router._readyResolve()\n router._readyResolve = null\n }\n })\n\n return router\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function runGuard(\n guard: NavigationGuard,\n to: ResolvedRoute,\n from: ResolvedRoute,\n): Promise<NavigationGuardResult> {\n try {\n return await guard(to, from)\n } catch (err) {\n if (__DEV__) {\n console.warn(`[Pyreon Router] Navigation guard threw an error — navigation cancelled:`, err)\n }\n return false\n }\n}\n\nfunction resolveNamedPath(\n name: string,\n params: Record<string, string>,\n query: Record<string, string>,\n index: Map<string, RouteRecord>,\n): string {\n const record = index.get(name)\n if (!record) {\n return '/'\n }\n let path = buildPath(record.path, params)\n const qs = Object.entries(query)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join('&')\n if (qs) path += `?${qs}`\n return path\n}\n\n/** Normalize a base path: ensure leading `/`, strip trailing `/`. */\nfunction normalizeBase(raw: string): string {\n if (!raw) return ''\n let b = raw\n if (!b.startsWith('/')) b = `/${b}`\n if (b.endsWith('/')) b = b.slice(0, -1)\n return b\n}\n\n/** Strip the base prefix from a full URL path. Returns the app-relative path. */\nfunction stripBase(path: string, base: string): string {\n if (!base) return path\n if (path === base || path === `${base}/`) return '/'\n if (path.startsWith(`${base}/`)) return path.slice(base.length)\n return path\n}\n\n/** Normalize trailing slash on a path according to the configured strategy. */\nfunction normalizeTrailingSlash(path: string, strategy: 'strip' | 'add' | 'ignore'): string {\n if (strategy === 'ignore' || path === '/') return path\n // Split off query string + hash so we only touch the path portion\n const qIdx = path.indexOf('?')\n const hIdx = path.indexOf('#')\n const endIdx = qIdx >= 0 ? qIdx : hIdx >= 0 ? hIdx : path.length\n const pathPart = path.slice(0, endIdx)\n const suffix = path.slice(endIdx)\n if (strategy === 'strip') {\n return pathPart.length > 1 && pathPart.endsWith('/') ? pathPart.slice(0, -1) + suffix : path\n }\n // strategy === \"add\"\n return !pathPart.endsWith('/') ? `${pathPart}/${suffix}` : path\n}\n\n/**\n * Resolve a relative path (starting with `.` or `..`) against the current path.\n * Non-relative paths are returned as-is.\n */\nfunction resolveRelativePath(to: string, from: string): string {\n if (!to.startsWith('./') && !to.startsWith('../') && to !== '.' && to !== '..') return to\n\n // Split current path into segments, drop the last segment (file-like resolution)\n const fromSegments = from.split('/').filter(Boolean)\n fromSegments.pop()\n\n const toSegments = to.split('/').filter(Boolean)\n for (const seg of toSegments) {\n if (seg === '..') {\n fromSegments.pop()\n } else if (seg !== '.') {\n fromSegments.push(seg)\n }\n }\n return `/${fromSegments.join('/')}`\n}\n\n/** Block unsafe navigation targets: javascript/data/vbscript URIs and absolute URLs. */\nfunction sanitizePath(path: string): string {\n const trimmed = path.trim()\n if (/^(?:javascript|data|vbscript):/i.test(trimmed)) {\n return '/'\n }\n // Block absolute URLs and protocol-relative URLs — router only handles same-origin paths\n if (/^\\/\\/|^https?:/i.test(trimmed)) {\n return '/'\n }\n return path\n}\n\nexport { isLazy }\n","import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'\nimport { createRef, ErrorBoundary, h, onUnmount, provide, useContext } from '@pyreon/core'\nimport { LoaderDataContext, prefetchLoaderData } from './loader'\nimport { isLazy, RouterContext, setActiveRouter } from './router'\nimport type { LazyComponent, ResolvedRoute, RouteRecord, Router, RouterInstance } from './types'\n\n// Track prefetched paths per router to avoid duplicate fetches\nconst _prefetched = new WeakMap<RouterInstance, Set<string>>()\n\n// ─── RouterProvider ───────────────────────────────────────────────────────────\n\nexport interface RouterProviderProps extends Props {\n router: Router\n children?: VNodeChild\n}\n\nexport const RouterProvider: ComponentFn<RouterProviderProps> = (props) => {\n const router = props.router as RouterInstance\n // Push router into the context stack — isolated per request in SSR via ALS,\n // isolated per component tree in CSR.\n provide(RouterContext, router)\n onUnmount(() => {\n // Clean up event listeners, caches, abort in-flight navigations.\n // Safe to call multiple times (destroy is idempotent).\n router.destroy()\n setActiveRouter(null)\n })\n // Also set the module fallback so programmatic useRouter() outside a component\n // tree (e.g. navigation guards in event handlers) still works in CSR.\n setActiveRouter(router)\n return props.children ?? null\n}\n\n// ─── RouterView ───────────────────────────────────────────────────────────────\n\nexport interface RouterViewProps extends Props {\n /** Explicitly pass a router (optional — uses the active router by default) */\n router?: Router\n}\n\n/**\n * Renders the matched route component at this nesting level.\n *\n * Nested layouts work by placing a second `<RouterView />` inside the layout\n * component — it automatically renders the next level of the matched route.\n *\n * How depth tracking works:\n * Pyreon components run once in depth-first tree order. Each `RouterView`\n * captures `router._viewDepth` at setup time and immediately increments it,\n * so sibling and child views get the correct index. `onUnmount` decrements\n * the counter so dynamic route swaps work correctly.\n *\n * @example\n * // Route config:\n * { path: \"/admin\", component: AdminLayout, children: [\n * { path: \"users\", component: AdminUsers },\n * ]}\n *\n * // AdminLayout renders a nested RouterView:\n * function AdminLayout() {\n * return <div><Sidebar /><RouterView /></div>\n * }\n */\nexport const RouterView: ComponentFn<RouterViewProps> = (props) => {\n const router = ((props.router as RouterInstance | undefined) ??\n useContext(RouterContext)) as RouterInstance | null\n if (!router) return null\n\n // Claim this view's depth at setup time (depth-first component init order)\n const depth = router._viewDepth\n router._viewDepth++\n\n onUnmount(() => {\n router._viewDepth--\n })\n\n const child = (): VNodeChild => {\n router._loadingSignal() // reactive — re-renders after lazy load completes\n\n const route = router.currentRoute()\n\n if (route.matched.length === 0) return null\n\n // Render the matched record at this view's depth level\n const record = route.matched[depth]\n if (!record) return null // no component at this nesting level\n\n const cached = router._componentCache.get(record)\n if (cached) {\n return renderWithLoader(router, record, cached, route)\n }\n\n const raw = record.component\n\n if (!isLazy(raw)) {\n cacheSet(router, record, raw)\n return renderWithLoader(router, record, raw, route)\n }\n\n return renderLazyRoute(router, record, raw)\n }\n\n return h('div', { 'data-pyreon-router-view': true }, child as unknown as VNodeChild)\n}\n\n// ─── RouterLink ───────────────────────────────────────────────────────────────\n\nexport interface RouterLinkProps extends Props {\n to: string\n /** If true, uses router.replace() instead of router.push() */\n replace?: boolean\n /** CSS class applied when this link is active (default: \"router-link-active\") */\n activeClass?: string\n /** CSS class for exact-match active state (default: \"router-link-exact-active\") */\n exactActiveClass?: string\n /** If true, only applies activeClass on exact match */\n exact?: boolean\n /**\n * Prefetch strategy for loader data:\n * - \"hover\" (default) — prefetch when the user hovers over the link\n * - \"viewport\" — prefetch when the link scrolls into the viewport\n * - \"none\" — no prefetching\n */\n prefetch?: 'hover' | 'viewport' | 'none'\n children?: VNodeChild | null\n}\n\nexport const RouterLink: ComponentFn<RouterLinkProps> = (props) => {\n const router = useContext(RouterContext)\n const prefetchMode = props.prefetch ?? 'hover'\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault()\n if (!router) return\n if (props.replace) {\n router.replace(props.to)\n } else {\n router.push(props.to)\n }\n }\n\n const handleMouseEnter = () => {\n if (prefetchMode !== 'hover' || !router) return\n prefetchRoute(router as RouterInstance, props.to)\n }\n\n const inst = router as RouterInstance | null\n const href = inst?.mode === 'history' ? `${inst._base}${props.to}` : `#${props.to}`\n\n const activeClass = (): string => {\n if (!router) return ''\n const current = router.currentRoute().path\n const target = props.to\n const isExact = current === target\n const isActive = isExact || (!props.exact && isSegmentPrefix(current, target))\n\n const classes: string[] = []\n if (isActive) classes.push(props.activeClass ?? 'router-link-active')\n if (isExact) classes.push(props.exactActiveClass ?? 'router-link-exact-active')\n return classes.join(' ').trim()\n }\n\n // Viewport prefetching — observe link visibility with IntersectionObserver\n const ref = createRef<Element>()\n if (prefetchMode === 'viewport' && router && typeof IntersectionObserver !== 'undefined') {\n const observer = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n prefetchRoute(router as RouterInstance, props.to)\n observer.disconnect()\n break\n }\n }\n })\n // Observe after mount — the ref will be populated once the element is in the DOM\n queueMicrotask(() => {\n observer.observe(ref.current as Element)\n })\n onUnmount(() => observer.disconnect())\n }\n\n // Forward all non-RouterLink props (style, class, id, data-*, etc.) to the <a>.\n const { to: _to, replace: _replace, activeClass: _ac, exactActiveClass: _eac, exact: _exact, prefetch: _prefetch, children, ...rest } = props\n\n return h(\n 'a',\n { ...rest, ref, href, class: activeClass, onClick: handleClick, onMouseEnter: handleMouseEnter },\n children ?? props.to,\n )\n}\n\n/** Prefetch loader data for a route (only once per router + path). */\nfunction prefetchRoute(router: RouterInstance, path: string): void {\n let set = _prefetched.get(router)\n if (!set) {\n set = new Set()\n _prefetched.set(router, set)\n }\n if (set.has(path)) return\n set.add(path)\n prefetchLoaderData(router, path).catch(() => {\n // Silently ignore — prefetch is best-effort\n set?.delete(path)\n })\n}\n\nfunction renderLazyRoute(\n router: RouterInstance,\n record: RouteRecord,\n raw: LazyComponent,\n): VNodeChild {\n if (router._erroredChunks.has(record)) {\n return raw.errorComponent ? h(raw.errorComponent, {}) : null\n }\n\n const tryLoad = (attempt: number): Promise<void> =>\n raw\n .loader()\n .then((mod) => {\n const resolved = typeof mod === 'function' ? mod : mod.default\n cacheSet(router, record, resolved)\n router._loadingSignal.update((n) => n + 1)\n })\n .catch((err: unknown) => {\n if (attempt < 3) {\n return new Promise<void>((res) => setTimeout(res, 500 * 2 ** attempt)).then(() =>\n tryLoad(attempt + 1),\n )\n }\n if (typeof window !== 'undefined' && isStaleChunk(err)) {\n window.location.reload()\n return\n }\n\n router._erroredChunks.add(record)\n router._loadingSignal.update((n) => n + 1)\n })\n\n tryLoad(0)\n return raw.loadingComponent ? h(raw.loadingComponent, {}) : null\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Wraps the route component with a LoaderDataProvider so `useLoaderData()` works\n * inside the component. If the record has no loader, renders the component directly.\n */\nfunction renderWithLoader(\n router: RouterInstance,\n record: RouteRecord,\n Comp: ComponentFn,\n route: Pick<ResolvedRoute, 'params' | 'query' | 'meta'>,\n): VNodeChild {\n const routeProps = { params: route.params, query: route.query, meta: route.meta }\n\n // If route has an error component, wrap rendering in error boundary\n if (record.errorComponent) {\n return h(ErrorBoundary, {\n fallback: (error: Error) => h(record.errorComponent!, { ...routeProps, error }),\n children: record.loader\n ? renderLoaderContent(router, record, Comp, routeProps)\n : h(Comp, routeProps),\n })\n }\n\n if (!record.loader) return h(Comp, routeProps)\n return renderLoaderContent(router, record, Comp, routeProps)\n}\n\nfunction renderLoaderContent(\n router: RouterInstance,\n record: RouteRecord,\n Comp: ComponentFn,\n routeProps: Record<string, unknown>,\n): VNodeChild {\n const data = router._loaderData.get(record)\n if (data === undefined && record.errorComponent) {\n return h(record.errorComponent, routeProps)\n }\n return h(LoaderDataProvider, { data, children: h(Comp, routeProps) })\n}\n\n/**\n * Thin provider component that pushes LoaderDataContext before children mount.\n * Uses Pyreon's context stack so useLoaderData() reads it during child setup.\n */\nfunction LoaderDataProvider(props: { data: unknown; children: VNodeChild }): VNodeChild {\n provide(LoaderDataContext, props.data)\n return props.children\n}\n\n/** Evict oldest cache entries when the component cache exceeds maxCacheSize. */\nfunction cacheSet(router: RouterInstance, record: RouteRecord, comp: ComponentFn): void {\n router._componentCache.set(record, comp)\n if (router._componentCache.size > router._maxCacheSize) {\n // Map iterates in insertion order — first key is oldest\n const oldest = router._componentCache.keys().next().value as RouteRecord\n router._componentCache.delete(oldest)\n }\n}\n\n/**\n * Segment-aware prefix check for active link matching.\n * `/admin` is a prefix of `/admin/users` but NOT of `/admin-panel`.\n */\nfunction isSegmentPrefix(current: string, target: string): boolean {\n if (target === '/') return false\n const cs = current.split('/').filter(Boolean)\n const ts = target.split('/').filter(Boolean)\n if (ts.length > cs.length) return false\n return ts.every((seg, i) => seg === cs[i])\n}\n\n/**\n * Detect a stale chunk error — happens post-deploy when the browser requests\n * a hashed filename that no longer exists on the server. Trigger a full reload\n * so the user gets the new bundle instead of a broken loading state.\n */\nfunction isStaleChunk(err: unknown): boolean {\n if (err instanceof TypeError && String(err.message).includes('Failed to fetch')) return true\n if (err instanceof SyntaxError) return true\n return false\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/loader.ts","../src/match.ts","../src/scroll.ts","../src/types.ts","../src/router.ts","../src/components.tsx"],"sourcesContent":["import type { Context } from '@pyreon/core'\nimport { createContext, useContext } from '@pyreon/core'\nimport type { RouterInstance } from './types'\n\n/**\n * Context frame that holds the loader data for the currently rendered route record.\n * Pushed by RouterView's withLoaderData wrapper before invoking the route component.\n */\nexport const LoaderDataContext: Context<unknown> = createContext<unknown>(undefined)\n\n/**\n * Returns the data resolved by the current route's `loader` function.\n * Must be called inside a route component rendered by <RouterView />.\n *\n * @example\n * const routes = [{ path: \"/users\", component: Users, loader: fetchUsers }]\n *\n * function Users() {\n * const users = useLoaderData<User[]>()\n * return h(\"ul\", null, users.map(u => h(\"li\", null, u.name)))\n * }\n */\nexport function useLoaderData<T = unknown>(): T {\n return useContext(LoaderDataContext) as T\n}\n\n/**\n * SSR helper: pre-run all loaders for the given path before rendering.\n * Call this before `renderToString` so route components can read data via `useLoaderData()`.\n *\n * @example\n * const router = createRouter({ routes, url: req.url })\n * await prefetchLoaderData(router, req.url)\n * const html = await renderToString(h(App, { router }))\n */\nexport async function prefetchLoaderData(router: RouterInstance, path: string): Promise<void> {\n const route = router._resolve(path)\n // Use a local AbortController — prefetch is best-effort and must NOT\n // clobber `router._abortController`, which belongs to the active\n // navigation. Previously, hovering a link during a navigation replaced\n // the nav's controller, destroying its abort capability.\n const ac = new AbortController()\n await Promise.all(\n route.matched\n .filter((r) => r.loader)\n .map(async (r) => {\n const data = await r.loader?.({\n params: route.params,\n query: route.query,\n signal: ac.signal,\n })\n router._loaderData.set(r, data)\n }),\n )\n}\n\n/**\n * Serialize loader data to a JSON-safe plain object for embedding in SSR HTML.\n * Keys are route path patterns (stable across server and client).\n *\n * @example — SSR handler:\n * await prefetchLoaderData(router, req.url)\n * const { html, head } = await renderWithHead(h(App, null))\n * const page = `...${head}\n * <script>window.__PYREON_LOADER_DATA__=${JSON.stringify(serializeLoaderData(router))}</script>\n * ...${html}...`\n */\nexport function serializeLoaderData(router: RouterInstance): Record<string, unknown> {\n const result: Record<string, unknown> = {}\n for (const [record, data] of router._loaderData) {\n result[record.path] = data\n }\n return result\n}\n\n/**\n * Hydrate loader data from a serialized object (e.g. `window.__PYREON_LOADER_DATA__`).\n * Populates the router's internal `_loaderData` map so the initial render uses\n * server-fetched data without re-running loaders on the client.\n *\n * Call this before `mount()`, after `createRouter()`.\n *\n * @example — client entry:\n * import { hydrateLoaderData } from \"@pyreon/router\"\n * const router = createRouter({ routes })\n * hydrateLoaderData(router, window.__PYREON_LOADER_DATA__ ?? {})\n * mount(h(App, null), document.getElementById(\"app\")!)\n */\nexport function hydrateLoaderData(\n router: RouterInstance,\n serialized: Record<string, unknown>,\n): void {\n if (!serialized || typeof serialized !== 'object') return\n const route = router._resolve(router.currentRoute().path)\n for (const record of route.matched) {\n if (Object.hasOwn(serialized, record.path)) {\n router._loaderData.set(record, serialized[record.path])\n }\n }\n}\n","import type { ResolvedRoute, RouteMeta, RouteRecord } from './types'\n\n// ─── Query string ─────────────────────────────────────────────────────────────\n\n/**\n * Parse a query string into key-value pairs. Duplicate keys are overwritten\n * (last value wins). Use `parseQueryMulti` to preserve duplicates as arrays.\n */\nexport function parseQuery(qs: string): Record<string, string> {\n if (!qs) return {}\n const result: Record<string, string> = {}\n for (const part of qs.split('&')) {\n const eqIdx = part.indexOf('=')\n if (eqIdx < 0) {\n const key = decodeURIComponent(part)\n if (key) result[key] = ''\n } else {\n const key = decodeURIComponent(part.slice(0, eqIdx))\n const val = decodeURIComponent(part.slice(eqIdx + 1))\n if (key) result[key] = val\n }\n }\n return result\n}\n\n/**\n * Parse a query string preserving duplicate keys as arrays.\n *\n * @example\n * parseQueryMulti(\"color=red&color=blue&size=lg\")\n * // → { color: [\"red\", \"blue\"], size: \"lg\" }\n */\nexport function parseQueryMulti(qs: string): Record<string, string | string[]> {\n if (!qs) return {}\n const result: Record<string, string | string[]> = {}\n for (const part of qs.split('&')) {\n const eqIdx = part.indexOf('=')\n let key: string\n let val: string\n if (eqIdx < 0) {\n key = decodeURIComponent(part)\n val = ''\n } else {\n key = decodeURIComponent(part.slice(0, eqIdx))\n val = decodeURIComponent(part.slice(eqIdx + 1))\n }\n if (!key) continue\n const existing = result[key]\n if (existing === undefined) {\n result[key] = val\n } else if (Array.isArray(existing)) {\n existing.push(val)\n } else {\n result[key] = [existing, val]\n }\n }\n return result\n}\n\nexport function stringifyQuery(query: Record<string, string>): string {\n const parts: string[] = []\n for (const [k, v] of Object.entries(query)) {\n parts.push(v ? `${encodeURIComponent(k)}=${encodeURIComponent(v)}` : encodeURIComponent(k))\n }\n return parts.length ? `?${parts.join('&')}` : ''\n}\n\n// ─── Compiled route structures ───────────────────────────────────────────────\n\n/**\n * Pre-compiled segment info — computed once per route, reused on every match.\n * Avoids repeated split()/filter()/startsWith(\":\") on hot paths.\n */\ninterface CompiledSegment {\n /** Original segment string */\n raw: string\n /** true if this segment is a `:param` */\n isParam: boolean\n /** true if this segment is a `:param*` splat */\n isSplat: boolean\n /** true if this segment is a `:param?` optional */\n isOptional: boolean\n /** Param name (without leading `:` and trailing `*`/`?`) — empty for static segments */\n paramName: string\n}\n\ninterface CompiledRoute {\n route: RouteRecord\n /** true for wildcard patterns: `(.*)` or `*` */\n isWildcard: boolean\n /** Pre-split and classified segments */\n segments: CompiledSegment[]\n /** Number of segments */\n segmentCount: number\n /** true if route has no dynamic segments (pure static) */\n isStatic: boolean\n /** For static routes: the normalized path (e.g. \"/about\") for Map lookup */\n staticPath: string | null\n /** Compiled children (lazily populated) */\n children: CompiledRoute[] | null\n /** First static segment (for dispatch index), null if first segment is dynamic or route is wildcard */\n firstSegment: string | null\n}\n\n/**\n * A flattened route entry — pre-joins parent+child segments at compile time\n * so nested routes can be matched in a single pass without recursion.\n */\ninterface FlattenedRoute {\n /** All segments from root to leaf, concatenated */\n segments: CompiledSegment[]\n segmentCount: number\n /** The full matched chain from root to leaf (e.g. [adminLayout, usersPage]) */\n matchedChain: RouteRecord[]\n /** true if all segments are static */\n isStatic: boolean\n /** For static flattened routes: the full joined path */\n staticPath: string | null\n /** Pre-merged meta from all routes in the chain */\n meta: RouteMeta\n /** First static segment for dispatch index */\n firstSegment: string | null\n /** true if any segment is a splat */\n hasSplat: boolean\n /** true if this is a wildcard catch-all route (`*` or `(.*)`) */\n isWildcard: boolean\n /** true if any segment is optional (`:param?`) */\n hasOptional: boolean\n /** Minimum number of segments that must be present (excluding trailing optionals) */\n minSegments: number\n}\n\n/** WeakMap cache: compile each RouteRecord[] once */\nconst _compiledCache = new WeakMap<RouteRecord[], CompiledRoute[]>()\n\nfunction compileSegment(raw: string): CompiledSegment {\n if (raw.endsWith('*') && raw.startsWith(':')) {\n return { raw, isParam: true, isSplat: true, isOptional: false, paramName: raw.slice(1, -1) }\n }\n if (raw.endsWith('?') && raw.startsWith(':')) {\n return { raw, isParam: true, isSplat: false, isOptional: true, paramName: raw.slice(1, -1) }\n }\n if (raw.startsWith(':')) {\n return { raw, isParam: true, isSplat: false, isOptional: false, paramName: raw.slice(1) }\n }\n return { raw, isParam: false, isSplat: false, isOptional: false, paramName: '' }\n}\n\nfunction compileRoute(route: RouteRecord): CompiledRoute {\n const pattern = route.path\n const isWildcard = pattern === '(.*)' || pattern === '*'\n\n if (isWildcard) {\n return {\n route,\n isWildcard: true,\n segments: [],\n segmentCount: 0,\n isStatic: false,\n staticPath: null,\n children: null,\n firstSegment: null,\n }\n }\n\n const segments = pattern.split('/').filter(Boolean).map(compileSegment)\n const isStatic = segments.every((s) => !s.isParam)\n const staticPath = isStatic ? `/${segments.map((s) => s.raw).join('/')}` : null\n const first = segments.length > 0 ? segments[0] : undefined\n const firstSegment = first && !first.isParam ? first.raw : null\n\n return {\n route,\n isWildcard: false,\n segments,\n segmentCount: segments.length,\n isStatic,\n staticPath,\n children: null,\n firstSegment,\n }\n}\n\n/** Expand alias paths into additional compiled entries sharing the original RouteRecord */\nfunction expandAliases(r: RouteRecord, c: CompiledRoute): CompiledRoute[] {\n if (!r.alias) return []\n const aliases = Array.isArray(r.alias) ? r.alias : [r.alias]\n return aliases.map((aliasPath) => {\n const { alias: _, ...withoutAlias } = r\n const ac = compileRoute({ ...withoutAlias, path: aliasPath })\n ac.children = c.children\n ac.route = r\n return ac\n })\n}\n\nfunction compileRoutes(routes: RouteRecord[]): CompiledRoute[] {\n const cached = _compiledCache.get(routes)\n if (cached) return cached\n\n const compiled: CompiledRoute[] = []\n for (const r of routes) {\n const c = compileRoute(r)\n if (r.children && r.children.length > 0) {\n c.children = compileRoutes(r.children)\n }\n compiled.push(c)\n compiled.push(...expandAliases(r, c))\n }\n _compiledCache.set(routes, compiled)\n return compiled\n}\n\n// ─── Route flattening ────────────────────────────────────────────────────────\n\n/** Extract first static segment from a segment list, or null if dynamic/empty */\nfunction getFirstSegment(segments: CompiledSegment[]): string | null {\n const first = segments[0]\n if (first && !first.isParam) return first.raw\n return null\n}\n\n/** Build a FlattenedRoute from segments + metadata */\nfunction makeFlatEntry(\n segments: CompiledSegment[],\n chain: RouteRecord[],\n meta: RouteMeta,\n isWildcard: boolean,\n): FlattenedRoute {\n const isStatic = !isWildcard && segments.every((s) => !s.isParam)\n const hasOptional = segments.some((s) => s.isOptional)\n // minSegments: count of segments up to and not including trailing optionals\n let minSegs = segments.length\n if (hasOptional) {\n while (minSegs > 0 && segments[minSegs - 1]?.isOptional) minSegs--\n }\n return {\n segments,\n segmentCount: segments.length,\n matchedChain: chain,\n isStatic,\n staticPath: isStatic ? `/${segments.map((s) => s.raw).join('/')}` : null,\n meta,\n firstSegment: getFirstSegment(segments),\n hasSplat: segments.some((s) => s.isSplat),\n isWildcard,\n hasOptional,\n minSegments: minSegs,\n }\n}\n\n/**\n * Flatten nested routes into leaf entries with pre-joined segments.\n * This eliminates recursion during matching for the common case.\n */\nfunction flattenRoutes(compiled: CompiledRoute[]): FlattenedRoute[] {\n const result: FlattenedRoute[] = []\n flattenWalk(result, compiled, [], [], {})\n return result\n}\n\nfunction flattenWalk(\n result: FlattenedRoute[],\n routes: CompiledRoute[],\n parentSegments: CompiledSegment[],\n parentChain: RouteRecord[],\n parentMeta: RouteMeta,\n): void {\n for (const c of routes) {\n const chain = [...parentChain, c.route]\n const meta = c.route.meta ? { ...parentMeta, ...c.route.meta } : { ...parentMeta }\n flattenOne(result, c, parentSegments, chain, meta)\n }\n}\n\nfunction flattenOne(\n result: FlattenedRoute[],\n c: CompiledRoute,\n parentSegments: CompiledSegment[],\n chain: RouteRecord[],\n meta: RouteMeta,\n): void {\n if (c.isWildcard) {\n result.push(makeFlatEntry(parentSegments, chain, meta, true))\n if (c.children && c.children.length > 0) {\n flattenWalk(result, c.children, parentSegments, chain, meta)\n }\n return\n }\n\n const joined = [...parentSegments, ...c.segments]\n if (c.children && c.children.length > 0) {\n flattenWalk(result, c.children, joined, chain, meta)\n }\n result.push(makeFlatEntry(joined, chain, meta, false))\n}\n\n// ─── Combined index ─────────────────────────────────────────────────────────\n\ninterface RouteIndex {\n /** O(1) lookup for fully static paths (including nested) */\n staticMap: Map<string, FlattenedRoute>\n /** First-segment dispatch: maps first path segment → candidate routes */\n segmentMap: Map<string, FlattenedRoute[]>\n /** Routes whose first segment is dynamic (fallback) */\n dynamicFirst: FlattenedRoute[]\n /** Wildcard/catch-all routes */\n wildcards: FlattenedRoute[]\n}\n\nconst _indexCache = new WeakMap<RouteRecord[], RouteIndex>()\n\n/** Classify a single flattened route into the appropriate index bucket */\nfunction indexFlatRoute(\n f: FlattenedRoute,\n staticMap: Map<string, FlattenedRoute>,\n segmentMap: Map<string, FlattenedRoute[]>,\n dynamicFirst: FlattenedRoute[],\n wildcards: FlattenedRoute[],\n): void {\n // Static map: first static entry wins (preserves definition order)\n if (f.isStatic && f.staticPath && !staticMap.has(f.staticPath)) {\n staticMap.set(f.staticPath, f)\n }\n\n if (f.isWildcard) {\n wildcards.push(f)\n return\n }\n\n // Root route \"/\" has 0 segments — already in static map\n if (f.segmentCount === 0) return\n\n // First-segment dispatch\n if (f.firstSegment) {\n let bucket = segmentMap.get(f.firstSegment)\n if (!bucket) {\n bucket = []\n segmentMap.set(f.firstSegment, bucket)\n }\n bucket.push(f)\n } else {\n dynamicFirst.push(f)\n }\n}\n\nfunction buildRouteIndex(routes: RouteRecord[], compiled: CompiledRoute[]): RouteIndex {\n const cached = _indexCache.get(routes)\n if (cached) return cached\n\n const flattened = flattenRoutes(compiled)\n\n const staticMap = new Map<string, FlattenedRoute>()\n const segmentMap = new Map<string, FlattenedRoute[]>()\n const dynamicFirst: FlattenedRoute[] = []\n const wildcards: FlattenedRoute[] = []\n\n for (const f of flattened) {\n indexFlatRoute(f, staticMap, segmentMap, dynamicFirst, wildcards)\n }\n\n const index: RouteIndex = { staticMap, segmentMap, dynamicFirst, wildcards }\n _indexCache.set(routes, index)\n return index\n}\n\n// ─── Fast path splitting ─────────────────────────────────────────────────────\n\n/** Split path into segments without allocating a filtered array */\nfunction splitPath(path: string): string[] {\n // Fast path for common cases\n if (path === '/') return []\n // Remove leading slash, split, no filter needed if path is clean\n const start = path.charCodeAt(0) === 47 /* / */ ? 1 : 0\n const end = path.length\n if (start >= end) return []\n\n const parts: string[] = []\n let segStart = start\n for (let i = start; i <= end; i++) {\n if (i === end || path.charCodeAt(i) === 47 /* / */) {\n if (i > segStart) {\n parts.push(path.substring(segStart, i))\n }\n segStart = i + 1\n }\n }\n return parts\n}\n\n/** Decode only if the segment contains a `%` character */\nfunction decodeSafe(s: string): string {\n return s.indexOf('%') >= 0 ? decodeURIComponent(s) : s\n}\n\n// ─── Path matching (compiled) ────────────────────────────────────────────────\n\n/**\n * Match a single route pattern against a path segment.\n * Returns extracted params or null if no match.\n *\n * Supports:\n * - Exact segments: \"/about\"\n * - Param segments: \"/user/:id\"\n * - Wildcard: \"(.*)\" matches everything\n */\n/** Match a single pattern segment against a path segment, extracting params. Returns false on mismatch. */\nfunction matchPatternSegment(\n pp: string,\n pt: string | undefined,\n params: Record<string, string>,\n pathParts: string[],\n i: number,\n): 'splat' | 'continue' | 'fail' {\n if (pp.endsWith('*') && pp.startsWith(':')) {\n params[pp.slice(1, -1)] = pathParts.slice(i).map(decodeURIComponent).join('/')\n return 'splat'\n }\n if (pp.endsWith('?') && pp.startsWith(':')) {\n if (pt !== undefined) params[pp.slice(1, -1)] = decodeURIComponent(pt)\n return 'continue'\n }\n if (pt === undefined) return 'fail'\n if (pp.startsWith(':')) {\n params[pp.slice(1)] = decodeURIComponent(pt)\n return 'continue'\n }\n return pp === pt ? 'continue' : 'fail'\n}\n\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n if (pattern === '(.*)' || pattern === '*') return {}\n\n const patternParts = pattern.split('/').filter(Boolean)\n const pathParts = path.split('/').filter(Boolean)\n\n const params: Record<string, string> = {}\n for (let i = 0; i < patternParts.length; i++) {\n const result = matchPatternSegment(\n patternParts[i] as string,\n pathParts[i],\n params,\n pathParts,\n i,\n )\n if (result === 'splat') return params\n if (result === 'fail') return null\n }\n\n if (pathParts.length > patternParts.length) return null\n return params\n}\n\n// ─── Compiled matching helpers ────────────────────────────────────────────────\n\n/** Collect remaining path segments as a decoded splat value */\nfunction captureSplat(pathParts: string[], from: number, pathLen: number): string {\n const remaining: string[] = []\n for (let j = from; j < pathLen; j++) {\n const p = pathParts[j]\n if (p !== undefined) remaining.push(decodeSafe(p))\n }\n return remaining.join('/')\n}\n\n// ─── Flattened route matching ─────────────────────────────────────────────────\n\n/** Check whether a flattened route's segment count is compatible with the path length */\nfunction isSegmentCountCompatible(f: FlattenedRoute, pathLen: number): boolean {\n if (f.segmentCount === pathLen) return true\n if (f.hasSplat && pathLen >= f.segmentCount) return true\n if (f.hasOptional && pathLen >= f.minSegments && pathLen <= f.segmentCount) return true\n return false\n}\n\n/** Try to match a flattened route against path parts */\nfunction matchFlattened(\n f: FlattenedRoute,\n pathParts: string[],\n pathLen: number,\n): Record<string, string> | null {\n if (!isSegmentCountCompatible(f, pathLen)) return null\n\n const params: Record<string, string> = {}\n const segments = f.segments\n const count = f.segmentCount\n for (let i = 0; i < count; i++) {\n const seg = segments[i]\n const pt = pathParts[i]\n if (!seg) return null\n if (seg.isSplat) {\n params[seg.paramName] = captureSplat(pathParts, i, pathLen)\n return params\n }\n if (pt === undefined) {\n if (!seg.isOptional) return null\n continue\n }\n if (seg.isParam) {\n params[seg.paramName] = decodeSafe(pt)\n } else if (seg.raw !== pt) {\n return null\n }\n }\n return params\n}\n\n/** Search a list of flattened candidates for a match */\nfunction searchCandidates(\n candidates: FlattenedRoute[],\n pathParts: string[],\n pathLen: number,\n): MatchResult | null {\n for (let i = 0; i < candidates.length; i++) {\n const f = candidates[i]\n if (!f) continue\n const params = matchFlattened(f, pathParts, pathLen)\n if (params) {\n return { params, matched: f.matchedChain }\n }\n }\n return null\n}\n\n// ─── Route resolution ─────────────────────────────────────────────────────────\n\ninterface MatchResult {\n params: Record<string, string>\n matched: RouteRecord[]\n}\n\n/**\n * Resolve a raw path (including query string and hash) against the route tree.\n * Uses flattened index for O(1) static lookup and first-segment dispatch.\n */\nexport function resolveRoute(rawPath: string, routes: RouteRecord[]): ResolvedRoute {\n const qIdx = rawPath.indexOf('?')\n const pathAndHash = qIdx >= 0 ? rawPath.slice(0, qIdx) : rawPath\n const queryPart = qIdx >= 0 ? rawPath.slice(qIdx + 1) : ''\n\n const hIdx = pathAndHash.indexOf('#')\n const cleanPath = hIdx >= 0 ? pathAndHash.slice(0, hIdx) : pathAndHash\n const hash = hIdx >= 0 ? pathAndHash.slice(hIdx + 1) : ''\n\n const query = parseQuery(queryPart)\n\n // Build index (cached after first call)\n const compiled = compileRoutes(routes)\n const index = buildRouteIndex(routes, compiled)\n\n // Fast path 1: O(1) static Map lookup (covers nested static too)\n const staticMatch = index.staticMap.get(cleanPath)\n if (staticMatch) {\n return {\n path: cleanPath,\n params: {},\n query,\n hash,\n matched: staticMatch.matchedChain,\n meta: staticMatch.meta,\n }\n }\n\n // Split path for segment-based matching\n const pathParts = splitPath(cleanPath)\n const pathLen = pathParts.length\n\n // Fast path 2: first-segment dispatch (O(1) bucket lookup + small scan)\n if (pathLen > 0) {\n const first = pathParts[0] as string\n const bucket = index.segmentMap.get(first)\n if (bucket) {\n const match = searchCandidates(bucket, pathParts, pathLen)\n if (match) {\n return {\n path: cleanPath,\n params: match.params,\n query,\n hash,\n matched: match.matched,\n meta: mergeMeta(match.matched),\n }\n }\n }\n }\n\n // Fallback: dynamic-first-segment routes\n const dynMatch = searchCandidates(index.dynamicFirst, pathParts, pathLen)\n if (dynMatch) {\n return {\n path: cleanPath,\n params: dynMatch.params,\n query,\n hash,\n matched: dynMatch.matched,\n meta: mergeMeta(dynMatch.matched),\n }\n }\n\n // Fallback: wildcard/catch-all routes\n const w = index.wildcards[0]\n if (w) {\n return {\n path: cleanPath,\n params: {},\n query,\n hash,\n matched: w.matchedChain,\n meta: w.meta,\n }\n }\n\n return { path: cleanPath, params: {}, query, hash, matched: [], meta: {} }\n}\n\n/** Merge meta from matched routes (leaf takes precedence) */\nfunction mergeMeta(matched: RouteRecord[]): RouteMeta {\n const meta: RouteMeta = {}\n for (const record of matched) {\n if (record.meta) Object.assign(meta, record.meta)\n }\n return meta\n}\n\n/** Build a path string from a named route's pattern and params */\nexport function buildPath(pattern: string, params: Record<string, string>): string {\n const built = pattern.replace(/\\/:([^/]+)\\?/g, (_match, key) => {\n const val = params[key]\n // Optional param — omit the entire segment if no value provided\n if (!val) return ''\n return `/${encodeURIComponent(val)}`\n })\n return built.replace(/:([^/]+)\\*?/g, (match, key) => {\n const val = params[key] ?? ''\n // Splat params contain slashes — don't encode them\n if (match.endsWith('*')) return val.split('/').map(encodeURIComponent).join('/')\n return encodeURIComponent(val)\n })\n}\n\n/** Find a route record by name (recursive, O(n)). Prefer buildNameIndex for repeated lookups. */\nexport function findRouteByName(name: string, routes: RouteRecord[]): RouteRecord | null {\n for (const route of routes) {\n if (route.name === name) return route\n if (route.children) {\n const found = findRouteByName(name, route.children)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Pre-build a name → RouteRecord index from a route tree for O(1) named navigation.\n * Called once at router creation time; avoids O(n) depth-first search per push({ name }).\n */\nexport function buildNameIndex(routes: RouteRecord[]): Map<string, RouteRecord> {\n const index = new Map<string, RouteRecord>()\n function walk(list: RouteRecord[]): void {\n for (const route of list) {\n if (route.name) index.set(route.name, route)\n if (route.children) walk(route.children)\n }\n }\n walk(routes)\n return index\n}\n","import type { ResolvedRoute, RouterOptions } from './types'\n\n/**\n * Scroll restoration manager.\n *\n * Saves scroll position before each navigation and restores it when\n * navigating back to a previously visited path.\n */\n// LRU cap — in SPAs with unbounded URL space (`/user/:id`, query-string\n// variations, etc.) the `_positions` Map would grow per unique path\n// forever. 100 entries covers typical back-navigation depth; beyond that,\n// scroll restoration is a nice-to-have not a correctness requirement.\nconst MAX_SCROLL_POSITIONS = 100\n\nexport class ScrollManager {\n private readonly _positions = new Map<string, number>()\n private readonly _behavior: RouterOptions['scrollBehavior']\n\n constructor(behavior: RouterOptions['scrollBehavior'] = 'top') {\n this._behavior = behavior\n }\n\n /** Call before navigating away — saves current scroll position for `fromPath` */\n save(fromPath: string): void {\n // ScrollManager methods are only invoked from browser navigation paths,\n // but an explicit early-return documents the SSR-safety contract at the\n // callsite (the `no-window-in-ssr` lint rule can't AST-trace indirect\n // calls from router setup).\n if (typeof window === 'undefined') return\n // LRU: re-insert moves the entry to newest. Evict oldest when over cap.\n if (this._positions.has(fromPath)) this._positions.delete(fromPath)\n this._positions.set(fromPath, window.scrollY)\n while (this._positions.size > MAX_SCROLL_POSITIONS) {\n const oldest = this._positions.keys().next().value\n if (oldest === undefined) break\n this._positions.delete(oldest)\n }\n }\n\n /** Call after navigation is committed — applies scroll behavior */\n restore(to: ResolvedRoute, from: ResolvedRoute): void {\n const behavior = (to.meta.scrollBehavior as typeof this._behavior) ?? this._behavior ?? 'top'\n\n if (typeof behavior === 'function') {\n const saved = this._positions.get(to.path) ?? null\n const result = behavior(to, from, saved)\n this._applyResult(result, to.path)\n return\n }\n\n this._applyResult(behavior, to.path)\n }\n\n private _applyResult(result: 'top' | 'restore' | 'none' | number, toPath: string): void {\n if (typeof window === 'undefined') return\n // Hash scrolling: if the path contains #, scroll to the element\n const hashIdx = toPath.indexOf('#')\n if (hashIdx >= 0) {\n const id = toPath.slice(hashIdx + 1)\n if (id) {\n // Use requestAnimationFrame to ensure DOM is updated before scrolling\n requestAnimationFrame(() => {\n const el = document.getElementById(id)\n if (el) {\n el.scrollIntoView({ behavior: 'smooth' })\n return\n }\n // Fallback: try name attribute (for anchors)\n const namedEl = document.querySelector(`[name=\"${CSS.escape(id)}\"]`)\n if (namedEl) namedEl.scrollIntoView({ behavior: 'smooth' })\n })\n return\n }\n }\n\n if (result === 'none') return\n if (result === 'top' || result === undefined) {\n window.scrollTo({ top: 0, behavior: 'instant' as ScrollBehavior })\n return\n }\n if (result === 'restore') {\n const saved = this._positions.get(toPath) ?? 0\n window.scrollTo({ top: saved, behavior: 'instant' as ScrollBehavior })\n return\n }\n // At this point result must be a number (all string cases handled above)\n window.scrollTo({ top: result as number, behavior: 'instant' as ScrollBehavior })\n }\n\n getSavedPosition(path: string): number | null {\n return this._positions.get(path) ?? null\n }\n}\n","import type { ComponentFn } from '@pyreon/core'\n\nexport type { ComponentFn }\n\n// ─── Path param extraction ────────────────────────────────────────────────────\n\n/**\n * Extracts typed params from a path string at compile time.\n * Supports optional params via `:param?` — their type is `string | undefined`.\n *\n * @example\n * ExtractParams<'/user/:id/posts/:postId'>\n * // → { id: string; postId: string }\n *\n * ExtractParams<'/user/:id?'>\n * // → { id?: string | undefined }\n */\nexport type ExtractParams<T extends string> = T extends `${string}:${infer Param}*/${infer Rest}`\n ? { [K in Param]: string } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}*`\n ? { [K in Param]: string }\n : T extends `${string}:${infer Param}?/${infer Rest}`\n ? { [K in Param]?: string | undefined } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}?`\n ? { [K in Param]?: string | undefined }\n : T extends `${string}:${infer Param}/${infer Rest}`\n ? { [K in Param]: string } & ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? { [K in Param]: string }\n : Record<never, never>\n\n// ─── Route meta ───────────────────────────────────────────────────────────────\n\n/**\n * Route metadata interface. Extend it via module augmentation to add custom fields:\n *\n * @example\n * // globals.d.ts\n * declare module \"@pyreon/router\" {\n * interface RouteMeta {\n * requiresRole?: \"admin\" | \"user\"\n * pageTitle?: string\n * }\n * }\n */\nexport interface RouteMeta {\n /** Sets document.title on navigation */\n title?: string\n /** Page description (for meta tags) */\n description?: string\n /** If true, guards can redirect to login */\n requiresAuth?: boolean\n /** Scroll behavior for this route */\n scrollBehavior?: 'top' | 'restore' | 'none'\n /** Set to false to disable View Transitions API for this route. Default: true */\n viewTransition?: boolean\n}\n\n// ─── Resolved route ───────────────────────────────────────────────────────────\n\nexport interface ResolvedRoute<\n P extends Record<string, string | undefined> = Record<string, string>,\n Q extends Record<string, string> = Record<string, string>,\n> {\n path: string\n params: P\n query: Q\n hash: string\n /** All matched records from root to leaf (one per nesting level) */\n matched: RouteRecord[]\n meta: RouteMeta\n}\n\n// ─── Lazy component ───────────────────────────────────────────────────────────\n\nexport const LAZY_SYMBOL = Symbol('pyreon.lazy')\n\nexport interface LazyComponent {\n readonly [LAZY_SYMBOL]: true\n readonly loader: () => Promise<ComponentFn | { default: ComponentFn }>\n /** Optional component shown while the lazy chunk is loading */\n readonly loadingComponent?: ComponentFn\n /** Optional component shown after all retries have failed */\n readonly errorComponent?: ComponentFn\n}\n\nexport function lazy(\n loader: () => Promise<ComponentFn | { default: ComponentFn }>,\n options?: { loading?: ComponentFn; error?: ComponentFn },\n): LazyComponent {\n return {\n [LAZY_SYMBOL]: true,\n loader,\n ...(options?.loading ? { loadingComponent: options.loading } : {}),\n ...(options?.error ? { errorComponent: options.error } : {}),\n }\n}\n\nexport function isLazy(c: RouteComponent): c is LazyComponent {\n return typeof c === 'object' && c !== null && (c as LazyComponent)[LAZY_SYMBOL] === true\n}\n\nexport type RouteComponent = ComponentFn | LazyComponent\n\n// ─── Navigation guard ─────────────────────────────────────────────────────────\n\nexport type NavigationGuardResult = boolean | string | undefined\nexport type NavigationGuard = (\n to: ResolvedRoute,\n from: ResolvedRoute,\n) => NavigationGuardResult | Promise<NavigationGuardResult>\n\nexport type AfterEachHook = (to: ResolvedRoute, from: ResolvedRoute) => void\n\n// ─── Route middleware ────────────────────────────────────────────────────────\n\n/**\n * Context object passed through the middleware chain.\n * Middleware can read/write arbitrary data on `ctx.data`.\n */\nexport interface RouteMiddlewareContext {\n /** The route being navigated to. */\n to: ResolvedRoute\n /** The route being navigated from. */\n from: ResolvedRoute\n /** Shared data — middleware can accumulate state here for downstream middleware/components. */\n data: Record<string, unknown>\n}\n\n/**\n * Route middleware function. Called before guards.\n * - Return nothing/undefined to continue\n * - Return `false` to cancel navigation\n * - Return a string to redirect\n */\nexport type RouteMiddleware = (\n ctx: RouteMiddlewareContext,\n) => void | false | string | Promise<void | false | string>\n\n// ─── Navigation blockers ──────────────────────────────────────────────────────\n\n/**\n * Called before each navigation. Return `true` to block, `false` to allow.\n * Async blockers are supported (e.g. to show a confirmation dialog).\n */\nexport type BlockerFn = (to: ResolvedRoute, from: ResolvedRoute) => boolean | Promise<boolean>\n\nexport interface Blocker {\n /** Unregister this blocker so future navigations proceed freely. */\n remove(): void\n}\n\n// ─── Route loaders ────────────────────────────────────────────────────────────\n\nexport interface LoaderContext {\n params: Record<string, string>\n query: Record<string, string>\n /** Aborted when a newer navigation supersedes this one */\n signal: AbortSignal\n}\n\nexport type RouteLoaderFn = (ctx: LoaderContext) => Promise<unknown>\n\n// ─── Route record ─────────────────────────────────────────────────────────────\n\nexport interface RouteRecord<TPath extends string = string> {\n /** Path pattern — supports `:param` segments and `(.*)` wildcard */\n path: TPath\n component: RouteComponent\n /** Optional route name for named navigation */\n name?: string\n /** Metadata attached to this route */\n meta?: RouteMeta\n /**\n * Redirect target. Evaluated before guards.\n * String: redirect to that path.\n * Function: called with the resolved route, return path string.\n */\n redirect?: string | ((to: ResolvedRoute) => string)\n /** Guard(s) run only for this route, before global beforeEach guards */\n beforeEnter?: NavigationGuard | NavigationGuard[]\n /** Guard(s) run before leaving this route. Return false to cancel. */\n beforeLeave?: NavigationGuard | NavigationGuard[]\n /**\n * Alternative path(s) for this route. Alias paths render the same component\n * and share guards, loaders, and metadata with the primary path.\n *\n * @example\n * { path: \"/user/:id\", alias: [\"/profile/:id\"], component: UserPage }\n */\n alias?: string | string[]\n /** Child routes rendered inside this route's component via <RouterView /> */\n children?: RouteRecord[]\n /**\n * Data loader — runs before navigation commits, in parallel with sibling loaders.\n * The result is accessible via `useLoaderData()` inside the route component.\n * Receives an AbortSignal that fires if a newer navigation supersedes this one.\n */\n loader?: RouteLoaderFn\n /**\n * When true, the router shows cached loader data immediately (stale) and\n * revalidates in the background. The component re-renders once fresh data arrives.\n * Only applies when navigating to a route that already has cached loader data.\n */\n staleWhileRevalidate?: boolean\n /** Component rendered when this route's loader throws an error */\n errorComponent?: ComponentFn\n /** Per-route middleware — runs before guards, can accumulate context data. */\n middleware?: RouteMiddleware | RouteMiddleware[]\n}\n\n// ─── Router options ───────────────────────────────────────────────────────────\n\nexport type ScrollBehaviorFn = (\n to: ResolvedRoute,\n from: ResolvedRoute,\n savedPosition: number | null,\n) => 'top' | 'restore' | 'none' | number\n\nexport interface RouterOptions {\n routes: RouteRecord[]\n /** \"hash\" (default) uses location.hash; \"history\" uses pushState */\n mode?: 'hash' | 'history'\n /**\n * Base path for the application. Used when deploying to a sub-path\n * (e.g. `\"/app\"` for `https://example.com/app/`).\n * Only applies in history mode. Must start with `/`.\n * Default: `\"\"` (no base path).\n */\n base?: string\n /**\n * Global scroll behavior. Per-route meta.scrollBehavior takes precedence.\n * Default: \"top\"\n */\n scrollBehavior?: ScrollBehaviorFn | 'top' | 'restore' | 'none'\n /**\n * Initial URL for SSR. On the server, window.location is unavailable;\n * pass the request URL here so the router resolves the correct route.\n *\n * @example\n * // In your SSR handler:\n * const router = createRouter({ routes, url: req.url })\n */\n url?: string\n /**\n * Called when a route loader throws. If not provided, errors are logged\n * and the navigation continues with `undefined` data for the failed loader.\n * Return `false` to cancel the navigation.\n */\n onError?: (err: unknown, route: ResolvedRoute) => undefined | false\n /**\n * Maximum number of resolved lazy components to cache.\n * When exceeded, the oldest entry is evicted.\n * Default: 100.\n */\n maxCacheSize?: number\n /**\n * Trailing slash handling:\n * - `\"strip\"` — removes trailing slashes before matching (default)\n * - `\"add\"` — ensures paths always end with `/`\n * - `\"ignore\"` — no normalization\n */\n trailingSlash?: 'strip' | 'add' | 'ignore'\n}\n\n// ─── Router interface ─────────────────────────────────────────────────────────\n\n/**\n * Router interface. Parameterized by route name union for type-safe named navigation.\n *\n * @example\n * ```ts\n * type MyRoutes = 'home' | 'user' | 'settings'\n * const router: Router<MyRoutes> = createRouter({ routes })\n * router.push({ name: 'user', params: { id: '42' } }) // ✓\n * router.push({ name: 'typo' }) // TS error\n * ```\n */\nexport interface Router<TNames extends string = string> {\n /** Navigate to a path */\n push(path: string): Promise<void>\n /** Navigate to a named route */\n push(location: {\n name: TNames\n params?: Record<string, string>\n query?: Record<string, string>\n }): Promise<void>\n /** Replace current history entry */\n replace(path: string): Promise<void>\n /** Replace current history entry using a named route */\n replace(location: {\n name: TNames\n params?: Record<string, string>\n query?: Record<string, string>\n }): Promise<void>\n /** Go back one step in history */\n back(): void\n /** Go forward one step in history */\n forward(): void\n /** Navigate forward or backward by `delta` steps in the history stack */\n go(delta: number): void\n /** Register a global before-navigation guard. Returns an unregister function. */\n beforeEach(guard: NavigationGuard): () => void\n /** Register a global after-navigation hook. Returns an unregister function. */\n afterEach(hook: AfterEachHook): () => void\n /** Current resolved route (reactive signal) */\n readonly currentRoute: () => ResolvedRoute\n /** True while a navigation (guards + loaders) is in flight */\n readonly loading: () => boolean\n /**\n * Promise that resolves once the initial navigation is complete.\n * Useful for SSR and for delaying rendering until the first route is resolved.\n */\n isReady(): Promise<void>\n /**\n * Resolve `path` and prepare everything needed to render it: load any lazy\n * route components into the router's cache and run the matched routes'\n * loaders. After this resolves, a `RouterView` rendered against this router\n * for `path` will produce final HTML synchronously — no loading fallbacks,\n * no `useLoaderData()` returning `undefined`.\n *\n * Used by SSR/SSG to hydrate the route tree before `renderToString`.\n * The router's `currentRoute` is NOT changed by `preload` — pass the path\n * separately when creating the router (`createRouter({ url, ... })`) or\n * call this for the same `url` you initialised the router with.\n */\n preload(path: string): Promise<void>\n /** Remove all event listeners, clear caches, and abort in-flight navigations. */\n destroy(): void\n}\n\n// ─── Internal router instance ─────────────────────────────────────────────────\n\nimport type { Computed, Signal } from '@pyreon/reactivity'\n\nexport interface RouterInstance extends Router {\n routes: RouteRecord[]\n mode: 'hash' | 'history'\n /** Normalized base path (e.g. \"/app\"), empty string if none */\n _base: string\n _currentPath: Signal<string>\n _currentRoute: Computed<ResolvedRoute>\n _componentCache: Map<RouteRecord, ComponentFn>\n _loadingSignal: Signal<number>\n _resolve(rawPath: string): ResolvedRoute\n _scrollPositions: Map<string, number>\n _scrollBehavior: RouterOptions['scrollBehavior']\n _onError: RouterOptions['onError']\n _maxCacheSize: number\n /**\n * Current RouterView nesting depth. Incremented by each RouterView as it\n * mounts (in tree order = depth-first), so each view knows which level of\n * `matched[]` to render. Reset to 0 by RouterProvider.\n */\n _viewDepth: number\n /** Route records whose lazy chunk permanently failed (all retries exhausted) */\n _erroredChunks: Set<RouteRecord>\n /** Loader data keyed by route record — populated before each navigation commits */\n _loaderData: Map<RouteRecord, unknown>\n /** AbortController for the in-flight loader batch — aborted when a newer navigation starts */\n _abortController: AbortController | null\n /** Registered navigation blockers */\n _blockers: Set<BlockerFn>\n /** Resolves the isReady() promise after initial navigation completes */\n _readyResolve: (() => void) | null\n /** The isReady() promise instance */\n _readyPromise: Promise<void>\n}\n","import { createContext, onUnmount, useContext } from '@pyreon/core'\nimport { computed, signal } from '@pyreon/reactivity'\nimport { buildNameIndex, buildPath, resolveRoute, stringifyQuery } from './match'\nimport { ScrollManager } from './scroll'\nimport {\n type AfterEachHook,\n type Blocker,\n type BlockerFn,\n type ComponentFn,\n isLazy,\n type LoaderContext,\n type NavigationGuard,\n type NavigationGuardResult,\n type ResolvedRoute,\n type RouteMiddleware,\n type RouteMiddlewareContext,\n type RouteRecord,\n type Router,\n type RouterInstance,\n type RouterOptions,\n} from './types'\n\n// Evaluated once at module load — collapses to `true` in browser / happy-dom,\n// `false` on the server. Using a constant avoids per-call `typeof` branches\n// that are uncoverable in test environments.\nconst _isBrowser = typeof window !== 'undefined'\n// Dev-mode gate: see `pyreon/no-process-dev-gate` lint rule for why this\n// uses `import.meta.env.DEV` instead of `typeof process !== 'undefined'`.\n// @ts-ignore — `import.meta.env.DEV` is provided by Vite/Rolldown at build time\nconst __DEV__ = import.meta.env?.DEV === true\n\n// ─── Router context ───────────────────────────────────────────────────────────\n// Context-based access: isolated per request in SSR (ALS-backed via\n// @pyreon/runtime-server), isolated per component tree in CSR.\n// Falls back to the module-level singleton for code running outside a component\n// tree (e.g. programmatic navigation from event handlers).\n\nexport const RouterContext = createContext<RouterInstance | null>(null)\n\n// Module-level fallback — safe for CSR (single-threaded), not for concurrent SSR.\n// RouterProvider also sets this so legacy useRouter() calls outside the tree work.\nlet _activeRouter: RouterInstance | null = null\n\nexport function getActiveRouter(): RouterInstance | null {\n return useContext(RouterContext) ?? _activeRouter\n}\n\nexport function setActiveRouter(router: RouterInstance | null): void {\n if (router) router._viewDepth = 0\n _activeRouter = router\n}\n\n// ─── Hooks ────────────────────────────────────────────────────────────────────\n\nexport function useRouter(): Router {\n const router = useContext(RouterContext) ?? _activeRouter\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return router\n}\n\nexport function useRoute<TPath extends string = string>(): () => ResolvedRoute<\n import('./types').ExtractParams<TPath> & Record<string, string>,\n Record<string, string>\n> {\n const router = useContext(RouterContext) ?? _activeRouter\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return router.currentRoute as never\n}\n\n/**\n * In-component guard: called before the component's route is left.\n * Return `false` to cancel, a string to redirect, or `undefined`/`true` to proceed.\n * Automatically removed on component unmount.\n *\n * @example\n * onBeforeRouteLeave((to, from) => {\n * if (hasUnsavedChanges()) return false\n * })\n */\nexport function onBeforeRouteLeave(guard: NavigationGuard): () => void {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n // Register as a global guard that only fires when leaving the current route\n const currentMatched = router.currentRoute().matched\n const wrappedGuard: NavigationGuard = (to, from) => {\n // Only fire if we're actually leaving one of the matched routes\n const isLeaving = from.matched.some((r) => currentMatched.includes(r))\n if (!isLeaving) return undefined\n return guard(to, from)\n }\n const remove = router.beforeEach(wrappedGuard)\n onUnmount(() => remove())\n return remove\n}\n\n/**\n * In-component guard: called when the route changes but the component is reused\n * (e.g. `/user/1` → `/user/2`). Useful for reacting to param changes.\n * Automatically removed on component unmount.\n *\n * @example\n * onBeforeRouteUpdate((to, from) => {\n * if (!isValidId(to.params.id)) return false\n * })\n */\nexport function onBeforeRouteUpdate(guard: NavigationGuard): () => void {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n const currentMatched = router.currentRoute().matched\n const wrappedGuard: NavigationGuard = (to, from) => {\n // Only fire when the same component is reused (matched routes overlap)\n const isReused = to.matched.some((r) => currentMatched.includes(r))\n if (!isReused) return undefined\n return guard(to, from)\n }\n const remove = router.beforeEach(wrappedGuard)\n onUnmount(() => remove())\n return remove\n}\n\n/**\n * Register a navigation blocker. The `fn` callback is called before each\n * navigation — return `true` (or resolve to `true`) to block it.\n *\n * Automatically removed on component unmount if called during component setup.\n * Also installs a `beforeunload` handler so the browser shows a confirmation\n * dialog when the user tries to close the tab while a blocker is active.\n *\n * @example\n * const blocker = useBlocker((to, from) => {\n * return hasUnsavedChanges() && !confirm(\"Discard changes?\")\n * })\n * // later: blocker.remove()\n */\nexport function useBlocker(fn: BlockerFn): Blocker {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n router._blockers.add(fn)\n\n // Warn before tab/window close while this blocker is registered\n const beforeUnloadHandler = _isBrowser\n ? (e: BeforeUnloadEvent) => {\n e.preventDefault()\n }\n : null\n if (beforeUnloadHandler) {\n window.addEventListener('beforeunload', beforeUnloadHandler)\n }\n\n const remove = () => {\n router._blockers.delete(fn)\n if (beforeUnloadHandler) {\n window.removeEventListener('beforeunload', beforeUnloadHandler)\n }\n }\n\n // Auto-remove when the component that called useBlocker unmounts\n onUnmount(() => remove())\n\n return { remove }\n}\n\n/**\n * Reactive read/write access to the current route's query parameters.\n *\n * Returns `[get, set]` where `get` is a reactive signal producing the merged\n * query object and `set` navigates to the current path with updated params.\n *\n * @example\n * const [params, setParams] = useSearchParams({ page: \"1\", sort: \"name\" })\n * params().page // \"1\" if not in URL\n * setParams({ page: \"2\" }) // navigates to ?page=2&sort=name\n */\n/**\n * Check if a path is active (matches the current route).\n * Returns a reactive boolean signal.\n *\n * - Exact mode: `/admin` matches only `/admin`\n * - Partial mode (default): `/admin` matches `/admin`, `/admin/users`, `/admin/settings`\n * Uses segment-aware prefix matching — `/admin` does NOT match `/admin-panel`\n *\n * @example\n * ```tsx\n * const isAdmin = useIsActive(\"/admin\") // partial — matches /admin/*\n * const isExact = useIsActive(\"/admin\", true) // exact — only /admin\n *\n * <div class={isAdmin() ? \"active\" : \"\"}>Admin</div>\n * <Show when={isAdmin()}><Badge>Active</Badge></Show>\n * ```\n */\nexport function useIsActive(path: string, exact = false): () => boolean {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return () => {\n const current = router.currentRoute().path\n if (exact) {\n return matchSegments(current, path, true)\n }\n if (path === '/') return current === '/'\n // Segment-aware prefix: /admin matches /admin/users but NOT /admin-panel\n return matchSegments(current, path, false)\n }\n}\n\n/** Match current path segments against a pattern that may contain `:param` segments. */\nfunction matchSegments(current: string, pattern: string, exact: boolean): boolean {\n const cs = current.split('/').filter(Boolean)\n const ps = pattern.split('/').filter(Boolean)\n if (exact) {\n if (cs.length !== ps.length) return false\n return ps.every((seg, i) => seg.startsWith(':') || seg === cs[i])\n }\n if (ps.length > cs.length) return false\n return ps.every((seg, i) => seg.startsWith(':') || seg === cs[i])\n}\n\n/** Schema entry for typed search params. */\nexport type SearchParamSchema = {\n [key: string]: 'string' | 'number' | 'boolean'\n}\n\n/** Infer the typed result from a search param schema. */\ntype InferSearchParams<T extends SearchParamSchema> = {\n [K in keyof T]: T[K] extends 'number' ? number\n : T[K] extends 'boolean' ? boolean\n : string\n}\n\n/**\n * Read and write URL search params reactively.\n *\n * @example Basic (untyped)\n * ```ts\n * const [params, setParams] = useSearchParams({ page: \"1\" })\n * params().page // \"1\"\n * setParams({ page: \"2\" }) // updates URL\n * ```\n *\n * @example Typed with schema\n * ```ts\n * const [params, setParams] = useSearchParams({\n * page: 'number',\n * sort: 'string',\n * desc: 'boolean',\n * })\n * params().page // number (auto-coerced)\n * params().desc // boolean\n * ```\n */\nexport function useSearchParams<T extends Record<string, string>>(\n defaults?: T,\n): [get: () => T, set: (updates: Partial<T>) => Promise<void>] {\n const router = _getRouter()\n const get = (): T => {\n const query = router.currentRoute().query\n if (!defaults) return query as T\n return { ...defaults, ...query } as T\n }\n const set = (updates: Partial<T>): Promise<void> => {\n const merged = { ...get(), ...updates }\n const path = router.currentRoute().path + stringifyQuery(merged as Record<string, string>)\n return router.replace(path)\n }\n return [get, set]\n}\n\n/**\n * Typed search params with auto-coercion.\n *\n * Schema values define the type: `'string'`, `'number'`, or `'boolean'`.\n * Query string values are automatically coerced to the declared type.\n *\n * @example\n * ```ts\n * const [params, setParams] = useTypedSearchParams({\n * page: 'number',\n * sort: 'string',\n * desc: 'boolean',\n * })\n * params().page // number (coerced from \"3\" → 3)\n * params().desc // boolean (coerced from \"true\" → true)\n * setParams({ page: 2 }) // updates URL with ?page=2\n * ```\n */\nexport function useTypedSearchParams<T extends SearchParamSchema>(\n schema: T,\n): [get: () => InferSearchParams<T>, set: (updates: Partial<InferSearchParams<T>>) => Promise<void>] {\n const router = _getRouter()\n const get = (): InferSearchParams<T> => {\n const query = router.currentRoute().query\n const result: Record<string, unknown> = {}\n for (const [key, type] of Object.entries(schema)) {\n const raw = query[key]\n if (type === 'number') result[key] = raw !== undefined ? Number(raw) : 0\n else if (type === 'boolean') result[key] = raw === 'true' || raw === '1'\n else result[key] = raw ?? ''\n }\n return result as InferSearchParams<T>\n }\n const set = (updates: Partial<InferSearchParams<T>>): Promise<void> => {\n const current = get()\n const merged: Record<string, string> = {}\n for (const [k, v] of Object.entries({ ...current, ...updates })) {\n merged[k] = String(v)\n }\n const path = router.currentRoute().path + stringifyQuery(merged)\n return router.replace(path)\n }\n return [get, set]\n}\n\nfunction _getRouter(): RouterInstance {\n const router = (useContext(RouterContext) ?? _activeRouter) as RouterInstance | null\n if (!router)\n throw new Error(\n '[Pyreon] No router installed. Wrap your app in <RouterProvider router={router}>.',\n )\n return router\n}\n\n/**\n * Returns true while a navigation is in progress (guards + loaders running).\n * Use this to show loading indicators during route transitions.\n *\n * @example\n * ```tsx\n * const isNavigating = useTransition()\n * <Show when={isNavigating}>\n * <LoadingBar />\n * </Show>\n * ```\n */\nexport function useTransition(): () => boolean {\n const router = _getRouter()\n return () => router._loadingSignal() > 0\n}\n\n/**\n * Read data accumulated by route middleware.\n *\n * @example\n * ```ts\n * // In middleware:\n * const authMiddleware: RouteMiddleware = async (ctx) => {\n * ctx.data.user = await getUser(ctx.to)\n * if (!ctx.data.user) return '/login'\n * }\n *\n * // In component:\n * const data = useMiddlewareData()\n * const user = () => data().user as User\n * ```\n */\nexport function useMiddlewareData(): () => Record<string, unknown> {\n const router = _getRouter()\n return () => (router.currentRoute() as any)._middlewareData ?? {}\n}\n\n// ─── Factory ──────────────────────────────────────────────────────────────────\n\nexport function createRouter(options: RouterOptions | RouteRecord[]): Router {\n const opts: RouterOptions = Array.isArray(options) ? { routes: options } : options\n const {\n routes,\n mode = 'hash',\n scrollBehavior,\n onError,\n maxCacheSize = 100,\n trailingSlash = 'strip',\n } = opts\n\n // Base path only applies to history mode — hash-based routing already namespaces via #\n const base = mode === 'history' ? normalizeBase(opts.base ?? '') : ''\n\n // Pre-built O(1) name → record index. Computed once at startup.\n const nameIndex = buildNameIndex(routes)\n\n const guards: NavigationGuard[] = []\n const afterHooks: AfterEachHook[] = []\n const scrollManager = new ScrollManager(scrollBehavior)\n\n // Navigation generation counter — cancels in-flight navigations when a newer\n // one starts. Prevents out-of-order completion from stale async guards.\n let _navGen = 0\n\n // ── Initial location ──────────────────────────────────────────────────────\n\n const getInitialLocation = (): string => {\n // SSR: use explicitly provided url (strip base if present)\n if (opts.url) return stripBase(opts.url, base)\n if (!_isBrowser) return '/'\n if (mode === 'history') {\n return stripBase(window.location.pathname, base) + window.location.search\n }\n const hash = window.location.hash\n return hash.startsWith('#') ? hash.slice(1) || '/' : '/'\n }\n\n const getCurrentLocation = (): string => {\n if (!_isBrowser) return currentPath()\n if (mode === 'history') {\n return stripBase(window.location.pathname, base) + window.location.search\n }\n const hash = window.location.hash\n return hash.startsWith('#') ? hash.slice(1) || '/' : '/'\n }\n\n // ── Signals ───────────────────────────────────────────────────────────────\n\n const currentPath = signal(normalizeTrailingSlash(getInitialLocation(), trailingSlash))\n const currentRoute = computed<ResolvedRoute>(() => resolveRoute(currentPath(), routes))\n\n // Browser event listeners — stored so destroy() can remove them.\n // Ternary-bound on `_isBrowser` (a typeof-derived const) so the lint rule\n // can trace these to an SSR-safe shape without needing `if (_isBrowser &&\n // handler)` contortions at every use site.\n const _popstateHandler: (() => void) | null =\n _isBrowser && mode === 'history' ? () => currentPath.set(getCurrentLocation()) : null\n const _hashchangeHandler: (() => void) | null =\n _isBrowser && mode !== 'history' ? () => currentPath.set(getCurrentLocation()) : null\n\n if (_popstateHandler) window.addEventListener('popstate', _popstateHandler)\n if (_hashchangeHandler) window.addEventListener('hashchange', _hashchangeHandler)\n\n const componentCache = new Map<RouteRecord, ComponentFn>()\n const loadingSignal = signal(0)\n\n // ── Navigation ────────────────────────────────────────────────────────────\n\n type GuardOutcome =\n | { action: 'continue' }\n | { action: 'cancel' }\n | { action: 'redirect'; target: string }\n\n async function evaluateGuard(\n guard: NavigationGuard,\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n const result = await runGuard(guard, to, from)\n if (gen !== _navGen) return { action: 'cancel' }\n if (result === false) return { action: 'cancel' }\n if (typeof result === 'string') return { action: 'redirect', target: result }\n return { action: 'continue' }\n }\n\n async function runRouteGuards(\n records: RouteRecord[],\n guardKey: 'beforeLeave' | 'beforeEnter',\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n for (const record of records) {\n const raw = record[guardKey]\n if (!raw) continue\n const routeGuards = Array.isArray(raw) ? raw : [raw]\n for (const guard of routeGuards) {\n const outcome = await evaluateGuard(guard, to, from, gen)\n if (outcome.action !== 'continue') return outcome\n }\n }\n return { action: 'continue' }\n }\n\n async function runGlobalGuards(\n globalGuards: NavigationGuard[],\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n for (const guard of globalGuards) {\n const outcome = await evaluateGuard(guard, to, from, gen)\n if (outcome.action !== 'continue') return outcome\n }\n return { action: 'continue' }\n }\n\n function processLoaderResult(\n result: PromiseSettledResult<unknown>,\n record: RouteRecord,\n ac: AbortController,\n to: ResolvedRoute,\n ): boolean {\n if (result.status === 'fulfilled') {\n router._loaderData.set(record, result.value)\n return true\n }\n if (ac.signal.aborted) return true\n if (router._onError) {\n const cancel = router._onError(result.reason, to)\n if (cancel === false) return false\n }\n router._loaderData.set(record, undefined)\n return true\n }\n\n function syncBrowserUrl(path: string, replace: boolean): void {\n if (!_isBrowser) return\n const url = mode === 'history' ? `${base}${path}` : `#${path}`\n if (replace) {\n window.history.replaceState(null, '', url)\n } else {\n window.history.pushState(null, '', url)\n }\n }\n\n function resolveRedirect(to: ResolvedRoute): string | null {\n const leaf = to.matched[to.matched.length - 1]\n if (!leaf?.redirect) return null\n return sanitizePath(typeof leaf.redirect === 'function' ? leaf.redirect(to) : leaf.redirect)\n }\n\n async function runAllGuards(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<GuardOutcome> {\n const leaveOutcome = await runRouteGuards(from.matched, 'beforeLeave', to, from, gen)\n if (leaveOutcome.action !== 'continue') return leaveOutcome\n\n const enterOutcome = await runRouteGuards(to.matched, 'beforeEnter', to, from, gen)\n if (enterOutcome.action !== 'continue') return enterOutcome\n\n return runGlobalGuards(guards, to, from, gen)\n }\n\n async function runBlockingLoaders(\n records: RouteRecord[],\n to: ResolvedRoute,\n gen: number,\n ac: AbortController,\n ): Promise<boolean> {\n const loaderCtx: LoaderContext = { params: to.params, query: to.query, signal: ac.signal }\n const results = await Promise.allSettled(\n records.map((r) => (r.loader ? r.loader(loaderCtx) : Promise.resolve(undefined))),\n )\n if (gen !== _navGen) return false\n for (let i = 0; i < records.length; i++) {\n const result = results[i]\n const record = records[i]\n if (!result || !record) continue\n if (!processLoaderResult(result, record, ac, to)) return false\n }\n return true\n }\n\n /** Fire-and-forget background revalidation for stale-while-revalidate routes. */\n function revalidateSwrLoaders(records: RouteRecord[], to: ResolvedRoute, ac: AbortController) {\n const loaderCtx: LoaderContext = { params: to.params, query: to.query, signal: ac.signal }\n for (const r of records) {\n if (!r.loader) continue\n r.loader(loaderCtx)\n .then((data) => {\n if (!ac.signal.aborted) {\n router._loaderData.set(r, data)\n // Bump loadingSignal to trigger reactive re-render with fresh data\n loadingSignal.update((n) => n + 1)\n loadingSignal.update((n) => n - 1)\n }\n })\n .catch(() => {\n /* Background revalidation failure — stale data remains valid */\n })\n }\n }\n\n async function runLoaders(to: ResolvedRoute, gen: number, ac: AbortController): Promise<boolean> {\n const loadableRecords = to.matched.filter((r) => r.loader)\n if (loadableRecords.length === 0) return true\n\n const blocking: RouteRecord[] = []\n const swr: RouteRecord[] = []\n for (const r of loadableRecords) {\n if (r.staleWhileRevalidate && router._loaderData.has(r)) {\n swr.push(r)\n } else {\n blocking.push(r)\n }\n }\n\n if (blocking.length > 0) {\n const ok = await runBlockingLoaders(blocking, to, gen, ac)\n if (!ok) return false\n }\n if (swr.length > 0) revalidateSwrLoaders(swr, to, ac)\n return true\n }\n\n async function commitNavigation(\n path: string,\n replace: boolean,\n to: ResolvedRoute,\n from: ResolvedRoute,\n ): Promise<void> {\n scrollManager.save(from.path)\n\n const doCommit = () => {\n currentPath.set(path)\n syncBrowserUrl(path, replace)\n\n if (_isBrowser && to.meta.title) {\n document.title = to.meta.title\n }\n\n for (const record of router._loaderData.keys()) {\n if (!to.matched.includes(record)) {\n router._loaderData.delete(record)\n }\n }\n }\n\n // Use View Transitions API when available and not explicitly disabled.\n // Route meta can opt out: meta: { viewTransition: false }\n const useVT = _isBrowser\n && to.meta.viewTransition !== false\n && typeof (document as any).startViewTransition === 'function'\n\n if (useVT) {\n // `startViewTransition(cb)` runs `cb` inside an async transition. Its\n // `.updateCallbackDone` promise resolves as soon as the callback\n // finishes — DOM has swapped, state is live, but the fade/slide\n // animation is still running. That's what `await router.push()`\n // should wait for: callers need the new route live before they act\n // (e.g. focus an element, inspect `location`, query a new DOM node);\n // they don't want to block on the full animation (`.finished`),\n // which would add 200-300ms to every programmatic navigation.\n //\n // Before this await, `commitNavigation` was sync: the transition\n // callback ran in a later microtask, so `await router.push()`\n // resolved BEFORE the DOM swap. Browser smoke tests had to opt out\n // of View Transitions per-route via `meta: { viewTransition: false }`\n // to stay deterministic — a flag whose only purpose was to paper\n // over this bug.\n type ViewTransitionLike = {\n updateCallbackDone?: Promise<void>\n ready?: Promise<void>\n finished?: Promise<void>\n }\n const vt = (document as { startViewTransition?: (cb: () => void) => ViewTransitionLike | undefined })\n .startViewTransition!(() => {\n doCommit()\n })\n // `startViewTransition` may return `undefined` in test doubles\n // that shim it with a bare `(cb) => cb()`. Guard accordingly.\n if (vt) {\n // The ViewTransition object exposes THREE promises —\n // `updateCallbackDone`, `ready`, `finished`. When a newer\n // `startViewTransition()` starts while this one is in flight,\n // `ready` and `finished` reject with `AbortError: Transition\n // was skipped`. We only need to wait on `updateCallbackDone`\n // (the DOM-commit signal), but the other two MUST still be\n // handled or the rejection surfaces as an unhandled promise\n // rejection that breaks test runners and CI dashboards.\n vt.ready?.catch(() => {})\n vt.finished?.catch(() => {})\n if (vt.updateCallbackDone) {\n try {\n await vt.updateCallbackDone\n } catch {\n // `updateCallbackDone` rejects if the callback itself throws.\n // The DOM may be in a partial-commit state; the newer\n // navigation (if any) will re-commit. Swallow so the\n // navigation chain never hangs on a transition error.\n }\n }\n }\n } else {\n doCommit()\n }\n\n for (const hook of afterHooks) {\n try {\n hook(to, from)\n } catch (err) {\n if (__DEV__) {\n console.warn(`[Pyreon Router] afterEach hook threw an error:`, err)\n }\n }\n }\n\n if (_isBrowser) {\n queueMicrotask(() => scrollManager.restore(to, from))\n }\n }\n\n async function checkBlockers(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<'continue' | 'cancel'> {\n for (const blocker of router._blockers) {\n const blocked = await blocker(to, from)\n if (gen !== _navGen || blocked) return 'cancel'\n }\n return 'continue'\n }\n\n /** Run per-route middleware chain. Middleware from all matched routes execute in order. */\n async function runMiddleware(\n to: ResolvedRoute,\n from: ResolvedRoute,\n gen: number,\n ): Promise<{ action: 'continue' } | { action: 'cancel' } | { action: 'redirect'; target: string }> {\n const ctx: RouteMiddlewareContext = { to, from, data: {} }\n\n for (const record of to.matched) {\n if (!record.middleware) continue\n const mws = Array.isArray(record.middleware) ? record.middleware : [record.middleware]\n for (const mw of mws) {\n if (gen !== _navGen) return { action: 'cancel' }\n const result = await mw(ctx)\n if (result === false) return { action: 'cancel' }\n if (typeof result === 'string') return { action: 'redirect', target: result }\n }\n }\n\n // Store middleware data on the resolved route for component access\n ;(to as any)._middlewareData = ctx.data\n return { action: 'continue' }\n }\n\n async function navigate(rawPath: string, replace: boolean, redirectDepth = 0): Promise<void> {\n if (redirectDepth > 10) {\n if (__DEV__) {\n // oxlint-disable-next-line no-console\n console.warn(\n `[Pyreon] Navigation to \"${rawPath}\" aborted: redirect depth exceeded 10 levels. ` +\n 'This likely indicates a redirect loop in your route configuration.',\n )\n }\n return\n }\n\n const path = normalizeTrailingSlash(rawPath, trailingSlash)\n const gen = ++_navGen\n loadingSignal.update((n) => n + 1)\n\n const to = resolveRoute(path, routes)\n const from = currentRoute()\n\n const redirectTarget = resolveRedirect(to)\n if (redirectTarget !== null) {\n loadingSignal.update((n) => n - 1)\n return navigate(redirectTarget, replace, redirectDepth + 1)\n }\n\n const blockerResult = await checkBlockers(to, from, gen)\n if (blockerResult !== 'continue') {\n loadingSignal.update((n) => n - 1)\n return\n }\n\n // Run per-route middleware chain (before guards)\n const mwResult = await runMiddleware(to, from, gen)\n if (mwResult.action !== 'continue') {\n loadingSignal.update((n) => n - 1)\n if (mwResult.action === 'redirect') {\n return navigate(sanitizePath(mwResult.target), replace, redirectDepth + 1)\n }\n return\n }\n\n const guardOutcome = await runAllGuards(to, from, gen)\n if (guardOutcome.action !== 'continue') {\n loadingSignal.update((n) => n - 1)\n if (guardOutcome.action === 'redirect') {\n return navigate(sanitizePath(guardOutcome.target), replace, redirectDepth + 1)\n }\n return\n }\n\n router._abortController?.abort()\n const ac = new AbortController()\n router._abortController = ac\n\n const loadersOk = await runLoaders(to, gen, ac)\n if (!loadersOk) {\n loadingSignal.update((n) => n - 1)\n return\n }\n\n await commitNavigation(path, replace, to, from)\n loadingSignal.update((n) => n - 1)\n }\n\n // ── isReady promise ─────────────────────────────────────────────────────\n // Resolves after the first navigation (including guards + loaders) completes.\n\n let _readyResolve: (() => void) | null = null\n const _readyPromise = new Promise<void>((resolve) => {\n _readyResolve = resolve\n })\n\n // ── Public router object ──────────────────────────────────────────────────\n\n const router: RouterInstance = {\n routes,\n mode,\n _base: base,\n currentRoute,\n _currentPath: currentPath,\n _currentRoute: currentRoute,\n _componentCache: componentCache,\n _loadingSignal: loadingSignal,\n _scrollPositions: new Map(),\n _scrollBehavior: scrollBehavior,\n _viewDepth: 0,\n _erroredChunks: new Set(),\n _loaderData: new Map(),\n _abortController: null,\n _blockers: new Set(),\n _readyResolve,\n _readyPromise,\n _onError: onError,\n _maxCacheSize: maxCacheSize,\n\n async push(\n location:\n | string\n | { name: string; params?: Record<string, string>; query?: Record<string, string> },\n ) {\n if (typeof location === 'string') {\n const resolved = resolveRelativePath(location, currentPath())\n return navigate(sanitizePath(resolved), false)\n }\n const path = resolveNamedPath(\n location.name,\n location.params ?? {},\n location.query ?? {},\n nameIndex,\n )\n return navigate(path, false)\n },\n\n async replace(\n location:\n | string\n | { name: string; params?: Record<string, string>; query?: Record<string, string> },\n ) {\n if (typeof location === 'string') {\n const resolved = resolveRelativePath(location, currentPath())\n return navigate(sanitizePath(resolved), true)\n }\n const path = resolveNamedPath(\n location.name,\n location.params ?? {},\n location.query ?? {},\n nameIndex,\n )\n return navigate(path, true)\n },\n\n back() {\n if (_isBrowser) window.history.back()\n },\n\n forward() {\n if (_isBrowser) window.history.forward()\n },\n\n go(delta: number) {\n if (_isBrowser) window.history.go(delta)\n },\n\n beforeEach(guard: NavigationGuard) {\n guards.push(guard)\n return () => {\n const idx = guards.indexOf(guard)\n if (idx >= 0) guards.splice(idx, 1)\n }\n },\n\n afterEach(hook: AfterEachHook) {\n afterHooks.push(hook)\n return () => {\n const idx = afterHooks.indexOf(hook)\n if (idx >= 0) afterHooks.splice(idx, 1)\n }\n },\n\n loading: () => loadingSignal() > 0,\n\n isReady() {\n return router._readyPromise\n },\n\n async preload(path: string) {\n const resolved = resolveRoute(path, routes)\n // Load lazy components in parallel and populate the component cache so\n // the synchronous render pass finds ready components instead of kicking\n // off async imports (which would fall back to loadingComponent).\n await Promise.all(\n resolved.matched.map(async (record) => {\n if (componentCache.has(record)) return\n const raw = record.component\n if (!isLazy(raw)) {\n componentCache.set(record, raw)\n return\n }\n const mod = await raw.loader()\n const comp = typeof mod === 'function' ? mod : mod.default\n componentCache.set(record, comp)\n }),\n )\n // Run loaders for the matched path — uses the same code path SSR\n // already relied on, so loader data ends up in `_loaderData` under the\n // matched route records. Uses a LOCAL AbortController: `preload` is\n // a prefetch operation and must NOT clobber `router._abortController`,\n // which belongs to the active navigation. Without this, calling\n // `router.preload(...)` during a navigation destroyed the nav's\n // abort capability.\n const ac = new AbortController()\n await Promise.all(\n resolved.matched\n .filter((r) => r.loader)\n .map(async (r) => {\n const data = await r.loader?.({\n params: resolved.params,\n query: resolved.query,\n signal: ac.signal,\n })\n router._loaderData.set(r, data)\n }),\n )\n },\n\n destroy() {\n if (_popstateHandler) window.removeEventListener('popstate', _popstateHandler)\n if (_hashchangeHandler) window.removeEventListener('hashchange', _hashchangeHandler)\n guards.length = 0\n afterHooks.length = 0\n router._blockers.clear()\n componentCache.clear()\n router._loaderData.clear()\n router._abortController?.abort()\n router._abortController = null\n },\n\n _resolve: (rawPath: string) => resolveRoute(rawPath, routes),\n }\n\n // Initial route is resolved synchronously — mark ready on next microtask\n // so consumers can await isReady() before the first render.\n queueMicrotask(() => {\n if (router._readyResolve) {\n router._readyResolve()\n router._readyResolve = null\n }\n })\n\n return router\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nasync function runGuard(\n guard: NavigationGuard,\n to: ResolvedRoute,\n from: ResolvedRoute,\n): Promise<NavigationGuardResult> {\n try {\n return await guard(to, from)\n } catch (err) {\n if (__DEV__) {\n console.warn(`[Pyreon Router] Navigation guard threw an error — navigation cancelled:`, err)\n }\n return false\n }\n}\n\nfunction resolveNamedPath(\n name: string,\n params: Record<string, string>,\n query: Record<string, string>,\n index: Map<string, RouteRecord>,\n): string {\n const record = index.get(name)\n if (!record) {\n return '/'\n }\n let path = buildPath(record.path, params)\n const qs = Object.entries(query)\n .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)\n .join('&')\n if (qs) path += `?${qs}`\n return path\n}\n\n/** Normalize a base path: ensure leading `/`, strip trailing `/`. */\nfunction normalizeBase(raw: string): string {\n if (!raw) return ''\n let b = raw\n if (!b.startsWith('/')) b = `/${b}`\n if (b.endsWith('/')) b = b.slice(0, -1)\n return b\n}\n\n/** Strip the base prefix from a full URL path. Returns the app-relative path. */\nfunction stripBase(path: string, base: string): string {\n if (!base) return path\n if (path === base || path === `${base}/`) return '/'\n if (path.startsWith(`${base}/`)) return path.slice(base.length)\n return path\n}\n\n/** Normalize trailing slash on a path according to the configured strategy. */\nfunction normalizeTrailingSlash(path: string, strategy: 'strip' | 'add' | 'ignore'): string {\n if (strategy === 'ignore' || path === '/') return path\n // Split off query string + hash so we only touch the path portion\n const qIdx = path.indexOf('?')\n const hIdx = path.indexOf('#')\n const endIdx = qIdx >= 0 ? qIdx : hIdx >= 0 ? hIdx : path.length\n const pathPart = path.slice(0, endIdx)\n const suffix = path.slice(endIdx)\n if (strategy === 'strip') {\n return pathPart.length > 1 && pathPart.endsWith('/') ? pathPart.slice(0, -1) + suffix : path\n }\n // strategy === \"add\"\n return !pathPart.endsWith('/') ? `${pathPart}/${suffix}` : path\n}\n\n/**\n * Resolve a relative path (starting with `.` or `..`) against the current path.\n * Non-relative paths are returned as-is.\n */\nfunction resolveRelativePath(to: string, from: string): string {\n if (!to.startsWith('./') && !to.startsWith('../') && to !== '.' && to !== '..') return to\n\n // Split current path into segments, drop the last segment (file-like resolution)\n const fromSegments = from.split('/').filter(Boolean)\n fromSegments.pop()\n\n const toSegments = to.split('/').filter(Boolean)\n for (const seg of toSegments) {\n if (seg === '..') {\n fromSegments.pop()\n } else if (seg !== '.') {\n fromSegments.push(seg)\n }\n }\n return `/${fromSegments.join('/')}`\n}\n\n/** Block unsafe navigation targets: javascript/data/vbscript URIs and absolute URLs. */\nfunction sanitizePath(path: string): string {\n const trimmed = path.trim()\n if (/^(?:javascript|data|vbscript):/i.test(trimmed)) {\n return '/'\n }\n // Block absolute URLs and protocol-relative URLs — router only handles same-origin paths\n if (/^\\/\\/|^https?:/i.test(trimmed)) {\n return '/'\n }\n return path\n}\n\nexport { isLazy }\n","import type { ComponentFn, Props, VNodeChild } from '@pyreon/core'\nimport { createRef, ErrorBoundary, h, onUnmount, provide, useContext } from '@pyreon/core'\nimport { LoaderDataContext, prefetchLoaderData } from './loader'\nimport { isLazy, RouterContext, setActiveRouter } from './router'\nimport type { LazyComponent, ResolvedRoute, RouteRecord, Router, RouterInstance } from './types'\n\n// Track prefetched paths per router to avoid duplicate fetches\nconst _prefetched = new WeakMap<RouterInstance, Set<string>>()\n\n// ─── RouterProvider ───────────────────────────────────────────────────────────\n\nexport interface RouterProviderProps extends Props {\n router: Router\n children?: VNodeChild\n}\n\nexport const RouterProvider: ComponentFn<RouterProviderProps> = (props) => {\n const router = props.router as RouterInstance\n // Push router into the context stack — isolated per request in SSR via ALS,\n // isolated per component tree in CSR.\n provide(RouterContext, router)\n onUnmount(() => {\n // Clean up event listeners, caches, abort in-flight navigations.\n // Safe to call multiple times (destroy is idempotent).\n router.destroy()\n setActiveRouter(null)\n })\n // Also set the module fallback so programmatic useRouter() outside a component\n // tree (e.g. navigation guards in event handlers) still works in CSR.\n setActiveRouter(router)\n return props.children ?? null\n}\n\n// ─── RouterView ───────────────────────────────────────────────────────────────\n\nexport interface RouterViewProps extends Props {\n /** Explicitly pass a router (optional — uses the active router by default) */\n router?: Router\n}\n\n/**\n * Renders the matched route component at this nesting level.\n *\n * Nested layouts work by placing a second `<RouterView />` inside the layout\n * component — it automatically renders the next level of the matched route.\n *\n * How depth tracking works:\n * Pyreon components run once in depth-first tree order. Each `RouterView`\n * captures `router._viewDepth` at setup time and immediately increments it,\n * so sibling and child views get the correct index. `onUnmount` decrements\n * the counter so dynamic route swaps work correctly.\n *\n * @example\n * // Route config:\n * { path: \"/admin\", component: AdminLayout, children: [\n * { path: \"users\", component: AdminUsers },\n * ]}\n *\n * // AdminLayout renders a nested RouterView:\n * function AdminLayout() {\n * return <div><Sidebar /><RouterView /></div>\n * }\n */\nexport const RouterView: ComponentFn<RouterViewProps> = (props) => {\n const router = ((props.router as RouterInstance | undefined) ??\n useContext(RouterContext)) as RouterInstance | null\n if (!router) return null\n\n // Claim this view's depth at setup time (depth-first component init order)\n const depth = router._viewDepth\n router._viewDepth++\n\n onUnmount(() => {\n router._viewDepth--\n })\n\n const child = (): VNodeChild => {\n router._loadingSignal() // reactive — re-renders after lazy load completes\n\n const route = router.currentRoute()\n\n if (route.matched.length === 0) return null\n\n // Render the matched record at this view's depth level\n const record = route.matched[depth]\n if (!record) return null // no component at this nesting level\n\n const cached = router._componentCache.get(record)\n if (cached) {\n return renderWithLoader(router, record, cached, route)\n }\n\n const raw = record.component\n\n if (!isLazy(raw)) {\n cacheSet(router, record, raw)\n return renderWithLoader(router, record, raw, route)\n }\n\n return renderLazyRoute(router, record, raw)\n }\n\n return h('div', { 'data-pyreon-router-view': true }, child as unknown as VNodeChild)\n}\n\n// ─── RouterLink ───────────────────────────────────────────────────────────────\n\nexport interface RouterLinkProps extends Props {\n to: string\n /** If true, uses router.replace() instead of router.push() */\n replace?: boolean\n /** CSS class applied when this link is active (default: \"router-link-active\") */\n activeClass?: string\n /** CSS class for exact-match active state (default: \"router-link-exact-active\") */\n exactActiveClass?: string\n /** If true, only applies activeClass on exact match */\n exact?: boolean\n /**\n * Prefetch strategy for loader data:\n * - \"hover\" (default) — prefetch when the user hovers over the link\n * - \"viewport\" — prefetch when the link scrolls into the viewport\n * - \"none\" — no prefetching\n */\n prefetch?: 'hover' | 'viewport' | 'none'\n children?: VNodeChild | null\n}\n\nexport const RouterLink: ComponentFn<RouterLinkProps> = (props) => {\n const router = useContext(RouterContext)\n const prefetchMode = props.prefetch ?? 'hover'\n\n const handleClick = (e: MouseEvent) => {\n e.preventDefault()\n if (!router) return\n if (props.replace) {\n router.replace(props.to)\n } else {\n router.push(props.to)\n }\n }\n\n const handleMouseEnter = () => {\n if (prefetchMode !== 'hover' || !router) return\n prefetchRoute(router as RouterInstance, props.to)\n }\n\n const inst = router as RouterInstance | null\n const href = inst?.mode === 'history' ? `${inst._base}${props.to}` : `#${props.to}`\n\n const activeClass = (): string => {\n if (!router) return ''\n const current = router.currentRoute().path\n const target = props.to\n const isExact = current === target\n const isActive = isExact || (!props.exact && isSegmentPrefix(current, target))\n\n const classes: string[] = []\n if (isActive) classes.push(props.activeClass ?? 'router-link-active')\n if (isExact) classes.push(props.exactActiveClass ?? 'router-link-exact-active')\n return classes.join(' ').trim()\n }\n\n // Viewport prefetching — observe link visibility with IntersectionObserver\n const ref = createRef<Element>()\n if (prefetchMode === 'viewport' && router && typeof IntersectionObserver !== 'undefined') {\n const observer = new IntersectionObserver((entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n prefetchRoute(router as RouterInstance, props.to)\n observer.disconnect()\n break\n }\n }\n })\n // Observe after mount — the ref will be populated once the element is in the DOM\n queueMicrotask(() => {\n observer.observe(ref.current as Element)\n })\n onUnmount(() => observer.disconnect())\n }\n\n // Forward all non-RouterLink props (style, class, id, data-*, etc.) to the <a>.\n const { to: _to, replace: _replace, activeClass: _ac, exactActiveClass: _eac, exact: _exact, prefetch: _prefetch, children, ...rest } = props\n\n return h(\n 'a',\n { ...rest, ref, href, class: activeClass, onClick: handleClick, onMouseEnter: handleMouseEnter },\n children ?? props.to,\n )\n}\n\n/** Prefetch loader data for a route (only once per router + path). */\nfunction prefetchRoute(router: RouterInstance, path: string): void {\n let set = _prefetched.get(router)\n if (!set) {\n set = new Set()\n _prefetched.set(router, set)\n }\n if (set.has(path)) return\n set.add(path)\n prefetchLoaderData(router, path).catch(() => {\n // Silently ignore — prefetch is best-effort\n set?.delete(path)\n })\n}\n\nfunction renderLazyRoute(\n router: RouterInstance,\n record: RouteRecord,\n raw: LazyComponent,\n): VNodeChild {\n if (router._erroredChunks.has(record)) {\n return raw.errorComponent ? h(raw.errorComponent, {}) : null\n }\n\n const tryLoad = (attempt: number): Promise<void> =>\n raw\n .loader()\n .then((mod) => {\n const resolved = typeof mod === 'function' ? mod : mod.default\n cacheSet(router, record, resolved)\n router._loadingSignal.update((n) => n + 1)\n })\n .catch((err: unknown) => {\n if (attempt < 3) {\n return new Promise<void>((res) => setTimeout(res, 500 * 2 ** attempt)).then(() =>\n tryLoad(attempt + 1),\n )\n }\n if (typeof window !== 'undefined' && isStaleChunk(err)) {\n window.location.reload()\n return\n }\n\n router._erroredChunks.add(record)\n router._loadingSignal.update((n) => n + 1)\n })\n\n tryLoad(0)\n return raw.loadingComponent ? h(raw.loadingComponent, {}) : null\n}\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/**\n * Wraps the route component with a LoaderDataProvider so `useLoaderData()` works\n * inside the component. If the record has no loader, renders the component directly.\n */\nfunction renderWithLoader(\n router: RouterInstance,\n record: RouteRecord,\n Comp: ComponentFn,\n route: Pick<ResolvedRoute, 'params' | 'query' | 'meta'>,\n): VNodeChild {\n const routeProps = { params: route.params, query: route.query, meta: route.meta }\n\n // If route has an error component, wrap rendering in error boundary\n if (record.errorComponent) {\n return h(ErrorBoundary, {\n fallback: (error: Error) => h(record.errorComponent!, { ...routeProps, error }),\n children: record.loader\n ? renderLoaderContent(router, record, Comp, routeProps)\n : h(Comp, routeProps),\n })\n }\n\n if (!record.loader) return h(Comp, routeProps)\n return renderLoaderContent(router, record, Comp, routeProps)\n}\n\nfunction renderLoaderContent(\n router: RouterInstance,\n record: RouteRecord,\n Comp: ComponentFn,\n routeProps: Record<string, unknown>,\n): VNodeChild {\n const data = router._loaderData.get(record)\n if (data === undefined && record.errorComponent) {\n return h(record.errorComponent, routeProps)\n }\n return h(LoaderDataProvider, { data, children: h(Comp, routeProps) })\n}\n\n/**\n * Thin provider component that pushes LoaderDataContext before children mount.\n * Uses Pyreon's context stack so useLoaderData() reads it during child setup.\n */\nfunction LoaderDataProvider(props: { data: unknown; children: VNodeChild }): VNodeChild {\n provide(LoaderDataContext, props.data)\n return props.children\n}\n\n/** Evict oldest cache entries when the component cache exceeds maxCacheSize. */\nfunction cacheSet(router: RouterInstance, record: RouteRecord, comp: ComponentFn): void {\n router._componentCache.set(record, comp)\n if (router._componentCache.size > router._maxCacheSize) {\n // Map iterates in insertion order — first key is oldest\n const oldest = router._componentCache.keys().next().value as RouteRecord\n router._componentCache.delete(oldest)\n }\n}\n\n/**\n * Segment-aware prefix check for active link matching.\n * `/admin` is a prefix of `/admin/users` but NOT of `/admin-panel`.\n */\nfunction isSegmentPrefix(current: string, target: string): boolean {\n if (target === '/') return false\n const cs = current.split('/').filter(Boolean)\n const ts = target.split('/').filter(Boolean)\n if (ts.length > cs.length) return false\n return ts.every((seg, i) => seg === cs[i])\n}\n\n/**\n * Detect a stale chunk error — happens post-deploy when the browser requests\n * a hashed filename that no longer exists on the server. Trigger a full reload\n * so the user gets the new bundle instead of a broken loading state.\n */\nfunction isStaleChunk(err: unknown): boolean {\n if (err instanceof TypeError && String(err.message).includes('Failed to fetch')) return true\n if (err instanceof SyntaxError) return true\n return false\n}\n"],"mappings":"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"}
|
package/lib/types/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/components.tsx","../../../src/loader.ts","../../../src/match.ts","../../../src/router.ts"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index2.d.ts","names":[],"sources":["../../../src/types.ts","../../../src/components.tsx","../../../src/loader.ts","../../../src/match.ts","../../../src/router.ts"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pyreon/router",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.15",
|
|
4
4
|
"description": "Official router for Pyreon",
|
|
5
5
|
"homepage": "https://github.com/pyreon/pyreon/tree/main/packages/router#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"prepublishOnly": "bun run build"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@pyreon/core": "^0.12.
|
|
47
|
-
"@pyreon/reactivity": "^0.12.
|
|
48
|
-
"@pyreon/runtime-dom": "^0.12.
|
|
46
|
+
"@pyreon/core": "^0.12.15",
|
|
47
|
+
"@pyreon/reactivity": "^0.12.15",
|
|
48
|
+
"@pyreon/runtime-dom": "^0.12.15"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@happy-dom/global-registrator": "^20.8.9",
|
package/src/loader.ts
CHANGED
|
@@ -35,8 +35,11 @@ export function useLoaderData<T = unknown>(): T {
|
|
|
35
35
|
*/
|
|
36
36
|
export async function prefetchLoaderData(router: RouterInstance, path: string): Promise<void> {
|
|
37
37
|
const route = router._resolve(path)
|
|
38
|
+
// Use a local AbortController — prefetch is best-effort and must NOT
|
|
39
|
+
// clobber `router._abortController`, which belongs to the active
|
|
40
|
+
// navigation. Previously, hovering a link during a navigation replaced
|
|
41
|
+
// the nav's controller, destroying its abort capability.
|
|
38
42
|
const ac = new AbortController()
|
|
39
|
-
router._abortController = ac
|
|
40
43
|
await Promise.all(
|
|
41
44
|
route.matched
|
|
42
45
|
.filter((r) => r.loader)
|
package/src/router.ts
CHANGED
|
@@ -924,9 +924,12 @@ export function createRouter(options: RouterOptions | RouteRecord[]): Router {
|
|
|
924
924
|
)
|
|
925
925
|
// Run loaders for the matched path — uses the same code path SSR
|
|
926
926
|
// already relied on, so loader data ends up in `_loaderData` under the
|
|
927
|
-
// matched route records.
|
|
927
|
+
// matched route records. Uses a LOCAL AbortController: `preload` is
|
|
928
|
+
// a prefetch operation and must NOT clobber `router._abortController`,
|
|
929
|
+
// which belongs to the active navigation. Without this, calling
|
|
930
|
+
// `router.preload(...)` during a navigation destroyed the nav's
|
|
931
|
+
// abort capability.
|
|
928
932
|
const ac = new AbortController()
|
|
929
|
-
router._abortController = ac
|
|
930
933
|
await Promise.all(
|
|
931
934
|
resolved.matched
|
|
932
935
|
.filter((r) => r.loader)
|
package/src/scroll.ts
CHANGED
|
@@ -6,6 +6,12 @@ import type { ResolvedRoute, RouterOptions } from './types'
|
|
|
6
6
|
* Saves scroll position before each navigation and restores it when
|
|
7
7
|
* navigating back to a previously visited path.
|
|
8
8
|
*/
|
|
9
|
+
// LRU cap — in SPAs with unbounded URL space (`/user/:id`, query-string
|
|
10
|
+
// variations, etc.) the `_positions` Map would grow per unique path
|
|
11
|
+
// forever. 100 entries covers typical back-navigation depth; beyond that,
|
|
12
|
+
// scroll restoration is a nice-to-have not a correctness requirement.
|
|
13
|
+
const MAX_SCROLL_POSITIONS = 100
|
|
14
|
+
|
|
9
15
|
export class ScrollManager {
|
|
10
16
|
private readonly _positions = new Map<string, number>()
|
|
11
17
|
private readonly _behavior: RouterOptions['scrollBehavior']
|
|
@@ -21,7 +27,14 @@ export class ScrollManager {
|
|
|
21
27
|
// callsite (the `no-window-in-ssr` lint rule can't AST-trace indirect
|
|
22
28
|
// calls from router setup).
|
|
23
29
|
if (typeof window === 'undefined') return
|
|
30
|
+
// LRU: re-insert moves the entry to newest. Evict oldest when over cap.
|
|
31
|
+
if (this._positions.has(fromPath)) this._positions.delete(fromPath)
|
|
24
32
|
this._positions.set(fromPath, window.scrollY)
|
|
33
|
+
while (this._positions.size > MAX_SCROLL_POSITIONS) {
|
|
34
|
+
const oldest = this._positions.keys().next().value
|
|
35
|
+
if (oldest === undefined) break
|
|
36
|
+
this._positions.delete(oldest)
|
|
37
|
+
}
|
|
25
38
|
}
|
|
26
39
|
|
|
27
40
|
/** Call after navigation is committed — applies scroll behavior */
|
package/src/tests/loader.test.ts
CHANGED
|
@@ -78,6 +78,24 @@ describe('loader data serialization — edge cases', () => {
|
|
|
78
78
|
expect(values[0]).toEqual(complexData)
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
+
test('prefetchLoaderData does NOT clobber router._abortController', async () => {
|
|
82
|
+
// Regression: `prefetchLoaderData` used to overwrite
|
|
83
|
+
// `router._abortController` with its own fresh controller. Hovering
|
|
84
|
+
// a <Link> during an in-flight navigation destroyed the nav's
|
|
85
|
+
// abort capability — subsequent navigations couldn't cancel the
|
|
86
|
+
// first one. Fix: prefetch uses a LOCAL controller.
|
|
87
|
+
const routes: RouteRecord[] = [
|
|
88
|
+
{ path: '/data', component: Home, loader: async () => 'ok' },
|
|
89
|
+
]
|
|
90
|
+
const router = createRouter({ routes, url: '/' }) as RouterInstance
|
|
91
|
+
const navController = new AbortController()
|
|
92
|
+
router._abortController = navController
|
|
93
|
+
await prefetchLoaderData(router, '/data')
|
|
94
|
+
// Prefetch finished; nav's controller must be untouched.
|
|
95
|
+
expect(router._abortController).toBe(navController)
|
|
96
|
+
expect(navController.signal.aborted).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
81
99
|
test('prefetchLoaderData passes AbortSignal to loaders', async () => {
|
|
82
100
|
let receivedSignal: AbortSignal | undefined
|
|
83
101
|
const routes: RouteRecord[] = [
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest'
|
|
2
|
+
import { ScrollManager } from '../scroll'
|
|
3
|
+
|
|
4
|
+
describe('ScrollManager — LRU bound', () => {
|
|
5
|
+
test('evicts oldest entry when cap (100) is exceeded', () => {
|
|
6
|
+
const mgr = new ScrollManager('top')
|
|
7
|
+
// Fake window.scrollY for each save — happy-dom provides window.
|
|
8
|
+
Object.defineProperty(window, 'scrollY', { value: 42, configurable: true })
|
|
9
|
+
// Save 150 distinct paths.
|
|
10
|
+
for (let i = 0; i < 150; i++) mgr.save(`/path-${i}`)
|
|
11
|
+
// Oldest 50 evicted; newest 100 remain.
|
|
12
|
+
expect(mgr.getSavedPosition('/path-0')).toBeNull()
|
|
13
|
+
expect(mgr.getSavedPosition('/path-49')).toBeNull()
|
|
14
|
+
expect(mgr.getSavedPosition('/path-50')).toBe(42)
|
|
15
|
+
expect(mgr.getSavedPosition('/path-149')).toBe(42)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('re-saving an existing path bumps it to newest (LRU, not FIFO)', () => {
|
|
19
|
+
const mgr = new ScrollManager('top')
|
|
20
|
+
Object.defineProperty(window, 'scrollY', { value: 42, configurable: true })
|
|
21
|
+
for (let i = 0; i < 100; i++) mgr.save(`/path-${i}`)
|
|
22
|
+
// Touch the oldest entry — should move to newest.
|
|
23
|
+
Object.defineProperty(window, 'scrollY', { value: 99, configurable: true })
|
|
24
|
+
mgr.save('/path-0')
|
|
25
|
+
// Now add one more to push out the new-oldest (/path-1).
|
|
26
|
+
mgr.save('/new-path')
|
|
27
|
+
expect(mgr.getSavedPosition('/path-1')).toBeNull()
|
|
28
|
+
expect(mgr.getSavedPosition('/path-0')).toBe(99)
|
|
29
|
+
expect(mgr.getSavedPosition('/new-path')).toBe(99)
|
|
30
|
+
})
|
|
31
|
+
})
|