@elurjs/core 3.6.0 → 3.6.2
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/dist/lib/elur/router.cjs +1 -1
- package/dist/lib/elur/router.cjs.map +1 -1
- package/dist/lib/elur/router.js +1 -1
- package/dist/lib/elur/router.js.map +1 -1
- package/dist/lib/elur/server/index.cjs +1 -1
- package/dist/lib/elur/server/index.cjs.map +1 -1
- package/dist/lib/elur/server/index.js +5 -1
- package/dist/lib/elur/server/index.js.map +1 -1
- package/dist/lib/elur/store.cjs +1 -1
- package/dist/lib/elur/store.cjs.map +1 -1
- package/dist/lib/elur/store.js +9 -3
- package/dist/lib/elur/store.js.map +1 -1
- package/dist/lib/elur/template/bindings.cjs +1 -1
- package/dist/lib/elur/template/bindings.cjs.map +1 -1
- package/dist/lib/elur/template/bindings.js +1 -1
- package/dist/lib/elur/template/bindings.js.map +1 -1
- package/dist/lib/router.cjs +1 -1
- package/dist/lib/router.js +1 -1
- package/dist/lib/server.cjs +1 -1
- package/dist/lib/server.js +5 -1
- package/dist/lib/store.cjs +1 -1
- package/dist/lib/store.js +9 -3
- package/package.json +12 -1
package/dist/lib/elur/router.cjs
CHANGED
|
@@ -7,6 +7,6 @@ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=requi
|
|
|
7
7
|
`;let a=i.route.chain[e];return a?a():r.html`
|
|
8
8
|
<span></span>
|
|
9
9
|
`}}</div>`}},O=class extends t.ElurComponent{_to;_label;_router;constructor(e,t,n){super(),this._to=e,this._label=t,this._router=n}render(){let e=this._to,t=this._label,n=this._router??T(),i=e.startsWith(`/`)?e:`/`+e,a=(n._base?n._base+i:i).replace(/\/+/g,`/`);return r.html`
|
|
10
|
-
<a href=${n._mode===`hash`?`#`+a:a} style=${()=>n.current.value===
|
|
10
|
+
<a href=${n._mode===`hash`?`#`+a:a} style=${()=>n.current.value===i?`color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a`:`color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px`} @click=${t=>{t.preventDefault(),n.navigate(e)}}>${t}</a>
|
|
11
11
|
`}};function k(){let e=i._mountedRouters.length?i._mountedRouters[i._mountedRouters.length-1]:a;if(!e)return null;let t=e,n=t.current.value,r=x(n,t._flat),o=r?.route.beforeEnter,s=t._guards.map((e,t)=>e.name||`beforeEach#${t+1}`);return o&&s.push(o.name||`beforeEnter`),{mode:t._mode,base:t._base||`/`,currentPath:n,params:{...t.params.value},query:{...t.query.value},matchedPath:r?.route.fullPath??null,activeGuards:{globalCount:t._guards.length,hasRouteGuard:!!o,names:s}}}exports.Link=O,exports.RouterKey=i.RouterKey,exports.RouterView=D,exports._debugGetRouterInternal=k,exports._debugRegisterRouter=i._debugRegisterRouter,exports._debugUnregisterRouter=i._debugUnregisterRouter,exports._hasActiveRouter=u,exports._resetRouter=E,exports.createRouter=w,exports.elurRouter=T;
|
|
12
12
|
//# sourceMappingURL=router.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.cjs","names":[],"sources":["../../src/elur/router.ts"],"sourcesContent":["import { signal } from \"./reactivity.js\";\nimport type { Signal } from \"./reactivity.js\";\nimport { ElurComponent } from \"./lifecycle.js\";\nimport type { ElurTemplate } from \"./template/index.js\";\nimport { html } from \"./template/index.js\";\nimport { inject } from \"./context.js\";\nimport { RouterKey, _mountedRouters, _debugRegisterRouter, _debugUnregisterRouter } from \"./router-registry.js\";\n\n// =============================================================================\n// Public types\n// =============================================================================\n\n/**\n * Value returned (or resolved) by a navigation guard.\n *\n * - `true` / `void` / `undefined` — allow.\n * - `false` — cancel (no redirect).\n * - `string` — redirect to that path.\n * - `{ redirect: string }` — redirect, object form.\n *\n * The object form exists so guards written for the outlet API can be reused\n * verbatim by the core. See `elur-ionic`'s `GuardResult`.\n */\nexport type NavigationGuardResult =\n | void\n | undefined\n | boolean\n | string\n | { redirect: string };\n\n/** Guard function invoked before navigation commits. */\nexport type NavigationGuard = (\n to: string,\n from: string,\n) => NavigationGuardResult | Promise<NavigationGuardResult>;\n\nexport interface RouteRecord {\n /** Optional unique name to enable named navigation. */\n name?: string;\n /** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */\n path: string;\n /**\n * Factory returning the view for this route level.\n *\n * OPTIONAL — when the core router is auto-bootstrapped by an outlet\n * (Ionic's IonRouterOutlet, future others), the outlet owns component\n * mounting and the core never invokes this. In that case, omit it.\n */\n component?: () => ElurTemplate | ElurComponent;\n /** Optional arbitrary metadata for guards, layouts, and auth checks. */\n meta?: Record<string, unknown>;\n /** Child routes. Paths are joined with the parent. */\n children?: RouteRecord[];\n /** Route-level guard. Runs only when entering this specific route. */\n beforeEnter?: NavigationGuard;\n}\n\n/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */\nexport type AfterEachHook = (to: string, from: string) => void;\n\n/** Named route target for programmatic navigation. */\nexport interface NamedRouteLocation {\n name: string;\n params?: Record<string, string | number>;\n query?: Record<string, string | number | boolean | null | undefined>;\n}\n\n/** Navigation input accepted by `navigate` / `replace`. */\nexport type RouteLocation = string | NamedRouteLocation;\n\n/** Serializable scroll position used by the router for history restoration. */\nexport interface ScrollPosition {\n left: number;\n top: number;\n}\n\nexport type ScrollBehavior = (\n to: string,\n from: string,\n savedPosition: ScrollPosition | null,\n) => ScrollPosition | false | void;\n\nexport type RouterMode = \"history\" | \"hash\";\n\nexport interface ResolvedRoute {\n matched: boolean;\n params: Record<string, string>;\n route: RouteRecord | undefined;\n}\n\n// -----------------------------------------------------------------------------\n// Navigation intent\n// -----------------------------------------------------------------------------\n\nexport type NavigationAction = \"push\" | \"replace\" | \"pop\" | \"initial\";\nexport type NavigationDirection = \"forward\" | \"back\" | \"root\" | \"none\";\n\nexport interface NavigationIntent {\n action: NavigationAction;\n direction: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface NavigateOptions {\n query?: Record<string, string | number | boolean | null | undefined>;\n direction?: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface RouterOptions {\n base?: string;\n mode?: RouterMode;\n scrollBehavior?: ScrollBehavior;\n}\n\nexport interface Router {\n readonly current: Signal<string>;\n readonly params: Signal<Record<string, string>>;\n readonly query: Signal<Record<string, string>>;\n readonly base: string;\n readonly intent: Signal<NavigationIntent>;\n readonly canGoBack: Signal<boolean>;\n navigate(location: RouteLocation, options?: NavigateOptions): void;\n replace(location: RouteLocation, options?: NavigateOptions): void;\n back(animation?: unknown): void;\n forward(animation?: unknown): void;\n go(delta: number): void;\n isActive(path: string, exact?: boolean): boolean;\n resolve(path: string): ResolvedRoute;\n readonly routes: RouteRecord[];\n beforeEach(guard: NavigationGuard): () => void;\n afterEach(hook: AfterEachHook): () => void;\n}\n\n// RouterKey and the mounted-router debug registry live in router-registry.ts\n// so that component.ts (mount) can use them without importing this module —\n// keeping the router out of bundles that never call createRouter().\nexport { RouterKey, _debugRegisterRouter, _debugUnregisterRouter };\n\n// =============================================================================\n// Internals\n// =============================================================================\n\ntype Segment =\n | { kind: \"literal\"; value: string }\n | { kind: \"param\"; name: string }\n | { kind: \"wildcard\" };\n\ninterface FlatRoute {\n fullPath: string;\n segments: Segment[];\n chain: Array<(() => ElurTemplate | ElurComponent) | undefined>;\n name?: string;\n meta?: Record<string, unknown>;\n beforeEnter?: NavigationGuard;\n record: RouteRecord;\n}\n\ninterface RouterInternal extends Router {\n _flat: FlatRoute[];\n _guards: NavigationGuard[];\n _base: string;\n _mode: RouterMode;\n}\n\nlet _currentRouter: RouterInternal | null = null;\nlet _currentPopstateCleanup: (() => void) | null = null;\n\nconst SCROLL_STATE_KEY = \"__elur_scroll\";\nconst POSITION_STATE_KEY = \"__elur_pos\";\n\nfunction getRouter(): RouterInternal {\n if (!_currentRouter) {\n throw new Error(\n \"[elur] No active router. Call createRouter() first, \" +\n \"or instantiate an outlet that auto-bootstraps one (e.g. IonRouterOutlet).\"\n );\n }\n return _currentRouter;\n}\n\n/**\n * @internal Whether a router is currently active. Used by outlets that\n * want to auto-bootstrap if the user didn't call `createRouter()` themselves.\n */\nexport function _hasActiveRouter(): boolean {\n return _currentRouter !== null;\n}\n\n// =============================================================================\n// History state helpers\n// =============================================================================\n\nfunction getCurrentScrollPosition(): ScrollPosition {\n return {\n left: window.scrollX ?? window.pageXOffset ?? 0,\n top: window.scrollY ?? window.pageYOffset ?? 0,\n };\n}\n\nfunction readScrollPositionFromState(state: unknown): ScrollPosition | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[SCROLL_STATE_KEY];\n if (!raw || typeof raw !== \"object\") return null;\n const left = (raw as Record<string, unknown>).left;\n const top = (raw as Record<string, unknown>).top;\n if (typeof left !== \"number\" || typeof top !== \"number\") return null;\n return { left, top };\n}\n\nfunction readPositionFromState(state: unknown): number | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[POSITION_STATE_KEY];\n return typeof raw === \"number\" ? raw : null;\n}\n\nfunction buildHistoryState(\n prev: unknown,\n scroll: ScrollPosition,\n position: number,\n): Record<string, unknown> {\n const base = prev && typeof prev === \"object\"\n ? { ...(prev as Record<string, unknown>) }\n : {};\n base[SCROLL_STATE_KEY] = { left: scroll.left, top: scroll.top };\n base[POSITION_STATE_KEY] = position;\n return base;\n}\n\n// =============================================================================\n// Query / path helpers\n// =============================================================================\n\nfunction parseQuery(search: string): Record<string, string> {\n const result: Record<string, string> = {};\n new URLSearchParams(search).forEach((v, k) => { result[k] = v; });\n return result;\n}\n\nfunction buildQueryString(\n q: Record<string, string | number | boolean | null | undefined>,\n): string {\n const p = new URLSearchParams();\n for (const [k, v] of Object.entries(q)) {\n if (v != null && v !== false) p.set(k, String(v));\n }\n const s = p.toString();\n return s ? \"?\" + s : \"\";\n}\n\nfunction parseSegments(fullPath: string): Segment[] {\n if (fullPath === \"*\") return [{ kind: \"wildcard\" }];\n return fullPath\n .split(\"/\")\n .filter(Boolean)\n .map((part): Segment => {\n if (part === \"*\") return { kind: \"wildcard\" };\n if (part.startsWith(\":\")) return { kind: \"param\", name: part.slice(1) };\n return { kind: \"literal\", value: part };\n });\n}\n\nfunction joinPaths(parent: string, child: string): string {\n if (child === \"*\") return parent === \"\" ? \"*\" : parent + \"/*\";\n const segment = child.startsWith(\"/\") ? child : \"/\" + child;\n return (parent + segment).replace(/\\/+/g, \"/\") || \"/\";\n}\n\nfunction flattenRoutes(\n routes: RouteRecord[],\n parentPath = \"\",\n parentChain: Array<(() => ElurTemplate | ElurComponent) | undefined> = [],\n): FlatRoute[] {\n const result: FlatRoute[] = [];\n for (const route of routes) {\n const fullPath = joinPaths(parentPath, route.path);\n const chain = [...parentChain, route.component];\n const segments = parseSegments(fullPath);\n result.push({\n fullPath,\n segments,\n chain,\n name: route.name,\n meta: route.meta,\n beforeEnter: route.beforeEnter,\n record: route,\n });\n if (route.children?.length) {\n result.push(...flattenRoutes(route.children, fullPath, chain));\n }\n }\n return result;\n}\n\nfunction tryMatch(path: string, route: FlatRoute): Record<string, string> | null {\n const parts = path.split(\"/\").filter(Boolean);\n const segs = route.segments;\n if (segs.length === 1 && segs[0].kind === \"wildcard\") return {};\n const lastIsWild = segs.length > 0 && segs[segs.length - 1].kind === \"wildcard\";\n const fixedSegs = lastIsWild ? segs.slice(0, -1) : segs;\n if (lastIsWild) {\n if (parts.length < fixedSegs.length) return null;\n } else {\n if (parts.length !== fixedSegs.length) return null;\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < fixedSegs.length; i++) {\n const seg = fixedSegs[i];\n if (seg.kind === \"literal\") {\n if (parts[i] !== seg.value) return null;\n } else if (seg.kind === \"param\") {\n try {\n params[seg.name] = decodeURIComponent(parts[i] ?? \"\");\n } catch {\n params[seg.name] = parts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction specificity(route: FlatRoute): number {\n return route.segments.reduce((acc, seg) => {\n if (seg.kind === \"literal\") return acc + 2;\n if (seg.kind === \"param\") return acc + 1;\n return acc;\n }, 0);\n}\n\nfunction matchFlat(\n path: string,\n flat: FlatRoute[],\n): { route: FlatRoute; params: Record<string, string> } | undefined {\n let best: FlatRoute | undefined;\n let bestParams: Record<string, string> = {};\n let bestScore = -1;\n for (const route of flat) {\n const params = tryMatch(path, route);\n if (params === null) continue;\n const score = specificity(route);\n if (score > bestScore) {\n best = route;\n bestParams = params;\n bestScore = score;\n }\n }\n return best ? { route: best, params: bestParams } : undefined;\n}\n\n// =============================================================================\n// Base path helpers\n// =============================================================================\n\nfunction normalizeBase(raw: string): string {\n let b = raw.trim();\n if (!b || b === \"/\") return \"\";\n if (!b.startsWith(\"/\")) b = \"/\" + b;\n if (b.endsWith(\"/\")) b = b.slice(0, -1);\n return b;\n}\n\nfunction detectBase(): string {\n if (typeof document === \"undefined\") return \"\";\n const baseEl = document.querySelector(\"base\");\n if (!baseEl) return \"\";\n const href = baseEl.getAttribute(\"href\") || \"\";\n try {\n const url = new URL(href, window.location.origin);\n return normalizeBase(url.pathname);\n } catch {\n return normalizeBase(href);\n }\n}\n\n// =============================================================================\n// Guard result normalization\n// =============================================================================\n\n/**\n * Normalize any guard result into the internal flow's {allow, redirect} shape.\n * Accepts: `true`, `false`, `void`/`undefined`, `string`, `{ redirect: string }`.\n */\nfunction normalizeGuardResult(\n r: NavigationGuardResult,\n): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n // Unknown — be permissive rather than break navigation.\n return { allow: true };\n}\n\n// =============================================================================\n// createRouter\n// =============================================================================\n\nexport function createRouter(routes: RouteRecord[], options?: RouterOptions): Router {\n const _base = options?.base != null ? normalizeBase(options.base) : detectBase();\n const _mode: RouterMode = options?.mode ?? \"history\";\n const _isHashMode = _mode === \"hash\";\n const _scrollBehavior = options?.scrollBehavior;\n const _hashScrollPositions = new Map<string, ScrollPosition>();\n let _ignoreNextHashChange = false;\n\n function normalizeAppPath(raw: string): string {\n if (!raw) return \"/\";\n return raw.startsWith(\"/\") ? raw : \"/\" + raw;\n }\n\n function stripBase(rawPath: string): string {\n const path = normalizeAppPath(rawPath || \"/\");\n if (_base && path.startsWith(_base)) {\n const stripped = path.slice(_base.length);\n return stripped === \"\" ? \"/\" : normalizeAppPath(stripped);\n }\n return path;\n }\n\n function withBase(appPath: string): string {\n const p = normalizeAppPath(appPath);\n if (!_base) return p;\n return (_base + p).replace(/\\/+/g, \"/\") || \"/\";\n }\n\n function readHashLocation(): { pathname: string; search: string } {\n let raw = window.location.hash || \"\";\n if (raw.startsWith(\"#\")) raw = raw.slice(1);\n if (!raw) return { pathname: \"/\", search: \"\" };\n if (!raw.startsWith(\"/\")) raw = \"/\" + raw;\n const qIdx = raw.indexOf(\"?\");\n const pathname = qIdx === -1 ? raw : raw.slice(0, qIdx);\n const search = qIdx === -1 ? \"\" : raw.slice(qIdx);\n return { pathname: stripBase(pathname), search };\n }\n\n function readLocation(): { pathname: string; search: string } {\n if (_isHashMode) return readHashLocation();\n return {\n pathname: stripBase(window.location.pathname || \"/\"),\n search: window.location.search || \"\",\n };\n }\n\n function buildUrl(pathname: string, stringQuery: Record<string, string>): string {\n const fullPath = withBase(pathname) + buildQueryString(stringQuery);\n return _isHashMode ? \"#\" + fullPath : fullPath;\n }\n\n function routeKey(pathname: string, stringQuery: Record<string, string>): string {\n return normalizeAppPath(pathname) + buildQueryString(stringQuery);\n }\n\n // -------------------------------------------------------------------------\n // Initial state\n // -------------------------------------------------------------------------\n\n const initialLoc = readLocation();\n const initialPath = initialLoc.pathname;\n const initialQuery = parseQuery(initialLoc.search);\n const flat = flattenRoutes(routes);\n\n const _nameIndex = new Map<string, FlatRoute>();\n for (const route of flat) {\n if (!route.name) continue;\n if (_nameIndex.has(route.name)) {\n console.warn(`[Elur Router] Duplicate route name: \"${route.name}\"`);\n }\n _nameIndex.set(route.name, route);\n }\n const initialMatch = matchFlat(initialPath, flat);\n\n const current = signal(initialPath);\n const params = signal<Record<string, string>>(initialMatch?.params ?? {});\n const query = signal<Record<string, string>>(initialQuery);\n\n let _currentPosition = readPositionFromState(history.state) ?? 0;\n\n const intent = signal<NavigationIntent>({\n action: \"initial\",\n direction: \"none\",\n });\n\n const canGoBack = signal<boolean>(_currentPosition > 0);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(initialPath, initialQuery), getCurrentScrollPosition());\n } else {\n history.replaceState(\n buildHistoryState(history.state, getCurrentScrollPosition(), _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Scroll\n // -------------------------------------------------------------------------\n\n function _scrollTo(pos: ScrollPosition): void {\n window.scrollTo(pos.left, pos.top);\n }\n\n function _applyScroll(to: string, from: string, savedPosition: ScrollPosition | null): void {\n if (_scrollBehavior) {\n const result = _scrollBehavior(to, from, savedPosition);\n if (!result) return;\n _scrollTo(result);\n return;\n }\n _scrollTo(savedPosition ?? { left: 0, top: 0 });\n }\n\n function _saveCurrentEntryScroll(pathname: string, stringQuery: Record<string, string>): void {\n const pos = getCurrentScrollPosition();\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), pos);\n return;\n }\n history.replaceState(\n buildHistoryState(history.state, pos, _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Guards\n // -------------------------------------------------------------------------\n\n const _guards: NavigationGuard[] = [];\n const _afterHooks: AfterEachHook[] = [];\n let _navGeneration = 0;\n\n function _runGuards(\n to: string,\n from: string,\n routeGuard: NavigationGuard | undefined,\n onCommit: () => void,\n onCancel?: () => void,\n ): void {\n const guards: NavigationGuard[] = [..._guards];\n if (routeGuard) guards.push(routeGuard);\n\n const gen = ++_navGeneration;\n\n if (guards.length === 0) { onCommit(); return; }\n\n let idx = 0;\n function runNext(prev: NavigationGuardResult): void {\n if (gen !== _navGeneration) return;\n\n const norm = normalizeGuardResult(prev);\n if (!norm.allow) {\n if (norm.redirect && norm.redirect !== to) {\n navigate(norm.redirect);\n return;\n }\n if (norm.redirect === to) {\n // Guarding TO the same path — treat as allow to avoid loops\n onCommit();\n return;\n }\n onCancel?.();\n return;\n }\n if (idx >= guards.length) { onCommit(); return; }\n const result = guards[idx++](to, from);\n if (result instanceof Promise) { result.then(runNext); return; }\n runNext(result);\n }\n runNext(undefined);\n }\n\n // -------------------------------------------------------------------------\n // Path / location resolution\n // -------------------------------------------------------------------------\n\n let _hasNavigated = false;\n\n function _parsePath(\n path: string,\n queryObj?: Record<string, string | number | boolean | null | undefined>,\n ): { pathname: string; stringQuery: Record<string, string> } {\n const qIdx = path.indexOf(\"?\");\n const rawPath = qIdx === -1 ? path : path.slice(0, qIdx);\n const pathname = normalizeAppPath(rawPath || \"/\");\n const inlineQ = qIdx === -1 ? {} : parseQuery(path.slice(qIdx));\n const finalQuery = queryObj ? { ...inlineQ, ...queryObj } : inlineQ;\n const stringQuery: Record<string, string> = {};\n for (const [k, v] of Object.entries(finalQuery)) {\n if (v != null && v !== false) stringQuery[k] = String(v);\n }\n return { pathname, stringQuery };\n }\n\n function _resolveNamedPath(location: NamedRouteLocation): string {\n const found = _nameIndex.get(location.name);\n if (!found) {\n throw new Error(`[Elur Router] No route with name \"${location.name}\"`);\n }\n const parts = found.segments.map((seg) => {\n if (seg.kind === \"literal\") return seg.value;\n if (seg.kind === \"wildcard\") return \"\";\n const value = location.params?.[seg.name];\n if (value == null) {\n throw new Error(\n `[Elur Router] Missing param \"${seg.name}\" for route \"${location.name}\"`,\n );\n }\n return encodeURIComponent(String(value));\n });\n return \"/\" + parts.filter(Boolean).join(\"/\");\n }\n\n function _resolveLocation(\n location: RouteLocation,\n options?: NavigateOptions,\n ): { pathname: string; stringQuery: Record<string, string> } {\n if (typeof location === \"string\") {\n return _parsePath(location, options?.query);\n }\n const pathname = _resolveNamedPath(location);\n const mergedQuery = { ...(location.query ?? {}), ...(options?.query ?? {}) };\n return _parsePath(pathname, mergedQuery);\n }\n\n // -------------------------------------------------------------------------\n // Popstate / hashchange listener\n // -------------------------------------------------------------------------\n\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n\n const handleBrowserNav = (\n p: string,\n newQuery: Record<string, string>,\n savedPos: ScrollPosition | null,\n nextPosition: number | null,\n onCancelRestore: (from: string, fromQuery: Record<string, string>) => void,\n ) => {\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(p, flat);\n\n let direction: NavigationDirection = \"none\";\n if (nextPosition != null) {\n if (nextPosition < _currentPosition) direction = \"back\";\n else if (nextPosition > _currentPosition) direction = \"forward\";\n }\n\n _runGuards(\n p,\n from,\n m?.route.beforeEnter,\n () => {\n if (nextPosition != null) _currentPosition = nextPosition;\n const animation = _pendingPopAnimation;\n _pendingPopAnimation = undefined;\n intent.value = { action: \"pop\", direction, animation };\n params.value = m?.params ?? {};\n query.value = newQuery;\n current.value = p;\n canGoBack.value = _currentPosition > 0;\n _applyScroll(p, from, savedPos);\n for (const hook of _afterHooks) {\n try { hook(p, from); } catch { /* ignore */ }\n }\n },\n () => onCancelRestore(from, fromQuery),\n );\n };\n\n if (_isHashMode) {\n const onHashChange = () => {\n if (_ignoreNextHashChange) {\n _ignoreNextHashChange = false;\n return;\n }\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = _hashScrollPositions.get(routeKey(loc.pathname, nextQuery)) ?? null;\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n null,\n (from, fromQuery) => {\n _ignoreNextHashChange = true;\n window.location.hash = buildUrl(from, fromQuery).slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n },\n );\n };\n window.addEventListener(\"hashchange\", onHashChange);\n _currentPopstateCleanup = () => window.removeEventListener(\"hashchange\", onHashChange);\n } else {\n const onPopstate = (ev: PopStateEvent) => {\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = readScrollPositionFromState(ev.state ?? history.state);\n const nextPos = readPositionFromState(ev.state ?? history.state);\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n nextPos,\n (from, fromQuery) => {\n history.pushState(\n buildHistoryState({}, getCurrentScrollPosition(), _currentPosition),\n \"\",\n buildUrl(from, fromQuery),\n );\n },\n );\n };\n window.addEventListener(\"popstate\", onPopstate);\n _currentPopstateCleanup = () => window.removeEventListener(\"popstate\", onPopstate);\n }\n\n // -------------------------------------------------------------------------\n // Internal commit (programmatic navigation)\n // -------------------------------------------------------------------------\n\n function _commit(\n pathname: string,\n stringQuery: Record<string, string>,\n from: string,\n fromQuery: Record<string, string>,\n m: ReturnType<typeof matchFlat>,\n nextIntent: NavigationIntent,\n useReplace: boolean,\n ): void {\n if (!useReplace) {\n _saveCurrentEntryScroll(from, fromQuery);\n _currentPosition += 1;\n }\n\n intent.value = nextIntent;\n params.value = m?.params ?? {};\n query.value = stringQuery;\n current.value = pathname;\n canGoBack.value = _currentPosition > 0;\n\n const url = buildUrl(pathname, stringQuery);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), { left: 0, top: 0 });\n if (useReplace) {\n history.replaceState(history.state, \"\", url);\n } else {\n _ignoreNextHashChange = true;\n window.location.hash = url.slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n }\n } else {\n const nextState = buildHistoryState({}, { left: 0, top: 0 }, _currentPosition);\n if (useReplace) {\n history.replaceState(nextState, \"\", url);\n } else {\n history.pushState(nextState, \"\", url);\n }\n }\n\n _applyScroll(pathname, from, null);\n for (const hook of _afterHooks) {\n try { hook(pathname, from); } catch { /* ignore */ }\n }\n }\n\n // -------------------------------------------------------------------------\n // Public navigation API\n // -------------------------------------------------------------------------\n\n let _pendingPopAnimation: unknown = undefined;\n\n function navigate(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"push\",\n direction: options?.direction ?? \"forward\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, false),\n );\n }\n\n function replace(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"replace\",\n direction: options?.direction ?? \"root\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, true),\n );\n }\n\n function back(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.back();\n }\n\n function forward(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.forward();\n }\n\n function go(delta: number): void { history.go(delta); }\n\n function isActive(path: string, exact = true): boolean {\n const cur = current.value;\n if (exact) return cur === path;\n return cur === path || cur.startsWith(path.endsWith(\"/\") ? path : path + \"/\");\n }\n\n function resolve(path: string): ResolvedRoute {\n const m = matchFlat(path, flat);\n if (!m) return { matched: false, params: {}, route: undefined };\n return { matched: true, params: m.params, route: m.route.record };\n }\n\n function beforeEach(guard: NavigationGuard): () => void {\n _guards.push(guard);\n return () => {\n const idx = _guards.indexOf(guard);\n if (idx !== -1) _guards.splice(idx, 1);\n };\n }\n\n function afterEach(hook: AfterEachHook): () => void {\n _afterHooks.push(hook);\n return () => {\n const idx = _afterHooks.indexOf(hook);\n if (idx !== -1) _afterHooks.splice(idx, 1);\n };\n }\n\n const router: RouterInternal = {\n current, params, query, intent, canGoBack,\n base: _base || \"/\",\n navigate, replace, back, forward, go,\n isActive, resolve,\n beforeEach, afterEach, routes,\n _flat: flat, _guards, _base, _mode,\n };\n\n if (_currentRouter) {\n console.warn(\n \"[elur] A router already exists. The previous router is being replaced. \" +\n \"Only one router instance should be active at a time.\",\n );\n }\n _currentRouter = router;\n\n queueMicrotask(() => {\n if (_hasNavigated) return;\n\n const m = matchFlat(initialPath, flat);\n _runGuards(\n initialPath,\n \"\",\n m?.route.beforeEnter,\n () => { /* allowed */ },\n () => {\n const fallback = \"/\";\n const url = buildUrl(fallback, {});\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(fallback, {}), { left: 0, top: 0 });\n history.replaceState(history.state, \"\", url);\n } else {\n history.replaceState(\n buildHistoryState({}, { left: 0, top: 0 }, _currentPosition),\n \"\",\n url,\n );\n }\n const fm = matchFlat(fallback, flat);\n intent.value = { action: \"replace\", direction: \"root\" };\n current.value = fallback;\n params.value = fm?.params ?? {};\n query.value = {};\n canGoBack.value = _currentPosition > 0;\n _applyScroll(fallback, initialPath, null);\n },\n );\n });\n\n return router;\n}\n\nexport function elurRouter(): Router {\n const injected = inject(RouterKey);\n if (injected) return injected;\n return getRouter();\n}\n\n/** @internal */\nexport function _resetRouter(): void {\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n _currentRouter = null;\n _mountedRouters.length = 0;\n}\n\nexport class RouterView extends ElurComponent {\n private _depth: number;\n private _router?: RouterInternal;\n\n constructor(depth = 0, router?: Router) {\n super();\n this._depth = depth;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const depth = this._depth;\n const explicitRouter = this._router;\n return html`<div class=\"router-view\">${() => {\n const router = explicitRouter ?? elurRouter() as RouterInternal;\n const matched = matchFlat(router.current.value, router._flat);\n if (!matched) {\n return html`\n <div style=\"color:#f87171;padding:16px 0\">\n 404 — Route not found: <strong>${router.current.value}</strong>\n </div>\n `;\n }\n if (depth >= matched.route.chain.length) {\n return html`\n <span></span>\n `;\n }\n const factory = matched.route.chain[depth];\n // chain entries can be undefined when the route was registered without\n // a `component` (typical when an outlet auto-bootstraps the router).\n // In that case there's nothing to render at this depth.\n if (!factory) return html`\n <span></span>\n `;\n return factory();\n }}</div>`;\n }\n}\n\nexport class Link extends ElurComponent {\n private _to: string;\n private _label: string;\n private _router?: RouterInternal;\n\n constructor(to: string, label: string, router?: Router) {\n super();\n this._to = to;\n this._label = label;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const to = this._to;\n const label = this._label;\n const router = this._router ?? elurRouter() as RouterInternal;\n const appPath = to.startsWith(\"/\") ? to : \"/\" + to;\n const fullPath = (router._base ? (router._base + appPath) : appPath).replace(/\\/+/g, \"/\");\n const href = router._mode === \"hash\" ? \"#\" + fullPath : fullPath;\n return html`\n <a href=${href} style=${() => router.current.value === to\n ? \"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a\"\n : \"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px\"} @click=${(e: Event) => { e.preventDefault(); router.navigate(to); }}>${label}</a>\n `;\n }\n}\n\nexport interface _RouterDebugInternal {\n mode: RouterMode;\n base: string;\n currentPath: string;\n params: Record<string, string>;\n query: Record<string, string>;\n matchedPath: string | null;\n activeGuards: { globalCount: number; hasRouteGuard: boolean; names: string[] };\n}\n\nexport function _debugGetRouterInternal(): _RouterDebugInternal | null {\n // Prefer the most recently mounted injected router over the global singleton\n // so devtools inspect the router actually active in the current UI.\n const router = _mountedRouters.length\n ? _mountedRouters[_mountedRouters.length - 1]\n : _currentRouter;\n if (!router) return null;\n const internal = router as RouterInternal;\n const currentPath = internal.current.value;\n const matched = matchFlat(currentPath, internal._flat);\n const routeGuard = matched?.route.beforeEnter;\n const names = internal._guards.map((g, idx) => g.name || `beforeEach#${idx + 1}`);\n if (routeGuard) names.push(routeGuard.name || \"beforeEnter\");\n return {\n mode: internal._mode,\n base: internal._base || \"/\",\n currentPath,\n params: { ...internal.params.value },\n query: { ...internal.query.value },\n matchedPath: matched?.route.fullPath ?? null,\n activeGuards: {\n globalCount: internal._guards.length,\n hasRouteGuard: Boolean(routeGuard),\n names,\n },\n };\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"router.cjs","names":[],"sources":["../../src/elur/router.ts"],"sourcesContent":["import { signal } from \"./reactivity.js\";\nimport type { Signal } from \"./reactivity.js\";\nimport { ElurComponent } from \"./lifecycle.js\";\nimport type { ElurTemplate } from \"./template/index.js\";\nimport { html } from \"./template/index.js\";\nimport { inject } from \"./context.js\";\nimport { RouterKey, _mountedRouters, _debugRegisterRouter, _debugUnregisterRouter } from \"./router-registry.js\";\n\n// =============================================================================\n// Public types\n// =============================================================================\n\n/**\n * Value returned (or resolved) by a navigation guard.\n *\n * - `true` / `void` / `undefined` — allow.\n * - `false` — cancel (no redirect).\n * - `string` — redirect to that path.\n * - `{ redirect: string }` — redirect, object form.\n *\n * The object form exists so guards written for the outlet API can be reused\n * verbatim by the core. See `elur-ionic`'s `GuardResult`.\n */\nexport type NavigationGuardResult =\n | void\n | undefined\n | boolean\n | string\n | { redirect: string };\n\n/** Guard function invoked before navigation commits. */\nexport type NavigationGuard = (\n to: string,\n from: string,\n) => NavigationGuardResult | Promise<NavigationGuardResult>;\n\nexport interface RouteRecord {\n /** Optional unique name to enable named navigation. */\n name?: string;\n /** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */\n path: string;\n /**\n * Factory returning the view for this route level.\n *\n * OPTIONAL — when the core router is auto-bootstrapped by an outlet\n * (Ionic's IonRouterOutlet, future others), the outlet owns component\n * mounting and the core never invokes this. In that case, omit it.\n */\n component?: () => ElurTemplate | ElurComponent;\n /** Optional arbitrary metadata for guards, layouts, and auth checks. */\n meta?: Record<string, unknown>;\n /** Child routes. Paths are joined with the parent. */\n children?: RouteRecord[];\n /** Route-level guard. Runs only when entering this specific route. */\n beforeEnter?: NavigationGuard;\n}\n\n/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */\nexport type AfterEachHook = (to: string, from: string) => void;\n\n/** Named route target for programmatic navigation. */\nexport interface NamedRouteLocation {\n name: string;\n params?: Record<string, string | number>;\n query?: Record<string, string | number | boolean | null | undefined>;\n}\n\n/** Navigation input accepted by `navigate` / `replace`. */\nexport type RouteLocation = string | NamedRouteLocation;\n\n/** Serializable scroll position used by the router for history restoration. */\nexport interface ScrollPosition {\n left: number;\n top: number;\n}\n\nexport type ScrollBehavior = (\n to: string,\n from: string,\n savedPosition: ScrollPosition | null,\n) => ScrollPosition | false | void;\n\nexport type RouterMode = \"history\" | \"hash\";\n\nexport interface ResolvedRoute {\n matched: boolean;\n params: Record<string, string>;\n route: RouteRecord | undefined;\n}\n\n// -----------------------------------------------------------------------------\n// Navigation intent\n// -----------------------------------------------------------------------------\n\nexport type NavigationAction = \"push\" | \"replace\" | \"pop\" | \"initial\";\nexport type NavigationDirection = \"forward\" | \"back\" | \"root\" | \"none\";\n\nexport interface NavigationIntent {\n action: NavigationAction;\n direction: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface NavigateOptions {\n query?: Record<string, string | number | boolean | null | undefined>;\n direction?: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface RouterOptions {\n base?: string;\n mode?: RouterMode;\n scrollBehavior?: ScrollBehavior;\n}\n\nexport interface Router {\n readonly current: Signal<string>;\n readonly params: Signal<Record<string, string>>;\n readonly query: Signal<Record<string, string>>;\n readonly base: string;\n readonly intent: Signal<NavigationIntent>;\n readonly canGoBack: Signal<boolean>;\n navigate(location: RouteLocation, options?: NavigateOptions): void;\n replace(location: RouteLocation, options?: NavigateOptions): void;\n back(animation?: unknown): void;\n forward(animation?: unknown): void;\n go(delta: number): void;\n isActive(path: string, exact?: boolean): boolean;\n resolve(path: string): ResolvedRoute;\n readonly routes: RouteRecord[];\n beforeEach(guard: NavigationGuard): () => void;\n afterEach(hook: AfterEachHook): () => void;\n}\n\n// RouterKey and the mounted-router debug registry live in router-registry.ts\n// so that component.ts (mount) can use them without importing this module —\n// keeping the router out of bundles that never call createRouter().\nexport { RouterKey, _debugRegisterRouter, _debugUnregisterRouter };\n\n// =============================================================================\n// Internals\n// =============================================================================\n\ntype Segment =\n | { kind: \"literal\"; value: string }\n | { kind: \"param\"; name: string }\n | { kind: \"wildcard\" };\n\ninterface FlatRoute {\n fullPath: string;\n segments: Segment[];\n chain: Array<(() => ElurTemplate | ElurComponent) | undefined>;\n name?: string;\n meta?: Record<string, unknown>;\n beforeEnter?: NavigationGuard;\n record: RouteRecord;\n}\n\ninterface RouterInternal extends Router {\n _flat: FlatRoute[];\n _guards: NavigationGuard[];\n _base: string;\n _mode: RouterMode;\n}\n\nlet _currentRouter: RouterInternal | null = null;\nlet _currentPopstateCleanup: (() => void) | null = null;\n\nconst SCROLL_STATE_KEY = \"__elur_scroll\";\nconst POSITION_STATE_KEY = \"__elur_pos\";\n\nfunction getRouter(): RouterInternal {\n if (!_currentRouter) {\n throw new Error(\n \"[elur] No active router. Call createRouter() first, \" +\n \"or instantiate an outlet that auto-bootstraps one (e.g. IonRouterOutlet).\"\n );\n }\n return _currentRouter;\n}\n\n/**\n * @internal Whether a router is currently active. Used by outlets that\n * want to auto-bootstrap if the user didn't call `createRouter()` themselves.\n */\nexport function _hasActiveRouter(): boolean {\n return _currentRouter !== null;\n}\n\n// =============================================================================\n// History state helpers\n// =============================================================================\n\nfunction getCurrentScrollPosition(): ScrollPosition {\n return {\n left: window.scrollX ?? window.pageXOffset ?? 0,\n top: window.scrollY ?? window.pageYOffset ?? 0,\n };\n}\n\nfunction readScrollPositionFromState(state: unknown): ScrollPosition | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[SCROLL_STATE_KEY];\n if (!raw || typeof raw !== \"object\") return null;\n const left = (raw as Record<string, unknown>).left;\n const top = (raw as Record<string, unknown>).top;\n if (typeof left !== \"number\" || typeof top !== \"number\") return null;\n return { left, top };\n}\n\nfunction readPositionFromState(state: unknown): number | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[POSITION_STATE_KEY];\n return typeof raw === \"number\" ? raw : null;\n}\n\nfunction buildHistoryState(\n prev: unknown,\n scroll: ScrollPosition,\n position: number,\n): Record<string, unknown> {\n const base = prev && typeof prev === \"object\"\n ? { ...(prev as Record<string, unknown>) }\n : {};\n base[SCROLL_STATE_KEY] = { left: scroll.left, top: scroll.top };\n base[POSITION_STATE_KEY] = position;\n return base;\n}\n\n// =============================================================================\n// Query / path helpers\n// =============================================================================\n\nfunction parseQuery(search: string): Record<string, string> {\n const result: Record<string, string> = {};\n new URLSearchParams(search).forEach((v, k) => { result[k] = v; });\n return result;\n}\n\nfunction buildQueryString(\n q: Record<string, string | number | boolean | null | undefined>,\n): string {\n const p = new URLSearchParams();\n for (const [k, v] of Object.entries(q)) {\n if (v != null && v !== false) p.set(k, String(v));\n }\n const s = p.toString();\n return s ? \"?\" + s : \"\";\n}\n\nfunction parseSegments(fullPath: string): Segment[] {\n if (fullPath === \"*\") return [{ kind: \"wildcard\" }];\n return fullPath\n .split(\"/\")\n .filter(Boolean)\n .map((part): Segment => {\n if (part === \"*\") return { kind: \"wildcard\" };\n if (part.startsWith(\":\")) return { kind: \"param\", name: part.slice(1) };\n return { kind: \"literal\", value: part };\n });\n}\n\nfunction joinPaths(parent: string, child: string): string {\n if (child === \"*\") return parent === \"\" ? \"*\" : parent + \"/*\";\n const segment = child.startsWith(\"/\") ? child : \"/\" + child;\n return (parent + segment).replace(/\\/+/g, \"/\") || \"/\";\n}\n\nfunction flattenRoutes(\n routes: RouteRecord[],\n parentPath = \"\",\n parentChain: Array<(() => ElurTemplate | ElurComponent) | undefined> = [],\n): FlatRoute[] {\n const result: FlatRoute[] = [];\n for (const route of routes) {\n const fullPath = joinPaths(parentPath, route.path);\n const chain = [...parentChain, route.component];\n const segments = parseSegments(fullPath);\n result.push({\n fullPath,\n segments,\n chain,\n name: route.name,\n meta: route.meta,\n beforeEnter: route.beforeEnter,\n record: route,\n });\n if (route.children?.length) {\n result.push(...flattenRoutes(route.children, fullPath, chain));\n }\n }\n return result;\n}\n\nfunction tryMatch(path: string, route: FlatRoute): Record<string, string> | null {\n const parts = path.split(\"/\").filter(Boolean);\n const segs = route.segments;\n if (segs.length === 1 && segs[0].kind === \"wildcard\") return {};\n const lastIsWild = segs.length > 0 && segs[segs.length - 1].kind === \"wildcard\";\n const fixedSegs = lastIsWild ? segs.slice(0, -1) : segs;\n if (lastIsWild) {\n if (parts.length < fixedSegs.length) return null;\n } else {\n if (parts.length !== fixedSegs.length) return null;\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < fixedSegs.length; i++) {\n const seg = fixedSegs[i];\n if (seg.kind === \"literal\") {\n if (parts[i] !== seg.value) return null;\n } else if (seg.kind === \"param\") {\n try {\n params[seg.name] = decodeURIComponent(parts[i] ?? \"\");\n } catch {\n params[seg.name] = parts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction specificity(route: FlatRoute): number {\n return route.segments.reduce((acc, seg) => {\n if (seg.kind === \"literal\") return acc + 2;\n if (seg.kind === \"param\") return acc + 1;\n return acc;\n }, 0);\n}\n\nfunction matchFlat(\n path: string,\n flat: FlatRoute[],\n): { route: FlatRoute; params: Record<string, string> } | undefined {\n let best: FlatRoute | undefined;\n let bestParams: Record<string, string> = {};\n let bestScore = -1;\n for (const route of flat) {\n const params = tryMatch(path, route);\n if (params === null) continue;\n const score = specificity(route);\n if (score > bestScore) {\n best = route;\n bestParams = params;\n bestScore = score;\n }\n }\n return best ? { route: best, params: bestParams } : undefined;\n}\n\n// =============================================================================\n// Base path helpers\n// =============================================================================\n\nfunction normalizeBase(raw: string): string {\n let b = raw.trim();\n if (!b || b === \"/\") return \"\";\n if (!b.startsWith(\"/\")) b = \"/\" + b;\n if (b.endsWith(\"/\")) b = b.slice(0, -1);\n return b;\n}\n\nfunction detectBase(): string {\n if (typeof document === \"undefined\") return \"\";\n const baseEl = document.querySelector(\"base\");\n if (!baseEl) return \"\";\n const href = baseEl.getAttribute(\"href\") || \"\";\n try {\n const url = new URL(href, window.location.origin);\n return normalizeBase(url.pathname);\n } catch {\n return normalizeBase(href);\n }\n}\n\n// =============================================================================\n// Guard result normalization\n// =============================================================================\n\n/**\n * Normalize any guard result into the internal flow's {allow, redirect} shape.\n * Accepts: `true`, `false`, `void`/`undefined`, `string`, `{ redirect: string }`.\n */\nfunction normalizeGuardResult(\n r: NavigationGuardResult,\n): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n // Unknown — be permissive rather than break navigation.\n return { allow: true };\n}\n\n// =============================================================================\n// createRouter\n// =============================================================================\n\nexport function createRouter(routes: RouteRecord[], options?: RouterOptions): Router {\n const _base = options?.base != null ? normalizeBase(options.base) : detectBase();\n const _mode: RouterMode = options?.mode ?? \"history\";\n const _isHashMode = _mode === \"hash\";\n const _scrollBehavior = options?.scrollBehavior;\n const _hashScrollPositions = new Map<string, ScrollPosition>();\n let _ignoreNextHashChange = false;\n\n function normalizeAppPath(raw: string): string {\n if (!raw) return \"/\";\n return raw.startsWith(\"/\") ? raw : \"/\" + raw;\n }\n\n function stripBase(rawPath: string): string {\n const path = normalizeAppPath(rawPath || \"/\");\n if (_base && path.startsWith(_base)) {\n const stripped = path.slice(_base.length);\n return stripped === \"\" ? \"/\" : normalizeAppPath(stripped);\n }\n return path;\n }\n\n function withBase(appPath: string): string {\n const p = normalizeAppPath(appPath);\n if (!_base) return p;\n return (_base + p).replace(/\\/+/g, \"/\") || \"/\";\n }\n\n function readHashLocation(): { pathname: string; search: string } {\n let raw = window.location.hash || \"\";\n if (raw.startsWith(\"#\")) raw = raw.slice(1);\n if (!raw) return { pathname: \"/\", search: \"\" };\n if (!raw.startsWith(\"/\")) raw = \"/\" + raw;\n const qIdx = raw.indexOf(\"?\");\n const pathname = qIdx === -1 ? raw : raw.slice(0, qIdx);\n const search = qIdx === -1 ? \"\" : raw.slice(qIdx);\n return { pathname: stripBase(pathname), search };\n }\n\n function readLocation(): { pathname: string; search: string } {\n if (_isHashMode) return readHashLocation();\n return {\n pathname: stripBase(window.location.pathname || \"/\"),\n search: window.location.search || \"\",\n };\n }\n\n function buildUrl(pathname: string, stringQuery: Record<string, string>): string {\n const fullPath = withBase(pathname) + buildQueryString(stringQuery);\n return _isHashMode ? \"#\" + fullPath : fullPath;\n }\n\n function routeKey(pathname: string, stringQuery: Record<string, string>): string {\n return normalizeAppPath(pathname) + buildQueryString(stringQuery);\n }\n\n // -------------------------------------------------------------------------\n // Initial state\n // -------------------------------------------------------------------------\n\n const initialLoc = readLocation();\n const initialPath = initialLoc.pathname;\n const initialQuery = parseQuery(initialLoc.search);\n const flat = flattenRoutes(routes);\n\n const _nameIndex = new Map<string, FlatRoute>();\n for (const route of flat) {\n if (!route.name) continue;\n if (_nameIndex.has(route.name)) {\n console.warn(`[Elur Router] Duplicate route name: \"${route.name}\"`);\n }\n _nameIndex.set(route.name, route);\n }\n const initialMatch = matchFlat(initialPath, flat);\n\n const current = signal(initialPath);\n const params = signal<Record<string, string>>(initialMatch?.params ?? {});\n const query = signal<Record<string, string>>(initialQuery);\n\n let _currentPosition = readPositionFromState(history.state) ?? 0;\n\n const intent = signal<NavigationIntent>({\n action: \"initial\",\n direction: \"none\",\n });\n\n const canGoBack = signal<boolean>(_currentPosition > 0);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(initialPath, initialQuery), getCurrentScrollPosition());\n } else {\n history.replaceState(\n buildHistoryState(history.state, getCurrentScrollPosition(), _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Scroll\n // -------------------------------------------------------------------------\n\n function _scrollTo(pos: ScrollPosition): void {\n window.scrollTo(pos.left, pos.top);\n }\n\n function _applyScroll(to: string, from: string, savedPosition: ScrollPosition | null): void {\n if (_scrollBehavior) {\n const result = _scrollBehavior(to, from, savedPosition);\n if (!result) return;\n _scrollTo(result);\n return;\n }\n _scrollTo(savedPosition ?? { left: 0, top: 0 });\n }\n\n function _saveCurrentEntryScroll(pathname: string, stringQuery: Record<string, string>): void {\n const pos = getCurrentScrollPosition();\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), pos);\n return;\n }\n history.replaceState(\n buildHistoryState(history.state, pos, _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Guards\n // -------------------------------------------------------------------------\n\n const _guards: NavigationGuard[] = [];\n const _afterHooks: AfterEachHook[] = [];\n let _navGeneration = 0;\n\n function _runGuards(\n to: string,\n from: string,\n routeGuard: NavigationGuard | undefined,\n onCommit: () => void,\n onCancel?: () => void,\n ): void {\n const guards: NavigationGuard[] = [..._guards];\n if (routeGuard) guards.push(routeGuard);\n\n const gen = ++_navGeneration;\n\n if (guards.length === 0) { onCommit(); return; }\n\n let idx = 0;\n function runNext(prev: NavigationGuardResult): void {\n if (gen !== _navGeneration) return;\n\n const norm = normalizeGuardResult(prev);\n if (!norm.allow) {\n if (norm.redirect && norm.redirect !== to) {\n navigate(norm.redirect);\n return;\n }\n if (norm.redirect === to) {\n // Guarding TO the same path — treat as allow to avoid loops\n onCommit();\n return;\n }\n onCancel?.();\n return;\n }\n if (idx >= guards.length) { onCommit(); return; }\n const result = guards[idx++](to, from);\n if (result instanceof Promise) { result.then(runNext); return; }\n runNext(result);\n }\n runNext(undefined);\n }\n\n // -------------------------------------------------------------------------\n // Path / location resolution\n // -------------------------------------------------------------------------\n\n let _hasNavigated = false;\n\n function _parsePath(\n path: string,\n queryObj?: Record<string, string | number | boolean | null | undefined>,\n ): { pathname: string; stringQuery: Record<string, string> } {\n const qIdx = path.indexOf(\"?\");\n const rawPath = qIdx === -1 ? path : path.slice(0, qIdx);\n const pathname = normalizeAppPath(rawPath || \"/\");\n const inlineQ = qIdx === -1 ? {} : parseQuery(path.slice(qIdx));\n const finalQuery = queryObj ? { ...inlineQ, ...queryObj } : inlineQ;\n const stringQuery: Record<string, string> = {};\n for (const [k, v] of Object.entries(finalQuery)) {\n if (v != null && v !== false) stringQuery[k] = String(v);\n }\n return { pathname, stringQuery };\n }\n\n function _resolveNamedPath(location: NamedRouteLocation): string {\n const found = _nameIndex.get(location.name);\n if (!found) {\n throw new Error(`[Elur Router] No route with name \"${location.name}\"`);\n }\n const parts = found.segments.map((seg) => {\n if (seg.kind === \"literal\") return seg.value;\n if (seg.kind === \"wildcard\") return \"\";\n const value = location.params?.[seg.name];\n if (value == null) {\n throw new Error(\n `[Elur Router] Missing param \"${seg.name}\" for route \"${location.name}\"`,\n );\n }\n return encodeURIComponent(String(value));\n });\n return \"/\" + parts.filter(Boolean).join(\"/\");\n }\n\n function _resolveLocation(\n location: RouteLocation,\n options?: NavigateOptions,\n ): { pathname: string; stringQuery: Record<string, string> } {\n if (typeof location === \"string\") {\n return _parsePath(location, options?.query);\n }\n const pathname = _resolveNamedPath(location);\n const mergedQuery = { ...(location.query ?? {}), ...(options?.query ?? {}) };\n return _parsePath(pathname, mergedQuery);\n }\n\n // -------------------------------------------------------------------------\n // Popstate / hashchange listener\n // -------------------------------------------------------------------------\n\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n\n const handleBrowserNav = (\n p: string,\n newQuery: Record<string, string>,\n savedPos: ScrollPosition | null,\n nextPosition: number | null,\n onCancelRestore: (from: string, fromQuery: Record<string, string>) => void,\n ) => {\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(p, flat);\n\n let direction: NavigationDirection = \"none\";\n if (nextPosition != null) {\n if (nextPosition < _currentPosition) direction = \"back\";\n else if (nextPosition > _currentPosition) direction = \"forward\";\n }\n\n _runGuards(\n p,\n from,\n m?.route.beforeEnter,\n () => {\n if (nextPosition != null) _currentPosition = nextPosition;\n const animation = _pendingPopAnimation;\n _pendingPopAnimation = undefined;\n intent.value = { action: \"pop\", direction, animation };\n params.value = m?.params ?? {};\n query.value = newQuery;\n current.value = p;\n canGoBack.value = _currentPosition > 0;\n _applyScroll(p, from, savedPos);\n for (const hook of _afterHooks) {\n try { hook(p, from); } catch { /* ignore */ }\n }\n },\n () => onCancelRestore(from, fromQuery),\n );\n };\n\n if (_isHashMode) {\n const onHashChange = () => {\n if (_ignoreNextHashChange) {\n _ignoreNextHashChange = false;\n return;\n }\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = _hashScrollPositions.get(routeKey(loc.pathname, nextQuery)) ?? null;\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n null,\n (from, fromQuery) => {\n _ignoreNextHashChange = true;\n window.location.hash = buildUrl(from, fromQuery).slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n },\n );\n };\n window.addEventListener(\"hashchange\", onHashChange);\n _currentPopstateCleanup = () => window.removeEventListener(\"hashchange\", onHashChange);\n } else {\n const onPopstate = (ev: PopStateEvent) => {\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = readScrollPositionFromState(ev.state ?? history.state);\n const nextPos = readPositionFromState(ev.state ?? history.state);\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n nextPos,\n (from, fromQuery) => {\n history.pushState(\n buildHistoryState({}, getCurrentScrollPosition(), _currentPosition),\n \"\",\n buildUrl(from, fromQuery),\n );\n },\n );\n };\n window.addEventListener(\"popstate\", onPopstate);\n _currentPopstateCleanup = () => window.removeEventListener(\"popstate\", onPopstate);\n }\n\n // -------------------------------------------------------------------------\n // Internal commit (programmatic navigation)\n // -------------------------------------------------------------------------\n\n function _commit(\n pathname: string,\n stringQuery: Record<string, string>,\n from: string,\n fromQuery: Record<string, string>,\n m: ReturnType<typeof matchFlat>,\n nextIntent: NavigationIntent,\n useReplace: boolean,\n ): void {\n if (!useReplace) {\n _saveCurrentEntryScroll(from, fromQuery);\n _currentPosition += 1;\n }\n\n intent.value = nextIntent;\n params.value = m?.params ?? {};\n query.value = stringQuery;\n current.value = pathname;\n canGoBack.value = _currentPosition > 0;\n\n const url = buildUrl(pathname, stringQuery);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), { left: 0, top: 0 });\n if (useReplace) {\n history.replaceState(history.state, \"\", url);\n } else {\n _ignoreNextHashChange = true;\n window.location.hash = url.slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n }\n } else {\n const nextState = buildHistoryState({}, { left: 0, top: 0 }, _currentPosition);\n if (useReplace) {\n history.replaceState(nextState, \"\", url);\n } else {\n history.pushState(nextState, \"\", url);\n }\n }\n\n _applyScroll(pathname, from, null);\n for (const hook of _afterHooks) {\n try { hook(pathname, from); } catch { /* ignore */ }\n }\n }\n\n // -------------------------------------------------------------------------\n // Public navigation API\n // -------------------------------------------------------------------------\n\n let _pendingPopAnimation: unknown = undefined;\n\n function navigate(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"push\",\n direction: options?.direction ?? \"forward\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, false),\n );\n }\n\n function replace(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"replace\",\n direction: options?.direction ?? \"root\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, true),\n );\n }\n\n function back(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.back();\n }\n\n function forward(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.forward();\n }\n\n function go(delta: number): void { history.go(delta); }\n\n function isActive(path: string, exact = true): boolean {\n const cur = current.value;\n if (exact) return cur === path;\n return cur === path || cur.startsWith(path.endsWith(\"/\") ? path : path + \"/\");\n }\n\n function resolve(path: string): ResolvedRoute {\n const m = matchFlat(path, flat);\n if (!m) return { matched: false, params: {}, route: undefined };\n return { matched: true, params: m.params, route: m.route.record };\n }\n\n function beforeEach(guard: NavigationGuard): () => void {\n _guards.push(guard);\n return () => {\n const idx = _guards.indexOf(guard);\n if (idx !== -1) _guards.splice(idx, 1);\n };\n }\n\n function afterEach(hook: AfterEachHook): () => void {\n _afterHooks.push(hook);\n return () => {\n const idx = _afterHooks.indexOf(hook);\n if (idx !== -1) _afterHooks.splice(idx, 1);\n };\n }\n\n const router: RouterInternal = {\n current, params, query, intent, canGoBack,\n base: _base || \"/\",\n navigate, replace, back, forward, go,\n isActive, resolve,\n beforeEach, afterEach, routes,\n _flat: flat, _guards, _base, _mode,\n };\n\n if (_currentRouter) {\n console.warn(\n \"[elur] A router already exists. The previous router is being replaced. \" +\n \"Only one router instance should be active at a time.\",\n );\n }\n _currentRouter = router;\n\n queueMicrotask(() => {\n if (_hasNavigated) return;\n\n const m = matchFlat(initialPath, flat);\n _runGuards(\n initialPath,\n \"\",\n m?.route.beforeEnter,\n () => { /* allowed */ },\n () => {\n const fallback = \"/\";\n const url = buildUrl(fallback, {});\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(fallback, {}), { left: 0, top: 0 });\n history.replaceState(history.state, \"\", url);\n } else {\n history.replaceState(\n buildHistoryState({}, { left: 0, top: 0 }, _currentPosition),\n \"\",\n url,\n );\n }\n const fm = matchFlat(fallback, flat);\n intent.value = { action: \"replace\", direction: \"root\" };\n current.value = fallback;\n params.value = fm?.params ?? {};\n query.value = {};\n canGoBack.value = _currentPosition > 0;\n _applyScroll(fallback, initialPath, null);\n },\n );\n });\n\n return router;\n}\n\nexport function elurRouter(): Router {\n const injected = inject(RouterKey);\n if (injected) return injected;\n return getRouter();\n}\n\n/** @internal */\nexport function _resetRouter(): void {\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n _currentRouter = null;\n _mountedRouters.length = 0;\n}\n\nexport class RouterView extends ElurComponent {\n private _depth: number;\n private _router?: RouterInternal;\n\n constructor(depth = 0, router?: Router) {\n super();\n this._depth = depth;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const depth = this._depth;\n const explicitRouter = this._router;\n return html`<div class=\"router-view\">${() => {\n const router = explicitRouter ?? elurRouter() as RouterInternal;\n const matched = matchFlat(router.current.value, router._flat);\n if (!matched) {\n return html`\n <div style=\"color:#f87171;padding:16px 0\">\n 404 — Route not found: <strong>${router.current.value}</strong>\n </div>\n `;\n }\n if (depth >= matched.route.chain.length) {\n return html`\n <span></span>\n `;\n }\n const factory = matched.route.chain[depth];\n // chain entries can be undefined when the route was registered without\n // a `component` (typical when an outlet auto-bootstraps the router).\n // In that case there's nothing to render at this depth.\n if (!factory) return html`\n <span></span>\n `;\n return factory();\n }}</div>`;\n }\n}\n\nexport class Link extends ElurComponent {\n private _to: string;\n private _label: string;\n private _router?: RouterInternal;\n\n constructor(to: string, label: string, router?: Router) {\n super();\n this._to = to;\n this._label = label;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const to = this._to;\n const label = this._label;\n const router = this._router ?? elurRouter() as RouterInternal;\n const appPath = to.startsWith(\"/\") ? to : \"/\" + to;\n const fullPath = (router._base ? (router._base + appPath) : appPath).replace(/\\/+/g, \"/\");\n const href = router._mode === \"hash\" ? \"#\" + fullPath : fullPath;\n return html`\n <a href=${href} style=${() => router.current.value === appPath\n ? \"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a\"\n : \"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px\"} @click=${(e: Event) => { e.preventDefault(); router.navigate(to); }}>${label}</a>\n `;\n }\n}\n\nexport interface _RouterDebugInternal {\n mode: RouterMode;\n base: string;\n currentPath: string;\n params: Record<string, string>;\n query: Record<string, string>;\n matchedPath: string | null;\n activeGuards: { globalCount: number; hasRouteGuard: boolean; names: string[] };\n}\n\nexport function _debugGetRouterInternal(): _RouterDebugInternal | null {\n // Prefer the most recently mounted injected router over the global singleton\n // so devtools inspect the router actually active in the current UI.\n const router = _mountedRouters.length\n ? _mountedRouters[_mountedRouters.length - 1]\n : _currentRouter;\n if (!router) return null;\n const internal = router as RouterInternal;\n const currentPath = internal.current.value;\n const matched = matchFlat(currentPath, internal._flat);\n const routeGuard = matched?.route.beforeEnter;\n const names = internal._guards.map((g, idx) => g.name || `beforeEach#${idx + 1}`);\n if (routeGuard) names.push(routeGuard.name || \"beforeEnter\");\n return {\n mode: internal._mode,\n base: internal._base || \"/\",\n currentPath,\n params: { ...internal.params.value },\n query: { ...internal.query.value },\n matchedPath: matched?.route.fullPath ?? null,\n activeGuards: {\n globalCount: internal._guards.length,\n hasRouteGuard: Boolean(routeGuard),\n names,\n },\n };\n}"],"mappings":"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"}
|
package/dist/lib/elur/router.js
CHANGED
|
@@ -476,7 +476,7 @@ var j = class extends t {
|
|
|
476
476
|
render() {
|
|
477
477
|
let e = this._to, t = this._label, n = this._router ?? k(), i = e.startsWith("/") ? e : "/" + e, a = (n._base ? n._base + i : i).replace(/\/+/g, "/");
|
|
478
478
|
return r`
|
|
479
|
-
<a href=${n._mode === "hash" ? "#" + a : a} style=${() => n.current.value ===
|
|
479
|
+
<a href=${n._mode === "hash" ? "#" + a : a} style=${() => n.current.value === i ? "color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a" : "color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px"} @click=${(t) => {
|
|
480
480
|
t.preventDefault(), n.navigate(e);
|
|
481
481
|
}}>${t}</a>
|
|
482
482
|
`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","names":[],"sources":["../../src/elur/router.ts"],"sourcesContent":["import { signal } from \"./reactivity.js\";\nimport type { Signal } from \"./reactivity.js\";\nimport { ElurComponent } from \"./lifecycle.js\";\nimport type { ElurTemplate } from \"./template/index.js\";\nimport { html } from \"./template/index.js\";\nimport { inject } from \"./context.js\";\nimport { RouterKey, _mountedRouters, _debugRegisterRouter, _debugUnregisterRouter } from \"./router-registry.js\";\n\n// =============================================================================\n// Public types\n// =============================================================================\n\n/**\n * Value returned (or resolved) by a navigation guard.\n *\n * - `true` / `void` / `undefined` — allow.\n * - `false` — cancel (no redirect).\n * - `string` — redirect to that path.\n * - `{ redirect: string }` — redirect, object form.\n *\n * The object form exists so guards written for the outlet API can be reused\n * verbatim by the core. See `elur-ionic`'s `GuardResult`.\n */\nexport type NavigationGuardResult =\n | void\n | undefined\n | boolean\n | string\n | { redirect: string };\n\n/** Guard function invoked before navigation commits. */\nexport type NavigationGuard = (\n to: string,\n from: string,\n) => NavigationGuardResult | Promise<NavigationGuardResult>;\n\nexport interface RouteRecord {\n /** Optional unique name to enable named navigation. */\n name?: string;\n /** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */\n path: string;\n /**\n * Factory returning the view for this route level.\n *\n * OPTIONAL — when the core router is auto-bootstrapped by an outlet\n * (Ionic's IonRouterOutlet, future others), the outlet owns component\n * mounting and the core never invokes this. In that case, omit it.\n */\n component?: () => ElurTemplate | ElurComponent;\n /** Optional arbitrary metadata for guards, layouts, and auth checks. */\n meta?: Record<string, unknown>;\n /** Child routes. Paths are joined with the parent. */\n children?: RouteRecord[];\n /** Route-level guard. Runs only when entering this specific route. */\n beforeEnter?: NavigationGuard;\n}\n\n/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */\nexport type AfterEachHook = (to: string, from: string) => void;\n\n/** Named route target for programmatic navigation. */\nexport interface NamedRouteLocation {\n name: string;\n params?: Record<string, string | number>;\n query?: Record<string, string | number | boolean | null | undefined>;\n}\n\n/** Navigation input accepted by `navigate` / `replace`. */\nexport type RouteLocation = string | NamedRouteLocation;\n\n/** Serializable scroll position used by the router for history restoration. */\nexport interface ScrollPosition {\n left: number;\n top: number;\n}\n\nexport type ScrollBehavior = (\n to: string,\n from: string,\n savedPosition: ScrollPosition | null,\n) => ScrollPosition | false | void;\n\nexport type RouterMode = \"history\" | \"hash\";\n\nexport interface ResolvedRoute {\n matched: boolean;\n params: Record<string, string>;\n route: RouteRecord | undefined;\n}\n\n// -----------------------------------------------------------------------------\n// Navigation intent\n// -----------------------------------------------------------------------------\n\nexport type NavigationAction = \"push\" | \"replace\" | \"pop\" | \"initial\";\nexport type NavigationDirection = \"forward\" | \"back\" | \"root\" | \"none\";\n\nexport interface NavigationIntent {\n action: NavigationAction;\n direction: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface NavigateOptions {\n query?: Record<string, string | number | boolean | null | undefined>;\n direction?: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface RouterOptions {\n base?: string;\n mode?: RouterMode;\n scrollBehavior?: ScrollBehavior;\n}\n\nexport interface Router {\n readonly current: Signal<string>;\n readonly params: Signal<Record<string, string>>;\n readonly query: Signal<Record<string, string>>;\n readonly base: string;\n readonly intent: Signal<NavigationIntent>;\n readonly canGoBack: Signal<boolean>;\n navigate(location: RouteLocation, options?: NavigateOptions): void;\n replace(location: RouteLocation, options?: NavigateOptions): void;\n back(animation?: unknown): void;\n forward(animation?: unknown): void;\n go(delta: number): void;\n isActive(path: string, exact?: boolean): boolean;\n resolve(path: string): ResolvedRoute;\n readonly routes: RouteRecord[];\n beforeEach(guard: NavigationGuard): () => void;\n afterEach(hook: AfterEachHook): () => void;\n}\n\n// RouterKey and the mounted-router debug registry live in router-registry.ts\n// so that component.ts (mount) can use them without importing this module —\n// keeping the router out of bundles that never call createRouter().\nexport { RouterKey, _debugRegisterRouter, _debugUnregisterRouter };\n\n// =============================================================================\n// Internals\n// =============================================================================\n\ntype Segment =\n | { kind: \"literal\"; value: string }\n | { kind: \"param\"; name: string }\n | { kind: \"wildcard\" };\n\ninterface FlatRoute {\n fullPath: string;\n segments: Segment[];\n chain: Array<(() => ElurTemplate | ElurComponent) | undefined>;\n name?: string;\n meta?: Record<string, unknown>;\n beforeEnter?: NavigationGuard;\n record: RouteRecord;\n}\n\ninterface RouterInternal extends Router {\n _flat: FlatRoute[];\n _guards: NavigationGuard[];\n _base: string;\n _mode: RouterMode;\n}\n\nlet _currentRouter: RouterInternal | null = null;\nlet _currentPopstateCleanup: (() => void) | null = null;\n\nconst SCROLL_STATE_KEY = \"__elur_scroll\";\nconst POSITION_STATE_KEY = \"__elur_pos\";\n\nfunction getRouter(): RouterInternal {\n if (!_currentRouter) {\n throw new Error(\n \"[elur] No active router. Call createRouter() first, \" +\n \"or instantiate an outlet that auto-bootstraps one (e.g. IonRouterOutlet).\"\n );\n }\n return _currentRouter;\n}\n\n/**\n * @internal Whether a router is currently active. Used by outlets that\n * want to auto-bootstrap if the user didn't call `createRouter()` themselves.\n */\nexport function _hasActiveRouter(): boolean {\n return _currentRouter !== null;\n}\n\n// =============================================================================\n// History state helpers\n// =============================================================================\n\nfunction getCurrentScrollPosition(): ScrollPosition {\n return {\n left: window.scrollX ?? window.pageXOffset ?? 0,\n top: window.scrollY ?? window.pageYOffset ?? 0,\n };\n}\n\nfunction readScrollPositionFromState(state: unknown): ScrollPosition | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[SCROLL_STATE_KEY];\n if (!raw || typeof raw !== \"object\") return null;\n const left = (raw as Record<string, unknown>).left;\n const top = (raw as Record<string, unknown>).top;\n if (typeof left !== \"number\" || typeof top !== \"number\") return null;\n return { left, top };\n}\n\nfunction readPositionFromState(state: unknown): number | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[POSITION_STATE_KEY];\n return typeof raw === \"number\" ? raw : null;\n}\n\nfunction buildHistoryState(\n prev: unknown,\n scroll: ScrollPosition,\n position: number,\n): Record<string, unknown> {\n const base = prev && typeof prev === \"object\"\n ? { ...(prev as Record<string, unknown>) }\n : {};\n base[SCROLL_STATE_KEY] = { left: scroll.left, top: scroll.top };\n base[POSITION_STATE_KEY] = position;\n return base;\n}\n\n// =============================================================================\n// Query / path helpers\n// =============================================================================\n\nfunction parseQuery(search: string): Record<string, string> {\n const result: Record<string, string> = {};\n new URLSearchParams(search).forEach((v, k) => { result[k] = v; });\n return result;\n}\n\nfunction buildQueryString(\n q: Record<string, string | number | boolean | null | undefined>,\n): string {\n const p = new URLSearchParams();\n for (const [k, v] of Object.entries(q)) {\n if (v != null && v !== false) p.set(k, String(v));\n }\n const s = p.toString();\n return s ? \"?\" + s : \"\";\n}\n\nfunction parseSegments(fullPath: string): Segment[] {\n if (fullPath === \"*\") return [{ kind: \"wildcard\" }];\n return fullPath\n .split(\"/\")\n .filter(Boolean)\n .map((part): Segment => {\n if (part === \"*\") return { kind: \"wildcard\" };\n if (part.startsWith(\":\")) return { kind: \"param\", name: part.slice(1) };\n return { kind: \"literal\", value: part };\n });\n}\n\nfunction joinPaths(parent: string, child: string): string {\n if (child === \"*\") return parent === \"\" ? \"*\" : parent + \"/*\";\n const segment = child.startsWith(\"/\") ? child : \"/\" + child;\n return (parent + segment).replace(/\\/+/g, \"/\") || \"/\";\n}\n\nfunction flattenRoutes(\n routes: RouteRecord[],\n parentPath = \"\",\n parentChain: Array<(() => ElurTemplate | ElurComponent) | undefined> = [],\n): FlatRoute[] {\n const result: FlatRoute[] = [];\n for (const route of routes) {\n const fullPath = joinPaths(parentPath, route.path);\n const chain = [...parentChain, route.component];\n const segments = parseSegments(fullPath);\n result.push({\n fullPath,\n segments,\n chain,\n name: route.name,\n meta: route.meta,\n beforeEnter: route.beforeEnter,\n record: route,\n });\n if (route.children?.length) {\n result.push(...flattenRoutes(route.children, fullPath, chain));\n }\n }\n return result;\n}\n\nfunction tryMatch(path: string, route: FlatRoute): Record<string, string> | null {\n const parts = path.split(\"/\").filter(Boolean);\n const segs = route.segments;\n if (segs.length === 1 && segs[0].kind === \"wildcard\") return {};\n const lastIsWild = segs.length > 0 && segs[segs.length - 1].kind === \"wildcard\";\n const fixedSegs = lastIsWild ? segs.slice(0, -1) : segs;\n if (lastIsWild) {\n if (parts.length < fixedSegs.length) return null;\n } else {\n if (parts.length !== fixedSegs.length) return null;\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < fixedSegs.length; i++) {\n const seg = fixedSegs[i];\n if (seg.kind === \"literal\") {\n if (parts[i] !== seg.value) return null;\n } else if (seg.kind === \"param\") {\n try {\n params[seg.name] = decodeURIComponent(parts[i] ?? \"\");\n } catch {\n params[seg.name] = parts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction specificity(route: FlatRoute): number {\n return route.segments.reduce((acc, seg) => {\n if (seg.kind === \"literal\") return acc + 2;\n if (seg.kind === \"param\") return acc + 1;\n return acc;\n }, 0);\n}\n\nfunction matchFlat(\n path: string,\n flat: FlatRoute[],\n): { route: FlatRoute; params: Record<string, string> } | undefined {\n let best: FlatRoute | undefined;\n let bestParams: Record<string, string> = {};\n let bestScore = -1;\n for (const route of flat) {\n const params = tryMatch(path, route);\n if (params === null) continue;\n const score = specificity(route);\n if (score > bestScore) {\n best = route;\n bestParams = params;\n bestScore = score;\n }\n }\n return best ? { route: best, params: bestParams } : undefined;\n}\n\n// =============================================================================\n// Base path helpers\n// =============================================================================\n\nfunction normalizeBase(raw: string): string {\n let b = raw.trim();\n if (!b || b === \"/\") return \"\";\n if (!b.startsWith(\"/\")) b = \"/\" + b;\n if (b.endsWith(\"/\")) b = b.slice(0, -1);\n return b;\n}\n\nfunction detectBase(): string {\n if (typeof document === \"undefined\") return \"\";\n const baseEl = document.querySelector(\"base\");\n if (!baseEl) return \"\";\n const href = baseEl.getAttribute(\"href\") || \"\";\n try {\n const url = new URL(href, window.location.origin);\n return normalizeBase(url.pathname);\n } catch {\n return normalizeBase(href);\n }\n}\n\n// =============================================================================\n// Guard result normalization\n// =============================================================================\n\n/**\n * Normalize any guard result into the internal flow's {allow, redirect} shape.\n * Accepts: `true`, `false`, `void`/`undefined`, `string`, `{ redirect: string }`.\n */\nfunction normalizeGuardResult(\n r: NavigationGuardResult,\n): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n // Unknown — be permissive rather than break navigation.\n return { allow: true };\n}\n\n// =============================================================================\n// createRouter\n// =============================================================================\n\nexport function createRouter(routes: RouteRecord[], options?: RouterOptions): Router {\n const _base = options?.base != null ? normalizeBase(options.base) : detectBase();\n const _mode: RouterMode = options?.mode ?? \"history\";\n const _isHashMode = _mode === \"hash\";\n const _scrollBehavior = options?.scrollBehavior;\n const _hashScrollPositions = new Map<string, ScrollPosition>();\n let _ignoreNextHashChange = false;\n\n function normalizeAppPath(raw: string): string {\n if (!raw) return \"/\";\n return raw.startsWith(\"/\") ? raw : \"/\" + raw;\n }\n\n function stripBase(rawPath: string): string {\n const path = normalizeAppPath(rawPath || \"/\");\n if (_base && path.startsWith(_base)) {\n const stripped = path.slice(_base.length);\n return stripped === \"\" ? \"/\" : normalizeAppPath(stripped);\n }\n return path;\n }\n\n function withBase(appPath: string): string {\n const p = normalizeAppPath(appPath);\n if (!_base) return p;\n return (_base + p).replace(/\\/+/g, \"/\") || \"/\";\n }\n\n function readHashLocation(): { pathname: string; search: string } {\n let raw = window.location.hash || \"\";\n if (raw.startsWith(\"#\")) raw = raw.slice(1);\n if (!raw) return { pathname: \"/\", search: \"\" };\n if (!raw.startsWith(\"/\")) raw = \"/\" + raw;\n const qIdx = raw.indexOf(\"?\");\n const pathname = qIdx === -1 ? raw : raw.slice(0, qIdx);\n const search = qIdx === -1 ? \"\" : raw.slice(qIdx);\n return { pathname: stripBase(pathname), search };\n }\n\n function readLocation(): { pathname: string; search: string } {\n if (_isHashMode) return readHashLocation();\n return {\n pathname: stripBase(window.location.pathname || \"/\"),\n search: window.location.search || \"\",\n };\n }\n\n function buildUrl(pathname: string, stringQuery: Record<string, string>): string {\n const fullPath = withBase(pathname) + buildQueryString(stringQuery);\n return _isHashMode ? \"#\" + fullPath : fullPath;\n }\n\n function routeKey(pathname: string, stringQuery: Record<string, string>): string {\n return normalizeAppPath(pathname) + buildQueryString(stringQuery);\n }\n\n // -------------------------------------------------------------------------\n // Initial state\n // -------------------------------------------------------------------------\n\n const initialLoc = readLocation();\n const initialPath = initialLoc.pathname;\n const initialQuery = parseQuery(initialLoc.search);\n const flat = flattenRoutes(routes);\n\n const _nameIndex = new Map<string, FlatRoute>();\n for (const route of flat) {\n if (!route.name) continue;\n if (_nameIndex.has(route.name)) {\n console.warn(`[Elur Router] Duplicate route name: \"${route.name}\"`);\n }\n _nameIndex.set(route.name, route);\n }\n const initialMatch = matchFlat(initialPath, flat);\n\n const current = signal(initialPath);\n const params = signal<Record<string, string>>(initialMatch?.params ?? {});\n const query = signal<Record<string, string>>(initialQuery);\n\n let _currentPosition = readPositionFromState(history.state) ?? 0;\n\n const intent = signal<NavigationIntent>({\n action: \"initial\",\n direction: \"none\",\n });\n\n const canGoBack = signal<boolean>(_currentPosition > 0);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(initialPath, initialQuery), getCurrentScrollPosition());\n } else {\n history.replaceState(\n buildHistoryState(history.state, getCurrentScrollPosition(), _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Scroll\n // -------------------------------------------------------------------------\n\n function _scrollTo(pos: ScrollPosition): void {\n window.scrollTo(pos.left, pos.top);\n }\n\n function _applyScroll(to: string, from: string, savedPosition: ScrollPosition | null): void {\n if (_scrollBehavior) {\n const result = _scrollBehavior(to, from, savedPosition);\n if (!result) return;\n _scrollTo(result);\n return;\n }\n _scrollTo(savedPosition ?? { left: 0, top: 0 });\n }\n\n function _saveCurrentEntryScroll(pathname: string, stringQuery: Record<string, string>): void {\n const pos = getCurrentScrollPosition();\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), pos);\n return;\n }\n history.replaceState(\n buildHistoryState(history.state, pos, _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Guards\n // -------------------------------------------------------------------------\n\n const _guards: NavigationGuard[] = [];\n const _afterHooks: AfterEachHook[] = [];\n let _navGeneration = 0;\n\n function _runGuards(\n to: string,\n from: string,\n routeGuard: NavigationGuard | undefined,\n onCommit: () => void,\n onCancel?: () => void,\n ): void {\n const guards: NavigationGuard[] = [..._guards];\n if (routeGuard) guards.push(routeGuard);\n\n const gen = ++_navGeneration;\n\n if (guards.length === 0) { onCommit(); return; }\n\n let idx = 0;\n function runNext(prev: NavigationGuardResult): void {\n if (gen !== _navGeneration) return;\n\n const norm = normalizeGuardResult(prev);\n if (!norm.allow) {\n if (norm.redirect && norm.redirect !== to) {\n navigate(norm.redirect);\n return;\n }\n if (norm.redirect === to) {\n // Guarding TO the same path — treat as allow to avoid loops\n onCommit();\n return;\n }\n onCancel?.();\n return;\n }\n if (idx >= guards.length) { onCommit(); return; }\n const result = guards[idx++](to, from);\n if (result instanceof Promise) { result.then(runNext); return; }\n runNext(result);\n }\n runNext(undefined);\n }\n\n // -------------------------------------------------------------------------\n // Path / location resolution\n // -------------------------------------------------------------------------\n\n let _hasNavigated = false;\n\n function _parsePath(\n path: string,\n queryObj?: Record<string, string | number | boolean | null | undefined>,\n ): { pathname: string; stringQuery: Record<string, string> } {\n const qIdx = path.indexOf(\"?\");\n const rawPath = qIdx === -1 ? path : path.slice(0, qIdx);\n const pathname = normalizeAppPath(rawPath || \"/\");\n const inlineQ = qIdx === -1 ? {} : parseQuery(path.slice(qIdx));\n const finalQuery = queryObj ? { ...inlineQ, ...queryObj } : inlineQ;\n const stringQuery: Record<string, string> = {};\n for (const [k, v] of Object.entries(finalQuery)) {\n if (v != null && v !== false) stringQuery[k] = String(v);\n }\n return { pathname, stringQuery };\n }\n\n function _resolveNamedPath(location: NamedRouteLocation): string {\n const found = _nameIndex.get(location.name);\n if (!found) {\n throw new Error(`[Elur Router] No route with name \"${location.name}\"`);\n }\n const parts = found.segments.map((seg) => {\n if (seg.kind === \"literal\") return seg.value;\n if (seg.kind === \"wildcard\") return \"\";\n const value = location.params?.[seg.name];\n if (value == null) {\n throw new Error(\n `[Elur Router] Missing param \"${seg.name}\" for route \"${location.name}\"`,\n );\n }\n return encodeURIComponent(String(value));\n });\n return \"/\" + parts.filter(Boolean).join(\"/\");\n }\n\n function _resolveLocation(\n location: RouteLocation,\n options?: NavigateOptions,\n ): { pathname: string; stringQuery: Record<string, string> } {\n if (typeof location === \"string\") {\n return _parsePath(location, options?.query);\n }\n const pathname = _resolveNamedPath(location);\n const mergedQuery = { ...(location.query ?? {}), ...(options?.query ?? {}) };\n return _parsePath(pathname, mergedQuery);\n }\n\n // -------------------------------------------------------------------------\n // Popstate / hashchange listener\n // -------------------------------------------------------------------------\n\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n\n const handleBrowserNav = (\n p: string,\n newQuery: Record<string, string>,\n savedPos: ScrollPosition | null,\n nextPosition: number | null,\n onCancelRestore: (from: string, fromQuery: Record<string, string>) => void,\n ) => {\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(p, flat);\n\n let direction: NavigationDirection = \"none\";\n if (nextPosition != null) {\n if (nextPosition < _currentPosition) direction = \"back\";\n else if (nextPosition > _currentPosition) direction = \"forward\";\n }\n\n _runGuards(\n p,\n from,\n m?.route.beforeEnter,\n () => {\n if (nextPosition != null) _currentPosition = nextPosition;\n const animation = _pendingPopAnimation;\n _pendingPopAnimation = undefined;\n intent.value = { action: \"pop\", direction, animation };\n params.value = m?.params ?? {};\n query.value = newQuery;\n current.value = p;\n canGoBack.value = _currentPosition > 0;\n _applyScroll(p, from, savedPos);\n for (const hook of _afterHooks) {\n try { hook(p, from); } catch { /* ignore */ }\n }\n },\n () => onCancelRestore(from, fromQuery),\n );\n };\n\n if (_isHashMode) {\n const onHashChange = () => {\n if (_ignoreNextHashChange) {\n _ignoreNextHashChange = false;\n return;\n }\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = _hashScrollPositions.get(routeKey(loc.pathname, nextQuery)) ?? null;\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n null,\n (from, fromQuery) => {\n _ignoreNextHashChange = true;\n window.location.hash = buildUrl(from, fromQuery).slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n },\n );\n };\n window.addEventListener(\"hashchange\", onHashChange);\n _currentPopstateCleanup = () => window.removeEventListener(\"hashchange\", onHashChange);\n } else {\n const onPopstate = (ev: PopStateEvent) => {\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = readScrollPositionFromState(ev.state ?? history.state);\n const nextPos = readPositionFromState(ev.state ?? history.state);\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n nextPos,\n (from, fromQuery) => {\n history.pushState(\n buildHistoryState({}, getCurrentScrollPosition(), _currentPosition),\n \"\",\n buildUrl(from, fromQuery),\n );\n },\n );\n };\n window.addEventListener(\"popstate\", onPopstate);\n _currentPopstateCleanup = () => window.removeEventListener(\"popstate\", onPopstate);\n }\n\n // -------------------------------------------------------------------------\n // Internal commit (programmatic navigation)\n // -------------------------------------------------------------------------\n\n function _commit(\n pathname: string,\n stringQuery: Record<string, string>,\n from: string,\n fromQuery: Record<string, string>,\n m: ReturnType<typeof matchFlat>,\n nextIntent: NavigationIntent,\n useReplace: boolean,\n ): void {\n if (!useReplace) {\n _saveCurrentEntryScroll(from, fromQuery);\n _currentPosition += 1;\n }\n\n intent.value = nextIntent;\n params.value = m?.params ?? {};\n query.value = stringQuery;\n current.value = pathname;\n canGoBack.value = _currentPosition > 0;\n\n const url = buildUrl(pathname, stringQuery);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), { left: 0, top: 0 });\n if (useReplace) {\n history.replaceState(history.state, \"\", url);\n } else {\n _ignoreNextHashChange = true;\n window.location.hash = url.slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n }\n } else {\n const nextState = buildHistoryState({}, { left: 0, top: 0 }, _currentPosition);\n if (useReplace) {\n history.replaceState(nextState, \"\", url);\n } else {\n history.pushState(nextState, \"\", url);\n }\n }\n\n _applyScroll(pathname, from, null);\n for (const hook of _afterHooks) {\n try { hook(pathname, from); } catch { /* ignore */ }\n }\n }\n\n // -------------------------------------------------------------------------\n // Public navigation API\n // -------------------------------------------------------------------------\n\n let _pendingPopAnimation: unknown = undefined;\n\n function navigate(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"push\",\n direction: options?.direction ?? \"forward\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, false),\n );\n }\n\n function replace(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"replace\",\n direction: options?.direction ?? \"root\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, true),\n );\n }\n\n function back(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.back();\n }\n\n function forward(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.forward();\n }\n\n function go(delta: number): void { history.go(delta); }\n\n function isActive(path: string, exact = true): boolean {\n const cur = current.value;\n if (exact) return cur === path;\n return cur === path || cur.startsWith(path.endsWith(\"/\") ? path : path + \"/\");\n }\n\n function resolve(path: string): ResolvedRoute {\n const m = matchFlat(path, flat);\n if (!m) return { matched: false, params: {}, route: undefined };\n return { matched: true, params: m.params, route: m.route.record };\n }\n\n function beforeEach(guard: NavigationGuard): () => void {\n _guards.push(guard);\n return () => {\n const idx = _guards.indexOf(guard);\n if (idx !== -1) _guards.splice(idx, 1);\n };\n }\n\n function afterEach(hook: AfterEachHook): () => void {\n _afterHooks.push(hook);\n return () => {\n const idx = _afterHooks.indexOf(hook);\n if (idx !== -1) _afterHooks.splice(idx, 1);\n };\n }\n\n const router: RouterInternal = {\n current, params, query, intent, canGoBack,\n base: _base || \"/\",\n navigate, replace, back, forward, go,\n isActive, resolve,\n beforeEach, afterEach, routes,\n _flat: flat, _guards, _base, _mode,\n };\n\n if (_currentRouter) {\n console.warn(\n \"[elur] A router already exists. The previous router is being replaced. \" +\n \"Only one router instance should be active at a time.\",\n );\n }\n _currentRouter = router;\n\n queueMicrotask(() => {\n if (_hasNavigated) return;\n\n const m = matchFlat(initialPath, flat);\n _runGuards(\n initialPath,\n \"\",\n m?.route.beforeEnter,\n () => { /* allowed */ },\n () => {\n const fallback = \"/\";\n const url = buildUrl(fallback, {});\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(fallback, {}), { left: 0, top: 0 });\n history.replaceState(history.state, \"\", url);\n } else {\n history.replaceState(\n buildHistoryState({}, { left: 0, top: 0 }, _currentPosition),\n \"\",\n url,\n );\n }\n const fm = matchFlat(fallback, flat);\n intent.value = { action: \"replace\", direction: \"root\" };\n current.value = fallback;\n params.value = fm?.params ?? {};\n query.value = {};\n canGoBack.value = _currentPosition > 0;\n _applyScroll(fallback, initialPath, null);\n },\n );\n });\n\n return router;\n}\n\nexport function elurRouter(): Router {\n const injected = inject(RouterKey);\n if (injected) return injected;\n return getRouter();\n}\n\n/** @internal */\nexport function _resetRouter(): void {\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n _currentRouter = null;\n _mountedRouters.length = 0;\n}\n\nexport class RouterView extends ElurComponent {\n private _depth: number;\n private _router?: RouterInternal;\n\n constructor(depth = 0, router?: Router) {\n super();\n this._depth = depth;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const depth = this._depth;\n const explicitRouter = this._router;\n return html`<div class=\"router-view\">${() => {\n const router = explicitRouter ?? elurRouter() as RouterInternal;\n const matched = matchFlat(router.current.value, router._flat);\n if (!matched) {\n return html`\n <div style=\"color:#f87171;padding:16px 0\">\n 404 — Route not found: <strong>${router.current.value}</strong>\n </div>\n `;\n }\n if (depth >= matched.route.chain.length) {\n return html`\n <span></span>\n `;\n }\n const factory = matched.route.chain[depth];\n // chain entries can be undefined when the route was registered without\n // a `component` (typical when an outlet auto-bootstraps the router).\n // In that case there's nothing to render at this depth.\n if (!factory) return html`\n <span></span>\n `;\n return factory();\n }}</div>`;\n }\n}\n\nexport class Link extends ElurComponent {\n private _to: string;\n private _label: string;\n private _router?: RouterInternal;\n\n constructor(to: string, label: string, router?: Router) {\n super();\n this._to = to;\n this._label = label;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const to = this._to;\n const label = this._label;\n const router = this._router ?? elurRouter() as RouterInternal;\n const appPath = to.startsWith(\"/\") ? to : \"/\" + to;\n const fullPath = (router._base ? (router._base + appPath) : appPath).replace(/\\/+/g, \"/\");\n const href = router._mode === \"hash\" ? \"#\" + fullPath : fullPath;\n return html`\n <a href=${href} style=${() => router.current.value === to\n ? \"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a\"\n : \"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px\"} @click=${(e: Event) => { e.preventDefault(); router.navigate(to); }}>${label}</a>\n `;\n }\n}\n\nexport interface _RouterDebugInternal {\n mode: RouterMode;\n base: string;\n currentPath: string;\n params: Record<string, string>;\n query: Record<string, string>;\n matchedPath: string | null;\n activeGuards: { globalCount: number; hasRouteGuard: boolean; names: string[] };\n}\n\nexport function _debugGetRouterInternal(): _RouterDebugInternal | null {\n // Prefer the most recently mounted injected router over the global singleton\n // so devtools inspect the router actually active in the current UI.\n const router = _mountedRouters.length\n ? _mountedRouters[_mountedRouters.length - 1]\n : _currentRouter;\n if (!router) return null;\n const internal = router as RouterInternal;\n const currentPath = internal.current.value;\n const matched = matchFlat(currentPath, internal._flat);\n const routeGuard = matched?.route.beforeEnter;\n const names = internal._guards.map((g, idx) => g.name || `beforeEach#${idx + 1}`);\n if (routeGuard) names.push(routeGuard.name || \"beforeEnter\");\n return {\n mode: internal._mode,\n base: internal._base || \"/\",\n currentPath,\n params: { ...internal.params.value },\n query: { ...internal.query.value },\n matchedPath: matched?.route.fullPath ?? null,\n activeGuards: {\n globalCount: internal._guards.length,\n hasRouteGuard: Boolean(routeGuard),\n names,\n },\n };\n}"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"router.js","names":[],"sources":["../../src/elur/router.ts"],"sourcesContent":["import { signal } from \"./reactivity.js\";\nimport type { Signal } from \"./reactivity.js\";\nimport { ElurComponent } from \"./lifecycle.js\";\nimport type { ElurTemplate } from \"./template/index.js\";\nimport { html } from \"./template/index.js\";\nimport { inject } from \"./context.js\";\nimport { RouterKey, _mountedRouters, _debugRegisterRouter, _debugUnregisterRouter } from \"./router-registry.js\";\n\n// =============================================================================\n// Public types\n// =============================================================================\n\n/**\n * Value returned (or resolved) by a navigation guard.\n *\n * - `true` / `void` / `undefined` — allow.\n * - `false` — cancel (no redirect).\n * - `string` — redirect to that path.\n * - `{ redirect: string }` — redirect, object form.\n *\n * The object form exists so guards written for the outlet API can be reused\n * verbatim by the core. See `elur-ionic`'s `GuardResult`.\n */\nexport type NavigationGuardResult =\n | void\n | undefined\n | boolean\n | string\n | { redirect: string };\n\n/** Guard function invoked before navigation commits. */\nexport type NavigationGuard = (\n to: string,\n from: string,\n) => NavigationGuardResult | Promise<NavigationGuardResult>;\n\nexport interface RouteRecord {\n /** Optional unique name to enable named navigation. */\n name?: string;\n /** Route path segment. Supports literals, params (`:id`), and wildcards (`*`). */\n path: string;\n /**\n * Factory returning the view for this route level.\n *\n * OPTIONAL — when the core router is auto-bootstrapped by an outlet\n * (Ionic's IonRouterOutlet, future others), the outlet owns component\n * mounting and the core never invokes this. In that case, omit it.\n */\n component?: () => ElurTemplate | ElurComponent;\n /** Optional arbitrary metadata for guards, layouts, and auth checks. */\n meta?: Record<string, unknown>;\n /** Child routes. Paths are joined with the parent. */\n children?: RouteRecord[];\n /** Route-level guard. Runs only when entering this specific route. */\n beforeEnter?: NavigationGuard;\n}\n\n/** Callback for `afterEach` hooks — receives the committed `to` and `from` paths. */\nexport type AfterEachHook = (to: string, from: string) => void;\n\n/** Named route target for programmatic navigation. */\nexport interface NamedRouteLocation {\n name: string;\n params?: Record<string, string | number>;\n query?: Record<string, string | number | boolean | null | undefined>;\n}\n\n/** Navigation input accepted by `navigate` / `replace`. */\nexport type RouteLocation = string | NamedRouteLocation;\n\n/** Serializable scroll position used by the router for history restoration. */\nexport interface ScrollPosition {\n left: number;\n top: number;\n}\n\nexport type ScrollBehavior = (\n to: string,\n from: string,\n savedPosition: ScrollPosition | null,\n) => ScrollPosition | false | void;\n\nexport type RouterMode = \"history\" | \"hash\";\n\nexport interface ResolvedRoute {\n matched: boolean;\n params: Record<string, string>;\n route: RouteRecord | undefined;\n}\n\n// -----------------------------------------------------------------------------\n// Navigation intent\n// -----------------------------------------------------------------------------\n\nexport type NavigationAction = \"push\" | \"replace\" | \"pop\" | \"initial\";\nexport type NavigationDirection = \"forward\" | \"back\" | \"root\" | \"none\";\n\nexport interface NavigationIntent {\n action: NavigationAction;\n direction: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface NavigateOptions {\n query?: Record<string, string | number | boolean | null | undefined>;\n direction?: NavigationDirection;\n animation?: unknown;\n}\n\nexport interface RouterOptions {\n base?: string;\n mode?: RouterMode;\n scrollBehavior?: ScrollBehavior;\n}\n\nexport interface Router {\n readonly current: Signal<string>;\n readonly params: Signal<Record<string, string>>;\n readonly query: Signal<Record<string, string>>;\n readonly base: string;\n readonly intent: Signal<NavigationIntent>;\n readonly canGoBack: Signal<boolean>;\n navigate(location: RouteLocation, options?: NavigateOptions): void;\n replace(location: RouteLocation, options?: NavigateOptions): void;\n back(animation?: unknown): void;\n forward(animation?: unknown): void;\n go(delta: number): void;\n isActive(path: string, exact?: boolean): boolean;\n resolve(path: string): ResolvedRoute;\n readonly routes: RouteRecord[];\n beforeEach(guard: NavigationGuard): () => void;\n afterEach(hook: AfterEachHook): () => void;\n}\n\n// RouterKey and the mounted-router debug registry live in router-registry.ts\n// so that component.ts (mount) can use them without importing this module —\n// keeping the router out of bundles that never call createRouter().\nexport { RouterKey, _debugRegisterRouter, _debugUnregisterRouter };\n\n// =============================================================================\n// Internals\n// =============================================================================\n\ntype Segment =\n | { kind: \"literal\"; value: string }\n | { kind: \"param\"; name: string }\n | { kind: \"wildcard\" };\n\ninterface FlatRoute {\n fullPath: string;\n segments: Segment[];\n chain: Array<(() => ElurTemplate | ElurComponent) | undefined>;\n name?: string;\n meta?: Record<string, unknown>;\n beforeEnter?: NavigationGuard;\n record: RouteRecord;\n}\n\ninterface RouterInternal extends Router {\n _flat: FlatRoute[];\n _guards: NavigationGuard[];\n _base: string;\n _mode: RouterMode;\n}\n\nlet _currentRouter: RouterInternal | null = null;\nlet _currentPopstateCleanup: (() => void) | null = null;\n\nconst SCROLL_STATE_KEY = \"__elur_scroll\";\nconst POSITION_STATE_KEY = \"__elur_pos\";\n\nfunction getRouter(): RouterInternal {\n if (!_currentRouter) {\n throw new Error(\n \"[elur] No active router. Call createRouter() first, \" +\n \"or instantiate an outlet that auto-bootstraps one (e.g. IonRouterOutlet).\"\n );\n }\n return _currentRouter;\n}\n\n/**\n * @internal Whether a router is currently active. Used by outlets that\n * want to auto-bootstrap if the user didn't call `createRouter()` themselves.\n */\nexport function _hasActiveRouter(): boolean {\n return _currentRouter !== null;\n}\n\n// =============================================================================\n// History state helpers\n// =============================================================================\n\nfunction getCurrentScrollPosition(): ScrollPosition {\n return {\n left: window.scrollX ?? window.pageXOffset ?? 0,\n top: window.scrollY ?? window.pageYOffset ?? 0,\n };\n}\n\nfunction readScrollPositionFromState(state: unknown): ScrollPosition | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[SCROLL_STATE_KEY];\n if (!raw || typeof raw !== \"object\") return null;\n const left = (raw as Record<string, unknown>).left;\n const top = (raw as Record<string, unknown>).top;\n if (typeof left !== \"number\" || typeof top !== \"number\") return null;\n return { left, top };\n}\n\nfunction readPositionFromState(state: unknown): number | null {\n if (!state || typeof state !== \"object\") return null;\n const raw = (state as Record<string, unknown>)[POSITION_STATE_KEY];\n return typeof raw === \"number\" ? raw : null;\n}\n\nfunction buildHistoryState(\n prev: unknown,\n scroll: ScrollPosition,\n position: number,\n): Record<string, unknown> {\n const base = prev && typeof prev === \"object\"\n ? { ...(prev as Record<string, unknown>) }\n : {};\n base[SCROLL_STATE_KEY] = { left: scroll.left, top: scroll.top };\n base[POSITION_STATE_KEY] = position;\n return base;\n}\n\n// =============================================================================\n// Query / path helpers\n// =============================================================================\n\nfunction parseQuery(search: string): Record<string, string> {\n const result: Record<string, string> = {};\n new URLSearchParams(search).forEach((v, k) => { result[k] = v; });\n return result;\n}\n\nfunction buildQueryString(\n q: Record<string, string | number | boolean | null | undefined>,\n): string {\n const p = new URLSearchParams();\n for (const [k, v] of Object.entries(q)) {\n if (v != null && v !== false) p.set(k, String(v));\n }\n const s = p.toString();\n return s ? \"?\" + s : \"\";\n}\n\nfunction parseSegments(fullPath: string): Segment[] {\n if (fullPath === \"*\") return [{ kind: \"wildcard\" }];\n return fullPath\n .split(\"/\")\n .filter(Boolean)\n .map((part): Segment => {\n if (part === \"*\") return { kind: \"wildcard\" };\n if (part.startsWith(\":\")) return { kind: \"param\", name: part.slice(1) };\n return { kind: \"literal\", value: part };\n });\n}\n\nfunction joinPaths(parent: string, child: string): string {\n if (child === \"*\") return parent === \"\" ? \"*\" : parent + \"/*\";\n const segment = child.startsWith(\"/\") ? child : \"/\" + child;\n return (parent + segment).replace(/\\/+/g, \"/\") || \"/\";\n}\n\nfunction flattenRoutes(\n routes: RouteRecord[],\n parentPath = \"\",\n parentChain: Array<(() => ElurTemplate | ElurComponent) | undefined> = [],\n): FlatRoute[] {\n const result: FlatRoute[] = [];\n for (const route of routes) {\n const fullPath = joinPaths(parentPath, route.path);\n const chain = [...parentChain, route.component];\n const segments = parseSegments(fullPath);\n result.push({\n fullPath,\n segments,\n chain,\n name: route.name,\n meta: route.meta,\n beforeEnter: route.beforeEnter,\n record: route,\n });\n if (route.children?.length) {\n result.push(...flattenRoutes(route.children, fullPath, chain));\n }\n }\n return result;\n}\n\nfunction tryMatch(path: string, route: FlatRoute): Record<string, string> | null {\n const parts = path.split(\"/\").filter(Boolean);\n const segs = route.segments;\n if (segs.length === 1 && segs[0].kind === \"wildcard\") return {};\n const lastIsWild = segs.length > 0 && segs[segs.length - 1].kind === \"wildcard\";\n const fixedSegs = lastIsWild ? segs.slice(0, -1) : segs;\n if (lastIsWild) {\n if (parts.length < fixedSegs.length) return null;\n } else {\n if (parts.length !== fixedSegs.length) return null;\n }\n const params: Record<string, string> = {};\n for (let i = 0; i < fixedSegs.length; i++) {\n const seg = fixedSegs[i];\n if (seg.kind === \"literal\") {\n if (parts[i] !== seg.value) return null;\n } else if (seg.kind === \"param\") {\n try {\n params[seg.name] = decodeURIComponent(parts[i] ?? \"\");\n } catch {\n params[seg.name] = parts[i] ?? \"\";\n }\n }\n }\n return params;\n}\n\nfunction specificity(route: FlatRoute): number {\n return route.segments.reduce((acc, seg) => {\n if (seg.kind === \"literal\") return acc + 2;\n if (seg.kind === \"param\") return acc + 1;\n return acc;\n }, 0);\n}\n\nfunction matchFlat(\n path: string,\n flat: FlatRoute[],\n): { route: FlatRoute; params: Record<string, string> } | undefined {\n let best: FlatRoute | undefined;\n let bestParams: Record<string, string> = {};\n let bestScore = -1;\n for (const route of flat) {\n const params = tryMatch(path, route);\n if (params === null) continue;\n const score = specificity(route);\n if (score > bestScore) {\n best = route;\n bestParams = params;\n bestScore = score;\n }\n }\n return best ? { route: best, params: bestParams } : undefined;\n}\n\n// =============================================================================\n// Base path helpers\n// =============================================================================\n\nfunction normalizeBase(raw: string): string {\n let b = raw.trim();\n if (!b || b === \"/\") return \"\";\n if (!b.startsWith(\"/\")) b = \"/\" + b;\n if (b.endsWith(\"/\")) b = b.slice(0, -1);\n return b;\n}\n\nfunction detectBase(): string {\n if (typeof document === \"undefined\") return \"\";\n const baseEl = document.querySelector(\"base\");\n if (!baseEl) return \"\";\n const href = baseEl.getAttribute(\"href\") || \"\";\n try {\n const url = new URL(href, window.location.origin);\n return normalizeBase(url.pathname);\n } catch {\n return normalizeBase(href);\n }\n}\n\n// =============================================================================\n// Guard result normalization\n// =============================================================================\n\n/**\n * Normalize any guard result into the internal flow's {allow, redirect} shape.\n * Accepts: `true`, `false`, `void`/`undefined`, `string`, `{ redirect: string }`.\n */\nfunction normalizeGuardResult(\n r: NavigationGuardResult,\n): { allow: boolean; redirect?: string } {\n if (r === false) return { allow: false };\n if (r === true || r === undefined || r === null) return { allow: true };\n if (typeof r === \"string\") return { allow: false, redirect: r };\n if (typeof r === \"object\" && \"redirect\" in r && typeof r.redirect === \"string\") {\n return { allow: false, redirect: r.redirect };\n }\n // Unknown — be permissive rather than break navigation.\n return { allow: true };\n}\n\n// =============================================================================\n// createRouter\n// =============================================================================\n\nexport function createRouter(routes: RouteRecord[], options?: RouterOptions): Router {\n const _base = options?.base != null ? normalizeBase(options.base) : detectBase();\n const _mode: RouterMode = options?.mode ?? \"history\";\n const _isHashMode = _mode === \"hash\";\n const _scrollBehavior = options?.scrollBehavior;\n const _hashScrollPositions = new Map<string, ScrollPosition>();\n let _ignoreNextHashChange = false;\n\n function normalizeAppPath(raw: string): string {\n if (!raw) return \"/\";\n return raw.startsWith(\"/\") ? raw : \"/\" + raw;\n }\n\n function stripBase(rawPath: string): string {\n const path = normalizeAppPath(rawPath || \"/\");\n if (_base && path.startsWith(_base)) {\n const stripped = path.slice(_base.length);\n return stripped === \"\" ? \"/\" : normalizeAppPath(stripped);\n }\n return path;\n }\n\n function withBase(appPath: string): string {\n const p = normalizeAppPath(appPath);\n if (!_base) return p;\n return (_base + p).replace(/\\/+/g, \"/\") || \"/\";\n }\n\n function readHashLocation(): { pathname: string; search: string } {\n let raw = window.location.hash || \"\";\n if (raw.startsWith(\"#\")) raw = raw.slice(1);\n if (!raw) return { pathname: \"/\", search: \"\" };\n if (!raw.startsWith(\"/\")) raw = \"/\" + raw;\n const qIdx = raw.indexOf(\"?\");\n const pathname = qIdx === -1 ? raw : raw.slice(0, qIdx);\n const search = qIdx === -1 ? \"\" : raw.slice(qIdx);\n return { pathname: stripBase(pathname), search };\n }\n\n function readLocation(): { pathname: string; search: string } {\n if (_isHashMode) return readHashLocation();\n return {\n pathname: stripBase(window.location.pathname || \"/\"),\n search: window.location.search || \"\",\n };\n }\n\n function buildUrl(pathname: string, stringQuery: Record<string, string>): string {\n const fullPath = withBase(pathname) + buildQueryString(stringQuery);\n return _isHashMode ? \"#\" + fullPath : fullPath;\n }\n\n function routeKey(pathname: string, stringQuery: Record<string, string>): string {\n return normalizeAppPath(pathname) + buildQueryString(stringQuery);\n }\n\n // -------------------------------------------------------------------------\n // Initial state\n // -------------------------------------------------------------------------\n\n const initialLoc = readLocation();\n const initialPath = initialLoc.pathname;\n const initialQuery = parseQuery(initialLoc.search);\n const flat = flattenRoutes(routes);\n\n const _nameIndex = new Map<string, FlatRoute>();\n for (const route of flat) {\n if (!route.name) continue;\n if (_nameIndex.has(route.name)) {\n console.warn(`[Elur Router] Duplicate route name: \"${route.name}\"`);\n }\n _nameIndex.set(route.name, route);\n }\n const initialMatch = matchFlat(initialPath, flat);\n\n const current = signal(initialPath);\n const params = signal<Record<string, string>>(initialMatch?.params ?? {});\n const query = signal<Record<string, string>>(initialQuery);\n\n let _currentPosition = readPositionFromState(history.state) ?? 0;\n\n const intent = signal<NavigationIntent>({\n action: \"initial\",\n direction: \"none\",\n });\n\n const canGoBack = signal<boolean>(_currentPosition > 0);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(initialPath, initialQuery), getCurrentScrollPosition());\n } else {\n history.replaceState(\n buildHistoryState(history.state, getCurrentScrollPosition(), _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Scroll\n // -------------------------------------------------------------------------\n\n function _scrollTo(pos: ScrollPosition): void {\n window.scrollTo(pos.left, pos.top);\n }\n\n function _applyScroll(to: string, from: string, savedPosition: ScrollPosition | null): void {\n if (_scrollBehavior) {\n const result = _scrollBehavior(to, from, savedPosition);\n if (!result) return;\n _scrollTo(result);\n return;\n }\n _scrollTo(savedPosition ?? { left: 0, top: 0 });\n }\n\n function _saveCurrentEntryScroll(pathname: string, stringQuery: Record<string, string>): void {\n const pos = getCurrentScrollPosition();\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), pos);\n return;\n }\n history.replaceState(\n buildHistoryState(history.state, pos, _currentPosition),\n \"\",\n );\n }\n\n // -------------------------------------------------------------------------\n // Guards\n // -------------------------------------------------------------------------\n\n const _guards: NavigationGuard[] = [];\n const _afterHooks: AfterEachHook[] = [];\n let _navGeneration = 0;\n\n function _runGuards(\n to: string,\n from: string,\n routeGuard: NavigationGuard | undefined,\n onCommit: () => void,\n onCancel?: () => void,\n ): void {\n const guards: NavigationGuard[] = [..._guards];\n if (routeGuard) guards.push(routeGuard);\n\n const gen = ++_navGeneration;\n\n if (guards.length === 0) { onCommit(); return; }\n\n let idx = 0;\n function runNext(prev: NavigationGuardResult): void {\n if (gen !== _navGeneration) return;\n\n const norm = normalizeGuardResult(prev);\n if (!norm.allow) {\n if (norm.redirect && norm.redirect !== to) {\n navigate(norm.redirect);\n return;\n }\n if (norm.redirect === to) {\n // Guarding TO the same path — treat as allow to avoid loops\n onCommit();\n return;\n }\n onCancel?.();\n return;\n }\n if (idx >= guards.length) { onCommit(); return; }\n const result = guards[idx++](to, from);\n if (result instanceof Promise) { result.then(runNext); return; }\n runNext(result);\n }\n runNext(undefined);\n }\n\n // -------------------------------------------------------------------------\n // Path / location resolution\n // -------------------------------------------------------------------------\n\n let _hasNavigated = false;\n\n function _parsePath(\n path: string,\n queryObj?: Record<string, string | number | boolean | null | undefined>,\n ): { pathname: string; stringQuery: Record<string, string> } {\n const qIdx = path.indexOf(\"?\");\n const rawPath = qIdx === -1 ? path : path.slice(0, qIdx);\n const pathname = normalizeAppPath(rawPath || \"/\");\n const inlineQ = qIdx === -1 ? {} : parseQuery(path.slice(qIdx));\n const finalQuery = queryObj ? { ...inlineQ, ...queryObj } : inlineQ;\n const stringQuery: Record<string, string> = {};\n for (const [k, v] of Object.entries(finalQuery)) {\n if (v != null && v !== false) stringQuery[k] = String(v);\n }\n return { pathname, stringQuery };\n }\n\n function _resolveNamedPath(location: NamedRouteLocation): string {\n const found = _nameIndex.get(location.name);\n if (!found) {\n throw new Error(`[Elur Router] No route with name \"${location.name}\"`);\n }\n const parts = found.segments.map((seg) => {\n if (seg.kind === \"literal\") return seg.value;\n if (seg.kind === \"wildcard\") return \"\";\n const value = location.params?.[seg.name];\n if (value == null) {\n throw new Error(\n `[Elur Router] Missing param \"${seg.name}\" for route \"${location.name}\"`,\n );\n }\n return encodeURIComponent(String(value));\n });\n return \"/\" + parts.filter(Boolean).join(\"/\");\n }\n\n function _resolveLocation(\n location: RouteLocation,\n options?: NavigateOptions,\n ): { pathname: string; stringQuery: Record<string, string> } {\n if (typeof location === \"string\") {\n return _parsePath(location, options?.query);\n }\n const pathname = _resolveNamedPath(location);\n const mergedQuery = { ...(location.query ?? {}), ...(options?.query ?? {}) };\n return _parsePath(pathname, mergedQuery);\n }\n\n // -------------------------------------------------------------------------\n // Popstate / hashchange listener\n // -------------------------------------------------------------------------\n\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n\n const handleBrowserNav = (\n p: string,\n newQuery: Record<string, string>,\n savedPos: ScrollPosition | null,\n nextPosition: number | null,\n onCancelRestore: (from: string, fromQuery: Record<string, string>) => void,\n ) => {\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(p, flat);\n\n let direction: NavigationDirection = \"none\";\n if (nextPosition != null) {\n if (nextPosition < _currentPosition) direction = \"back\";\n else if (nextPosition > _currentPosition) direction = \"forward\";\n }\n\n _runGuards(\n p,\n from,\n m?.route.beforeEnter,\n () => {\n if (nextPosition != null) _currentPosition = nextPosition;\n const animation = _pendingPopAnimation;\n _pendingPopAnimation = undefined;\n intent.value = { action: \"pop\", direction, animation };\n params.value = m?.params ?? {};\n query.value = newQuery;\n current.value = p;\n canGoBack.value = _currentPosition > 0;\n _applyScroll(p, from, savedPos);\n for (const hook of _afterHooks) {\n try { hook(p, from); } catch { /* ignore */ }\n }\n },\n () => onCancelRestore(from, fromQuery),\n );\n };\n\n if (_isHashMode) {\n const onHashChange = () => {\n if (_ignoreNextHashChange) {\n _ignoreNextHashChange = false;\n return;\n }\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = _hashScrollPositions.get(routeKey(loc.pathname, nextQuery)) ?? null;\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n null,\n (from, fromQuery) => {\n _ignoreNextHashChange = true;\n window.location.hash = buildUrl(from, fromQuery).slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n },\n );\n };\n window.addEventListener(\"hashchange\", onHashChange);\n _currentPopstateCleanup = () => window.removeEventListener(\"hashchange\", onHashChange);\n } else {\n const onPopstate = (ev: PopStateEvent) => {\n const loc = readLocation();\n const nextQuery = parseQuery(loc.search);\n const savedPos = readScrollPositionFromState(ev.state ?? history.state);\n const nextPos = readPositionFromState(ev.state ?? history.state);\n handleBrowserNav(\n loc.pathname,\n nextQuery,\n savedPos,\n nextPos,\n (from, fromQuery) => {\n history.pushState(\n buildHistoryState({}, getCurrentScrollPosition(), _currentPosition),\n \"\",\n buildUrl(from, fromQuery),\n );\n },\n );\n };\n window.addEventListener(\"popstate\", onPopstate);\n _currentPopstateCleanup = () => window.removeEventListener(\"popstate\", onPopstate);\n }\n\n // -------------------------------------------------------------------------\n // Internal commit (programmatic navigation)\n // -------------------------------------------------------------------------\n\n function _commit(\n pathname: string,\n stringQuery: Record<string, string>,\n from: string,\n fromQuery: Record<string, string>,\n m: ReturnType<typeof matchFlat>,\n nextIntent: NavigationIntent,\n useReplace: boolean,\n ): void {\n if (!useReplace) {\n _saveCurrentEntryScroll(from, fromQuery);\n _currentPosition += 1;\n }\n\n intent.value = nextIntent;\n params.value = m?.params ?? {};\n query.value = stringQuery;\n current.value = pathname;\n canGoBack.value = _currentPosition > 0;\n\n const url = buildUrl(pathname, stringQuery);\n\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(pathname, stringQuery), { left: 0, top: 0 });\n if (useReplace) {\n history.replaceState(history.state, \"\", url);\n } else {\n _ignoreNextHashChange = true;\n window.location.hash = url.slice(1);\n queueMicrotask(() => { _ignoreNextHashChange = false; });\n }\n } else {\n const nextState = buildHistoryState({}, { left: 0, top: 0 }, _currentPosition);\n if (useReplace) {\n history.replaceState(nextState, \"\", url);\n } else {\n history.pushState(nextState, \"\", url);\n }\n }\n\n _applyScroll(pathname, from, null);\n for (const hook of _afterHooks) {\n try { hook(pathname, from); } catch { /* ignore */ }\n }\n }\n\n // -------------------------------------------------------------------------\n // Public navigation API\n // -------------------------------------------------------------------------\n\n let _pendingPopAnimation: unknown = undefined;\n\n function navigate(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"push\",\n direction: options?.direction ?? \"forward\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, false),\n );\n }\n\n function replace(location: RouteLocation, options?: NavigateOptions): void {\n _hasNavigated = true;\n const { pathname, stringQuery } = _resolveLocation(location, options);\n const from = current.value;\n const fromQuery = { ...query.value };\n const m = matchFlat(pathname, flat);\n\n const nextIntent: NavigationIntent = {\n action: \"replace\",\n direction: options?.direction ?? \"root\",\n animation: options?.animation,\n };\n\n _runGuards(\n pathname,\n from,\n m?.route.beforeEnter,\n () => _commit(pathname, stringQuery, from, fromQuery, m, nextIntent, true),\n );\n }\n\n function back(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.back();\n }\n\n function forward(animation?: unknown): void {\n if (animation !== undefined) _pendingPopAnimation = animation;\n history.forward();\n }\n\n function go(delta: number): void { history.go(delta); }\n\n function isActive(path: string, exact = true): boolean {\n const cur = current.value;\n if (exact) return cur === path;\n return cur === path || cur.startsWith(path.endsWith(\"/\") ? path : path + \"/\");\n }\n\n function resolve(path: string): ResolvedRoute {\n const m = matchFlat(path, flat);\n if (!m) return { matched: false, params: {}, route: undefined };\n return { matched: true, params: m.params, route: m.route.record };\n }\n\n function beforeEach(guard: NavigationGuard): () => void {\n _guards.push(guard);\n return () => {\n const idx = _guards.indexOf(guard);\n if (idx !== -1) _guards.splice(idx, 1);\n };\n }\n\n function afterEach(hook: AfterEachHook): () => void {\n _afterHooks.push(hook);\n return () => {\n const idx = _afterHooks.indexOf(hook);\n if (idx !== -1) _afterHooks.splice(idx, 1);\n };\n }\n\n const router: RouterInternal = {\n current, params, query, intent, canGoBack,\n base: _base || \"/\",\n navigate, replace, back, forward, go,\n isActive, resolve,\n beforeEach, afterEach, routes,\n _flat: flat, _guards, _base, _mode,\n };\n\n if (_currentRouter) {\n console.warn(\n \"[elur] A router already exists. The previous router is being replaced. \" +\n \"Only one router instance should be active at a time.\",\n );\n }\n _currentRouter = router;\n\n queueMicrotask(() => {\n if (_hasNavigated) return;\n\n const m = matchFlat(initialPath, flat);\n _runGuards(\n initialPath,\n \"\",\n m?.route.beforeEnter,\n () => { /* allowed */ },\n () => {\n const fallback = \"/\";\n const url = buildUrl(fallback, {});\n if (_isHashMode) {\n _hashScrollPositions.set(routeKey(fallback, {}), { left: 0, top: 0 });\n history.replaceState(history.state, \"\", url);\n } else {\n history.replaceState(\n buildHistoryState({}, { left: 0, top: 0 }, _currentPosition),\n \"\",\n url,\n );\n }\n const fm = matchFlat(fallback, flat);\n intent.value = { action: \"replace\", direction: \"root\" };\n current.value = fallback;\n params.value = fm?.params ?? {};\n query.value = {};\n canGoBack.value = _currentPosition > 0;\n _applyScroll(fallback, initialPath, null);\n },\n );\n });\n\n return router;\n}\n\nexport function elurRouter(): Router {\n const injected = inject(RouterKey);\n if (injected) return injected;\n return getRouter();\n}\n\n/** @internal */\nexport function _resetRouter(): void {\n if (_currentPopstateCleanup) {\n _currentPopstateCleanup();\n _currentPopstateCleanup = null;\n }\n _currentRouter = null;\n _mountedRouters.length = 0;\n}\n\nexport class RouterView extends ElurComponent {\n private _depth: number;\n private _router?: RouterInternal;\n\n constructor(depth = 0, router?: Router) {\n super();\n this._depth = depth;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const depth = this._depth;\n const explicitRouter = this._router;\n return html`<div class=\"router-view\">${() => {\n const router = explicitRouter ?? elurRouter() as RouterInternal;\n const matched = matchFlat(router.current.value, router._flat);\n if (!matched) {\n return html`\n <div style=\"color:#f87171;padding:16px 0\">\n 404 — Route not found: <strong>${router.current.value}</strong>\n </div>\n `;\n }\n if (depth >= matched.route.chain.length) {\n return html`\n <span></span>\n `;\n }\n const factory = matched.route.chain[depth];\n // chain entries can be undefined when the route was registered without\n // a `component` (typical when an outlet auto-bootstraps the router).\n // In that case there's nothing to render at this depth.\n if (!factory) return html`\n <span></span>\n `;\n return factory();\n }}</div>`;\n }\n}\n\nexport class Link extends ElurComponent {\n private _to: string;\n private _label: string;\n private _router?: RouterInternal;\n\n constructor(to: string, label: string, router?: Router) {\n super();\n this._to = to;\n this._label = label;\n this._router = router as RouterInternal | undefined;\n }\n\n render(): ElurTemplate {\n const to = this._to;\n const label = this._label;\n const router = this._router ?? elurRouter() as RouterInternal;\n const appPath = to.startsWith(\"/\") ? to : \"/\" + to;\n const fullPath = (router._base ? (router._base + appPath) : appPath).replace(/\\/+/g, \"/\");\n const href = router._mode === \"hash\" ? \"#\" + fullPath : fullPath;\n return html`\n <a href=${href} style=${() => router.current.value === appPath\n ? \"color:#38bdf8;font-weight:700;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px;background:#0c2a3a\"\n : \"color:#a3a3a3;text-decoration:none;cursor:pointer;padding:4px 10px;border-radius:4px\"} @click=${(e: Event) => { e.preventDefault(); router.navigate(to); }}>${label}</a>\n `;\n }\n}\n\nexport interface _RouterDebugInternal {\n mode: RouterMode;\n base: string;\n currentPath: string;\n params: Record<string, string>;\n query: Record<string, string>;\n matchedPath: string | null;\n activeGuards: { globalCount: number; hasRouteGuard: boolean; names: string[] };\n}\n\nexport function _debugGetRouterInternal(): _RouterDebugInternal | null {\n // Prefer the most recently mounted injected router over the global singleton\n // so devtools inspect the router actually active in the current UI.\n const router = _mountedRouters.length\n ? _mountedRouters[_mountedRouters.length - 1]\n : _currentRouter;\n if (!router) return null;\n const internal = router as RouterInternal;\n const currentPath = internal.current.value;\n const matched = matchFlat(currentPath, internal._flat);\n const routeGuard = matched?.route.beforeEnter;\n const names = internal._guards.map((g, idx) => g.name || `beforeEach#${idx + 1}`);\n if (routeGuard) names.push(routeGuard.name || \"beforeEnter\");\n return {\n mode: internal._mode,\n base: internal._base || \"/\",\n currentPath,\n params: { ...internal.params.value },\n query: { ...internal.query.value },\n matchedPath: matched?.route.fullPath ?? null,\n activeGuards: {\n globalCount: internal._guards.length,\n hasRouteGuard: Boolean(routeGuard),\n names,\n },\n };\n}"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./elur/template/types.cjs`),t=require(`./lifecycle.cjs`),n=require(`./context.cjs`),r=require(`./elur/template/keyed.cjs`),i=require(`./elur/template/sanitize.cjs`);let a=require(`node:async_hooks`);var o=e.isElurTemplate,s=e.isKeyedList,c=new a.AsyncLocalStorage;n._setContextScopeResolver(()=>c.getStore());function l(e={}){let t=new AbortController,n={markers:e.markers===`hydration`,context:e.context,onError:e.onError},r=e=>Promise.resolve(c.run([],e));return{signal:t.signal,async render(e,i){let a={...n,markers:i?.markers??n.markers,signal:t.signal},o=``;return await r(()=>(async()=>{for await(let t of p(e,a))o+=t.value})()),o},renderToChunks(e,r){return u(e,{...n,markers:r?.markers??n.markers,signal:t.signal})},abort(e){t.abort(e)}}}function u(e,t){return{async*[Symbol.asyncIterator](){let n=[],r=[],i=!1,a,o=()=>{r.shift()?.()},s=c.run([],async()=>{try{for await(let r of p(e,t))n.push(r),o()}catch(e){a=e}finally{i=!0,o()}});for(;;){for(;n.length===0&&!i;)await new Promise(e=>r.push(e));if(n.length===0)break;let e=n.shift();e&&(yield e)}if(a)throw a;yield{type:`done`,value:``,index:-1},await s}}}async function d(e,t={}){let n={markers:t.markers===`hydration`,signal:t.signal,context:t.context,onError:t.onError},r=``;return await Promise.resolve(c.run([],async()=>{for await(let t of p(e,n))r+=t.value})),r}function f(e,t={}){return u(e,{markers:t.markers===`hydration`,signal:t.signal,context:t.context,onError:t.onError})}async function*p(n,i){if(_(i),n instanceof Promise){yield*p(await n,i);return}if(!(n==null||n===!1||n===!0)){if(typeof n==`string`||typeof n==`number`||typeof n==`bigint`){yield{type:`markup`,value:b(String(n)),index:-1};return}if(Array.isArray(n)){let e=await Promise.all(n.map(e=>v(e)));_(i);for(let t of e)i.markers&&(yield{type:`markup`,value:`<!--elur-ai-->`,index:-1}),yield*p(t,i),i.markers&&(yield{type:`markup`,value:`<!--elur-aiend-->`,index:-1});return}if((typeof n==`object`||typeof n==`function`)&&n!==null){let t=n[e.ELUR_RENDER_PROTOCOL];if(t?.renderServer){let e=await t.renderServer({markers:i.markers,signal:i.signal,context:i.context,render:(e,t)=>g(e,{...i,markers:t?.markers??i.markers})});_(i),yield{type:`markup`,value:e,index:-1};return}}if(t.isElurComponent(n)){yield*m(n,i);return}if(s(n)){if(!i.markers){yield{type:`markup`,value:(await Promise.all(n.items.map((e,t)=>g(n.renderFn(e,t),i)))).join(``),index:-1};return}let e=new Set;for(let t=0;t<n.items.length;t++){let a=n.items[t],o=r.normalizeRepeatKey(n.keyFn(a,t),t),s=r.serializeRepeatKey(o);e.has(s)&&console.warn(`[elur] repeat(): duplicate key "${o}" during server render. Keys must be unique; entries after the first will leak during hydration.`),e.add(s),yield{type:`markup`,value:`<!--elur-ki:${s}-->`,index:-1},yield*p(n.renderFn(a,t),i),yield{type:`markup`,value:`<!--elur-ke-->`,index:-1}}return}if(o(n)){let t=n[e.ELUR_TEMPLATE_DESCRIPTOR];if(!t)throw TypeError(`[elur] Template does not support server rendering`);yield*h(t,i);return}yield{type:`markup`,value:b(String(n)),index:-1}}}async function*m(e,t){n._pushComponentContext();let r={index:-1,context:`component`,cause:void 0,component:e._debugName};try{try{e.onInit?.(),e.onServerRender?.()}catch(t){if(e.onError)e.onError(t);else throw t}try{yield*p(e.render(),t)}catch(t){if(e.onError){e.onError(t);return}throw t}}catch(e){throw t.onError?.(e,{...r,cause:e}),e}finally{n._popComponentContext()}}async function*h(e,t){let n=new Uint8Array(e.strings.length);for(let r=0;r<e.strings.length;r++){if(t.signal?.aborted)throw t.signal.reason??new DOMException(`Render aborted`,`AbortError`);let a=e.strings[r];if(n[r]===1&&(a[0]===`"`||a[0]===`'`)&&(a=a.slice(1)),r>=e.contexts.length){yield{type:`markup`,value:a,index:r};continue}let o=e.contexts[r],s=e.values[r];if(o.type===`node`){yield{type:`markup`,value:a,index:r},t.markers&&(yield{type:`boundary-start`,value:`<!--elur-${r}-->`,index:r});try{let e=await v(s);_(t),yield*p(e,t)}catch(e){throw t.onError?.(e,{index:r,context:`node`,cause:e}),e}t.markers&&(yield{type:`boundary-end`,value:`<!--elur-end-${r}-->`,index:r});continue}let c=y(o),l=a.slice(0,-c);if(o.hadOpenQuote&&(n[r+1]=1),o.type===`event`){t.markers?yield{type:`markup`,value:`${l}${/\s$/.test(l)?``:` `}data-elur-e-${r}="${x(o.eventName)}"`,index:r}:yield{type:`markup`,value:l.replace(/\s+$/,``),index:r};continue}yield{type:`markup`,value:l,index:r};let u=await v(s);if(_(t),o.attrName!==`ref`&&u!=null&&u!==!1){let e=o.url?i.sanitizeUrl(String(u)):String(u);yield{type:`markup`,value:`${/\s$/.test(l)?``:` `}${o.attrName}="${x(e)}"`,index:r}}t.markers&&(yield{type:`markup`,value:` data-elur-a-${r}="${x(o.attrName)}"`,index:r})}}async function g(e,t){let n=``;for await(let r of p(e,t))n+=r.value;return n}function _(e){if(e.signal?.aborted)throw e.signal.reason??new DOMException(`Render aborted`,`AbortError`)}async function v(e){let t=typeof e==`function`?e():e;return t instanceof Promise?await t:t}function y(e){return e.type===`event`?`@${e.modifiers.length?`${e.eventName}.${e.modifiers.join(`.`)}`:e.eventName}=`.length+ +!!e.hadOpenQuote:`${e.attrName}=`.length+ +!!e.hadOpenQuote}function b(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`)}function x(e){return b(e).replace(/"/g,`"`)}exports.createServerRenderScope=l,exports.renderToChunks=f,exports.renderToString=d;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./elur/template/types.cjs`),t=require(`./lifecycle.cjs`),n=require(`./context.cjs`),r=require(`./elur/template/keyed.cjs`),i=require(`./elur/template/sanitize.cjs`);let a=require(`node:async_hooks`);var o=e.isElurTemplate,s=e.isKeyedList,c=new a.AsyncLocalStorage;n._setContextScopeResolver(()=>c.getStore());function l(e={}){let t=new AbortController,n={markers:e.markers===`hydration`,context:e.context,onError:e.onError},r=e=>Promise.resolve(c.run([],e));return{signal:t.signal,async render(e,i){let a={...n,markers:i?.markers??n.markers,signal:t.signal},o=``;return await r(()=>(async()=>{for await(let t of p(e,a))o+=t.value})()),o},renderToChunks(e,r){return u(e,{...n,markers:r?.markers??n.markers,signal:t.signal})},abort(e){t.abort(e)}}}function u(e,t){return{async*[Symbol.asyncIterator](){let n=[],r=[],i=!1,a,o=()=>{r.shift()?.()},s=c.run([],async()=>{try{for await(let r of p(e,t))n.push(r),o()}catch(e){a=e}finally{i=!0,o()}});for(;;){for(;n.length===0&&!i;)await new Promise(e=>r.push(e));if(n.length===0)break;let e=n.shift();e&&(yield e)}if(a)throw yield{type:`error`,value:a instanceof Error?a.message:String(a),index:-1},a;yield{type:`done`,value:``,index:-1},await s}}}async function d(e,t={}){let n={markers:t.markers===`hydration`,signal:t.signal,context:t.context,onError:t.onError},r=``;return await Promise.resolve(c.run([],async()=>{for await(let t of p(e,n))r+=t.value})),r}function f(e,t={}){return u(e,{markers:t.markers===`hydration`,signal:t.signal,context:t.context,onError:t.onError})}async function*p(n,i){if(_(i),n instanceof Promise){yield*p(await n,i);return}if(!(n==null||n===!1||n===!0)){if(typeof n==`string`||typeof n==`number`||typeof n==`bigint`){yield{type:`markup`,value:b(String(n)),index:-1};return}if(Array.isArray(n)){let e=await Promise.all(n.map(e=>v(e)));_(i);for(let t of e)i.markers&&(yield{type:`markup`,value:`<!--elur-ai-->`,index:-1}),yield*p(t,i),i.markers&&(yield{type:`markup`,value:`<!--elur-aiend-->`,index:-1});return}if((typeof n==`object`||typeof n==`function`)&&n!==null){let t=n[e.ELUR_RENDER_PROTOCOL];if(t?.renderServer){let e=await t.renderServer({markers:i.markers,signal:i.signal,context:i.context,render:(e,t)=>g(e,{...i,markers:t?.markers??i.markers})});_(i),yield{type:`markup`,value:e,index:-1};return}}if(t.isElurComponent(n)){yield*m(n,i);return}if(s(n)){if(!i.markers){yield{type:`markup`,value:(await Promise.all(n.items.map((e,t)=>g(n.renderFn(e,t),i)))).join(``),index:-1};return}let e=new Set;for(let t=0;t<n.items.length;t++){let a=n.items[t],o=r.normalizeRepeatKey(n.keyFn(a,t),t),s=r.serializeRepeatKey(o);e.has(s)&&console.warn(`[elur] repeat(): duplicate key "${o}" during server render. Keys must be unique; entries after the first will leak during hydration.`),e.add(s),yield{type:`markup`,value:`<!--elur-ki:${s}-->`,index:-1},yield*p(n.renderFn(a,t),i),yield{type:`markup`,value:`<!--elur-ke-->`,index:-1}}return}if(o(n)){let t=n[e.ELUR_TEMPLATE_DESCRIPTOR];if(!t)throw TypeError(`[elur] Template does not support server rendering`);yield*h(t,i);return}yield{type:`markup`,value:b(String(n)),index:-1}}}async function*m(e,t){n._pushComponentContext();let r={index:-1,context:`component`,cause:void 0,component:e._debugName};try{try{e.onInit?.(),e.onServerRender?.()}catch(t){if(e.onError)e.onError(t);else throw t}try{yield*p(e.render(),t)}catch(t){if(e.onError){e.onError(t);return}throw t}}catch(e){throw t.onError?.(e,{...r,cause:e}),e}finally{n._popComponentContext()}}async function*h(e,t){let n=new Uint8Array(e.strings.length);for(let r=0;r<e.strings.length;r++){if(t.signal?.aborted)throw t.signal.reason??new DOMException(`Render aborted`,`AbortError`);let a=e.strings[r];if(n[r]===1&&(a[0]===`"`||a[0]===`'`)&&(a=a.slice(1)),r>=e.contexts.length){yield{type:`markup`,value:a,index:r};continue}let o=e.contexts[r],s=e.values[r];if(o.type===`node`){yield{type:`markup`,value:a,index:r},t.markers&&(yield{type:`boundary-start`,value:`<!--elur-${r}-->`,index:r});try{let e=await v(s);_(t),yield*p(e,t)}catch(e){throw t.onError?.(e,{index:r,context:`node`,cause:e}),e}t.markers&&(yield{type:`boundary-end`,value:`<!--elur-end-${r}-->`,index:r});continue}let c=y(o),l=a.slice(0,-c);if(o.hadOpenQuote&&(n[r+1]=1),o.type===`event`){t.markers?yield{type:`markup`,value:`${l}${/\s$/.test(l)?``:` `}data-elur-e-${r}="${x(o.eventName)}"`,index:r}:yield{type:`markup`,value:l.replace(/\s+$/,``),index:r};continue}yield{type:`markup`,value:l,index:r};let u=await v(s);if(_(t),o.attrName!==`ref`&&u!=null&&u!==!1){let e=o.url?i.sanitizeUrl(String(u)):String(u);yield{type:`markup`,value:`${/\s$/.test(l)?``:` `}${o.attrName}="${x(e)}"`,index:r}}t.markers&&(yield{type:`markup`,value:` data-elur-a-${r}="${x(o.attrName)}"`,index:r})}}async function g(e,t){let n=``;for await(let r of p(e,t))n+=r.value;return n}function _(e){if(e.signal?.aborted)throw e.signal.reason??new DOMException(`Render aborted`,`AbortError`)}async function v(e){let t=typeof e==`function`?e():e;return t instanceof Promise?await t:t}function y(e){return e.type===`event`?`@${e.modifiers.length?`${e.eventName}.${e.modifiers.join(`.`)}`:e.eventName}=`.length+ +!!e.hadOpenQuote:`${e.attrName}=`.length+ +!!e.hadOpenQuote}function b(e){return e.replace(/&/g,`&`).replace(/</g,`<`).replace(/>/g,`>`)}function x(e){return b(e).replace(/"/g,`"`)}exports.createServerRenderScope=l,exports.renderToChunks=f,exports.renderToString=d;
|
|
2
2
|
//# sourceMappingURL=server.cjs.map
|