@revealui/router 0.3.0 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import React from 'react';
3
- import { R as Router, L as Location, a as RouteMatch, N as NavigateOptions } from './router-B2MrlNC3.js';
4
- export { M as MiddlewareContext, b as Route, c as RouteMeta, d as RouteMiddleware, e as RouteParams, f as RouterOptions } from './router-B2MrlNC3.js';
3
+ import { R as Router, L as Location, a as RouteMatch, N as NavigateOptions } from './router-SBtAXNTB.js';
4
+ export { M as MiddlewareContext, b as Route, c as RouteMeta, d as RouteMiddleware, e as RouteParams, f as RouterOptions } from './router-SBtAXNTB.js';
5
5
 
6
6
  /**
7
7
  * RouterProvider - Provides router instance to the app
@@ -14,18 +14,18 @@ declare function RouterProvider({ router, children, }: {
14
14
  * Routes - Renders the matched route component
15
15
  */
16
16
  declare function Routes(): react_jsx_runtime.JSX.Element;
17
- /**
18
- * Link - Client-side navigation link
19
- */
20
- declare function Link({ to, replace, children, className, style, onClick, ...props }: {
17
+ interface LinkProps extends Record<string, unknown> {
21
18
  to: string;
22
19
  replace?: boolean;
23
20
  children: React.ReactNode;
24
21
  className?: string;
25
22
  style?: React.CSSProperties;
26
23
  onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;
27
- [key: string]: unknown;
28
- }): react_jsx_runtime.JSX.Element;
24
+ }
25
+ /**
26
+ * Link - Client-side navigation link
27
+ */
28
+ declare function Link({ to, replace, children, className, style, onClick, ...props }: LinkProps): react_jsx_runtime.JSX.Element;
29
29
  /**
30
30
  * useRouter - Hook to access router instance
31
31
  */
package/dist/index.js CHANGED
@@ -437,9 +437,8 @@ var Router = class {
437
437
  if (typeof window === "undefined") {
438
438
  return;
439
439
  }
440
- const g = globalThis;
441
- if (g.__revealui_router_initialized) return;
442
- g.__revealui_router_initialized = true;
440
+ if (globalThis.__revealui_router_initialized) return;
441
+ globalThis.__revealui_router_initialized = true;
443
442
  this.popstateHandler = () => {
444
443
  this.lastPathname = window.location.pathname;
445
444
  this.currentMatch = this.match(window.location.pathname);
@@ -472,8 +471,7 @@ var Router = class {
472
471
  this.clickHandler = null;
473
472
  }
474
473
  this.listeners.clear();
475
- const g = globalThis;
476
- g.__revealui_router_initialized = false;
474
+ globalThis.__revealui_router_initialized = false;
477
475
  }
478
476
  };
479
477
  function joinPaths(parent, child) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components.tsx","../src/router.ts"],"sourcesContent":["import type React from 'react';\nimport {\n Component,\n createContext,\n use,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport type { Router } from './router';\nimport type { Location, NavigateOptions, RouteMatch } from './types';\n\n/**\n * Router context\n */\nconst RouterContext = createContext<Router | null>(null);\n\n/**\n * Current match context\n */\nconst MatchContext = createContext<RouteMatch | null>(null);\n\n/**\n * RouterProvider - Provides router instance to the app\n */\nexport function RouterProvider({\n router,\n children,\n}: {\n router: Router;\n children: React.ReactNode;\n}) {\n return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>;\n}\n\n/**\n * Routes - Renders the matched route component\n */\nexport function Routes() {\n const router = useRouter();\n const options = router.getOptions();\n\n // Subscribe to router changes\n const match = useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => router.getCurrentMatch(),\n () => router.getCurrentMatch(), // Server-side snapshot (same as client)\n );\n\n if (!match) {\n const CustomNotFound = options.notFound;\n return CustomNotFound ? <CustomNotFound /> : <NotFound />;\n }\n\n const { route, params, data } = match;\n const RouteComponent = route.component;\n const Layout = route.layout;\n\n const element = <RouteComponent params={params} data={data} />;\n const wrapped = Layout ? <Layout>{element}</Layout> : element;\n\n return (\n <MatchContext.Provider value={match}>\n {options.errorBoundary ? (\n <RouteErrorBoundary fallback={options.errorBoundary}>{wrapped}</RouteErrorBoundary>\n ) : (\n wrapped\n )}\n </MatchContext.Provider>\n );\n}\n\n/**\n * Link - Client-side navigation link\n */\nexport function Link({\n to,\n replace = false,\n children,\n className,\n style,\n onClick,\n ...props\n}: {\n to: string;\n replace?: boolean;\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;\n [key: string]: unknown;\n}) {\n const router = useRouter();\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n onClick?.(e);\n\n // Don't navigate if default was prevented\n if (e.defaultPrevented) {\n return;\n }\n\n // Only handle left clicks\n if (e.button !== 0) {\n return;\n }\n\n // Ignore if modifier keys are pressed\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n e.preventDefault();\n router.navigate(to, { replace });\n };\n\n return (\n <a href={to} onClick={handleClick} className={className} style={style} {...props}>\n {children}\n </a>\n );\n}\n\n/**\n * useRouter - Hook to access router instance\n */\nexport function useRouter(): Router {\n const router = use(RouterContext);\n\n if (!router) {\n throw new Error('useRouter must be used within a RouterProvider');\n }\n\n return router;\n}\n\n/**\n * useMatch - Hook to access current route match\n */\nexport function useMatch(): RouteMatch | null {\n return use(MatchContext);\n}\n\n/**\n * useParams - Hook to access route parameters\n */\nexport function useParams<T = Record<string, string>>(): T {\n const match = useMatch();\n return (match?.params as T) || ({} as T);\n}\n\n/**\n * useData - Hook to access route data\n */\nexport function useData<T = unknown>(): T | undefined {\n const match = useMatch();\n return match?.data as T | undefined;\n}\n\n/**\n * useNavigate - Hook to get navigation function\n */\nexport function useNavigate(): (to: string, options?: NavigateOptions) => void {\n const router = useRouter();\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options);\n };\n}\n\nconst SERVER_LOCATION: Location = { pathname: '/', search: '', hash: '' };\n\nfunction getWindowLocation(): Location {\n if (typeof window === 'undefined') return SERVER_LOCATION;\n return {\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n };\n}\n\n/**\n * useLocation - Hook to access current location (pathname, search, hash).\n * Returns a stable reference that only changes when the URL actually changes.\n */\nexport function useLocation(): Location {\n const router = useRouter();\n const locationRef = useRef<Location>(getWindowLocation());\n\n // Re-subscribe on route changes to detect URL updates\n useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname + window.location.search + window.location.hash;\n },\n () => '/',\n );\n\n const current = getWindowLocation();\n if (\n current.pathname !== locationRef.current.pathname ||\n current.search !== locationRef.current.search ||\n current.hash !== locationRef.current.hash\n ) {\n locationRef.current = current;\n }\n\n return locationRef.current;\n}\n\n/**\n * useSearchParams - Hook to access parsed query string parameters\n */\nexport function useSearchParams(): URLSearchParams {\n const { search } = useLocation();\n return useMemo(() => new URLSearchParams(search), [search]);\n}\n\n/**\n * NotFound - Default 404 component\n */\nfunction NotFound() {\n return (\n <div style={{ padding: '2rem', textAlign: 'center' }}>\n <h1>404 - Page Not Found</h1>\n <p>The page you're looking for doesn't exist.</p>\n <Link to=\"/\">Go Home</Link>\n </div>\n );\n}\n\n/**\n * RouteErrorBoundary - Catches render errors in route components\n */\nclass RouteErrorBoundary extends Component<\n { fallback: React.ComponentType<{ error: Error }>; children: React.ReactNode },\n { error: Error | null }\n> {\n constructor(props: {\n fallback: React.ComponentType<{ error: Error }>;\n children: React.ReactNode;\n }) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error): { error: Error } {\n return { error };\n }\n\n render() {\n if (this.state.error) {\n const Fallback = this.props.fallback;\n return <Fallback error={this.state.error} />;\n }\n return this.props.children;\n }\n}\n\n/**\n * Navigate - Component for declarative navigation\n */\nexport function Navigate({ to, replace = false }: { to: string; replace?: boolean }) {\n const router = useRouter();\n\n useEffect(() => {\n router.navigate(to, { replace });\n }, [to, replace, router]);\n\n return null;\n}\n","import { logger } from '@revealui/core/observability/logger';\nimport type React from 'react';\nimport { createElement } from 'react';\nimport type {\n MiddlewareContext,\n NavigateOptions,\n Route,\n RouteMatch,\n RouteMiddleware,\n RouteParams,\n RouterOptions,\n} from './types';\n\n// ---------------------------------------------------------------------------\n// Hand-rolled path matcher — replaces `path-to-regexp`.\n// Supports: exact paths, named params (:id), wildcards (*path),\n// and optional segments ({/...}).\n// ---------------------------------------------------------------------------\n\ninterface PathKey {\n name: string;\n wildcard: boolean;\n}\n\n/** Maximum pattern length to prevent ReDoS from malicious/misconfigured routes */\nconst MAX_PATTERN_LENGTH = 2048;\n\nfunction compilePathPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new Error(`Route pattern exceeds ${MAX_PATTERN_LENGTH} characters`);\n }\n const keys: PathKey[] = [];\n let src = '^';\n let i = 0;\n while (i < pattern.length) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const ch = pattern[i]!;\n if (ch === '{') {\n src += '(?:';\n i++;\n } else if (ch === '}') {\n src += ')?';\n i++;\n } else if (ch === ':') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name, wildcard: false });\n src += '([^/]+)';\n } else if (ch === '*') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name: name || '0', wildcard: true });\n src += '(.+)';\n } else {\n src += ch.replace(/[.+?^$|()[\\]\\\\]/g, '\\\\$&');\n i++;\n }\n }\n src += '$';\n return { regex: new RegExp(src), keys };\n}\n\n/** Cache compiled patterns to avoid recompilation on every match() call */\nconst patternCache = new Map<string, { regex: RegExp; keys: PathKey[] }>();\n\nfunction getCompiledPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n let compiled = patternCache.get(pattern);\n if (!compiled) {\n compiled = compilePathPattern(pattern);\n patternCache.set(pattern, compiled);\n }\n return compiled;\n}\n\nfunction pathMatch(\n pattern: string,\n options: { decode?: (s: string) => string } = {},\n): (path: string) => { params: Record<string, string | string[]> } | false {\n const { regex, keys } = getCompiledPattern(pattern);\n const decode = options.decode ?? ((s) => s);\n return (path: string) => {\n const m = regex.exec(path);\n if (!m) return false;\n const params: Record<string, string | string[]> = {};\n for (let j = 0; j < keys.length; j++) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const key = keys[j]!;\n const val = m[j + 1];\n if (val === undefined) continue;\n params[key.name] = key.wildcard ? val.split('/').map(decode) : decode(val);\n }\n return { params };\n };\n}\n\n/**\n * RevealUI Router - Lightweight file-based routing with SSR support\n */\nexport class Router {\n private routes: Route[] = [];\n private flatRoutes: Route[] = [];\n private globalMiddleware: RouteMiddleware[] = [];\n private options: RouterOptions;\n private listeners: Set<() => void> = new Set();\n private currentMatch: RouteMatch | null = null;\n private lastPathname: string | null = null;\n private popstateHandler: (() => void) | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n };\n }\n\n /**\n * Add global middleware that runs before all routes.\n */\n use(...middleware: RouteMiddleware[]): void {\n this.globalMiddleware.push(...middleware);\n }\n\n /**\n * Get router options\n */\n getOptions(): RouterOptions {\n return this.options;\n }\n\n /**\n * Register a route. Nested children are flattened with combined paths,\n * middleware, and layout chains.\n */\n register(route: Route): void {\n this.routes.push(route);\n this.flattenRoute(route, '', [], undefined);\n }\n\n /**\n * Register multiple routes\n */\n registerRoutes(routes: Route[]): void {\n routes.forEach((route) => {\n this.register(route);\n });\n }\n\n /**\n * Flatten nested routes into the flat lookup table.\n * Children inherit parent path prefix, middleware, and layout.\n */\n private flattenRoute(\n route: Route,\n parentPath: string,\n parentMiddleware: RouteMiddleware[],\n parentLayout: Route['layout'],\n ): void {\n const fullPath = joinPaths(parentPath, route.path);\n const combinedMiddleware = [...parentMiddleware, ...(route.middleware ?? [])];\n // Child layout wraps inside parent layout\n const effectiveLayout =\n parentLayout && route.layout\n ? wrapLayouts(parentLayout, route.layout)\n : (route.layout ?? parentLayout);\n\n // Register this route if it has a component (layout-only routes may not)\n if (route.component) {\n this.flatRoutes.push({\n ...route,\n path: fullPath,\n middleware: combinedMiddleware.length > 0 ? combinedMiddleware : undefined,\n layout: effectiveLayout,\n children: undefined, // Already flattened\n });\n }\n\n // Recursively flatten children\n if (route.children) {\n for (const child of route.children) {\n this.flattenRoute(child, fullPath, combinedMiddleware, effectiveLayout);\n }\n }\n }\n\n /**\n * Match a URL to a route.\n * Checks flattened routes first (includes nested), then falls back to top-level.\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url);\n\n // Check flattened routes (includes nested children)\n const allRoutes = this.flatRoutes.length > 0 ? this.flatRoutes : this.routes;\n\n // Try to match each route\n for (const route of allRoutes) {\n const matcher = pathMatch(route.path, { decode: decodeURIComponent });\n const result = matcher(path);\n\n if (result) {\n return {\n route,\n params: (result.params as RouteParams) || {},\n };\n }\n }\n\n return null;\n }\n\n /**\n * Resolve a route with middleware execution and data loading.\n *\n * Middleware chain: global middleware → route middleware → loader.\n * If any middleware returns `false`, resolution is aborted (returns null).\n * If any middleware returns a string, navigation is redirected to that path.\n */\n async resolve(url: string): Promise<RouteMatch | null> {\n const matched = this.match(url);\n\n if (!matched) {\n return null;\n }\n\n // Run middleware chain (global + route-specific)\n const allMiddleware = [...this.globalMiddleware, ...(matched.route.middleware ?? [])];\n\n if (allMiddleware.length > 0) {\n const context: MiddlewareContext = {\n pathname: this.normalizePath(url),\n params: matched.params,\n meta: matched.route.meta,\n };\n\n for (const mw of allMiddleware) {\n const result = await mw(context);\n if (result === false) {\n return null; // Middleware blocked\n }\n if (typeof result === 'string') {\n // Middleware requested redirect\n this.navigate(result);\n return null;\n }\n }\n }\n\n // Load data if loader exists\n if (matched.route.loader) {\n try {\n matched.data = await matched.route.loader(matched.params);\n } catch (error) {\n logger.error(\n 'Route loader error',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n }\n\n // Store resolved match for getCurrentMatch (SSR and resolve-based flows)\n this.currentMatch = matched;\n\n return matched;\n }\n\n /**\n * Navigate to a URL (client-side only)\n */\n navigate(url: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const fullUrl = this.options.basePath + url;\n\n if (options.replace) {\n window.history.replaceState(options.state || null, '', fullUrl);\n } else {\n window.history.pushState(options.state || null, '', fullUrl);\n }\n\n // Update cached match so useSyncExternalStore gets a stable reference\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n\n this.notifyListeners();\n }\n\n /**\n * Go back in history\n */\n back(): void {\n if (typeof window !== 'undefined') {\n window.history.back();\n }\n }\n\n /**\n * Go forward in history\n */\n forward(): void {\n if (typeof window !== 'undefined') {\n window.history.forward();\n }\n }\n\n /**\n * Subscribe to route changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Get current route match\n */\n getCurrentMatch(): RouteMatch | null {\n if (typeof window === 'undefined') {\n return this.currentMatch;\n }\n\n // Return cached match if pathname hasn't changed (stable reference for useSyncExternalStore)\n const pathname = window.location.pathname;\n if (pathname !== this.lastPathname) {\n this.lastPathname = pathname;\n this.currentMatch = this.match(pathname);\n }\n\n return this.currentMatch;\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes];\n }\n\n /**\n * Clear all routes and middleware\n */\n clear(): void {\n this.routes = [];\n this.flatRoutes = [];\n this.globalMiddleware = [];\n }\n\n private normalizePath(url: string): string {\n // Remove base path\n let path = url;\n if (this.options.basePath && path.startsWith(this.options.basePath)) {\n path = path.slice(this.options.basePath.length);\n }\n\n // Remove query string and hash\n path = path.split('?')[0].split('#')[0];\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = `/${path}`;\n }\n\n return path;\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => {\n listener();\n });\n }\n\n /**\n * Initialize client-side routing.\n * Uses a global flag to prevent duplicate event listeners on HMR re-invocation.\n */\n initClient(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: global HMR guard\n const g = globalThis as any;\n if (g.__revealui_router_initialized) return;\n g.__revealui_router_initialized = true;\n\n // Handle browser back/forward buttons\n this.popstateHandler = () => {\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n this.notifyListeners();\n };\n window.addEventListener('popstate', this.popstateHandler);\n\n // Intercept link clicks\n this.clickHandler = (e: MouseEvent) => {\n const target = (e.target as HTMLElement).closest('a');\n\n if (!target) return;\n\n const href = target.getAttribute('href');\n\n // Only handle internal links\n if (\n href?.startsWith('/') &&\n !target.hasAttribute('target') &&\n !target.hasAttribute('download') &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n ) {\n e.preventDefault();\n this.navigate(href);\n }\n };\n document.addEventListener('click', this.clickHandler);\n }\n\n /**\n * Clean up client-side event listeners.\n * Call this before unmounting or during HMR teardown.\n */\n dispose(): void {\n if (typeof window === 'undefined') return;\n\n if (this.popstateHandler) {\n window.removeEventListener('popstate', this.popstateHandler);\n this.popstateHandler = null;\n }\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler);\n this.clickHandler = null;\n }\n\n this.listeners.clear();\n\n // biome-ignore lint/suspicious/noExplicitAny: global HMR guard cleanup\n const g = globalThis as any;\n g.__revealui_router_initialized = false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Join parent and child path segments, avoiding double slashes */\nfunction joinPaths(parent: string, child: string): string {\n if (!parent || parent === '/') return child;\n if (!child || child === '/') return parent;\n return `${parent.replace(/\\/$/, '')}/${child.replace(/^\\//, '')}`;\n}\n\n/** Compose two layout components so the child renders inside the parent */\nfunction wrapLayouts(\n Parent: React.ComponentType<{ children: React.ReactNode }>,\n Child: React.ComponentType<{ children: React.ReactNode }>,\n): React.ComponentType<{ children: React.ReactNode }> {\n const WrappedLayout = ({ children }: { children: React.ReactNode }) =>\n createElement(Parent, null, createElement(Child, null, children));\n return WrappedLayout;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/components.tsx","../src/router.ts"],"sourcesContent":["import type React from 'react';\nimport {\n Component,\n createContext,\n use,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport type { Router } from './router';\nimport type { Location, NavigateOptions, RouteMatch } from './types';\n\n/**\n * Router context\n */\nconst RouterContext = createContext<Router | null>(null);\n\n/**\n * Current match context\n */\nconst MatchContext = createContext<RouteMatch | null>(null);\n\n/**\n * RouterProvider - Provides router instance to the app\n */\nexport function RouterProvider({\n router,\n children,\n}: {\n router: Router;\n children: React.ReactNode;\n}) {\n return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>;\n}\n\n/**\n * Routes - Renders the matched route component\n */\nexport function Routes() {\n const router = useRouter();\n const options = router.getOptions();\n\n // Subscribe to router changes\n const match = useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => router.getCurrentMatch(),\n () => router.getCurrentMatch(), // Server-side snapshot (same as client)\n );\n\n if (!match) {\n const CustomNotFound = options.notFound;\n return CustomNotFound ? <CustomNotFound /> : <NotFound />;\n }\n\n const { route, params, data } = match;\n const RouteComponent = route.component;\n const Layout = route.layout;\n\n const element = <RouteComponent params={params} data={data} />;\n const wrapped = Layout ? <Layout>{element}</Layout> : element;\n\n return (\n <MatchContext.Provider value={match}>\n {options.errorBoundary ? (\n <RouteErrorBoundary fallback={options.errorBoundary}>{wrapped}</RouteErrorBoundary>\n ) : (\n wrapped\n )}\n </MatchContext.Provider>\n );\n}\n\ninterface LinkProps extends Record<string, unknown> {\n to: string;\n replace?: boolean;\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;\n}\n\n/**\n * Link - Client-side navigation link\n */\nexport function Link({\n to,\n replace = false,\n children,\n className,\n style,\n onClick,\n ...props\n}: LinkProps) {\n const router = useRouter();\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n onClick?.(e);\n\n // Don't navigate if default was prevented\n if (e.defaultPrevented) {\n return;\n }\n\n // Only handle left clicks\n if (e.button !== 0) {\n return;\n }\n\n // Ignore if modifier keys are pressed\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n e.preventDefault();\n router.navigate(to, { replace });\n };\n\n return (\n <a href={to} onClick={handleClick} className={className} style={style} {...props}>\n {children}\n </a>\n );\n}\n\n/**\n * useRouter - Hook to access router instance\n */\nexport function useRouter(): Router {\n const router = use(RouterContext);\n\n if (!router) {\n throw new Error('useRouter must be used within a RouterProvider');\n }\n\n return router;\n}\n\n/**\n * useMatch - Hook to access current route match\n */\nexport function useMatch(): RouteMatch | null {\n return use(MatchContext);\n}\n\n/**\n * useParams - Hook to access route parameters\n */\nexport function useParams<T = Record<string, string>>(): T {\n const match = useMatch();\n return (match?.params as T) || ({} as T);\n}\n\n/**\n * useData - Hook to access route data\n */\nexport function useData<T = unknown>(): T | undefined {\n const match = useMatch();\n return match?.data as T | undefined;\n}\n\n/**\n * useNavigate - Hook to get navigation function\n */\nexport function useNavigate(): (to: string, options?: NavigateOptions) => void {\n const router = useRouter();\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options);\n };\n}\n\nconst SERVER_LOCATION: Location = { pathname: '/', search: '', hash: '' };\n\nfunction getWindowLocation(): Location {\n if (typeof window === 'undefined') return SERVER_LOCATION;\n return {\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n };\n}\n\n/**\n * useLocation - Hook to access current location (pathname, search, hash).\n * Returns a stable reference that only changes when the URL actually changes.\n */\nexport function useLocation(): Location {\n const router = useRouter();\n const locationRef = useRef<Location>(getWindowLocation());\n\n // Re-subscribe on route changes to detect URL updates\n useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname + window.location.search + window.location.hash;\n },\n () => '/',\n );\n\n const current = getWindowLocation();\n if (\n current.pathname !== locationRef.current.pathname ||\n current.search !== locationRef.current.search ||\n current.hash !== locationRef.current.hash\n ) {\n locationRef.current = current;\n }\n\n return locationRef.current;\n}\n\n/**\n * useSearchParams - Hook to access parsed query string parameters\n */\nexport function useSearchParams(): URLSearchParams {\n const { search } = useLocation();\n return useMemo(() => new URLSearchParams(search), [search]);\n}\n\n/**\n * NotFound - Default 404 component\n */\nfunction NotFound() {\n return (\n <div style={{ padding: '2rem', textAlign: 'center' }}>\n <h1>404 - Page Not Found</h1>\n <p>The page you're looking for doesn't exist.</p>\n <Link to=\"/\">Go Home</Link>\n </div>\n );\n}\n\n/**\n * RouteErrorBoundary - Catches render errors in route components\n */\nclass RouteErrorBoundary extends Component<\n { fallback: React.ComponentType<{ error: Error }>; children: React.ReactNode },\n { error: Error | null }\n> {\n constructor(props: {\n fallback: React.ComponentType<{ error: Error }>;\n children: React.ReactNode;\n }) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error): { error: Error } {\n return { error };\n }\n\n render() {\n if (this.state.error) {\n const Fallback = this.props.fallback;\n return <Fallback error={this.state.error} />;\n }\n return this.props.children;\n }\n}\n\n/**\n * Navigate - Component for declarative navigation\n */\nexport function Navigate({ to, replace = false }: { to: string; replace?: boolean }) {\n const router = useRouter();\n\n useEffect(() => {\n router.navigate(to, { replace });\n }, [to, replace, router]);\n\n return null;\n}\n","import { logger } from '@revealui/core/observability/logger';\nimport type React from 'react';\nimport { createElement } from 'react';\nimport type {\n MiddlewareContext,\n NavigateOptions,\n Route,\n RouteMatch,\n RouteMiddleware,\n RouteParams,\n RouterOptions,\n} from './types';\n\ndeclare global {\n var __revealui_router_initialized: boolean | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Hand-rolled path matcher — replaces `path-to-regexp`.\n// Supports: exact paths, named params (:id), wildcards (*path),\n// and optional segments ({/...}).\n// ---------------------------------------------------------------------------\n\ninterface PathKey {\n name: string;\n wildcard: boolean;\n}\n\n/** Maximum pattern length to prevent ReDoS from malicious/misconfigured routes */\nconst MAX_PATTERN_LENGTH = 2048;\n\nfunction compilePathPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new Error(`Route pattern exceeds ${MAX_PATTERN_LENGTH} characters`);\n }\n const keys: PathKey[] = [];\n let src = '^';\n let i = 0;\n while (i < pattern.length) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const ch = pattern[i]!;\n if (ch === '{') {\n src += '(?:';\n i++;\n } else if (ch === '}') {\n src += ')?';\n i++;\n } else if (ch === ':') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name, wildcard: false });\n src += '([^/]+)';\n } else if (ch === '*') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name: name || '0', wildcard: true });\n src += '(.+)';\n } else {\n src += ch.replace(/[.+?^$|()[\\]\\\\]/g, '\\\\$&');\n i++;\n }\n }\n src += '$';\n return { regex: new RegExp(src), keys };\n}\n\n/** Cache compiled patterns to avoid recompilation on every match() call */\nconst patternCache = new Map<string, { regex: RegExp; keys: PathKey[] }>();\n\nfunction getCompiledPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n let compiled = patternCache.get(pattern);\n if (!compiled) {\n compiled = compilePathPattern(pattern);\n patternCache.set(pattern, compiled);\n }\n return compiled;\n}\n\nfunction pathMatch(\n pattern: string,\n options: { decode?: (s: string) => string } = {},\n): (path: string) => { params: Record<string, string | string[]> } | false {\n const { regex, keys } = getCompiledPattern(pattern);\n const decode = options.decode ?? ((s) => s);\n return (path: string) => {\n const m = regex.exec(path);\n if (!m) return false;\n const params: Record<string, string | string[]> = {};\n for (let j = 0; j < keys.length; j++) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const key = keys[j]!;\n const val = m[j + 1];\n if (val === undefined) continue;\n params[key.name] = key.wildcard ? val.split('/').map(decode) : decode(val);\n }\n return { params };\n };\n}\n\n/**\n * RevealUI Router - Lightweight file-based routing with SSR support\n */\nexport class Router {\n private routes: Route[] = [];\n private flatRoutes: Route[] = [];\n private globalMiddleware: RouteMiddleware[] = [];\n private options: RouterOptions;\n private listeners: Set<() => void> = new Set();\n private currentMatch: RouteMatch | null = null;\n private lastPathname: string | null = null;\n private popstateHandler: (() => void) | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n };\n }\n\n /**\n * Add global middleware that runs before all routes.\n */\n use(...middleware: RouteMiddleware[]): void {\n this.globalMiddleware.push(...middleware);\n }\n\n /**\n * Get router options\n */\n getOptions(): RouterOptions {\n return this.options;\n }\n\n /**\n * Register a route. Nested children are flattened with combined paths,\n * middleware, and layout chains.\n */\n register(route: Route): void {\n this.routes.push(route);\n this.flattenRoute(route, '', [], undefined);\n }\n\n /**\n * Register multiple routes\n */\n registerRoutes(routes: Route[]): void {\n routes.forEach((route) => {\n this.register(route);\n });\n }\n\n /**\n * Flatten nested routes into the flat lookup table.\n * Children inherit parent path prefix, middleware, and layout.\n */\n private flattenRoute(\n route: Route,\n parentPath: string,\n parentMiddleware: RouteMiddleware[],\n parentLayout: Route['layout'],\n ): void {\n const fullPath = joinPaths(parentPath, route.path);\n const combinedMiddleware = [...parentMiddleware, ...(route.middleware ?? [])];\n // Child layout wraps inside parent layout\n const effectiveLayout =\n parentLayout && route.layout\n ? wrapLayouts(parentLayout, route.layout)\n : (route.layout ?? parentLayout);\n\n // Register this route if it has a component (layout-only routes may not)\n if (route.component) {\n this.flatRoutes.push({\n ...route,\n path: fullPath,\n middleware: combinedMiddleware.length > 0 ? combinedMiddleware : undefined,\n layout: effectiveLayout,\n children: undefined, // Already flattened\n });\n }\n\n // Recursively flatten children\n if (route.children) {\n for (const child of route.children) {\n this.flattenRoute(child, fullPath, combinedMiddleware, effectiveLayout);\n }\n }\n }\n\n /**\n * Match a URL to a route.\n * Checks flattened routes first (includes nested), then falls back to top-level.\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url);\n\n // Check flattened routes (includes nested children)\n const allRoutes = this.flatRoutes.length > 0 ? this.flatRoutes : this.routes;\n\n // Try to match each route\n for (const route of allRoutes) {\n const matcher = pathMatch(route.path, { decode: decodeURIComponent });\n const result = matcher(path);\n\n if (result) {\n return {\n route,\n params: (result.params as RouteParams) || {},\n };\n }\n }\n\n return null;\n }\n\n /**\n * Resolve a route with middleware execution and data loading.\n *\n * Middleware chain: global middleware → route middleware → loader.\n * If any middleware returns `false`, resolution is aborted (returns null).\n * If any middleware returns a string, navigation is redirected to that path.\n */\n async resolve(url: string): Promise<RouteMatch | null> {\n const matched = this.match(url);\n\n if (!matched) {\n return null;\n }\n\n // Run middleware chain (global + route-specific)\n const allMiddleware = [...this.globalMiddleware, ...(matched.route.middleware ?? [])];\n\n if (allMiddleware.length > 0) {\n const context: MiddlewareContext = {\n pathname: this.normalizePath(url),\n params: matched.params,\n meta: matched.route.meta,\n };\n\n for (const mw of allMiddleware) {\n const result = await mw(context);\n if (result === false) {\n return null; // Middleware blocked\n }\n if (typeof result === 'string') {\n // Middleware requested redirect\n this.navigate(result);\n return null;\n }\n }\n }\n\n // Load data if loader exists\n if (matched.route.loader) {\n try {\n matched.data = await matched.route.loader(matched.params);\n } catch (error) {\n logger.error(\n 'Route loader error',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n }\n\n // Store resolved match for getCurrentMatch (SSR and resolve-based flows)\n this.currentMatch = matched;\n\n return matched;\n }\n\n /**\n * Navigate to a URL (client-side only)\n */\n navigate(url: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const fullUrl = this.options.basePath + url;\n\n if (options.replace) {\n window.history.replaceState(options.state || null, '', fullUrl);\n } else {\n window.history.pushState(options.state || null, '', fullUrl);\n }\n\n // Update cached match so useSyncExternalStore gets a stable reference\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n\n this.notifyListeners();\n }\n\n /**\n * Go back in history\n */\n back(): void {\n if (typeof window !== 'undefined') {\n window.history.back();\n }\n }\n\n /**\n * Go forward in history\n */\n forward(): void {\n if (typeof window !== 'undefined') {\n window.history.forward();\n }\n }\n\n /**\n * Subscribe to route changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Get current route match\n */\n getCurrentMatch(): RouteMatch | null {\n if (typeof window === 'undefined') {\n return this.currentMatch;\n }\n\n // Return cached match if pathname hasn't changed (stable reference for useSyncExternalStore)\n const pathname = window.location.pathname;\n if (pathname !== this.lastPathname) {\n this.lastPathname = pathname;\n this.currentMatch = this.match(pathname);\n }\n\n return this.currentMatch;\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes];\n }\n\n /**\n * Clear all routes and middleware\n */\n clear(): void {\n this.routes = [];\n this.flatRoutes = [];\n this.globalMiddleware = [];\n }\n\n private normalizePath(url: string): string {\n // Remove base path\n let path = url;\n if (this.options.basePath && path.startsWith(this.options.basePath)) {\n path = path.slice(this.options.basePath.length);\n }\n\n // Remove query string and hash\n path = path.split('?')[0].split('#')[0];\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = `/${path}`;\n }\n\n return path;\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => {\n listener();\n });\n }\n\n /**\n * Initialize client-side routing.\n * Uses a global flag to prevent duplicate event listeners on HMR re-invocation.\n */\n initClient(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n if (globalThis.__revealui_router_initialized) return;\n globalThis.__revealui_router_initialized = true;\n\n // Handle browser back/forward buttons\n this.popstateHandler = () => {\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n this.notifyListeners();\n };\n window.addEventListener('popstate', this.popstateHandler);\n\n // Intercept link clicks\n this.clickHandler = (e: MouseEvent) => {\n const target = (e.target as HTMLElement).closest('a');\n\n if (!target) return;\n\n const href = target.getAttribute('href');\n\n // Only handle internal links\n if (\n href?.startsWith('/') &&\n !target.hasAttribute('target') &&\n !target.hasAttribute('download') &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n ) {\n e.preventDefault();\n this.navigate(href);\n }\n };\n document.addEventListener('click', this.clickHandler);\n }\n\n /**\n * Clean up client-side event listeners.\n * Call this before unmounting or during HMR teardown.\n */\n dispose(): void {\n if (typeof window === 'undefined') return;\n\n if (this.popstateHandler) {\n window.removeEventListener('popstate', this.popstateHandler);\n this.popstateHandler = null;\n }\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler);\n this.clickHandler = null;\n }\n\n this.listeners.clear();\n\n globalThis.__revealui_router_initialized = false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Join parent and child path segments, avoiding double slashes */\nfunction joinPaths(parent: string, child: string): string {\n if (!parent || parent === '/') return child;\n if (!child || child === '/') return parent;\n return `${parent.replace(/\\/$/, '')}/${child.replace(/^\\//, '')}`;\n}\n\n/** Compose two layout components so the child renders inside the parent */\nfunction wrapLayouts(\n Parent: React.ComponentType<{ children: React.ReactNode }>,\n Child: React.ComponentType<{ children: React.ReactNode }>,\n): React.ComponentType<{ children: React.ReactNode }> {\n const WrappedLayout = ({ children }: { children: React.ReactNode }) =>\n createElement(Parent, null, createElement(Child, null, children));\n return WrappedLayout;\n}\n"],"mappings":";AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAwBE,cAkML,YAlMK;AAjBT,IAAM,gBAAgB,cAA6B,IAAI;AAKvD,IAAM,eAAe,cAAiC,IAAI;AAKnD,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGG;AACD,SAAO,oBAAC,cAAc,UAAd,EAAuB,OAAO,QAAS,UAAS;AAC1D;AAKO,SAAS,SAAS;AACvB,QAAM,SAAS,UAAU;AACzB,QAAM,UAAU,OAAO,WAAW;AAGlC,QAAM,QAAQ;AAAA,IACZ,CAAC,aAAa,OAAO,UAAU,QAAQ;AAAA,IACvC,MAAM,OAAO,gBAAgB;AAAA,IAC7B,MAAM,OAAO,gBAAgB;AAAA;AAAA,EAC/B;AAEA,MAAI,CAAC,OAAO;AACV,UAAM,iBAAiB,QAAQ;AAC/B,WAAO,iBAAiB,oBAAC,kBAAe,IAAK,oBAAC,YAAS;AAAA,EACzD;AAEA,QAAM,EAAE,OAAO,QAAQ,KAAK,IAAI;AAChC,QAAM,iBAAiB,MAAM;AAC7B,QAAM,SAAS,MAAM;AAErB,QAAM,UAAU,oBAAC,kBAAe,QAAgB,MAAY;AAC5D,QAAM,UAAU,SAAS,oBAAC,UAAQ,mBAAQ,IAAY;AAEtD,SACE,oBAAC,aAAa,UAAb,EAAsB,OAAO,OAC3B,kBAAQ,gBACP,oBAAC,sBAAmB,UAAU,QAAQ,eAAgB,mBAAQ,IAE9D,SAEJ;AAEJ;AAcO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAc;AACZ,QAAM,SAAS,UAAU;AAEzB,QAAM,cAAc,CAAC,MAA2C;AAE9D,cAAU,CAAC;AAGX,QAAI,EAAE,kBAAkB;AACtB;AAAA,IACF;AAGA,QAAI,EAAE,WAAW,GAAG;AAClB;AAAA,IACF;AAGA,QAAI,EAAE,WAAW,EAAE,WAAW,EAAE,YAAY,EAAE,QAAQ;AACpD;AAAA,IACF;AAEA,MAAE,eAAe;AACjB,WAAO,SAAS,IAAI,EAAE,QAAQ,CAAC;AAAA,EACjC;AAEA,SACE,oBAAC,OAAE,MAAM,IAAI,SAAS,aAAa,WAAsB,OAAe,GAAG,OACxE,UACH;AAEJ;AAKO,SAAS,YAAoB;AAClC,QAAM,SAAS,IAAI,aAAa;AAEhC,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,gDAAgD;AAAA,EAClE;AAEA,SAAO;AACT;AAKO,SAAS,WAA8B;AAC5C,SAAO,IAAI,YAAY;AACzB;AAKO,SAAS,YAA2C;AACzD,QAAM,QAAQ,SAAS;AACvB,SAAQ,OAAO,UAAiB,CAAC;AACnC;AAKO,SAAS,UAAsC;AACpD,QAAM,QAAQ,SAAS;AACvB,SAAO,OAAO;AAChB;AAKO,SAAS,cAA+D;AAC7E,QAAM,SAAS,UAAU;AAEzB,SAAO,CAAC,IAAY,YAA8B;AAChD,WAAO,SAAS,IAAI,OAAO;AAAA,EAC7B;AACF;AAEA,IAAM,kBAA4B,EAAE,UAAU,KAAK,QAAQ,IAAI,MAAM,GAAG;AAExE,SAAS,oBAA8B;AACrC,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,SAAO;AAAA,IACL,UAAU,OAAO,SAAS;AAAA,IAC1B,QAAQ,OAAO,SAAS;AAAA,IACxB,MAAM,OAAO,SAAS;AAAA,EACxB;AACF;AAMO,SAAS,cAAwB;AACtC,QAAM,SAAS,UAAU;AACzB,QAAM,cAAc,OAAiB,kBAAkB,CAAC;AAGxD;AAAA,IACE,CAAC,aAAa,OAAO,UAAU,QAAQ;AAAA,IACvC,MAAM;AACJ,UAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,aAAO,OAAO,SAAS,WAAW,OAAO,SAAS,SAAS,OAAO,SAAS;AAAA,IAC7E;AAAA,IACA,MAAM;AAAA,EACR;AAEA,QAAM,UAAU,kBAAkB;AAClC,MACE,QAAQ,aAAa,YAAY,QAAQ,YACzC,QAAQ,WAAW,YAAY,QAAQ,UACvC,QAAQ,SAAS,YAAY,QAAQ,MACrC;AACA,gBAAY,UAAU;AAAA,EACxB;AAEA,SAAO,YAAY;AACrB;AAKO,SAAS,kBAAmC;AACjD,QAAM,EAAE,OAAO,IAAI,YAAY;AAC/B,SAAO,QAAQ,MAAM,IAAI,gBAAgB,MAAM,GAAG,CAAC,MAAM,CAAC;AAC5D;AAKA,SAAS,WAAW;AAClB,SACE,qBAAC,SAAI,OAAO,EAAE,SAAS,QAAQ,WAAW,SAAS,GACjD;AAAA,wBAAC,QAAG,kCAAoB;AAAA,IACxB,oBAAC,OAAE,wDAA0C;AAAA,IAC7C,oBAAC,QAAK,IAAG,KAAI,qBAAO;AAAA,KACtB;AAEJ;AAKA,IAAM,qBAAN,cAAiC,UAG/B;AAAA,EACA,YAAY,OAGT;AACD,UAAM,KAAK;AACX,SAAK,QAAQ,EAAE,OAAO,KAAK;AAAA,EAC7B;AAAA,EAEA,OAAO,yBAAyB,OAAgC;AAC9D,WAAO,EAAE,MAAM;AAAA,EACjB;AAAA,EAEA,SAAS;AACP,QAAI,KAAK,MAAM,OAAO;AACpB,YAAM,WAAW,KAAK,MAAM;AAC5B,aAAO,oBAAC,YAAS,OAAO,KAAK,MAAM,OAAO;AAAA,IAC5C;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;AAKO,SAAS,SAAS,EAAE,IAAI,UAAU,MAAM,GAAsC;AACnF,QAAM,SAAS,UAAU;AAEzB,YAAU,MAAM;AACd,WAAO,SAAS,IAAI,EAAE,QAAQ,CAAC;AAAA,EACjC,GAAG,CAAC,IAAI,SAAS,MAAM,CAAC;AAExB,SAAO;AACT;;;AClRA,SAAS,cAAc;AAEvB,SAAS,qBAAqB;AA2B9B,IAAM,qBAAqB;AAE3B,SAAS,mBAAmB,SAAqD;AAC/E,MAAI,QAAQ,SAAS,oBAAoB;AACvC,UAAM,IAAI,MAAM,yBAAyB,kBAAkB,aAAa;AAAA,EAC1E;AACA,QAAM,OAAkB,CAAC;AACzB,MAAI,MAAM;AACV,MAAI,IAAI;AACR,SAAO,IAAI,QAAQ,QAAQ;AAEzB,UAAM,KAAK,QAAQ,CAAC;AACpB,QAAI,OAAO,KAAK;AACd,aAAO;AACP;AAAA,IACF,WAAW,OAAO,KAAK;AACrB,aAAO;AACP;AAAA,IACF,WAAW,OAAO,KAAK;AACrB;AACA,UAAI,OAAO;AAEX,aAAO,IAAI,QAAQ,UAAU,KAAK,KAAK,QAAQ,CAAC,CAAE,EAAG,SAAQ,QAAQ,GAAG;AACxE,WAAK,KAAK,EAAE,MAAM,UAAU,MAAM,CAAC;AACnC,aAAO;AAAA,IACT,WAAW,OAAO,KAAK;AACrB;AACA,UAAI,OAAO;AAEX,aAAO,IAAI,QAAQ,UAAU,KAAK,KAAK,QAAQ,CAAC,CAAE,EAAG,SAAQ,QAAQ,GAAG;AACxE,WAAK,KAAK,EAAE,MAAM,QAAQ,KAAK,UAAU,KAAK,CAAC;AAC/C,aAAO;AAAA,IACT,OAAO;AACL,aAAO,GAAG,QAAQ,oBAAoB,MAAM;AAC5C;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACP,SAAO,EAAE,OAAO,IAAI,OAAO,GAAG,GAAG,KAAK;AACxC;AAGA,IAAM,eAAe,oBAAI,IAAgD;AAEzE,SAAS,mBAAmB,SAAqD;AAC/E,MAAI,WAAW,aAAa,IAAI,OAAO;AACvC,MAAI,CAAC,UAAU;AACb,eAAW,mBAAmB,OAAO;AACrC,iBAAa,IAAI,SAAS,QAAQ;AAAA,EACpC;AACA,SAAO;AACT;AAEA,SAAS,UACP,SACA,UAA8C,CAAC,GAC0B;AACzE,QAAM,EAAE,OAAO,KAAK,IAAI,mBAAmB,OAAO;AAClD,QAAM,SAAS,QAAQ,WAAW,CAAC,MAAM;AACzC,SAAO,CAAC,SAAiB;AACvB,UAAM,IAAI,MAAM,KAAK,IAAI;AACzB,QAAI,CAAC,EAAG,QAAO;AACf,UAAM,SAA4C,CAAC;AACnD,aAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AAEpC,YAAM,MAAM,KAAK,CAAC;AAClB,YAAM,MAAM,EAAE,IAAI,CAAC;AACnB,UAAI,QAAQ,OAAW;AACvB,aAAO,IAAI,IAAI,IAAI,IAAI,WAAW,IAAI,MAAM,GAAG,EAAE,IAAI,MAAM,IAAI,OAAO,GAAG;AAAA,IAC3E;AACA,WAAO,EAAE,OAAO;AAAA,EAClB;AACF;AAKO,IAAM,SAAN,MAAa;AAAA,EACV,SAAkB,CAAC;AAAA,EACnB,aAAsB,CAAC;AAAA,EACvB,mBAAsC,CAAC;AAAA,EACvC;AAAA,EACA,YAA6B,oBAAI,IAAI;AAAA,EACrC,eAAkC;AAAA,EAClC,eAA8B;AAAA,EAC9B,kBAAuC;AAAA,EACvC,eAAiD;AAAA,EAEzD,YAAY,UAAyB,CAAC,GAAG;AACvC,SAAK,UAAU;AAAA,MACb,UAAU;AAAA,MACV,GAAG;AAAA,IACL;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,YAAqC;AAC1C,SAAK,iBAAiB,KAAK,GAAG,UAAU;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,aAA4B;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAS,OAAoB;AAC3B,SAAK,OAAO,KAAK,KAAK;AACtB,SAAK,aAAa,OAAO,IAAI,CAAC,GAAG,MAAS;AAAA,EAC5C;AAAA;AAAA;AAAA;AAAA,EAKA,eAAe,QAAuB;AACpC,WAAO,QAAQ,CAAC,UAAU;AACxB,WAAK,SAAS,KAAK;AAAA,IACrB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,aACN,OACA,YACA,kBACA,cACM;AACN,UAAM,WAAW,UAAU,YAAY,MAAM,IAAI;AACjD,UAAM,qBAAqB,CAAC,GAAG,kBAAkB,GAAI,MAAM,cAAc,CAAC,CAAE;AAE5E,UAAM,kBACJ,gBAAgB,MAAM,SAClB,YAAY,cAAc,MAAM,MAAM,IACrC,MAAM,UAAU;AAGvB,QAAI,MAAM,WAAW;AACnB,WAAK,WAAW,KAAK;AAAA,QACnB,GAAG;AAAA,QACH,MAAM;AAAA,QACN,YAAY,mBAAmB,SAAS,IAAI,qBAAqB;AAAA,QACjE,QAAQ;AAAA,QACR,UAAU;AAAA;AAAA,MACZ,CAAC;AAAA,IACH;AAGA,QAAI,MAAM,UAAU;AAClB,iBAAW,SAAS,MAAM,UAAU;AAClC,aAAK,aAAa,OAAO,UAAU,oBAAoB,eAAe;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,KAAgC;AAEpC,UAAM,OAAO,KAAK,cAAc,GAAG;AAGnC,UAAM,YAAY,KAAK,WAAW,SAAS,IAAI,KAAK,aAAa,KAAK;AAGtE,eAAW,SAAS,WAAW;AAC7B,YAAM,UAAU,UAAU,MAAM,MAAM,EAAE,QAAQ,mBAAmB,CAAC;AACpE,YAAM,SAAS,QAAQ,IAAI;AAE3B,UAAI,QAAQ;AACV,eAAO;AAAA,UACL;AAAA,UACA,QAAS,OAAO,UAA0B,CAAC;AAAA,QAC7C;AAAA,MACF;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,QAAQ,KAAyC;AACrD,UAAM,UAAU,KAAK,MAAM,GAAG;AAE9B,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAGA,UAAM,gBAAgB,CAAC,GAAG,KAAK,kBAAkB,GAAI,QAAQ,MAAM,cAAc,CAAC,CAAE;AAEpF,QAAI,cAAc,SAAS,GAAG;AAC5B,YAAM,UAA6B;AAAA,QACjC,UAAU,KAAK,cAAc,GAAG;AAAA,QAChC,QAAQ,QAAQ;AAAA,QAChB,MAAM,QAAQ,MAAM;AAAA,MACtB;AAEA,iBAAW,MAAM,eAAe;AAC9B,cAAM,SAAS,MAAM,GAAG,OAAO;AAC/B,YAAI,WAAW,OAAO;AACpB,iBAAO;AAAA,QACT;AACA,YAAI,OAAO,WAAW,UAAU;AAE9B,eAAK,SAAS,MAAM;AACpB,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,IACF;AAGA,QAAI,QAAQ,MAAM,QAAQ;AACxB,UAAI;AACF,gBAAQ,OAAO,MAAM,QAAQ,MAAM,OAAO,QAAQ,MAAM;AAAA,MAC1D,SAAS,OAAO;AACd,eAAO;AAAA,UACL;AAAA,UACA,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,QAC1D;AACA,cAAM;AAAA,MACR;AAAA,IACF;AAGA,SAAK,eAAe;AAEpB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,KAAa,UAA2B,CAAC,GAAS;AACzD,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,QAAQ,WAAW;AAExC,QAAI,QAAQ,SAAS;AACnB,aAAO,QAAQ,aAAa,QAAQ,SAAS,MAAM,IAAI,OAAO;AAAA,IAChE,OAAO;AACL,aAAO,QAAQ,UAAU,QAAQ,SAAS,MAAM,IAAI,OAAO;AAAA,IAC7D;AAGA,SAAK,eAAe,OAAO,SAAS;AACpC,SAAK,eAAe,KAAK,MAAM,OAAO,SAAS,QAAQ;AAEvD,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,OAAa;AACX,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,QAAQ,KAAK;AAAA,IACtB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAgB;AACd,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,UAAU,UAAkC;AAC1C,SAAK,UAAU,IAAI,QAAQ;AAG3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAqC;AACnC,QAAI,OAAO,WAAW,aAAa;AACjC,aAAO,KAAK;AAAA,IACd;AAGA,UAAM,WAAW,OAAO,SAAS;AACjC,QAAI,aAAa,KAAK,cAAc;AAClC,WAAK,eAAe;AACpB,WAAK,eAAe,KAAK,MAAM,QAAQ;AAAA,IACzC;AAEA,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,YAAqB;AACnB,WAAO,CAAC,GAAG,KAAK,MAAM;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA,EAKA,QAAc;AACZ,SAAK,SAAS,CAAC;AACf,SAAK,aAAa,CAAC;AACnB,SAAK,mBAAmB,CAAC;AAAA,EAC3B;AAAA,EAEQ,cAAc,KAAqB;AAEzC,QAAI,OAAO;AACX,QAAI,KAAK,QAAQ,YAAY,KAAK,WAAW,KAAK,QAAQ,QAAQ,GAAG;AACnE,aAAO,KAAK,MAAM,KAAK,QAAQ,SAAS,MAAM;AAAA,IAChD;AAGA,WAAO,KAAK,MAAM,GAAG,EAAE,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC;AAGtC,QAAI,CAAC,KAAK,WAAW,GAAG,GAAG;AACzB,aAAO,IAAI,IAAI;AAAA,IACjB;AAEA,WAAO;AAAA,EACT;AAAA,EAEQ,kBAAwB;AAC9B,SAAK,UAAU,QAAQ,CAAC,aAAa;AACnC,eAAS;AAAA,IACX,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAmB;AACjB,QAAI,OAAO,WAAW,aAAa;AACjC;AAAA,IACF;AAEA,QAAI,WAAW,8BAA+B;AAC9C,eAAW,gCAAgC;AAG3C,SAAK,kBAAkB,MAAM;AAC3B,WAAK,eAAe,OAAO,SAAS;AACpC,WAAK,eAAe,KAAK,MAAM,OAAO,SAAS,QAAQ;AACvD,WAAK,gBAAgB;AAAA,IACvB;AACA,WAAO,iBAAiB,YAAY,KAAK,eAAe;AAGxD,SAAK,eAAe,CAAC,MAAkB;AACrC,YAAM,SAAU,EAAE,OAAuB,QAAQ,GAAG;AAEpD,UAAI,CAAC,OAAQ;AAEb,YAAM,OAAO,OAAO,aAAa,MAAM;AAGvC,UACE,MAAM,WAAW,GAAG,KACpB,CAAC,OAAO,aAAa,QAAQ,KAC7B,CAAC,OAAO,aAAa,UAAU,KAC/B,CAAC,EAAE,WACH,CAAC,EAAE,WACH,CAAC,EAAE,YACH,CAAC,EAAE,QACH;AACA,UAAE,eAAe;AACjB,aAAK,SAAS,IAAI;AAAA,MACpB;AAAA,IACF;AACA,aAAS,iBAAiB,SAAS,KAAK,YAAY;AAAA,EACtD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAgB;AACd,QAAI,OAAO,WAAW,YAAa;AAEnC,QAAI,KAAK,iBAAiB;AACxB,aAAO,oBAAoB,YAAY,KAAK,eAAe;AAC3D,WAAK,kBAAkB;AAAA,IACzB;AACA,QAAI,KAAK,cAAc;AACrB,eAAS,oBAAoB,SAAS,KAAK,YAAY;AACvD,WAAK,eAAe;AAAA,IACtB;AAEA,SAAK,UAAU,MAAM;AAErB,eAAW,gCAAgC;AAAA,EAC7C;AACF;AAOA,SAAS,UAAU,QAAgB,OAAuB;AACxD,MAAI,CAAC,UAAU,WAAW,IAAK,QAAO;AACtC,MAAI,CAAC,SAAS,UAAU,IAAK,QAAO;AACpC,SAAO,GAAG,OAAO,QAAQ,OAAO,EAAE,CAAC,IAAI,MAAM,QAAQ,OAAO,EAAE,CAAC;AACjE;AAGA,SAAS,YACP,QACA,OACoD;AACpD,QAAM,gBAAgB,CAAC,EAAE,SAAS,MAChC,cAAc,QAAQ,MAAM,cAAc,OAAO,MAAM,QAAQ,CAAC;AAClE,SAAO;AACT;","names":[]}
@@ -93,6 +93,9 @@ interface Location {
93
93
  hash: string;
94
94
  }
95
95
 
96
+ declare global {
97
+ var __revealui_router_initialized: boolean | undefined;
98
+ }
96
99
  /**
97
100
  * RevealUI Router - Lightweight file-based routing with SSR support
98
101
  */
package/dist/server.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as _hono_node_server from '@hono/node-server';
2
2
  import { Context } from 'hono';
3
- import { b as Route, R as Router } from './router-B2MrlNC3.js';
3
+ import { b as Route, R as Router } from './router-SBtAXNTB.js';
4
4
  import 'react';
5
5
 
6
6
  /**
package/dist/server.js CHANGED
@@ -387,9 +387,8 @@ var Router = class {
387
387
  if (typeof window === "undefined") {
388
388
  return;
389
389
  }
390
- const g = globalThis;
391
- if (g.__revealui_router_initialized) return;
392
- g.__revealui_router_initialized = true;
390
+ if (globalThis.__revealui_router_initialized) return;
391
+ globalThis.__revealui_router_initialized = true;
393
392
  this.popstateHandler = () => {
394
393
  this.lastPathname = window.location.pathname;
395
394
  this.currentMatch = this.match(window.location.pathname);
@@ -422,8 +421,7 @@ var Router = class {
422
421
  this.clickHandler = null;
423
422
  }
424
423
  this.listeners.clear();
425
- const g = globalThis;
426
- g.__revealui_router_initialized = false;
424
+ globalThis.__revealui_router_initialized = false;
427
425
  }
428
426
  };
429
427
  function joinPaths(parent, child) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/server.tsx","../src/components.tsx","../src/router.ts"],"sourcesContent":["import { logger } from '@revealui/core/observability/logger';\nimport type { Context } from 'hono';\nimport { renderToReadableStream, renderToString } from 'react-dom/server';\nimport { RouterProvider, Routes } from './components';\nimport { Router } from './router';\nimport type { Route } from './types';\n\n/**\n * SSR Options\n */\nexport interface SSROptions {\n /** HTML template function */\n template?: (html: string, data?: Record<string, unknown>) => string;\n /** Enable streaming SSR */\n streaming?: boolean;\n /** Error handler */\n onError?: (error: Error, context: Context) => void;\n}\n\n/**\n * Create a Hono handler for SSR\n */\nexport function createSSRHandler(\n routes: Route[],\n options: SSROptions = {},\n): (c: Context) => Promise<Response> {\n const router = new Router();\n router.registerRoutes(routes);\n\n const defaultTemplate = (html: string, data?: Record<string, unknown>) =>\n `\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${data?.title || 'RevealUI'}</title>\n ${data?.meta || ''}\n </head>\n <body>\n <div id=\"root\">${html}</div>\n <script id=\"__REVEALUI_DATA__\" type=\"application/json\">${JSON.stringify(data || {})}</script>\n <script type=\"module\" src=\"/src/client.tsx\"></script>\n </body>\n</html>\n `.trim();\n\n const template = options.template || defaultTemplate;\n\n return async (c: Context) => {\n const url = c.req.url;\n const pathname = new URL(url).pathname;\n\n try {\n // Match and resolve route\n logger.debug('Attempting to match pathname', { pathname });\n const match = await router.resolve(pathname);\n logger.debug('Match result', {\n match: match ? { path: match.route.path, hasComponent: !!match.route.component } : null,\n });\n\n if (!match) {\n c.status(404);\n return c.html(template('<div>404 - Page Not Found</div>'));\n }\n\n // Render with streaming if enabled\n if (options.streaming) {\n const stream = await renderToReadableStream(\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n {\n onError(error) {\n logger.error('SSR error', error instanceof Error ? error : new Error(String(error)));\n if (options.onError) {\n options.onError(error as Error, c);\n }\n },\n },\n );\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\n });\n }\n\n // Regular SSR\n const html = renderToString(\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n );\n\n const data = {\n route: match.route.path,\n params: match.params,\n data: match.data,\n title: match.route.meta?.title,\n };\n\n return c.html(template(html, data));\n } catch (error) {\n logger.error('SSR error', error instanceof Error ? error : new Error(String(error)));\n\n if (options.onError) {\n options.onError(error as Error, c);\n }\n\n c.status(500);\n return c.html(template('<div>500 - Server Error</div>'));\n }\n };\n}\n\n/**\n * Create a simple dev server with Hono\n */\nexport async function createDevServer(\n routes: Route[],\n options: SSROptions & { port?: number } = {},\n) {\n const { Hono } = await import('hono');\n const { serve } = await import('@hono/node-server');\n\n const app = new Hono();\n const port = options.port || 3000;\n\n // Static files (you'll need to add your own static middleware)\n // app.use('/assets/*', serveStatic({ root: './public' }))\n\n // SSR handler for all routes\n app.get('*', createSSRHandler(routes, options));\n\n const server = serve({ fetch: app.fetch, port });\n\n logger.info('RevealUI dev server running', { url: `http://localhost:${port}` });\n\n return server;\n}\n\n/**\n * Hydrate the client-side app\n */\nexport async function hydrate(router: Router, rootElement: HTMLElement | null = null) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const root = rootElement || document.getElementById('root');\n\n if (!root) {\n logger.error('Root element not found', new Error('Root element not found'));\n return;\n }\n\n // Extract SSR data and pre-populate the router's match so loaders don't re-run\n const dataScript = document.getElementById('__REVEALUI_DATA__');\n const ssrData = dataScript ? JSON.parse(dataScript.textContent || '{}') : {};\n\n // If we have SSR data with a route, resolve it to seed the router's current match\n if (ssrData.route) {\n const match = router.match(window.location.pathname);\n if (match) {\n match.data = ssrData.data;\n }\n }\n\n // Initialize client-side routing\n router.initClient();\n\n // Use hydrateRoot for React 18+\n const { hydrateRoot } = await import('react-dom/client');\n\n hydrateRoot(\n root,\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n );\n\n logger.info('RevealUI hydrated');\n}\n","import type React from 'react';\nimport {\n Component,\n createContext,\n use,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport type { Router } from './router';\nimport type { Location, NavigateOptions, RouteMatch } from './types';\n\n/**\n * Router context\n */\nconst RouterContext = createContext<Router | null>(null);\n\n/**\n * Current match context\n */\nconst MatchContext = createContext<RouteMatch | null>(null);\n\n/**\n * RouterProvider - Provides router instance to the app\n */\nexport function RouterProvider({\n router,\n children,\n}: {\n router: Router;\n children: React.ReactNode;\n}) {\n return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>;\n}\n\n/**\n * Routes - Renders the matched route component\n */\nexport function Routes() {\n const router = useRouter();\n const options = router.getOptions();\n\n // Subscribe to router changes\n const match = useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => router.getCurrentMatch(),\n () => router.getCurrentMatch(), // Server-side snapshot (same as client)\n );\n\n if (!match) {\n const CustomNotFound = options.notFound;\n return CustomNotFound ? <CustomNotFound /> : <NotFound />;\n }\n\n const { route, params, data } = match;\n const RouteComponent = route.component;\n const Layout = route.layout;\n\n const element = <RouteComponent params={params} data={data} />;\n const wrapped = Layout ? <Layout>{element}</Layout> : element;\n\n return (\n <MatchContext.Provider value={match}>\n {options.errorBoundary ? (\n <RouteErrorBoundary fallback={options.errorBoundary}>{wrapped}</RouteErrorBoundary>\n ) : (\n wrapped\n )}\n </MatchContext.Provider>\n );\n}\n\n/**\n * Link - Client-side navigation link\n */\nexport function Link({\n to,\n replace = false,\n children,\n className,\n style,\n onClick,\n ...props\n}: {\n to: string;\n replace?: boolean;\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;\n [key: string]: unknown;\n}) {\n const router = useRouter();\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n onClick?.(e);\n\n // Don't navigate if default was prevented\n if (e.defaultPrevented) {\n return;\n }\n\n // Only handle left clicks\n if (e.button !== 0) {\n return;\n }\n\n // Ignore if modifier keys are pressed\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n e.preventDefault();\n router.navigate(to, { replace });\n };\n\n return (\n <a href={to} onClick={handleClick} className={className} style={style} {...props}>\n {children}\n </a>\n );\n}\n\n/**\n * useRouter - Hook to access router instance\n */\nexport function useRouter(): Router {\n const router = use(RouterContext);\n\n if (!router) {\n throw new Error('useRouter must be used within a RouterProvider');\n }\n\n return router;\n}\n\n/**\n * useMatch - Hook to access current route match\n */\nexport function useMatch(): RouteMatch | null {\n return use(MatchContext);\n}\n\n/**\n * useParams - Hook to access route parameters\n */\nexport function useParams<T = Record<string, string>>(): T {\n const match = useMatch();\n return (match?.params as T) || ({} as T);\n}\n\n/**\n * useData - Hook to access route data\n */\nexport function useData<T = unknown>(): T | undefined {\n const match = useMatch();\n return match?.data as T | undefined;\n}\n\n/**\n * useNavigate - Hook to get navigation function\n */\nexport function useNavigate(): (to: string, options?: NavigateOptions) => void {\n const router = useRouter();\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options);\n };\n}\n\nconst SERVER_LOCATION: Location = { pathname: '/', search: '', hash: '' };\n\nfunction getWindowLocation(): Location {\n if (typeof window === 'undefined') return SERVER_LOCATION;\n return {\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n };\n}\n\n/**\n * useLocation - Hook to access current location (pathname, search, hash).\n * Returns a stable reference that only changes when the URL actually changes.\n */\nexport function useLocation(): Location {\n const router = useRouter();\n const locationRef = useRef<Location>(getWindowLocation());\n\n // Re-subscribe on route changes to detect URL updates\n useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname + window.location.search + window.location.hash;\n },\n () => '/',\n );\n\n const current = getWindowLocation();\n if (\n current.pathname !== locationRef.current.pathname ||\n current.search !== locationRef.current.search ||\n current.hash !== locationRef.current.hash\n ) {\n locationRef.current = current;\n }\n\n return locationRef.current;\n}\n\n/**\n * useSearchParams - Hook to access parsed query string parameters\n */\nexport function useSearchParams(): URLSearchParams {\n const { search } = useLocation();\n return useMemo(() => new URLSearchParams(search), [search]);\n}\n\n/**\n * NotFound - Default 404 component\n */\nfunction NotFound() {\n return (\n <div style={{ padding: '2rem', textAlign: 'center' }}>\n <h1>404 - Page Not Found</h1>\n <p>The page you're looking for doesn't exist.</p>\n <Link to=\"/\">Go Home</Link>\n </div>\n );\n}\n\n/**\n * RouteErrorBoundary - Catches render errors in route components\n */\nclass RouteErrorBoundary extends Component<\n { fallback: React.ComponentType<{ error: Error }>; children: React.ReactNode },\n { error: Error | null }\n> {\n constructor(props: {\n fallback: React.ComponentType<{ error: Error }>;\n children: React.ReactNode;\n }) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error): { error: Error } {\n return { error };\n }\n\n render() {\n if (this.state.error) {\n const Fallback = this.props.fallback;\n return <Fallback error={this.state.error} />;\n }\n return this.props.children;\n }\n}\n\n/**\n * Navigate - Component for declarative navigation\n */\nexport function Navigate({ to, replace = false }: { to: string; replace?: boolean }) {\n const router = useRouter();\n\n useEffect(() => {\n router.navigate(to, { replace });\n }, [to, replace, router]);\n\n return null;\n}\n","import { logger } from '@revealui/core/observability/logger';\nimport type React from 'react';\nimport { createElement } from 'react';\nimport type {\n MiddlewareContext,\n NavigateOptions,\n Route,\n RouteMatch,\n RouteMiddleware,\n RouteParams,\n RouterOptions,\n} from './types';\n\n// ---------------------------------------------------------------------------\n// Hand-rolled path matcher — replaces `path-to-regexp`.\n// Supports: exact paths, named params (:id), wildcards (*path),\n// and optional segments ({/...}).\n// ---------------------------------------------------------------------------\n\ninterface PathKey {\n name: string;\n wildcard: boolean;\n}\n\n/** Maximum pattern length to prevent ReDoS from malicious/misconfigured routes */\nconst MAX_PATTERN_LENGTH = 2048;\n\nfunction compilePathPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new Error(`Route pattern exceeds ${MAX_PATTERN_LENGTH} characters`);\n }\n const keys: PathKey[] = [];\n let src = '^';\n let i = 0;\n while (i < pattern.length) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const ch = pattern[i]!;\n if (ch === '{') {\n src += '(?:';\n i++;\n } else if (ch === '}') {\n src += ')?';\n i++;\n } else if (ch === ':') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name, wildcard: false });\n src += '([^/]+)';\n } else if (ch === '*') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name: name || '0', wildcard: true });\n src += '(.+)';\n } else {\n src += ch.replace(/[.+?^$|()[\\]\\\\]/g, '\\\\$&');\n i++;\n }\n }\n src += '$';\n return { regex: new RegExp(src), keys };\n}\n\n/** Cache compiled patterns to avoid recompilation on every match() call */\nconst patternCache = new Map<string, { regex: RegExp; keys: PathKey[] }>();\n\nfunction getCompiledPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n let compiled = patternCache.get(pattern);\n if (!compiled) {\n compiled = compilePathPattern(pattern);\n patternCache.set(pattern, compiled);\n }\n return compiled;\n}\n\nfunction pathMatch(\n pattern: string,\n options: { decode?: (s: string) => string } = {},\n): (path: string) => { params: Record<string, string | string[]> } | false {\n const { regex, keys } = getCompiledPattern(pattern);\n const decode = options.decode ?? ((s) => s);\n return (path: string) => {\n const m = regex.exec(path);\n if (!m) return false;\n const params: Record<string, string | string[]> = {};\n for (let j = 0; j < keys.length; j++) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const key = keys[j]!;\n const val = m[j + 1];\n if (val === undefined) continue;\n params[key.name] = key.wildcard ? val.split('/').map(decode) : decode(val);\n }\n return { params };\n };\n}\n\n/**\n * RevealUI Router - Lightweight file-based routing with SSR support\n */\nexport class Router {\n private routes: Route[] = [];\n private flatRoutes: Route[] = [];\n private globalMiddleware: RouteMiddleware[] = [];\n private options: RouterOptions;\n private listeners: Set<() => void> = new Set();\n private currentMatch: RouteMatch | null = null;\n private lastPathname: string | null = null;\n private popstateHandler: (() => void) | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n };\n }\n\n /**\n * Add global middleware that runs before all routes.\n */\n use(...middleware: RouteMiddleware[]): void {\n this.globalMiddleware.push(...middleware);\n }\n\n /**\n * Get router options\n */\n getOptions(): RouterOptions {\n return this.options;\n }\n\n /**\n * Register a route. Nested children are flattened with combined paths,\n * middleware, and layout chains.\n */\n register(route: Route): void {\n this.routes.push(route);\n this.flattenRoute(route, '', [], undefined);\n }\n\n /**\n * Register multiple routes\n */\n registerRoutes(routes: Route[]): void {\n routes.forEach((route) => {\n this.register(route);\n });\n }\n\n /**\n * Flatten nested routes into the flat lookup table.\n * Children inherit parent path prefix, middleware, and layout.\n */\n private flattenRoute(\n route: Route,\n parentPath: string,\n parentMiddleware: RouteMiddleware[],\n parentLayout: Route['layout'],\n ): void {\n const fullPath = joinPaths(parentPath, route.path);\n const combinedMiddleware = [...parentMiddleware, ...(route.middleware ?? [])];\n // Child layout wraps inside parent layout\n const effectiveLayout =\n parentLayout && route.layout\n ? wrapLayouts(parentLayout, route.layout)\n : (route.layout ?? parentLayout);\n\n // Register this route if it has a component (layout-only routes may not)\n if (route.component) {\n this.flatRoutes.push({\n ...route,\n path: fullPath,\n middleware: combinedMiddleware.length > 0 ? combinedMiddleware : undefined,\n layout: effectiveLayout,\n children: undefined, // Already flattened\n });\n }\n\n // Recursively flatten children\n if (route.children) {\n for (const child of route.children) {\n this.flattenRoute(child, fullPath, combinedMiddleware, effectiveLayout);\n }\n }\n }\n\n /**\n * Match a URL to a route.\n * Checks flattened routes first (includes nested), then falls back to top-level.\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url);\n\n // Check flattened routes (includes nested children)\n const allRoutes = this.flatRoutes.length > 0 ? this.flatRoutes : this.routes;\n\n // Try to match each route\n for (const route of allRoutes) {\n const matcher = pathMatch(route.path, { decode: decodeURIComponent });\n const result = matcher(path);\n\n if (result) {\n return {\n route,\n params: (result.params as RouteParams) || {},\n };\n }\n }\n\n return null;\n }\n\n /**\n * Resolve a route with middleware execution and data loading.\n *\n * Middleware chain: global middleware → route middleware → loader.\n * If any middleware returns `false`, resolution is aborted (returns null).\n * If any middleware returns a string, navigation is redirected to that path.\n */\n async resolve(url: string): Promise<RouteMatch | null> {\n const matched = this.match(url);\n\n if (!matched) {\n return null;\n }\n\n // Run middleware chain (global + route-specific)\n const allMiddleware = [...this.globalMiddleware, ...(matched.route.middleware ?? [])];\n\n if (allMiddleware.length > 0) {\n const context: MiddlewareContext = {\n pathname: this.normalizePath(url),\n params: matched.params,\n meta: matched.route.meta,\n };\n\n for (const mw of allMiddleware) {\n const result = await mw(context);\n if (result === false) {\n return null; // Middleware blocked\n }\n if (typeof result === 'string') {\n // Middleware requested redirect\n this.navigate(result);\n return null;\n }\n }\n }\n\n // Load data if loader exists\n if (matched.route.loader) {\n try {\n matched.data = await matched.route.loader(matched.params);\n } catch (error) {\n logger.error(\n 'Route loader error',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n }\n\n // Store resolved match for getCurrentMatch (SSR and resolve-based flows)\n this.currentMatch = matched;\n\n return matched;\n }\n\n /**\n * Navigate to a URL (client-side only)\n */\n navigate(url: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const fullUrl = this.options.basePath + url;\n\n if (options.replace) {\n window.history.replaceState(options.state || null, '', fullUrl);\n } else {\n window.history.pushState(options.state || null, '', fullUrl);\n }\n\n // Update cached match so useSyncExternalStore gets a stable reference\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n\n this.notifyListeners();\n }\n\n /**\n * Go back in history\n */\n back(): void {\n if (typeof window !== 'undefined') {\n window.history.back();\n }\n }\n\n /**\n * Go forward in history\n */\n forward(): void {\n if (typeof window !== 'undefined') {\n window.history.forward();\n }\n }\n\n /**\n * Subscribe to route changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Get current route match\n */\n getCurrentMatch(): RouteMatch | null {\n if (typeof window === 'undefined') {\n return this.currentMatch;\n }\n\n // Return cached match if pathname hasn't changed (stable reference for useSyncExternalStore)\n const pathname = window.location.pathname;\n if (pathname !== this.lastPathname) {\n this.lastPathname = pathname;\n this.currentMatch = this.match(pathname);\n }\n\n return this.currentMatch;\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes];\n }\n\n /**\n * Clear all routes and middleware\n */\n clear(): void {\n this.routes = [];\n this.flatRoutes = [];\n this.globalMiddleware = [];\n }\n\n private normalizePath(url: string): string {\n // Remove base path\n let path = url;\n if (this.options.basePath && path.startsWith(this.options.basePath)) {\n path = path.slice(this.options.basePath.length);\n }\n\n // Remove query string and hash\n path = path.split('?')[0].split('#')[0];\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = `/${path}`;\n }\n\n return path;\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => {\n listener();\n });\n }\n\n /**\n * Initialize client-side routing.\n * Uses a global flag to prevent duplicate event listeners on HMR re-invocation.\n */\n initClient(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: global HMR guard\n const g = globalThis as any;\n if (g.__revealui_router_initialized) return;\n g.__revealui_router_initialized = true;\n\n // Handle browser back/forward buttons\n this.popstateHandler = () => {\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n this.notifyListeners();\n };\n window.addEventListener('popstate', this.popstateHandler);\n\n // Intercept link clicks\n this.clickHandler = (e: MouseEvent) => {\n const target = (e.target as HTMLElement).closest('a');\n\n if (!target) return;\n\n const href = target.getAttribute('href');\n\n // Only handle internal links\n if (\n href?.startsWith('/') &&\n !target.hasAttribute('target') &&\n !target.hasAttribute('download') &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n ) {\n e.preventDefault();\n this.navigate(href);\n }\n };\n document.addEventListener('click', this.clickHandler);\n }\n\n /**\n * Clean up client-side event listeners.\n * Call this before unmounting or during HMR teardown.\n */\n dispose(): void {\n if (typeof window === 'undefined') return;\n\n if (this.popstateHandler) {\n window.removeEventListener('popstate', this.popstateHandler);\n this.popstateHandler = null;\n }\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler);\n this.clickHandler = null;\n }\n\n this.listeners.clear();\n\n // biome-ignore lint/suspicious/noExplicitAny: global HMR guard cleanup\n const g = globalThis as any;\n g.__revealui_router_initialized = false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Join parent and child path segments, avoiding double slashes */\nfunction joinPaths(parent: string, child: string): string {\n if (!parent || parent === '/') return child;\n if (!child || child === '/') return parent;\n return `${parent.replace(/\\/$/, '')}/${child.replace(/^\\//, '')}`;\n}\n\n/** Compose two layout components so the child renders inside the parent */\nfunction wrapLayouts(\n Parent: React.ComponentType<{ children: React.ReactNode }>,\n Child: React.ComponentType<{ children: React.ReactNode }>,\n): React.ComponentType<{ children: React.ReactNode }> {\n const WrappedLayout = ({ children }: { children: React.ReactNode }) =>\n createElement(Parent, null, createElement(Child, null, children));\n return WrappedLayout;\n}\n"],"mappings":"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","names":["logger","jsx","logger"]}
1
+ {"version":3,"sources":["../src/server.tsx","../src/components.tsx","../src/router.ts"],"sourcesContent":["import { logger } from '@revealui/core/observability/logger';\nimport type { Context } from 'hono';\nimport { renderToReadableStream, renderToString } from 'react-dom/server';\nimport { RouterProvider, Routes } from './components';\nimport { Router } from './router';\nimport type { Route } from './types';\n\n/**\n * SSR Options\n */\nexport interface SSROptions {\n /** HTML template function */\n template?: (html: string, data?: Record<string, unknown>) => string;\n /** Enable streaming SSR */\n streaming?: boolean;\n /** Error handler */\n onError?: (error: Error, context: Context) => void;\n}\n\n/**\n * Create a Hono handler for SSR\n */\nexport function createSSRHandler(\n routes: Route[],\n options: SSROptions = {},\n): (c: Context) => Promise<Response> {\n const router = new Router();\n router.registerRoutes(routes);\n\n const defaultTemplate = (html: string, data?: Record<string, unknown>) =>\n `\n<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>${data?.title || 'RevealUI'}</title>\n ${data?.meta || ''}\n </head>\n <body>\n <div id=\"root\">${html}</div>\n <script id=\"__REVEALUI_DATA__\" type=\"application/json\">${JSON.stringify(data || {})}</script>\n <script type=\"module\" src=\"/src/client.tsx\"></script>\n </body>\n</html>\n `.trim();\n\n const template = options.template || defaultTemplate;\n\n return async (c: Context) => {\n const url = c.req.url;\n const pathname = new URL(url).pathname;\n\n try {\n // Match and resolve route\n logger.debug('Attempting to match pathname', { pathname });\n const match = await router.resolve(pathname);\n logger.debug('Match result', {\n match: match ? { path: match.route.path, hasComponent: !!match.route.component } : null,\n });\n\n if (!match) {\n c.status(404);\n return c.html(template('<div>404 - Page Not Found</div>'));\n }\n\n // Render with streaming if enabled\n if (options.streaming) {\n const stream = await renderToReadableStream(\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n {\n onError(error) {\n logger.error('SSR error', error instanceof Error ? error : new Error(String(error)));\n if (options.onError) {\n options.onError(error as Error, c);\n }\n },\n },\n );\n\n return new Response(stream, {\n headers: { 'Content-Type': 'text/html; charset=utf-8' },\n });\n }\n\n // Regular SSR\n const html = renderToString(\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n );\n\n const data = {\n route: match.route.path,\n params: match.params,\n data: match.data,\n title: match.route.meta?.title,\n };\n\n return c.html(template(html, data));\n } catch (error) {\n logger.error('SSR error', error instanceof Error ? error : new Error(String(error)));\n\n if (options.onError) {\n options.onError(error as Error, c);\n }\n\n c.status(500);\n return c.html(template('<div>500 - Server Error</div>'));\n }\n };\n}\n\n/**\n * Create a simple dev server with Hono\n */\nexport async function createDevServer(\n routes: Route[],\n options: SSROptions & { port?: number } = {},\n) {\n const { Hono } = await import('hono');\n const { serve } = await import('@hono/node-server');\n\n const app = new Hono();\n const port = options.port || 3000;\n\n // Static files (you'll need to add your own static middleware)\n // app.use('/assets/*', serveStatic({ root: './public' }))\n\n // SSR handler for all routes\n app.get('*', createSSRHandler(routes, options));\n\n const server = serve({ fetch: app.fetch, port });\n\n logger.info('RevealUI dev server running', { url: `http://localhost:${port}` });\n\n return server;\n}\n\n/**\n * Hydrate the client-side app\n */\nexport async function hydrate(router: Router, rootElement: HTMLElement | null = null) {\n if (typeof window === 'undefined') {\n return;\n }\n\n const root = rootElement || document.getElementById('root');\n\n if (!root) {\n logger.error('Root element not found', new Error('Root element not found'));\n return;\n }\n\n // Extract SSR data and pre-populate the router's match so loaders don't re-run\n const dataScript = document.getElementById('__REVEALUI_DATA__');\n const ssrData = dataScript ? JSON.parse(dataScript.textContent || '{}') : {};\n\n // If we have SSR data with a route, resolve it to seed the router's current match\n if (ssrData.route) {\n const match = router.match(window.location.pathname);\n if (match) {\n match.data = ssrData.data;\n }\n }\n\n // Initialize client-side routing\n router.initClient();\n\n // Use hydrateRoot for React 18+\n const { hydrateRoot } = await import('react-dom/client');\n\n hydrateRoot(\n root,\n <RouterProvider router={router}>\n <Routes />\n </RouterProvider>,\n );\n\n logger.info('RevealUI hydrated');\n}\n","import type React from 'react';\nimport {\n Component,\n createContext,\n use,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n} from 'react';\nimport type { Router } from './router';\nimport type { Location, NavigateOptions, RouteMatch } from './types';\n\n/**\n * Router context\n */\nconst RouterContext = createContext<Router | null>(null);\n\n/**\n * Current match context\n */\nconst MatchContext = createContext<RouteMatch | null>(null);\n\n/**\n * RouterProvider - Provides router instance to the app\n */\nexport function RouterProvider({\n router,\n children,\n}: {\n router: Router;\n children: React.ReactNode;\n}) {\n return <RouterContext.Provider value={router}>{children}</RouterContext.Provider>;\n}\n\n/**\n * Routes - Renders the matched route component\n */\nexport function Routes() {\n const router = useRouter();\n const options = router.getOptions();\n\n // Subscribe to router changes\n const match = useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => router.getCurrentMatch(),\n () => router.getCurrentMatch(), // Server-side snapshot (same as client)\n );\n\n if (!match) {\n const CustomNotFound = options.notFound;\n return CustomNotFound ? <CustomNotFound /> : <NotFound />;\n }\n\n const { route, params, data } = match;\n const RouteComponent = route.component;\n const Layout = route.layout;\n\n const element = <RouteComponent params={params} data={data} />;\n const wrapped = Layout ? <Layout>{element}</Layout> : element;\n\n return (\n <MatchContext.Provider value={match}>\n {options.errorBoundary ? (\n <RouteErrorBoundary fallback={options.errorBoundary}>{wrapped}</RouteErrorBoundary>\n ) : (\n wrapped\n )}\n </MatchContext.Provider>\n );\n}\n\ninterface LinkProps extends Record<string, unknown> {\n to: string;\n replace?: boolean;\n children: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;\n}\n\n/**\n * Link - Client-side navigation link\n */\nexport function Link({\n to,\n replace = false,\n children,\n className,\n style,\n onClick,\n ...props\n}: LinkProps) {\n const router = useRouter();\n\n const handleClick = (e: React.MouseEvent<HTMLAnchorElement>) => {\n // Call custom onClick if provided\n onClick?.(e);\n\n // Don't navigate if default was prevented\n if (e.defaultPrevented) {\n return;\n }\n\n // Only handle left clicks\n if (e.button !== 0) {\n return;\n }\n\n // Ignore if modifier keys are pressed\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {\n return;\n }\n\n e.preventDefault();\n router.navigate(to, { replace });\n };\n\n return (\n <a href={to} onClick={handleClick} className={className} style={style} {...props}>\n {children}\n </a>\n );\n}\n\n/**\n * useRouter - Hook to access router instance\n */\nexport function useRouter(): Router {\n const router = use(RouterContext);\n\n if (!router) {\n throw new Error('useRouter must be used within a RouterProvider');\n }\n\n return router;\n}\n\n/**\n * useMatch - Hook to access current route match\n */\nexport function useMatch(): RouteMatch | null {\n return use(MatchContext);\n}\n\n/**\n * useParams - Hook to access route parameters\n */\nexport function useParams<T = Record<string, string>>(): T {\n const match = useMatch();\n return (match?.params as T) || ({} as T);\n}\n\n/**\n * useData - Hook to access route data\n */\nexport function useData<T = unknown>(): T | undefined {\n const match = useMatch();\n return match?.data as T | undefined;\n}\n\n/**\n * useNavigate - Hook to get navigation function\n */\nexport function useNavigate(): (to: string, options?: NavigateOptions) => void {\n const router = useRouter();\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options);\n };\n}\n\nconst SERVER_LOCATION: Location = { pathname: '/', search: '', hash: '' };\n\nfunction getWindowLocation(): Location {\n if (typeof window === 'undefined') return SERVER_LOCATION;\n return {\n pathname: window.location.pathname,\n search: window.location.search,\n hash: window.location.hash,\n };\n}\n\n/**\n * useLocation - Hook to access current location (pathname, search, hash).\n * Returns a stable reference that only changes when the URL actually changes.\n */\nexport function useLocation(): Location {\n const router = useRouter();\n const locationRef = useRef<Location>(getWindowLocation());\n\n // Re-subscribe on route changes to detect URL updates\n useSyncExternalStore(\n (callback) => router.subscribe(callback),\n () => {\n if (typeof window === 'undefined') return '/';\n return window.location.pathname + window.location.search + window.location.hash;\n },\n () => '/',\n );\n\n const current = getWindowLocation();\n if (\n current.pathname !== locationRef.current.pathname ||\n current.search !== locationRef.current.search ||\n current.hash !== locationRef.current.hash\n ) {\n locationRef.current = current;\n }\n\n return locationRef.current;\n}\n\n/**\n * useSearchParams - Hook to access parsed query string parameters\n */\nexport function useSearchParams(): URLSearchParams {\n const { search } = useLocation();\n return useMemo(() => new URLSearchParams(search), [search]);\n}\n\n/**\n * NotFound - Default 404 component\n */\nfunction NotFound() {\n return (\n <div style={{ padding: '2rem', textAlign: 'center' }}>\n <h1>404 - Page Not Found</h1>\n <p>The page you're looking for doesn't exist.</p>\n <Link to=\"/\">Go Home</Link>\n </div>\n );\n}\n\n/**\n * RouteErrorBoundary - Catches render errors in route components\n */\nclass RouteErrorBoundary extends Component<\n { fallback: React.ComponentType<{ error: Error }>; children: React.ReactNode },\n { error: Error | null }\n> {\n constructor(props: {\n fallback: React.ComponentType<{ error: Error }>;\n children: React.ReactNode;\n }) {\n super(props);\n this.state = { error: null };\n }\n\n static getDerivedStateFromError(error: Error): { error: Error } {\n return { error };\n }\n\n render() {\n if (this.state.error) {\n const Fallback = this.props.fallback;\n return <Fallback error={this.state.error} />;\n }\n return this.props.children;\n }\n}\n\n/**\n * Navigate - Component for declarative navigation\n */\nexport function Navigate({ to, replace = false }: { to: string; replace?: boolean }) {\n const router = useRouter();\n\n useEffect(() => {\n router.navigate(to, { replace });\n }, [to, replace, router]);\n\n return null;\n}\n","import { logger } from '@revealui/core/observability/logger';\nimport type React from 'react';\nimport { createElement } from 'react';\nimport type {\n MiddlewareContext,\n NavigateOptions,\n Route,\n RouteMatch,\n RouteMiddleware,\n RouteParams,\n RouterOptions,\n} from './types';\n\ndeclare global {\n var __revealui_router_initialized: boolean | undefined;\n}\n\n// ---------------------------------------------------------------------------\n// Hand-rolled path matcher — replaces `path-to-regexp`.\n// Supports: exact paths, named params (:id), wildcards (*path),\n// and optional segments ({/...}).\n// ---------------------------------------------------------------------------\n\ninterface PathKey {\n name: string;\n wildcard: boolean;\n}\n\n/** Maximum pattern length to prevent ReDoS from malicious/misconfigured routes */\nconst MAX_PATTERN_LENGTH = 2048;\n\nfunction compilePathPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n if (pattern.length > MAX_PATTERN_LENGTH) {\n throw new Error(`Route pattern exceeds ${MAX_PATTERN_LENGTH} characters`);\n }\n const keys: PathKey[] = [];\n let src = '^';\n let i = 0;\n while (i < pattern.length) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const ch = pattern[i]!;\n if (ch === '{') {\n src += '(?:';\n i++;\n } else if (ch === '}') {\n src += ')?';\n i++;\n } else if (ch === ':') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name, wildcard: false });\n src += '([^/]+)';\n } else if (ch === '*') {\n i++;\n let name = '';\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n while (i < pattern.length && /\\w/.test(pattern[i]!)) name += pattern[i++];\n keys.push({ name: name || '0', wildcard: true });\n src += '(.+)';\n } else {\n src += ch.replace(/[.+?^$|()[\\]\\\\]/g, '\\\\$&');\n i++;\n }\n }\n src += '$';\n return { regex: new RegExp(src), keys };\n}\n\n/** Cache compiled patterns to avoid recompilation on every match() call */\nconst patternCache = new Map<string, { regex: RegExp; keys: PathKey[] }>();\n\nfunction getCompiledPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n let compiled = patternCache.get(pattern);\n if (!compiled) {\n compiled = compilePathPattern(pattern);\n patternCache.set(pattern, compiled);\n }\n return compiled;\n}\n\nfunction pathMatch(\n pattern: string,\n options: { decode?: (s: string) => string } = {},\n): (path: string) => { params: Record<string, string | string[]> } | false {\n const { regex, keys } = getCompiledPattern(pattern);\n const decode = options.decode ?? ((s) => s);\n return (path: string) => {\n const m = regex.exec(path);\n if (!m) return false;\n const params: Record<string, string | string[]> = {};\n for (let j = 0; j < keys.length; j++) {\n // biome-ignore lint/style/noNonNullAssertion: index bounds-checked by loop condition\n const key = keys[j]!;\n const val = m[j + 1];\n if (val === undefined) continue;\n params[key.name] = key.wildcard ? val.split('/').map(decode) : decode(val);\n }\n return { params };\n };\n}\n\n/**\n * RevealUI Router - Lightweight file-based routing with SSR support\n */\nexport class Router {\n private routes: Route[] = [];\n private flatRoutes: Route[] = [];\n private globalMiddleware: RouteMiddleware[] = [];\n private options: RouterOptions;\n private listeners: Set<() => void> = new Set();\n private currentMatch: RouteMatch | null = null;\n private lastPathname: string | null = null;\n private popstateHandler: (() => void) | null = null;\n private clickHandler: ((e: MouseEvent) => void) | null = null;\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n };\n }\n\n /**\n * Add global middleware that runs before all routes.\n */\n use(...middleware: RouteMiddleware[]): void {\n this.globalMiddleware.push(...middleware);\n }\n\n /**\n * Get router options\n */\n getOptions(): RouterOptions {\n return this.options;\n }\n\n /**\n * Register a route. Nested children are flattened with combined paths,\n * middleware, and layout chains.\n */\n register(route: Route): void {\n this.routes.push(route);\n this.flattenRoute(route, '', [], undefined);\n }\n\n /**\n * Register multiple routes\n */\n registerRoutes(routes: Route[]): void {\n routes.forEach((route) => {\n this.register(route);\n });\n }\n\n /**\n * Flatten nested routes into the flat lookup table.\n * Children inherit parent path prefix, middleware, and layout.\n */\n private flattenRoute(\n route: Route,\n parentPath: string,\n parentMiddleware: RouteMiddleware[],\n parentLayout: Route['layout'],\n ): void {\n const fullPath = joinPaths(parentPath, route.path);\n const combinedMiddleware = [...parentMiddleware, ...(route.middleware ?? [])];\n // Child layout wraps inside parent layout\n const effectiveLayout =\n parentLayout && route.layout\n ? wrapLayouts(parentLayout, route.layout)\n : (route.layout ?? parentLayout);\n\n // Register this route if it has a component (layout-only routes may not)\n if (route.component) {\n this.flatRoutes.push({\n ...route,\n path: fullPath,\n middleware: combinedMiddleware.length > 0 ? combinedMiddleware : undefined,\n layout: effectiveLayout,\n children: undefined, // Already flattened\n });\n }\n\n // Recursively flatten children\n if (route.children) {\n for (const child of route.children) {\n this.flattenRoute(child, fullPath, combinedMiddleware, effectiveLayout);\n }\n }\n }\n\n /**\n * Match a URL to a route.\n * Checks flattened routes first (includes nested), then falls back to top-level.\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url);\n\n // Check flattened routes (includes nested children)\n const allRoutes = this.flatRoutes.length > 0 ? this.flatRoutes : this.routes;\n\n // Try to match each route\n for (const route of allRoutes) {\n const matcher = pathMatch(route.path, { decode: decodeURIComponent });\n const result = matcher(path);\n\n if (result) {\n return {\n route,\n params: (result.params as RouteParams) || {},\n };\n }\n }\n\n return null;\n }\n\n /**\n * Resolve a route with middleware execution and data loading.\n *\n * Middleware chain: global middleware → route middleware → loader.\n * If any middleware returns `false`, resolution is aborted (returns null).\n * If any middleware returns a string, navigation is redirected to that path.\n */\n async resolve(url: string): Promise<RouteMatch | null> {\n const matched = this.match(url);\n\n if (!matched) {\n return null;\n }\n\n // Run middleware chain (global + route-specific)\n const allMiddleware = [...this.globalMiddleware, ...(matched.route.middleware ?? [])];\n\n if (allMiddleware.length > 0) {\n const context: MiddlewareContext = {\n pathname: this.normalizePath(url),\n params: matched.params,\n meta: matched.route.meta,\n };\n\n for (const mw of allMiddleware) {\n const result = await mw(context);\n if (result === false) {\n return null; // Middleware blocked\n }\n if (typeof result === 'string') {\n // Middleware requested redirect\n this.navigate(result);\n return null;\n }\n }\n }\n\n // Load data if loader exists\n if (matched.route.loader) {\n try {\n matched.data = await matched.route.loader(matched.params);\n } catch (error) {\n logger.error(\n 'Route loader error',\n error instanceof Error ? error : new Error(String(error)),\n );\n throw error;\n }\n }\n\n // Store resolved match for getCurrentMatch (SSR and resolve-based flows)\n this.currentMatch = matched;\n\n return matched;\n }\n\n /**\n * Navigate to a URL (client-side only)\n */\n navigate(url: string, options: NavigateOptions = {}): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n const fullUrl = this.options.basePath + url;\n\n if (options.replace) {\n window.history.replaceState(options.state || null, '', fullUrl);\n } else {\n window.history.pushState(options.state || null, '', fullUrl);\n }\n\n // Update cached match so useSyncExternalStore gets a stable reference\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n\n this.notifyListeners();\n }\n\n /**\n * Go back in history\n */\n back(): void {\n if (typeof window !== 'undefined') {\n window.history.back();\n }\n }\n\n /**\n * Go forward in history\n */\n forward(): void {\n if (typeof window !== 'undefined') {\n window.history.forward();\n }\n }\n\n /**\n * Subscribe to route changes\n */\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n\n // Return unsubscribe function\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Get current route match\n */\n getCurrentMatch(): RouteMatch | null {\n if (typeof window === 'undefined') {\n return this.currentMatch;\n }\n\n // Return cached match if pathname hasn't changed (stable reference for useSyncExternalStore)\n const pathname = window.location.pathname;\n if (pathname !== this.lastPathname) {\n this.lastPathname = pathname;\n this.currentMatch = this.match(pathname);\n }\n\n return this.currentMatch;\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes];\n }\n\n /**\n * Clear all routes and middleware\n */\n clear(): void {\n this.routes = [];\n this.flatRoutes = [];\n this.globalMiddleware = [];\n }\n\n private normalizePath(url: string): string {\n // Remove base path\n let path = url;\n if (this.options.basePath && path.startsWith(this.options.basePath)) {\n path = path.slice(this.options.basePath.length);\n }\n\n // Remove query string and hash\n path = path.split('?')[0].split('#')[0];\n\n // Ensure leading slash\n if (!path.startsWith('/')) {\n path = `/${path}`;\n }\n\n return path;\n }\n\n private notifyListeners(): void {\n this.listeners.forEach((listener) => {\n listener();\n });\n }\n\n /**\n * Initialize client-side routing.\n * Uses a global flag to prevent duplicate event listeners on HMR re-invocation.\n */\n initClient(): void {\n if (typeof window === 'undefined') {\n return;\n }\n\n if (globalThis.__revealui_router_initialized) return;\n globalThis.__revealui_router_initialized = true;\n\n // Handle browser back/forward buttons\n this.popstateHandler = () => {\n this.lastPathname = window.location.pathname;\n this.currentMatch = this.match(window.location.pathname);\n this.notifyListeners();\n };\n window.addEventListener('popstate', this.popstateHandler);\n\n // Intercept link clicks\n this.clickHandler = (e: MouseEvent) => {\n const target = (e.target as HTMLElement).closest('a');\n\n if (!target) return;\n\n const href = target.getAttribute('href');\n\n // Only handle internal links\n if (\n href?.startsWith('/') &&\n !target.hasAttribute('target') &&\n !target.hasAttribute('download') &&\n !e.metaKey &&\n !e.ctrlKey &&\n !e.shiftKey &&\n !e.altKey\n ) {\n e.preventDefault();\n this.navigate(href);\n }\n };\n document.addEventListener('click', this.clickHandler);\n }\n\n /**\n * Clean up client-side event listeners.\n * Call this before unmounting or during HMR teardown.\n */\n dispose(): void {\n if (typeof window === 'undefined') return;\n\n if (this.popstateHandler) {\n window.removeEventListener('popstate', this.popstateHandler);\n this.popstateHandler = null;\n }\n if (this.clickHandler) {\n document.removeEventListener('click', this.clickHandler);\n this.clickHandler = null;\n }\n\n this.listeners.clear();\n\n globalThis.__revealui_router_initialized = false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/** Join parent and child path segments, avoiding double slashes */\nfunction joinPaths(parent: string, child: string): string {\n if (!parent || parent === '/') return child;\n if (!child || child === '/') return parent;\n return `${parent.replace(/\\/$/, '')}/${child.replace(/^\\//, '')}`;\n}\n\n/** Compose two layout components so the child renders inside the parent */\nfunction wrapLayouts(\n Parent: React.ComponentType<{ children: React.ReactNode }>,\n Child: React.ComponentType<{ children: React.ReactNode }>,\n): React.ComponentType<{ children: React.ReactNode }> {\n const WrappedLayout = ({ children }: { children: React.ReactNode }) =>\n createElement(Parent, null, createElement(Child, null, children));\n return WrappedLayout;\n}\n"],"mappings":"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","names":["logger","jsx","logger"]}
package/package.json CHANGED
@@ -1,25 +1,22 @@
1
1
  {
2
2
  "name": "@revealui/router",
3
- "version": "0.3.0",
4
- "license": "MIT",
5
- "files": [
6
- "dist"
7
- ],
3
+ "version": "0.3.4",
8
4
  "description": "Lightweight internal router for RevealUI apps (docs, marketing). Not a general-purpose router.",
5
+ "license": "MIT",
9
6
  "dependencies": {
10
7
  "@hono/node-server": "^1.19.10",
11
8
  "hono": "^4.12.7",
12
- "@revealui/core": "0.3.0"
9
+ "@revealui/core": "0.5.2"
13
10
  },
14
11
  "devDependencies": {
15
12
  "@testing-library/jest-dom": "^6.9.1",
16
13
  "@testing-library/react": "^16.3.2",
17
- "@types/node": "^25.3.0",
14
+ "@types/node": "^25.5.0",
18
15
  "@types/react": "^19.2.14",
19
16
  "react": "^19.2.3",
20
17
  "react-dom": "^19.2.3",
21
18
  "tsup": "^8.0.0",
22
- "typescript": "^5.9.3"
19
+ "typescript": "^6.0.2"
23
20
  },
24
21
  "engines": {
25
22
  "node": ">=24.13.0"
@@ -34,6 +31,9 @@
34
31
  "import": "./dist/server.js"
35
32
  }
36
33
  },
34
+ "files": [
35
+ "dist"
36
+ ],
37
37
  "main": "./dist/index.js",
38
38
  "peerDependencies": {
39
39
  "react": "^18.0.0 || ^19.0.0",