@thi.ng/router 3.4.0 → 4.0.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/CHANGELOG.md +39 -1
- package/README.md +85 -40
- package/api.d.ts +86 -36
- package/basic.d.ts +20 -31
- package/basic.js +103 -80
- package/history.d.ts +10 -7
- package/history.js +18 -23
- package/html.d.ts +31 -0
- package/html.js +91 -0
- package/index.d.ts +3 -2
- package/index.js +3 -2
- package/package.json +22 -16
- package/router.d.ts +46 -0
- package/router.js +233 -0
- package/trie.d.ts +54 -0
- package/trie.js +45 -0
package/basic.js
CHANGED
|
@@ -15,41 +15,37 @@ import { equiv } from "@thi.ng/equiv";
|
|
|
15
15
|
import { assert } from "@thi.ng/errors/assert";
|
|
16
16
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
17
17
|
import { illegalArity } from "@thi.ng/errors/illegal-arity";
|
|
18
|
+
import { illegalState } from "@thi.ng/errors/illegal-state";
|
|
18
19
|
import {
|
|
19
20
|
EVENT_ROUTE_CHANGED,
|
|
20
21
|
EVENT_ROUTE_FAILED
|
|
21
22
|
} from "./api.js";
|
|
23
|
+
import { Trie } from "./trie.js";
|
|
22
24
|
let BasicRouter = class {
|
|
23
|
-
|
|
25
|
+
opts;
|
|
24
26
|
current;
|
|
25
|
-
|
|
27
|
+
index = {};
|
|
28
|
+
routes = new Trie();
|
|
26
29
|
constructor(config) {
|
|
27
|
-
this.
|
|
28
|
-
authenticator: (
|
|
29
|
-
id: route.id,
|
|
30
|
-
title: route.title,
|
|
31
|
-
params
|
|
32
|
-
}),
|
|
30
|
+
this.opts = {
|
|
31
|
+
authenticator: (match) => match,
|
|
33
32
|
prefix: "/",
|
|
34
33
|
separator: "/",
|
|
35
|
-
|
|
34
|
+
trim: true,
|
|
36
35
|
...config
|
|
37
36
|
};
|
|
38
|
-
this.
|
|
37
|
+
this.addRoutes(this.opts.routes);
|
|
39
38
|
assert(
|
|
40
|
-
this.routeForID(this.
|
|
41
|
-
`missing config for default route: '${this.
|
|
39
|
+
this.routeForID(this.opts.default) !== void 0,
|
|
40
|
+
`missing config for default route: '${this.opts.default}'`
|
|
42
41
|
);
|
|
43
|
-
if (config.
|
|
44
|
-
const route = this.routeForID(config.
|
|
42
|
+
if (config.initial) {
|
|
43
|
+
const route = this.routeForID(config.initial);
|
|
45
44
|
assert(
|
|
46
45
|
route !== void 0,
|
|
47
|
-
`missing config for initial route: ${this.
|
|
48
|
-
);
|
|
49
|
-
assert(
|
|
50
|
-
!isParametricRoute(route),
|
|
51
|
-
"initial route MUST not be parametric"
|
|
46
|
+
`missing config for initial route: ${this.opts.initial}`
|
|
52
47
|
);
|
|
48
|
+
assert(!route.params, "initial route MUST not be parametric");
|
|
53
49
|
}
|
|
54
50
|
}
|
|
55
51
|
// @ts-ignore: arguments
|
|
@@ -64,15 +60,24 @@ let BasicRouter = class {
|
|
|
64
60
|
notify(event) {
|
|
65
61
|
}
|
|
66
62
|
start() {
|
|
67
|
-
if (this.
|
|
68
|
-
const route = this.routeForID(this.
|
|
69
|
-
this.current = { id: route.id,
|
|
63
|
+
if (this.opts.initial) {
|
|
64
|
+
const route = this.routeForID(this.opts.initial);
|
|
65
|
+
this.current = { id: route.id, params: {} };
|
|
70
66
|
this.notify({ id: EVENT_ROUTE_CHANGED, value: this.current });
|
|
71
67
|
}
|
|
72
68
|
}
|
|
73
|
-
addRoutes(
|
|
74
|
-
|
|
75
|
-
|
|
69
|
+
addRoutes(routes) {
|
|
70
|
+
for (let r of routes) {
|
|
71
|
+
try {
|
|
72
|
+
const route = this.augmentRoute(r);
|
|
73
|
+
this.routes.set(route.match, route);
|
|
74
|
+
this.index[route.id] = route;
|
|
75
|
+
} catch (e) {
|
|
76
|
+
illegalArgs(
|
|
77
|
+
`error in route "${r.id}": ${e.origMessage}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
76
81
|
}
|
|
77
82
|
/**
|
|
78
83
|
* Main router function. Attempts to match given input string against all
|
|
@@ -81,21 +86,25 @@ let BasicRouter = class {
|
|
|
81
86
|
* {@link EVENT_ROUTE_FAILED} and then falls back to configured default
|
|
82
87
|
* route.
|
|
83
88
|
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* See {@link RouteAuthenticator} for details about `ctx` handling.
|
|
91
|
+
*
|
|
84
92
|
* @param src - route path to match
|
|
93
|
+
* @param ctx - arbitrary user context
|
|
85
94
|
*/
|
|
86
|
-
route(src) {
|
|
87
|
-
if (this.
|
|
95
|
+
route(src, ctx) {
|
|
96
|
+
if (this.opts.trim && src.charAt(src.length - 1) === this.opts.separator) {
|
|
88
97
|
src = src.substring(0, src.length - 1);
|
|
89
98
|
}
|
|
90
|
-
src = src.substring(this.
|
|
91
|
-
let match = this.matchRoutes(src);
|
|
99
|
+
src = src.substring(this.opts.prefix.length);
|
|
100
|
+
let match = this.matchRoutes(src, ctx);
|
|
92
101
|
if (!match) {
|
|
93
102
|
this.notify({ id: EVENT_ROUTE_FAILED, value: src });
|
|
94
103
|
if (!this.handleRouteFailure()) {
|
|
95
104
|
return;
|
|
96
105
|
}
|
|
97
|
-
const route = this.routeForID(this.
|
|
98
|
-
match = { id: route.id,
|
|
106
|
+
const route = this.routeForID(this.opts.default);
|
|
107
|
+
match = { id: route.id, redirect: true };
|
|
99
108
|
}
|
|
100
109
|
if (!equiv(match, this.current)) {
|
|
101
110
|
this.current = match;
|
|
@@ -104,9 +113,12 @@ let BasicRouter = class {
|
|
|
104
113
|
return match;
|
|
105
114
|
}
|
|
106
115
|
format(...args) {
|
|
107
|
-
let [id, params] = args;
|
|
116
|
+
let [id, params, rest] = args;
|
|
108
117
|
let match;
|
|
109
118
|
switch (args.length) {
|
|
119
|
+
case 3:
|
|
120
|
+
match = { id, params, rest };
|
|
121
|
+
break;
|
|
110
122
|
case 2:
|
|
111
123
|
match = { id, params };
|
|
112
124
|
break;
|
|
@@ -118,67 +130,81 @@ let BasicRouter = class {
|
|
|
118
130
|
}
|
|
119
131
|
const route = this.routeForID(match.id);
|
|
120
132
|
if (route) {
|
|
121
|
-
const params2 = match.params
|
|
122
|
-
|
|
133
|
+
const params2 = match.params;
|
|
134
|
+
let parts = route.match.map((x) => {
|
|
123
135
|
if (isRouteParam(x)) {
|
|
124
136
|
const id2 = x.substring(1);
|
|
125
|
-
const p = params2[id2];
|
|
126
|
-
if (p
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
const p = params2?.[id2];
|
|
138
|
+
if (p == null) {
|
|
139
|
+
illegalArgs(`missing value for param '${id2}'`);
|
|
140
|
+
}
|
|
141
|
+
return p;
|
|
129
142
|
}
|
|
130
143
|
return x;
|
|
131
|
-
})
|
|
144
|
+
});
|
|
145
|
+
if (route.rest >= 0)
|
|
146
|
+
parts = parts.slice(0, route.rest).concat(match.rest || []);
|
|
147
|
+
return this.opts.prefix + parts.join(this.opts.separator);
|
|
132
148
|
} else {
|
|
133
149
|
illegalArgs(`invalid route ID: ${match.id}`);
|
|
134
150
|
}
|
|
135
151
|
}
|
|
136
152
|
routeForID(id) {
|
|
137
|
-
return this.
|
|
153
|
+
return this.index[id];
|
|
138
154
|
}
|
|
139
|
-
|
|
140
|
-
this.
|
|
141
|
-
this.
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
augmentRoute(route) {
|
|
156
|
+
const match = isString(route.match) ? route.match.split(this.opts.separator).filter((x) => !!x) : route.match;
|
|
157
|
+
const existing = this.routes.get(match);
|
|
158
|
+
if (existing) {
|
|
159
|
+
illegalArgs(
|
|
160
|
+
`duplicate route: ${match} (id: ${route.id}, conflicts with: ${existing.id})`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
let hasParams = false;
|
|
164
|
+
const params = match.reduce((acc, x, i) => {
|
|
165
|
+
if (isRouteParam(x)) {
|
|
166
|
+
hasParams = true;
|
|
167
|
+
acc[i] = x.substring(1);
|
|
145
168
|
}
|
|
146
|
-
acc[fmt] = true;
|
|
147
169
|
return acc;
|
|
148
170
|
}, {});
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
171
|
+
return {
|
|
172
|
+
...route,
|
|
173
|
+
match,
|
|
174
|
+
params: hasParams ? params : void 0,
|
|
175
|
+
rest: match.indexOf("+")
|
|
176
|
+
};
|
|
153
177
|
}
|
|
154
|
-
matchRoutes(src) {
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
178
|
+
matchRoutes(src, ctx) {
|
|
179
|
+
const curr = src.split(this.opts.separator);
|
|
180
|
+
const route = this.routes.get(curr);
|
|
181
|
+
if (!route)
|
|
182
|
+
return;
|
|
183
|
+
let params;
|
|
184
|
+
if (route.params) {
|
|
185
|
+
params = Object.entries(route.params).reduce(
|
|
186
|
+
(acc, [i, k]) => (acc[k] = curr[+i], acc),
|
|
187
|
+
{}
|
|
188
|
+
);
|
|
162
189
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return;
|
|
190
|
+
if (route.validate && !this.validateRouteParams(params, route.validate)) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const rest = route.rest >= 0 ? curr.slice(route.rest) : void 0;
|
|
194
|
+
let match = {
|
|
195
|
+
id: route.id,
|
|
196
|
+
params,
|
|
197
|
+
rest
|
|
198
|
+
};
|
|
199
|
+
if (route.auth) {
|
|
200
|
+
match = this.opts.authenticator(match, route, ctx);
|
|
201
|
+
if (match && !this.index[match.id]) {
|
|
202
|
+
illegalState(
|
|
203
|
+
"auth handler returned invalid route ID: " + match.id
|
|
204
|
+
);
|
|
179
205
|
}
|
|
180
|
-
return route.auth ? this.config.authenticator(route, curr, params) : { id: route.id, title: route.title, params };
|
|
181
206
|
}
|
|
207
|
+
return match;
|
|
182
208
|
}
|
|
183
209
|
validateRouteParams(params, validators) {
|
|
184
210
|
for (let id in validators) {
|
|
@@ -201,10 +227,7 @@ let BasicRouter = class {
|
|
|
201
227
|
BasicRouter = __decorateClass([
|
|
202
228
|
INotifyMixin
|
|
203
229
|
], BasicRouter);
|
|
204
|
-
const isParametricRoute = (route) => route.match.some(isRouteParam);
|
|
205
230
|
const isRouteParam = (x) => x[0] === "?";
|
|
206
|
-
const defMatch = (pattern, sep = "/") => pattern.split(sep).filter((x) => !!x);
|
|
207
231
|
export {
|
|
208
|
-
BasicRouter
|
|
209
|
-
defMatch
|
|
232
|
+
BasicRouter
|
|
210
233
|
};
|
package/history.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Fn } from "@thi.ng/api";
|
|
2
2
|
import type { HTMLRouterConfig } from "./api.js";
|
|
3
3
|
import { BasicRouter } from "./basic.js";
|
|
4
|
-
export declare class HTMLRouter extends BasicRouter {
|
|
4
|
+
export declare class HTMLRouter<T = any> extends BasicRouter<T> {
|
|
5
5
|
protected currentPath: string;
|
|
6
6
|
protected popHandler: Fn<PopStateEvent, void>;
|
|
7
7
|
protected hashHandler: EventListener;
|
|
@@ -11,16 +11,19 @@ export declare class HTMLRouter extends BasicRouter {
|
|
|
11
11
|
start(): void;
|
|
12
12
|
release(): void;
|
|
13
13
|
/**
|
|
14
|
-
* Like
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
14
|
+
* Like {@link BasicRouter.route}, but takes additional arg to control if
|
|
15
|
+
* this routing operation should manipulate the browser's `history`.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* If called from userland, this normally is true (also default). However,
|
|
19
|
+
* we want to avoid this if called from this router's own event handlers.
|
|
18
20
|
*
|
|
19
21
|
* @param src -
|
|
22
|
+
* @param ctx -
|
|
20
23
|
* @param pushState -
|
|
21
24
|
*/
|
|
22
|
-
route(src: string, pushState?: boolean): import("./api.js").RouteMatch | undefined;
|
|
23
|
-
routeTo(route: string): void;
|
|
25
|
+
route(src: string, ctx?: T, pushState?: boolean): import("./api.js").RouteMatch | undefined;
|
|
26
|
+
routeTo(route: string, ctx?: T): void;
|
|
24
27
|
protected handlePopChange(): Fn<PopStateEvent, void>;
|
|
25
28
|
protected handleHashChange(): EventListener;
|
|
26
29
|
protected handleRouteFailure(): boolean;
|
package/history.js
CHANGED
|
@@ -16,14 +16,9 @@ class HTMLRouter extends BasicRouter {
|
|
|
16
16
|
if (this.useFragment) {
|
|
17
17
|
window.addEventListener("hashchange", this.handleHashChange());
|
|
18
18
|
}
|
|
19
|
-
if (this.
|
|
20
|
-
const route = this.routeForID(this.
|
|
21
|
-
this.route(
|
|
22
|
-
this.format({
|
|
23
|
-
id: route.id,
|
|
24
|
-
title: route.title
|
|
25
|
-
})
|
|
26
|
-
);
|
|
19
|
+
if (this.opts.initial) {
|
|
20
|
+
const route = this.routeForID(this.opts.initial);
|
|
21
|
+
this.route(this.format({ id: route.id }));
|
|
27
22
|
} else {
|
|
28
23
|
this.route(this.useFragment ? location.hash : location.pathname);
|
|
29
24
|
}
|
|
@@ -35,39 +30,39 @@ class HTMLRouter extends BasicRouter {
|
|
|
35
30
|
}
|
|
36
31
|
}
|
|
37
32
|
/**
|
|
38
|
-
* Like
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
33
|
+
* Like {@link BasicRouter.route}, but takes additional arg to control if
|
|
34
|
+
* this routing operation should manipulate the browser's `history`.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* If called from userland, this normally is true (also default). However,
|
|
38
|
+
* we want to avoid this if called from this router's own event handlers.
|
|
42
39
|
*
|
|
43
40
|
* @param src -
|
|
41
|
+
* @param ctx -
|
|
44
42
|
* @param pushState -
|
|
45
43
|
*/
|
|
46
|
-
route(src, pushState = true) {
|
|
44
|
+
route(src, ctx, pushState = true) {
|
|
47
45
|
const old = this.current;
|
|
48
|
-
const route = super.route(src);
|
|
46
|
+
const route = super.route(src, ctx);
|
|
49
47
|
if (route && !equiv(route, old)) {
|
|
50
48
|
this.currentPath = this.format(route);
|
|
51
49
|
if (pushState) {
|
|
52
|
-
history.pushState(
|
|
53
|
-
this.currentPath,
|
|
54
|
-
route.title || window.document.title || "",
|
|
55
|
-
this.currentPath
|
|
56
|
-
);
|
|
50
|
+
history.pushState(this.currentPath, "", this.currentPath);
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
return route;
|
|
60
54
|
}
|
|
61
|
-
routeTo(route) {
|
|
55
|
+
routeTo(route, ctx) {
|
|
62
56
|
if (this.useFragment) {
|
|
63
57
|
location.hash = route;
|
|
64
58
|
}
|
|
65
|
-
this.route(route);
|
|
59
|
+
this.route(route, ctx);
|
|
66
60
|
}
|
|
67
61
|
handlePopChange() {
|
|
68
62
|
return this.popHandler = this.popHandler || ((e) => {
|
|
69
63
|
this.route(
|
|
70
64
|
e.state || (this.useFragment ? location.hash : location.pathname),
|
|
65
|
+
void 0,
|
|
71
66
|
false
|
|
72
67
|
);
|
|
73
68
|
}).bind(this);
|
|
@@ -77,7 +72,7 @@ class HTMLRouter extends BasicRouter {
|
|
|
77
72
|
if (!this.ignoreHashChange) {
|
|
78
73
|
const hash = e.newURL.substring(e.newURL.indexOf("#"));
|
|
79
74
|
if (hash !== this.currentPath) {
|
|
80
|
-
this.route(hash, false);
|
|
75
|
+
this.route(hash, void 0, false);
|
|
81
76
|
}
|
|
82
77
|
}
|
|
83
78
|
}).bind(this);
|
|
@@ -85,7 +80,7 @@ class HTMLRouter extends BasicRouter {
|
|
|
85
80
|
handleRouteFailure() {
|
|
86
81
|
this.ignoreHashChange = true;
|
|
87
82
|
location.hash = this.format({
|
|
88
|
-
id: this.routeForID(this.
|
|
83
|
+
id: this.routeForID(this.opts.default).id
|
|
89
84
|
});
|
|
90
85
|
this.ignoreHashChange = false;
|
|
91
86
|
return true;
|
package/html.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Fn } from "@thi.ng/api";
|
|
2
|
+
import type { HTMLRouterOpts } from "./api.js";
|
|
3
|
+
import { Router } from "./router.js";
|
|
4
|
+
export declare class HTMLRouter<T = any> extends Router<T> {
|
|
5
|
+
protected currentPath: string;
|
|
6
|
+
protected popHandler: Fn<PopStateEvent, void>;
|
|
7
|
+
protected hashHandler: EventListener;
|
|
8
|
+
protected useFragment: boolean;
|
|
9
|
+
protected ignoreHashChange: boolean;
|
|
10
|
+
constructor(config: HTMLRouterOpts);
|
|
11
|
+
start(): void;
|
|
12
|
+
release(): void;
|
|
13
|
+
/**
|
|
14
|
+
* Like {@link Router.route}, but takes additional arg to control if
|
|
15
|
+
* this routing operation should manipulate the browser's `history`.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* If called from userland, this normally is true (also default). However,
|
|
19
|
+
* we want to avoid this if called from this router's own event handlers.
|
|
20
|
+
*
|
|
21
|
+
* @param src -
|
|
22
|
+
* @param ctx -
|
|
23
|
+
* @param pushState -
|
|
24
|
+
*/
|
|
25
|
+
route(src: string, ctx?: T, pushState?: boolean): import("./api.js").RouteMatch | undefined;
|
|
26
|
+
routeTo(route: string, ctx?: T): void;
|
|
27
|
+
protected handlePopChange(): Fn<PopStateEvent, void>;
|
|
28
|
+
protected handleHashChange(): EventListener;
|
|
29
|
+
protected handleRouteFailure(): boolean;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=html.d.ts.map
|
package/html.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { equiv } from "@thi.ng/equiv";
|
|
2
|
+
import { Router } from "./router.js";
|
|
3
|
+
class HTMLRouter extends Router {
|
|
4
|
+
currentPath;
|
|
5
|
+
popHandler;
|
|
6
|
+
hashHandler;
|
|
7
|
+
useFragment;
|
|
8
|
+
ignoreHashChange;
|
|
9
|
+
constructor(config) {
|
|
10
|
+
super({ prefix: config.useFragment ? "#/" : "/", ...config });
|
|
11
|
+
this.useFragment = config.useFragment !== false;
|
|
12
|
+
this.ignoreHashChange = false;
|
|
13
|
+
}
|
|
14
|
+
start() {
|
|
15
|
+
window.addEventListener("popstate", this.handlePopChange());
|
|
16
|
+
if (this.useFragment) {
|
|
17
|
+
window.addEventListener("hashchange", this.handleHashChange());
|
|
18
|
+
}
|
|
19
|
+
if (this.opts.initial) {
|
|
20
|
+
const route = this.routeForID(this.opts.initial);
|
|
21
|
+
this.route(this.format({ id: route.id }));
|
|
22
|
+
} else {
|
|
23
|
+
this.route(this.useFragment ? location.hash : location.pathname);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
release() {
|
|
27
|
+
window.removeEventListener("popstate", this.popHandler);
|
|
28
|
+
if (this.useFragment) {
|
|
29
|
+
window.removeEventListener("hashchange", this.hashHandler);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Like {@link Router.route}, but takes additional arg to control if
|
|
34
|
+
* this routing operation should manipulate the browser's `history`.
|
|
35
|
+
*
|
|
36
|
+
* @remarks
|
|
37
|
+
* If called from userland, this normally is true (also default). However,
|
|
38
|
+
* we want to avoid this if called from this router's own event handlers.
|
|
39
|
+
*
|
|
40
|
+
* @param src -
|
|
41
|
+
* @param ctx -
|
|
42
|
+
* @param pushState -
|
|
43
|
+
*/
|
|
44
|
+
route(src, ctx, pushState = true) {
|
|
45
|
+
const old = this.current;
|
|
46
|
+
const route = super.route(src, ctx);
|
|
47
|
+
if (route && !equiv(route, old)) {
|
|
48
|
+
this.currentPath = this.format(route);
|
|
49
|
+
if (pushState) {
|
|
50
|
+
history.pushState(this.currentPath, "", this.currentPath);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return route;
|
|
54
|
+
}
|
|
55
|
+
routeTo(route, ctx) {
|
|
56
|
+
if (this.useFragment) {
|
|
57
|
+
location.hash = route;
|
|
58
|
+
}
|
|
59
|
+
this.route(route, ctx);
|
|
60
|
+
}
|
|
61
|
+
handlePopChange() {
|
|
62
|
+
return this.popHandler = this.popHandler || ((e) => {
|
|
63
|
+
this.route(
|
|
64
|
+
e.state || (this.useFragment ? location.hash : location.pathname),
|
|
65
|
+
void 0,
|
|
66
|
+
false
|
|
67
|
+
);
|
|
68
|
+
}).bind(this);
|
|
69
|
+
}
|
|
70
|
+
handleHashChange() {
|
|
71
|
+
return this.hashHandler = this.hashHandler || ((e) => {
|
|
72
|
+
if (!this.ignoreHashChange) {
|
|
73
|
+
const hash = e.newURL.substring(e.newURL.indexOf("#"));
|
|
74
|
+
if (hash !== this.currentPath) {
|
|
75
|
+
this.route(hash, void 0, false);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}).bind(this);
|
|
79
|
+
}
|
|
80
|
+
handleRouteFailure() {
|
|
81
|
+
this.ignoreHashChange = true;
|
|
82
|
+
location.hash = this.format({
|
|
83
|
+
id: this.routeForID(this.opts.default).id
|
|
84
|
+
});
|
|
85
|
+
this.ignoreHashChange = false;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
HTMLRouter
|
|
91
|
+
};
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thi.ng/router",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Generic router for
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Generic trie-based router with support for wildcards, route param validation/coercion, auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./index.js",
|
|
7
7
|
"typings": "./index.d.ts",
|
|
@@ -36,18 +36,18 @@
|
|
|
36
36
|
"tool:tangle": "../../node_modules/.bin/tangle src/**/*.ts"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@thi.ng/api": "^8.9.
|
|
40
|
-
"@thi.ng/checks": "^3.5.
|
|
41
|
-
"@thi.ng/equiv": "^2.1.
|
|
42
|
-
"@thi.ng/errors": "^2.
|
|
39
|
+
"@thi.ng/api": "^8.9.30",
|
|
40
|
+
"@thi.ng/checks": "^3.5.3",
|
|
41
|
+
"@thi.ng/equiv": "^2.1.52",
|
|
42
|
+
"@thi.ng/errors": "^2.5.0",
|
|
43
43
|
"tslib": "^2.6.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@microsoft/api-extractor": "^7.
|
|
47
|
-
"esbuild": "^0.20.
|
|
46
|
+
"@microsoft/api-extractor": "^7.42.3",
|
|
47
|
+
"esbuild": "^0.20.1",
|
|
48
48
|
"rimraf": "^5.0.5",
|
|
49
|
-
"typedoc": "^0.25.
|
|
50
|
-
"typescript": "^5.
|
|
49
|
+
"typedoc": "^0.25.12",
|
|
50
|
+
"typescript": "^5.4.2"
|
|
51
51
|
},
|
|
52
52
|
"keywords": [
|
|
53
53
|
"browser",
|
|
@@ -56,10 +56,13 @@
|
|
|
56
56
|
"history",
|
|
57
57
|
"html",
|
|
58
58
|
"parametric",
|
|
59
|
+
"pattern",
|
|
59
60
|
"router",
|
|
61
|
+
"trie",
|
|
60
62
|
"typescript",
|
|
61
63
|
"ui",
|
|
62
|
-
"validate"
|
|
64
|
+
"validate",
|
|
65
|
+
"wildcards"
|
|
63
66
|
],
|
|
64
67
|
"publishConfig": {
|
|
65
68
|
"access": "public"
|
|
@@ -78,11 +81,14 @@
|
|
|
78
81
|
"./api": {
|
|
79
82
|
"default": "./api.js"
|
|
80
83
|
},
|
|
81
|
-
"./
|
|
82
|
-
"default": "./
|
|
84
|
+
"./html": {
|
|
85
|
+
"default": "./html.js"
|
|
83
86
|
},
|
|
84
|
-
"./
|
|
85
|
-
"default": "./
|
|
87
|
+
"./router": {
|
|
88
|
+
"default": "./router.js"
|
|
89
|
+
},
|
|
90
|
+
"./trie": {
|
|
91
|
+
"default": "./trie.js"
|
|
86
92
|
}
|
|
87
93
|
},
|
|
88
94
|
"thi.ng": {
|
|
@@ -92,5 +98,5 @@
|
|
|
92
98
|
],
|
|
93
99
|
"year": 2014
|
|
94
100
|
},
|
|
95
|
-
"gitHead": "
|
|
101
|
+
"gitHead": "7f3fcbd6c0462b0ce45afa141fe163d1f297fd51\n"
|
|
96
102
|
}
|
package/router.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Event, INotify, IObjectOf, Listener, SomeRequired } from "@thi.ng/api";
|
|
2
|
+
import { type AugmentedRoute, type Route, type RouteMatch, type RouteParamValidator, type RouterEventType, type RouterOpts } from "./api.js";
|
|
3
|
+
import { Trie } from "./trie.js";
|
|
4
|
+
export declare class Router<T = any> implements INotify<RouterEventType> {
|
|
5
|
+
opts: RouterOpts<T>;
|
|
6
|
+
current: RouteMatch | undefined;
|
|
7
|
+
protected index: Record<string, AugmentedRoute>;
|
|
8
|
+
protected routes: Trie<AugmentedRoute>;
|
|
9
|
+
constructor(config: RouterOpts<T>);
|
|
10
|
+
addListener(id: RouterEventType, fn: Listener<RouterEventType>, scope?: any): boolean;
|
|
11
|
+
removeListener(id: RouterEventType, fn: Listener<RouterEventType>, scope?: any): boolean;
|
|
12
|
+
notify(event: Event<RouterEventType>): boolean;
|
|
13
|
+
start(): void;
|
|
14
|
+
addRoutes(routes: Route[]): void;
|
|
15
|
+
/**
|
|
16
|
+
* Main router function. Attempts to match given input string against all
|
|
17
|
+
* configured routes. Before returning, triggers {@link EVENT_ROUTE_CHANGED}
|
|
18
|
+
* with return value as well. If none of the routes matches, emits
|
|
19
|
+
* {@link EVENT_ROUTE_FAILED} and then falls back to configured default
|
|
20
|
+
* route.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* See {@link RouteAuthenticator} for details about `ctx` handling.
|
|
24
|
+
*
|
|
25
|
+
* @param src - route path to match
|
|
26
|
+
* @param ctx - arbitrary user context
|
|
27
|
+
*/
|
|
28
|
+
route(src: string, ctx?: T): RouteMatch | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Returns a formatted version of given {@link RouteMatch}, incl. any
|
|
31
|
+
* params, or alternatively a registered route ID (and optional route
|
|
32
|
+
* params). Throws an error if an invalid route `id` is provided.
|
|
33
|
+
*
|
|
34
|
+
* @param id -
|
|
35
|
+
* @param params -
|
|
36
|
+
* @param rest -
|
|
37
|
+
*/
|
|
38
|
+
format(id: string, params?: any, rest?: string[]): string;
|
|
39
|
+
format(match: SomeRequired<RouteMatch, "id">): string;
|
|
40
|
+
routeForID(id: string): AugmentedRoute | undefined;
|
|
41
|
+
protected augmentRoute(route: Route): AugmentedRoute;
|
|
42
|
+
protected matchRoutes(src: string, ctx?: T): RouteMatch | undefined;
|
|
43
|
+
protected validateRouteParams(params: any, validators: IObjectOf<Partial<RouteParamValidator>>): boolean;
|
|
44
|
+
protected handleRouteFailure(): boolean;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=router.d.ts.map
|