@real-router/navigation-plugin 0.1.0 → 0.2.0

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["safelyEncodePath","extractPath","extractPath","extractPath","RouterError","shouldReplaceHistory","#router","#api","#options","#browser","#removeStartInterceptor","#removeExtensions","#lifecycle","#metaByState","buildUrl","urlToPath","#isSyncingFromRouter","#pendingMeta","extractPath","#pendingTraverseKey","UNKNOWN_ROUTE","createWarnOnce","createOptionsValidator","isBrowserEnvironment","normalizeBase"],"sources":["../../../browser-env/dist/esm/index.mjs","../../src/constants.ts","../../src/navigation-browser.ts","../../src/history-extensions.ts","../../src/navigate-handler.ts","../../src/plugin-utils.ts","../../src/plugin.ts","../../src/ssr-fallback.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["import{isStateStrict as e}from\"type-guards\";import{RouterError as t}from\"@real-router/core\";const n=()=>globalThis.window!==void 0&&!!globalThis.history,r=(e,t)=>{globalThis.history.pushState(e,``,t)},i=(e,t)=>{globalThis.history.replaceState(e,``,t)},a=e=>(globalThis.addEventListener(`popstate`,e),()=>{globalThis.removeEventListener(`popstate`,e)}),o=()=>globalThis.location.hash;function s(e){if(!e)return e;let t=e;return t.startsWith(`/`)||(t=`/${t}`),t.endsWith(`/`)&&(t=t.slice(0,-1)),t}const c=e=>{try{return encodeURI(decodeURI(e))}catch(t){return console.warn(`[browser-env] Could not encode path \"${e}\"`,t),e}},l=()=>{},u=e=>{let t=!1;return n=>{t||=(console.warn(`[browser-env] Browser API is running in a non-browser environment (context: \"${e}\"). Method \"${n}\" is a no-op. This is expected for SSR, but may indicate misconfiguration if you expected browser behavior.`),!0)}},d=e=>{let t=u(e);return{pushState:()=>{t(`pushState`)},replaceState:()=>{t(`replaceState`)},addPopstateListener:()=>(t(`addPopstateListener`),l),getHash:()=>(t(`getHash`),``)}};function f(t,n,r){if(e(t.state))return{name:t.state.name,params:t.state.params};let i=n.matchPath(r.getLocation());return i?{name:i.name,params:i.params}:void 0}function p(e,t,n,r){let i={name:e.name,params:e.params,path:e.path};n?r.replaceState(i,t):r.pushState(i,t)}function m(e,t){return n=>{if(n){for(let r of Object.keys(n))if(r in e){let i=n[r],a=typeof e[r],o=typeof i;if(i!==void 0&&o!==a)throw Error(`[${t}] Invalid type for '${r}': expected ${a}, got ${o}`)}}}}function h(e,t){if(n())return{pushState:r,replaceState:i,addPopstateListener:a,getLocation:e,getHash:o};let s=u(t);return{...d(t),getLocation:()=>(s(`getLocation`),``)}}function g(e){let n=!1,r=null;function i(){if(r){let t=r;r=null,console.warn(`[${e.loggerContext}] Processing deferred popstate event`),o(t)}}function a(t){console.error(`[${e.loggerContext}] Critical error in onPopState`,t);try{let t=e.router.getState();if(t){let n=e.buildUrl(t.name,t.params);e.browser.replaceState(t,n)}}catch(t){console.error(`[${e.loggerContext}] Failed to recover from critical error`,t)}}async function o(o){if(n){console.warn(`[${e.loggerContext}] Transition in progress, deferring popstate event`),r=o;return}n=!0;try{let t=f(o,e.api,e.browser);t?await e.router.navigate(t.name,t.params,e.transitionOptions):e.allowNotFound?e.router.navigateToNotFound(e.browser.getLocation()):await e.router.navigateToDefault({...e.transitionOptions,reload:!0,replace:!0})}catch(e){e instanceof t||a(e)}finally{n=!1,i()}}return e=>void o(e)}function _(e){return{onStart:()=>{e.shared.removePopStateListener&&e.shared.removePopStateListener(),e.shared.removePopStateListener=e.browser.addPopstateListener(e.handler)},onStop:()=>{e.shared.removePopStateListener&&(e.shared.removePopStateListener(),e.shared.removePopStateListener=void 0)},teardown:()=>{e.shared.removePopStateListener&&(e.shared.removePopStateListener(),e.shared.removePopStateListener=void 0),e.cleanup()}}}function v(e,t){return e.addInterceptor(`start`,(e,n)=>e(n??t.getLocation()))}function y(e,t,n,r){return(i,a={})=>{let o=e.buildState(i,a);if(!o)throw Error(`[real-router] Cannot replace state: route \"${i}\" is not found`);p(e.makeState(o.name,o.params,t.buildPath(o.name,o.params),{params:o.meta}),r(i,a),!0,n)}}function b(e,t,n){return(e.replace??!n)||!!e.reload&&t.path===n.path}function x(e,t){try{let n=new URL(e,globalThis.location.origin);return[`http:`,`https:`].includes(n.protocol)?n:(console.warn(`[${t}] Invalid URL protocol in ${e}`),null)}catch(n){return console.warn(`[${t}] Could not parse url ${e}`,n),null}}function S(e,t){if(t&&e.startsWith(t)){let n=e.slice(t.length);return n.startsWith(`/`)?n:`/${n}`}return e}function C(e,t){return t+e}function w(e,t,n){let r=x(e,n);return r?S(r.pathname,t)+r.search:null}export{a as addPopstateListener,C as buildUrl,d as createHistoryFallbackBrowser,m as createOptionsValidator,g as createPopstateHandler,_ as createPopstateLifecycle,y as createReplaceHistoryState,h as createSafeBrowser,v as createStartInterceptor,u as createWarnOnce,S as extractPath,o as getHash,f as getRouteFromEvent,n as isBrowserEnvironment,s as normalizeBase,r as pushState,i as replaceState,x as safeParseUrl,c as safelyEncodePath,b as shouldReplaceHistory,p as updateBrowserState,w as urlToPath};\n//# sourceMappingURL=index.mjs.map","import type { NavigationPluginOptions } from \"./types\";\n\nexport const defaultOptions: Required<NavigationPluginOptions> = {\n forceDeactivate: true,\n base: \"\",\n};\n\n/**\n * Source identifier for transitions triggered by navigate events.\n * Distinguishes browser-initiated navigation (back/forward, link clicks)\n * from programmatic navigation (router.navigate()).\n */\nexport const source = \"navigate\";\n\nexport const LOGGER_CONTEXT = \"navigation-plugin\";\n","import { safelyEncodePath, extractPath } from \"browser-env\";\n\nimport type { NavigationBrowser } from \"./types\";\n\n/**\n * Creates a NavigationBrowser wrapping the real Navigation API.\n * Only call this when `\"navigation\" in globalThis` is true.\n */\nexport function createNavigationBrowser(base: string): NavigationBrowser {\n const nav = globalThis.navigation;\n\n return {\n getLocation: () =>\n safelyEncodePath(extractPath(globalThis.location.pathname, base)) +\n globalThis.location.search,\n\n getHash: () => globalThis.location.hash,\n\n navigate: (url, options) => {\n nav.navigate(url, {\n state: options.state,\n history: options.history,\n });\n },\n\n replaceState: (state, url) => {\n nav.navigate(url, {\n state,\n history: \"replace\",\n });\n },\n\n updateCurrentEntry: (options) => {\n nav.updateCurrentEntry(options);\n },\n\n traverseTo: (key) => {\n nav.traverseTo(key);\n },\n\n addNavigateListener: (fn) => {\n nav.addEventListener(\"navigate\", fn);\n\n return () => {\n nav.removeEventListener(\"navigate\", fn);\n };\n },\n\n entries: () => nav.entries(),\n\n get currentEntry() {\n return nav.currentEntry;\n },\n };\n}\n","import { extractPath } from \"browser-env\";\n\nimport type { NavigationBrowser } from \"./types\";\nimport type { State } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Converts a NavigationHistoryEntry to a State via URL matching.\n * Uses URL matching (not entry.getState()) because:\n * - Entries before plugin init have no state\n * - Entries after router.replace(routes) may have stale state\n * - Entries from other SPAs on the same origin have foreign state\n */\nexport function entryToState(\n entry: NavigationHistoryEntry | undefined,\n api: PluginApi,\n base: string,\n): State | undefined {\n if (!entry?.url) {\n return undefined;\n }\n\n const pathname = new URL(entry.url).pathname;\n const path = extractPath(pathname, base);\n\n return api.matchPath(path) ?? undefined;\n}\n\nfunction peekAt(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n offset: number,\n): State | undefined {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return undefined;\n }\n\n return entryToState(browser.entries()[idx + offset], api, base);\n}\n\nexport function peekBack(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, -1);\n}\n\nexport function peekForward(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, 1);\n}\n\nexport function hasVisited(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n return browser.entries().some((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n });\n}\n\nexport function getVisitedRoutes(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): string[] {\n const names = new Set<string>();\n\n for (const entry of browser.entries()) {\n const state = entryToState(entry, api, base);\n\n if (state) {\n names.add(state.name);\n }\n }\n\n return [...names];\n}\n\nexport function getRouteVisitCount(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): number {\n return browser.entries().filter((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n }).length;\n}\n\n/**\n * Finds the last NavigationHistoryEntry matching the given route name,\n * excluding the current entry (to avoid SAME_STATES on traverseToLast(\"current-route\")).\n */\nexport function findLastEntryForRoute(\n entries: NavigationHistoryEntry[],\n routeName: string,\n api: PluginApi,\n base: string,\n currentKey: string | undefined,\n): NavigationHistoryEntry | undefined {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n\n if (entry.key === currentKey) {\n continue;\n }\n\n const state = entryToState(entry, api, base);\n\n if (state?.name === routeName) {\n return entry;\n }\n }\n\n return undefined;\n}\n\nexport function canGoBack(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n return idx != null && idx > 0;\n}\n\nexport function canGoForward(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n return idx < browser.entries().length - 1;\n}\n\nexport function canGoBackTo(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n const entries = browser.entries();\n\n for (let i = idx - 1; i >= 0; i--) {\n const state = entryToState(entries[i], api, base);\n\n if (state?.name === routeName) {\n return true;\n }\n }\n\n return false;\n}\n","import { RouterError } from \"@real-router/core\";\nimport { extractPath } from \"browser-env\";\n\nimport type { NavigationBrowser, NavigationMeta } from \"./types\";\nimport type { Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\ninterface NavigateHandlerDeps {\n router: Router;\n api: PluginApi;\n browser: NavigationBrowser;\n isSyncingFromRouter: () => boolean;\n setSyncing: (value: boolean) => void;\n setPendingMeta: (meta: NavigationMeta) => void;\n base: string;\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n };\n}\n\nexport function createNavigateHandler(deps: NavigateHandlerDeps) {\n const {\n router,\n api,\n browser,\n isSyncingFromRouter,\n setSyncing,\n base,\n transitionOptions,\n } = deps;\n const { allowNotFound } = api.getOptions();\n\n return function handleNavigateEvent(event: NavigateEvent): void {\n if (!event.canIntercept) {\n return;\n }\n if (isSyncingFromRouter()) {\n return;\n }\n if (!router.isActive()) {\n return;\n }\n\n const destinationUrl = new URL(event.destination.url);\n const path =\n extractPath(destinationUrl.pathname, base) + destinationUrl.search;\n const matchedState = api.matchPath(path);\n\n // Set pendingMeta BEFORE event.intercept() — available in guards via getNavigationMeta()\n deps.setPendingMeta({\n navigationType: event.navigationType as NavigationMeta[\"navigationType\"],\n userInitiated: event.userInitiated,\n info: event.info,\n });\n\n if (matchedState) {\n event.intercept({\n handler: async () => {\n try {\n await router.navigate(matchedState.name, matchedState.params, {\n ...transitionOptions,\n signal: event.signal,\n });\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n } else if (allowNotFound) {\n event.intercept({\n handler: () => {\n router.navigateToNotFound(path);\n },\n });\n } else {\n event.intercept({\n handler: async () => {\n try {\n await router.navigateToDefault();\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n }\n };\n}\n\nfunction recoverFromNavigateError(\n error: unknown,\n router: Router,\n browser: NavigationBrowser,\n setSyncing: (value: boolean) => void,\n): void {\n console.error(\n \"[navigation-plugin] Critical error in navigate handler\",\n error,\n );\n\n try {\n const currentState = router.getState();\n\n if (currentState) {\n const url = router.buildUrl(currentState.name, currentState.params);\n\n setSyncing(true);\n browser.navigate(url, {\n state: {\n name: currentState.name,\n params: currentState.params,\n path: currentState.path,\n },\n history: \"replace\",\n });\n setSyncing(false);\n }\n } catch (recoveryError) {\n console.error(\n \"[navigation-plugin] Failed to recover from critical error\",\n recoveryError,\n );\n }\n}\n","import type { NavigationBrowser } from \"./types\";\nimport type { Params, Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Makes `router.start()` path optional by injecting browser location.\n * Identical to browser-env's createStartInterceptor, adapted for NavigationBrowser.\n */\nexport function createStartInterceptor(\n api: PluginApi,\n browser: NavigationBrowser,\n): () => void {\n return api.addInterceptor(\"start\", (next, path) =>\n next(path ?? browser.getLocation()),\n );\n}\n\n/**\n * Creates replaceHistoryState extension for NavigationBrowser.\n *\n * IMPORTANT: Must set isSyncingFromRouter=true before calling browser.replaceState\n * because navigation.navigate({history:\"replace\"}) fires a navigate event.\n * Without this flag, the navigate handler would trigger a full navigation.\n */\nexport function createReplaceHistoryState(\n api: PluginApi,\n router: Router,\n browser: NavigationBrowser,\n buildUrl: (name: string, params?: Params) => string,\n setSyncing: (value: boolean) => void,\n): (name: string, params?: Params) => void {\n return (name: string, params: Params = {}) => {\n const state = api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n );\n\n const url = buildUrl(name, params);\n const historyState = {\n name: builtState.name,\n params: builtState.params,\n path: builtState.path,\n };\n\n setSyncing(true);\n browser.replaceState(historyState, url);\n setSyncing(false);\n };\n}\n","import { UNKNOWN_ROUTE } from \"@real-router/core\";\nimport {\n shouldReplaceHistory,\n buildUrl,\n extractPath,\n urlToPath,\n} from \"browser-env\";\n\nimport { LOGGER_CONTEXT } from \"./constants\";\nimport {\n peekBack,\n peekForward,\n hasVisited,\n getVisitedRoutes,\n getRouteVisitCount,\n findLastEntryForRoute,\n canGoBack,\n canGoForward,\n canGoBackTo,\n} from \"./history-extensions\";\nimport { createNavigateHandler } from \"./navigate-handler\";\nimport {\n createStartInterceptor,\n createReplaceHistoryState,\n} from \"./plugin-utils\";\n\nimport type {\n NavigationBrowser,\n NavigationMeta,\n NavigationPluginOptions,\n NavigationSharedState,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Params,\n Router,\n State,\n Plugin,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nfunction deriveNavigationType(\n navOptions: NavigationOptions,\n toState: State,\n fromState: State | undefined,\n): NavigationMeta[\"navigationType\"] {\n if (navOptions.reload && toState.path === fromState?.path) {\n return \"reload\";\n }\n\n if (shouldReplaceHistory(navOptions, toState, fromState)) {\n return \"replace\";\n }\n\n return \"push\";\n}\n\nexport class NavigationPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #options: Required<NavigationPluginOptions>;\n readonly #browser: NavigationBrowser;\n readonly #removeStartInterceptor: () => void;\n readonly #removeExtensions: () => void;\n readonly #lifecycle: Pick<Plugin, \"onStart\" | \"onStop\" | \"teardown\">;\n\n #isSyncingFromRouter = false;\n readonly #metaByState = new WeakMap<State, NavigationMeta>();\n #pendingMeta: NavigationMeta | undefined;\n #pendingTraverseKey: string | undefined;\n\n constructor(\n router: Router,\n api: PluginApi,\n options: Required<NavigationPluginOptions>,\n browser: NavigationBrowser,\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n },\n shared: NavigationSharedState,\n ) {\n this.#router = router;\n this.#api = api;\n this.#options = options;\n this.#browser = browser;\n\n this.#removeStartInterceptor = createStartInterceptor(api, browser);\n\n const pluginBuildUrl = (route: string, params?: Params) => {\n const path = router.buildPath(route, params);\n\n return buildUrl(path, options.base);\n };\n\n this.#removeExtensions = api.extendRouter({\n buildUrl: pluginBuildUrl,\n matchUrl: (url: string) => {\n const path = urlToPath(url, options.base, LOGGER_CONTEXT);\n\n return path ? api.matchPath(path) : undefined;\n },\n replaceHistoryState: createReplaceHistoryState(\n api,\n router,\n browser,\n pluginBuildUrl,\n (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n ),\n\n peekBack: () => peekBack(browser, api, options.base),\n peekForward: () => peekForward(browser, api, options.base),\n hasVisited: (routeName: string) =>\n hasVisited(browser, api, options.base, routeName),\n getVisitedRoutes: () => getVisitedRoutes(browser, api, options.base),\n getRouteVisitCount: (routeName: string) =>\n getRouteVisitCount(browser, api, options.base, routeName),\n traverseToLast: (routeName: string) => this.traverseToLast(routeName),\n getNavigationMeta: (state?: State): NavigationMeta | undefined => {\n if (!state) {\n return this.#pendingMeta;\n }\n\n return this.#metaByState.get(state);\n },\n canGoBack: () => canGoBack(browser),\n canGoForward: () => canGoForward(browser),\n canGoBackTo: (routeName: string) =>\n canGoBackTo(browser, api, options.base, routeName),\n });\n\n const handler = createNavigateHandler({\n router,\n api,\n browser,\n isSyncingFromRouter: () => this.#isSyncingFromRouter,\n setSyncing: (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n setPendingMeta: (meta) => {\n this.#pendingMeta = meta;\n },\n base: options.base,\n transitionOptions,\n });\n\n this.#lifecycle = createNavigateLifecycle({\n browser,\n shared,\n handler,\n removeStartInterceptor: this.#removeStartInterceptor,\n removeExtensions: this.#removeExtensions,\n });\n }\n\n async traverseToLast(routeName: string): Promise<State> {\n const entries = this.#browser.entries();\n const currentKey = this.#browser.currentEntry?.key;\n const entry = findLastEntryForRoute(\n entries,\n routeName,\n this.#api,\n this.#options.base,\n currentKey,\n );\n\n if (!entry) {\n throw new Error(`No history entry for route \"${routeName}\"`);\n }\n\n if (!entry.url) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n const parsedUrl = new URL(entry.url);\n const path =\n extractPath(parsedUrl.pathname, this.#options.base) + parsedUrl.search;\n const matchedState = this.#api.matchPath(path);\n\n if (!matchedState) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n this.#pendingMeta = {\n navigationType: \"traverse\",\n userInitiated: false,\n };\n this.#pendingTraverseKey = entry.key;\n\n return this.#router.navigate(matchedState.name, matchedState.params);\n }\n\n getPlugin(): Plugin {\n return {\n ...this.#lifecycle,\n\n onTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n navOptions: NavigationOptions,\n ) => {\n if (!this.#pendingMeta) {\n this.#pendingMeta = {\n navigationType: deriveNavigationType(\n navOptions,\n toState,\n fromState,\n ),\n userInitiated: false,\n };\n }\n\n this.#metaByState.set(toState, this.#pendingMeta);\n this.#pendingMeta = undefined;\n\n this.#isSyncingFromRouter = true;\n\n if (this.#pendingTraverseKey) {\n this.#browser.traverseTo(this.#pendingTraverseKey);\n this.#pendingTraverseKey = undefined;\n } else {\n const url = this.#router.buildUrl(toState.name, toState.params);\n const shouldPreserveHash =\n !fromState || fromState.path === toState.path;\n const finalUrl = shouldPreserveHash\n ? url + this.#browser.getHash()\n : url;\n const historyState = {\n name: toState.name,\n params: toState.params,\n path: toState.path,\n };\n\n if (toState.name === UNKNOWN_ROUTE) {\n this.#browser.updateCurrentEntry({ state: historyState });\n } else {\n const replace = shouldReplaceHistory(\n navOptions,\n toState,\n fromState,\n );\n\n this.#browser.navigate(finalUrl, {\n state: historyState,\n history: replace ? \"replace\" : \"push\",\n });\n }\n }\n\n this.#isSyncingFromRouter = false;\n },\n\n onTransitionCancel: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n\n onTransitionError: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n };\n }\n}\n\ninterface NavigateLifecycleDeps {\n browser: NavigationBrowser;\n handler: (event: NavigateEvent) => void;\n removeStartInterceptor: () => void;\n removeExtensions: () => void;\n shared: NavigationSharedState;\n}\n\nfunction createNavigateLifecycle(deps: NavigateLifecycleDeps): Plugin {\n return {\n onStart() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = deps.browser.addNavigateListener(\n deps.handler,\n );\n },\n\n onStop() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n },\n\n teardown() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n deps.removeStartInterceptor();\n deps.removeExtensions();\n },\n };\n}\n","import { createWarnOnce } from \"browser-env\";\n\nimport type { NavigationBrowser } from \"./types\";\n\nconst NOOP = (): void => {};\n\nexport const createNavigationFallbackBrowser = (\n context: string,\n): NavigationBrowser => {\n const warnOnce = createWarnOnce(context);\n\n return {\n getLocation: () => {\n warnOnce(\"getLocation\");\n\n return \"/\";\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n navigate: () => {\n warnOnce(\"navigate\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n updateCurrentEntry: () => {\n warnOnce(\"updateCurrentEntry\");\n },\n traverseTo: () => {\n warnOnce(\"traverseTo\");\n },\n addNavigateListener: () => {\n warnOnce(\"addNavigateListener\");\n\n return NOOP;\n },\n entries: () => {\n warnOnce(\"entries\");\n\n return [];\n },\n currentEntry: null,\n };\n};\n","import { createOptionsValidator } from \"browser-env\";\n\nimport { LOGGER_CONTEXT, defaultOptions } from \"./constants\";\n\nimport type { NavigationPluginOptions } from \"./types\";\n\nexport const validateOptions = createOptionsValidator<NavigationPluginOptions>(\n defaultOptions,\n LOGGER_CONTEXT,\n);\n","import { getPluginApi } from \"@real-router/core/api\";\nimport { isBrowserEnvironment, normalizeBase } from \"browser-env\";\n\nimport { defaultOptions, source } from \"./constants\";\nimport { createNavigationBrowser } from \"./navigation-browser\";\nimport { NavigationPlugin } from \"./plugin\";\nimport { createNavigationFallbackBrowser } from \"./ssr-fallback\";\nimport { validateOptions } from \"./validation\";\n\nimport type {\n NavigationPluginOptions,\n NavigationBrowser,\n NavigationSharedState,\n} from \"./types\";\nimport type { PluginFactory, Router } from \"@real-router/core\";\n\nexport function navigationPluginFactory(\n opts?: Partial<NavigationPluginOptions>,\n browser?: NavigationBrowser,\n): PluginFactory {\n if (!browser && isBrowserEnvironment() && !(\"navigation\" in globalThis)) {\n throw new Error(\n \"[navigation-plugin] Navigation API is not supported. Use @real-router/browser-plugin instead.\",\n );\n }\n\n validateOptions(opts);\n\n const options: Required<NavigationPluginOptions> = {\n ...defaultOptions,\n ...opts,\n };\n\n options.base = normalizeBase(options.base);\n\n const resolvedBrowser = browser ?? createBrowser(options.base);\n\n const forceDeactivate = options.forceDeactivate;\n const transitionOptions = { forceDeactivate, source, replace: true as const };\n const shared: NavigationSharedState = { removeNavigateListener: undefined };\n\n return (routerBase) => {\n const api = getPluginApi(routerBase);\n\n const plugin = new NavigationPlugin(\n routerBase as Router,\n api,\n options,\n resolvedBrowser,\n transitionOptions,\n shared,\n );\n\n return plugin.getPlugin();\n };\n}\n\nfunction createBrowser(base: string): NavigationBrowser {\n if (\"navigation\" in globalThis) {\n return createNavigationBrowser(base);\n }\n\n return createNavigationFallbackBrowser(\"navigation-plugin\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["RouterError","#router","#api","#options","#browser","#removeStartInterceptor","#removeExtensions","#lifecycle","#metaByState","#isSyncingFromRouter","#pendingMeta","#pendingTraverseKey","UNKNOWN_ROUTE"],"sources":["../../../../shared/browser-env/detect.ts","../../../../shared/browser-env/utils.ts","../../../../shared/browser-env/ssr-fallback.ts","../../../../shared/browser-env/validation.ts","../../../../shared/browser-env/plugin-utils.ts","../../../../shared/browser-env/url-parsing.ts","../../../../shared/browser-env/url-utils.ts","../../src/constants.ts","../../src/navigation-browser.ts","../../src/history-extensions.ts","../../src/navigate-handler.ts","../../src/plugin-utils.ts","../../src/plugin.ts","../../src/ssr-fallback.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["export const isBrowserEnvironment = (): boolean =>\n typeof globalThis.window !== \"undefined\" && !!globalThis.history;\n","/**\n * Normalizes base path: ensures leading slash, removes trailing slash.\n *\n * @example\n * normalizeBase(\"app\") // \"/app\"\n * normalizeBase(\"/app/\") // \"/app\"\n * normalizeBase(\"\") // \"\"\n */\nexport function normalizeBase(base: string): string {\n if (!base) {\n return base;\n }\n\n let result = base;\n\n if (!result.startsWith(\"/\")) {\n result = `/${result}`;\n }\n\n if (result.endsWith(\"/\")) {\n result = result.slice(0, -1);\n }\n\n return result;\n}\n\nexport const safelyEncodePath = (path: string): string => {\n try {\n return encodeURI(decodeURI(path));\n } catch (error) {\n console.warn(`[browser-env] Could not encode path \"${path}\"`, error);\n\n return path;\n }\n};\n","import type { HistoryBrowser } from \"./types.js\";\n\nconst NOOP = (): void => {};\n\nexport const createWarnOnce = (context: string) => {\n let hasWarned = false;\n\n return (method: string): void => {\n if (!hasWarned) {\n console.warn(\n `[browser-env] Browser API is running in a non-browser environment (context: \"${context}\"). ` +\n `Method \"${method}\" is a no-op. ` +\n `This is expected for SSR, but may indicate misconfiguration if you expected browser behavior.`,\n );\n hasWarned = true;\n }\n };\n};\n\nexport const createHistoryFallbackBrowser = (\n context: string,\n): HistoryBrowser => {\n const warnOnce = createWarnOnce(context);\n\n return {\n pushState: () => {\n warnOnce(\"pushState\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n addPopstateListener: () => {\n warnOnce(\"addPopstateListener\");\n\n return NOOP;\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n };\n};\n","export function createOptionsValidator<T extends object>(\n defaults: Required<T>,\n loggerContext: string,\n): (opts: Partial<T> | undefined) => void {\n return (opts) => {\n if (!opts) {\n return;\n }\n\n for (const key of Object.keys(opts)) {\n if (key in defaults) {\n const value = opts[key as keyof typeof opts];\n const expected = typeof defaults[key as keyof typeof defaults];\n const actual = typeof value;\n\n if (value !== undefined && actual !== expected) {\n throw new Error(\n `[${loggerContext}] Invalid type for '${key}': expected ${expected}, got ${actual}`,\n );\n }\n }\n }\n };\n}\n","import { updateBrowserState } from \"./popstate-utils.js\";\n\nimport type { Browser } from \"./types.js\";\nimport type {\n NavigationOptions,\n Params,\n Router,\n State,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nexport function createStartInterceptor(\n api: PluginApi,\n browser: Browser,\n): () => void {\n return api.addInterceptor(\"start\", (next, path) =>\n next(path ?? browser.getLocation()),\n );\n}\n\nexport function createReplaceHistoryState(\n api: PluginApi,\n router: Router,\n browser: Browser,\n buildUrl: (name: string, params?: Params) => string,\n): (name: string, params?: Params) => void {\n return (name: string, params: Params = {}) => {\n const state = api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n );\n\n updateBrowserState(builtState, buildUrl(name, params), true, browser);\n };\n}\n\nexport function shouldReplaceHistory(\n navOptions: NavigationOptions,\n toState: State,\n fromState: State | undefined,\n): boolean {\n return (\n (navOptions.replace ?? !fromState) ||\n (!!navOptions.reload && toState.path === fromState.path)\n );\n}\n","export function safeParseUrl(url: string, loggerContext: string): URL | null {\n try {\n const parsedUrl = new URL(url, globalThis.location.origin);\n\n if (![\"http:\", \"https:\"].includes(parsedUrl.protocol)) {\n console.warn(`[${loggerContext}] Invalid URL protocol in ${url}`);\n\n return null;\n }\n\n return parsedUrl;\n } catch (error) {\n console.warn(`[${loggerContext}] Could not parse url ${url}`, error);\n\n return null;\n }\n}\n","import { safeParseUrl } from \"./url-parsing.js\";\n\nexport function extractPath(pathname: string, base: string): string {\n if (base && pathname.startsWith(base)) {\n const stripped = pathname.slice(base.length);\n\n return stripped.startsWith(\"/\") ? stripped : `/${stripped}`;\n }\n\n return pathname;\n}\n\nexport function buildUrl(path: string, base: string): string {\n return base + path;\n}\n\nexport function urlToPath(\n url: string,\n base: string,\n context: string,\n): string | null {\n const parsedUrl = safeParseUrl(url, context);\n\n return parsedUrl\n ? extractPath(parsedUrl.pathname, base) + parsedUrl.search\n : null;\n}\n","import type { NavigationPluginOptions } from \"./types\";\n\nexport const defaultOptions: Required<NavigationPluginOptions> = {\n forceDeactivate: true,\n base: \"\",\n};\n\n/**\n * Source identifier for transitions triggered by navigate events.\n * Distinguishes browser-initiated navigation (back/forward, link clicks)\n * from programmatic navigation (router.navigate()).\n */\nexport const source = \"navigate\";\n\nexport const LOGGER_CONTEXT = \"navigation-plugin\";\n","import { safelyEncodePath, extractPath } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser } from \"./types\";\n\n/**\n * Creates a NavigationBrowser wrapping the real Navigation API.\n * Only call this when `\"navigation\" in globalThis` is true.\n */\nexport function createNavigationBrowser(base: string): NavigationBrowser {\n const nav = globalThis.navigation;\n\n return {\n getLocation: () =>\n safelyEncodePath(extractPath(globalThis.location.pathname, base)) +\n globalThis.location.search,\n\n getHash: () => globalThis.location.hash,\n\n navigate: (url, options) => {\n nav.navigate(url, {\n state: options.state,\n history: options.history,\n });\n },\n\n replaceState: (state, url) => {\n nav.navigate(url, {\n state,\n history: \"replace\",\n });\n },\n\n updateCurrentEntry: (options) => {\n nav.updateCurrentEntry(options);\n },\n\n traverseTo: (key) => {\n nav.traverseTo(key);\n },\n\n addNavigateListener: (fn) => {\n nav.addEventListener(\"navigate\", fn);\n\n return () => {\n nav.removeEventListener(\"navigate\", fn);\n };\n },\n\n entries: () => nav.entries(),\n\n get currentEntry() {\n return nav.currentEntry;\n },\n };\n}\n","import { extractPath } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser } from \"./types\";\nimport type { State } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Converts a NavigationHistoryEntry to a State via URL matching.\n * Uses URL matching (not entry.getState()) because:\n * - Entries before plugin init have no state\n * - Entries after router.replace(routes) may have stale state\n * - Entries from other SPAs on the same origin have foreign state\n */\nexport function entryToState(\n entry: NavigationHistoryEntry | undefined,\n api: PluginApi,\n base: string,\n): State | undefined {\n if (!entry?.url) {\n return undefined;\n }\n\n const pathname = new URL(entry.url).pathname;\n const path = extractPath(pathname, base);\n\n return api.matchPath(path) ?? undefined;\n}\n\nfunction peekAt(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n offset: number,\n): State | undefined {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return undefined;\n }\n\n return entryToState(browser.entries()[idx + offset], api, base);\n}\n\nexport function peekBack(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, -1);\n}\n\nexport function peekForward(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, 1);\n}\n\nexport function hasVisited(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n return browser.entries().some((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n });\n}\n\nexport function getVisitedRoutes(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): string[] {\n const names = new Set<string>();\n\n for (const entry of browser.entries()) {\n const state = entryToState(entry, api, base);\n\n if (state) {\n names.add(state.name);\n }\n }\n\n return [...names];\n}\n\nexport function getRouteVisitCount(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): number {\n return browser.entries().filter((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n }).length;\n}\n\n/**\n * Finds the last NavigationHistoryEntry matching the given route name,\n * excluding the current entry (to avoid SAME_STATES on traverseToLast(\"current-route\")).\n */\nexport function findLastEntryForRoute(\n entries: NavigationHistoryEntry[],\n routeName: string,\n api: PluginApi,\n base: string,\n currentKey: string | undefined,\n): NavigationHistoryEntry | undefined {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n\n if (entry.key === currentKey) {\n continue;\n }\n\n const state = entryToState(entry, api, base);\n\n if (state?.name === routeName) {\n return entry;\n }\n }\n\n return undefined;\n}\n\nexport function canGoBack(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n return idx != null && idx > 0;\n}\n\nexport function canGoForward(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n return idx < browser.entries().length - 1;\n}\n\nexport function canGoBackTo(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n const entries = browser.entries();\n\n for (let i = idx - 1; i >= 0; i--) {\n const state = entryToState(entries[i], api, base);\n\n if (state?.name === routeName) {\n return true;\n }\n }\n\n return false;\n}\n","import { RouterError } from \"@real-router/core\";\n\nimport { extractPath } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser, NavigationMeta } from \"./types\";\nimport type { Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\ninterface NavigateHandlerDeps {\n router: Router;\n api: PluginApi;\n browser: NavigationBrowser;\n isSyncingFromRouter: () => boolean;\n setSyncing: (value: boolean) => void;\n setPendingMeta: (meta: NavigationMeta) => void;\n base: string;\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n };\n}\n\nexport function createNavigateHandler(deps: NavigateHandlerDeps) {\n const {\n router,\n api,\n browser,\n isSyncingFromRouter,\n setSyncing,\n base,\n transitionOptions,\n } = deps;\n const { allowNotFound } = api.getOptions();\n\n return function handleNavigateEvent(event: NavigateEvent): void {\n if (!event.canIntercept) {\n return;\n }\n if (isSyncingFromRouter()) {\n return;\n }\n if (!router.isActive()) {\n return;\n }\n\n const destinationUrl = new URL(event.destination.url);\n const path =\n extractPath(destinationUrl.pathname, base) + destinationUrl.search;\n const matchedState = api.matchPath(path);\n\n // Set pendingMeta BEFORE event.intercept() — available in guards via getNavigationMeta()\n deps.setPendingMeta({\n navigationType: event.navigationType as NavigationMeta[\"navigationType\"],\n userInitiated: event.userInitiated,\n info: event.info,\n });\n\n if (matchedState) {\n event.intercept({\n handler: async () => {\n try {\n await router.navigate(matchedState.name, matchedState.params, {\n ...transitionOptions,\n signal: event.signal,\n });\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n } else if (allowNotFound) {\n event.intercept({\n handler: () => {\n router.navigateToNotFound(path);\n },\n });\n } else {\n event.intercept({\n handler: async () => {\n try {\n await router.navigateToDefault();\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n }\n };\n}\n\nfunction recoverFromNavigateError(\n error: unknown,\n router: Router,\n browser: NavigationBrowser,\n setSyncing: (value: boolean) => void,\n): void {\n console.error(\n \"[navigation-plugin] Critical error in navigate handler\",\n error,\n );\n\n try {\n const currentState = router.getState();\n\n if (currentState) {\n const url = router.buildUrl(currentState.name, currentState.params);\n\n setSyncing(true);\n browser.navigate(url, {\n state: {\n name: currentState.name,\n params: currentState.params,\n path: currentState.path,\n },\n history: \"replace\",\n });\n setSyncing(false);\n }\n } catch (recoveryError) {\n console.error(\n \"[navigation-plugin] Failed to recover from critical error\",\n recoveryError,\n );\n }\n}\n","import type { NavigationBrowser } from \"./types\";\nimport type { Params, Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Makes `router.start()` path optional by injecting browser location.\n * Identical to browser-env's createStartInterceptor, adapted for NavigationBrowser.\n */\nexport function createStartInterceptor(\n api: PluginApi,\n browser: NavigationBrowser,\n): () => void {\n return api.addInterceptor(\"start\", (next, path) =>\n next(path ?? browser.getLocation()),\n );\n}\n\n/**\n * Creates replaceHistoryState extension for NavigationBrowser.\n *\n * IMPORTANT: Must set isSyncingFromRouter=true before calling browser.replaceState\n * because navigation.navigate({history:\"replace\"}) fires a navigate event.\n * Without this flag, the navigate handler would trigger a full navigation.\n */\nexport function createReplaceHistoryState(\n api: PluginApi,\n router: Router,\n browser: NavigationBrowser,\n buildUrl: (name: string, params?: Params) => string,\n setSyncing: (value: boolean) => void,\n): (name: string, params?: Params) => void {\n return (name: string, params: Params = {}) => {\n const state = api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n );\n\n const url = buildUrl(name, params);\n const historyState = {\n name: builtState.name,\n params: builtState.params,\n path: builtState.path,\n };\n\n setSyncing(true);\n browser.replaceState(historyState, url);\n setSyncing(false);\n };\n}\n","import { UNKNOWN_ROUTE } from \"@real-router/core\";\n\nimport {\n shouldReplaceHistory,\n buildUrl,\n extractPath,\n urlToPath,\n} from \"./browser-env/index.js\";\nimport { LOGGER_CONTEXT } from \"./constants\";\nimport {\n peekBack,\n peekForward,\n hasVisited,\n getVisitedRoutes,\n getRouteVisitCount,\n findLastEntryForRoute,\n canGoBack,\n canGoForward,\n canGoBackTo,\n} from \"./history-extensions\";\nimport { createNavigateHandler } from \"./navigate-handler\";\nimport {\n createStartInterceptor,\n createReplaceHistoryState,\n} from \"./plugin-utils\";\n\nimport type {\n NavigationBrowser,\n NavigationMeta,\n NavigationPluginOptions,\n NavigationSharedState,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Params,\n Router,\n State,\n Plugin,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nfunction deriveNavigationType(\n navOptions: NavigationOptions,\n toState: State,\n fromState: State | undefined,\n): NavigationMeta[\"navigationType\"] {\n if (navOptions.reload && toState.path === fromState?.path) {\n return \"reload\";\n }\n\n if (shouldReplaceHistory(navOptions, toState, fromState)) {\n return \"replace\";\n }\n\n return \"push\";\n}\n\nexport class NavigationPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #options: Required<NavigationPluginOptions>;\n readonly #browser: NavigationBrowser;\n readonly #removeStartInterceptor: () => void;\n readonly #removeExtensions: () => void;\n readonly #lifecycle: Pick<Plugin, \"onStart\" | \"onStop\" | \"teardown\">;\n\n #isSyncingFromRouter = false;\n readonly #metaByState = new WeakMap<State, NavigationMeta>();\n #pendingMeta: NavigationMeta | undefined;\n #pendingTraverseKey: string | undefined;\n\n constructor(\n router: Router,\n api: PluginApi,\n options: Required<NavigationPluginOptions>,\n browser: NavigationBrowser,\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n },\n shared: NavigationSharedState,\n ) {\n this.#router = router;\n this.#api = api;\n this.#options = options;\n this.#browser = browser;\n\n this.#removeStartInterceptor = createStartInterceptor(api, browser);\n\n const pluginBuildUrl = (route: string, params?: Params) => {\n const path = router.buildPath(route, params);\n\n return buildUrl(path, options.base);\n };\n\n this.#removeExtensions = api.extendRouter({\n buildUrl: pluginBuildUrl,\n matchUrl: (url: string) => {\n const path = urlToPath(url, options.base, LOGGER_CONTEXT);\n\n return path ? api.matchPath(path) : undefined;\n },\n replaceHistoryState: createReplaceHistoryState(\n api,\n router,\n browser,\n pluginBuildUrl,\n (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n ),\n\n peekBack: () => peekBack(browser, api, options.base),\n peekForward: () => peekForward(browser, api, options.base),\n hasVisited: (routeName: string) =>\n hasVisited(browser, api, options.base, routeName),\n getVisitedRoutes: () => getVisitedRoutes(browser, api, options.base),\n getRouteVisitCount: (routeName: string) =>\n getRouteVisitCount(browser, api, options.base, routeName),\n traverseToLast: (routeName: string) => this.traverseToLast(routeName),\n getNavigationMeta: (state?: State): NavigationMeta | undefined => {\n if (!state) {\n return this.#pendingMeta;\n }\n\n return this.#metaByState.get(state);\n },\n canGoBack: () => canGoBack(browser),\n canGoForward: () => canGoForward(browser),\n canGoBackTo: (routeName: string) =>\n canGoBackTo(browser, api, options.base, routeName),\n });\n\n const handler = createNavigateHandler({\n router,\n api,\n browser,\n isSyncingFromRouter: () => this.#isSyncingFromRouter,\n setSyncing: (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n setPendingMeta: (meta) => {\n this.#pendingMeta = meta;\n },\n base: options.base,\n transitionOptions,\n });\n\n this.#lifecycle = createNavigateLifecycle({\n browser,\n shared,\n handler,\n removeStartInterceptor: this.#removeStartInterceptor,\n removeExtensions: this.#removeExtensions,\n });\n }\n\n async traverseToLast(routeName: string): Promise<State> {\n const entries = this.#browser.entries();\n const currentKey = this.#browser.currentEntry?.key;\n const entry = findLastEntryForRoute(\n entries,\n routeName,\n this.#api,\n this.#options.base,\n currentKey,\n );\n\n if (!entry) {\n throw new Error(`No history entry for route \"${routeName}\"`);\n }\n\n if (!entry.url) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n const parsedUrl = new URL(entry.url);\n const path =\n extractPath(parsedUrl.pathname, this.#options.base) + parsedUrl.search;\n const matchedState = this.#api.matchPath(path);\n\n if (!matchedState) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n this.#pendingMeta = {\n navigationType: \"traverse\",\n userInitiated: false,\n };\n this.#pendingTraverseKey = entry.key;\n\n return this.#router.navigate(matchedState.name, matchedState.params);\n }\n\n getPlugin(): Plugin {\n return {\n ...this.#lifecycle,\n\n onTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n navOptions: NavigationOptions,\n ) => {\n if (!this.#pendingMeta) {\n this.#pendingMeta = {\n navigationType: deriveNavigationType(\n navOptions,\n toState,\n fromState,\n ),\n userInitiated: false,\n };\n }\n\n this.#metaByState.set(toState, this.#pendingMeta);\n this.#pendingMeta = undefined;\n\n this.#isSyncingFromRouter = true;\n\n if (this.#pendingTraverseKey) {\n this.#browser.traverseTo(this.#pendingTraverseKey);\n this.#pendingTraverseKey = undefined;\n } else {\n const url = this.#router.buildUrl(toState.name, toState.params);\n const shouldPreserveHash =\n !fromState || fromState.path === toState.path;\n const finalUrl = shouldPreserveHash\n ? url + this.#browser.getHash()\n : url;\n const historyState = {\n name: toState.name,\n params: toState.params,\n path: toState.path,\n };\n\n if (toState.name === UNKNOWN_ROUTE) {\n this.#browser.updateCurrentEntry({ state: historyState });\n } else {\n const replace = shouldReplaceHistory(\n navOptions,\n toState,\n fromState,\n );\n\n this.#browser.navigate(finalUrl, {\n state: historyState,\n history: replace ? \"replace\" : \"push\",\n });\n }\n }\n\n this.#isSyncingFromRouter = false;\n },\n\n onTransitionCancel: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n\n onTransitionError: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n };\n }\n}\n\ninterface NavigateLifecycleDeps {\n browser: NavigationBrowser;\n handler: (event: NavigateEvent) => void;\n removeStartInterceptor: () => void;\n removeExtensions: () => void;\n shared: NavigationSharedState;\n}\n\nfunction createNavigateLifecycle(deps: NavigateLifecycleDeps): Plugin {\n return {\n onStart() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = deps.browser.addNavigateListener(\n deps.handler,\n );\n },\n\n onStop() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n },\n\n teardown() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n deps.removeStartInterceptor();\n deps.removeExtensions();\n },\n };\n}\n","import { createWarnOnce } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser } from \"./types\";\n\nconst NOOP = (): void => {};\n\nexport const createNavigationFallbackBrowser = (\n context: string,\n): NavigationBrowser => {\n const warnOnce = createWarnOnce(context);\n\n return {\n getLocation: () => {\n warnOnce(\"getLocation\");\n\n return \"/\";\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n navigate: () => {\n warnOnce(\"navigate\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n updateCurrentEntry: () => {\n warnOnce(\"updateCurrentEntry\");\n },\n traverseTo: () => {\n warnOnce(\"traverseTo\");\n },\n addNavigateListener: () => {\n warnOnce(\"addNavigateListener\");\n\n return NOOP;\n },\n entries: () => {\n warnOnce(\"entries\");\n\n return [];\n },\n currentEntry: null,\n };\n};\n","import { createOptionsValidator } from \"./browser-env/index.js\";\nimport { LOGGER_CONTEXT, defaultOptions } from \"./constants\";\n\nimport type { NavigationPluginOptions } from \"./types\";\n\nexport const validateOptions = createOptionsValidator<NavigationPluginOptions>(\n defaultOptions,\n LOGGER_CONTEXT,\n);\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { isBrowserEnvironment, normalizeBase } from \"./browser-env/index.js\";\nimport { defaultOptions, source } from \"./constants\";\nimport { createNavigationBrowser } from \"./navigation-browser\";\nimport { NavigationPlugin } from \"./plugin\";\nimport { createNavigationFallbackBrowser } from \"./ssr-fallback\";\nimport { validateOptions } from \"./validation\";\n\nimport type {\n NavigationPluginOptions,\n NavigationBrowser,\n NavigationSharedState,\n} from \"./types\";\nimport type { PluginFactory, Router } from \"@real-router/core\";\n\nexport function navigationPluginFactory(\n opts?: Partial<NavigationPluginOptions>,\n browser?: NavigationBrowser,\n): PluginFactory {\n if (!browser && isBrowserEnvironment() && !(\"navigation\" in globalThis)) {\n throw new Error(\n \"[navigation-plugin] Navigation API is not supported. Use @real-router/browser-plugin instead.\",\n );\n }\n\n validateOptions(opts);\n\n const options: Required<NavigationPluginOptions> = {\n ...defaultOptions,\n ...opts,\n };\n\n options.base = normalizeBase(options.base);\n\n const resolvedBrowser = browser ?? createBrowser(options.base);\n\n const forceDeactivate = options.forceDeactivate;\n const transitionOptions = { forceDeactivate, source, replace: true as const };\n const shared: NavigationSharedState = { removeNavigateListener: undefined };\n\n return (routerBase) => {\n const api = getPluginApi(routerBase);\n\n const plugin = new NavigationPlugin(\n routerBase as Router,\n api,\n options,\n resolvedBrowser,\n transitionOptions,\n shared,\n );\n\n return plugin.getPlugin();\n };\n}\n\nfunction createBrowser(base: string): NavigationBrowser {\n if (\"navigation\" in globalThis) {\n return createNavigationBrowser(base);\n }\n\n return createNavigationFallbackBrowser(\"navigation-plugin\");\n}\n"],"mappings":"yIAAA,MAAa,MACJ,WAAW,SAAW,QAAe,CAAC,CAAC,WAAW,QCO3D,SAAgB,EAAc,EAAsB,CAClD,GAAI,CAAC,EACH,OAAO,EAGT,IAAI,EAAS,EAUb,OARK,EAAO,WAAW,IAAI,GACzB,EAAS,IAAI,KAGX,EAAO,SAAS,IAAI,GACtB,EAAS,EAAO,MAAM,EAAG,GAAG,EAGvB,EAGT,MAAa,EAAoB,GAAyB,CACxD,GAAI,CACF,OAAO,UAAU,UAAU,EAAK,CAAC,OAC1B,EAAO,CAGd,OAFA,QAAQ,KAAK,wCAAwC,EAAK,GAAI,EAAM,CAE7D,IC5BE,EAAkB,GAAoB,CACjD,IAAI,EAAY,GAEhB,MAAQ,IAAyB,CAC/B,AAME,KALA,QAAQ,KACN,gFAAgF,EAAQ,cAC3E,EAAO,6GAErB,CACW,MCdlB,SAAgB,EACd,EACA,EACwC,CACxC,MAAQ,IAAS,CACV,KAIL,KAAK,IAAM,KAAO,OAAO,KAAK,EAAK,CACjC,GAAI,KAAO,EAAU,CACnB,IAAM,EAAQ,EAAK,GACb,EAAW,OAAO,EAAS,GAC3B,EAAS,OAAO,EAEtB,GAAI,IAAU,IAAA,IAAa,IAAW,EACpC,MAAU,MACR,IAAI,EAAc,sBAAsB,EAAI,cAAc,EAAS,QAAQ,IAC5E,IC8BX,SAAgB,EACd,EACA,EACA,EACS,CACT,OACG,EAAW,SAAW,CAAC,IACvB,CAAC,CAAC,EAAW,QAAU,EAAQ,OAAS,EAAU,KCvDvD,SAAgB,EAAa,EAAa,EAAmC,CAC3E,GAAI,CACF,IAAM,EAAY,IAAI,IAAI,EAAK,WAAW,SAAS,OAAO,CAQ1D,MANK,CAAC,QAAS,SAAS,CAAC,SAAS,EAAU,SAAS,CAM9C,GALL,QAAQ,KAAK,IAAI,EAAc,4BAA4B,IAAM,CAE1D,YAIF,EAAO,CAGd,OAFA,QAAQ,KAAK,IAAI,EAAc,wBAAwB,IAAO,EAAM,CAE7D,MCZX,SAAgB,EAAY,EAAkB,EAAsB,CAClE,GAAI,GAAQ,EAAS,WAAW,EAAK,CAAE,CACrC,IAAM,EAAW,EAAS,MAAM,EAAK,OAAO,CAE5C,OAAO,EAAS,WAAW,IAAI,CAAG,EAAW,IAAI,IAGnD,OAAO,EAGT,SAAgB,EAAS,EAAc,EAAsB,CAC3D,OAAO,EAAO,EAGhB,SAAgB,EACd,EACA,EACA,EACe,CACf,IAAM,EAAY,EAAa,EAAK,EAAQ,CAE5C,OAAO,EACH,EAAY,EAAU,SAAU,EAAK,CAAG,EAAU,OAClD,KCvBN,MAAa,EAAoD,CAC/D,gBAAiB,GACjB,KAAM,GACP,CASY,EAAiB,oBCN9B,SAAgB,EAAwB,EAAiC,CACvE,IAAM,EAAM,WAAW,WAEvB,MAAO,CACL,gBACE,EAAiB,EAAY,WAAW,SAAS,SAAU,EAAK,CAAC,CACjE,WAAW,SAAS,OAEtB,YAAe,WAAW,SAAS,KAEnC,UAAW,EAAK,IAAY,CAC1B,EAAI,SAAS,EAAK,CAChB,MAAO,EAAQ,MACf,QAAS,EAAQ,QAClB,CAAC,EAGJ,cAAe,EAAO,IAAQ,CAC5B,EAAI,SAAS,EAAK,CAChB,QACA,QAAS,UACV,CAAC,EAGJ,mBAAqB,GAAY,CAC/B,EAAI,mBAAmB,EAAQ,EAGjC,WAAa,GAAQ,CACnB,EAAI,WAAW,EAAI,EAGrB,oBAAsB,IACpB,EAAI,iBAAiB,WAAY,EAAG,KAEvB,CACX,EAAI,oBAAoB,WAAY,EAAG,GAI3C,YAAe,EAAI,SAAS,CAE5B,IAAI,cAAe,CACjB,OAAO,EAAI,cAEd,CCxCH,SAAgB,EACd,EACA,EACA,EACmB,CACnB,GAAI,CAAC,GAAO,IACV,OAGF,IAAM,EAAW,IAAI,IAAI,EAAM,IAAI,CAAC,SAC9B,EAAO,EAAY,EAAU,EAAK,CAExC,OAAO,EAAI,UAAU,EAAK,EAAI,IAAA,GAGhC,SAAS,EACP,EACA,EACA,EACA,EACmB,CACnB,IAAM,EAAM,EAAQ,cAAc,MAE9B,MAAO,KAIX,OAAO,EAAa,EAAQ,SAAS,CAAC,EAAM,GAAS,EAAK,EAAK,CAGjE,SAAgB,EACd,EACA,EACA,EACmB,CACnB,OAAO,EAAO,EAAS,EAAK,EAAM,GAAG,CAGvC,SAAgB,EACd,EACA,EACA,EACmB,CACnB,OAAO,EAAO,EAAS,EAAK,EAAM,EAAE,CAGtC,SAAgB,EACd,EACA,EACA,EACA,EACS,CACT,OAAO,EAAQ,SAAS,CAAC,KAAM,GACf,EAAa,EAAO,EAAK,EAAK,EAE9B,OAAS,EACvB,CAGJ,SAAgB,EACd,EACA,EACA,EACU,CACV,IAAM,EAAQ,IAAI,IAElB,IAAK,IAAM,KAAS,EAAQ,SAAS,CAAE,CACrC,IAAM,EAAQ,EAAa,EAAO,EAAK,EAAK,CAExC,GACF,EAAM,IAAI,EAAM,KAAK,CAIzB,MAAO,CAAC,GAAG,EAAM,CAGnB,SAAgB,EACd,EACA,EACA,EACA,EACQ,CACR,OAAO,EAAQ,SAAS,CAAC,OAAQ,GACjB,EAAa,EAAO,EAAK,EAAK,EAE9B,OAAS,EACvB,CAAC,OAOL,SAAgB,EACd,EACA,EACA,EACA,EACA,EACoC,CACpC,IAAK,IAAI,EAAI,EAAQ,OAAS,EAAG,GAAK,EAAG,IAAK,CAC5C,IAAM,EAAQ,EAAQ,GAElB,KAAM,MAAQ,GAIJ,EAAa,EAAO,EAAK,EAAK,EAEjC,OAAS,EAClB,OAAO,GAOb,SAAgB,EAAU,EAAqC,CAC7D,IAAM,EAAM,EAAQ,cAAc,MAElC,OAAO,GAAO,MAAQ,EAAM,EAG9B,SAAgB,EAAa,EAAqC,CAChE,IAAM,EAAM,EAAQ,cAAc,MAMlC,OAJI,GAAO,KACF,GAGF,EAAM,EAAQ,SAAS,CAAC,OAAS,EAG1C,SAAgB,EACd,EACA,EACA,EACA,EACS,CACT,IAAM,EAAM,EAAQ,cAAc,MAElC,GAAI,GAAO,KACT,MAAO,GAGT,IAAM,EAAU,EAAQ,SAAS,CAEjC,IAAK,IAAI,EAAI,EAAM,EAAG,GAAK,EAAG,IAG5B,GAFc,EAAa,EAAQ,GAAI,EAAK,EAAK,EAEtC,OAAS,EAClB,MAAO,GAIX,MAAO,GClJT,SAAgB,EAAsB,EAA2B,CAC/D,GAAM,CACJ,SACA,MACA,UACA,sBACA,aACA,OACA,qBACE,EACE,CAAE,iBAAkB,EAAI,YAAY,CAE1C,OAAO,SAA6B,EAA4B,CAO9D,GANI,CAAC,EAAM,cAGP,GAAqB,EAGrB,CAAC,EAAO,UAAU,CACpB,OAGF,IAAM,EAAiB,IAAI,IAAI,EAAM,YAAY,IAAI,CAC/C,EACJ,EAAY,EAAe,SAAU,EAAK,CAAG,EAAe,OACxD,EAAe,EAAI,UAAU,EAAK,CAGxC,EAAK,eAAe,CAClB,eAAgB,EAAM,eACtB,cAAe,EAAM,cACrB,KAAM,EAAM,KACb,CAAC,CAEE,EACF,EAAM,UAAU,CACd,QAAS,SAAY,CACnB,GAAI,CACF,MAAM,EAAO,SAAS,EAAa,KAAM,EAAa,OAAQ,CAC5D,GAAG,EACH,OAAQ,EAAM,OACf,CAAC,OACK,EAAO,CACR,aAAiBA,EAAAA,aACrB,EAAyB,EAAO,EAAQ,EAAS,EAAW,GAInE,CAAC,CACO,EACT,EAAM,UAAU,CACd,YAAe,CACb,EAAO,mBAAmB,EAAK,EAElC,CAAC,CAEF,EAAM,UAAU,CACd,QAAS,SAAY,CACnB,GAAI,CACF,MAAM,EAAO,mBAAmB,OACzB,EAAO,CACR,aAAiBA,EAAAA,aACrB,EAAyB,EAAO,EAAQ,EAAS,EAAW,GAInE,CAAC,EAKR,SAAS,EACP,EACA,EACA,EACA,EACM,CACN,QAAQ,MACN,yDACA,EACD,CAED,GAAI,CACF,IAAM,EAAe,EAAO,UAAU,CAEtC,GAAI,EAAc,CAChB,IAAM,EAAM,EAAO,SAAS,EAAa,KAAM,EAAa,OAAO,CAEnE,EAAW,GAAK,CAChB,EAAQ,SAAS,EAAK,CACpB,MAAO,CACL,KAAM,EAAa,KACnB,OAAQ,EAAa,OACrB,KAAM,EAAa,KACpB,CACD,QAAS,UACV,CAAC,CACF,EAAW,GAAM,QAEZ,EAAe,CACtB,QAAQ,MACN,4DACA,EACD,ECvHL,SAAgB,EACd,EACA,EACY,CACZ,OAAO,EAAI,eAAe,SAAU,EAAM,IACxC,EAAK,GAAQ,EAAQ,aAAa,CAAC,CACpC,CAUH,SAAgB,EACd,EACA,EACA,EACA,EACA,EACyC,CACzC,OAAQ,EAAc,EAAiB,EAAE,GAAK,CAC5C,IAAM,EAAQ,EAAI,WAAW,EAAM,EAAO,CAE1C,GAAI,CAAC,EACH,MAAU,MACR,8CAA8C,EAAK,gBACpD,CAGH,IAAM,EAAa,EAAI,UACrB,EAAM,KACN,EAAM,OACN,EAAO,UAAU,EAAM,KAAM,EAAM,OAAO,CAC1C,CACE,OAAQ,EAAM,KACf,CACF,CAEK,EAAM,EAAS,EAAM,EAAO,CAC5B,EAAe,CACnB,KAAM,EAAW,KACjB,OAAQ,EAAW,OACnB,KAAM,EAAW,KAClB,CAED,EAAW,GAAK,CAChB,EAAQ,aAAa,EAAc,EAAI,CACvC,EAAW,GAAM,ECjBrB,SAAS,EACP,EACA,EACA,EACkC,CASlC,OARI,EAAW,QAAU,EAAQ,OAAS,GAAW,KAC5C,SAGL,EAAqB,EAAY,EAAS,EAAU,CAC/C,UAGF,OAGT,IAAa,EAAb,KAA8B,CAC5B,GACA,GACA,GACA,GACA,GACA,GACA,GAEA,GAAuB,GACvB,GAAwB,IAAI,QAC5B,GACA,GAEA,YACE,EACA,EACA,EACA,EACA,EAKA,EACA,CACA,MAAA,EAAe,EACf,MAAA,EAAY,EACZ,MAAA,EAAgB,EAChB,MAAA,EAAgB,EAEhB,MAAA,EAA+B,EAAuB,EAAK,EAAQ,CAEnE,IAAM,GAAkB,EAAe,IAG9B,EAFM,EAAO,UAAU,EAAO,EAAO,CAEtB,EAAQ,KAAK,CAGrC,MAAA,EAAyB,EAAI,aAAa,CACxC,SAAU,EACV,SAAW,GAAgB,CACzB,IAAM,EAAO,EAAU,EAAK,EAAQ,KAAM,EAAe,CAEzD,OAAO,EAAO,EAAI,UAAU,EAAK,CAAG,IAAA,IAEtC,oBAAqB,EACnB,EACA,EACA,EACA,EACC,GAAY,CACX,MAAA,EAA4B,GAE/B,CAED,aAAgB,EAAS,EAAS,EAAK,EAAQ,KAAK,CACpD,gBAAmB,EAAY,EAAS,EAAK,EAAQ,KAAK,CAC1D,WAAa,GACX,EAAW,EAAS,EAAK,EAAQ,KAAM,EAAU,CACnD,qBAAwB,EAAiB,EAAS,EAAK,EAAQ,KAAK,CACpE,mBAAqB,GACnB,EAAmB,EAAS,EAAK,EAAQ,KAAM,EAAU,CAC3D,eAAiB,GAAsB,KAAK,eAAe,EAAU,CACrE,kBAAoB,GACb,EAIE,MAAA,EAAkB,IAAI,EAAM,CAH1B,MAAA,EAKX,cAAiB,EAAU,EAAQ,CACnC,iBAAoB,EAAa,EAAQ,CACzC,YAAc,GACZ,EAAY,EAAS,EAAK,EAAQ,KAAM,EAAU,CACrD,CAAC,CAiBF,MAAA,EAAkB,EAAwB,CACxC,UACA,SACA,QAlBc,EAAsB,CACpC,SACA,MACA,UACA,wBAA2B,MAAA,EAC3B,WAAa,GAAY,CACvB,MAAA,EAA4B,GAE9B,eAAiB,GAAS,CACxB,MAAA,EAAoB,GAEtB,KAAM,EAAQ,KACd,oBACD,CAAC,CAMA,uBAAwB,MAAA,EACxB,iBAAkB,MAAA,EACnB,CAAC,CAGJ,MAAM,eAAe,EAAmC,CACtD,IAAM,EAAU,MAAA,EAAc,SAAS,CACjC,EAAa,MAAA,EAAc,cAAc,IACzC,EAAQ,EACZ,EACA,EACA,MAAA,EACA,MAAA,EAAc,KACd,EACD,CAED,GAAI,CAAC,EACH,MAAU,MAAM,+BAA+B,EAAU,GAAG,CAG9D,GAAI,CAAC,EAAM,IACT,MAAU,MAAM,oCAAoC,EAAM,IAAI,GAAG,CAGnE,IAAM,EAAY,IAAI,IAAI,EAAM,IAAI,CAC9B,EACJ,EAAY,EAAU,SAAU,MAAA,EAAc,KAAK,CAAG,EAAU,OAC5D,EAAe,MAAA,EAAU,UAAU,EAAK,CAE9C,GAAI,CAAC,EACH,MAAU,MAAM,oCAAoC,EAAM,IAAI,GAAG,CASnE,MANA,OAAA,EAAoB,CAClB,eAAgB,WAChB,cAAe,GAChB,CACD,MAAA,EAA2B,EAAM,IAE1B,MAAA,EAAa,SAAS,EAAa,KAAM,EAAa,OAAO,CAGtE,WAAoB,CAClB,MAAO,CACL,GAAG,MAAA,EAEH,qBACE,EACA,EACA,IACG,CAiBH,GAhBA,AACE,MAAA,IAAoB,CAClB,eAAgB,EACd,EACA,EACA,EACD,CACD,cAAe,GAChB,CAGH,MAAA,EAAkB,IAAI,EAAS,MAAA,EAAkB,CACjD,MAAA,EAAoB,IAAA,GAEpB,MAAA,EAA4B,GAExB,MAAA,EACF,MAAA,EAAc,WAAW,MAAA,EAAyB,CAClD,MAAA,EAA2B,IAAA,OACtB,CACL,IAAM,EAAM,MAAA,EAAa,SAAS,EAAQ,KAAM,EAAQ,OAAO,CAGzD,EADJ,CAAC,GAAa,EAAU,OAAS,EAAQ,KAEvC,EAAM,MAAA,EAAc,SAAS,CAC7B,EACE,EAAe,CACnB,KAAM,EAAQ,KACd,OAAQ,EAAQ,OAChB,KAAM,EAAQ,KACf,CAED,GAAI,EAAQ,OAASY,EAAAA,cACnB,MAAA,EAAc,mBAAmB,CAAE,MAAO,EAAc,CAAC,KACpD,CACL,IAAM,EAAU,EACd,EACA,EACA,EACD,CAED,MAAA,EAAc,SAAS,EAAU,CAC/B,MAAO,EACP,QAAS,EAAU,UAAY,OAChC,CAAC,EAIN,MAAA,EAA4B,IAG9B,uBAA0B,CACxB,MAAA,EAAoB,IAAA,GACpB,MAAA,EAA2B,IAAA,IAG7B,sBAAyB,CACvB,MAAA,EAAoB,IAAA,GACpB,MAAA,EAA2B,IAAA,IAE9B,GAYL,SAAS,EAAwB,EAAqC,CACpE,MAAO,CACL,SAAU,CACR,EAAK,OAAO,0BAA0B,CACtC,EAAK,OAAO,uBAAyB,EAAK,QAAQ,oBAChD,EAAK,QACN,EAGH,QAAS,CACP,EAAK,OAAO,0BAA0B,CACtC,EAAK,OAAO,uBAAyB,IAAA,IAGvC,UAAW,CACT,EAAK,OAAO,0BAA0B,CACtC,EAAK,OAAO,uBAAyB,IAAA,GACrC,EAAK,wBAAwB,CAC7B,EAAK,kBAAkB,EAE1B,CCpSH,MAAM,MAAmB,GAEZ,EACX,GACsB,CACtB,IAAM,EAAW,EAAe,EAAQ,CAExC,MAAO,CACL,iBACE,EAAS,cAAc,CAEhB,KAET,aACE,EAAS,UAAU,CAEZ,IAET,aAAgB,CACd,EAAS,WAAW,EAEtB,iBAAoB,CAClB,EAAS,eAAe,EAE1B,uBAA0B,CACxB,EAAS,qBAAqB,EAEhC,eAAkB,CAChB,EAAS,aAAa,EAExB,yBACE,EAAS,sBAAsB,CAExB,GAET,aACE,EAAS,UAAU,CAEZ,EAAE,EAEX,aAAc,KACf,ECxCU,EAAkB,EAC7B,EACA,EACD,CCQD,SAAgB,EACd,EACA,EACe,CACf,GAAI,CAAC,GAAW,GAAsB,EAAI,EAAE,eAAgB,YAC1D,MAAU,MACR,gGACD,CAGH,EAAgB,EAAK,CAErB,IAAM,EAA6C,CACjD,GAAG,EACH,GAAG,EACJ,CAED,EAAQ,KAAO,EAAc,EAAQ,KAAK,CAE1C,IAAM,EAAkB,GAAW,EAAc,EAAQ,KAAK,CAGxD,EAAoB,CAAE,gBADJ,EAAQ,gBACa,kBAAQ,QAAS,GAAe,CACvE,EAAgC,CAAE,uBAAwB,IAAA,GAAW,CAE3E,MAAQ,IAGS,IAAI,EACjB,GAAA,EAAA,EAAA,cAHuB,EAAW,CAKlC,EACA,EACA,EACA,EACD,CAEa,WAAW,CAI7B,SAAS,EAAc,EAAiC,CAKtD,MAJI,eAAgB,WACX,EAAwB,EAAK,CAG/B,EAAgC,oBAAoB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["safelyEncodePath","extractPath","extractPath","extractPath","shouldReplaceHistory","#router","#api","#options","#browser","#removeStartInterceptor","#removeExtensions","#lifecycle","#metaByState","buildUrl","urlToPath","#isSyncingFromRouter","#pendingMeta","extractPath","#pendingTraverseKey","createWarnOnce","createOptionsValidator","isBrowserEnvironment","normalizeBase"],"sources":["../../../browser-env/dist/esm/index.mjs","../../src/constants.ts","../../src/navigation-browser.ts","../../src/history-extensions.ts","../../src/navigate-handler.ts","../../src/plugin-utils.ts","../../src/plugin.ts","../../src/ssr-fallback.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["import{isStateStrict as e}from\"type-guards\";import{RouterError as t}from\"@real-router/core\";const n=()=>globalThis.window!==void 0&&!!globalThis.history,r=(e,t)=>{globalThis.history.pushState(e,``,t)},i=(e,t)=>{globalThis.history.replaceState(e,``,t)},a=e=>(globalThis.addEventListener(`popstate`,e),()=>{globalThis.removeEventListener(`popstate`,e)}),o=()=>globalThis.location.hash;function s(e){if(!e)return e;let t=e;return t.startsWith(`/`)||(t=`/${t}`),t.endsWith(`/`)&&(t=t.slice(0,-1)),t}const c=e=>{try{return encodeURI(decodeURI(e))}catch(t){return console.warn(`[browser-env] Could not encode path \"${e}\"`,t),e}},l=()=>{},u=e=>{let t=!1;return n=>{t||=(console.warn(`[browser-env] Browser API is running in a non-browser environment (context: \"${e}\"). Method \"${n}\" is a no-op. This is expected for SSR, but may indicate misconfiguration if you expected browser behavior.`),!0)}},d=e=>{let t=u(e);return{pushState:()=>{t(`pushState`)},replaceState:()=>{t(`replaceState`)},addPopstateListener:()=>(t(`addPopstateListener`),l),getHash:()=>(t(`getHash`),``)}};function f(t,n,r){if(e(t.state))return{name:t.state.name,params:t.state.params};let i=n.matchPath(r.getLocation());return i?{name:i.name,params:i.params}:void 0}function p(e,t,n,r){let i={name:e.name,params:e.params,path:e.path};n?r.replaceState(i,t):r.pushState(i,t)}function m(e,t){return n=>{if(n){for(let r of Object.keys(n))if(r in e){let i=n[r],a=typeof e[r],o=typeof i;if(i!==void 0&&o!==a)throw Error(`[${t}] Invalid type for '${r}': expected ${a}, got ${o}`)}}}}function h(e,t){if(n())return{pushState:r,replaceState:i,addPopstateListener:a,getLocation:e,getHash:o};let s=u(t);return{...d(t),getLocation:()=>(s(`getLocation`),``)}}function g(e){let n=!1,r=null;function i(){if(r){let t=r;r=null,console.warn(`[${e.loggerContext}] Processing deferred popstate event`),o(t)}}function a(t){console.error(`[${e.loggerContext}] Critical error in onPopState`,t);try{let t=e.router.getState();if(t){let n=e.buildUrl(t.name,t.params);e.browser.replaceState(t,n)}}catch(t){console.error(`[${e.loggerContext}] Failed to recover from critical error`,t)}}async function o(o){if(n){console.warn(`[${e.loggerContext}] Transition in progress, deferring popstate event`),r=o;return}n=!0;try{let t=f(o,e.api,e.browser);t?await e.router.navigate(t.name,t.params,e.transitionOptions):e.allowNotFound?e.router.navigateToNotFound(e.browser.getLocation()):await e.router.navigateToDefault({...e.transitionOptions,reload:!0,replace:!0})}catch(e){e instanceof t||a(e)}finally{n=!1,i()}}return e=>void o(e)}function _(e){return{onStart:()=>{e.shared.removePopStateListener&&e.shared.removePopStateListener(),e.shared.removePopStateListener=e.browser.addPopstateListener(e.handler)},onStop:()=>{e.shared.removePopStateListener&&(e.shared.removePopStateListener(),e.shared.removePopStateListener=void 0)},teardown:()=>{e.shared.removePopStateListener&&(e.shared.removePopStateListener(),e.shared.removePopStateListener=void 0),e.cleanup()}}}function v(e,t){return e.addInterceptor(`start`,(e,n)=>e(n??t.getLocation()))}function y(e,t,n,r){return(i,a={})=>{let o=e.buildState(i,a);if(!o)throw Error(`[real-router] Cannot replace state: route \"${i}\" is not found`);p(e.makeState(o.name,o.params,t.buildPath(o.name,o.params),{params:o.meta}),r(i,a),!0,n)}}function b(e,t,n){return(e.replace??!n)||!!e.reload&&t.path===n.path}function x(e,t){try{let n=new URL(e,globalThis.location.origin);return[`http:`,`https:`].includes(n.protocol)?n:(console.warn(`[${t}] Invalid URL protocol in ${e}`),null)}catch(n){return console.warn(`[${t}] Could not parse url ${e}`,n),null}}function S(e,t){if(t&&e.startsWith(t)){let n=e.slice(t.length);return n.startsWith(`/`)?n:`/${n}`}return e}function C(e,t){return t+e}function w(e,t,n){let r=x(e,n);return r?S(r.pathname,t)+r.search:null}export{a as addPopstateListener,C as buildUrl,d as createHistoryFallbackBrowser,m as createOptionsValidator,g as createPopstateHandler,_ as createPopstateLifecycle,y as createReplaceHistoryState,h as createSafeBrowser,v as createStartInterceptor,u as createWarnOnce,S as extractPath,o as getHash,f as getRouteFromEvent,n as isBrowserEnvironment,s as normalizeBase,r as pushState,i as replaceState,x as safeParseUrl,c as safelyEncodePath,b as shouldReplaceHistory,p as updateBrowserState,w as urlToPath};\n//# sourceMappingURL=index.mjs.map","import type { NavigationPluginOptions } from \"./types\";\n\nexport const defaultOptions: Required<NavigationPluginOptions> = {\n forceDeactivate: true,\n base: \"\",\n};\n\n/**\n * Source identifier for transitions triggered by navigate events.\n * Distinguishes browser-initiated navigation (back/forward, link clicks)\n * from programmatic navigation (router.navigate()).\n */\nexport const source = \"navigate\";\n\nexport const LOGGER_CONTEXT = \"navigation-plugin\";\n","import { safelyEncodePath, extractPath } from \"browser-env\";\n\nimport type { NavigationBrowser } from \"./types\";\n\n/**\n * Creates a NavigationBrowser wrapping the real Navigation API.\n * Only call this when `\"navigation\" in globalThis` is true.\n */\nexport function createNavigationBrowser(base: string): NavigationBrowser {\n const nav = globalThis.navigation;\n\n return {\n getLocation: () =>\n safelyEncodePath(extractPath(globalThis.location.pathname, base)) +\n globalThis.location.search,\n\n getHash: () => globalThis.location.hash,\n\n navigate: (url, options) => {\n nav.navigate(url, {\n state: options.state,\n history: options.history,\n });\n },\n\n replaceState: (state, url) => {\n nav.navigate(url, {\n state,\n history: \"replace\",\n });\n },\n\n updateCurrentEntry: (options) => {\n nav.updateCurrentEntry(options);\n },\n\n traverseTo: (key) => {\n nav.traverseTo(key);\n },\n\n addNavigateListener: (fn) => {\n nav.addEventListener(\"navigate\", fn);\n\n return () => {\n nav.removeEventListener(\"navigate\", fn);\n };\n },\n\n entries: () => nav.entries(),\n\n get currentEntry() {\n return nav.currentEntry;\n },\n };\n}\n","import { extractPath } from \"browser-env\";\n\nimport type { NavigationBrowser } from \"./types\";\nimport type { State } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Converts a NavigationHistoryEntry to a State via URL matching.\n * Uses URL matching (not entry.getState()) because:\n * - Entries before plugin init have no state\n * - Entries after router.replace(routes) may have stale state\n * - Entries from other SPAs on the same origin have foreign state\n */\nexport function entryToState(\n entry: NavigationHistoryEntry | undefined,\n api: PluginApi,\n base: string,\n): State | undefined {\n if (!entry?.url) {\n return undefined;\n }\n\n const pathname = new URL(entry.url).pathname;\n const path = extractPath(pathname, base);\n\n return api.matchPath(path) ?? undefined;\n}\n\nfunction peekAt(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n offset: number,\n): State | undefined {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return undefined;\n }\n\n return entryToState(browser.entries()[idx + offset], api, base);\n}\n\nexport function peekBack(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, -1);\n}\n\nexport function peekForward(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, 1);\n}\n\nexport function hasVisited(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n return browser.entries().some((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n });\n}\n\nexport function getVisitedRoutes(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): string[] {\n const names = new Set<string>();\n\n for (const entry of browser.entries()) {\n const state = entryToState(entry, api, base);\n\n if (state) {\n names.add(state.name);\n }\n }\n\n return [...names];\n}\n\nexport function getRouteVisitCount(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): number {\n return browser.entries().filter((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n }).length;\n}\n\n/**\n * Finds the last NavigationHistoryEntry matching the given route name,\n * excluding the current entry (to avoid SAME_STATES on traverseToLast(\"current-route\")).\n */\nexport function findLastEntryForRoute(\n entries: NavigationHistoryEntry[],\n routeName: string,\n api: PluginApi,\n base: string,\n currentKey: string | undefined,\n): NavigationHistoryEntry | undefined {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n\n if (entry.key === currentKey) {\n continue;\n }\n\n const state = entryToState(entry, api, base);\n\n if (state?.name === routeName) {\n return entry;\n }\n }\n\n return undefined;\n}\n\nexport function canGoBack(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n return idx != null && idx > 0;\n}\n\nexport function canGoForward(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n return idx < browser.entries().length - 1;\n}\n\nexport function canGoBackTo(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n const entries = browser.entries();\n\n for (let i = idx - 1; i >= 0; i--) {\n const state = entryToState(entries[i], api, base);\n\n if (state?.name === routeName) {\n return true;\n }\n }\n\n return false;\n}\n","import { RouterError } from \"@real-router/core\";\nimport { extractPath } from \"browser-env\";\n\nimport type { NavigationBrowser, NavigationMeta } from \"./types\";\nimport type { Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\ninterface NavigateHandlerDeps {\n router: Router;\n api: PluginApi;\n browser: NavigationBrowser;\n isSyncingFromRouter: () => boolean;\n setSyncing: (value: boolean) => void;\n setPendingMeta: (meta: NavigationMeta) => void;\n base: string;\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n };\n}\n\nexport function createNavigateHandler(deps: NavigateHandlerDeps) {\n const {\n router,\n api,\n browser,\n isSyncingFromRouter,\n setSyncing,\n base,\n transitionOptions,\n } = deps;\n const { allowNotFound } = api.getOptions();\n\n return function handleNavigateEvent(event: NavigateEvent): void {\n if (!event.canIntercept) {\n return;\n }\n if (isSyncingFromRouter()) {\n return;\n }\n if (!router.isActive()) {\n return;\n }\n\n const destinationUrl = new URL(event.destination.url);\n const path =\n extractPath(destinationUrl.pathname, base) + destinationUrl.search;\n const matchedState = api.matchPath(path);\n\n // Set pendingMeta BEFORE event.intercept() — available in guards via getNavigationMeta()\n deps.setPendingMeta({\n navigationType: event.navigationType as NavigationMeta[\"navigationType\"],\n userInitiated: event.userInitiated,\n info: event.info,\n });\n\n if (matchedState) {\n event.intercept({\n handler: async () => {\n try {\n await router.navigate(matchedState.name, matchedState.params, {\n ...transitionOptions,\n signal: event.signal,\n });\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n } else if (allowNotFound) {\n event.intercept({\n handler: () => {\n router.navigateToNotFound(path);\n },\n });\n } else {\n event.intercept({\n handler: async () => {\n try {\n await router.navigateToDefault();\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n }\n };\n}\n\nfunction recoverFromNavigateError(\n error: unknown,\n router: Router,\n browser: NavigationBrowser,\n setSyncing: (value: boolean) => void,\n): void {\n console.error(\n \"[navigation-plugin] Critical error in navigate handler\",\n error,\n );\n\n try {\n const currentState = router.getState();\n\n if (currentState) {\n const url = router.buildUrl(currentState.name, currentState.params);\n\n setSyncing(true);\n browser.navigate(url, {\n state: {\n name: currentState.name,\n params: currentState.params,\n path: currentState.path,\n },\n history: \"replace\",\n });\n setSyncing(false);\n }\n } catch (recoveryError) {\n console.error(\n \"[navigation-plugin] Failed to recover from critical error\",\n recoveryError,\n );\n }\n}\n","import type { NavigationBrowser } from \"./types\";\nimport type { Params, Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Makes `router.start()` path optional by injecting browser location.\n * Identical to browser-env's createStartInterceptor, adapted for NavigationBrowser.\n */\nexport function createStartInterceptor(\n api: PluginApi,\n browser: NavigationBrowser,\n): () => void {\n return api.addInterceptor(\"start\", (next, path) =>\n next(path ?? browser.getLocation()),\n );\n}\n\n/**\n * Creates replaceHistoryState extension for NavigationBrowser.\n *\n * IMPORTANT: Must set isSyncingFromRouter=true before calling browser.replaceState\n * because navigation.navigate({history:\"replace\"}) fires a navigate event.\n * Without this flag, the navigate handler would trigger a full navigation.\n */\nexport function createReplaceHistoryState(\n api: PluginApi,\n router: Router,\n browser: NavigationBrowser,\n buildUrl: (name: string, params?: Params) => string,\n setSyncing: (value: boolean) => void,\n): (name: string, params?: Params) => void {\n return (name: string, params: Params = {}) => {\n const state = api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n );\n\n const url = buildUrl(name, params);\n const historyState = {\n name: builtState.name,\n params: builtState.params,\n path: builtState.path,\n };\n\n setSyncing(true);\n browser.replaceState(historyState, url);\n setSyncing(false);\n };\n}\n","import { UNKNOWN_ROUTE } from \"@real-router/core\";\nimport {\n shouldReplaceHistory,\n buildUrl,\n extractPath,\n urlToPath,\n} from \"browser-env\";\n\nimport { LOGGER_CONTEXT } from \"./constants\";\nimport {\n peekBack,\n peekForward,\n hasVisited,\n getVisitedRoutes,\n getRouteVisitCount,\n findLastEntryForRoute,\n canGoBack,\n canGoForward,\n canGoBackTo,\n} from \"./history-extensions\";\nimport { createNavigateHandler } from \"./navigate-handler\";\nimport {\n createStartInterceptor,\n createReplaceHistoryState,\n} from \"./plugin-utils\";\n\nimport type {\n NavigationBrowser,\n NavigationMeta,\n NavigationPluginOptions,\n NavigationSharedState,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Params,\n Router,\n State,\n Plugin,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nfunction deriveNavigationType(\n navOptions: NavigationOptions,\n toState: State,\n fromState: State | undefined,\n): NavigationMeta[\"navigationType\"] {\n if (navOptions.reload && toState.path === fromState?.path) {\n return \"reload\";\n }\n\n if (shouldReplaceHistory(navOptions, toState, fromState)) {\n return \"replace\";\n }\n\n return \"push\";\n}\n\nexport class NavigationPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #options: Required<NavigationPluginOptions>;\n readonly #browser: NavigationBrowser;\n readonly #removeStartInterceptor: () => void;\n readonly #removeExtensions: () => void;\n readonly #lifecycle: Pick<Plugin, \"onStart\" | \"onStop\" | \"teardown\">;\n\n #isSyncingFromRouter = false;\n readonly #metaByState = new WeakMap<State, NavigationMeta>();\n #pendingMeta: NavigationMeta | undefined;\n #pendingTraverseKey: string | undefined;\n\n constructor(\n router: Router,\n api: PluginApi,\n options: Required<NavigationPluginOptions>,\n browser: NavigationBrowser,\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n },\n shared: NavigationSharedState,\n ) {\n this.#router = router;\n this.#api = api;\n this.#options = options;\n this.#browser = browser;\n\n this.#removeStartInterceptor = createStartInterceptor(api, browser);\n\n const pluginBuildUrl = (route: string, params?: Params) => {\n const path = router.buildPath(route, params);\n\n return buildUrl(path, options.base);\n };\n\n this.#removeExtensions = api.extendRouter({\n buildUrl: pluginBuildUrl,\n matchUrl: (url: string) => {\n const path = urlToPath(url, options.base, LOGGER_CONTEXT);\n\n return path ? api.matchPath(path) : undefined;\n },\n replaceHistoryState: createReplaceHistoryState(\n api,\n router,\n browser,\n pluginBuildUrl,\n (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n ),\n\n peekBack: () => peekBack(browser, api, options.base),\n peekForward: () => peekForward(browser, api, options.base),\n hasVisited: (routeName: string) =>\n hasVisited(browser, api, options.base, routeName),\n getVisitedRoutes: () => getVisitedRoutes(browser, api, options.base),\n getRouteVisitCount: (routeName: string) =>\n getRouteVisitCount(browser, api, options.base, routeName),\n traverseToLast: (routeName: string) => this.traverseToLast(routeName),\n getNavigationMeta: (state?: State): NavigationMeta | undefined => {\n if (!state) {\n return this.#pendingMeta;\n }\n\n return this.#metaByState.get(state);\n },\n canGoBack: () => canGoBack(browser),\n canGoForward: () => canGoForward(browser),\n canGoBackTo: (routeName: string) =>\n canGoBackTo(browser, api, options.base, routeName),\n });\n\n const handler = createNavigateHandler({\n router,\n api,\n browser,\n isSyncingFromRouter: () => this.#isSyncingFromRouter,\n setSyncing: (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n setPendingMeta: (meta) => {\n this.#pendingMeta = meta;\n },\n base: options.base,\n transitionOptions,\n });\n\n this.#lifecycle = createNavigateLifecycle({\n browser,\n shared,\n handler,\n removeStartInterceptor: this.#removeStartInterceptor,\n removeExtensions: this.#removeExtensions,\n });\n }\n\n async traverseToLast(routeName: string): Promise<State> {\n const entries = this.#browser.entries();\n const currentKey = this.#browser.currentEntry?.key;\n const entry = findLastEntryForRoute(\n entries,\n routeName,\n this.#api,\n this.#options.base,\n currentKey,\n );\n\n if (!entry) {\n throw new Error(`No history entry for route \"${routeName}\"`);\n }\n\n if (!entry.url) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n const parsedUrl = new URL(entry.url);\n const path =\n extractPath(parsedUrl.pathname, this.#options.base) + parsedUrl.search;\n const matchedState = this.#api.matchPath(path);\n\n if (!matchedState) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n this.#pendingMeta = {\n navigationType: \"traverse\",\n userInitiated: false,\n };\n this.#pendingTraverseKey = entry.key;\n\n return this.#router.navigate(matchedState.name, matchedState.params);\n }\n\n getPlugin(): Plugin {\n return {\n ...this.#lifecycle,\n\n onTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n navOptions: NavigationOptions,\n ) => {\n if (!this.#pendingMeta) {\n this.#pendingMeta = {\n navigationType: deriveNavigationType(\n navOptions,\n toState,\n fromState,\n ),\n userInitiated: false,\n };\n }\n\n this.#metaByState.set(toState, this.#pendingMeta);\n this.#pendingMeta = undefined;\n\n this.#isSyncingFromRouter = true;\n\n if (this.#pendingTraverseKey) {\n this.#browser.traverseTo(this.#pendingTraverseKey);\n this.#pendingTraverseKey = undefined;\n } else {\n const url = this.#router.buildUrl(toState.name, toState.params);\n const shouldPreserveHash =\n !fromState || fromState.path === toState.path;\n const finalUrl = shouldPreserveHash\n ? url + this.#browser.getHash()\n : url;\n const historyState = {\n name: toState.name,\n params: toState.params,\n path: toState.path,\n };\n\n if (toState.name === UNKNOWN_ROUTE) {\n this.#browser.updateCurrentEntry({ state: historyState });\n } else {\n const replace = shouldReplaceHistory(\n navOptions,\n toState,\n fromState,\n );\n\n this.#browser.navigate(finalUrl, {\n state: historyState,\n history: replace ? \"replace\" : \"push\",\n });\n }\n }\n\n this.#isSyncingFromRouter = false;\n },\n\n onTransitionCancel: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n\n onTransitionError: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n };\n }\n}\n\ninterface NavigateLifecycleDeps {\n browser: NavigationBrowser;\n handler: (event: NavigateEvent) => void;\n removeStartInterceptor: () => void;\n removeExtensions: () => void;\n shared: NavigationSharedState;\n}\n\nfunction createNavigateLifecycle(deps: NavigateLifecycleDeps): Plugin {\n return {\n onStart() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = deps.browser.addNavigateListener(\n deps.handler,\n );\n },\n\n onStop() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n },\n\n teardown() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n deps.removeStartInterceptor();\n deps.removeExtensions();\n },\n };\n}\n","import { createWarnOnce } from \"browser-env\";\n\nimport type { NavigationBrowser } from \"./types\";\n\nconst NOOP = (): void => {};\n\nexport const createNavigationFallbackBrowser = (\n context: string,\n): NavigationBrowser => {\n const warnOnce = createWarnOnce(context);\n\n return {\n getLocation: () => {\n warnOnce(\"getLocation\");\n\n return \"/\";\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n navigate: () => {\n warnOnce(\"navigate\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n updateCurrentEntry: () => {\n warnOnce(\"updateCurrentEntry\");\n },\n traverseTo: () => {\n warnOnce(\"traverseTo\");\n },\n addNavigateListener: () => {\n warnOnce(\"addNavigateListener\");\n\n return NOOP;\n },\n entries: () => {\n warnOnce(\"entries\");\n\n return [];\n },\n currentEntry: null,\n };\n};\n","import { createOptionsValidator } from \"browser-env\";\n\nimport { LOGGER_CONTEXT, defaultOptions } from \"./constants\";\n\nimport type { NavigationPluginOptions } from \"./types\";\n\nexport const validateOptions = createOptionsValidator<NavigationPluginOptions>(\n defaultOptions,\n LOGGER_CONTEXT,\n);\n","import { getPluginApi } from \"@real-router/core/api\";\nimport { isBrowserEnvironment, normalizeBase } from \"browser-env\";\n\nimport { defaultOptions, source } from \"./constants\";\nimport { createNavigationBrowser } from \"./navigation-browser\";\nimport { NavigationPlugin } from \"./plugin\";\nimport { createNavigationFallbackBrowser } from \"./ssr-fallback\";\nimport { validateOptions } from \"./validation\";\n\nimport type {\n NavigationPluginOptions,\n NavigationBrowser,\n NavigationSharedState,\n} from \"./types\";\nimport type { PluginFactory, Router } from \"@real-router/core\";\n\nexport function navigationPluginFactory(\n opts?: Partial<NavigationPluginOptions>,\n browser?: NavigationBrowser,\n): PluginFactory {\n if (!browser && isBrowserEnvironment() && !(\"navigation\" in globalThis)) {\n throw new Error(\n \"[navigation-plugin] Navigation API is not supported. Use @real-router/browser-plugin instead.\",\n );\n }\n\n validateOptions(opts);\n\n const options: Required<NavigationPluginOptions> = {\n ...defaultOptions,\n ...opts,\n };\n\n options.base = normalizeBase(options.base);\n\n const resolvedBrowser = browser ?? createBrowser(options.base);\n\n const forceDeactivate = options.forceDeactivate;\n const transitionOptions = { forceDeactivate, source, replace: true as const };\n const shared: NavigationSharedState = { removeNavigateListener: undefined };\n\n return (routerBase) => {\n const api = getPluginApi(routerBase);\n\n const plugin = new NavigationPlugin(\n routerBase as Router,\n api,\n options,\n resolvedBrowser,\n transitionOptions,\n shared,\n );\n\n return plugin.getPlugin();\n };\n}\n\nfunction createBrowser(base: string): NavigationBrowser {\n if (\"navigation\" in globalThis) {\n return createNavigationBrowser(base);\n }\n\n return createNavigationFallbackBrowser(\"navigation-plugin\");\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["#router","#api","#options","#browser","#removeStartInterceptor","#removeExtensions","#lifecycle","#metaByState","#isSyncingFromRouter","#pendingMeta","#pendingTraverseKey"],"sources":["../../../../shared/browser-env/detect.ts","../../../../shared/browser-env/utils.ts","../../../../shared/browser-env/ssr-fallback.ts","../../../../shared/browser-env/validation.ts","../../../../shared/browser-env/plugin-utils.ts","../../../../shared/browser-env/url-parsing.ts","../../../../shared/browser-env/url-utils.ts","../../src/constants.ts","../../src/navigation-browser.ts","../../src/history-extensions.ts","../../src/navigate-handler.ts","../../src/plugin-utils.ts","../../src/plugin.ts","../../src/ssr-fallback.ts","../../src/validation.ts","../../src/factory.ts"],"sourcesContent":["export const isBrowserEnvironment = (): boolean =>\n typeof globalThis.window !== \"undefined\" && !!globalThis.history;\n","/**\n * Normalizes base path: ensures leading slash, removes trailing slash.\n *\n * @example\n * normalizeBase(\"app\") // \"/app\"\n * normalizeBase(\"/app/\") // \"/app\"\n * normalizeBase(\"\") // \"\"\n */\nexport function normalizeBase(base: string): string {\n if (!base) {\n return base;\n }\n\n let result = base;\n\n if (!result.startsWith(\"/\")) {\n result = `/${result}`;\n }\n\n if (result.endsWith(\"/\")) {\n result = result.slice(0, -1);\n }\n\n return result;\n}\n\nexport const safelyEncodePath = (path: string): string => {\n try {\n return encodeURI(decodeURI(path));\n } catch (error) {\n console.warn(`[browser-env] Could not encode path \"${path}\"`, error);\n\n return path;\n }\n};\n","import type { HistoryBrowser } from \"./types.js\";\n\nconst NOOP = (): void => {};\n\nexport const createWarnOnce = (context: string) => {\n let hasWarned = false;\n\n return (method: string): void => {\n if (!hasWarned) {\n console.warn(\n `[browser-env] Browser API is running in a non-browser environment (context: \"${context}\"). ` +\n `Method \"${method}\" is a no-op. ` +\n `This is expected for SSR, but may indicate misconfiguration if you expected browser behavior.`,\n );\n hasWarned = true;\n }\n };\n};\n\nexport const createHistoryFallbackBrowser = (\n context: string,\n): HistoryBrowser => {\n const warnOnce = createWarnOnce(context);\n\n return {\n pushState: () => {\n warnOnce(\"pushState\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n addPopstateListener: () => {\n warnOnce(\"addPopstateListener\");\n\n return NOOP;\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n };\n};\n","export function createOptionsValidator<T extends object>(\n defaults: Required<T>,\n loggerContext: string,\n): (opts: Partial<T> | undefined) => void {\n return (opts) => {\n if (!opts) {\n return;\n }\n\n for (const key of Object.keys(opts)) {\n if (key in defaults) {\n const value = opts[key as keyof typeof opts];\n const expected = typeof defaults[key as keyof typeof defaults];\n const actual = typeof value;\n\n if (value !== undefined && actual !== expected) {\n throw new Error(\n `[${loggerContext}] Invalid type for '${key}': expected ${expected}, got ${actual}`,\n );\n }\n }\n }\n };\n}\n","import { updateBrowserState } from \"./popstate-utils.js\";\n\nimport type { Browser } from \"./types.js\";\nimport type {\n NavigationOptions,\n Params,\n Router,\n State,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nexport function createStartInterceptor(\n api: PluginApi,\n browser: Browser,\n): () => void {\n return api.addInterceptor(\"start\", (next, path) =>\n next(path ?? browser.getLocation()),\n );\n}\n\nexport function createReplaceHistoryState(\n api: PluginApi,\n router: Router,\n browser: Browser,\n buildUrl: (name: string, params?: Params) => string,\n): (name: string, params?: Params) => void {\n return (name: string, params: Params = {}) => {\n const state = api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n );\n\n updateBrowserState(builtState, buildUrl(name, params), true, browser);\n };\n}\n\nexport function shouldReplaceHistory(\n navOptions: NavigationOptions,\n toState: State,\n fromState: State | undefined,\n): boolean {\n return (\n (navOptions.replace ?? !fromState) ||\n (!!navOptions.reload && toState.path === fromState.path)\n );\n}\n","export function safeParseUrl(url: string, loggerContext: string): URL | null {\n try {\n const parsedUrl = new URL(url, globalThis.location.origin);\n\n if (![\"http:\", \"https:\"].includes(parsedUrl.protocol)) {\n console.warn(`[${loggerContext}] Invalid URL protocol in ${url}`);\n\n return null;\n }\n\n return parsedUrl;\n } catch (error) {\n console.warn(`[${loggerContext}] Could not parse url ${url}`, error);\n\n return null;\n }\n}\n","import { safeParseUrl } from \"./url-parsing.js\";\n\nexport function extractPath(pathname: string, base: string): string {\n if (base && pathname.startsWith(base)) {\n const stripped = pathname.slice(base.length);\n\n return stripped.startsWith(\"/\") ? stripped : `/${stripped}`;\n }\n\n return pathname;\n}\n\nexport function buildUrl(path: string, base: string): string {\n return base + path;\n}\n\nexport function urlToPath(\n url: string,\n base: string,\n context: string,\n): string | null {\n const parsedUrl = safeParseUrl(url, context);\n\n return parsedUrl\n ? extractPath(parsedUrl.pathname, base) + parsedUrl.search\n : null;\n}\n","import type { NavigationPluginOptions } from \"./types\";\n\nexport const defaultOptions: Required<NavigationPluginOptions> = {\n forceDeactivate: true,\n base: \"\",\n};\n\n/**\n * Source identifier for transitions triggered by navigate events.\n * Distinguishes browser-initiated navigation (back/forward, link clicks)\n * from programmatic navigation (router.navigate()).\n */\nexport const source = \"navigate\";\n\nexport const LOGGER_CONTEXT = \"navigation-plugin\";\n","import { safelyEncodePath, extractPath } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser } from \"./types\";\n\n/**\n * Creates a NavigationBrowser wrapping the real Navigation API.\n * Only call this when `\"navigation\" in globalThis` is true.\n */\nexport function createNavigationBrowser(base: string): NavigationBrowser {\n const nav = globalThis.navigation;\n\n return {\n getLocation: () =>\n safelyEncodePath(extractPath(globalThis.location.pathname, base)) +\n globalThis.location.search,\n\n getHash: () => globalThis.location.hash,\n\n navigate: (url, options) => {\n nav.navigate(url, {\n state: options.state,\n history: options.history,\n });\n },\n\n replaceState: (state, url) => {\n nav.navigate(url, {\n state,\n history: \"replace\",\n });\n },\n\n updateCurrentEntry: (options) => {\n nav.updateCurrentEntry(options);\n },\n\n traverseTo: (key) => {\n nav.traverseTo(key);\n },\n\n addNavigateListener: (fn) => {\n nav.addEventListener(\"navigate\", fn);\n\n return () => {\n nav.removeEventListener(\"navigate\", fn);\n };\n },\n\n entries: () => nav.entries(),\n\n get currentEntry() {\n return nav.currentEntry;\n },\n };\n}\n","import { extractPath } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser } from \"./types\";\nimport type { State } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Converts a NavigationHistoryEntry to a State via URL matching.\n * Uses URL matching (not entry.getState()) because:\n * - Entries before plugin init have no state\n * - Entries after router.replace(routes) may have stale state\n * - Entries from other SPAs on the same origin have foreign state\n */\nexport function entryToState(\n entry: NavigationHistoryEntry | undefined,\n api: PluginApi,\n base: string,\n): State | undefined {\n if (!entry?.url) {\n return undefined;\n }\n\n const pathname = new URL(entry.url).pathname;\n const path = extractPath(pathname, base);\n\n return api.matchPath(path) ?? undefined;\n}\n\nfunction peekAt(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n offset: number,\n): State | undefined {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return undefined;\n }\n\n return entryToState(browser.entries()[idx + offset], api, base);\n}\n\nexport function peekBack(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, -1);\n}\n\nexport function peekForward(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): State | undefined {\n return peekAt(browser, api, base, 1);\n}\n\nexport function hasVisited(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n return browser.entries().some((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n });\n}\n\nexport function getVisitedRoutes(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n): string[] {\n const names = new Set<string>();\n\n for (const entry of browser.entries()) {\n const state = entryToState(entry, api, base);\n\n if (state) {\n names.add(state.name);\n }\n }\n\n return [...names];\n}\n\nexport function getRouteVisitCount(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): number {\n return browser.entries().filter((entry) => {\n const state = entryToState(entry, api, base);\n\n return state?.name === routeName;\n }).length;\n}\n\n/**\n * Finds the last NavigationHistoryEntry matching the given route name,\n * excluding the current entry (to avoid SAME_STATES on traverseToLast(\"current-route\")).\n */\nexport function findLastEntryForRoute(\n entries: NavigationHistoryEntry[],\n routeName: string,\n api: PluginApi,\n base: string,\n currentKey: string | undefined,\n): NavigationHistoryEntry | undefined {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n\n if (entry.key === currentKey) {\n continue;\n }\n\n const state = entryToState(entry, api, base);\n\n if (state?.name === routeName) {\n return entry;\n }\n }\n\n return undefined;\n}\n\nexport function canGoBack(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n return idx != null && idx > 0;\n}\n\nexport function canGoForward(browser: NavigationBrowser): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n return idx < browser.entries().length - 1;\n}\n\nexport function canGoBackTo(\n browser: NavigationBrowser,\n api: PluginApi,\n base: string,\n routeName: string,\n): boolean {\n const idx = browser.currentEntry?.index;\n\n if (idx == null) {\n return false;\n }\n\n const entries = browser.entries();\n\n for (let i = idx - 1; i >= 0; i--) {\n const state = entryToState(entries[i], api, base);\n\n if (state?.name === routeName) {\n return true;\n }\n }\n\n return false;\n}\n","import { RouterError } from \"@real-router/core\";\n\nimport { extractPath } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser, NavigationMeta } from \"./types\";\nimport type { Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\ninterface NavigateHandlerDeps {\n router: Router;\n api: PluginApi;\n browser: NavigationBrowser;\n isSyncingFromRouter: () => boolean;\n setSyncing: (value: boolean) => void;\n setPendingMeta: (meta: NavigationMeta) => void;\n base: string;\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n };\n}\n\nexport function createNavigateHandler(deps: NavigateHandlerDeps) {\n const {\n router,\n api,\n browser,\n isSyncingFromRouter,\n setSyncing,\n base,\n transitionOptions,\n } = deps;\n const { allowNotFound } = api.getOptions();\n\n return function handleNavigateEvent(event: NavigateEvent): void {\n if (!event.canIntercept) {\n return;\n }\n if (isSyncingFromRouter()) {\n return;\n }\n if (!router.isActive()) {\n return;\n }\n\n const destinationUrl = new URL(event.destination.url);\n const path =\n extractPath(destinationUrl.pathname, base) + destinationUrl.search;\n const matchedState = api.matchPath(path);\n\n // Set pendingMeta BEFORE event.intercept() — available in guards via getNavigationMeta()\n deps.setPendingMeta({\n navigationType: event.navigationType as NavigationMeta[\"navigationType\"],\n userInitiated: event.userInitiated,\n info: event.info,\n });\n\n if (matchedState) {\n event.intercept({\n handler: async () => {\n try {\n await router.navigate(matchedState.name, matchedState.params, {\n ...transitionOptions,\n signal: event.signal,\n });\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n } else if (allowNotFound) {\n event.intercept({\n handler: () => {\n router.navigateToNotFound(path);\n },\n });\n } else {\n event.intercept({\n handler: async () => {\n try {\n await router.navigateToDefault();\n } catch (error) {\n if (!(error instanceof RouterError)) {\n recoverFromNavigateError(error, router, browser, setSyncing);\n }\n }\n },\n });\n }\n };\n}\n\nfunction recoverFromNavigateError(\n error: unknown,\n router: Router,\n browser: NavigationBrowser,\n setSyncing: (value: boolean) => void,\n): void {\n console.error(\n \"[navigation-plugin] Critical error in navigate handler\",\n error,\n );\n\n try {\n const currentState = router.getState();\n\n if (currentState) {\n const url = router.buildUrl(currentState.name, currentState.params);\n\n setSyncing(true);\n browser.navigate(url, {\n state: {\n name: currentState.name,\n params: currentState.params,\n path: currentState.path,\n },\n history: \"replace\",\n });\n setSyncing(false);\n }\n } catch (recoveryError) {\n console.error(\n \"[navigation-plugin] Failed to recover from critical error\",\n recoveryError,\n );\n }\n}\n","import type { NavigationBrowser } from \"./types\";\nimport type { Params, Router } from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\n/**\n * Makes `router.start()` path optional by injecting browser location.\n * Identical to browser-env's createStartInterceptor, adapted for NavigationBrowser.\n */\nexport function createStartInterceptor(\n api: PluginApi,\n browser: NavigationBrowser,\n): () => void {\n return api.addInterceptor(\"start\", (next, path) =>\n next(path ?? browser.getLocation()),\n );\n}\n\n/**\n * Creates replaceHistoryState extension for NavigationBrowser.\n *\n * IMPORTANT: Must set isSyncingFromRouter=true before calling browser.replaceState\n * because navigation.navigate({history:\"replace\"}) fires a navigate event.\n * Without this flag, the navigate handler would trigger a full navigation.\n */\nexport function createReplaceHistoryState(\n api: PluginApi,\n router: Router,\n browser: NavigationBrowser,\n buildUrl: (name: string, params?: Params) => string,\n setSyncing: (value: boolean) => void,\n): (name: string, params?: Params) => void {\n return (name: string, params: Params = {}) => {\n const state = api.buildState(name, params);\n\n if (!state) {\n throw new Error(\n `[real-router] Cannot replace state: route \"${name}\" is not found`,\n );\n }\n\n const builtState = api.makeState(\n state.name,\n state.params,\n router.buildPath(state.name, state.params),\n {\n params: state.meta,\n },\n );\n\n const url = buildUrl(name, params);\n const historyState = {\n name: builtState.name,\n params: builtState.params,\n path: builtState.path,\n };\n\n setSyncing(true);\n browser.replaceState(historyState, url);\n setSyncing(false);\n };\n}\n","import { UNKNOWN_ROUTE } from \"@real-router/core\";\n\nimport {\n shouldReplaceHistory,\n buildUrl,\n extractPath,\n urlToPath,\n} from \"./browser-env/index.js\";\nimport { LOGGER_CONTEXT } from \"./constants\";\nimport {\n peekBack,\n peekForward,\n hasVisited,\n getVisitedRoutes,\n getRouteVisitCount,\n findLastEntryForRoute,\n canGoBack,\n canGoForward,\n canGoBackTo,\n} from \"./history-extensions\";\nimport { createNavigateHandler } from \"./navigate-handler\";\nimport {\n createStartInterceptor,\n createReplaceHistoryState,\n} from \"./plugin-utils\";\n\nimport type {\n NavigationBrowser,\n NavigationMeta,\n NavigationPluginOptions,\n NavigationSharedState,\n} from \"./types\";\nimport type {\n NavigationOptions,\n Params,\n Router,\n State,\n Plugin,\n} from \"@real-router/core\";\nimport type { PluginApi } from \"@real-router/core/api\";\n\nfunction deriveNavigationType(\n navOptions: NavigationOptions,\n toState: State,\n fromState: State | undefined,\n): NavigationMeta[\"navigationType\"] {\n if (navOptions.reload && toState.path === fromState?.path) {\n return \"reload\";\n }\n\n if (shouldReplaceHistory(navOptions, toState, fromState)) {\n return \"replace\";\n }\n\n return \"push\";\n}\n\nexport class NavigationPlugin {\n readonly #router: Router;\n readonly #api: PluginApi;\n readonly #options: Required<NavigationPluginOptions>;\n readonly #browser: NavigationBrowser;\n readonly #removeStartInterceptor: () => void;\n readonly #removeExtensions: () => void;\n readonly #lifecycle: Pick<Plugin, \"onStart\" | \"onStop\" | \"teardown\">;\n\n #isSyncingFromRouter = false;\n readonly #metaByState = new WeakMap<State, NavigationMeta>();\n #pendingMeta: NavigationMeta | undefined;\n #pendingTraverseKey: string | undefined;\n\n constructor(\n router: Router,\n api: PluginApi,\n options: Required<NavigationPluginOptions>,\n browser: NavigationBrowser,\n transitionOptions: {\n source: string;\n replace: true;\n forceDeactivate?: boolean;\n },\n shared: NavigationSharedState,\n ) {\n this.#router = router;\n this.#api = api;\n this.#options = options;\n this.#browser = browser;\n\n this.#removeStartInterceptor = createStartInterceptor(api, browser);\n\n const pluginBuildUrl = (route: string, params?: Params) => {\n const path = router.buildPath(route, params);\n\n return buildUrl(path, options.base);\n };\n\n this.#removeExtensions = api.extendRouter({\n buildUrl: pluginBuildUrl,\n matchUrl: (url: string) => {\n const path = urlToPath(url, options.base, LOGGER_CONTEXT);\n\n return path ? api.matchPath(path) : undefined;\n },\n replaceHistoryState: createReplaceHistoryState(\n api,\n router,\n browser,\n pluginBuildUrl,\n (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n ),\n\n peekBack: () => peekBack(browser, api, options.base),\n peekForward: () => peekForward(browser, api, options.base),\n hasVisited: (routeName: string) =>\n hasVisited(browser, api, options.base, routeName),\n getVisitedRoutes: () => getVisitedRoutes(browser, api, options.base),\n getRouteVisitCount: (routeName: string) =>\n getRouteVisitCount(browser, api, options.base, routeName),\n traverseToLast: (routeName: string) => this.traverseToLast(routeName),\n getNavigationMeta: (state?: State): NavigationMeta | undefined => {\n if (!state) {\n return this.#pendingMeta;\n }\n\n return this.#metaByState.get(state);\n },\n canGoBack: () => canGoBack(browser),\n canGoForward: () => canGoForward(browser),\n canGoBackTo: (routeName: string) =>\n canGoBackTo(browser, api, options.base, routeName),\n });\n\n const handler = createNavigateHandler({\n router,\n api,\n browser,\n isSyncingFromRouter: () => this.#isSyncingFromRouter,\n setSyncing: (syncing) => {\n this.#isSyncingFromRouter = syncing;\n },\n setPendingMeta: (meta) => {\n this.#pendingMeta = meta;\n },\n base: options.base,\n transitionOptions,\n });\n\n this.#lifecycle = createNavigateLifecycle({\n browser,\n shared,\n handler,\n removeStartInterceptor: this.#removeStartInterceptor,\n removeExtensions: this.#removeExtensions,\n });\n }\n\n async traverseToLast(routeName: string): Promise<State> {\n const entries = this.#browser.entries();\n const currentKey = this.#browser.currentEntry?.key;\n const entry = findLastEntryForRoute(\n entries,\n routeName,\n this.#api,\n this.#options.base,\n currentKey,\n );\n\n if (!entry) {\n throw new Error(`No history entry for route \"${routeName}\"`);\n }\n\n if (!entry.url) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n const parsedUrl = new URL(entry.url);\n const path =\n extractPath(parsedUrl.pathname, this.#options.base) + parsedUrl.search;\n const matchedState = this.#api.matchPath(path);\n\n if (!matchedState) {\n throw new Error(`No matching route for entry URL \"${entry.url}\"`);\n }\n\n this.#pendingMeta = {\n navigationType: \"traverse\",\n userInitiated: false,\n };\n this.#pendingTraverseKey = entry.key;\n\n return this.#router.navigate(matchedState.name, matchedState.params);\n }\n\n getPlugin(): Plugin {\n return {\n ...this.#lifecycle,\n\n onTransitionSuccess: (\n toState: State,\n fromState: State | undefined,\n navOptions: NavigationOptions,\n ) => {\n if (!this.#pendingMeta) {\n this.#pendingMeta = {\n navigationType: deriveNavigationType(\n navOptions,\n toState,\n fromState,\n ),\n userInitiated: false,\n };\n }\n\n this.#metaByState.set(toState, this.#pendingMeta);\n this.#pendingMeta = undefined;\n\n this.#isSyncingFromRouter = true;\n\n if (this.#pendingTraverseKey) {\n this.#browser.traverseTo(this.#pendingTraverseKey);\n this.#pendingTraverseKey = undefined;\n } else {\n const url = this.#router.buildUrl(toState.name, toState.params);\n const shouldPreserveHash =\n !fromState || fromState.path === toState.path;\n const finalUrl = shouldPreserveHash\n ? url + this.#browser.getHash()\n : url;\n const historyState = {\n name: toState.name,\n params: toState.params,\n path: toState.path,\n };\n\n if (toState.name === UNKNOWN_ROUTE) {\n this.#browser.updateCurrentEntry({ state: historyState });\n } else {\n const replace = shouldReplaceHistory(\n navOptions,\n toState,\n fromState,\n );\n\n this.#browser.navigate(finalUrl, {\n state: historyState,\n history: replace ? \"replace\" : \"push\",\n });\n }\n }\n\n this.#isSyncingFromRouter = false;\n },\n\n onTransitionCancel: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n\n onTransitionError: () => {\n this.#pendingMeta = undefined;\n this.#pendingTraverseKey = undefined;\n },\n };\n }\n}\n\ninterface NavigateLifecycleDeps {\n browser: NavigationBrowser;\n handler: (event: NavigateEvent) => void;\n removeStartInterceptor: () => void;\n removeExtensions: () => void;\n shared: NavigationSharedState;\n}\n\nfunction createNavigateLifecycle(deps: NavigateLifecycleDeps): Plugin {\n return {\n onStart() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = deps.browser.addNavigateListener(\n deps.handler,\n );\n },\n\n onStop() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n },\n\n teardown() {\n deps.shared.removeNavigateListener?.();\n deps.shared.removeNavigateListener = undefined;\n deps.removeStartInterceptor();\n deps.removeExtensions();\n },\n };\n}\n","import { createWarnOnce } from \"./browser-env/index.js\";\n\nimport type { NavigationBrowser } from \"./types\";\n\nconst NOOP = (): void => {};\n\nexport const createNavigationFallbackBrowser = (\n context: string,\n): NavigationBrowser => {\n const warnOnce = createWarnOnce(context);\n\n return {\n getLocation: () => {\n warnOnce(\"getLocation\");\n\n return \"/\";\n },\n getHash: () => {\n warnOnce(\"getHash\");\n\n return \"\";\n },\n navigate: () => {\n warnOnce(\"navigate\");\n },\n replaceState: () => {\n warnOnce(\"replaceState\");\n },\n updateCurrentEntry: () => {\n warnOnce(\"updateCurrentEntry\");\n },\n traverseTo: () => {\n warnOnce(\"traverseTo\");\n },\n addNavigateListener: () => {\n warnOnce(\"addNavigateListener\");\n\n return NOOP;\n },\n entries: () => {\n warnOnce(\"entries\");\n\n return [];\n },\n currentEntry: null,\n };\n};\n","import { createOptionsValidator } from \"./browser-env/index.js\";\nimport { LOGGER_CONTEXT, defaultOptions } from \"./constants\";\n\nimport type { NavigationPluginOptions } from \"./types\";\n\nexport const validateOptions = createOptionsValidator<NavigationPluginOptions>(\n defaultOptions,\n LOGGER_CONTEXT,\n);\n","import { getPluginApi } from \"@real-router/core/api\";\n\nimport { isBrowserEnvironment, normalizeBase } from \"./browser-env/index.js\";\nimport { defaultOptions, source } from \"./constants\";\nimport { createNavigationBrowser } from \"./navigation-browser\";\nimport { NavigationPlugin } from \"./plugin\";\nimport { createNavigationFallbackBrowser } from \"./ssr-fallback\";\nimport { validateOptions } from \"./validation\";\n\nimport type {\n NavigationPluginOptions,\n NavigationBrowser,\n NavigationSharedState,\n} from \"./types\";\nimport type { PluginFactory, Router } from \"@real-router/core\";\n\nexport function navigationPluginFactory(\n opts?: Partial<NavigationPluginOptions>,\n browser?: NavigationBrowser,\n): PluginFactory {\n if (!browser && isBrowserEnvironment() && !(\"navigation\" in globalThis)) {\n throw new Error(\n \"[navigation-plugin] Navigation API is not supported. Use @real-router/browser-plugin instead.\",\n );\n }\n\n validateOptions(opts);\n\n const options: Required<NavigationPluginOptions> = {\n ...defaultOptions,\n ...opts,\n };\n\n options.base = normalizeBase(options.base);\n\n const resolvedBrowser = browser ?? createBrowser(options.base);\n\n const forceDeactivate = options.forceDeactivate;\n const transitionOptions = { forceDeactivate, source, replace: true as const };\n const shared: NavigationSharedState = { removeNavigateListener: undefined };\n\n return (routerBase) => {\n const api = getPluginApi(routerBase);\n\n const plugin = new NavigationPlugin(\n routerBase as Router,\n api,\n options,\n resolvedBrowser,\n transitionOptions,\n shared,\n );\n\n return plugin.getPlugin();\n };\n}\n\nfunction createBrowser(base: string): NavigationBrowser {\n if (\"navigation\" in globalThis) {\n return createNavigationBrowser(base);\n }\n\n return createNavigationFallbackBrowser(\"navigation-plugin\");\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/navigation-plugin",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "commonjs",
5
5
  "description": "Navigation API integration plugin for browser URL synchronization",
6
6
  "main": "./dist/cjs/index.js",
@@ -8,6 +8,7 @@
8
8
  "types": "./dist/esm/index.d.mts",
9
9
  "exports": {
10
10
  ".": {
11
+ "@real-router/internal-source": "./src/index.ts",
11
12
  "types": {
12
13
  "import": "./dist/esm/index.d.mts",
13
14
  "require": "./dist/cjs/index.d.ts"
@@ -43,11 +44,11 @@
43
44
  },
44
45
  "sideEffects": false,
45
46
  "dependencies": {
46
- "@real-router/core": "^0.46.0"
47
+ "@real-router/core": "^0.47.0"
47
48
  },
48
49
  "devDependencies": {
49
50
  "jsdom": "28.1.0",
50
- "browser-env": "^0.2.6"
51
+ "type-guards": "^0.4.7"
51
52
  },
52
53
  "scripts": {
53
54
  "test": "vitest",
@@ -57,7 +58,6 @@
57
58
  "type-check": "tsc --noEmit",
58
59
  "lint": "eslint --cache --ext .ts src/ tests/ --fix --max-warnings 0",
59
60
  "lint:package": "publint",
60
- "lint:types": "attw --pack .",
61
- "build:dist-only": "tsdown --config-loader unrun"
61
+ "lint:types": "attw --pack ."
62
62
  }
63
63
  }
package/src/factory.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getPluginApi } from "@real-router/core/api";
2
- import { isBrowserEnvironment, normalizeBase } from "browser-env";
3
2
 
3
+ import { isBrowserEnvironment, normalizeBase } from "./browser-env/index.js";
4
4
  import { defaultOptions, source } from "./constants";
5
5
  import { createNavigationBrowser } from "./navigation-browser";
6
6
  import { NavigationPlugin } from "./plugin";
@@ -1,4 +1,4 @@
1
- import { extractPath } from "browser-env";
1
+ import { extractPath } from "./browser-env/index.js";
2
2
 
3
3
  import type { NavigationBrowser } from "./types";
4
4
  import type { State } from "@real-router/core";
@@ -1,5 +1,6 @@
1
1
  import { RouterError } from "@real-router/core";
2
- import { extractPath } from "browser-env";
2
+
3
+ import { extractPath } from "./browser-env/index.js";
3
4
 
4
5
  import type { NavigationBrowser, NavigationMeta } from "./types";
5
6
  import type { Router } from "@real-router/core";
@@ -1,4 +1,4 @@
1
- import { safelyEncodePath, extractPath } from "browser-env";
1
+ import { safelyEncodePath, extractPath } from "./browser-env/index.js";
2
2
 
3
3
  import type { NavigationBrowser } from "./types";
4
4
 
package/src/plugin.ts CHANGED
@@ -1,11 +1,11 @@
1
1
  import { UNKNOWN_ROUTE } from "@real-router/core";
2
+
2
3
  import {
3
4
  shouldReplaceHistory,
4
5
  buildUrl,
5
6
  extractPath,
6
7
  urlToPath,
7
- } from "browser-env";
8
-
8
+ } from "./browser-env/index.js";
9
9
  import { LOGGER_CONTEXT } from "./constants";
10
10
  import {
11
11
  peekBack,
@@ -1,4 +1,4 @@
1
- import { createWarnOnce } from "browser-env";
1
+ import { createWarnOnce } from "./browser-env/index.js";
2
2
 
3
3
  import type { NavigationBrowser } from "./types";
4
4
 
package/src/validation.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { createOptionsValidator } from "browser-env";
2
-
1
+ import { createOptionsValidator } from "./browser-env/index.js";
3
2
  import { LOGGER_CONTEXT, defaultOptions } from "./constants";
4
3
 
5
4
  import type { NavigationPluginOptions } from "./types";