@fluixi/router 0.1.2-alpha.3 → 0.1.2-alpha.5

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.
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib/history.ts
21
+ var history_exports = {};
22
+ __export(history_exports, {
23
+ createBrowserHistory: () => createBrowserHistory,
24
+ createMemoryHistory: () => createMemoryHistory,
25
+ splitPath: () => splitPath
26
+ });
27
+ module.exports = __toCommonJS(history_exports);
28
+ var keys = 0;
29
+ var nextKey = () => `${Date.now().toString(36)}-${(keys++).toString(36)}`;
30
+ function splitPath(to) {
31
+ let rest = to;
32
+ let hash = "";
33
+ const hashIdx = rest.indexOf("#");
34
+ if (hashIdx !== -1) {
35
+ hash = rest.slice(hashIdx);
36
+ rest = rest.slice(0, hashIdx);
37
+ }
38
+ let search = "";
39
+ const qIdx = rest.indexOf("?");
40
+ if (qIdx !== -1) {
41
+ search = rest.slice(qIdx);
42
+ rest = rest.slice(0, qIdx);
43
+ }
44
+ return { pathname: rest || "/", search, hash };
45
+ }
46
+ function createMemoryHistory(initial = "/") {
47
+ let entries = [
48
+ { path: initial, state: null, key: nextKey() }
49
+ ];
50
+ let index = 0;
51
+ let action = "pop";
52
+ const listeners = /* @__PURE__ */ new Set();
53
+ const notify = () => listeners.forEach((l) => l());
54
+ return {
55
+ get() {
56
+ const { path, state, key } = entries[index];
57
+ return { ...splitPath(path), state, key, action };
58
+ },
59
+ push(to, state = null) {
60
+ entries = entries.slice(0, index + 1);
61
+ entries.push({ path: to, state, key: nextKey() });
62
+ index = entries.length - 1;
63
+ action = "push";
64
+ notify();
65
+ },
66
+ replace(to, state = null) {
67
+ entries[index] = { path: to, state, key: nextKey() };
68
+ action = "replace";
69
+ notify();
70
+ },
71
+ /** Back (`-1`) or forward (`1`), as the browser's buttons would. */
72
+ go(delta) {
73
+ const next = index + delta;
74
+ if (next < 0 || next >= entries.length) return;
75
+ index = next;
76
+ action = "pop";
77
+ notify();
78
+ },
79
+ subscribe(onChange) {
80
+ listeners.add(onChange);
81
+ return () => listeners.delete(onChange);
82
+ }
83
+ };
84
+ }
85
+ function createBrowserHistory() {
86
+ const w = typeof window !== "undefined" ? window : void 0;
87
+ const listeners = /* @__PURE__ */ new Set();
88
+ let action = "pop";
89
+ const notify = () => listeners.forEach((l) => l());
90
+ if (w) w.addEventListener("popstate", () => (action = "pop", notify()));
91
+ const wrapped = (value) => typeof value === "object" && value !== null && typeof value.fx === "string";
92
+ const entry = () => {
93
+ const raw = w.history.state;
94
+ if (wrapped(raw)) return raw;
95
+ const keyed = { fx: nextKey(), usr: raw ?? null };
96
+ w.history.replaceState(keyed, "");
97
+ return keyed;
98
+ };
99
+ return {
100
+ window: w !== void 0,
101
+ get() {
102
+ if (!w) return { pathname: "/", search: "", hash: "", state: null };
103
+ const { pathname, search, hash } = w.location;
104
+ const { fx, usr } = entry();
105
+ return { pathname, search, hash, state: usr, key: fx, action };
106
+ },
107
+ push(to, state = null) {
108
+ w?.history.pushState({ fx: nextKey(), usr: state }, "", to);
109
+ action = "push";
110
+ notify();
111
+ },
112
+ replace(to, state = null) {
113
+ w?.history.replaceState({ fx: nextKey(), usr: state }, "", to);
114
+ action = "replace";
115
+ notify();
116
+ },
117
+ subscribe(onChange) {
118
+ listeners.add(onChange);
119
+ return () => listeners.delete(onChange);
120
+ }
121
+ };
122
+ }
@@ -12,10 +12,12 @@ export declare function splitPath(to: string): {
12
12
  hash: string;
13
13
  };
14
14
  /**
15
- * In-memory history — for tests and server-side rendering. Holds a stack of
15
+ * In-memory history: for tests and server-side rendering. Holds a stack of
16
16
  * entries; `push`/`replace` mutate it and notify subscribers synchronously.
17
17
  */
18
- export declare function createMemoryHistory(initial?: string): HistoryAdapter;
18
+ export declare function createMemoryHistory(initial?: string): HistoryAdapter & {
19
+ go(delta: number): void;
20
+ };
19
21
  /**
20
22
  * Browser history backed by the History API. `pushState`/`replaceState` do NOT
21
23
  * emit `popstate`, so the adapter keeps its own listener set and notifies on
@@ -1 +1 @@
1
- {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/lib/history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAeA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,SAAM,GAAG,cAAc,CA4BjE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CAyBrD"}
1
+ {"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/lib/history.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,YAAY,CAAC;AAKhE,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;CACd,CAeA;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,SAAM,GAAG,cAAc,GAAG;IAAE,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAuC/F;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,cAAc,CA2CrD"}
@@ -4,6 +4,8 @@
4
4
  * The router core depends only on the {@link HistoryAdapter} interface, so it
5
5
  * runs identically in tests/SSR (memory) and the browser (History API).
6
6
  */
7
+ let keys = 0;
8
+ const nextKey = () => `${Date.now().toString(36)}-${(keys++).toString(36)}`;
7
9
  /** Split a full path string into pathname / search / hash. */
8
10
  export function splitPath(to) {
9
11
  let rest = to;
@@ -22,29 +24,41 @@ export function splitPath(to) {
22
24
  return { pathname: rest || '/', search, hash };
23
25
  }
24
26
  /**
25
- * In-memory history — for tests and server-side rendering. Holds a stack of
27
+ * In-memory history: for tests and server-side rendering. Holds a stack of
26
28
  * entries; `push`/`replace` mutate it and notify subscribers synchronously.
27
29
  */
28
30
  export function createMemoryHistory(initial = '/') {
29
31
  let entries = [
30
- { path: initial, state: null },
32
+ { path: initial, state: null, key: nextKey() },
31
33
  ];
32
34
  let index = 0;
35
+ let action = 'pop';
33
36
  const listeners = new Set();
34
37
  const notify = () => listeners.forEach((l) => l());
35
38
  return {
36
39
  get() {
37
- const { path, state } = entries[index];
38
- return { ...splitPath(path), state };
40
+ const { path, state, key } = entries[index];
41
+ return { ...splitPath(path), state, key, action };
39
42
  },
40
43
  push(to, state = null) {
41
44
  entries = entries.slice(0, index + 1);
42
- entries.push({ path: to, state });
45
+ entries.push({ path: to, state, key: nextKey() });
43
46
  index = entries.length - 1;
47
+ action = 'push';
44
48
  notify();
45
49
  },
46
50
  replace(to, state = null) {
47
- entries[index] = { path: to, state };
51
+ entries[index] = { path: to, state, key: nextKey() };
52
+ action = 'replace';
53
+ notify();
54
+ },
55
+ /** Back (`-1`) or forward (`1`), as the browser's buttons would. */
56
+ go(delta) {
57
+ const next = index + delta;
58
+ if (next < 0 || next >= entries.length)
59
+ return;
60
+ index = next;
61
+ action = 'pop';
48
62
  notify();
49
63
  },
50
64
  subscribe(onChange) {
@@ -62,22 +76,39 @@ export function createMemoryHistory(initial = '/') {
62
76
  export function createBrowserHistory() {
63
77
  const w = typeof window !== 'undefined' ? window : undefined;
64
78
  const listeners = new Set();
79
+ let action = 'pop';
65
80
  const notify = () => listeners.forEach((l) => l());
66
81
  if (w)
67
- w.addEventListener('popstate', notify);
82
+ w.addEventListener('popstate', () => ((action = 'pop'), notify()));
83
+ // Each entry's state carries its key beside the app's own state, `{ fx, usr }`, so back
84
+ // and forward can tell which entry they returned to; `get` hands the app its state alone.
85
+ // An entry made outside the router (the first load) gets a key when it is first read.
86
+ const wrapped = (value) => typeof value === 'object' && value !== null && typeof value.fx === 'string';
87
+ const entry = () => {
88
+ const raw = w.history.state;
89
+ if (wrapped(raw))
90
+ return raw;
91
+ const keyed = { fx: nextKey(), usr: raw ?? null };
92
+ w.history.replaceState(keyed, '');
93
+ return keyed;
94
+ };
68
95
  return {
96
+ window: w !== undefined,
69
97
  get() {
70
98
  if (!w)
71
99
  return { pathname: '/', search: '', hash: '', state: null };
72
100
  const { pathname, search, hash } = w.location;
73
- return { pathname, search, hash, state: w.history.state ?? null };
101
+ const { fx, usr } = entry();
102
+ return { pathname, search, hash, state: usr, key: fx, action };
74
103
  },
75
104
  push(to, state = null) {
76
- w?.history.pushState(state, '', to);
105
+ w?.history.pushState({ fx: nextKey(), usr: state }, '', to);
106
+ action = 'push';
77
107
  notify();
78
108
  },
79
109
  replace(to, state = null) {
80
- w?.history.replaceState(state, '', to);
110
+ w?.history.replaceState({ fx: nextKey(), usr: state }, '', to);
111
+ action = 'replace';
81
112
  notify();
82
113
  },
83
114
  subscribe(onChange) {
@@ -0,0 +1,101 @@
1
+ // src/lib/history.ts
2
+ var keys = 0;
3
+ var nextKey = () => `${Date.now().toString(36)}-${(keys++).toString(36)}`;
4
+ function splitPath(to) {
5
+ let rest = to;
6
+ let hash = "";
7
+ const hashIdx = rest.indexOf("#");
8
+ if (hashIdx !== -1) {
9
+ hash = rest.slice(hashIdx);
10
+ rest = rest.slice(0, hashIdx);
11
+ }
12
+ let search = "";
13
+ const qIdx = rest.indexOf("?");
14
+ if (qIdx !== -1) {
15
+ search = rest.slice(qIdx);
16
+ rest = rest.slice(0, qIdx);
17
+ }
18
+ return { pathname: rest || "/", search, hash };
19
+ }
20
+ function createMemoryHistory(initial = "/") {
21
+ let entries = [
22
+ { path: initial, state: null, key: nextKey() }
23
+ ];
24
+ let index = 0;
25
+ let action = "pop";
26
+ const listeners = /* @__PURE__ */ new Set();
27
+ const notify = () => listeners.forEach((l) => l());
28
+ return {
29
+ get() {
30
+ const { path, state, key } = entries[index];
31
+ return { ...splitPath(path), state, key, action };
32
+ },
33
+ push(to, state = null) {
34
+ entries = entries.slice(0, index + 1);
35
+ entries.push({ path: to, state, key: nextKey() });
36
+ index = entries.length - 1;
37
+ action = "push";
38
+ notify();
39
+ },
40
+ replace(to, state = null) {
41
+ entries[index] = { path: to, state, key: nextKey() };
42
+ action = "replace";
43
+ notify();
44
+ },
45
+ /** Back (`-1`) or forward (`1`), as the browser's buttons would. */
46
+ go(delta) {
47
+ const next = index + delta;
48
+ if (next < 0 || next >= entries.length) return;
49
+ index = next;
50
+ action = "pop";
51
+ notify();
52
+ },
53
+ subscribe(onChange) {
54
+ listeners.add(onChange);
55
+ return () => listeners.delete(onChange);
56
+ }
57
+ };
58
+ }
59
+ function createBrowserHistory() {
60
+ const w = typeof window !== "undefined" ? window : void 0;
61
+ const listeners = /* @__PURE__ */ new Set();
62
+ let action = "pop";
63
+ const notify = () => listeners.forEach((l) => l());
64
+ if (w) w.addEventListener("popstate", () => (action = "pop", notify()));
65
+ const wrapped = (value) => typeof value === "object" && value !== null && typeof value.fx === "string";
66
+ const entry = () => {
67
+ const raw = w.history.state;
68
+ if (wrapped(raw)) return raw;
69
+ const keyed = { fx: nextKey(), usr: raw ?? null };
70
+ w.history.replaceState(keyed, "");
71
+ return keyed;
72
+ };
73
+ return {
74
+ window: w !== void 0,
75
+ get() {
76
+ if (!w) return { pathname: "/", search: "", hash: "", state: null };
77
+ const { pathname, search, hash } = w.location;
78
+ const { fx, usr } = entry();
79
+ return { pathname, search, hash, state: usr, key: fx, action };
80
+ },
81
+ push(to, state = null) {
82
+ w?.history.pushState({ fx: nextKey(), usr: state }, "", to);
83
+ action = "push";
84
+ notify();
85
+ },
86
+ replace(to, state = null) {
87
+ w?.history.replaceState({ fx: nextKey(), usr: state }, "", to);
88
+ action = "replace";
89
+ notify();
90
+ },
91
+ subscribe(onChange) {
92
+ listeners.add(onChange);
93
+ return () => listeners.delete(onChange);
94
+ }
95
+ };
96
+ }
97
+ export {
98
+ createBrowserHistory,
99
+ createMemoryHistory,
100
+ splitPath
101
+ };
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/lib/match.ts
21
+ var match_exports = {};
22
+ __export(match_exports, {
23
+ matchPath: () => matchPath,
24
+ matchRoutes: () => matchRoutes,
25
+ rankRoutes: () => rankRoutes,
26
+ scoreRoute: () => scoreRoute
27
+ });
28
+ module.exports = __toCommonJS(match_exports);
29
+ function normalizePath(p) {
30
+ return p.startsWith("//") ? p.replace(/^\/+/, "/") : p;
31
+ }
32
+ function matchPath(pattern, path, exact = false) {
33
+ const keys = [];
34
+ if (pattern === path) return { params: {}, path };
35
+ if (!exact && pattern === "/" && path.startsWith("/"))
36
+ return { params: {}, path: "/" };
37
+ let regexStr = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
38
+ keys.push(key);
39
+ return "([^/]+)";
40
+ }).replace(/\*([a-zA-Z0-9_]*)/g, (_, name) => {
41
+ if (name) keys.push(name);
42
+ return "(.*)";
43
+ });
44
+ if (!regexStr.startsWith("/")) regexStr = "/" + regexStr;
45
+ if (regexStr === "") regexStr = "/";
46
+ const regex = new RegExp(`^${regexStr}${exact ? "$" : "(?=/|$)"}`);
47
+ const match = path.match(regex);
48
+ if (!match) return null;
49
+ const params = {};
50
+ keys.forEach((key, i) => {
51
+ params[key] = match[i + 1];
52
+ });
53
+ return { params, path: match[0] };
54
+ }
55
+ function scoreRoute(def) {
56
+ const segments = def.path.split("/").filter(Boolean);
57
+ if (segments.length === 0) return 5;
58
+ let score = 0;
59
+ for (const seg of segments) {
60
+ if (seg.startsWith("*")) score += 1;
61
+ else if (seg.startsWith(":")) score += 10;
62
+ else score += 100;
63
+ }
64
+ return score;
65
+ }
66
+ function rankRoutes(defs) {
67
+ return [...defs].sort((a, b) => scoreRoute(b) - scoreRoute(a));
68
+ }
69
+ function matchRoutes(routes, pathname) {
70
+ const path = pathname.length > 1 ? pathname.replace(/\/+$/, "") || "/" : pathname;
71
+ const result = matchInto(routes, path);
72
+ if (!result) return null;
73
+ const matched = [];
74
+ let cumulative = "";
75
+ const params = {};
76
+ for (const layer of result.layers) {
77
+ cumulative = normalizePath(joinMatched(cumulative, layer.consumed));
78
+ const layerParams = {};
79
+ for (const [k, v] of Object.entries(layer.params)) {
80
+ if (!(k in params)) layerParams[k] = v;
81
+ params[k] = v;
82
+ }
83
+ matched.push({
84
+ route: layer.route,
85
+ component: layer.route.component,
86
+ params: layerParams,
87
+ pathname: cumulative || "/"
88
+ });
89
+ }
90
+ return { matched, params, pathname };
91
+ }
92
+ function matchInto(defs, currentPath) {
93
+ if (!defs || defs.length === 0) return null;
94
+ const ranked = rankRoutes(defs);
95
+ for (const def of ranked) {
96
+ const hasChildren = !!def.children && def.children.length > 0;
97
+ const seg = matchPath(def.path, currentPath, !hasChildren);
98
+ if (!seg) continue;
99
+ if (hasChildren) {
100
+ const childMatch = matchInto(def.children, currentPath);
101
+ if (childMatch) {
102
+ return {
103
+ layers: [
104
+ { route: def, params: seg.params, consumed: seg.path },
105
+ ...childMatch.layers
106
+ ]
107
+ };
108
+ }
109
+ continue;
110
+ }
111
+ return { layers: [{ route: def, params: seg.params, consumed: seg.path }] };
112
+ }
113
+ return null;
114
+ }
115
+ function joinMatched(cumulative, consumed) {
116
+ if (!cumulative) return consumed;
117
+ if (consumed === cumulative) return cumulative;
118
+ if (consumed.startsWith(cumulative)) return consumed;
119
+ const a = cumulative.replace(/\/$/, "");
120
+ const b = consumed.replace(/^\//, "");
121
+ return `${a}/${b}`;
122
+ }
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Pure route matching.
3
3
  *
4
4
  * `matchRoutes(routes, pathname)` resolves a pathname to a parent → child
5
- * {@link RouteMatch}. No side effects, no reactivity — same input, same output.
5
+ * {@link RouteMatch}. No side effects, no reactivity, same input, same output.
6
6
  * Ported from the fluixi router's matcher and made framework-agnostic.
7
7
  */
8
8
  import type { RouteDefinition, RouteMatch } from './types.js';
package/dist/lib/match.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * @fileoverview Pure route matching.
3
3
  *
4
4
  * `matchRoutes(routes, pathname)` resolves a pathname to a parent → child
5
- * {@link RouteMatch}. No side effects, no reactivity — same input, same output.
5
+ * {@link RouteMatch}. No side effects, no reactivity, same input, same output.
6
6
  * Ported from the fluixi router's matcher and made framework-agnostic.
7
7
  */
8
8
  /** Normalise accidental double slashes (`//x` → `/x`). */
@@ -81,7 +81,7 @@ export function matchRoutes(routes, pathname) {
81
81
  const params = {};
82
82
  for (const layer of result.layers) {
83
83
  cumulative = normalizePath(joinMatched(cumulative, layer.consumed));
84
- // A layer's `params` is the DELTA it introduces — full-path nested routes
84
+ // A layer's `params` is the DELTA it introduces, full-path nested routes
85
85
  // re-match ancestor params, but those belong to the ancestor layer, not here.
86
86
  const layerParams = {};
87
87
  for (const [k, v] of Object.entries(layer.params)) {
@@ -117,7 +117,7 @@ function matchInto(defs, currentPath) {
117
117
  ],
118
118
  };
119
119
  }
120
- // A layout matched the prefix but no child matched the remainder — keep
120
+ // A layout matched the prefix but no child matched the remainder, keep
121
121
  // searching siblings (this layout is not the right branch).
122
122
  continue;
123
123
  }
@@ -0,0 +1,101 @@
1
+ // src/lib/match.ts
2
+ function normalizePath(p) {
3
+ return p.startsWith("//") ? p.replace(/^\/+/, "/") : p;
4
+ }
5
+ function matchPath(pattern, path, exact = false) {
6
+ const keys = [];
7
+ if (pattern === path) return { params: {}, path };
8
+ if (!exact && pattern === "/" && path.startsWith("/"))
9
+ return { params: {}, path: "/" };
10
+ let regexStr = pattern.replace(/:([a-zA-Z0-9_]+)/g, (_, key) => {
11
+ keys.push(key);
12
+ return "([^/]+)";
13
+ }).replace(/\*([a-zA-Z0-9_]*)/g, (_, name) => {
14
+ if (name) keys.push(name);
15
+ return "(.*)";
16
+ });
17
+ if (!regexStr.startsWith("/")) regexStr = "/" + regexStr;
18
+ if (regexStr === "") regexStr = "/";
19
+ const regex = new RegExp(`^${regexStr}${exact ? "$" : "(?=/|$)"}`);
20
+ const match = path.match(regex);
21
+ if (!match) return null;
22
+ const params = {};
23
+ keys.forEach((key, i) => {
24
+ params[key] = match[i + 1];
25
+ });
26
+ return { params, path: match[0] };
27
+ }
28
+ function scoreRoute(def) {
29
+ const segments = def.path.split("/").filter(Boolean);
30
+ if (segments.length === 0) return 5;
31
+ let score = 0;
32
+ for (const seg of segments) {
33
+ if (seg.startsWith("*")) score += 1;
34
+ else if (seg.startsWith(":")) score += 10;
35
+ else score += 100;
36
+ }
37
+ return score;
38
+ }
39
+ function rankRoutes(defs) {
40
+ return [...defs].sort((a, b) => scoreRoute(b) - scoreRoute(a));
41
+ }
42
+ function matchRoutes(routes, pathname) {
43
+ const path = pathname.length > 1 ? pathname.replace(/\/+$/, "") || "/" : pathname;
44
+ const result = matchInto(routes, path);
45
+ if (!result) return null;
46
+ const matched = [];
47
+ let cumulative = "";
48
+ const params = {};
49
+ for (const layer of result.layers) {
50
+ cumulative = normalizePath(joinMatched(cumulative, layer.consumed));
51
+ const layerParams = {};
52
+ for (const [k, v] of Object.entries(layer.params)) {
53
+ if (!(k in params)) layerParams[k] = v;
54
+ params[k] = v;
55
+ }
56
+ matched.push({
57
+ route: layer.route,
58
+ component: layer.route.component,
59
+ params: layerParams,
60
+ pathname: cumulative || "/"
61
+ });
62
+ }
63
+ return { matched, params, pathname };
64
+ }
65
+ function matchInto(defs, currentPath) {
66
+ if (!defs || defs.length === 0) return null;
67
+ const ranked = rankRoutes(defs);
68
+ for (const def of ranked) {
69
+ const hasChildren = !!def.children && def.children.length > 0;
70
+ const seg = matchPath(def.path, currentPath, !hasChildren);
71
+ if (!seg) continue;
72
+ if (hasChildren) {
73
+ const childMatch = matchInto(def.children, currentPath);
74
+ if (childMatch) {
75
+ return {
76
+ layers: [
77
+ { route: def, params: seg.params, consumed: seg.path },
78
+ ...childMatch.layers
79
+ ]
80
+ };
81
+ }
82
+ continue;
83
+ }
84
+ return { layers: [{ route: def, params: seg.params, consumed: seg.path }] };
85
+ }
86
+ return null;
87
+ }
88
+ function joinMatched(cumulative, consumed) {
89
+ if (!cumulative) return consumed;
90
+ if (consumed === cumulative) return cumulative;
91
+ if (consumed.startsWith(cumulative)) return consumed;
92
+ const a = cumulative.replace(/\/$/, "");
93
+ const b = consumed.replace(/^\//, "");
94
+ return `${a}/${b}`;
95
+ }
96
+ export {
97
+ matchPath,
98
+ matchRoutes,
99
+ rankRoutes,
100
+ scoreRoute
101
+ };