@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 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 = filterRoutesByLayer(
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, _b, _c;
274
- const from = (_b = (_a = this.route) == null ? void 0 : _a.clone()) != null ? _b : null;
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: (_c = from == null ? void 0 : from.url) != null ? _c : null
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
- const isSameOrigin = to.origin === base.origin;
89
- const isSameBase = to.pathname.startsWith(base.pathname);
90
- const match = isSameOrigin && isSameBase ? options.matcher(to, base) : null;
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
- Object.assign(this.params, match.params);
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[] | undefined>;
94
+ readonly params: Record<string, string | string[]>;
95
95
  }
96
- export type RouteMatcher = (targetURL: URL, baseURL: URL) => RouteMatchResult;
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": "stylelint '**/*.{css,vue}' --fix --aei",
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.50",
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": "7ec8747f78f4626941cb4da548b6df47df4df825"
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 (toURL: URL, baseURL: URL) => {
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 = filterRoutesByLayer(
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 || '',
@@ -380,7 +380,7 @@ export class RouteTransition {
380
380
  toType: RouteType,
381
381
  toInput: RouteLocationInput
382
382
  ): Promise<Route> {
383
- const from = this.route?.clone() ?? null;
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
- const isSameOrigin = to.origin === base.origin;
157
- const isSameBase = to.pathname.startsWith(base.pathname);
158
- const match =
159
- isSameOrigin && isSameBase ? options.matcher(to, base) : null;
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
- Object.assign(this.params, match.params);
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[] | undefined>;
146
+ readonly params: Record<string, string | string[]>;
147
147
  }
148
148
 
149
- export type RouteMatcher = (targetURL: URL, baseURL: URL) => RouteMatchResult;
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
+ }