@esmx/router 3.0.0-rc.12

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.
Files changed (61) hide show
  1. package/dist/history/abstract.d.ts +29 -0
  2. package/dist/history/abstract.mjs +107 -0
  3. package/dist/history/base.d.ts +79 -0
  4. package/dist/history/base.mjs +275 -0
  5. package/dist/history/html.d.ts +22 -0
  6. package/dist/history/html.mjs +181 -0
  7. package/dist/history/index.d.ts +7 -0
  8. package/dist/history/index.mjs +16 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.mjs +3 -0
  11. package/dist/matcher/create-matcher.d.ts +5 -0
  12. package/dist/matcher/create-matcher.mjs +218 -0
  13. package/dist/matcher/create-matcher.spec.d.ts +1 -0
  14. package/dist/matcher/create-matcher.spec.mjs +0 -0
  15. package/dist/matcher/index.d.ts +1 -0
  16. package/dist/matcher/index.mjs +1 -0
  17. package/dist/router.d.ts +111 -0
  18. package/dist/router.mjs +399 -0
  19. package/dist/task-pipe/index.d.ts +1 -0
  20. package/dist/task-pipe/index.mjs +1 -0
  21. package/dist/task-pipe/task.d.ts +30 -0
  22. package/dist/task-pipe/task.mjs +66 -0
  23. package/dist/utils/bom.d.ts +5 -0
  24. package/dist/utils/bom.mjs +10 -0
  25. package/dist/utils/encoding.d.ts +48 -0
  26. package/dist/utils/encoding.mjs +44 -0
  27. package/dist/utils/guards.d.ts +9 -0
  28. package/dist/utils/guards.mjs +12 -0
  29. package/dist/utils/index.d.ts +7 -0
  30. package/dist/utils/index.mjs +27 -0
  31. package/dist/utils/path.d.ts +60 -0
  32. package/dist/utils/path.mjs +264 -0
  33. package/dist/utils/path.spec.d.ts +1 -0
  34. package/dist/utils/path.spec.mjs +30 -0
  35. package/dist/utils/scroll.d.ts +25 -0
  36. package/dist/utils/scroll.mjs +59 -0
  37. package/dist/utils/utils.d.ts +16 -0
  38. package/dist/utils/utils.mjs +11 -0
  39. package/dist/utils/warn.d.ts +2 -0
  40. package/dist/utils/warn.mjs +12 -0
  41. package/package.json +66 -0
  42. package/src/history/abstract.ts +149 -0
  43. package/src/history/base.ts +408 -0
  44. package/src/history/html.ts +231 -0
  45. package/src/history/index.ts +20 -0
  46. package/src/index.ts +3 -0
  47. package/src/matcher/create-matcher.spec.ts +3 -0
  48. package/src/matcher/create-matcher.ts +293 -0
  49. package/src/matcher/index.ts +1 -0
  50. package/src/router.ts +521 -0
  51. package/src/task-pipe/index.ts +1 -0
  52. package/src/task-pipe/task.ts +97 -0
  53. package/src/utils/bom.ts +14 -0
  54. package/src/utils/encoding.ts +153 -0
  55. package/src/utils/guards.ts +25 -0
  56. package/src/utils/index.ts +27 -0
  57. package/src/utils/path.spec.ts +44 -0
  58. package/src/utils/path.ts +397 -0
  59. package/src/utils/scroll.ts +120 -0
  60. package/src/utils/utils.ts +30 -0
  61. package/src/utils/warn.ts +13 -0
@@ -0,0 +1,29 @@
1
+ import type { Route, RouteRecord, RouterInstance, RouterRawLocation } from '../types';
2
+ import { BaseRouterHistory } from './base';
3
+ export declare class AbstractHistory extends BaseRouterHistory {
4
+ index: number;
5
+ stack: RouteRecord[];
6
+ constructor(router: RouterInstance);
7
+ init({ replace }?: {
8
+ replace?: boolean;
9
+ }): Promise<void>;
10
+ destroy(): void;
11
+ setupListeners(): void;
12
+ isSameHost(location: RouterRawLocation, route: Route): any;
13
+ handleOutside(location: RouterRawLocation, replace?: boolean, isTriggerWithWindow?: boolean): boolean;
14
+ push(location: RouterRawLocation): Promise<void>;
15
+ /**
16
+ * 新开浏览器窗口的方法,在服务端会调用 push 作为替代
17
+ */
18
+ pushWindow(location: RouterRawLocation): Promise<void>;
19
+ replace(location: RouterRawLocation): Promise<void>;
20
+ /**
21
+ * 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
22
+ */
23
+ replaceWindow(location: RouterRawLocation): Promise<void>;
24
+ jump(location: RouterRawLocation, replace?: boolean): Promise<void>;
25
+ private _jump;
26
+ go(delta: number): void;
27
+ forward(): void;
28
+ back(): void;
29
+ }
@@ -0,0 +1,107 @@
1
+ import { isPathWithProtocolOrDomain, normalizeLocation } from "../utils/index.mjs";
2
+ import { BaseRouterHistory } from "./base.mjs";
3
+ export class AbstractHistory extends BaseRouterHistory {
4
+ index;
5
+ stack;
6
+ constructor(router) {
7
+ super(router);
8
+ this.index = -1;
9
+ this.stack = [];
10
+ this.init();
11
+ }
12
+ async init({ replace } = { replace: true }) {
13
+ const { initUrl } = this.router.options;
14
+ if (initUrl !== void 0) {
15
+ if (replace) {
16
+ await this.replace(initUrl);
17
+ } else {
18
+ await this.push(initUrl);
19
+ }
20
+ }
21
+ }
22
+ destroy() {
23
+ }
24
+ // 设置监听函数
25
+ setupListeners() {
26
+ }
27
+ // 服务端判断是否是相同域名
28
+ isSameHost(location, route) {
29
+ const rawLocation = typeof location === "string" ? { path: location } : location;
30
+ if (rawLocation.path === void 0) {
31
+ rawLocation.path = this.current.fullPath;
32
+ }
33
+ const { base } = normalizeLocation(rawLocation, this.router.base);
34
+ return base.includes(route.hostname);
35
+ }
36
+ // 处理外站跳转逻辑
37
+ handleOutside(location, replace = false, isTriggerWithWindow = false) {
38
+ const { flag, route } = isPathWithProtocolOrDomain(location);
39
+ if (!flag) {
40
+ return false;
41
+ }
42
+ const router = this.router;
43
+ const { validateOutside, handleOutside } = router.options;
44
+ const isSameHost = this.isSameHost(location, route);
45
+ if (isSameHost && !(validateOutside == null ? void 0 : validateOutside({ router, location, route }))) {
46
+ return false;
47
+ }
48
+ handleOutside == null ? void 0 : handleOutside({
49
+ router,
50
+ route,
51
+ replace,
52
+ isTriggerWithWindow,
53
+ isSameHost
54
+ });
55
+ return true;
56
+ }
57
+ // 新增路由记录跳转
58
+ async push(location) {
59
+ await this.jump(location, false);
60
+ }
61
+ /**
62
+ * 新开浏览器窗口的方法,在服务端会调用 push 作为替代
63
+ */
64
+ async pushWindow(location) {
65
+ await this._jump(location, false, true);
66
+ }
67
+ // 替换当前路由记录跳转
68
+ async replace(location) {
69
+ await this.jump(location, true);
70
+ }
71
+ /**
72
+ * 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
73
+ */
74
+ async replaceWindow(location) {
75
+ await this._jump(location, true, true);
76
+ }
77
+ // 跳转方法
78
+ async jump(location, replace = false) {
79
+ await this._jump(location, replace);
80
+ }
81
+ async _jump(location, replace = false, isTriggerWithWindow = false) {
82
+ if (this.handleOutside(location, replace, isTriggerWithWindow)) {
83
+ return;
84
+ }
85
+ await this.transitionTo(location, (route) => {
86
+ const index = replace ? this.index : this.index + 1;
87
+ this.stack = this.stack.slice(0, index).concat(route);
88
+ });
89
+ }
90
+ go(delta) {
91
+ const targetIndex = this.index + delta;
92
+ if (targetIndex < 0 || targetIndex >= this.stack.length) {
93
+ return;
94
+ }
95
+ const route = this.stack[targetIndex];
96
+ this.index = targetIndex;
97
+ this.updateRoute(route);
98
+ }
99
+ /* 路由历史记录前进方法 */
100
+ forward() {
101
+ this.go(1);
102
+ }
103
+ /* 路由历史记录后退方法 */
104
+ back() {
105
+ this.go(-1);
106
+ }
107
+ }
@@ -0,0 +1,79 @@
1
+ import { type Tasks } from '../task-pipe';
2
+ import type { RouteRecord, RouterHistory, RouterInstance, RouterRawLocation } from '../types';
3
+ export declare abstract class BaseRouterHistory implements RouterHistory {
4
+ /**
5
+ * 路由类实例
6
+ */
7
+ router: RouterInstance;
8
+ /**
9
+ * 路由是否冻结
10
+ */
11
+ get isFrozen(): any;
12
+ /**
13
+ * 匹配的当前路由
14
+ */
15
+ current: RouteRecord;
16
+ constructor(router: RouterInstance);
17
+ /**
18
+ * 更新当前路由
19
+ */
20
+ updateRoute(route: RouteRecord): void;
21
+ /**
22
+ * 解析路由
23
+ */
24
+ resolve(location: RouterRawLocation): RouteRecord;
25
+ /**
26
+ * 核心跳转方法
27
+ */
28
+ transitionTo(location: RouterRawLocation, onComplete?: (route: RouteRecord) => void): Promise<void>;
29
+ /**
30
+ * TODO 逻辑解耦,抽离到task
31
+ * 重定向方法
32
+ */
33
+ redirectTo(location: RouterRawLocation, from: RouteRecord, onComplete?: (route: RouteRecord) => void): Promise<void>;
34
+ tasks: Tasks | null;
35
+ abortTask(): Promise<void>;
36
+ /**
37
+ * 执行任务
38
+ * 任务分为三部分: 前置守卫(beforeEach、beforeEnter、beforeUpdate、beforeLeave)、加载路由(loadRoute)、后置守卫(afterEach)
39
+ * 根据触发方式不同,执行顺序分别为:
40
+ * 进入路由时: beforeEach -> beforeEnter -> loadRoute -> afterEach
41
+ * 更新路由时: beforeEach -> beforeUpdate -> afterEach
42
+ * 离开路由进入新路由时: beforeLeave -> beforeEach -> beforeEnter -> loadRoute -> afterEach
43
+ * @param from
44
+ * @param to
45
+ */
46
+ runTask(from: RouteRecord, to: RouteRecord, onComplete?: (route: RouteRecord) => void): Promise<void>;
47
+ /**
48
+ * 新开浏览器窗口的方法,在服务端会调用 push 作为替代
49
+ */
50
+ abstract pushWindow(location: RouterRawLocation): void;
51
+ /**
52
+ * 替换当前浏览器窗口的方法,在服务端会调用 replace 作为替代
53
+ */
54
+ abstract replaceWindow(location: RouterRawLocation): void;
55
+ /**
56
+ * 跳转方法,会创建新的历史纪录
57
+ */
58
+ abstract push(location: RouterRawLocation): Promise<void>;
59
+ /**
60
+ * 跳转方法,替换当前历史记录
61
+ */
62
+ abstract replace(location: RouterRawLocation): Promise<void>;
63
+ /**
64
+ * 路由移动到指定历史记录方法
65
+ */
66
+ abstract go(delta: number): void;
67
+ abstract forward(): void;
68
+ abstract back(): void;
69
+ /**
70
+ * 初始化方法
71
+ */
72
+ abstract init(params?: {
73
+ replace?: boolean;
74
+ }): Promise<void>;
75
+ /**
76
+ * 卸载方法
77
+ */
78
+ abstract destroy(): void;
79
+ }
@@ -0,0 +1,275 @@
1
+ import { createTasks } from "../task-pipe/index.mjs";
2
+ import {
3
+ isESModule,
4
+ isEqualRoute,
5
+ isSameRoute,
6
+ normalizeLocation,
7
+ stringifyPath
8
+ } from "../utils/index.mjs";
9
+ function createRouteRecord(route = {}) {
10
+ return {
11
+ base: "",
12
+ path: "/",
13
+ fullPath: "/",
14
+ meta: {},
15
+ matched: [],
16
+ query: {},
17
+ queryArray: {},
18
+ params: {},
19
+ hash: "",
20
+ state: {},
21
+ ...route
22
+ };
23
+ }
24
+ export class BaseRouterHistory {
25
+ /**
26
+ * 路由类实例
27
+ */
28
+ router;
29
+ /**
30
+ * 路由是否冻结
31
+ */
32
+ get isFrozen() {
33
+ return this.router.isFrozen;
34
+ }
35
+ /**
36
+ * 匹配的当前路由
37
+ */
38
+ current = createRouteRecord();
39
+ constructor(router) {
40
+ this.router = router;
41
+ Object.defineProperty(this, "router", {
42
+ enumerable: false
43
+ });
44
+ }
45
+ /**
46
+ * 更新当前路由
47
+ */
48
+ updateRoute(route) {
49
+ this.current = route;
50
+ this.router.updateRoute(route);
51
+ }
52
+ /**
53
+ * 解析路由
54
+ */
55
+ resolve(location) {
56
+ const rawLocation = typeof location === "string" ? { path: location } : location;
57
+ if (rawLocation.path === void 0) {
58
+ rawLocation.path = this.current.fullPath;
59
+ }
60
+ const { base, ...normalizedLocation } = normalizeLocation(
61
+ rawLocation,
62
+ this.router.base
63
+ );
64
+ const matcher = this.router.matcher.match(normalizedLocation, { base });
65
+ if (matcher) {
66
+ return matcher;
67
+ }
68
+ const {
69
+ path = "",
70
+ params = {},
71
+ query = {},
72
+ queryArray = {},
73
+ hash = "",
74
+ state = {}
75
+ } = normalizedLocation;
76
+ const route = createRouteRecord({
77
+ base,
78
+ fullPath: stringifyPath({
79
+ pathname: path,
80
+ query,
81
+ queryArray,
82
+ hash
83
+ }),
84
+ path,
85
+ params,
86
+ query,
87
+ queryArray,
88
+ hash,
89
+ state
90
+ });
91
+ return route;
92
+ }
93
+ /**
94
+ * 核心跳转方法
95
+ */
96
+ async transitionTo(location, onComplete) {
97
+ if (this.isFrozen) return;
98
+ const route = this.resolve(location);
99
+ this.abortTask();
100
+ if (isEqualRoute(this.current, route)) {
101
+ return;
102
+ }
103
+ await this.runTask(this.current, route, onComplete);
104
+ }
105
+ /**
106
+ * TODO 逻辑解耦,抽离到task
107
+ * 重定向方法
108
+ */
109
+ async redirectTo(location, from, onComplete) {
110
+ const route = this.resolve(location);
111
+ this.abortTask();
112
+ if (isEqualRoute(this.current, route)) {
113
+ return;
114
+ }
115
+ await this.runTask(
116
+ this.current,
117
+ {
118
+ ...route,
119
+ redirectedFrom: from
120
+ },
121
+ onComplete
122
+ );
123
+ }
124
+ /* 当前执行的任务 */
125
+ tasks = null;
126
+ /* 取消任务 */
127
+ async abortTask() {
128
+ var _a;
129
+ (_a = this.tasks) == null ? void 0 : _a.abort();
130
+ }
131
+ /**
132
+ * 执行任务
133
+ * 任务分为三部分: 前置守卫(beforeEach、beforeEnter、beforeUpdate、beforeLeave)、加载路由(loadRoute)、后置守卫(afterEach)
134
+ * 根据触发方式不同,执行顺序分别为:
135
+ * 进入路由时: beforeEach -> beforeEnter -> loadRoute -> afterEach
136
+ * 更新路由时: beforeEach -> beforeUpdate -> afterEach
137
+ * 离开路由进入新路由时: beforeLeave -> beforeEach -> beforeEnter -> loadRoute -> afterEach
138
+ * @param from
139
+ * @param to
140
+ */
141
+ async runTask(from, to, onComplete) {
142
+ var _a;
143
+ const {
144
+ beforeEach,
145
+ beforeEnter,
146
+ beforeUpdate,
147
+ beforeLeave,
148
+ afterEach,
149
+ loadRoute
150
+ } = getNavigationHooks(this.router, from, to);
151
+ const guardBeforeTasks = createTasks();
152
+ const guardAfterTasks = createTasks();
153
+ const loadRouteTasks = createTasks();
154
+ if (isSameRoute(from, to)) {
155
+ guardBeforeTasks.add([...beforeEach, ...beforeUpdate]);
156
+ guardAfterTasks.add(afterEach);
157
+ } else {
158
+ guardBeforeTasks.add([
159
+ ...beforeLeave,
160
+ ...beforeEach,
161
+ ...beforeEnter
162
+ ]);
163
+ loadRouteTasks.add(loadRoute);
164
+ guardAfterTasks.add(afterEach);
165
+ }
166
+ this.tasks = guardBeforeTasks;
167
+ await guardBeforeTasks.run({
168
+ cb: async (res) => {
169
+ var _a2;
170
+ switch (typeof res) {
171
+ case "boolean":
172
+ if (!res) {
173
+ (_a2 = this.tasks) == null ? void 0 : _a2.abort();
174
+ }
175
+ break;
176
+ case "undefined":
177
+ break;
178
+ default:
179
+ await this.redirectTo(res, from, onComplete);
180
+ break;
181
+ }
182
+ },
183
+ final: async () => {
184
+ this.tasks = loadRouteTasks;
185
+ await loadRouteTasks.run();
186
+ }
187
+ });
188
+ if (((_a = this.tasks) == null ? void 0 : _a.status) === "finished") {
189
+ this.tasks = null;
190
+ guardAfterTasks.run();
191
+ onComplete && onComplete(to);
192
+ this.updateRoute(to);
193
+ }
194
+ }
195
+ }
196
+ function getNavigationHooks(router, from, to) {
197
+ const beforeEach = router.guards.beforeEach.map((guard) => {
198
+ return () => {
199
+ return guard(from, to);
200
+ };
201
+ });
202
+ const afterEach = router.guards.afterEach.map((guard) => {
203
+ return () => {
204
+ return guard(from, to);
205
+ };
206
+ });
207
+ const { beforeLeave } = from.matched.reduce(
208
+ (acc, { beforeLeave: beforeLeave2 }) => {
209
+ if (beforeLeave2) {
210
+ acc.beforeLeave.unshift(() => {
211
+ return beforeLeave2(from, to);
212
+ });
213
+ }
214
+ return acc;
215
+ },
216
+ {
217
+ beforeLeave: []
218
+ }
219
+ );
220
+ const { beforeEnter, beforeUpdate } = to.matched.reduce(
221
+ (acc, { beforeEnter: beforeEnter2, beforeUpdate: beforeUpdate2 }) => {
222
+ if (beforeEnter2) {
223
+ acc.beforeEnter.push(() => {
224
+ return beforeEnter2(from, to);
225
+ });
226
+ }
227
+ if (beforeUpdate2) {
228
+ acc.beforeUpdate.push(() => {
229
+ return beforeUpdate2(from, to);
230
+ });
231
+ }
232
+ return acc;
233
+ },
234
+ {
235
+ beforeEnter: [],
236
+ beforeUpdate: []
237
+ }
238
+ );
239
+ const loadRoute = [
240
+ async () => {
241
+ return Promise.all(
242
+ to.matched.reduce((acc, route) => {
243
+ if (!route.component && route.asyncComponent) {
244
+ acc.push(
245
+ new Promise((resolve, reject) => {
246
+ if (!route.component && route.asyncComponent) {
247
+ route.asyncComponent().then((resolved) => {
248
+ if (!resolved) {
249
+ reject(
250
+ new Error(
251
+ `Couldn't resolve component at "${route.path}". Ensure you passed a function that returns a promise.`
252
+ )
253
+ );
254
+ }
255
+ route.component = isESModule(resolved) ? resolved.default : resolved;
256
+ resolve();
257
+ });
258
+ }
259
+ })
260
+ );
261
+ }
262
+ return acc;
263
+ }, [])
264
+ );
265
+ }
266
+ ];
267
+ return {
268
+ beforeEach,
269
+ afterEach,
270
+ beforeEnter,
271
+ beforeUpdate,
272
+ beforeLeave,
273
+ loadRoute
274
+ };
275
+ }
@@ -0,0 +1,22 @@
1
+ import { type RouterInstance, type RouterRawLocation } from '../types';
2
+ import { BaseRouterHistory } from './base';
3
+ export declare class HtmlHistory extends BaseRouterHistory {
4
+ constructor(router: RouterInstance);
5
+ getCurrentLocation(): any;
6
+ onPopState: (e: PopStateEvent) => void;
7
+ init({ replace }?: {
8
+ replace?: boolean;
9
+ }): Promise<void>;
10
+ setupListeners(): void;
11
+ destroy(): void;
12
+ pushWindow(location: RouterRawLocation): void;
13
+ replaceWindow(location: RouterRawLocation): void;
14
+ handleOutside(location: RouterRawLocation, replace?: boolean, isTriggerWithWindow?: boolean): boolean;
15
+ push(location: RouterRawLocation): Promise<void>;
16
+ replace(location: RouterRawLocation): Promise<void>;
17
+ jump(location: RouterRawLocation, replace?: boolean): Promise<void>;
18
+ go(delta: number): void;
19
+ forward(): void;
20
+ protected timer: NodeJS.Timeout | null;
21
+ back(): void;
22
+ }
@@ -0,0 +1,181 @@
1
+ import {
2
+ computeScrollPosition,
3
+ getKeepScrollPosition,
4
+ getSavedScrollPosition,
5
+ isPathWithProtocolOrDomain,
6
+ normalizeLocation,
7
+ openWindow,
8
+ saveScrollPosition,
9
+ scrollToPosition
10
+ } from "../utils/index.mjs";
11
+ import { BaseRouterHistory } from "./base.mjs";
12
+ export class HtmlHistory extends BaseRouterHistory {
13
+ constructor(router) {
14
+ super(router);
15
+ if ("scrollRestoration" in window.history) {
16
+ window.history.scrollRestoration = "manual";
17
+ }
18
+ }
19
+ // 获取当前地址,包括 path query hash
20
+ getCurrentLocation() {
21
+ const { href } = window.location;
22
+ const { state } = window.history;
23
+ const { path, base, ...rest } = normalizeLocation(
24
+ href,
25
+ this.router.base
26
+ );
27
+ return {
28
+ path: path.replace(new RegExp(`^(${base})`), ""),
29
+ base,
30
+ ...rest,
31
+ state
32
+ };
33
+ }
34
+ onPopState = (e) => {
35
+ if (this.isFrozen) return;
36
+ if (this.router.checkLayerState(e.state)) return;
37
+ const current = Object.assign({}, this.current);
38
+ this.transitionTo(this.getCurrentLocation(), async (route) => {
39
+ const { state } = window.history;
40
+ saveScrollPosition(current.fullPath, computeScrollPosition());
41
+ setTimeout(async () => {
42
+ const keepScrollPosition = state.keepScrollPosition;
43
+ if (keepScrollPosition) {
44
+ return;
45
+ }
46
+ const savedPosition = getSavedScrollPosition(route.fullPath);
47
+ const position = await this.router.scrollBehavior(
48
+ current,
49
+ route,
50
+ savedPosition
51
+ );
52
+ const { nextTick } = this.router.options;
53
+ if (position) {
54
+ nextTick && await nextTick();
55
+ scrollToPosition(position);
56
+ }
57
+ });
58
+ });
59
+ };
60
+ async init({ replace } = { replace: true }) {
61
+ const { initUrl } = this.router.options;
62
+ let route = this.getCurrentLocation();
63
+ if (initUrl !== void 0) {
64
+ route = this.resolve(initUrl);
65
+ } else {
66
+ const state = history.state || {};
67
+ route.state = {
68
+ ...state,
69
+ _ancientRoute: state._ancientRoute ?? true
70
+ // 最古历史的标记, 在调用返回事件时如果有这个标记则直接调用没有历史记录的钩子
71
+ };
72
+ }
73
+ if (replace) {
74
+ await this.replace(route);
75
+ } else {
76
+ await this.push(route);
77
+ }
78
+ this.setupListeners();
79
+ }
80
+ // 设置监听函数
81
+ setupListeners() {
82
+ window.addEventListener("popstate", this.onPopState);
83
+ }
84
+ destroy() {
85
+ window.removeEventListener("popstate", this.onPopState);
86
+ }
87
+ pushWindow(location) {
88
+ if (this.isFrozen) return;
89
+ this.handleOutside(location, false, true);
90
+ }
91
+ replaceWindow(location) {
92
+ if (this.isFrozen) return;
93
+ this.handleOutside(location, true, true);
94
+ }
95
+ // 处理外站跳转逻辑
96
+ handleOutside(location, replace = false, isTriggerWithWindow = false) {
97
+ const { flag, route } = isPathWithProtocolOrDomain(location);
98
+ const router = this.router;
99
+ const { handleOutside, validateOutside } = router.options;
100
+ const isSameHost = !flag || window.location.hostname === route.hostname;
101
+ if (!isTriggerWithWindow) {
102
+ if (isSameHost && !(validateOutside == null ? void 0 : validateOutside({ router, location, route }))) {
103
+ return false;
104
+ }
105
+ }
106
+ if (handleOutside == null ? void 0 : handleOutside({
107
+ router,
108
+ route,
109
+ replace,
110
+ isTriggerWithWindow,
111
+ isSameHost
112
+ })) {
113
+ return true;
114
+ }
115
+ if (replace) {
116
+ window.location.replace(route.href);
117
+ } else {
118
+ const { hostname, href } = route;
119
+ openWindow(href, hostname);
120
+ }
121
+ return true;
122
+ }
123
+ // 新增路由记录跳转
124
+ async push(location) {
125
+ await this.jump(location, false);
126
+ }
127
+ // 替换当前路由记录跳转
128
+ async replace(location) {
129
+ await this.jump(location, true);
130
+ }
131
+ // 跳转方法
132
+ async jump(location, replace = false) {
133
+ if (this.isFrozen) return;
134
+ if (this.handleOutside(location, replace)) {
135
+ return;
136
+ }
137
+ const current = Object.assign({}, this.current);
138
+ await this.transitionTo(location, (route) => {
139
+ const keepScrollPosition = getKeepScrollPosition(location);
140
+ if (!keepScrollPosition) {
141
+ saveScrollPosition(current.fullPath, computeScrollPosition());
142
+ scrollToPosition({ left: 0, top: 0 });
143
+ }
144
+ const state = Object.assign(
145
+ replace ? { ...history.state, ...route.state } : { ...route.state, _ancientRoute: false },
146
+ { keepScrollPosition }
147
+ );
148
+ window.history[replace ? "replaceState" : "pushState"](
149
+ state,
150
+ "",
151
+ route.fullPath
152
+ );
153
+ this.router.updateLayerState(route);
154
+ });
155
+ }
156
+ go(delta) {
157
+ if (this.isFrozen) return;
158
+ window.history.go(delta);
159
+ }
160
+ forward() {
161
+ if (this.isFrozen) return;
162
+ window.history.forward();
163
+ }
164
+ timer = null;
165
+ back() {
166
+ if (this.isFrozen) return;
167
+ const oldState = history.state;
168
+ const noBackNavigation = this.router.options.noBackNavigation;
169
+ if (oldState._ancientRoute === true) {
170
+ noBackNavigation && noBackNavigation(this.router);
171
+ return;
172
+ }
173
+ window.history.back();
174
+ this.timer = setTimeout(() => {
175
+ if (history.state === oldState) {
176
+ noBackNavigation && noBackNavigation(this.router);
177
+ }
178
+ this.timer = null;
179
+ }, 80);
180
+ }
181
+ }
@@ -0,0 +1,7 @@
1
+ import { type RouterInstance, RouterMode } from '../types';
2
+ import { AbstractHistory } from './abstract';
3
+ import { HtmlHistory } from './html';
4
+ export declare function createHistory({ router, mode }: {
5
+ router: RouterInstance;
6
+ mode: RouterMode;
7
+ }): AbstractHistory | HtmlHistory;