@thi.ng/router 3.3.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 +49 -1
- package/README.md +85 -40
- package/api.d.ts +86 -36
- package/basic.d.ts +20 -14
- package/basic.js +102 -77
- 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,45 +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.
|
|
39
|
-
this.routeIndex = this.config.routes.reduce(
|
|
40
|
-
(acc, r) => (acc[r.id] = r, acc),
|
|
41
|
-
{}
|
|
42
|
-
);
|
|
37
|
+
this.addRoutes(this.opts.routes);
|
|
43
38
|
assert(
|
|
44
|
-
this.routeForID(this.
|
|
45
|
-
`missing config for default route: '${this.
|
|
39
|
+
this.routeForID(this.opts.default) !== void 0,
|
|
40
|
+
`missing config for default route: '${this.opts.default}'`
|
|
46
41
|
);
|
|
47
|
-
if (config.
|
|
48
|
-
const route = this.routeForID(config.
|
|
42
|
+
if (config.initial) {
|
|
43
|
+
const route = this.routeForID(config.initial);
|
|
49
44
|
assert(
|
|
50
45
|
route !== void 0,
|
|
51
|
-
`missing config for initial route: ${this.
|
|
52
|
-
);
|
|
53
|
-
assert(
|
|
54
|
-
!isParametricRoute(route),
|
|
55
|
-
"initial route MUST not be parametric"
|
|
46
|
+
`missing config for initial route: ${this.opts.initial}`
|
|
56
47
|
);
|
|
48
|
+
assert(!route.params, "initial route MUST not be parametric");
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
// @ts-ignore: arguments
|
|
@@ -68,15 +60,24 @@ let BasicRouter = class {
|
|
|
68
60
|
notify(event) {
|
|
69
61
|
}
|
|
70
62
|
start() {
|
|
71
|
-
if (this.
|
|
72
|
-
const route = this.routeForID(this.
|
|
73
|
-
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: {} };
|
|
74
66
|
this.notify({ id: EVENT_ROUTE_CHANGED, value: this.current });
|
|
75
67
|
}
|
|
76
68
|
}
|
|
77
|
-
addRoutes(
|
|
78
|
-
|
|
79
|
-
|
|
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
|
+
}
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Main router function. Attempts to match given input string against all
|
|
@@ -85,21 +86,25 @@ let BasicRouter = class {
|
|
|
85
86
|
* {@link EVENT_ROUTE_FAILED} and then falls back to configured default
|
|
86
87
|
* route.
|
|
87
88
|
*
|
|
89
|
+
* @remarks
|
|
90
|
+
* See {@link RouteAuthenticator} for details about `ctx` handling.
|
|
91
|
+
*
|
|
88
92
|
* @param src - route path to match
|
|
93
|
+
* @param ctx - arbitrary user context
|
|
89
94
|
*/
|
|
90
|
-
route(src) {
|
|
91
|
-
if (this.
|
|
95
|
+
route(src, ctx) {
|
|
96
|
+
if (this.opts.trim && src.charAt(src.length - 1) === this.opts.separator) {
|
|
92
97
|
src = src.substring(0, src.length - 1);
|
|
93
98
|
}
|
|
94
|
-
src = src.substring(this.
|
|
95
|
-
let match = this.matchRoutes(src);
|
|
99
|
+
src = src.substring(this.opts.prefix.length);
|
|
100
|
+
let match = this.matchRoutes(src, ctx);
|
|
96
101
|
if (!match) {
|
|
97
102
|
this.notify({ id: EVENT_ROUTE_FAILED, value: src });
|
|
98
103
|
if (!this.handleRouteFailure()) {
|
|
99
104
|
return;
|
|
100
105
|
}
|
|
101
|
-
const route = this.routeForID(this.
|
|
102
|
-
match = { id: route.id,
|
|
106
|
+
const route = this.routeForID(this.opts.default);
|
|
107
|
+
match = { id: route.id, redirect: true };
|
|
103
108
|
}
|
|
104
109
|
if (!equiv(match, this.current)) {
|
|
105
110
|
this.current = match;
|
|
@@ -108,9 +113,12 @@ let BasicRouter = class {
|
|
|
108
113
|
return match;
|
|
109
114
|
}
|
|
110
115
|
format(...args) {
|
|
111
|
-
let [id, params] = args;
|
|
116
|
+
let [id, params, rest] = args;
|
|
112
117
|
let match;
|
|
113
118
|
switch (args.length) {
|
|
119
|
+
case 3:
|
|
120
|
+
match = { id, params, rest };
|
|
121
|
+
break;
|
|
114
122
|
case 2:
|
|
115
123
|
match = { id, params };
|
|
116
124
|
break;
|
|
@@ -122,63 +130,81 @@ let BasicRouter = class {
|
|
|
122
130
|
}
|
|
123
131
|
const route = this.routeForID(match.id);
|
|
124
132
|
if (route) {
|
|
125
|
-
const params2 = match.params
|
|
126
|
-
|
|
133
|
+
const params2 = match.params;
|
|
134
|
+
let parts = route.match.map((x) => {
|
|
127
135
|
if (isRouteParam(x)) {
|
|
128
136
|
const id2 = x.substring(1);
|
|
129
|
-
const p = params2[id2];
|
|
130
|
-
if (p
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
const p = params2?.[id2];
|
|
138
|
+
if (p == null) {
|
|
139
|
+
illegalArgs(`missing value for param '${id2}'`);
|
|
140
|
+
}
|
|
141
|
+
return p;
|
|
133
142
|
}
|
|
134
143
|
return x;
|
|
135
|
-
})
|
|
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);
|
|
136
148
|
} else {
|
|
137
149
|
illegalArgs(`invalid route ID: ${match.id}`);
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
152
|
routeForID(id) {
|
|
141
|
-
return this.
|
|
153
|
+
return this.index[id];
|
|
142
154
|
}
|
|
143
|
-
|
|
144
|
-
this.
|
|
145
|
-
this.
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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);
|
|
149
168
|
}
|
|
150
|
-
acc[fmt] = true;
|
|
151
169
|
return acc;
|
|
152
170
|
}, {});
|
|
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,7 +227,6 @@ 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
231
|
export {
|
|
207
232
|
BasicRouter
|
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
|