@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 +12 -3
- package/dist/index.js +253 -36
- package/dist/index.js.map +1 -1
- package/dist/router-B2MrlNC3.d.ts +187 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +239 -60
- package/dist/server.js.map +1 -1
- package/package.json +13 -7
- 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-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 {
|
|
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(() => {
|
|
@@ -92,25 +149,79 @@ function Navigate({ to, replace = false }) {
|
|
|
92
149
|
}
|
|
93
150
|
|
|
94
151
|
// src/router.ts
|
|
95
|
-
import {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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":"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","names":[]}
|