@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 +18 -9
- package/dist/index.js +203 -23
- package/dist/index.js.map +1 -1
- package/dist/router-SBtAXNTB.d.ts +190 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +189 -47
- package/dist/server.js.map +1 -1
- package/package.json +16 -9
- package/dist/router-DctgwX83.d.ts +0 -126
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-
|
|
4
|
-
export { b as Route, c as RouteMeta, d as
|
|
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
|
-
|
|
28
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
34
|
+
const RouteComponent = route.component;
|
|
25
35
|
const Layout = route.layout;
|
|
26
|
-
const element = /* @__PURE__ */ jsx(
|
|
27
|
-
|
|
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 =
|
|
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
|
|
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 } =
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":[]}
|