@viewfly/router 2.1.0 → 3.0.0-alpha.0
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/README.md +58 -112
- package/dist/hooks/_api.d.ts +1 -0
- package/dist/hooks/use-query-params.d.ts +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.esm.js +479 -0
- package/dist/index.js +494 -0
- package/dist/link.d.ts +12 -0
- package/dist/providers/_api.d.ts +3 -0
- package/dist/providers/navigator.d.ts +56 -0
- package/dist/providers/router.d.ts +28 -0
- package/dist/providers/url-parser.d.ts +38 -0
- package/dist/router-module.d.ts +10 -0
- package/dist/router-outlet.d.ts +6 -0
- package/package.json +27 -20
- package/bundles/index.d.ts +0 -151
- package/bundles/index.esm.js +0 -674
- package/bundles/index.js +0 -684
- package/rollup-d.config.ts +0 -14
package/README.md
CHANGED
|
@@ -1,132 +1,78 @@
|
|
|
1
|
-
|
|
2
|
-
================================
|
|
1
|
+
# @viewfly/router
|
|
3
2
|
|
|
4
|
-
Viewfly
|
|
3
|
+
基于 **Viewfly** 的浏览器端路由:声明式链接、嵌套路由出口、编程式导航等。需配合 **`@viewfly/core`** 与 **`@viewfly/platform-browser`** 使用。
|
|
4
|
+
|
|
5
|
+
---
|
|
5
6
|
|
|
6
7
|
## 安装
|
|
7
8
|
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @viewfly/router @viewfly/platform-browser @viewfly/core
|
|
8
11
|
```
|
|
9
|
-
npm install @viewfly/router
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
## 使用示例
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
import { createApp } from '@viewfly/platform-browser'
|
|
16
|
-
import { RouterModule } from '@viewfly/router'
|
|
17
|
-
|
|
18
|
-
function ListTab1() {
|
|
19
|
-
return () => {
|
|
20
|
-
return (
|
|
21
|
-
<div>listTab1</div>
|
|
22
|
-
)
|
|
23
|
-
}
|
|
24
|
-
}
|
|
13
|
+
---
|
|
25
14
|
|
|
26
|
-
|
|
27
|
-
return () => {
|
|
28
|
-
return (
|
|
29
|
-
<div>listTab2</div>
|
|
30
|
-
)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
15
|
+
## 接入应用
|
|
33
16
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
<div>listTab3</div>
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
17
|
+
1. 使用 **`RouterModule`** 作为应用级扩展(通过 `createApp(...).use(...)` 注册)。
|
|
18
|
+
2. 在布局中用 **`Link`** 生成导航,用 **`RouterOutlet`** 根据配置渲染匹配到的组件。
|
|
19
|
+
3. 在组件内通过 **`inject(Router)`** 拿到路由实例,调用 **`navigateTo`** 等方法做跳转。
|
|
41
20
|
|
|
42
|
-
|
|
43
|
-
return () => {
|
|
44
|
-
return (
|
|
45
|
-
<div>
|
|
46
|
-
<h3>list</h3>
|
|
47
|
-
<div>
|
|
48
|
-
<Link active="active" to='./tab1'>tab1</Link>
|
|
49
|
-
<Link active="active" to='./tab2'>tab2</Link>
|
|
50
|
-
<Link active="active" to='./tab3'>tab3</Link>
|
|
51
|
-
</div>
|
|
52
|
-
<div>
|
|
53
|
-
<RouterOutlet config={[
|
|
54
|
-
{
|
|
55
|
-
name: 'tab1',
|
|
56
|
-
component: ListTab1
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: 'tab2',
|
|
60
|
-
component: ListTab2
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: 'tab3',
|
|
64
|
-
component: ListTab3
|
|
65
|
-
}
|
|
66
|
-
]}>没找到 Tab</RouterOutlet>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
}
|
|
21
|
+
最小串联示例(节选,完整路由表与懒加载等见官网):
|
|
72
22
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
}
|
|
79
|
-
}
|
|
23
|
+
```tsx
|
|
24
|
+
import { inject } from '@viewfly/core'
|
|
25
|
+
import { createApp } from '@viewfly/platform-browser'
|
|
26
|
+
import { Link, Router, RouterModule, RouterOutlet } from '@viewfly/router'
|
|
80
27
|
|
|
81
28
|
function Home() {
|
|
82
29
|
const router = inject(Router)
|
|
83
|
-
return () =>
|
|
84
|
-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
}
|
|
30
|
+
return () => (
|
|
31
|
+
<div>
|
|
32
|
+
<p>Home</p>
|
|
33
|
+
<button type="button" onClick={() => router.navigateTo('/list')}>去列表</button>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function List() {
|
|
39
|
+
return () => <div>List</div>
|
|
95
40
|
}
|
|
96
41
|
|
|
97
42
|
function App() {
|
|
98
|
-
return () =>
|
|
99
|
-
|
|
100
|
-
<
|
|
101
|
-
<
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
asyncComponent: () => Promise.resolve().then(() => List)
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
name: 'detail',
|
|
118
|
-
component: Detail
|
|
119
|
-
}
|
|
120
|
-
]}>
|
|
121
|
-
未匹配到任何路由
|
|
122
|
-
</RouterOutlet>
|
|
123
|
-
</div>
|
|
124
|
-
</div>
|
|
125
|
-
)
|
|
126
|
-
}
|
|
43
|
+
return () => (
|
|
44
|
+
<div>
|
|
45
|
+
<nav>
|
|
46
|
+
<Link active="active" exact to="/">Home</Link>
|
|
47
|
+
<Link active="active" to="/list">List</Link>
|
|
48
|
+
</nav>
|
|
49
|
+
<RouterOutlet
|
|
50
|
+
config={[
|
|
51
|
+
{ name: 'home', component: Home },
|
|
52
|
+
{ name: 'list', component: List }
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
未匹配到路由
|
|
56
|
+
</RouterOutlet>
|
|
57
|
+
</div>
|
|
58
|
+
)
|
|
127
59
|
}
|
|
128
60
|
|
|
129
|
-
createApp(<App/>)
|
|
61
|
+
createApp(<App />)
|
|
62
|
+
.use(new RouterModule())
|
|
63
|
+
.mount(document.getElementById('app')!)
|
|
130
64
|
```
|
|
131
65
|
|
|
132
|
-
|
|
66
|
+
**嵌套路由**:在子页面组件内再次放置 `RouterOutlet`,并为其传入子级 `config`(与官网「路由」章节一致)。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 文档
|
|
71
|
+
|
|
72
|
+
- **官方文档**:[viewfly.org](https://viewfly.org)
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-query-params';
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import { Injectable, comparePropsWithCallbacks, createContext, inject, internalWrite, jsx, makeError, onUnmounted, reactive, readonlyProxyHandler, shallowReactive } from "@viewfly/core";
|
|
2
|
+
import { Subject, Subscription, fromEvent } from "@tanbo/stream";
|
|
3
|
+
//#region src/providers/router.ts
|
|
4
|
+
var routerErrorFn$1 = makeError("Router");
|
|
5
|
+
var Router = class {
|
|
6
|
+
onRefresh;
|
|
7
|
+
get deep() {
|
|
8
|
+
return this.parent ? this.parent.deep + 1 : 0;
|
|
9
|
+
}
|
|
10
|
+
get path() {
|
|
11
|
+
return this.navigator.urlTree.paths.at(this.deep) || "";
|
|
12
|
+
}
|
|
13
|
+
refreshEvent = new Subject();
|
|
14
|
+
constructor(navigator, parent) {
|
|
15
|
+
this.navigator = navigator;
|
|
16
|
+
this.parent = parent;
|
|
17
|
+
this.onRefresh = this.refreshEvent.asObservable();
|
|
18
|
+
}
|
|
19
|
+
navigateTo(path, params, fragment) {
|
|
20
|
+
this.navigator.to(path, this, params, fragment || void 0);
|
|
21
|
+
}
|
|
22
|
+
replaceTo(path, params) {
|
|
23
|
+
this.navigator.replace(path, this, params);
|
|
24
|
+
}
|
|
25
|
+
refresh() {
|
|
26
|
+
this.refreshEvent.next();
|
|
27
|
+
}
|
|
28
|
+
consumeConfig(routes) {
|
|
29
|
+
return this.matchRoute(routes, this.path);
|
|
30
|
+
}
|
|
31
|
+
back() {
|
|
32
|
+
this.navigator.back();
|
|
33
|
+
}
|
|
34
|
+
forward() {
|
|
35
|
+
this.navigator.forward();
|
|
36
|
+
}
|
|
37
|
+
go(offset) {
|
|
38
|
+
this.navigator.go(offset);
|
|
39
|
+
}
|
|
40
|
+
matchRoute(configs, pathname) {
|
|
41
|
+
let matchedConfig = null;
|
|
42
|
+
let defaultConfig = null;
|
|
43
|
+
let fallbackConfig = null;
|
|
44
|
+
for (const item of configs) if (item.path === pathname) {
|
|
45
|
+
matchedConfig = item;
|
|
46
|
+
break;
|
|
47
|
+
} else if (item.path === "*") {
|
|
48
|
+
if (!fallbackConfig) fallbackConfig = item;
|
|
49
|
+
} else if (item.path === "") {
|
|
50
|
+
if (!defaultConfig) defaultConfig = item;
|
|
51
|
+
}
|
|
52
|
+
const config = matchedConfig || defaultConfig || fallbackConfig;
|
|
53
|
+
if (!config) return config;
|
|
54
|
+
if (typeof config.redirectTo === "function") {
|
|
55
|
+
const p = config.redirectTo(pathname);
|
|
56
|
+
if (typeof p === "string") this.navigateTo(p);
|
|
57
|
+
else if (typeof p === "object") this.navigateTo(p.pathname, p.queryParams, p.fragment);
|
|
58
|
+
else throw routerErrorFn$1(`Router redirect to '${pathname}' not supported`);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (typeof config.redirectTo === "string") {
|
|
62
|
+
this.navigateTo(config.redirectTo);
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return config;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/providers/url-parser.ts
|
|
70
|
+
var UrlParser = class {
|
|
71
|
+
index = 0;
|
|
72
|
+
url = "";
|
|
73
|
+
tokens = [];
|
|
74
|
+
parse(url) {
|
|
75
|
+
this.index = 0;
|
|
76
|
+
this.url = url;
|
|
77
|
+
this.tokens = [];
|
|
78
|
+
while (this.index < this.url.length) {
|
|
79
|
+
this.ignore("/");
|
|
80
|
+
if (this.peek("../")) {
|
|
81
|
+
this.tokens.push({ type: "toParent" });
|
|
82
|
+
this.index += 3;
|
|
83
|
+
} else if (this.peek("?")) {
|
|
84
|
+
this.index++;
|
|
85
|
+
this.tokens.push({
|
|
86
|
+
type: "query",
|
|
87
|
+
params: this.readQuery()
|
|
88
|
+
});
|
|
89
|
+
} else if (this.peek("#")) {
|
|
90
|
+
this.index++;
|
|
91
|
+
this.tokens.push({
|
|
92
|
+
type: "hash",
|
|
93
|
+
value: this.readHash()
|
|
94
|
+
});
|
|
95
|
+
} else {
|
|
96
|
+
if (this.peek("./")) this.index += 2;
|
|
97
|
+
const path = this.readPath();
|
|
98
|
+
if (path) this.tokens.push({
|
|
99
|
+
type: "toChild",
|
|
100
|
+
value: path
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const urlTree = {
|
|
105
|
+
paths: [],
|
|
106
|
+
queryParams: {},
|
|
107
|
+
hash: null
|
|
108
|
+
};
|
|
109
|
+
for (const item of this.tokens) switch (item.type) {
|
|
110
|
+
case "toParent":
|
|
111
|
+
urlTree.paths.pop();
|
|
112
|
+
break;
|
|
113
|
+
case "toChild":
|
|
114
|
+
urlTree.paths.push(item.value);
|
|
115
|
+
break;
|
|
116
|
+
case "query":
|
|
117
|
+
urlTree.queryParams = item.params;
|
|
118
|
+
break;
|
|
119
|
+
case "hash": urlTree.hash = item.value;
|
|
120
|
+
}
|
|
121
|
+
return urlTree;
|
|
122
|
+
}
|
|
123
|
+
readHash() {
|
|
124
|
+
const hash = this.url.substring(this.index);
|
|
125
|
+
this.index = this.url.length;
|
|
126
|
+
return hash;
|
|
127
|
+
}
|
|
128
|
+
readQuery() {
|
|
129
|
+
const query = {};
|
|
130
|
+
while (this.index < this.url.length) {
|
|
131
|
+
const key = this.readQueryKey();
|
|
132
|
+
let value = "";
|
|
133
|
+
if (this.peek("=")) {
|
|
134
|
+
this.index++;
|
|
135
|
+
value = this.readQueryValue();
|
|
136
|
+
}
|
|
137
|
+
const oldValue = query[key];
|
|
138
|
+
if (oldValue) if (Array.isArray(oldValue)) oldValue.push(value);
|
|
139
|
+
else query[key] = [oldValue, value];
|
|
140
|
+
else query[key] = value;
|
|
141
|
+
if (this.peek("&")) {
|
|
142
|
+
this.index++;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
return query;
|
|
148
|
+
}
|
|
149
|
+
readQueryValue() {
|
|
150
|
+
const chars = [];
|
|
151
|
+
while (this.index < this.url.length) {
|
|
152
|
+
if (this.not("&#")) {
|
|
153
|
+
chars.push(this.url.at(this.index));
|
|
154
|
+
this.index++;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
return chars.join("");
|
|
160
|
+
}
|
|
161
|
+
readQueryKey() {
|
|
162
|
+
const chars = [];
|
|
163
|
+
while (this.index < this.url.length) {
|
|
164
|
+
if (this.not("=&#")) {
|
|
165
|
+
chars.push(this.url.at(this.index));
|
|
166
|
+
this.index++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
return chars.join("");
|
|
172
|
+
}
|
|
173
|
+
readPath() {
|
|
174
|
+
const chars = [];
|
|
175
|
+
while (this.index < this.url.length) {
|
|
176
|
+
if (this.not("./?#")) {
|
|
177
|
+
chars.push(this.url.at(this.index));
|
|
178
|
+
this.index++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
return chars.join("");
|
|
184
|
+
}
|
|
185
|
+
not(text) {
|
|
186
|
+
const ch = this.url.at(this.index);
|
|
187
|
+
return text.indexOf(ch) === -1;
|
|
188
|
+
}
|
|
189
|
+
peek(str) {
|
|
190
|
+
return this.url.slice(this.index, this.index + str.length) === str;
|
|
191
|
+
}
|
|
192
|
+
ignore(str) {
|
|
193
|
+
while (this.peek(str)) this.index++;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/decorateMetadata.js
|
|
198
|
+
function __decorateMetadata(k, v) {
|
|
199
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
200
|
+
}
|
|
201
|
+
//#endregion
|
|
202
|
+
//#region \0@oxc-project+runtime@0.126.0/helpers/decorate.js
|
|
203
|
+
function __decorate(decorators, target, key, desc) {
|
|
204
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
205
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
206
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
207
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
208
|
+
}
|
|
209
|
+
//#endregion
|
|
210
|
+
//#region src/providers/navigator.ts
|
|
211
|
+
var Navigator = class {
|
|
212
|
+
constructor(baseUrl) {
|
|
213
|
+
this.baseUrl = baseUrl;
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
function formatUrl(pathname, urlFormatParams) {
|
|
217
|
+
pathname = pathname.replace(/\/+/g, "/");
|
|
218
|
+
const { queryParams, fragment } = urlFormatParams;
|
|
219
|
+
return pathname + (queryParams ? "?" + formatQueryParams(queryParams) : "") + (fragment ? "#" + fragment : "");
|
|
220
|
+
}
|
|
221
|
+
function formatQueryParams(queryParams) {
|
|
222
|
+
const params = [];
|
|
223
|
+
Object.keys(queryParams).forEach((key) => {
|
|
224
|
+
const values = queryParams[key];
|
|
225
|
+
if (Array.isArray(values)) values.forEach((i) => {
|
|
226
|
+
params.push(`${key}=${decodeURIComponent(i)}`);
|
|
227
|
+
});
|
|
228
|
+
else params.push(`${key}=${decodeURIComponent(values)}`);
|
|
229
|
+
});
|
|
230
|
+
return params.join("&");
|
|
231
|
+
}
|
|
232
|
+
var BrowserNavigator = class BrowserNavigator extends Navigator {
|
|
233
|
+
onUrlChanged;
|
|
234
|
+
/** 挂载在 location 上的路径前缀;'' 或 '/' 表示站点根,不做剥离 */
|
|
235
|
+
get basePathPrefix() {
|
|
236
|
+
return this.baseUrl === "/" || this.baseUrl === "" ? "" : this.baseUrl;
|
|
237
|
+
}
|
|
238
|
+
get pathname() {
|
|
239
|
+
const pathname = location.pathname;
|
|
240
|
+
if (!this.basePathPrefix) return pathname;
|
|
241
|
+
return pathname.startsWith(this.basePathPrefix) ? pathname.substring(this.basePathPrefix.length) : pathname;
|
|
242
|
+
}
|
|
243
|
+
urlParser = new UrlParser();
|
|
244
|
+
urlTree = this.getUrlTree();
|
|
245
|
+
urlChangeEvent = new Subject();
|
|
246
|
+
subscription = new Subscription();
|
|
247
|
+
constructor(baseUrl, hooks = {}) {
|
|
248
|
+
super(baseUrl);
|
|
249
|
+
this.hooks = hooks;
|
|
250
|
+
this.onUrlChanged = this.urlChangeEvent.asObservable();
|
|
251
|
+
this.subscription.add(fromEvent(window, "popstate").subscribe(() => {
|
|
252
|
+
this.urlTree = this.getUrlTree();
|
|
253
|
+
this.urlChangeEvent.next();
|
|
254
|
+
}));
|
|
255
|
+
if (this.basePathPrefix && !location.pathname.startsWith(this.basePathPrefix)) history.replaceState(null, "", this.baseUrl);
|
|
256
|
+
}
|
|
257
|
+
to(pathName, relative, queryParams, fragment) {
|
|
258
|
+
const url = this.join(pathName, relative, queryParams, fragment);
|
|
259
|
+
if (location.origin + url === location.href) return true;
|
|
260
|
+
this.runHooks({
|
|
261
|
+
pathname: this.pathname,
|
|
262
|
+
queryParams: this.urlTree.queryParams,
|
|
263
|
+
fragment: this.urlTree.hash
|
|
264
|
+
}, {
|
|
265
|
+
pathname: pathName,
|
|
266
|
+
queryParams: queryParams || {},
|
|
267
|
+
fragment: fragment || null
|
|
268
|
+
}, () => {
|
|
269
|
+
history.pushState(null, "", url);
|
|
270
|
+
this.urlTree = this.getUrlTree();
|
|
271
|
+
this.urlChangeEvent.next();
|
|
272
|
+
});
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
replace(pathName, relative, queryParams, fragment) {
|
|
276
|
+
const url = this.join(pathName, relative, queryParams, fragment);
|
|
277
|
+
if (location.origin + url === location.href) return true;
|
|
278
|
+
this.runHooks({
|
|
279
|
+
pathname: this.pathname,
|
|
280
|
+
queryParams: this.urlTree.queryParams,
|
|
281
|
+
fragment: this.urlTree.hash
|
|
282
|
+
}, {
|
|
283
|
+
pathname: pathName,
|
|
284
|
+
queryParams: queryParams || {},
|
|
285
|
+
fragment: fragment || null
|
|
286
|
+
}, () => {
|
|
287
|
+
history.replaceState(null, "", url);
|
|
288
|
+
this.urlTree = this.getUrlTree();
|
|
289
|
+
this.urlChangeEvent.next();
|
|
290
|
+
});
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
join(pathname, relative, queryParams, fragment) {
|
|
294
|
+
if (pathname.startsWith("/")) return formatUrl(this.baseUrl + pathname, {
|
|
295
|
+
queryParams,
|
|
296
|
+
fragment
|
|
297
|
+
});
|
|
298
|
+
const beforePath = this.urlTree.paths.slice(0, relative.deep);
|
|
299
|
+
while (true) {
|
|
300
|
+
if (pathname.startsWith("./")) {
|
|
301
|
+
pathname = pathname.substring(2);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (pathname.startsWith("../")) {
|
|
305
|
+
pathname = pathname.substring(3);
|
|
306
|
+
beforePath.pop();
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
return formatUrl(this.baseUrl + "/" + beforePath.join("/") + "/" + pathname, {
|
|
312
|
+
queryParams,
|
|
313
|
+
fragment
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
back() {
|
|
317
|
+
history.back();
|
|
318
|
+
}
|
|
319
|
+
forward() {
|
|
320
|
+
history.forward();
|
|
321
|
+
}
|
|
322
|
+
go(offset) {
|
|
323
|
+
history.go(offset);
|
|
324
|
+
}
|
|
325
|
+
destroy() {
|
|
326
|
+
this.subscription.unsubscribe();
|
|
327
|
+
}
|
|
328
|
+
runHooks(beforeParams, currentParams, next) {
|
|
329
|
+
if (typeof this.hooks.beforeEach === "function") this.hooks.beforeEach?.(beforeParams, currentParams, () => {
|
|
330
|
+
next();
|
|
331
|
+
this.hooks.afterEach?.(currentParams);
|
|
332
|
+
});
|
|
333
|
+
else {
|
|
334
|
+
next();
|
|
335
|
+
this.hooks.afterEach?.(currentParams);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
getUrlTree() {
|
|
339
|
+
return this.urlParser.parse(this.pathname + location.search + location.hash);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
BrowserNavigator = __decorate([Injectable(), __decorateMetadata("design:paramtypes", [String, Object])], BrowserNavigator);
|
|
343
|
+
//#endregion
|
|
344
|
+
//#region src/hooks/use-query-params.ts
|
|
345
|
+
function useQueryParams() {
|
|
346
|
+
const router = inject(Router);
|
|
347
|
+
const navigator = inject(Navigator);
|
|
348
|
+
const params = { ...navigator.urlTree.queryParams };
|
|
349
|
+
const queryParams = new Proxy(params, readonlyProxyHandler);
|
|
350
|
+
const subscription = router.onRefresh.subscribe(() => {
|
|
351
|
+
comparePropsWithCallbacks(params, navigator.urlTree.queryParams, (key) => {
|
|
352
|
+
internalWrite(() => {
|
|
353
|
+
Reflect.deleteProperty(params, key);
|
|
354
|
+
});
|
|
355
|
+
}, (key, value) => {
|
|
356
|
+
internalWrite(() => {
|
|
357
|
+
params[key] = value;
|
|
358
|
+
});
|
|
359
|
+
}, (key, value) => {
|
|
360
|
+
internalWrite(() => {
|
|
361
|
+
params[key] = value;
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
onUnmounted(() => {
|
|
366
|
+
subscription.unsubscribe();
|
|
367
|
+
});
|
|
368
|
+
return queryParams;
|
|
369
|
+
}
|
|
370
|
+
//#endregion
|
|
371
|
+
//#region src/link.tsx
|
|
372
|
+
function Link(props) {
|
|
373
|
+
const navigator = inject(Navigator);
|
|
374
|
+
const router = inject(Router);
|
|
375
|
+
function getActive() {
|
|
376
|
+
return props.exact ? navigator.pathname === navigator.join(props.to, router) || navigator.pathname + "/" === navigator.join(props.to, router) : navigator.pathname.startsWith(navigator.join(props.to, router));
|
|
377
|
+
}
|
|
378
|
+
const isActive = reactive({ value: getActive() });
|
|
379
|
+
const subscription = navigator.onUrlChanged.subscribe(() => {
|
|
380
|
+
isActive.value = getActive();
|
|
381
|
+
});
|
|
382
|
+
onUnmounted(() => {
|
|
383
|
+
subscription.unsubscribe();
|
|
384
|
+
});
|
|
385
|
+
function navigate(ev) {
|
|
386
|
+
if ((!props.tag || props.tag === "a") && props.target === "_blank") return;
|
|
387
|
+
ev.preventDefault();
|
|
388
|
+
router.navigateTo(props.to, props.queryParams, props.fragment);
|
|
389
|
+
}
|
|
390
|
+
return () => {
|
|
391
|
+
const Tag = props.tag || "a";
|
|
392
|
+
const attrs = Object.assign({}, props, {
|
|
393
|
+
onClick(ev) {
|
|
394
|
+
navigate(ev);
|
|
395
|
+
props.onClick?.(ev);
|
|
396
|
+
},
|
|
397
|
+
...props
|
|
398
|
+
});
|
|
399
|
+
if (Tag === "a") attrs.href = navigator.join(props.to, router, props.queryParams, props.fragment);
|
|
400
|
+
if (isActive.value && props.active) attrs.class = [attrs.class, props.active];
|
|
401
|
+
return /* @__PURE__ */ jsx(Tag, {
|
|
402
|
+
...attrs,
|
|
403
|
+
children: props.children
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region src/router-module.ts
|
|
409
|
+
var RouterModule = class {
|
|
410
|
+
subscription = new Subscription();
|
|
411
|
+
navigator;
|
|
412
|
+
constructor(baseUrl = "", hooks = {}) {
|
|
413
|
+
this.baseUrl = baseUrl;
|
|
414
|
+
this.navigator = new BrowserNavigator(this.baseUrl, hooks);
|
|
415
|
+
}
|
|
416
|
+
setup(app) {
|
|
417
|
+
const navigator = this.navigator;
|
|
418
|
+
const router = new Router(navigator, null);
|
|
419
|
+
this.subscription.add(navigator.onUrlChanged.subscribe(() => {
|
|
420
|
+
router.refresh();
|
|
421
|
+
}));
|
|
422
|
+
app.provide([{
|
|
423
|
+
provide: Navigator,
|
|
424
|
+
useValue: navigator
|
|
425
|
+
}, {
|
|
426
|
+
provide: Router,
|
|
427
|
+
useValue: router
|
|
428
|
+
}]);
|
|
429
|
+
}
|
|
430
|
+
onDestroy() {
|
|
431
|
+
this.subscription.unsubscribe();
|
|
432
|
+
this.navigator.destroy();
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/router-outlet.tsx
|
|
437
|
+
var routerErrorFn = makeError("RouterOutlet");
|
|
438
|
+
function RouterOutlet(props) {
|
|
439
|
+
const router = inject(Router, null);
|
|
440
|
+
if (router === null) throw routerErrorFn("cannot found parent Router.");
|
|
441
|
+
const childRouter = new Router(inject(Navigator), router);
|
|
442
|
+
const Context = createContext([{
|
|
443
|
+
provide: Router,
|
|
444
|
+
useValue: childRouter
|
|
445
|
+
}]);
|
|
446
|
+
const children = shallowReactive({ value: null });
|
|
447
|
+
const subscription = router.onRefresh.subscribe(() => {
|
|
448
|
+
updateChildren();
|
|
449
|
+
});
|
|
450
|
+
onUnmounted(() => {
|
|
451
|
+
subscription.unsubscribe();
|
|
452
|
+
});
|
|
453
|
+
let currentComponent = null;
|
|
454
|
+
async function updateChildren() {
|
|
455
|
+
const routeConfig = router.consumeConfig(props.config);
|
|
456
|
+
if (!routeConfig) {
|
|
457
|
+
currentComponent = null;
|
|
458
|
+
children.value = props.children || null;
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (typeof routeConfig.beforeEach === "function") {
|
|
462
|
+
if (!await routeConfig.beforeEach()) return;
|
|
463
|
+
}
|
|
464
|
+
if (routeConfig.component) _updateChildren(routeConfig.component);
|
|
465
|
+
else if (routeConfig.asyncComponent) _updateChildren(await routeConfig.asyncComponent());
|
|
466
|
+
if (typeof routeConfig.afterEach === "function") routeConfig.afterEach();
|
|
467
|
+
}
|
|
468
|
+
function _updateChildren(Component) {
|
|
469
|
+
childRouter.refresh();
|
|
470
|
+
if (Component !== currentComponent) children.value = /* @__PURE__ */ jsx(Component, {});
|
|
471
|
+
currentComponent = Component;
|
|
472
|
+
}
|
|
473
|
+
updateChildren();
|
|
474
|
+
return () => {
|
|
475
|
+
return /* @__PURE__ */ jsx(Context, { children: children.value });
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
//#endregion
|
|
479
|
+
export { BrowserNavigator, Link, Navigator, Router, RouterModule, RouterOutlet, UrlParser, formatQueryParams, formatUrl, useQueryParams };
|