@esmx/router 3.0.0-rc.50 → 3.0.0-rc.52
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/matcher.mjs +4 -1
- package/dist/options.mjs +1 -13
- package/dist/route-transition.mjs +3 -3
- package/dist/route.d.ts +2 -1
- package/dist/route.mjs +23 -5
- package/dist/types.d.ts +2 -2
- package/dist/util.d.ts +1 -0
- package/dist/util.mjs +14 -0
- package/package.json +3 -5
- package/src/matcher.ts +8 -1
- package/src/options.ts +1 -17
- package/src/route-transition.ts +1 -1
- package/src/route.ts +33 -7
- package/src/types.ts +6 -2
- package/src/util.ts +19 -0
package/dist/matcher.mjs
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { compile, match } from "path-to-regexp";
|
|
2
2
|
export function createMatcher(routes) {
|
|
3
3
|
const compiledRoutes = createRouteMatches(routes);
|
|
4
|
-
return (toURL, baseURL) => {
|
|
4
|
+
return (toURL, baseURL, cb) => {
|
|
5
5
|
const matchPath = toURL.pathname.substring(baseURL.pathname.length - 1);
|
|
6
6
|
const matches = [];
|
|
7
7
|
const params = {};
|
|
8
8
|
const collectMatchingRoutes = (routes2) => {
|
|
9
9
|
for (const item of routes2) {
|
|
10
|
+
if (cb && !cb(item)) {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
10
13
|
if (item.children.length && collectMatchingRoutes(item.children)) {
|
|
11
14
|
matches.unshift(item);
|
|
12
15
|
return true;
|
package/dist/options.mjs
CHANGED
|
@@ -29,22 +29,10 @@ function getBaseUrl(options) {
|
|
|
29
29
|
base.search = base.hash = "";
|
|
30
30
|
return base;
|
|
31
31
|
}
|
|
32
|
-
function filterRoutesByLayer(routes, layerMode) {
|
|
33
|
-
return routes.filter((route) => {
|
|
34
|
-
if (layerMode && route.layer !== true) return false;
|
|
35
|
-
if (!layerMode && route.layer === true) return false;
|
|
36
|
-
if (route.children)
|
|
37
|
-
route.children = filterRoutesByLayer(route.children, layerMode);
|
|
38
|
-
return true;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
32
|
export function parsedOptions(options = {}) {
|
|
42
33
|
var _a, _b, _c, _d, _e, _f;
|
|
43
34
|
const base = getBaseUrl(options);
|
|
44
|
-
const routes =
|
|
45
|
-
(_a = options.routes) != null ? _a : [],
|
|
46
|
-
options.layer || false
|
|
47
|
-
);
|
|
35
|
+
const routes = (_a = options.routes) != null ? _a : [];
|
|
48
36
|
return Object.freeze({
|
|
49
37
|
rootStyle: options.rootStyle || false,
|
|
50
38
|
root: options.root || "",
|
|
@@ -270,14 +270,14 @@ export class RouteTransition {
|
|
|
270
270
|
this._controller = null;
|
|
271
271
|
}
|
|
272
272
|
async to(toType, toInput) {
|
|
273
|
-
var _a
|
|
274
|
-
const from =
|
|
273
|
+
var _a;
|
|
274
|
+
const from = this.route;
|
|
275
275
|
const to = await this._runTask(
|
|
276
276
|
new Route({
|
|
277
277
|
options: this.router.parsedOptions,
|
|
278
278
|
toType,
|
|
279
279
|
toInput,
|
|
280
|
-
from: (
|
|
280
|
+
from: (_a = from == null ? void 0 : from.url) != null ? _a : null
|
|
281
281
|
}),
|
|
282
282
|
from
|
|
283
283
|
);
|
package/dist/route.d.ts
CHANGED
|
@@ -36,7 +36,8 @@ export declare class Route {
|
|
|
36
36
|
readonly path: string;
|
|
37
37
|
readonly fullPath: string;
|
|
38
38
|
readonly hash: string;
|
|
39
|
-
readonly params: Record<string, string>;
|
|
39
|
+
readonly params: Record<string, string | undefined>;
|
|
40
|
+
readonly paramsArray: Record<string, string[] | undefined>;
|
|
40
41
|
readonly query: Record<string, string | undefined>;
|
|
41
42
|
readonly queryArray: Record<string, string[] | undefined>;
|
|
42
43
|
readonly meta: RouteMeta;
|
package/dist/route.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { parsedOptions } from "./options.mjs";
|
|
|
6
6
|
import {
|
|
7
7
|
RouteType
|
|
8
8
|
} from "./types.mjs";
|
|
9
|
-
import { isNonEmptyPlainObject, isPlainObject } from "./util.mjs";
|
|
9
|
+
import { decodeParams, isNonEmptyPlainObject, isPlainObject } from "./util.mjs";
|
|
10
10
|
export const NON_ENUMERABLE_PROPERTIES = [
|
|
11
11
|
// Private fields - internal implementation details
|
|
12
12
|
"_handled",
|
|
@@ -66,6 +66,7 @@ export class Route {
|
|
|
66
66
|
__publicField(this, "fullPath");
|
|
67
67
|
__publicField(this, "hash");
|
|
68
68
|
__publicField(this, "params", {});
|
|
69
|
+
__publicField(this, "paramsArray", {});
|
|
69
70
|
__publicField(this, "query", {});
|
|
70
71
|
__publicField(this, "queryArray", {});
|
|
71
72
|
__publicField(this, "meta");
|
|
@@ -85,12 +86,29 @@ export class Route {
|
|
|
85
86
|
const base = options.base;
|
|
86
87
|
const toInput = resolveRouteLocationInput(routeOptions.toInput, from);
|
|
87
88
|
const to = options.normalizeURL(parseLocation(toInput, base), from);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
let match = null;
|
|
90
|
+
if (to.origin === base.origin && to.pathname.startsWith(base.pathname)) {
|
|
91
|
+
const isLayer = toType === RouteType.pushLayer;
|
|
92
|
+
match = options.matcher(to, base, (config) => {
|
|
93
|
+
if (isLayer) {
|
|
94
|
+
return config.layer !== false;
|
|
95
|
+
}
|
|
96
|
+
return config.layer !== true;
|
|
97
|
+
});
|
|
98
|
+
}
|
|
91
99
|
if (match) {
|
|
92
100
|
applyRouteParams(match, toInput, base, to);
|
|
93
|
-
|
|
101
|
+
const decodedParams = decodeParams(match.params);
|
|
102
|
+
for (const key in decodedParams) {
|
|
103
|
+
const value = decodedParams[key];
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
this.params[key] = value[0] || "";
|
|
106
|
+
this.paramsArray[key] = value;
|
|
107
|
+
} else {
|
|
108
|
+
this.params[key] = value;
|
|
109
|
+
this.paramsArray[key] = [value];
|
|
110
|
+
}
|
|
111
|
+
}
|
|
94
112
|
}
|
|
95
113
|
this.url = to;
|
|
96
114
|
this.path = match ? to.pathname.substring(base.pathname.length - 1) : to.pathname;
|
package/dist/types.d.ts
CHANGED
|
@@ -91,9 +91,9 @@ export interface RouteParsedConfig extends RouteConfig {
|
|
|
91
91
|
}
|
|
92
92
|
export interface RouteMatchResult {
|
|
93
93
|
readonly matches: readonly RouteParsedConfig[];
|
|
94
|
-
readonly params: Record<string, string | string[]
|
|
94
|
+
readonly params: Record<string, string | string[]>;
|
|
95
95
|
}
|
|
96
|
-
export type RouteMatcher = (
|
|
96
|
+
export type RouteMatcher = (to: URL, base: URL, cb?: (item: RouteParsedConfig) => boolean) => RouteMatchResult;
|
|
97
97
|
/**
|
|
98
98
|
* Route constructor options interface
|
|
99
99
|
*/
|
package/dist/util.d.ts
CHANGED
|
@@ -24,3 +24,4 @@ export declare function isUrlEqual(url1: URL, url2?: URL | null): boolean;
|
|
|
24
24
|
* @returns Whether they match
|
|
25
25
|
*/
|
|
26
26
|
export declare function isRouteMatched(fromRoute: Route, toRoute: Route | null, matchType: RouteMatchType): boolean;
|
|
27
|
+
export declare function decodeParams<T extends Record<string, string | string[]>>(params: T): T;
|
package/dist/util.mjs
CHANGED
|
@@ -51,3 +51,17 @@ export function isRouteMatched(fromRoute, toRoute, matchType) {
|
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
+
export function decodeParams(params) {
|
|
55
|
+
const result = {};
|
|
56
|
+
for (const key in params) {
|
|
57
|
+
const value = params[key];
|
|
58
|
+
if (Array.isArray(value)) {
|
|
59
|
+
result[key] = value.map(
|
|
60
|
+
(item) => decodeURIComponent(item)
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
result[key] = decodeURIComponent(value);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"template": "library",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"lint:js": "biome check --write --no-errors-on-unmatched",
|
|
6
|
-
"lint:css": "
|
|
6
|
+
"lint:css": "npm run lint:js",
|
|
7
7
|
"lint:type": "tsc --noEmit",
|
|
8
8
|
"test": "vitest run --pass-with-no-tests",
|
|
9
9
|
"coverage": "vitest run --coverage --pass-with-no-tests",
|
|
@@ -32,16 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@biomejs/biome": "1.9.4",
|
|
35
|
-
"@esmx/lint": "3.0.0-rc.50",
|
|
36
35
|
"@types/node": "^24.0.0",
|
|
37
36
|
"@vitest/coverage-v8": "3.2.4",
|
|
38
37
|
"happy-dom": "^18.0.1",
|
|
39
|
-
"stylelint": "16.21.0",
|
|
40
38
|
"typescript": "5.8.3",
|
|
41
39
|
"unbuild": "3.5.0",
|
|
42
40
|
"vitest": "3.2.4"
|
|
43
41
|
},
|
|
44
|
-
"version": "3.0.0-rc.
|
|
42
|
+
"version": "3.0.0-rc.52",
|
|
45
43
|
"type": "module",
|
|
46
44
|
"private": false,
|
|
47
45
|
"exports": {
|
|
@@ -60,5 +58,5 @@
|
|
|
60
58
|
"template",
|
|
61
59
|
"public"
|
|
62
60
|
],
|
|
63
|
-
"gitHead": "
|
|
61
|
+
"gitHead": "6cb46d99223f82c65b7913514950497ce34860ae"
|
|
64
62
|
}
|
package/src/matcher.ts
CHANGED
|
@@ -3,7 +3,11 @@ import type { RouteConfig, RouteMatcher, RouteParsedConfig } from './types';
|
|
|
3
3
|
|
|
4
4
|
export function createMatcher(routes: RouteConfig[]): RouteMatcher {
|
|
5
5
|
const compiledRoutes = createRouteMatches(routes);
|
|
6
|
-
return (
|
|
6
|
+
return (
|
|
7
|
+
toURL: URL,
|
|
8
|
+
baseURL: URL,
|
|
9
|
+
cb?: (item: RouteParsedConfig) => boolean
|
|
10
|
+
) => {
|
|
7
11
|
const matchPath = toURL.pathname.substring(baseURL.pathname.length - 1);
|
|
8
12
|
const matches: RouteParsedConfig[] = [];
|
|
9
13
|
const params: Record<string, string | string[]> = {};
|
|
@@ -11,6 +15,9 @@ export function createMatcher(routes: RouteConfig[]): RouteMatcher {
|
|
|
11
15
|
routes: RouteParsedConfig[]
|
|
12
16
|
): boolean => {
|
|
13
17
|
for (const item of routes) {
|
|
18
|
+
if (cb && !cb(item)) {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
14
21
|
// Depth-first traversal
|
|
15
22
|
if (
|
|
16
23
|
item.children.length &&
|
package/src/options.ts
CHANGED
|
@@ -61,27 +61,11 @@ function getBaseUrl(options: RouterOptions): URL {
|
|
|
61
61
|
return base;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
function filterRoutesByLayer(
|
|
65
|
-
routes: RouteConfig[],
|
|
66
|
-
layerMode: boolean
|
|
67
|
-
): RouteConfig[] {
|
|
68
|
-
return routes.filter((route) => {
|
|
69
|
-
if (layerMode && route.layer !== true) return false;
|
|
70
|
-
if (!layerMode && route.layer === true) return false;
|
|
71
|
-
if (route.children)
|
|
72
|
-
route.children = filterRoutesByLayer(route.children, layerMode);
|
|
73
|
-
return true;
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
64
|
export function parsedOptions(
|
|
78
65
|
options: RouterOptions = {}
|
|
79
66
|
): RouterParsedOptions {
|
|
80
67
|
const base = getBaseUrl(options);
|
|
81
|
-
const routes =
|
|
82
|
-
options.routes ?? [],
|
|
83
|
-
options.layer || false
|
|
84
|
-
);
|
|
68
|
+
const routes = options.routes ?? [];
|
|
85
69
|
return Object.freeze<RouterParsedOptions>({
|
|
86
70
|
rootStyle: options.rootStyle || false,
|
|
87
71
|
root: options.root || '',
|
package/src/route-transition.ts
CHANGED
|
@@ -380,7 +380,7 @@ export class RouteTransition {
|
|
|
380
380
|
toType: RouteType,
|
|
381
381
|
toInput: RouteLocationInput
|
|
382
382
|
): Promise<Route> {
|
|
383
|
-
const from = this.route
|
|
383
|
+
const from = this.route;
|
|
384
384
|
const to = await this._runTask(
|
|
385
385
|
new Route({
|
|
386
386
|
options: this.router.parsedOptions,
|
package/src/route.ts
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
RouteType,
|
|
18
18
|
type RouterParsedOptions
|
|
19
19
|
} from './types';
|
|
20
|
-
import { isNonEmptyPlainObject, isPlainObject } from './util';
|
|
20
|
+
import { decodeParams, isNonEmptyPlainObject, isPlainObject } from './util';
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Configuration for non-enumerable properties in Route class
|
|
@@ -120,7 +120,8 @@ export class Route {
|
|
|
120
120
|
public readonly path: string;
|
|
121
121
|
public readonly fullPath: string;
|
|
122
122
|
public readonly hash: string;
|
|
123
|
-
public readonly params: Record<string, string> = {};
|
|
123
|
+
public readonly params: Record<string, string | undefined> = {};
|
|
124
|
+
public readonly paramsArray: Record<string, string[] | undefined> = {};
|
|
124
125
|
public readonly query: Record<string, string | undefined> = {};
|
|
125
126
|
public readonly queryArray: Record<string, string[] | undefined> = {};
|
|
126
127
|
public readonly meta: RouteMeta;
|
|
@@ -153,14 +154,39 @@ export class Route {
|
|
|
153
154
|
const base = options.base;
|
|
154
155
|
const toInput = resolveRouteLocationInput(routeOptions.toInput, from);
|
|
155
156
|
const to = options.normalizeURL(parseLocation(toInput, base), from);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
157
|
+
let match: RouteMatchResult | null = null;
|
|
158
|
+
|
|
159
|
+
// Check if URL origin matches base origin (protocol + hostname + port)
|
|
160
|
+
// If origins don't match, treat as external URL and don't attempt route matching
|
|
161
|
+
if (
|
|
162
|
+
to.origin === base.origin &&
|
|
163
|
+
to.pathname.startsWith(base.pathname)
|
|
164
|
+
) {
|
|
165
|
+
const isLayer = toType === RouteType.pushLayer;
|
|
166
|
+
match = options.matcher(to, base, (config) => {
|
|
167
|
+
if (isLayer) {
|
|
168
|
+
return config.layer !== false;
|
|
169
|
+
}
|
|
170
|
+
return config.layer !== true;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
160
173
|
|
|
161
174
|
if (match) {
|
|
162
175
|
applyRouteParams(match, toInput, base, to);
|
|
163
|
-
|
|
176
|
+
|
|
177
|
+
const decodedParams = decodeParams(match.params);
|
|
178
|
+
|
|
179
|
+
for (const key in decodedParams) {
|
|
180
|
+
const value = decodedParams[key];
|
|
181
|
+
|
|
182
|
+
if (Array.isArray(value)) {
|
|
183
|
+
this.params[key] = value[0] || '';
|
|
184
|
+
this.paramsArray[key] = value;
|
|
185
|
+
} else {
|
|
186
|
+
this.params[key] = value;
|
|
187
|
+
this.paramsArray[key] = [value];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
164
190
|
}
|
|
165
191
|
|
|
166
192
|
this.url = to;
|
package/src/types.ts
CHANGED
|
@@ -143,10 +143,14 @@ export interface RouteParsedConfig extends RouteConfig {
|
|
|
143
143
|
|
|
144
144
|
export interface RouteMatchResult {
|
|
145
145
|
readonly matches: readonly RouteParsedConfig[];
|
|
146
|
-
readonly params: Record<string, string | string[]
|
|
146
|
+
readonly params: Record<string, string | string[]>;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
export type RouteMatcher = (
|
|
149
|
+
export type RouteMatcher = (
|
|
150
|
+
to: URL,
|
|
151
|
+
base: URL,
|
|
152
|
+
cb?: (item: RouteParsedConfig) => boolean
|
|
153
|
+
) => RouteMatchResult;
|
|
150
154
|
|
|
151
155
|
/**
|
|
152
156
|
* Route constructor options interface
|
package/src/util.ts
CHANGED
|
@@ -114,3 +114,22 @@ export function isRouteMatched(
|
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
export function decodeParams<T extends Record<string, string | string[]>>(
|
|
119
|
+
params: T
|
|
120
|
+
): T {
|
|
121
|
+
const result = {} as T;
|
|
122
|
+
|
|
123
|
+
for (const key in params) {
|
|
124
|
+
const value = params[key];
|
|
125
|
+
if (Array.isArray(value)) {
|
|
126
|
+
result[key] = value.map((item) =>
|
|
127
|
+
decodeURIComponent(item)
|
|
128
|
+
) as T[typeof key];
|
|
129
|
+
} else {
|
|
130
|
+
result[key] = decodeURIComponent(value) as T[typeof key];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|