@revealui/router 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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-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';
5
5
 
6
6
  /**
7
7
  * RouterProvider - Provides router instance to the app
@@ -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(() => {
@@ -92,25 +149,79 @@ function Navigate({ to, replace = false }) {
92
149
  }
93
150
 
94
151
  // src/router.ts
95
- import { match as pathMatch } from "path-to-regexp";
96
-
97
- // ../core/src/observability/logger.ts
98
- import { logger as utilsLogger } from "@revealui/utils/logger";
99
- import {
100
- createLogger,
101
- Logger,
102
- logAudit,
103
- logError,
104
- logger,
105
- logQuery
106
- } from "@revealui/utils/logger";
107
-
108
- // src/router.ts
152
+ import { logger } from "@revealui/core/observability/logger";
153
+ import { createElement } from "react";
154
+ var MAX_PATTERN_LENGTH = 2048;
155
+ function compilePathPattern(pattern) {
156
+ if (pattern.length > MAX_PATTERN_LENGTH) {
157
+ throw new Error(`Route pattern exceeds ${MAX_PATTERN_LENGTH} characters`);
158
+ }
159
+ const keys = [];
160
+ let src = "^";
161
+ let i = 0;
162
+ while (i < pattern.length) {
163
+ const ch = pattern[i];
164
+ if (ch === "{") {
165
+ src += "(?:";
166
+ i++;
167
+ } else if (ch === "}") {
168
+ src += ")?";
169
+ i++;
170
+ } else if (ch === ":") {
171
+ i++;
172
+ let name = "";
173
+ while (i < pattern.length && /\w/.test(pattern[i])) name += pattern[i++];
174
+ keys.push({ name, wildcard: false });
175
+ src += "([^/]+)";
176
+ } else if (ch === "*") {
177
+ i++;
178
+ let name = "";
179
+ while (i < pattern.length && /\w/.test(pattern[i])) name += pattern[i++];
180
+ keys.push({ name: name || "0", wildcard: true });
181
+ src += "(.+)";
182
+ } else {
183
+ src += ch.replace(/[.+?^$|()[\]\\]/g, "\\$&");
184
+ i++;
185
+ }
186
+ }
187
+ src += "$";
188
+ return { regex: new RegExp(src), keys };
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
+ }
199
+ function pathMatch(pattern, options = {}) {
200
+ const { regex, keys } = getCompiledPattern(pattern);
201
+ const decode = options.decode ?? ((s) => s);
202
+ return (path) => {
203
+ const m = regex.exec(path);
204
+ if (!m) return false;
205
+ const params = {};
206
+ for (let j = 0; j < keys.length; j++) {
207
+ const key = keys[j];
208
+ const val = m[j + 1];
209
+ if (val === void 0) continue;
210
+ params[key.name] = key.wildcard ? val.split("/").map(decode) : decode(val);
211
+ }
212
+ return { params };
213
+ };
214
+ }
109
215
  var Router = class {
110
216
  routes = [];
217
+ flatRoutes = [];
218
+ globalMiddleware = [];
111
219
  options;
112
220
  listeners = /* @__PURE__ */ new Set();
113
221
  currentMatch = null;
222
+ lastPathname = null;
223
+ popstateHandler = null;
224
+ clickHandler = null;
114
225
  constructor(options = {}) {
115
226
  this.options = {
116
227
  basePath: "",
@@ -118,10 +229,24 @@ var Router = class {
118
229
  };
119
230
  }
120
231
  /**
121
- * 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.
122
246
  */
123
247
  register(route) {
124
248
  this.routes.push(route);
249
+ this.flattenRoute(route, "", [], void 0);
125
250
  }
126
251
  /**
127
252
  * Register multiple routes
@@ -132,11 +257,37 @@ var Router = class {
132
257
  });
133
258
  }
134
259
  /**
135
- * 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.
136
286
  */
137
287
  match(url) {
138
288
  const path = this.normalizePath(url);
139
- for (const route of this.routes) {
289
+ const allRoutes = this.flatRoutes.length > 0 ? this.flatRoutes : this.routes;
290
+ for (const route of allRoutes) {
140
291
  const matcher = pathMatch(route.path, { decode: decodeURIComponent });
141
292
  const result = matcher(path);
142
293
  if (result) {
@@ -149,13 +300,35 @@ var Router = class {
149
300
  return null;
150
301
  }
151
302
  /**
152
- * 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.
153
308
  */
154
309
  async resolve(url) {
155
310
  const matched = this.match(url);
156
311
  if (!matched) {
157
312
  return null;
158
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
+ }
159
332
  if (matched.route.loader) {
160
333
  try {
161
334
  matched.data = await matched.route.loader(matched.params);
@@ -167,9 +340,7 @@ var Router = class {
167
340
  throw error;
168
341
  }
169
342
  }
170
- if (typeof window === "undefined") {
171
- this.currentMatch = matched;
172
- }
343
+ this.currentMatch = matched;
173
344
  return matched;
174
345
  }
175
346
  /**
@@ -185,6 +356,8 @@ var Router = class {
185
356
  } else {
186
357
  window.history.pushState(options.state || null, "", fullUrl);
187
358
  }
359
+ this.lastPathname = window.location.pathname;
360
+ this.currentMatch = this.match(window.location.pathname);
188
361
  this.notifyListeners();
189
362
  }
190
363
  /**
@@ -219,7 +392,12 @@ var Router = class {
219
392
  if (typeof window === "undefined") {
220
393
  return this.currentMatch;
221
394
  }
222
- 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;
223
401
  }
224
402
  /**
225
403
  * Get all registered routes
@@ -228,10 +406,12 @@ var Router = class {
228
406
  return [...this.routes];
229
407
  }
230
408
  /**
231
- * Clear all routes
409
+ * Clear all routes and middleware
232
410
  */
233
411
  clear() {
234
412
  this.routes = [];
413
+ this.flatRoutes = [];
414
+ this.globalMiddleware = [];
235
415
  }
236
416
  normalizePath(url) {
237
417
  let path = url;
@@ -250,16 +430,23 @@ var Router = class {
250
430
  });
251
431
  }
252
432
  /**
253
- * Initialize client-side routing
433
+ * Initialize client-side routing.
434
+ * Uses a global flag to prevent duplicate event listeners on HMR re-invocation.
254
435
  */
255
436
  initClient() {
256
437
  if (typeof window === "undefined") {
257
438
  return;
258
439
  }
259
- window.addEventListener("popstate", () => {
440
+ const g = globalThis;
441
+ if (g.__revealui_router_initialized) return;
442
+ g.__revealui_router_initialized = true;
443
+ this.popstateHandler = () => {
444
+ this.lastPathname = window.location.pathname;
445
+ this.currentMatch = this.match(window.location.pathname);
260
446
  this.notifyListeners();
261
- });
262
- document.addEventListener("click", (e) => {
447
+ };
448
+ window.addEventListener("popstate", this.popstateHandler);
449
+ this.clickHandler = (e) => {
263
450
  const target = e.target.closest("a");
264
451
  if (!target) return;
265
452
  const href = target.getAttribute("href");
@@ -267,9 +454,37 @@ var Router = class {
267
454
  e.preventDefault();
268
455
  this.navigate(href);
269
456
  }
270
- });
457
+ };
458
+ document.addEventListener("click", this.clickHandler);
459
+ }
460
+ /**
461
+ * Clean up client-side event listeners.
462
+ * Call this before unmounting or during HMR teardown.
463
+ */
464
+ dispose() {
465
+ if (typeof window === "undefined") return;
466
+ if (this.popstateHandler) {
467
+ window.removeEventListener("popstate", this.popstateHandler);
468
+ this.popstateHandler = null;
469
+ }
470
+ if (this.clickHandler) {
471
+ document.removeEventListener("click", this.clickHandler);
472
+ this.clickHandler = null;
473
+ }
474
+ this.listeners.clear();
475
+ const g = globalThis;
476
+ g.__revealui_router_initialized = false;
271
477
  }
272
478
  };
479
+ function joinPaths(parent, child) {
480
+ if (!parent || parent === "/") return child;
481
+ if (!child || child === "/") return parent;
482
+ return `${parent.replace(/\/$/, "")}/${child.replace(/^\//, "")}`;
483
+ }
484
+ function wrapLayouts(Parent, Child) {
485
+ const WrappedLayout = ({ children }) => createElement(Parent, null, createElement(Child, null, children));
486
+ return WrappedLayout;
487
+ }
273
488
  export {
274
489
  Link,
275
490
  Navigate,
@@ -277,9 +492,11 @@ export {
277
492
  RouterProvider,
278
493
  Routes,
279
494
  useData,
495
+ useLocation,
280
496
  useMatch,
281
497
  useNavigate,
282
498
  useParams,
283
- useRouter
499
+ useRouter,
500
+ useSearchParams
284
501
  };
285
502
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components.tsx","../src/router.ts","../../core/src/observability/logger.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 { match as pathMatch } from 'path-to-regexp'\nimport { logger } from '../../core/src/observability/logger.js'\nimport type { NavigateOptions, Route, RouteMatch, RouteParams, RouterOptions } from './types'\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","/**\n * Structured Logging Infrastructure\n *\n * Re-exports from @revealui/utils to maintain backward compatibility.\n * The actual implementation has been moved to @revealui/utils to break circular dependencies.\n */\n\n// Import logger for internal use\nimport { logger as utilsLogger } from '@revealui/utils/logger'\n\n// Re-export all types and functions from utils\nexport type {\n LogContext,\n LogEntry,\n LoggerConfig,\n LogLevel,\n} from '@revealui/utils/logger'\n\nexport {\n createLogger,\n Logger,\n logAudit,\n logError,\n logger,\n logQuery,\n} from '@revealui/utils/logger'\n\n// Additional helper functions that were in core but not in utils\n// These can stay here as they're core-specific\n\n/**\n * Request logger middleware\n */\nexport function createRequestLogger<TRequest = unknown, TResponse = unknown>(\n options: { includeBody?: boolean; includeHeaders?: boolean } = {},\n) {\n return async (\n request: TRequest & {\n method: string\n url: string\n headers?: { get?: (key: string) => string | null; entries?: () => Iterable<[string, string]> }\n },\n next: () => Promise<TResponse>,\n ): Promise<TResponse> => {\n // Import logger at runtime to avoid circular deps\n const { logger } = await import('@revealui/utils/logger')\n const requestId = crypto.randomUUID()\n const startTime = Date.now()\n\n const requestLogger = logger.child({\n requestId,\n method: request.method,\n url: request.url,\n userAgent: request.headers?.get?.('user-agent'),\n })\n\n requestLogger.info('Request started')\n\n if (options.includeHeaders) {\n requestLogger.debug('Request headers', {\n headers: Object.fromEntries(request.headers?.entries?.() || []),\n })\n }\n\n try {\n const response = await next()\n\n const duration = Date.now() - startTime\n const responseWithStatus = response as typeof response & { status?: number }\n\n requestLogger.info('Request completed', {\n status: responseWithStatus.status ?? 200,\n duration,\n })\n\n return response\n } catch (error) {\n const duration = Date.now() - startTime\n\n requestLogger.error(\n 'Request failed',\n error instanceof Error ? error : new Error(String(error)),\n { duration },\n )\n\n throw error\n }\n }\n}\n\n/**\n * Performance logger\n */\nexport function logPerformance(\n operation: string,\n duration: number,\n context?: Record<string, unknown>,\n): void {\n const level = duration > 1000 ? 'warn' : 'info'\n\n utilsLogger[level](`Performance: ${operation}`, {\n ...context,\n operation,\n duration,\n slow: duration > 1000,\n })\n}\n\n/**\n * API call logger\n */\nexport function logAPICall(\n method: string,\n url: string,\n status: number,\n duration: number,\n context?: Record<string, unknown>,\n): void {\n const apiContext = {\n ...context,\n method,\n url,\n status,\n duration,\n }\n\n if (status >= 400) {\n utilsLogger.error('API call', undefined, apiContext)\n } else if (status >= 300) {\n utilsLogger.warn('API call', apiContext)\n } else {\n utilsLogger.info('API call', apiContext)\n }\n}\n\n/**\n * Cache operation logger\n */\nexport function logCache(\n operation: 'hit' | 'miss' | 'set' | 'delete',\n key: string,\n context?: Record<string, unknown>,\n): void {\n utilsLogger.debug(`Cache ${operation}`, {\n ...context,\n operation,\n key,\n })\n}\n\n/**\n * User action logger\n */\nexport function logUserAction(\n action: string,\n userId?: string,\n context?: Record<string, unknown>,\n): void {\n utilsLogger.info('User action', {\n ...context,\n action,\n userId,\n })\n}\n\n/**\n * System event logger\n */\nexport function logSystemEvent(event: string, context?: Record<string, unknown>): void {\n utilsLogger.info('System event', {\n ...context,\n event,\n })\n}\n\n/**\n * Sanitize sensitive data from logs\n */\nexport function sanitizeLogData(data: Record<string, unknown>): Record<string, unknown> {\n const sensitiveKeys = [\n 'password',\n 'token',\n 'secret',\n 'apiKey',\n 'accessToken',\n 'refreshToken',\n 'creditCard',\n 'ssn',\n ]\n\n const sanitized: Record<string, unknown> = {}\n\n for (const [key, value] of Object.entries(data)) {\n const lowerKey = key.toLowerCase()\n\n if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive))) {\n sanitized[key] = '[REDACTED]'\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n sanitized[key] = sanitizeLogData(value as Record<string, unknown>)\n } else {\n sanitized[key] = value\n }\n }\n\n return sanitized\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\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":";AACA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAwBE,cAiML,YAjMK;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;AAKO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAQG;AACD,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;;;ACjRA,SAAS,cAAc;AAEvB,SAAS,qBAAqB;AAuB9B,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;AAGA,UAAM,IAAI;AACV,QAAI,EAAE,8BAA+B;AACrC,MAAE,gCAAgC;AAGlC,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;AAGrB,UAAM,IAAI;AACV,MAAE,gCAAgC;AAAA,EACpC;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":[]}