@ktjs/router 0.33.3 → 0.33.4

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 CHANGED
@@ -45,7 +45,12 @@ interface RawRouteConfig {
45
45
  children?: RawRouteConfig[];
46
46
  }
47
47
 
48
- type RouteConfig = Required<Omit<RawRouteConfig, 'children'>> & { children: RouteConfig[] };
48
+ type RouteConfig = Omit<RawRouteConfig, 'children'> & {
49
+ meta: Record<string, any>;
50
+ beforeEnter: NonNullable<RawRouteConfig['beforeEnter']>;
51
+ after: NonNullable<RawRouteConfig['after']>;
52
+ children: RouteConfig[];
53
+ };
49
54
 
50
55
  /**
51
56
  * Current route context information
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { normalizePath, extractParams, $emptyFn, parseQuery, emplaceParams, buildQuery } from "@ktjs/shared";
1
+ import { normalizePath, extractParams, $emptyFn, emplaceParams, buildQuery, parseQuery } from "@ktjs/shared";
2
2
 
3
3
  function KTRouter({router: router}) {
4
4
  const view = document.createElement("kt-router-view");
@@ -6,112 +6,121 @@ function KTRouter({router: router}) {
6
6
  }
7
7
 
8
8
  const createRouter = ({beforeEach: beforeEach = $emptyFn, afterEach: afterEach = $emptyFn, onNotFound: onNotFound = $emptyFn, onError: onError = $emptyFn, prefix: prefix = "", routes: rawRoutes}) => {
9
- const routes = [], history = [];
10
- let routerView = null, current = null;
11
- const normalize = (rawRoutes, parentPath) => rawRoutes.map(route => {
9
+ const routes = [], matchedMap = new Map, history = [], prefixPath = prefix ? normalizePath(prefix) : "";
10
+ let routerView = null, current = null, ignoreNextHashChange = !1;
11
+ const normalize = (rawRoutes, parentPath, parentMatched) => rawRoutes.map(route => {
12
12
  const path = normalizePath(parentPath, route.path), normalized = {
13
- path: prefix + path,
14
- name: route.name ?? "",
13
+ path: prefixPath ? normalizePath(prefixPath, path) : path,
14
+ name: route.name,
15
15
  meta: route.meta ?? {},
16
16
  beforeEnter: route.beforeEnter ?? $emptyFn,
17
17
  after: route.after ?? $emptyFn,
18
- children: route.children ? normalize(route.children, path) : [],
18
+ children: [],
19
19
  component: route.component
20
- };
21
- return routes.push(normalized), normalized;
22
- }), navigate = async (options, redirectCount = 0) => {
20
+ }, matched = parentMatched.concat(normalized);
21
+ return matchedMap.set(normalized, matched), normalized.children = route.children ? normalize(route.children, path, matched) : [],
22
+ routes.push(normalized), normalized;
23
+ });
24
+ normalize(rawRoutes, "/", []);
25
+ const guard = async (to, from, guardLevel) => {
23
26
  try {
24
- if (redirectCount > 10) return onError(new Error("Maximum redirect count exceeded")),
25
- !1;
26
- const prep = (options => {
27
- let targetPath, targetRoute;
28
- if (options.name) {
29
- if (targetRoute = findByName(options.name), !targetRoute) throw new Error(`[@ktjs/router error] Route not found: ${options.name}`);
30
- targetPath = targetRoute.path;
31
- } else {
32
- if (!options.path) throw new Error("[@ktjs/router error] Either path or name must be provided");
33
- targetPath = normalizePath(options.path), targetRoute = match(targetPath)?.route;
34
- }
35
- options.params && (targetPath = emplaceParams(targetPath, options.params));
36
- const matched = match(targetPath);
37
- if (!matched) return onNotFound(targetPath), null;
38
- const fullPath = targetPath + (options.query ? buildQuery(options.query) : ""), to = {
39
- path: targetPath,
40
- name: matched.route.name,
41
- params: {
42
- ...matched.params,
43
- ...options.params ?? {}
44
- },
45
- query: options.query ?? {},
46
- meta: matched.route.meta ?? {},
47
- matched: matched.result
48
- };
49
- return {
50
- guardLevel: options.guardLevel ?? 15,
51
- replace: options.replace ?? !1,
52
- to: to,
53
- fullPath: fullPath
27
+ if (0 === guardLevel) return {
28
+ continue: !0
29
+ };
30
+ if (1 & guardLevel) {
31
+ const result = await beforeEach(to, from);
32
+ if (!1 === result) return {
33
+ continue: !1
54
34
  };
55
- })(options);
56
- if (!prep) return !1;
57
- const {guardLevel: guardLevel, replace: replace, to: to, fullPath: fullPath} = prep, guardResult = await (async (to, from, guardLevel) => {
58
- try {
59
- if (0 === guardLevel) return {
60
- continue: !0
61
- };
62
- if (1 & guardLevel) {
63
- const result = await beforeEach(to, from);
64
- if (!1 === result) return {
65
- continue: !1
66
- };
67
- if ("string" == typeof result) return {
68
- continue: !1,
69
- redirectTo: {
70
- path: result
71
- }
72
- };
73
- if (result && "object" == typeof result) return {
74
- continue: !1,
75
- redirectTo: result
76
- };
35
+ if ("string" == typeof result) return {
36
+ continue: !1,
37
+ redirectTo: {
38
+ path: result
77
39
  }
78
- if (2 & guardLevel) {
79
- const targetRoute = to.matched[to.matched.length - 1], result = await targetRoute.beforeEnter(to);
80
- if (!1 === result) return {
81
- continue: !1
82
- };
83
- if ("string" == typeof result) return {
84
- continue: !1,
85
- redirectTo: {
86
- path: result
87
- }
88
- };
89
- if (result && "object" == typeof result) return {
90
- continue: !1,
91
- redirectTo: result
92
- };
40
+ };
41
+ if (result && "object" == typeof result) return {
42
+ continue: !1,
43
+ redirectTo: result
44
+ };
45
+ }
46
+ if (2 & guardLevel) {
47
+ const targetRoute = to.matched[to.matched.length - 1], result = await targetRoute.beforeEnter(to);
48
+ if (!1 === result) return {
49
+ continue: !1
50
+ };
51
+ if ("string" == typeof result) return {
52
+ continue: !1,
53
+ redirectTo: {
54
+ path: result
93
55
  }
94
- return {
95
- continue: !0
96
- };
97
- } catch (error) {
98
- return onError(error), {
99
- continue: !1
100
- };
101
- }
102
- })(to, current, guardLevel);
103
- if (!guardResult.continue) return !!guardResult.redirectTo && await navigate(guardResult.redirectTo, redirectCount + 1);
104
- const hashUrl = "#" + fullPath;
105
- if (replace ? window.location.replace(hashUrl) : window.location.hash = fullPath,
106
- current = to, replace && history.length > 0 ? history[history.length - 1] = to : history.push(to),
107
- routerView && to.matched.length > 0) {
108
- const route = to.matched[to.matched.length - 1];
109
- if (route.component) {
110
- const element = await route.component();
111
- routerView.innerHTML = "", routerView.appendChild(element);
112
- }
56
+ };
57
+ if (result && "object" == typeof result) return {
58
+ continue: !1,
59
+ redirectTo: result
60
+ };
113
61
  }
114
- return executeAfterHooks(to, history[history.length - 2] ?? null), !0;
62
+ return {
63
+ continue: !0
64
+ };
65
+ } catch (error) {
66
+ return onError(error), {
67
+ continue: !1
68
+ };
69
+ }
70
+ }, navigatePrepare = options => {
71
+ let targetPath;
72
+ if (options.name) {
73
+ const targetRoute = findByName(options.name);
74
+ if (!targetRoute) throw new Error(`[@ktjs/router error] Route not found: ${options.name}`);
75
+ targetPath = targetRoute.path;
76
+ } else {
77
+ if (!options.path) throw new Error("[@ktjs/router error] Either path or name must be provided");
78
+ targetPath = normalizePath(options.path), prefixPath && targetPath !== prefixPath && !targetPath.startsWith(prefixPath + "/") && (targetPath = normalizePath(prefixPath, targetPath));
79
+ }
80
+ options.params && (targetPath = emplaceParams(targetPath, options.params));
81
+ const matched = match(targetPath);
82
+ if (!matched) return onNotFound(targetPath), null;
83
+ const fullPath = targetPath + (options.query ? buildQuery(options.query) : ""), to = {
84
+ path: targetPath,
85
+ name: matched.route.name ?? "",
86
+ params: {
87
+ ...matched.params,
88
+ ...options.params ?? {}
89
+ },
90
+ query: options.query ?? {},
91
+ meta: matched.route.meta ?? {},
92
+ matched: matched.result
93
+ };
94
+ return {
95
+ guardLevel: options.guardLevel ?? 15,
96
+ replace: options.replace ?? !1,
97
+ to: to,
98
+ fullPath: fullPath
99
+ };
100
+ }, commit = async (to, fullPath, replaceHistory, hashMode) => {
101
+ if (hashMode && window.location.hash.slice(1) !== fullPath) {
102
+ ignoreNextHashChange = !0;
103
+ const hashUrl = "#" + fullPath;
104
+ "replace" === hashMode ? window.location.replace(hashUrl) : window.location.hash = fullPath;
105
+ }
106
+ const from = current;
107
+ if (current = to, replaceHistory && history.length > 0 ? history[history.length - 1] = to : history.push(to),
108
+ routerView && to.matched.length > 0) {
109
+ const route = to.matched[to.matched.length - 1], element = await route.component();
110
+ routerView.innerHTML = "", routerView.appendChild(element);
111
+ }
112
+ executeAfterHooks(to, from).catch(error => {
113
+ onError(error, to.matched[to.matched.length - 1]);
114
+ });
115
+ }, navigate = async (options, redirectCount = 0) => {
116
+ try {
117
+ if (redirectCount > 10) return onError(new Error("Maximum redirect count exceeded")),
118
+ !1;
119
+ const prep = navigatePrepare(options);
120
+ if (!prep) return !1;
121
+ const {guardLevel: guardLevel, replace: replace, to: to, fullPath: fullPath} = prep, guardResult = await guard(to, current, guardLevel);
122
+ return guardResult.continue ? (await commit(to, fullPath, replace, replace ? "replace" : "push"),
123
+ !0) : !!guardResult.redirectTo && await navigate(guardResult.redirectTo, redirectCount + 1);
115
124
  } catch (error) {
116
125
  return onError(error), !1;
117
126
  }
@@ -125,30 +134,50 @@ const createRouter = ({beforeEach: beforeEach = $emptyFn, afterEach: afterEach =
125
134
  path: path,
126
135
  query: queryString ? parseQuery(queryString) : void 0
127
136
  };
128
- };
129
- window.addEventListener("hashchange", () => {
130
- const hash = window.location.hash.slice(1), [path] = hash.split("?"), normalizedPath = normalizePath(path);
131
- if (current && current.path === normalizedPath) return;
132
- const matched = match(normalizedPath);
133
- if (!matched) return void onNotFound(normalizedPath);
134
- const queryString = window.location.hash.slice(1).split("?")[1], to = {
135
- path: normalizedPath,
136
- name: matched.route.name,
137
- params: matched.params,
138
- query: queryString ? parseQuery(queryString) : {},
139
- meta: matched.route.meta ?? {},
140
- matched: matched.result
141
- };
142
- if (current = to, history.push(to), routerView && to.matched.length > 0) {
143
- const route = to.matched[to.matched.length - 1];
144
- if (route.component) {
145
- const element = route.component();
146
- element instanceof Promise ? element.then(el => {
147
- routerView.innerHTML = "", routerView.appendChild(el);
148
- }) : (routerView.innerHTML = "", routerView.appendChild(element));
137
+ }, {findByName: findByName, match: match} = ((routes, matchedMap) => {
138
+ const nameMap = {};
139
+ for (let i = 0; i < routes.length; i++) {
140
+ const route = routes[i];
141
+ if (void 0 !== route.name) {
142
+ if (route.name in nameMap) throw new Error(`[@ktjs/router error] Duplicate route name detected: '${route.name}'`);
143
+ nameMap[route.name] = route;
149
144
  }
150
145
  }
151
- executeAfterHooks(to, history[history.length - 2] ?? null);
146
+ return {
147
+ findByName: name => nameMap[name] ?? null,
148
+ match: path => {
149
+ const normalizedPath = normalizePath(path);
150
+ for (const route of routes) if (route.path === normalizedPath) return {
151
+ route: route,
152
+ params: {},
153
+ result: matchedMap.get(route) ?? [ route ]
154
+ };
155
+ for (const route of routes) if (route.path.includes(":")) {
156
+ const params = extractParams(route.path, normalizedPath);
157
+ if (params) return {
158
+ route: route,
159
+ params: params,
160
+ result: matchedMap.get(route) ?? [ route ]
161
+ };
162
+ }
163
+ return null;
164
+ }
165
+ };
166
+ })(routes, matchedMap), handleHashChange = async () => {
167
+ if (ignoreNextHashChange) return void (ignoreNextHashChange = !1);
168
+ const hash = window.location.hash.slice(1);
169
+ if (!hash) return;
170
+ const prep = navigatePrepare(normalizeLocation(hash));
171
+ if (!prep) return;
172
+ const {guardLevel: guardLevel, to: to, fullPath: fullPath} = prep, guardResult = await guard(to, current, guardLevel);
173
+ if (!guardResult.continue) return guardResult.redirectTo ? void await navigate({
174
+ ...guardResult.redirectTo,
175
+ replace: !0
176
+ }) : (ignoreNextHashChange = !0, void (current ? window.location.replace("#" + current.path + buildQuery(current.query)) : window.location.replace(window.location.pathname + window.location.search)));
177
+ await commit(to, fullPath, !1, fullPath === hash ? null : "replace");
178
+ };
179
+ window.addEventListener("hashchange", () => {
180
+ handleHashChange();
152
181
  });
153
182
  const instance = {
154
183
  get current() {
@@ -185,46 +214,7 @@ const createRouter = ({beforeEach: beforeEach = $emptyFn, afterEach: afterEach =
185
214
  window.history.forward();
186
215
  }
187
216
  };
188
- normalize(rawRoutes, "/");
189
- const {findByName: findByName, match: match} = (routes => {
190
- const nameMap = {};
191
- for (let i = 0; i < routes.length; i++) {
192
- const route = routes[i];
193
- if (void 0 !== route.name) {
194
- if (route.name in nameMap) throw new Error(`[@ktjs/router error] Duplicate route name detected: '${route.name}'`);
195
- nameMap[route.name] = route;
196
- }
197
- }
198
- const getMatchedChain = route => {
199
- const matched = [ route ], path = route.path;
200
- for (let i = 0; i < routes.length; i++) {
201
- const r = routes[i];
202
- r !== route && path.startsWith(r.path) && path !== r.path && matched.push(r);
203
- }
204
- return matched.reverse();
205
- };
206
- return {
207
- findByName: name => nameMap[name] ?? null,
208
- match: path => {
209
- const normalizedPath = normalizePath(path);
210
- for (const route of routes) if (route.path === normalizedPath) return {
211
- route: route,
212
- params: {},
213
- result: getMatchedChain(route)
214
- };
215
- for (const route of routes) if (route.path.includes(":")) {
216
- const params = extractParams(route.path, normalizedPath);
217
- if (params) return {
218
- route: route,
219
- params: params,
220
- result: getMatchedChain(route)
221
- };
222
- }
223
- return null;
224
- }
225
- };
226
- })(routes), currentHash = window.location.hash.slice(1);
227
- return currentHash && instance.push(currentHash), instance;
217
+ return window.location.hash.slice(1) && handleHashChange(), instance;
228
218
  };
229
219
 
230
220
  export { KTRouter, createRouter };
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../src/core/kt-router.ts","../src/core/index.ts","../src/core/matcher.ts"],"sourcesContent":["import type { JSX } from '@ktjs/core/jsx-runtime';\nimport type { Router } from '../types/router.js';\n\n/**\n * Create a router view container that automatically renders route components\n */\nexport function KTRouter({ router }: { router: Router }): JSX.Element {\n const view = document.createElement('kt-router-view');\n router.setRouterView(view);\n return view as JSX.Element;\n}\n","import { $emptyFn, buildQuery, normalizePath, parseQuery, emplaceParams } from '@ktjs/shared';\n\nimport type { Router, RouterConfig, RouteContext, NavOptions, RawRouteConfig, RouteConfig } from '../types/router.js';\nimport { GuardLevel } from './consts.js';\nimport { createMatcher } from './matcher.js';\n\n/**\n * Create a new router instance\n */\nexport const createRouter = ({\n beforeEach = $emptyFn,\n afterEach = $emptyFn,\n onNotFound = $emptyFn,\n onError = $emptyFn,\n prefix = '',\n routes: rawRoutes,\n}: RouterConfig): Router => {\n // # private values\n const routes: RouteConfig[] = [];\n const history: RouteContext[] = [];\n let routerView: HTMLElement | null = null;\n let current: RouteContext | null = null;\n\n // # methods\n const normalize = (rawRoutes: RawRouteConfig[], parentPath: string): RouteConfig[] =>\n rawRoutes.map((route) => {\n const path = normalizePath(parentPath, route.path);\n const normalized = {\n path: prefix + path,\n name: route.name ?? '',\n meta: route.meta ?? {},\n beforeEnter: route.beforeEnter ?? $emptyFn,\n after: route.after ?? $emptyFn,\n children: route.children ? normalize(route.children, path) : [],\n component: route.component,\n };\n\n // directly push the normalized route to the list\n // avoid flatten them again\n routes.push(normalized);\n return normalized;\n });\n\n const guard = async (\n to: RouteContext,\n from: RouteContext | null,\n guardLevel: GuardLevel,\n ): Promise<{ continue: boolean; redirectTo?: NavOptions }> => {\n try {\n if (guardLevel === GuardLevel.None) {\n return { continue: true };\n }\n\n if (guardLevel & GuardLevel.Global) {\n const result = await beforeEach(to, from);\n if (result === false) {\n return { continue: false };\n }\n if (typeof result === 'string') {\n return { continue: false, redirectTo: { path: result } };\n }\n if (result && typeof result === 'object') {\n return { continue: false, redirectTo: result };\n }\n }\n\n if (guardLevel & GuardLevel.Route) {\n const targetRoute = to.matched[to.matched.length - 1];\n const result = await targetRoute.beforeEnter(to);\n if (result === false) {\n return { continue: false };\n }\n if (typeof result === 'string') {\n return { continue: false, redirectTo: { path: result } };\n }\n if (result && typeof result === 'object') {\n return { continue: false, redirectTo: result };\n }\n }\n\n return { continue: true };\n } catch (error) {\n onError(error as Error);\n return { continue: false };\n }\n };\n\n const navigatePrepare = (options: NavOptions) => {\n // Resolve target route\n let targetPath: string;\n let targetRoute;\n\n if (options.name) {\n targetRoute = findByName(options.name);\n if (!targetRoute) {\n $throw(`Route not found: ${options.name}`);\n }\n targetPath = targetRoute.path;\n } else if (options.path) {\n targetPath = normalizePath(options.path);\n targetRoute = match(targetPath)?.route;\n } else {\n $throw(`Either path or name must be provided`);\n }\n\n // Substitute params\n if (options.params) {\n targetPath = emplaceParams(targetPath, options.params);\n }\n\n // Match final path\n const matched = match(targetPath);\n if (!matched) {\n onNotFound(targetPath);\n return null;\n }\n\n // Build route context\n const queryString = options.query ? buildQuery(options.query) : '';\n const fullPath = targetPath + queryString;\n\n const to: RouteContext = {\n path: targetPath,\n name: matched.route.name,\n params: { ...matched.params, ...(options.params ?? {}) },\n query: options.query ?? {},\n meta: matched.route.meta ?? {},\n matched: matched.result,\n };\n\n return {\n guardLevel: options.guardLevel ?? GuardLevel.Default,\n replace: options.replace ?? false,\n to,\n fullPath,\n };\n };\n\n const navigate = async (options: NavOptions, redirectCount = 0): Promise<boolean> => {\n try {\n // Prevent infinite redirect loop\n if (redirectCount > 10) {\n onError(new Error('Maximum redirect count exceeded'));\n return false;\n }\n\n const prep = navigatePrepare(options);\n if (!prep) {\n return false;\n }\n\n const { guardLevel, replace, to, fullPath } = prep;\n\n const guardResult = await guard(to, current, guardLevel);\n if (!guardResult.continue) {\n // Check if there's a redirect\n if (guardResult.redirectTo) {\n return await navigate(guardResult.redirectTo, redirectCount + 1);\n }\n return false;\n }\n\n // ---- Guards passed ----\n\n const hashUrl = '#' + fullPath;\n if (replace) {\n window.location.replace(hashUrl);\n } else {\n window.location.hash = fullPath;\n }\n\n current = to;\n if (replace) {\n if (history.length > 0) {\n history[history.length - 1] = to;\n } else {\n history.push(to);\n }\n } else {\n history.push(to);\n }\n\n // Render component if routerView exists\n if (routerView && to.matched.length > 0) {\n const route = to.matched[to.matched.length - 1];\n if (route.component) {\n const element = await route.component();\n routerView.innerHTML = '';\n routerView.appendChild(element);\n }\n }\n\n executeAfterHooks(to, history[history.length - 2] ?? null);\n return true;\n } catch (error) {\n onError(error as Error);\n return false;\n }\n };\n\n const executeAfterHooks = async (to: RouteContext, from: RouteContext | null): Promise<void> => {\n const targetRoute = to.matched[to.matched.length - 1];\n await targetRoute.after(to);\n await afterEach(to, from);\n };\n\n /**\n * Normalize navigation argument\n */\n const normalizeLocation = (loc: string | NavOptions): NavOptions => {\n if (typeof loc !== 'string') {\n return loc;\n }\n\n const [path, queryString] = loc.split('?');\n return {\n path,\n query: queryString ? parseQuery(queryString) : undefined,\n };\n };\n\n // # register events\n window.addEventListener('hashchange', () => {\n const hash = window.location.hash.slice(1);\n const [path] = hash.split('?');\n const normalizedPath = normalizePath(path);\n if (current && current.path === normalizedPath) {\n return;\n }\n // render route for new hash without adding extra history entry\n const matched = match(normalizedPath);\n if (!matched) {\n onNotFound(normalizedPath);\n return;\n }\n const queryString = window.location.hash.slice(1).split('?')[1];\n const to: RouteContext = {\n path: normalizedPath,\n name: matched.route.name,\n params: matched.params,\n query: queryString ? parseQuery(queryString) : {},\n meta: matched.route.meta ?? {},\n matched: matched.result,\n };\n\n // apply without modifying browser history\n current = to;\n history.push(to);\n\n if (routerView && to.matched.length > 0) {\n const route = to.matched[to.matched.length - 1];\n if (route.component) {\n const element = route.component();\n if (element instanceof Promise) {\n element.then((el) => {\n routerView!.innerHTML = '';\n routerView!.appendChild(el);\n });\n } else {\n routerView.innerHTML = '';\n routerView.appendChild(element);\n }\n }\n }\n\n executeAfterHooks(to, history[history.length - 2] ?? null);\n });\n\n // # initialize\n const instance: Router = {\n get current() {\n return current;\n },\n\n get history() {\n return history.concat();\n },\n\n setRouterView(view: HTMLElement) {\n routerView = view;\n },\n\n push(location: string | NavOptions): boolean | Promise<boolean> {\n const options = normalizeLocation(location);\n return navigate(options);\n },\n\n silentPush(location: string | NavOptions): boolean | Promise<boolean> {\n const options = normalizeLocation(location);\n return navigate({ ...options, guardLevel: GuardLevel.Route });\n },\n\n replace(location: string | NavOptions): boolean | Promise<boolean> {\n const options = normalizeLocation(location);\n return navigate({ ...options, replace: true });\n },\n\n back() {\n window.history.back();\n },\n\n forward() {\n window.history.forward();\n },\n };\n normalize(rawRoutes, '/');\n const { findByName, match } = createMatcher(routes);\n const currentHash = window.location.hash.slice(1);\n if (currentHash) {\n instance.push(currentHash);\n }\n\n return instance;\n};\n\nexport { GuardLevel };\nexport { KTRouter } from './kt-router.js';\n","import type { RouteConfig, RouteMatch } from '../types/router.js';\nimport { extractParams, normalizePath } from '@ktjs/shared';\n\n/**\n * Route matcher for finding matching routes and extracting params\n */\nexport const createMatcher = (routes: RouteConfig[]) => {\n const nameMap: Record<string, RouteConfig> = {};\n\n for (let i = 0; i < routes.length; i++) {\n const route = routes[i];\n if (route.name !== undefined) {\n if (route.name in nameMap) {\n $throw(`Duplicate route name detected: '${route.name}'`);\n }\n nameMap[route.name] = route;\n }\n }\n\n /**\n * Find route by name\n */\n const findByName = (name: string): RouteConfig | null => {\n return nameMap[name] ?? null;\n };\n\n /**\n * Match path against all routes\n */\n const match = (path: string): RouteMatch | null => {\n const normalizedPath = normalizePath(path);\n\n // Try exact match first\n for (const route of routes) {\n if (route.path === normalizedPath) {\n return {\n route,\n params: {},\n result: getMatchedChain(route),\n };\n }\n }\n\n // Try dynamic routes\n for (const route of routes) {\n if (route.path.includes(':')) {\n const params = extractParams(route.path, normalizedPath);\n if (params) {\n return {\n route,\n params,\n result: getMatchedChain(route),\n };\n }\n }\n }\n\n return null;\n };\n\n /**\n * Get chain of matched routes (for nested routes)\n * - parent roots ahead\n */\n const getMatchedChain = (route: RouteConfig): RouteConfig[] => {\n const matched: RouteConfig[] = [route];\n const path = route.path;\n\n // Find parent routes by path prefix matching\n for (let i = 0; i < routes.length; i++) {\n const r = routes[i];\n if (r !== route && path.startsWith(r.path) && path !== r.path) {\n matched.push(r);\n }\n }\n\n return matched.reverse();\n };\n\n return {\n findByName,\n match,\n };\n};\n"],"names":["KTRouter","router","view","document","createElement","setRouterView","createRouter","beforeEach","$emptyFn","afterEach","onNotFound","onError","prefix","routes","rawRoutes","history","routerView","current","normalize","parentPath","map","route","path","normalizePath","normalized","name","meta","beforeEnter","after","children","component","push","navigate","async","options","redirectCount","Error","prep","targetPath","targetRoute","findByName","match","params","emplaceParams","matched","fullPath","query","buildQuery","to","result","guardLevel","replace","navigatePrepare","guardResult","from","continue","redirectTo","length","error","guard","hashUrl","window","location","hash","element","innerHTML","appendChild","executeAfterHooks","normalizeLocation","loc","queryString","split","parseQuery","undefined","addEventListener","slice","normalizedPath","Promise","then","el","instance","concat","silentPush","back","forward","nameMap","i","getMatchedChain","r","startsWith","reverse","includes","extractParams","createMatcher","currentHash"],"mappings":";;AAMM,SAAUA,UAASC,QAAEA;IACzB,MAAMC,OAAOC,SAASC,cAAc;IAEpC,OADAH,OAAOI,cAAcH,OACdA;AACT;;ACDO,MAAMI,eAAe,EAC1BC,yBAAaC,UACbC,uBAAYD,UACZE,yBAAaF,UACbG,mBAAUH,UACVI,iBAAS,IACTC,QAAQC;IAGR,MAAMD,SAAwB,IACxBE,UAA0B;IAChC,IAAIC,aAAiC,MACjCC,UAA+B;IAGnC,MAAMC,YAAY,CAACJ,WAA6BK,eAC9CL,UAAUM,IAAKC;QACb,MAAMC,OAAOC,cAAcJ,YAAYE,MAAMC,OACvCE,aAAa;YACjBF,MAAMV,SAASU;YACfG,MAAMJ,MAAMI,QAAQ;YACpBC,MAAML,MAAMK,QAAQ,CAAA;YACpBC,aAAaN,MAAMM,eAAenB;YAClCoB,OAAOP,MAAMO,SAASpB;YACtBqB,UAAUR,MAAMQ,WAAWX,UAAUG,MAAMQ,UAAUP,QAAQ;YAC7DQ,WAAWT,MAAMS;;QAMnB,OADAjB,OAAOkB,KAAKP,aACLA;QAkGLQ,WAAWC,OAAOC,SAAqBC,gBAAgB;QAC3D;YAEE,IAAIA,gBAAgB,IAElB,OADAxB,QAAQ,IAAIyB,MAAM;aACX;YAGT,MAAMC,OA3Dc,CAACH;gBAEvB,IAAII,YACAC;gBAEJ,IAAIL,QAAQT,MAAM;oBAEhB,IADAc,cAAcC,WAAWN,QAAQT,QAC5Bc,aACH,MAAA,IAAAH,MAAA,yCAA2BF,QAAQT;oBAErCa,aAAaC,YAAYjB;AAC3B,uBAAO;oBAAA,KAAIY,QAAQZ,MAIjB;oBAHAgB,aAAaf,cAAcW,QAAQZ,OACnCiB,cAAcE,MAAMH,aAAajB;AAGnC;gBAGIa,QAAQQ,WACVJ,aAAaK,cAAcL,YAAYJ,QAAQQ;gBAIjD,MAAME,UAAUH,MAAMH;gBACtB,KAAKM,SAEH,OADAlC,WAAW4B,aACJ;gBAIT,MACMO,WAAWP,cADGJ,QAAQY,QAAQC,WAAWb,QAAQY,SAAS,KAG1DE,KAAmB;oBACvB1B,MAAMgB;oBACNb,MAAMmB,QAAQvB,MAAMI;oBACpBiB,QAAQ;2BAAKE,QAAQF;2BAAYR,QAAQQ,UAAU,CAAA;;oBACnDI,OAAOZ,QAAQY,SAAS,CAAA;oBACxBpB,MAAMkB,QAAQvB,MAAMK,QAAQ,CAAA;oBAC5BkB,SAASA,QAAQK;;gBAGnB,OAAO;oBACLC,YAAYhB,QAAQgB,cAAU;oBAC9BC,SAASjB,QAAQiB,YAAW;oBAC5BH;oBACAH;;cAYaO,CAAgBlB;YAC7B,KAAKG,MACH,QAAO;YAGT,OAAMa,YAAEA,YAAUC,SAAEA,SAAOH,IAAEA,IAAEH,UAAEA,YAAaR,MAExCgB,oBA9GIpB,QACZe,IACAM,MACAJ;gBAEA;oBACE,IAAc,MAAVA,YACF,OAAO;wBAAEK,WAAU;;oBAGrB,IAAc,IAAVL,YAAgC;wBAClC,MAAMD,eAAe1C,WAAWyC,IAAIM;wBACpC,KAAe,MAAXL,QACF,OAAO;4BAAEM,WAAU;;wBAErB,IAAsB,mBAAXN,QACT,OAAO;4BAAEM,WAAU;4BAAOC,YAAY;gCAAElC,MAAM2B;;;wBAEhD,IAAIA,UAA4B,mBAAXA,QACnB,OAAO;4BAAEM,WAAU;4BAAOC,YAAYP;;AAE1C;oBAEA,IAAc,IAAVC,YAA+B;wBACjC,MAAMX,cAAcS,GAAGJ,QAAQI,GAAGJ,QAAQa,SAAS,IAC7CR,eAAeV,YAAYZ,YAAYqB;wBAC7C,KAAe,MAAXC,QACF,OAAO;4BAAEM,WAAU;;wBAErB,IAAsB,mBAAXN,QACT,OAAO;4BAAEM,WAAU;4BAAOC,YAAY;gCAAElC,MAAM2B;;;wBAEhD,IAAIA,UAA4B,mBAAXA,QACnB,OAAO;4BAAEM,WAAU;4BAAOC,YAAYP;;AAE1C;oBAEA,OAAO;wBAAEM,WAAU;;AACrB,kBAAE,OAAOG;oBAEP,OADA/C,QAAQ+C,QACD;wBAAEH,WAAU;;AACrB;cAqE4BI,CAAMX,IAAI/B,SAASiC;YAC7C,KAAKG,YAAYE,UAEf,SAAIF,YAAYG,oBACDxB,SAASqB,YAAYG,YAAYrB,gBAAgB;YAOlE,MAAMyB,UAAU,MAAMf;YAmBtB,IAlBIM,UACFU,OAAOC,SAASX,QAAQS,WAExBC,OAAOC,SAASC,OAAOlB;YAGzB5B,UAAU+B,IACNG,WACEpC,QAAQ0C,SAAS,IACnB1C,QAAQA,QAAQ0C,SAAS,KAAKT,KAKhCjC,QAAQgB,KAAKiB;YAIXhC,cAAcgC,GAAGJ,QAAQa,SAAS,GAAG;gBACvC,MAAMpC,QAAQ2B,GAAGJ,QAAQI,GAAGJ,QAAQa,SAAS;gBAC7C,IAAIpC,MAAMS,WAAW;oBACnB,MAAMkC,gBAAgB3C,MAAMS;oBAC5Bd,WAAWiD,YAAY,IACvBjD,WAAWkD,YAAYF;AACzB;AACF;YAGA,OADAG,kBAAkBnB,IAAIjC,QAAQA,QAAQ0C,SAAS,MAAM,QAC9C;AACT,UAAE,OAAOC;YAEP,OADA/C,QAAQ+C,SACD;AACT;OAGIS,oBAAoBlC,OAAOe,IAAkBM;QACjD,MAAMf,cAAcS,GAAGJ,QAAQI,GAAGJ,QAAQa,SAAS;cAC7ClB,YAAYX,MAAMoB,WAClBvC,UAAUuC,IAAIM;OAMhBc,oBAAqBC;QACzB,IAAmB,mBAARA,KACT,OAAOA;QAGT,OAAO/C,MAAMgD,eAAeD,IAAIE,MAAM;QACtC,OAAO;YACLjD;YACAwB,OAAOwB,cAAcE,WAAWF,oBAAeG;;;IAKnDZ,OAAOa,iBAAiB,cAAc;QACpC,MAAMX,OAAOF,OAAOC,SAASC,KAAKY,MAAM,KACjCrD,QAAQyC,KAAKQ,MAAM,MACpBK,iBAAiBrD,cAAcD;QACrC,IAAIL,WAAWA,QAAQK,SAASsD,gBAC9B;QAGF,MAAMhC,UAAUH,MAAMmC;QACtB,KAAKhC,SAEH,YADAlC,WAAWkE;QAGb,MAAMN,cAAcT,OAAOC,SAASC,KAAKY,MAAM,GAAGJ,MAAM,KAAK,IACvDvB,KAAmB;YACvB1B,MAAMsD;YACNnD,MAAMmB,QAAQvB,MAAMI;YACpBiB,QAAQE,QAAQF;YAChBI,OAAOwB,cAAcE,WAAWF,eAAe,CAAA;YAC/C5C,MAAMkB,QAAQvB,MAAMK,QAAQ,CAAA;YAC5BkB,SAASA,QAAQK;;QAOnB,IAHAhC,UAAU+B,IACVjC,QAAQgB,KAAKiB,KAEThC,cAAcgC,GAAGJ,QAAQa,SAAS,GAAG;YACvC,MAAMpC,QAAQ2B,GAAGJ,QAAQI,GAAGJ,QAAQa,SAAS;YAC7C,IAAIpC,MAAMS,WAAW;gBACnB,MAAMkC,UAAU3C,MAAMS;gBAClBkC,mBAAmBa,UACrBb,QAAQc,KAAMC;oBACZ/D,WAAYiD,YAAY,IACxBjD,WAAYkD,YAAYa;sBAG1B/D,WAAWiD,YAAY,IACvBjD,WAAWkD,YAAYF;AAE3B;AACF;QAEAG,kBAAkBnB,IAAIjC,QAAQA,QAAQ0C,SAAS,MAAM;;IAIvD,MAAMuB,WAAmB;QACvB,WAAI/D;YACF,OAAOA;AACT;QAEA,WAAIF;YACF,OAAOA,QAAQkE;AACjB;QAEA,aAAA5E,CAAcH;YACZc,aAAad;AACf;QAEA,IAAA6B,CAAK+B;YACH,MAAM5B,UAAUkC,kBAAkBN;YAClC,OAAO9B,SAASE;AAClB;QAEA,UAAAgD,CAAWpB;YACT,MAAM5B,UAAUkC,kBAAkBN;YAClC,OAAO9B,SAAS;mBAAKE;gBAASgB,YAAU;;AAC1C;QAEA,OAAAC,CAAQW;YACN,MAAM5B,UAAUkC,kBAAkBN;YAClC,OAAO9B,SAAS;mBAAKE;gBAASiB,UAAS;;AACzC;QAEA,IAAAgC;YACEtB,OAAO9C,QAAQoE;AACjB;QAEA,OAAAC;YACEvB,OAAO9C,QAAQqE;AACjB;;IAEFlE,UAAUJ,WAAW;IACrB,OAAM0B,YAAEA,YAAUC,OAAEA,SC5SO,CAAC5B;QAC5B,MAAMwE,UAAuC,CAAA;QAE7C,KAAK,IAAIC,IAAI,GAAGA,IAAIzE,OAAO4C,QAAQ6B,KAAK;YACtC,MAAMjE,QAAQR,OAAOyE;YACrB,SAAmBb,MAAfpD,MAAMI,MAAoB;gBAC5B,IAAIJ,MAAMI,QAAQ4D,SAChB,MAAA,IAAAjD,MAAA,wDAA0Cf,MAAMI;gBAElD4D,QAAQhE,MAAMI,QAAQJ;AACxB;AACF;QAKA,MA0CMkE,kBAAmBlE;YACvB,MAAMuB,UAAyB,EAACvB,SAC1BC,OAAOD,MAAMC;YAGnB,KAAK,IAAIgE,IAAI,GAAGA,IAAIzE,OAAO4C,QAAQ6B,KAAK;gBACtC,MAAME,IAAI3E,OAAOyE;gBACbE,MAAMnE,SAASC,KAAKmE,WAAWD,EAAElE,SAASA,SAASkE,EAAElE,QACvDsB,QAAQb,KAAKyD;AAEjB;YAEA,OAAO5C,QAAQ8C;;QAGjB,OAAO;YACLlD,YA1DkBf,QACX4D,QAAQ5D,SAAS;YA0DxBgB,OApDanB;gBACb,MAAMsD,iBAAiBrD,cAAcD;gBAGrC,KAAK,MAAMD,SAASR,QAClB,IAAIQ,MAAMC,SAASsD,gBACjB,OAAO;oBACLvD;oBACAqB,QAAQ,CAAA;oBACRO,QAAQsC,gBAAgBlE;;gBAM9B,KAAK,MAAMA,SAASR,QAClB,IAAIQ,MAAMC,KAAKqE,SAAS,MAAM;oBAC5B,MAAMjD,SAASkD,cAAcvE,MAAMC,MAAMsD;oBACzC,IAAIlC,QACF,OAAO;wBACLrB;wBACAqB;wBACAO,QAAQsC,gBAAgBlE;;AAG9B;gBAGF,OAAO;;;MDyPqBwE,CAAchF,SACtCiF,cAAcjC,OAAOC,SAASC,KAAKY,MAAM;IAK/C,OAJImB,eACFd,SAASjD,KAAK+D,cAGTd;;;"}
1
+ {"version":3,"file":"index.mjs","sources":["../src/core/kt-router.ts","../src/core/index.ts","../src/core/matcher.ts"],"sourcesContent":["import type { JSX } from '@ktjs/core/jsx-runtime';\nimport type { Router } from '../types/router.js';\n\n/**\n * Create a router view container that automatically renders route components\n */\nexport function KTRouter({ router }: { router: Router }): JSX.Element {\n const view = document.createElement('kt-router-view');\n router.setRouterView(view);\n return view as JSX.Element;\n}\n","import { $emptyFn, buildQuery, normalizePath, parseQuery, emplaceParams } from '@ktjs/shared';\n\nimport type { Router, RouterConfig, RouteContext, NavOptions, RawRouteConfig, RouteConfig } from '../types/router.js';\nimport { GuardLevel } from './consts.js';\nimport { createMatcher } from './matcher.js';\n\n/**\n * Create a new router instance\n */\nexport const createRouter = ({\n beforeEach = $emptyFn,\n afterEach = $emptyFn,\n onNotFound = $emptyFn,\n onError = $emptyFn,\n prefix = '',\n routes: rawRoutes,\n}: RouterConfig): Router => {\n // # private values\n const routes: RouteConfig[] = [];\n const matchedMap = new Map<RouteConfig, RouteConfig[]>();\n const history: RouteContext[] = [];\n const prefixPath = prefix ? normalizePath(prefix) : '';\n let routerView: HTMLElement | null = null;\n let current: RouteContext | null = null;\n let ignoreNextHashChange = false;\n\n // # methods\n const normalize = (rawRoutes: RawRouteConfig[], parentPath: string, parentMatched: RouteConfig[]): RouteConfig[] =>\n rawRoutes.map((route) => {\n const path = normalizePath(parentPath, route.path);\n const normalized: RouteConfig = {\n path: prefixPath ? normalizePath(prefixPath, path) : path,\n name: route.name,\n meta: route.meta ?? {},\n beforeEnter: route.beforeEnter ?? $emptyFn,\n after: route.after ?? $emptyFn,\n children: [],\n component: route.component,\n };\n const matched = parentMatched.concat(normalized);\n matchedMap.set(normalized, matched);\n normalized.children = route.children ? normalize(route.children, path, matched) : [];\n\n // directly push the normalized route to the list\n // avoid flatten them again\n routes.push(normalized);\n return normalized;\n });\n\n normalize(rawRoutes, '/', []);\n\n const guard = async (\n to: RouteContext,\n from: RouteContext | null,\n guardLevel: GuardLevel,\n ): Promise<{ continue: boolean; redirectTo?: NavOptions }> => {\n try {\n if (guardLevel === GuardLevel.None) {\n return { continue: true };\n }\n\n if (guardLevel & GuardLevel.Global) {\n const result = await beforeEach(to, from);\n if (result === false) {\n return { continue: false };\n }\n if (typeof result === 'string') {\n return { continue: false, redirectTo: { path: result } };\n }\n if (result && typeof result === 'object') {\n return { continue: false, redirectTo: result };\n }\n }\n\n if (guardLevel & GuardLevel.Route) {\n const targetRoute = to.matched[to.matched.length - 1];\n const result = await targetRoute.beforeEnter(to);\n if (result === false) {\n return { continue: false };\n }\n if (typeof result === 'string') {\n return { continue: false, redirectTo: { path: result } };\n }\n if (result && typeof result === 'object') {\n return { continue: false, redirectTo: result };\n }\n }\n\n return { continue: true };\n } catch (error) {\n onError(error as Error);\n return { continue: false };\n }\n };\n\n const navigatePrepare = (options: NavOptions) => {\n // Resolve target route\n let targetPath: string;\n\n if (options.name) {\n const targetRoute = findByName(options.name);\n if (!targetRoute) {\n $throw(`Route not found: ${options.name}`);\n }\n targetPath = targetRoute.path;\n } else if (options.path) {\n targetPath = normalizePath(options.path);\n if (prefixPath && targetPath !== prefixPath && !targetPath.startsWith(prefixPath + '/')) {\n targetPath = normalizePath(prefixPath, targetPath);\n }\n } else {\n $throw(`Either path or name must be provided`);\n }\n\n // Substitute params\n if (options.params) {\n targetPath = emplaceParams(targetPath, options.params);\n }\n\n // Match final path\n const matched = match(targetPath);\n if (!matched) {\n onNotFound(targetPath);\n return null;\n }\n\n // Build route context\n const queryString = options.query ? buildQuery(options.query) : '';\n const fullPath = targetPath + queryString;\n\n const to: RouteContext = {\n path: targetPath,\n name: matched.route.name ?? '',\n params: { ...matched.params, ...(options.params ?? {}) },\n query: options.query ?? {},\n meta: matched.route.meta ?? {},\n matched: matched.result,\n };\n\n return {\n guardLevel: options.guardLevel ?? GuardLevel.Default,\n replace: options.replace ?? false,\n to,\n fullPath,\n };\n };\n\n const commit = async (\n to: RouteContext,\n fullPath: string,\n replaceHistory: boolean,\n hashMode: 'push' | 'replace' | null,\n ): Promise<void> => {\n if (hashMode && window.location.hash.slice(1) !== fullPath) {\n ignoreNextHashChange = true;\n const hashUrl = '#' + fullPath;\n if (hashMode === 'replace') {\n window.location.replace(hashUrl);\n } else {\n window.location.hash = fullPath;\n }\n }\n\n const from = current;\n current = to;\n if (replaceHistory) {\n if (history.length > 0) {\n history[history.length - 1] = to;\n } else {\n history.push(to);\n }\n } else {\n history.push(to);\n }\n\n if (routerView && to.matched.length > 0) {\n const route = to.matched[to.matched.length - 1];\n const element = await route.component();\n routerView.innerHTML = '';\n routerView.appendChild(element);\n }\n\n void executeAfterHooks(to, from).catch((error) => {\n onError(error as Error, to.matched[to.matched.length - 1]);\n });\n };\n\n const navigate = async (options: NavOptions, redirectCount = 0): Promise<boolean> => {\n try {\n // Prevent infinite redirect loop\n if (redirectCount > 10) {\n onError(new Error('Maximum redirect count exceeded'));\n return false;\n }\n\n const prep = navigatePrepare(options);\n if (!prep) {\n return false;\n }\n\n const { guardLevel, replace, to, fullPath } = prep;\n\n const guardResult = await guard(to, current, guardLevel);\n if (!guardResult.continue) {\n // Check if there's a redirect\n if (guardResult.redirectTo) {\n return await navigate(guardResult.redirectTo, redirectCount + 1);\n }\n return false;\n }\n\n // ---- Guards passed ----\n await commit(to, fullPath, replace, replace ? 'replace' : 'push');\n return true;\n } catch (error) {\n onError(error as Error);\n return false;\n }\n };\n\n const executeAfterHooks = async (to: RouteContext, from: RouteContext | null): Promise<void> => {\n const targetRoute = to.matched[to.matched.length - 1];\n await targetRoute.after(to);\n await afterEach(to, from);\n };\n\n /**\n * Normalize navigation argument\n */\n const normalizeLocation = (loc: string | NavOptions): NavOptions => {\n if (typeof loc !== 'string') {\n return loc;\n }\n\n const [path, queryString] = loc.split('?');\n return {\n path,\n query: queryString ? parseQuery(queryString) : undefined,\n };\n };\n\n const { findByName, match } = createMatcher(routes, matchedMap);\n\n const handleHashChange = async () => {\n if (ignoreNextHashChange) {\n ignoreNextHashChange = false;\n return;\n }\n\n const hash = window.location.hash.slice(1);\n if (!hash) {\n return;\n }\n\n const prep = navigatePrepare(normalizeLocation(hash));\n if (!prep) {\n return;\n }\n\n const { guardLevel, to, fullPath } = prep;\n const guardResult = await guard(to, current, guardLevel);\n if (!guardResult.continue) {\n if (guardResult.redirectTo) {\n await navigate({ ...guardResult.redirectTo, replace: true });\n return;\n }\n\n ignoreNextHashChange = true;\n if (current) {\n window.location.replace('#' + current.path + buildQuery(current.query));\n } else {\n window.location.replace(window.location.pathname + window.location.search);\n }\n return;\n }\n\n await commit(to, fullPath, false, fullPath === hash ? null : 'replace');\n };\n\n // # register events\n window.addEventListener('hashchange', () => {\n void handleHashChange();\n });\n\n // # initialize\n const instance: Router = {\n get current() {\n return current;\n },\n\n get history() {\n return history.concat();\n },\n\n setRouterView(view: HTMLElement) {\n routerView = view;\n },\n\n push(location: string | NavOptions): boolean | Promise<boolean> {\n const options = normalizeLocation(location);\n return navigate(options);\n },\n\n silentPush(location: string | NavOptions): boolean | Promise<boolean> {\n const options = normalizeLocation(location);\n return navigate({ ...options, guardLevel: GuardLevel.Route });\n },\n\n replace(location: string | NavOptions): boolean | Promise<boolean> {\n const options = normalizeLocation(location);\n return navigate({ ...options, replace: true });\n },\n\n back() {\n window.history.back();\n },\n\n forward() {\n window.history.forward();\n },\n };\n const currentHash = window.location.hash.slice(1);\n if (currentHash) {\n void handleHashChange();\n }\n\n return instance;\n};\n\nexport { GuardLevel };\nexport { KTRouter } from './kt-router.js';\n","import type { RouteConfig, RouteMatch } from '../types/router.js';\nimport { extractParams, normalizePath } from '@ktjs/shared';\n\n/**\n * Route matcher for finding matching routes and extracting params\n */\nexport const createMatcher = (routes: RouteConfig[], matchedMap: Map<RouteConfig, RouteConfig[]>) => {\n const nameMap: Record<string, RouteConfig> = {};\n\n for (let i = 0; i < routes.length; i++) {\n const route = routes[i];\n if (route.name !== undefined) {\n if (route.name in nameMap) {\n $throw(`Duplicate route name detected: '${route.name}'`);\n }\n nameMap[route.name] = route;\n }\n }\n\n /**\n * Find route by name\n */\n const findByName = (name: string): RouteConfig | null => {\n return nameMap[name] ?? null;\n };\n\n /**\n * Match path against all routes\n */\n const match = (path: string): RouteMatch | null => {\n const normalizedPath = normalizePath(path);\n\n // Try exact match first\n for (const route of routes) {\n if (route.path === normalizedPath) {\n return {\n route,\n params: {},\n result: matchedMap.get(route) ?? [route],\n };\n }\n }\n\n // Try dynamic routes\n for (const route of routes) {\n if (route.path.includes(':')) {\n const params = extractParams(route.path, normalizedPath);\n if (params) {\n return {\n route,\n params,\n result: matchedMap.get(route) ?? [route],\n };\n }\n }\n }\n\n return null;\n };\n\n return {\n findByName,\n match,\n };\n};\n"],"names":["KTRouter","router","view","document","createElement","setRouterView","createRouter","beforeEach","$emptyFn","afterEach","onNotFound","onError","prefix","routes","rawRoutes","matchedMap","Map","history","prefixPath","normalizePath","routerView","current","ignoreNextHashChange","normalize","parentPath","parentMatched","map","route","path","normalized","name","meta","beforeEnter","after","children","component","matched","concat","set","push","guard","async","to","from","guardLevel","continue","result","redirectTo","targetRoute","length","error","navigatePrepare","options","targetPath","findByName","Error","startsWith","params","emplaceParams","match","fullPath","query","buildQuery","replace","commit","replaceHistory","hashMode","window","location","hash","slice","hashUrl","element","innerHTML","appendChild","executeAfterHooks","catch","navigate","redirectCount","prep","guardResult","normalizeLocation","loc","queryString","split","parseQuery","undefined","nameMap","i","normalizedPath","get","includes","extractParams","createMatcher","handleHashChange","pathname","search","addEventListener","instance","silentPush","back","forward"],"mappings":";;AAMM,SAAUA,UAASC,QAAEA;IACzB,MAAMC,OAAOC,SAASC,cAAc;IAEpC,OADAH,OAAOI,cAAcH,OACdA;AACT;;ACDO,MAAMI,eAAe,EAC1BC,yBAAaC,UACbC,uBAAYD,UACZE,yBAAaF,UACbG,mBAAUH,UACVI,iBAAS,IACTC,QAAQC;IAGR,MAAMD,SAAwB,IACxBE,aAAa,IAAIC,KACjBC,UAA0B,IAC1BC,aAAaN,SAASO,cAAcP,UAAU;IACpD,IAAIQ,aAAiC,MACjCC,UAA+B,MAC/BC,wBAAuB;IAG3B,MAAMC,YAAY,CAACT,WAA6BU,YAAoBC,kBAClEX,UAAUY,IAAKC;QACb,MAAMC,OAAOT,cAAcK,YAAYG,MAAMC,OACvCC,aAA0B;YAC9BD,MAAMV,aAAaC,cAAcD,YAAYU,QAAQA;YACrDE,MAAMH,MAAMG;YACZC,MAAMJ,MAAMI,QAAQ,CAAA;YACpBC,aAAaL,MAAMK,eAAexB;YAClCyB,OAAON,MAAMM,SAASzB;YACtB0B,UAAU;YACVC,WAAWR,MAAMQ;WAEbC,UAAUX,cAAcY,OAAOR;QAOrC,OANAd,WAAWuB,IAAIT,YAAYO,UAC3BP,WAAWK,WAAWP,MAAMO,WAAWX,UAAUI,MAAMO,UAAUN,MAAMQ,WAAW;QAIlFvB,OAAO0B,KAAKV,aACLA;;IAGXN,UAAUT,WAAW,KAAK;IAE1B,MAAM0B,QAAQC,OACZC,IACAC,MACAC;QAEA;YACE,IAAc,MAAVA,YACF,OAAO;gBAAEC,WAAU;;YAGrB,IAAc,IAAVD,YAAgC;gBAClC,MAAME,eAAevC,WAAWmC,IAAIC;gBACpC,KAAe,MAAXG,QACF,OAAO;oBAAED,WAAU;;gBAErB,IAAsB,mBAAXC,QACT,OAAO;oBAAED,WAAU;oBAAOE,YAAY;wBAAEnB,MAAMkB;;;gBAEhD,IAAIA,UAA4B,mBAAXA,QACnB,OAAO;oBAAED,WAAU;oBAAOE,YAAYD;;AAE1C;YAEA,IAAc,IAAVF,YAA+B;gBACjC,MAAMI,cAAcN,GAAGN,QAAQM,GAAGN,QAAQa,SAAS,IAC7CH,eAAeE,YAAYhB,YAAYU;gBAC7C,KAAe,MAAXI,QACF,OAAO;oBAAED,WAAU;;gBAErB,IAAsB,mBAAXC,QACT,OAAO;oBAAED,WAAU;oBAAOE,YAAY;wBAAEnB,MAAMkB;;;gBAEhD,IAAIA,UAA4B,mBAAXA,QACnB,OAAO;oBAAED,WAAU;oBAAOE,YAAYD;;AAE1C;YAEA,OAAO;gBAAED,WAAU;;AACrB,UAAE,OAAOK;YAEP,OADAvC,QAAQuC,QACD;gBAAEL,WAAU;;AACrB;OAGIM,kBAAmBC;QAEvB,IAAIC;QAEJ,IAAID,QAAQtB,MAAM;YAChB,MAAMkB,cAAcM,WAAWF,QAAQtB;YACvC,KAAKkB,aACH,MAAA,IAAAO,MAAA,yCAA2BH,QAAQtB;YAErCuB,aAAaL,YAAYpB;AAC3B,eAAO;YAAA,KAAIwB,QAAQxB,MAMjB;YALAyB,aAAalC,cAAciC,QAAQxB,OAC/BV,cAAcmC,eAAenC,eAAemC,WAAWG,WAAWtC,aAAa,SACjFmC,aAAalC,cAAcD,YAAYmC;AAI3C;QAGID,QAAQK,WACVJ,aAAaK,cAAcL,YAAYD,QAAQK;QAIjD,MAAMrB,UAAUuB,MAAMN;QACtB,KAAKjB,SAEH,OADA1B,WAAW2C,aACJ;QAIT,MACMO,WAAWP,cADGD,QAAQS,QAAQC,WAAWV,QAAQS,SAAS,KAG1DnB,KAAmB;YACvBd,MAAMyB;YACNvB,MAAMM,QAAQT,MAAMG,QAAQ;YAC5B2B,QAAQ;mBAAKrB,QAAQqB;mBAAYL,QAAQK,UAAU,CAAA;;YACnDI,OAAOT,QAAQS,SAAS,CAAA;YACxB9B,MAAMK,QAAQT,MAAMI,QAAQ,CAAA;YAC5BK,SAASA,QAAQU;;QAGnB,OAAO;YACLF,YAAYQ,QAAQR,cAAU;YAC9BmB,SAASX,QAAQW,YAAW;YAC5BrB;YACAkB;;OAIEI,SAASvB,OACbC,IACAkB,UACAK,gBACAC;QAEA,IAAIA,YAAYC,OAAOC,SAASC,KAAKC,MAAM,OAAOV,UAAU;YAC1DtC,wBAAuB;YACvB,MAAMiD,UAAU,MAAMX;YACL,cAAbM,WACFC,OAAOC,SAASL,QAAQQ,WAExBJ,OAAOC,SAASC,OAAOT;AAE3B;QAEA,MAAMjB,OAAOtB;QAYb,IAXAA,UAAUqB,IACNuB,kBACEhD,QAAQgC,SAAS,IACnBhC,QAAQA,QAAQgC,SAAS,KAAKP,KAKhCzB,QAAQsB,KAAKG;QAGXtB,cAAcsB,GAAGN,QAAQa,SAAS,GAAG;YACvC,MAAMtB,QAAQe,GAAGN,QAAQM,GAAGN,QAAQa,SAAS,IACvCuB,gBAAgB7C,MAAMQ;YAC5Bf,WAAWqD,YAAY,IACvBrD,WAAWsD,YAAYF;AACzB;QAEKG,kBAAkBjC,IAAIC,MAAMiC,MAAO1B;YACtCvC,QAAQuC,OAAgBR,GAAGN,QAAQM,GAAGN,QAAQa,SAAS;;OAIrD4B,WAAWpC,OAAOW,SAAqB0B,gBAAgB;QAC3D;YAEE,IAAIA,gBAAgB,IAElB,OADAnE,QAAQ,IAAI4C,MAAM;aACX;YAGT,MAAMwB,OAAO5B,gBAAgBC;YAC7B,KAAK2B,MACH,QAAO;YAGT,OAAMnC,YAAEA,YAAUmB,SAAEA,SAAOrB,IAAEA,IAAEkB,UAAEA,YAAamB,MAExCC,oBAAoBxC,MAAME,IAAIrB,SAASuB;YAC7C,OAAKoC,YAAYnC,kBASXmB,OAAOtB,IAAIkB,UAAUG,SAASA,UAAU,YAAY;aACnD,OARDiB,YAAYjC,oBACD8B,SAASG,YAAYjC,YAAY+B,gBAAgB;AAQpE,UAAE,OAAO5B;YAEP,OADAvC,QAAQuC,SACD;AACT;OAGIyB,oBAAoBlC,OAAOC,IAAkBC;QACjD,MAAMK,cAAcN,GAAGN,QAAQM,GAAGN,QAAQa,SAAS;cAC7CD,YAAYf,MAAMS,WAClBjC,UAAUiC,IAAIC;OAMhBsC,oBAAqBC;QACzB,IAAmB,mBAARA,KACT,OAAOA;QAGT,OAAOtD,MAAMuD,eAAeD,IAAIE,MAAM;QACtC,OAAO;YACLxD;YACAiC,OAAOsB,cAAcE,WAAWF,oBAAeG;;QAI7ChC,YAAEA,YAAUK,OAAEA,SC3OO,EAAC9C,QAAuBE;QACnD,MAAMwE,UAAuC,CAAA;QAE7C,KAAK,IAAIC,IAAI,GAAGA,IAAI3E,OAAOoC,QAAQuC,KAAK;YACtC,MAAM7D,QAAQd,OAAO2E;YACrB,SAAmBF,MAAf3D,MAAMG,MAAoB;gBAC5B,IAAIH,MAAMG,QAAQyD,SAChB,MAAA,IAAAhC,MAAA,wDAA0C5B,MAAMG;gBAElDyD,QAAQ5D,MAAMG,QAAQH;AACxB;AACF;QA2CA,OAAO;YACL2B,YAvCkBxB,QACXyD,QAAQzD,SAAS;YAuCxB6B,OAjCa/B;gBACb,MAAM6D,iBAAiBtE,cAAcS;gBAGrC,KAAK,MAAMD,SAASd,QAClB,IAAIc,MAAMC,SAAS6D,gBACjB,OAAO;oBACL9D;oBACA8B,QAAQ,CAAA;oBACRX,QAAQ/B,WAAW2E,IAAI/D,UAAU,EAACA;;gBAMxC,KAAK,MAAMA,SAASd,QAClB,IAAIc,MAAMC,KAAK+D,SAAS,MAAM;oBAC5B,MAAMlC,SAASmC,cAAcjE,MAAMC,MAAM6D;oBACzC,IAAIhC,QACF,OAAO;wBACL9B;wBACA8B;wBACAX,QAAQ/B,WAAW2E,IAAI/D,UAAU,EAACA;;AAGxC;gBAGF,OAAO;;;MDwLqBkE,CAAchF,QAAQE,aAE9C+E,mBAAmBrD;QACvB,IAAInB,sBAEF,aADAA,wBAAuB;QAIzB,MAAM+C,OAAOF,OAAOC,SAASC,KAAKC,MAAM;QACxC,KAAKD,MACH;QAGF,MAAMU,OAAO5B,gBAAgB8B,kBAAkBZ;QAC/C,KAAKU,MACH;QAGF,OAAMnC,YAAEA,YAAUF,IAAEA,IAAEkB,UAAEA,YAAamB,MAC/BC,oBAAoBxC,MAAME,IAAIrB,SAASuB;QAC7C,KAAKoC,YAAYnC,UACf,OAAImC,YAAYjC,wBACR8B,SAAS;eAAKG,YAAYjC;YAAYgB,UAAS;cAIvDzC,wBAAuB,SACnBD,UACF8C,OAAOC,SAASL,QAAQ,MAAM1C,QAAQO,OAAOkC,WAAWzC,QAAQwC,UAEhEM,OAAOC,SAASL,QAAQI,OAAOC,SAAS2B,WAAW5B,OAAOC,SAAS4B;cAKjEhC,OAAOtB,IAAIkB,WAAU,GAAOA,aAAaS,OAAO,OAAO;;IAI/DF,OAAO8B,iBAAiB,cAAc;QAC/BH;;IAIP,MAAMI,WAAmB;QACvB,WAAI7E;YACF,OAAOA;AACT;QAEA,WAAIJ;YACF,OAAOA,QAAQoB;AACjB;QAEA,aAAAhC,CAAcH;YACZkB,aAAalB;AACf;QAEA,IAAAqC,CAAK6B;YACH,MAAMhB,UAAU6B,kBAAkBb;YAClC,OAAOS,SAASzB;AAClB;QAEA,UAAA+C,CAAW/B;YACT,MAAMhB,UAAU6B,kBAAkBb;YAClC,OAAOS,SAAS;mBAAKzB;gBAASR,YAAU;;AAC1C;QAEA,OAAAmB,CAAQK;YACN,MAAMhB,UAAU6B,kBAAkBb;YAClC,OAAOS,SAAS;mBAAKzB;gBAASW,UAAS;;AACzC;QAEA,IAAAqC;YACEjC,OAAOlD,QAAQmF;AACjB;QAEA,OAAAC;YACElC,OAAOlD,QAAQoF;AACjB;;IAOF,OALoBlC,OAAOC,SAASC,KAAKC,MAAM,MAExCwB,oBAGAI;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ktjs/router",
3
- "version": "0.33.3",
3
+ "version": "0.33.4",
4
4
  "description": "Router for kt.js - client-side routing with navigation guards",
5
5
  "description_zh": "kt.js 的路由库,支持前端路由与导航守卫。",
6
6
  "type": "module",
@@ -36,8 +36,8 @@
36
36
  "@ktjs/shared": "^*"
37
37
  },
38
38
  "scripts": {
39
- "build": "rollup -c rollup.config.mjs",
40
- "dev": "rollup -c rollup.config.mjs -w",
41
- "test": "vitest run"
39
+ "build": "node -e \"process.env.CURRENT_PKG_PATH=process.cwd(); require('node:child_process').execSync('rollup -c ../../configs/rollup.config.base.js', { stdio: 'inherit' })\"",
40
+ "dev": "node -e \"process.env.CURRENT_PKG_PATH=process.cwd(); require('node:child_process').execSync('rollup -c ../../configs/rollup.config.base.js -w', { stdio: 'inherit' })\"",
41
+ "test": "node -e \"process.env.CURRENT_PKG_PATH=process.cwd(); require('node:child_process').execSync('vitest run . --config ../../configs/vitest.config.ts', { stdio: 'inherit' })\""
42
42
  }
43
43
  }