@revealui/router 0.2.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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, a as RouteMatch, N as NavigateOptions } from './router-DctgwX83.js';
4
- export { b as Route, c as RouteMeta, d as RouteParams, e as RouterOptions } from './router-DctgwX83.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
  */
@@ -46,6 +46,15 @@ declare function useData<T = unknown>(): T | undefined;
46
46
  * useNavigate - Hook to get navigation function
47
47
  */
48
48
  declare function useNavigate(): (to: string, options?: NavigateOptions) => void;
49
+ /**
50
+ * useLocation - Hook to access current location (pathname, search, hash).
51
+ * Returns a stable reference that only changes when the URL actually changes.
52
+ */
53
+ declare function useLocation(): Location;
54
+ /**
55
+ * useSearchParams - Hook to access parsed query string parameters
56
+ */
57
+ declare function useSearchParams(): URLSearchParams;
49
58
  /**
50
59
  * Navigate - Component for declarative navigation
51
60
  */
@@ -54,4 +63,4 @@ declare function Navigate({ to, replace }: {
54
63
  replace?: boolean;
55
64
  }): null;
56
65
 
57
- export { Link, Navigate, NavigateOptions, RouteMatch, Router, RouterProvider, Routes, useData, useMatch, useNavigate, useParams, useRouter };
66
+ export { Link, Location, Navigate, NavigateOptions, RouteMatch, Router, RouterProvider, Routes, useData, useLocation, useMatch, useNavigate, useParams, useRouter, useSearchParams };
package/dist/index.js CHANGED
@@ -1,5 +1,13 @@
1
1
  // src/components.tsx
2
- import { createContext, useContext, useEffect, useSyncExternalStore } from "react";
2
+ import {
3
+ Component,
4
+ createContext,
5
+ use,
6
+ useEffect,
7
+ useMemo,
8
+ useRef,
9
+ useSyncExternalStore
10
+ } from "react";
3
11
  import { jsx, jsxs } from "react/jsx-runtime";
4
12
  var RouterContext = createContext(null);
5
13
  var MatchContext = createContext(null);
@@ -11,6 +19,7 @@ function RouterProvider({
11
19
  }
12
20
  function Routes() {
13
21
  const router = useRouter();
22
+ const options = router.getOptions();
14
23
  const match = useSyncExternalStore(
15
24
  (callback) => router.subscribe(callback),
16
25
  () => router.getCurrentMatch(),
@@ -18,13 +27,15 @@ function Routes() {
18
27
  // Server-side snapshot (same as client)
19
28
  );
20
29
  if (!match) {
21
- return /* @__PURE__ */ jsx(NotFound, {});
30
+ const CustomNotFound = options.notFound;
31
+ return CustomNotFound ? /* @__PURE__ */ jsx(CustomNotFound, {}) : /* @__PURE__ */ jsx(NotFound, {});
22
32
  }
23
33
  const { route, params, data } = match;
24
- const Component = route.component;
34
+ const RouteComponent = route.component;
25
35
  const Layout = route.layout;
26
- const element = /* @__PURE__ */ jsx(Component, { params, data });
27
- return /* @__PURE__ */ jsx(MatchContext.Provider, { value: match, children: Layout ? /* @__PURE__ */ jsx(Layout, { children: element }) : element });
36
+ const element = /* @__PURE__ */ jsx(RouteComponent, { params, data });
37
+ const wrapped = Layout ? /* @__PURE__ */ jsx(Layout, { children: element }) : element;
38
+ return /* @__PURE__ */ jsx(MatchContext.Provider, { value: match, children: options.errorBoundary ? /* @__PURE__ */ jsx(RouteErrorBoundary, { fallback: options.errorBoundary, children: wrapped }) : wrapped });
28
39
  }
29
40
  function Link({
30
41
  to,
@@ -53,14 +64,14 @@ function Link({
53
64
  return /* @__PURE__ */ jsx("a", { href: to, onClick: handleClick, className, style, ...props, children });
54
65
  }
55
66
  function useRouter() {
56
- const router = useContext(RouterContext);
67
+ const router = use(RouterContext);
57
68
  if (!router) {
58
69
  throw new Error("useRouter must be used within a RouterProvider");
59
70
  }
60
71
  return router;
61
72
  }
62
73
  function useMatch() {
63
- return useContext(MatchContext);
74
+ return use(MatchContext);
64
75
  }
65
76
  function useParams() {
66
77
  const match = useMatch();
@@ -76,6 +87,36 @@ function useNavigate() {
76
87
  router.navigate(to, options);
77
88
  };
78
89
  }
90
+ var SERVER_LOCATION = { pathname: "/", search: "", hash: "" };
91
+ function getWindowLocation() {
92
+ if (typeof window === "undefined") return SERVER_LOCATION;
93
+ return {
94
+ pathname: window.location.pathname,
95
+ search: window.location.search,
96
+ hash: window.location.hash
97
+ };
98
+ }
99
+ function useLocation() {
100
+ const router = useRouter();
101
+ const locationRef = useRef(getWindowLocation());
102
+ useSyncExternalStore(
103
+ (callback) => router.subscribe(callback),
104
+ () => {
105
+ if (typeof window === "undefined") return "/";
106
+ return window.location.pathname + window.location.search + window.location.hash;
107
+ },
108
+ () => "/"
109
+ );
110
+ const current = getWindowLocation();
111
+ if (current.pathname !== locationRef.current.pathname || current.search !== locationRef.current.search || current.hash !== locationRef.current.hash) {
112
+ locationRef.current = current;
113
+ }
114
+ return locationRef.current;
115
+ }
116
+ function useSearchParams() {
117
+ const { search } = useLocation();
118
+ return useMemo(() => new URLSearchParams(search), [search]);
119
+ }
79
120
  function NotFound() {
80
121
  return /* @__PURE__ */ jsxs("div", { style: { padding: "2rem", textAlign: "center" }, children: [
81
122
  /* @__PURE__ */ jsx("h1", { children: "404 - Page Not Found" }),
@@ -83,6 +124,22 @@ function NotFound() {
83
124
  /* @__PURE__ */ jsx(Link, { to: "/", children: "Go Home" })
84
125
  ] });
85
126
  }
127
+ var RouteErrorBoundary = class extends Component {
128
+ constructor(props) {
129
+ super(props);
130
+ this.state = { error: null };
131
+ }
132
+ static getDerivedStateFromError(error) {
133
+ return { error };
134
+ }
135
+ render() {
136
+ if (this.state.error) {
137
+ const Fallback = this.props.fallback;
138
+ return /* @__PURE__ */ jsx(Fallback, { error: this.state.error });
139
+ }
140
+ return this.props.children;
141
+ }
142
+ };
86
143
  function Navigate({ to, replace = false }) {
87
144
  const router = useRouter();
88
145
  useEffect(() => {
@@ -93,7 +150,12 @@ function Navigate({ to, replace = false }) {
93
150
 
94
151
  // src/router.ts
95
152
  import { logger } from "@revealui/core/observability/logger";
153
+ import { createElement } from "react";
154
+ var MAX_PATTERN_LENGTH = 2048;
96
155
  function compilePathPattern(pattern) {
156
+ if (pattern.length > MAX_PATTERN_LENGTH) {
157
+ throw new Error(`Route pattern exceeds ${MAX_PATTERN_LENGTH} characters`);
158
+ }
97
159
  const keys = [];
98
160
  let src = "^";
99
161
  let i = 0;
@@ -125,8 +187,17 @@ function compilePathPattern(pattern) {
125
187
  src += "$";
126
188
  return { regex: new RegExp(src), keys };
127
189
  }
190
+ var patternCache = /* @__PURE__ */ new Map();
191
+ function getCompiledPattern(pattern) {
192
+ let compiled = patternCache.get(pattern);
193
+ if (!compiled) {
194
+ compiled = compilePathPattern(pattern);
195
+ patternCache.set(pattern, compiled);
196
+ }
197
+ return compiled;
198
+ }
128
199
  function pathMatch(pattern, options = {}) {
129
- const { regex, keys } = compilePathPattern(pattern);
200
+ const { regex, keys } = getCompiledPattern(pattern);
130
201
  const decode = options.decode ?? ((s) => s);
131
202
  return (path) => {
132
203
  const m = regex.exec(path);
@@ -143,9 +214,14 @@ function pathMatch(pattern, options = {}) {
143
214
  }
144
215
  var Router = class {
145
216
  routes = [];
217
+ flatRoutes = [];
218
+ globalMiddleware = [];
146
219
  options;
147
220
  listeners = /* @__PURE__ */ new Set();
148
221
  currentMatch = null;
222
+ lastPathname = null;
223
+ popstateHandler = null;
224
+ clickHandler = null;
149
225
  constructor(options = {}) {
150
226
  this.options = {
151
227
  basePath: "",
@@ -153,10 +229,24 @@ var Router = class {
153
229
  };
154
230
  }
155
231
  /**
156
- * Register a route
232
+ * Add global middleware that runs before all routes.
233
+ */
234
+ use(...middleware) {
235
+ this.globalMiddleware.push(...middleware);
236
+ }
237
+ /**
238
+ * Get router options
239
+ */
240
+ getOptions() {
241
+ return this.options;
242
+ }
243
+ /**
244
+ * Register a route. Nested children are flattened with combined paths,
245
+ * middleware, and layout chains.
157
246
  */
158
247
  register(route) {
159
248
  this.routes.push(route);
249
+ this.flattenRoute(route, "", [], void 0);
160
250
  }
161
251
  /**
162
252
  * Register multiple routes
@@ -167,11 +257,37 @@ var Router = class {
167
257
  });
168
258
  }
169
259
  /**
170
- * Match a URL to a route
260
+ * Flatten nested routes into the flat lookup table.
261
+ * Children inherit parent path prefix, middleware, and layout.
262
+ */
263
+ flattenRoute(route, parentPath, parentMiddleware, parentLayout) {
264
+ const fullPath = joinPaths(parentPath, route.path);
265
+ const combinedMiddleware = [...parentMiddleware, ...route.middleware ?? []];
266
+ const effectiveLayout = parentLayout && route.layout ? wrapLayouts(parentLayout, route.layout) : route.layout ?? parentLayout;
267
+ if (route.component) {
268
+ this.flatRoutes.push({
269
+ ...route,
270
+ path: fullPath,
271
+ middleware: combinedMiddleware.length > 0 ? combinedMiddleware : void 0,
272
+ layout: effectiveLayout,
273
+ children: void 0
274
+ // Already flattened
275
+ });
276
+ }
277
+ if (route.children) {
278
+ for (const child of route.children) {
279
+ this.flattenRoute(child, fullPath, combinedMiddleware, effectiveLayout);
280
+ }
281
+ }
282
+ }
283
+ /**
284
+ * Match a URL to a route.
285
+ * Checks flattened routes first (includes nested), then falls back to top-level.
171
286
  */
172
287
  match(url) {
173
288
  const path = this.normalizePath(url);
174
- for (const route of this.routes) {
289
+ const allRoutes = this.flatRoutes.length > 0 ? this.flatRoutes : this.routes;
290
+ for (const route of allRoutes) {
175
291
  const matcher = pathMatch(route.path, { decode: decodeURIComponent });
176
292
  const result = matcher(path);
177
293
  if (result) {
@@ -184,13 +300,35 @@ var Router = class {
184
300
  return null;
185
301
  }
186
302
  /**
187
- * Resolve a route with data loading
303
+ * Resolve a route with middleware execution and data loading.
304
+ *
305
+ * Middleware chain: global middleware → route middleware → loader.
306
+ * If any middleware returns `false`, resolution is aborted (returns null).
307
+ * If any middleware returns a string, navigation is redirected to that path.
188
308
  */
189
309
  async resolve(url) {
190
310
  const matched = this.match(url);
191
311
  if (!matched) {
192
312
  return null;
193
313
  }
314
+ const allMiddleware = [...this.globalMiddleware, ...matched.route.middleware ?? []];
315
+ if (allMiddleware.length > 0) {
316
+ const context = {
317
+ pathname: this.normalizePath(url),
318
+ params: matched.params,
319
+ meta: matched.route.meta
320
+ };
321
+ for (const mw of allMiddleware) {
322
+ const result = await mw(context);
323
+ if (result === false) {
324
+ return null;
325
+ }
326
+ if (typeof result === "string") {
327
+ this.navigate(result);
328
+ return null;
329
+ }
330
+ }
331
+ }
194
332
  if (matched.route.loader) {
195
333
  try {
196
334
  matched.data = await matched.route.loader(matched.params);
@@ -202,9 +340,7 @@ var Router = class {
202
340
  throw error;
203
341
  }
204
342
  }
205
- if (typeof window === "undefined") {
206
- this.currentMatch = matched;
207
- }
343
+ this.currentMatch = matched;
208
344
  return matched;
209
345
  }
210
346
  /**
@@ -220,6 +356,8 @@ var Router = class {
220
356
  } else {
221
357
  window.history.pushState(options.state || null, "", fullUrl);
222
358
  }
359
+ this.lastPathname = window.location.pathname;
360
+ this.currentMatch = this.match(window.location.pathname);
223
361
  this.notifyListeners();
224
362
  }
225
363
  /**
@@ -254,7 +392,12 @@ var Router = class {
254
392
  if (typeof window === "undefined") {
255
393
  return this.currentMatch;
256
394
  }
257
- return this.match(window.location.pathname);
395
+ const pathname = window.location.pathname;
396
+ if (pathname !== this.lastPathname) {
397
+ this.lastPathname = pathname;
398
+ this.currentMatch = this.match(pathname);
399
+ }
400
+ return this.currentMatch;
258
401
  }
259
402
  /**
260
403
  * Get all registered routes
@@ -263,10 +406,12 @@ var Router = class {
263
406
  return [...this.routes];
264
407
  }
265
408
  /**
266
- * Clear all routes
409
+ * Clear all routes and middleware
267
410
  */
268
411
  clear() {
269
412
  this.routes = [];
413
+ this.flatRoutes = [];
414
+ this.globalMiddleware = [];
270
415
  }
271
416
  normalizePath(url) {
272
417
  let path = url;
@@ -285,16 +430,22 @@ var Router = class {
285
430
  });
286
431
  }
287
432
  /**
288
- * Initialize client-side routing
433
+ * Initialize client-side routing.
434
+ * Uses a global flag to prevent duplicate event listeners on HMR re-invocation.
289
435
  */
290
436
  initClient() {
291
437
  if (typeof window === "undefined") {
292
438
  return;
293
439
  }
294
- window.addEventListener("popstate", () => {
440
+ if (globalThis.__revealui_router_initialized) return;
441
+ globalThis.__revealui_router_initialized = true;
442
+ this.popstateHandler = () => {
443
+ this.lastPathname = window.location.pathname;
444
+ this.currentMatch = this.match(window.location.pathname);
295
445
  this.notifyListeners();
296
- });
297
- document.addEventListener("click", (e) => {
446
+ };
447
+ window.addEventListener("popstate", this.popstateHandler);
448
+ this.clickHandler = (e) => {
298
449
  const target = e.target.closest("a");
299
450
  if (!target) return;
300
451
  const href = target.getAttribute("href");
@@ -302,9 +453,36 @@ var Router = class {
302
453
  e.preventDefault();
303
454
  this.navigate(href);
304
455
  }
305
- });
456
+ };
457
+ document.addEventListener("click", this.clickHandler);
458
+ }
459
+ /**
460
+ * Clean up client-side event listeners.
461
+ * Call this before unmounting or during HMR teardown.
462
+ */
463
+ dispose() {
464
+ if (typeof window === "undefined") return;
465
+ if (this.popstateHandler) {
466
+ window.removeEventListener("popstate", this.popstateHandler);
467
+ this.popstateHandler = null;
468
+ }
469
+ if (this.clickHandler) {
470
+ document.removeEventListener("click", this.clickHandler);
471
+ this.clickHandler = null;
472
+ }
473
+ this.listeners.clear();
474
+ globalThis.__revealui_router_initialized = false;
306
475
  }
307
476
  };
477
+ function joinPaths(parent, child) {
478
+ if (!parent || parent === "/") return child;
479
+ if (!child || child === "/") return parent;
480
+ return `${parent.replace(/\/$/, "")}/${child.replace(/^\//, "")}`;
481
+ }
482
+ function wrapLayouts(Parent, Child) {
483
+ const WrappedLayout = ({ children }) => createElement(Parent, null, createElement(Child, null, children));
484
+ return WrappedLayout;
485
+ }
308
486
  export {
309
487
  Link,
310
488
  Navigate,
@@ -312,9 +490,11 @@ export {
312
490
  RouterProvider,
313
491
  Routes,
314
492
  useData,
493
+ useLocation,
315
494
  useMatch,
316
495
  useNavigate,
317
496
  useParams,
318
- useRouter
497
+ useRouter,
498
+ useSearchParams
319
499
  };
320
500
  //# sourceMappingURL=index.js.map
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 { createContext, useContext, useEffect, useSyncExternalStore } from 'react'\nimport type { Router } from './router'\nimport type { 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\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 return <NotFound />\n }\n\n const { route, params, data } = match\n const Component = route.component\n const Layout = route.layout\n\n const element = <Component params={params} data={data} />\n\n return (\n <MatchContext.Provider value={match}>\n {Layout ? <Layout>{element}</Layout> : element}\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 = useContext(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 useContext(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() {\n const router = useRouter()\n\n return (to: string, options?: NavigateOptions) => {\n router.navigate(to, options)\n }\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 * 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 { NavigateOptions, Route, RouteMatch, RouteParams, RouterOptions } 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\nfunction compilePathPattern(pattern: string): { regex: RegExp; keys: PathKey[] } {\n const keys: PathKey[] = []\n let src = '^'\n let i = 0\n while (i < pattern.length) {\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 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 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\nfunction pathMatch(\n pattern: string,\n options: { decode?: (s: string) => string } = {},\n): (path: string) => { params: Record<string, string | string[]> } | false {\n const { regex, keys } = compilePathPattern(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 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 options: RouterOptions\n private listeners: Set<() => void> = new Set()\n private currentMatch: RouteMatch | null = null\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n basePath: '',\n ...options,\n }\n }\n\n /**\n * Register a route\n */\n register(route: Route): void {\n this.routes.push(route)\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 * Match a URL to a route\n */\n match(url: string): RouteMatch | null {\n // Remove base path if present\n const path = this.normalizePath(url)\n\n // Try to match each route\n for (const route of this.routes) {\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 data loading\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 // 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 match for SSR (if on server)\n if (typeof window === 'undefined') {\n this.currentMatch = matched\n }\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 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 // On server, return stored match (set by resolve during SSR)\n if (typeof window === 'undefined') {\n return this.currentMatch\n }\n\n // On client, match against current URL\n return this.match(window.location.pathname)\n }\n\n /**\n * Get all registered routes\n */\n getRoutes(): Route[] {\n return [...this.routes]\n }\n\n /**\n * Clear all routes\n */\n clear(): void {\n this.routes = []\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 */\n initClient(): void {\n if (typeof window === 'undefined') {\n return\n }\n\n // Handle browser back/forward buttons\n window.addEventListener('popstate', () => {\n this.notifyListeners()\n })\n\n // Intercept link clicks\n document.addEventListener('click', (e) => {\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 }\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":"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","names":[]}