@buzola/router 0.0.1
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/__tests__/create-page.test.d.ts +2 -0
- package/dist/__tests__/create-page.test.d.ts.map +1 -0
- package/dist/__tests__/create-page.test.js +457 -0
- package/dist/__tests__/create-page.test.js.map +1 -0
- package/dist/__tests__/define-routes.test.d.ts +2 -0
- package/dist/__tests__/define-routes.test.d.ts.map +1 -0
- package/dist/__tests__/define-routes.test.js +42 -0
- package/dist/__tests__/define-routes.test.js.map +1 -0
- package/dist/__tests__/matcher.test.d.ts +2 -0
- package/dist/__tests__/matcher.test.d.ts.map +1 -0
- package/dist/__tests__/matcher.test.js +108 -0
- package/dist/__tests__/matcher.test.js.map +1 -0
- package/dist/__tests__/router.test.d.ts +2 -0
- package/dist/__tests__/router.test.d.ts.map +1 -0
- package/dist/__tests__/router.test.js +327 -0
- package/dist/__tests__/router.test.js.map +1 -0
- package/dist/__tests__/type-safety.test.d.ts +15 -0
- package/dist/__tests__/type-safety.test.d.ts.map +1 -0
- package/dist/__tests__/type-safety.test.js +163 -0
- package/dist/__tests__/type-safety.test.js.map +1 -0
- package/dist/__tests__/types.test.d.ts +2 -0
- package/dist/__tests__/types.test.d.ts.map +1 -0
- package/dist/__tests__/types.test.js +22 -0
- package/dist/__tests__/types.test.js.map +1 -0
- package/dist/define/create-page.d.ts +87 -0
- package/dist/define/create-page.d.ts.map +1 -0
- package/dist/define/create-page.js +305 -0
- package/dist/define/create-page.js.map +1 -0
- package/dist/define/define-routes.d.ts +34 -0
- package/dist/define/define-routes.d.ts.map +1 -0
- package/dist/define/define-routes.js +53 -0
- package/dist/define/define-routes.js.map +1 -0
- package/dist/engine/matcher.d.ts +8 -0
- package/dist/engine/matcher.d.ts.map +1 -0
- package/dist/engine/matcher.js +79 -0
- package/dist/engine/matcher.js.map +1 -0
- package/dist/engine/navigation-adapter.d.ts +13 -0
- package/dist/engine/navigation-adapter.d.ts.map +1 -0
- package/dist/engine/navigation-adapter.js +156 -0
- package/dist/engine/navigation-adapter.js.map +1 -0
- package/dist/engine/route-tree.d.ts +21 -0
- package/dist/engine/route-tree.d.ts.map +1 -0
- package/dist/engine/route-tree.js +95 -0
- package/dist/engine/route-tree.js.map +1 -0
- package/dist/engine/router.d.ts +162 -0
- package/dist/engine/router.d.ts.map +1 -0
- package/dist/engine/router.js +400 -0
- package/dist/engine/router.js.map +1 -0
- package/dist/engine/schema.d.ts +46 -0
- package/dist/engine/schema.d.ts.map +1 -0
- package/dist/engine/schema.js +110 -0
- package/dist/engine/schema.js.map +1 -0
- package/dist/engine/types.d.ts +183 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/utils.d.ts +7 -0
- package/dist/engine/utils.d.ts.map +1 -0
- package/dist/engine/utils.js +17 -0
- package/dist/engine/utils.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/react/context.d.ts +21 -0
- package/dist/react/context.d.ts.map +1 -0
- package/dist/react/context.js +7 -0
- package/dist/react/context.js.map +1 -0
- package/dist/react/error-boundary.d.ts +24 -0
- package/dist/react/error-boundary.d.ts.map +1 -0
- package/dist/react/error-boundary.js +37 -0
- package/dist/react/error-boundary.js.map +1 -0
- package/dist/react/hooks.d.ts +85 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +161 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +12 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +7 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/link.d.ts +42 -0
- package/dist/react/link.d.ts.map +1 -0
- package/dist/react/link.js +38 -0
- package/dist/react/link.js.map +1 -0
- package/dist/react/outlet.d.ts +13 -0
- package/dist/react/outlet.d.ts.map +1 -0
- package/dist/react/outlet.js +46 -0
- package/dist/react/outlet.js.map +1 -0
- package/dist/react/provider.d.ts +22 -0
- package/dist/react/provider.d.ts.map +1 -0
- package/dist/react/provider.js +37 -0
- package/dist/react/provider.js.map +1 -0
- package/package.json +29 -0
- package/src/__tests__/create-page.test.ts +588 -0
- package/src/__tests__/matcher.test.ts +127 -0
- package/src/__tests__/router.test.ts +424 -0
- package/src/__tests__/type-safety.test.tsx +207 -0
- package/src/define/create-page.tsx +472 -0
- package/src/engine/matcher.ts +88 -0
- package/src/engine/navigation-adapter.ts +191 -0
- package/src/engine/route-tree.ts +129 -0
- package/src/engine/router.ts +471 -0
- package/src/engine/schema.ts +130 -0
- package/src/engine/types.ts +203 -0
- package/src/engine/utils.ts +16 -0
- package/src/env.d.ts +120 -0
- package/src/index.ts +61 -0
- package/src/react/context.ts +24 -0
- package/src/react/error-boundary.tsx +56 -0
- package/src/react/hooks.ts +211 -0
- package/src/react/index.ts +16 -0
- package/src/react/link.tsx +93 -0
- package/src/react/outlet.tsx +67 -0
- package/src/react/provider.tsx +60 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { RouteComponent, RouteConfig, RouteGuard, RouteNode, StandardSchema } from '../engine/types';
|
|
2
|
+
export interface DefineRouteOptions {
|
|
3
|
+
/** Schema for validating search params. */
|
|
4
|
+
searchSchema?: StandardSchema;
|
|
5
|
+
/** Guard function called before entering this route. */
|
|
6
|
+
beforeEnter?: RouteGuard;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Define route-level configuration for a route file.
|
|
10
|
+
* Used in file-based routing: `export const route = defineRoute({ ... })`.
|
|
11
|
+
*/
|
|
12
|
+
export declare function defineRoute(options: DefineRouteOptions): DefineRouteOptions;
|
|
13
|
+
export interface RouteDefinitionOptions {
|
|
14
|
+
component?: RouteComponent;
|
|
15
|
+
searchSchema?: StandardSchema;
|
|
16
|
+
beforeEnter?: RouteGuard;
|
|
17
|
+
}
|
|
18
|
+
export type RouteBuilderFn = (route: (path: string, options: RouteDefinitionOptions | RouteComponent, children?: RouteConfig[]) => RouteConfig) => RouteConfig[];
|
|
19
|
+
/**
|
|
20
|
+
* Declarative route definition builder.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const routeTree = defineRoutes(route => [
|
|
25
|
+
* route('/', { component: Home }),
|
|
26
|
+
* route('/users', { component: UsersLayout }, [
|
|
27
|
+
* route('/', { component: UsersList }),
|
|
28
|
+
* route('/:userId', { component: UserDetail }),
|
|
29
|
+
* ]),
|
|
30
|
+
* ]);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function defineRoutes(builder: RouteBuilderFn): RouteNode[];
|
|
34
|
+
//# sourceMappingURL=define-routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-routes.d.ts","sourceRoot":"","sources":["../../src/define/define-routes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIzG,MAAM,WAAW,kBAAkB;IAClC,2CAA2C;IAC3C,YAAY,CAAC,EAAE,cAAc,CAAA;IAC7B,wDAAwD;IACxD,WAAW,CAAC,EAAE,UAAU,CAAA;CACxB;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,CAE3E;AAaD,MAAM,WAAW,sBAAsB;IACtC,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAA;IAC7B,WAAW,CAAC,EAAE,UAAU,CAAA;CACxB;AAED,MAAM,MAAM,cAAc,GAAG,CAC5B,KAAK,EAAE,CACN,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,GAAG,cAAc,EAChD,QAAQ,CAAC,EAAE,WAAW,EAAE,KACpB,WAAW,KACZ,WAAW,EAAE,CAAA;AAElB;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,EAAE,CA2BjE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { buildRouteTree } from '../engine/route-tree';
|
|
2
|
+
/**
|
|
3
|
+
* Define route-level configuration for a route file.
|
|
4
|
+
* Used in file-based routing: `export const route = defineRoute({ ... })`.
|
|
5
|
+
*/
|
|
6
|
+
export function defineRoute(options) {
|
|
7
|
+
return options;
|
|
8
|
+
}
|
|
9
|
+
function isRouteComponent(value) {
|
|
10
|
+
if (typeof value === 'function')
|
|
11
|
+
return true;
|
|
12
|
+
// Check for known option keys to distinguish from component objects (e.g. React.lazy)
|
|
13
|
+
if (typeof value === 'object' && value !== null) {
|
|
14
|
+
return !('component' in value || 'searchSchema' in value || 'beforeEnter' in value);
|
|
15
|
+
}
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Declarative route definition builder.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const routeTree = defineRoutes(route => [
|
|
24
|
+
* route('/', { component: Home }),
|
|
25
|
+
* route('/users', { component: UsersLayout }, [
|
|
26
|
+
* route('/', { component: UsersList }),
|
|
27
|
+
* route('/:userId', { component: UserDetail }),
|
|
28
|
+
* ]),
|
|
29
|
+
* ]);
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function defineRoutes(builder) {
|
|
33
|
+
function route(path, options, children) {
|
|
34
|
+
// If options is a component directly (function or lazy), wrap it
|
|
35
|
+
const opts = isRouteComponent(options)
|
|
36
|
+
? { component: options }
|
|
37
|
+
: options;
|
|
38
|
+
const isIndex = path === '/' || path === '';
|
|
39
|
+
const hasChildren = children != null && children.length > 0;
|
|
40
|
+
return {
|
|
41
|
+
path,
|
|
42
|
+
component: opts.component,
|
|
43
|
+
searchSchema: opts.searchSchema,
|
|
44
|
+
beforeEnter: opts.beforeEnter,
|
|
45
|
+
children,
|
|
46
|
+
isIndex: isIndex && !hasChildren,
|
|
47
|
+
isLayout: hasChildren,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const configs = builder(route);
|
|
51
|
+
return buildRouteTree(configs);
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=define-routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-routes.js","sourceRoot":"","sources":["../../src/define/define-routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAYrD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,OAA2B;IACtD,OAAO,OAAO,CAAA;AACf,CAAC;AAED,SAAS,gBAAgB,CAAC,KAA8C;IACvE,IAAI,OAAO,KAAK,KAAK,UAAU;QAAE,OAAO,IAAI,CAAA;IAC5C,sFAAsF;IACtF,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACjD,OAAO,CAAC,CAAC,WAAW,IAAI,KAAK,IAAI,cAAc,IAAI,KAAK,IAAI,aAAa,IAAI,KAAK,CAAC,CAAA;IACpF,CAAC;IACD,OAAO,KAAK,CAAA;AACb,CAAC;AAkBD;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,YAAY,CAAC,OAAuB;IACnD,SAAS,KAAK,CACb,IAAY,EACZ,OAAgD,EAChD,QAAwB;QAExB,iEAAiE;QACjE,MAAM,IAAI,GAA2B,gBAAgB,CAAC,OAAO,CAAC;YAC7D,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,EAAE;YACxB,CAAC,CAAC,OAAO,CAAA;QAEV,MAAM,OAAO,GAAG,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,EAAE,CAAA;QAC3C,MAAM,WAAW,GAAG,QAAQ,IAAI,IAAI,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAA;QAE3D,OAAO;YACN,IAAI;YACJ,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,QAAQ;YACR,OAAO,EAAE,OAAO,IAAI,CAAC,WAAW;YAChC,QAAQ,EAAE,WAAW;SACrB,CAAA;IACF,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAC9B,OAAO,cAAc,CAAC,OAAO,CAAC,CAAA;AAC/B,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RouteMatch, RouteNode } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Match a URL against the route tree.
|
|
4
|
+
* Returns an array of RouteMatch from root layout to leaf page, or null if no match.
|
|
5
|
+
* Performs depth-first matching.
|
|
6
|
+
*/
|
|
7
|
+
export declare function matchRoutes(nodes: RouteNode[], url: URL): RouteMatch[] | null;
|
|
8
|
+
//# sourceMappingURL=matcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.d.ts","sourceRoot":"","sources":["../../src/engine/matcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEpD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,GAAG,EAAE,GAAG,GAAG,UAAU,EAAE,GAAG,IAAI,CAS7E"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Match a URL against the route tree.
|
|
3
|
+
* Returns an array of RouteMatch from root layout to leaf page, or null if no match.
|
|
4
|
+
* Performs depth-first matching.
|
|
5
|
+
*/
|
|
6
|
+
export function matchRoutes(nodes, url) {
|
|
7
|
+
const pathname = url.pathname;
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
const result = matchNode(node, pathname);
|
|
10
|
+
if (result)
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
function matchNode(node, pathname) {
|
|
16
|
+
// Layout nodes: try to match children, prepend self if any child matches
|
|
17
|
+
if (node.isLayout && !node.isIndex) {
|
|
18
|
+
for (const child of node.children) {
|
|
19
|
+
const childMatch = matchNode(child, pathname);
|
|
20
|
+
if (childMatch) {
|
|
21
|
+
// Layout is part of the match chain
|
|
22
|
+
const layoutMatch = {
|
|
23
|
+
node,
|
|
24
|
+
params: extractLayoutParams(node, pathname),
|
|
25
|
+
pathname,
|
|
26
|
+
};
|
|
27
|
+
return [layoutMatch, ...childMatch];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
// Leaf/index nodes: try to match the URLPattern
|
|
33
|
+
if (node.pattern) {
|
|
34
|
+
const result = node.pattern.exec({ pathname });
|
|
35
|
+
if (result) {
|
|
36
|
+
const params = extractParamsFromResult(result);
|
|
37
|
+
const match = { node, params, pathname };
|
|
38
|
+
// If this node has children (e.g., a page with nested routes), try them too
|
|
39
|
+
if (node.children.length > 0) {
|
|
40
|
+
for (const child of node.children) {
|
|
41
|
+
const childMatch = matchNode(child, pathname);
|
|
42
|
+
if (childMatch) {
|
|
43
|
+
return [match, ...childMatch];
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [match];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Extract params from a URLPattern exec result.
|
|
54
|
+
*/
|
|
55
|
+
function extractParamsFromResult(result) {
|
|
56
|
+
const params = {};
|
|
57
|
+
const groups = result.pathname.groups;
|
|
58
|
+
for (const [key, value] of Object.entries(groups)) {
|
|
59
|
+
// Skip unnamed groups (numeric keys from wildcards/optionals)
|
|
60
|
+
if (value !== undefined && !/^\d+$/.test(key)) {
|
|
61
|
+
params[key] = value;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return params;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extract params from a layout node using its prefix URLPattern.
|
|
68
|
+
* Falls back to empty params if no prefix pattern is available.
|
|
69
|
+
*/
|
|
70
|
+
function extractLayoutParams(node, pathname) {
|
|
71
|
+
if (node.prefixPattern) {
|
|
72
|
+
const result = node.prefixPattern.exec({ pathname });
|
|
73
|
+
if (result) {
|
|
74
|
+
return extractParamsFromResult(result);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=matcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matcher.js","sourceRoot":"","sources":["../../src/engine/matcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { NavigationAdapter } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Create a NavigationAdapter that delegates to the browser's Navigation API.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createBrowserNavigationAdapter(): NavigationAdapter;
|
|
6
|
+
export interface MemoryNavigationAdapterOptions {
|
|
7
|
+
initialURL?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Create an in-memory NavigationAdapter for testing.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createMemoryNavigationAdapter(options?: MemoryNavigationAdapterOptions): NavigationAdapter;
|
|
13
|
+
//# sourceMappingURL=navigation-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-adapter.d.ts","sourceRoot":"","sources":["../../src/engine/navigation-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAwC,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAItF;;GAEG;AACH,wBAAgB,8BAA8B,IAAI,iBAAiB,CAwElE;AAID,MAAM,WAAW,8BAA8B;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAA;CACnB;AAOD;;GAEG;AACH,wBAAgB,6BAA6B,CAC5C,OAAO,GAAE,8BAAmC,GAC1C,iBAAiB,CA6FnB"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
// ─── Browser adapter ────────────────────────────────────────────────────────
|
|
2
|
+
/**
|
|
3
|
+
* Create a NavigationAdapter that delegates to the browser's Navigation API.
|
|
4
|
+
*/
|
|
5
|
+
export function createBrowserNavigationAdapter() {
|
|
6
|
+
const nav = window.navigation;
|
|
7
|
+
if (!nav) {
|
|
8
|
+
throw new Error('Navigation API is not available. Buzola requires a browser that supports the Navigation API.');
|
|
9
|
+
}
|
|
10
|
+
const listeners = new Set();
|
|
11
|
+
let pendingViewTransition = false;
|
|
12
|
+
const nativeHandler = (event) => {
|
|
13
|
+
const viewTransition = pendingViewTransition;
|
|
14
|
+
pendingViewTransition = false;
|
|
15
|
+
const buzolaEvent = {
|
|
16
|
+
destination: { url: event.destination.url },
|
|
17
|
+
canIntercept: event.canIntercept,
|
|
18
|
+
navigationType: event.navigationType,
|
|
19
|
+
userInitiated: event.userInitiated,
|
|
20
|
+
viewTransition,
|
|
21
|
+
intercept(options) {
|
|
22
|
+
event.intercept({
|
|
23
|
+
handler: options.handler,
|
|
24
|
+
scroll: options.scroll,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
for (const listener of listeners) {
|
|
29
|
+
listener(buzolaEvent);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
return {
|
|
33
|
+
getCurrentURL() {
|
|
34
|
+
return new URL(nav.currentEntry.url);
|
|
35
|
+
},
|
|
36
|
+
navigate(url, options) {
|
|
37
|
+
if (options?.viewTransition) {
|
|
38
|
+
pendingViewTransition = true;
|
|
39
|
+
}
|
|
40
|
+
nav.navigate(url, {
|
|
41
|
+
history: options?.replace ? 'replace' : 'push',
|
|
42
|
+
state: options?.state,
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
back() {
|
|
46
|
+
nav.back();
|
|
47
|
+
},
|
|
48
|
+
forward() {
|
|
49
|
+
nav.forward();
|
|
50
|
+
},
|
|
51
|
+
addEventListener(_type, handler) {
|
|
52
|
+
// Lazily register the native handler when the first listener is added
|
|
53
|
+
const wasEmpty = listeners.size === 0;
|
|
54
|
+
listeners.add(handler);
|
|
55
|
+
if (wasEmpty) {
|
|
56
|
+
nav.addEventListener('navigate', nativeHandler);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
removeEventListener(_type, handler) {
|
|
60
|
+
listeners.delete(handler);
|
|
61
|
+
// Clean up the native handler when all listeners are removed
|
|
62
|
+
if (listeners.size === 0) {
|
|
63
|
+
nav.removeEventListener('navigate', nativeHandler);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
getState() {
|
|
67
|
+
return nav.currentEntry?.getState?.();
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create an in-memory NavigationAdapter for testing.
|
|
73
|
+
*/
|
|
74
|
+
export function createMemoryNavigationAdapter(options = {}) {
|
|
75
|
+
const { initialURL = 'http://localhost/' } = options;
|
|
76
|
+
const entries = [{ url: initialURL }];
|
|
77
|
+
let currentIndex = 0;
|
|
78
|
+
const listeners = new Set();
|
|
79
|
+
function emitNavigateEvent(url, navigationType, commitNavigation, viewTransition) {
|
|
80
|
+
let intercepted = false;
|
|
81
|
+
let interceptHandler;
|
|
82
|
+
const event = {
|
|
83
|
+
destination: { url },
|
|
84
|
+
canIntercept: true,
|
|
85
|
+
navigationType,
|
|
86
|
+
userInitiated: false,
|
|
87
|
+
viewTransition,
|
|
88
|
+
intercept(options) {
|
|
89
|
+
intercepted = true;
|
|
90
|
+
interceptHandler = options.handler;
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
for (const listener of listeners) {
|
|
94
|
+
listener(event);
|
|
95
|
+
}
|
|
96
|
+
if (intercepted && interceptHandler) {
|
|
97
|
+
// Run the handler and only commit navigation state on success.
|
|
98
|
+
void interceptHandler().then(() => {
|
|
99
|
+
commitNavigation();
|
|
100
|
+
}, (error) => {
|
|
101
|
+
// Guard aborts are expected — rethrow unexpected errors
|
|
102
|
+
if (error instanceof Error && error.name === 'NavigationAbortedError')
|
|
103
|
+
return;
|
|
104
|
+
throw error;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// No interception — commit immediately
|
|
109
|
+
commitNavigation();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
getCurrentURL() {
|
|
114
|
+
return new URL(entries[currentIndex].url);
|
|
115
|
+
},
|
|
116
|
+
navigate(url, options) {
|
|
117
|
+
const resolved = new URL(url, entries[currentIndex].url).href;
|
|
118
|
+
const type = options?.replace ? 'replace' : 'push';
|
|
119
|
+
emitNavigateEvent(resolved, type, () => {
|
|
120
|
+
if (options?.replace) {
|
|
121
|
+
entries[currentIndex] = { url: resolved, state: options.state };
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Remove any forward entries
|
|
125
|
+
entries.length = currentIndex + 1;
|
|
126
|
+
entries.push({ url: resolved, state: options?.state });
|
|
127
|
+
currentIndex++;
|
|
128
|
+
}
|
|
129
|
+
}, options?.viewTransition);
|
|
130
|
+
},
|
|
131
|
+
back() {
|
|
132
|
+
if (currentIndex > 0) {
|
|
133
|
+
emitNavigateEvent(entries[currentIndex - 1].url, 'traverse', () => {
|
|
134
|
+
currentIndex--;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
forward() {
|
|
139
|
+
if (currentIndex < entries.length - 1) {
|
|
140
|
+
emitNavigateEvent(entries[currentIndex + 1].url, 'traverse', () => {
|
|
141
|
+
currentIndex++;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
addEventListener(_type, handler) {
|
|
146
|
+
listeners.add(handler);
|
|
147
|
+
},
|
|
148
|
+
removeEventListener(_type, handler) {
|
|
149
|
+
listeners.delete(handler);
|
|
150
|
+
},
|
|
151
|
+
getState() {
|
|
152
|
+
return entries[currentIndex].state;
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=navigation-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"navigation-adapter.js","sourceRoot":"","sources":["../../src/engine/navigation-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RouteComponent, RouteConfig, RouteGuard, RouteNode, StandardSchema } from './types';
|
|
2
|
+
export interface CreateRouteNodeOptions {
|
|
3
|
+
path: string;
|
|
4
|
+
fullPath: string;
|
|
5
|
+
component?: RouteComponent;
|
|
6
|
+
searchSchema?: StandardSchema;
|
|
7
|
+
beforeEnter?: RouteGuard;
|
|
8
|
+
children?: RouteNode[];
|
|
9
|
+
parent?: RouteNode;
|
|
10
|
+
isLayout?: boolean;
|
|
11
|
+
isIndex?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a route node with a compiled URLPattern.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createRouteNode(options: CreateRouteNodeOptions): RouteNode;
|
|
17
|
+
/**
|
|
18
|
+
* Build a route tree from an array of RouteConfig.
|
|
19
|
+
*/
|
|
20
|
+
export declare function buildRouteTree(configs: RouteConfig[], parentPath?: string): RouteNode[];
|
|
21
|
+
//# sourceMappingURL=route-tree.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-tree.d.ts","sourceRoot":"","sources":["../../src/engine/route-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AAqBjG,MAAM,WAAW,sBAAsB;IACtC,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,YAAY,CAAC,EAAE,cAAc,CAAA;IAC7B,WAAW,CAAC,EAAE,UAAU,CAAA;IACxB,QAAQ,CAAC,EAAE,SAAS,EAAE,CAAA;IACtB,MAAM,CAAC,EAAE,SAAS,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,SAAS,CAgD1E;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,UAAU,SAAK,GAAG,SAAS,EAAE,CAEnF"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalize a path segment — ensure it starts with "/" and strip trailing slashes.
|
|
3
|
+
*/
|
|
4
|
+
function normalizePath(path) {
|
|
5
|
+
if (path === '' || path === '/')
|
|
6
|
+
return '/';
|
|
7
|
+
const p = path.startsWith('/') ? path : `/${path}`;
|
|
8
|
+
return p.endsWith('/') ? p.slice(0, -1) : p;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Join two path segments.
|
|
12
|
+
*/
|
|
13
|
+
function joinPaths(parent, child) {
|
|
14
|
+
if (child === '/')
|
|
15
|
+
return parent === '/' ? '/' : parent;
|
|
16
|
+
const base = parent === '/' ? '' : parent;
|
|
17
|
+
const segment = child.startsWith('/') ? child : `/${child}`;
|
|
18
|
+
return base + segment;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Create a route node with a compiled URLPattern.
|
|
22
|
+
*/
|
|
23
|
+
export function createRouteNode(options) {
|
|
24
|
+
const { path, fullPath, component, searchSchema, beforeEnter, children = [], parent, isLayout = false, isIndex = false, } = options;
|
|
25
|
+
// Build the pattern — layouts get a prefix pattern for param extraction,
|
|
26
|
+
// index and leaf routes get an exact pattern for matching.
|
|
27
|
+
let pattern;
|
|
28
|
+
let prefixPattern;
|
|
29
|
+
if (!isLayout || isIndex) {
|
|
30
|
+
pattern = new URLPattern({ pathname: fullPath === '' ? '/' : fullPath });
|
|
31
|
+
}
|
|
32
|
+
if (isLayout && !isIndex) {
|
|
33
|
+
// Layouts get a prefix pattern that matches any path starting with their fullPath.
|
|
34
|
+
// The {/.*}? suffix optionally matches a trailing slash and anything after.
|
|
35
|
+
const prefix = fullPath === '' || fullPath === '/' ? '/' : fullPath;
|
|
36
|
+
prefixPattern = new URLPattern({ pathname: prefix === '/' ? '/{.*}?' : `${prefix}{/.*}?` });
|
|
37
|
+
}
|
|
38
|
+
const node = {
|
|
39
|
+
id: `route:${fullPath || '/'}${isLayout ? ':layout' : ''}`,
|
|
40
|
+
path: normalizePath(path),
|
|
41
|
+
fullPath: normalizePath(fullPath),
|
|
42
|
+
pattern,
|
|
43
|
+
prefixPattern,
|
|
44
|
+
component,
|
|
45
|
+
searchSchema,
|
|
46
|
+
beforeEnter,
|
|
47
|
+
children,
|
|
48
|
+
parent,
|
|
49
|
+
isLayout,
|
|
50
|
+
isIndex,
|
|
51
|
+
};
|
|
52
|
+
// Set parent reference on children
|
|
53
|
+
for (const child of node.children) {
|
|
54
|
+
child.parent = node;
|
|
55
|
+
}
|
|
56
|
+
return node;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a route tree from an array of RouteConfig.
|
|
60
|
+
*/
|
|
61
|
+
export function buildRouteTree(configs, parentPath = '') {
|
|
62
|
+
return buildRouteNodes(configs, parentPath);
|
|
63
|
+
}
|
|
64
|
+
function buildRouteNodes(configs, parentPath) {
|
|
65
|
+
const nodes = [];
|
|
66
|
+
for (const config of configs) {
|
|
67
|
+
const segment = config.isPathlessGroup ? '' : config.path;
|
|
68
|
+
const hasChildren = config.children != null && config.children.length > 0;
|
|
69
|
+
const isLayout = config.isLayout ?? hasChildren;
|
|
70
|
+
const isIndex = config.isIndex ?? (!isLayout && (config.path === '/' || config.path === ''));
|
|
71
|
+
let fullPath;
|
|
72
|
+
if (isIndex) {
|
|
73
|
+
fullPath = parentPath || '/';
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
fullPath = joinPaths(parentPath || '/', segment);
|
|
77
|
+
}
|
|
78
|
+
const children = config.children
|
|
79
|
+
? buildRouteNodes(config.children, config.isPathlessGroup ? parentPath : fullPath)
|
|
80
|
+
: [];
|
|
81
|
+
const node = createRouteNode({
|
|
82
|
+
path: segment,
|
|
83
|
+
fullPath,
|
|
84
|
+
component: config.component,
|
|
85
|
+
searchSchema: config.searchSchema,
|
|
86
|
+
beforeEnter: config.beforeEnter,
|
|
87
|
+
children,
|
|
88
|
+
isLayout,
|
|
89
|
+
isIndex,
|
|
90
|
+
});
|
|
91
|
+
nodes.push(node);
|
|
92
|
+
}
|
|
93
|
+
return nodes;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=route-tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-tree.js","sourceRoot":"","sources":["../../src/engine/route-tree.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type { BlockerFn, NavigateOptions, NavigationAdapter, RouteNode, RouterState } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a route guard prevents navigation.
|
|
4
|
+
*/
|
|
5
|
+
export declare class NavigationAbortedError extends Error {
|
|
6
|
+
constructor();
|
|
7
|
+
}
|
|
8
|
+
export type RouterSubscriber = (state: RouterState) => void;
|
|
9
|
+
export interface RouterOptions {
|
|
10
|
+
/** Route tree nodes. */
|
|
11
|
+
routes: RouteNode[];
|
|
12
|
+
/** Navigation adapter (browser or memory). */
|
|
13
|
+
adapter: NavigationAdapter;
|
|
14
|
+
/** Enable View Transitions API for navigations. */
|
|
15
|
+
viewTransitions?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Base path prefix for all routes (e.g., "/app").
|
|
18
|
+
* Routes are defined without the base path; the router prepends it
|
|
19
|
+
* for navigation and strips it for matching.
|
|
20
|
+
*/
|
|
21
|
+
basePath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Callback that returns fallback values for persistent parameters.
|
|
24
|
+
* Used when a persistent param is not explicitly provided and not present in the current URL.
|
|
25
|
+
*/
|
|
26
|
+
persistentParams?: () => Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* Page registry mapping page IDs to route patterns.
|
|
29
|
+
* Generated by the Vite plugin.
|
|
30
|
+
*/
|
|
31
|
+
pageRegistry?: Record<string, string>;
|
|
32
|
+
/**
|
|
33
|
+
* Number of previously visited URLs to keep cached per page component.
|
|
34
|
+
* When navigating back to a cached URL, stale data is shown immediately
|
|
35
|
+
* while a background reload runs (SWR).
|
|
36
|
+
* Set to 0 to always re-fetch on URL change (default).
|
|
37
|
+
*/
|
|
38
|
+
loaderCacheSize?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Central Router orchestrator.
|
|
42
|
+
* Intercepts all navigations via the Navigation API and manages route matching.
|
|
43
|
+
*/
|
|
44
|
+
export declare class Router {
|
|
45
|
+
private routes;
|
|
46
|
+
private adapter;
|
|
47
|
+
private viewTransitions;
|
|
48
|
+
private readonly _basePath;
|
|
49
|
+
private readonly persistentParamsFn?;
|
|
50
|
+
private readonly pageRegistry;
|
|
51
|
+
private subscribers;
|
|
52
|
+
private invalidationListeners;
|
|
53
|
+
private state;
|
|
54
|
+
private navigationId;
|
|
55
|
+
private currentAbortController?;
|
|
56
|
+
private stopFn;
|
|
57
|
+
private blockers;
|
|
58
|
+
private pendingScrollBehavior;
|
|
59
|
+
private readonly _loaderCacheSize;
|
|
60
|
+
constructor(options: RouterOptions);
|
|
61
|
+
/**
|
|
62
|
+
* The configured base path (e.g., "/app"). Empty string if none.
|
|
63
|
+
*/
|
|
64
|
+
get basePath(): string;
|
|
65
|
+
/**
|
|
66
|
+
* Number of previously visited URLs to keep cached per page component.
|
|
67
|
+
*/
|
|
68
|
+
get loaderCacheSize(): number;
|
|
69
|
+
/**
|
|
70
|
+
* Get the current router state.
|
|
71
|
+
*/
|
|
72
|
+
getState(): RouterState;
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to state changes. Returns an unsubscribe function.
|
|
75
|
+
*/
|
|
76
|
+
subscribe(subscriber: RouterSubscriber): () => void;
|
|
77
|
+
/**
|
|
78
|
+
* Start intercepting navigations.
|
|
79
|
+
* Call this once when the router is initialized.
|
|
80
|
+
*/
|
|
81
|
+
start(): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* Navigate programmatically.
|
|
84
|
+
* The `to` path should not include the base path — it is prepended automatically.
|
|
85
|
+
*/
|
|
86
|
+
navigate(to: string, options?: NavigateOptions): void;
|
|
87
|
+
/**
|
|
88
|
+
* Navigate to a page by its page ID.
|
|
89
|
+
* Looks up the route pattern from the page registry and builds the URL.
|
|
90
|
+
*/
|
|
91
|
+
navigateToPage(pageId: string, params?: Record<string, string>, options?: NavigateOptions): void;
|
|
92
|
+
/**
|
|
93
|
+
* Build a URL path for a page ID with the given params.
|
|
94
|
+
* Path params are substituted into the route pattern; extra params become query string.
|
|
95
|
+
*/
|
|
96
|
+
buildPagePath(pageId: string, params?: Record<string, string>): string;
|
|
97
|
+
/**
|
|
98
|
+
* Go back in history.
|
|
99
|
+
*/
|
|
100
|
+
back(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Go forward in history.
|
|
103
|
+
*/
|
|
104
|
+
forward(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Build a URL from a path pattern and params.
|
|
107
|
+
* The returned path includes the base path prefix.
|
|
108
|
+
*/
|
|
109
|
+
buildPath(pattern: string, params?: Record<string, string>): string;
|
|
110
|
+
/**
|
|
111
|
+
* Strip the base path prefix from a pathname.
|
|
112
|
+
* Returns the pathname relative to the base path.
|
|
113
|
+
*/
|
|
114
|
+
stripBasePath(pathname: string): string;
|
|
115
|
+
/**
|
|
116
|
+
* Resolve params for a route pattern by merging:
|
|
117
|
+
* 1. Explicitly provided params (highest priority)
|
|
118
|
+
* 2. Current URL match params
|
|
119
|
+
* 3. persistentParams() callback (fallback)
|
|
120
|
+
*
|
|
121
|
+
* Only fills params that are present in the target pattern.
|
|
122
|
+
*/
|
|
123
|
+
resolveParams(pattern: string, explicitParams?: Record<string, string>): Record<string, string>;
|
|
124
|
+
/**
|
|
125
|
+
* Extract parameter names from a route pattern.
|
|
126
|
+
*/
|
|
127
|
+
private extractParamNames;
|
|
128
|
+
/**
|
|
129
|
+
* Get merged params from all current route matches.
|
|
130
|
+
*/
|
|
131
|
+
private getCurrentParams;
|
|
132
|
+
/**
|
|
133
|
+
* Trigger all registered invalidation listeners.
|
|
134
|
+
* Used to re-run page loaders in the background.
|
|
135
|
+
*/
|
|
136
|
+
invalidateAll(): void;
|
|
137
|
+
/**
|
|
138
|
+
* Register an invalidation listener. Returns an unsubscribe function.
|
|
139
|
+
*/
|
|
140
|
+
onInvalidate(listener: () => void): () => void;
|
|
141
|
+
/**
|
|
142
|
+
* Register a navigation blocker.
|
|
143
|
+
* The blocker function is called before each navigation.
|
|
144
|
+
* It must return a Promise that resolves to `true` to allow or `false` to block.
|
|
145
|
+
* Returns an unsubscribe function.
|
|
146
|
+
*/
|
|
147
|
+
addBlocker(fn: BlockerFn): () => void;
|
|
148
|
+
/**
|
|
149
|
+
* Dispose the router — clear all subscribers and reset state.
|
|
150
|
+
*/
|
|
151
|
+
dispose(): void;
|
|
152
|
+
/**
|
|
153
|
+
* Update the route tree (for HMR).
|
|
154
|
+
*/
|
|
155
|
+
updateRoutes(routes: RouteNode[]): void;
|
|
156
|
+
/**
|
|
157
|
+
* Create a URL with basePath stripped for route matching.
|
|
158
|
+
*/
|
|
159
|
+
private createMatchUrl;
|
|
160
|
+
private setState;
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=router.d.ts.map
|