@thi.ng/router 3.2.42 → 3.2.44

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # Change Log
2
2
 
3
- - **Last updated**: 2023-12-03T12:13:31Z
3
+ - **Last updated**: 2023-12-11T10:07:09Z
4
4
  - **Generator**: [thi.ng/monopub](https://thi.ng/monopub)
5
5
 
6
6
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -68,7 +68,7 @@ For Node.js REPL:
68
68
  const router = await import("@thi.ng/router");
69
69
  ```
70
70
 
71
- Package sizes (brotli'd, pre-treeshake): ESM: 1.50 KB
71
+ Package sizes (brotli'd, pre-treeshake): ESM: 1.57 KB
72
72
 
73
73
  ## Dependencies
74
74
 
package/api.js CHANGED
@@ -1,5 +1,6 @@
1
- /**
2
- * ID of success event being triggered by `router.match()`
3
- */
4
- export const EVENT_ROUTE_CHANGED = "route-changed";
5
- export const EVENT_ROUTE_FAILED = "route-failed";
1
+ const EVENT_ROUTE_CHANGED = "route-changed";
2
+ const EVENT_ROUTE_FAILED = "route-failed";
3
+ export {
4
+ EVENT_ROUTE_CHANGED,
5
+ EVENT_ROUTE_FAILED
6
+ };
package/basic.js CHANGED
@@ -1,174 +1,195 @@
1
- import { __decorate } from "tslib";
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result)
9
+ __defProp(target, key, result);
10
+ return result;
11
+ };
2
12
  import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
3
13
  import { isString } from "@thi.ng/checks/is-string";
4
14
  import { equiv } from "@thi.ng/equiv";
5
15
  import { assert } from "@thi.ng/errors/assert";
6
16
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
7
17
  import { illegalArity } from "@thi.ng/errors/illegal-arity";
8
- import { EVENT_ROUTE_CHANGED, EVENT_ROUTE_FAILED, } from "./api.js";
9
- let BasicRouter = class BasicRouter {
10
- config;
11
- current;
12
- routeIndex;
13
- constructor(config) {
14
- this.config = {
15
- authenticator: (route, _, params) => ({
16
- id: route.id,
17
- title: route.title,
18
- params,
19
- }),
20
- prefix: "/",
21
- separator: "/",
22
- removeTrailingSlash: true,
23
- ...config,
24
- };
25
- this.routeIndex = this.config.routes.reduce((acc, r) => ((acc[r.id] = r), acc), {});
26
- assert(this.routeForID(this.config.defaultRouteID) !== undefined, `missing config for default route: '${this.config.defaultRouteID}'`);
27
- if (config.initialRouteID) {
28
- const route = this.routeForID(config.initialRouteID);
29
- assert(route !== undefined, `missing config for initial route: ${this.config.initialRouteID}`);
30
- assert(!isParametricRoute(route), "initial route MUST not be parametric");
31
- }
18
+ import {
19
+ EVENT_ROUTE_CHANGED,
20
+ EVENT_ROUTE_FAILED
21
+ } from "./api.js";
22
+ let BasicRouter = class {
23
+ config;
24
+ current;
25
+ routeIndex;
26
+ constructor(config) {
27
+ this.config = {
28
+ authenticator: (route, _, params) => ({
29
+ id: route.id,
30
+ title: route.title,
31
+ params
32
+ }),
33
+ prefix: "/",
34
+ separator: "/",
35
+ removeTrailingSlash: true,
36
+ ...config
37
+ };
38
+ this.routeIndex = this.config.routes.reduce(
39
+ (acc, r) => (acc[r.id] = r, acc),
40
+ {}
41
+ );
42
+ assert(
43
+ this.routeForID(this.config.defaultRouteID) !== void 0,
44
+ `missing config for default route: '${this.config.defaultRouteID}'`
45
+ );
46
+ if (config.initialRouteID) {
47
+ const route = this.routeForID(config.initialRouteID);
48
+ assert(
49
+ route !== void 0,
50
+ `missing config for initial route: ${this.config.initialRouteID}`
51
+ );
52
+ assert(
53
+ !isParametricRoute(route),
54
+ "initial route MUST not be parametric"
55
+ );
32
56
  }
33
- /** {@inheritDoc @thi.ng/api#INotify.addListener} */
34
- // @ts-ignore: arguments
35
- // prettier-ignore
36
- addListener(id, fn, scope) { }
37
- /** {@inheritDoc @thi.ng/api#INotify.removeListener} */
38
- // @ts-ignore: arguments
39
- // prettier-ignore
40
- removeListener(id, fn, scope) { }
41
- /** {@inheritDoc @thi.ng/api#INotify.notify} */
42
- // @ts-ignore: arguments
43
- notify(event) { }
44
- start() {
45
- if (this.config.initialRouteID) {
46
- const route = this.routeForID(this.config.initialRouteID);
47
- this.current = { id: route.id, title: route.title, params: {} };
48
- this.notify({ id: EVENT_ROUTE_CHANGED, value: this.current });
49
- }
57
+ }
58
+ /** {@inheritDoc @thi.ng/api#INotify.addListener} */
59
+ // @ts-ignore: arguments
60
+ // prettier-ignore
61
+ addListener(id, fn, scope) {
62
+ }
63
+ /** {@inheritDoc @thi.ng/api#INotify.removeListener} */
64
+ // @ts-ignore: arguments
65
+ // prettier-ignore
66
+ removeListener(id, fn, scope) {
67
+ }
68
+ /** {@inheritDoc @thi.ng/api#INotify.notify} */
69
+ // @ts-ignore: arguments
70
+ notify(event) {
71
+ }
72
+ start() {
73
+ if (this.config.initialRouteID) {
74
+ const route = this.routeForID(this.config.initialRouteID);
75
+ this.current = { id: route.id, title: route.title, params: {} };
76
+ this.notify({ id: EVENT_ROUTE_CHANGED, value: this.current });
50
77
  }
51
- /**
52
- * Main router function. Attempts to match given input string against all
53
- * configured routes. Before returning, triggers {@link EVENT_ROUTE_CHANGED}
54
- * with return value as well. If none of the routes matches, emits
55
- * {@link EVENT_ROUTE_FAILED} and then falls back to configured default
56
- * route.
57
- *
58
- * @param src - route path to match
59
- */
60
- route(src) {
61
- if (this.config.removeTrailingSlash &&
62
- src.charAt(src.length - 1) === this.config.separator) {
63
- src = src.substring(0, src.length - 1);
64
- }
65
- src = src.substring(this.config.prefix.length);
66
- let match = this.matchRoutes(src);
67
- if (!match) {
68
- this.notify({ id: EVENT_ROUTE_FAILED, value: src });
69
- if (!this.handleRouteFailure()) {
70
- return;
71
- }
72
- const route = this.routeForID(this.config.defaultRouteID);
73
- match = { id: route.id, title: route.title, params: {} };
74
- }
75
- if (!equiv(match, this.current)) {
76
- this.current = match;
77
- this.notify({ id: EVENT_ROUTE_CHANGED, value: match });
78
- }
79
- return match;
78
+ }
79
+ /**
80
+ * Main router function. Attempts to match given input string against all
81
+ * configured routes. Before returning, triggers {@link EVENT_ROUTE_CHANGED}
82
+ * with return value as well. If none of the routes matches, emits
83
+ * {@link EVENT_ROUTE_FAILED} and then falls back to configured default
84
+ * route.
85
+ *
86
+ * @param src - route path to match
87
+ */
88
+ route(src) {
89
+ if (this.config.removeTrailingSlash && src.charAt(src.length - 1) === this.config.separator) {
90
+ src = src.substring(0, src.length - 1);
80
91
  }
81
- format(...args) {
82
- let [id, params] = args;
83
- let match;
84
- switch (args.length) {
85
- case 2:
86
- match = { id, params };
87
- break;
88
- case 1:
89
- match = isString(id) ? { id } : id;
90
- break;
91
- default:
92
- illegalArity(args.length);
93
- }
94
- const route = this.routeForID(match.id);
95
- if (route) {
96
- const params = match.params || {};
97
- return (this.config.prefix +
98
- route.match
99
- .map((x) => {
100
- if (isRouteParam(x)) {
101
- const id = x.substring(1);
102
- const p = params[id];
103
- if (p != null)
104
- return p;
105
- illegalArgs(`missing value for param '${id}'`);
106
- }
107
- return x;
108
- })
109
- .join(this.config.separator));
110
- }
111
- else {
112
- illegalArgs(`invalid route ID: ${match.id}`);
113
- }
92
+ src = src.substring(this.config.prefix.length);
93
+ let match = this.matchRoutes(src);
94
+ if (!match) {
95
+ this.notify({ id: EVENT_ROUTE_FAILED, value: src });
96
+ if (!this.handleRouteFailure()) {
97
+ return;
98
+ }
99
+ const route = this.routeForID(this.config.defaultRouteID);
100
+ match = { id: route.id, title: route.title, params: {} };
114
101
  }
115
- routeForID(id) {
116
- return this.routeIndex[id];
102
+ if (!equiv(match, this.current)) {
103
+ this.current = match;
104
+ this.notify({ id: EVENT_ROUTE_CHANGED, value: match });
117
105
  }
118
- matchRoutes(src) {
119
- const routes = this.config.routes;
120
- const curr = src.split(this.config.separator);
121
- for (let i = 0, n = routes.length; i < n; i++) {
122
- const match = this.matchRoute(curr, routes[i]);
123
- if (match) {
124
- return match;
125
- }
126
- }
106
+ return match;
107
+ }
108
+ format(...args) {
109
+ let [id, params] = args;
110
+ let match;
111
+ switch (args.length) {
112
+ case 2:
113
+ match = { id, params };
114
+ break;
115
+ case 1:
116
+ match = isString(id) ? { id } : id;
117
+ break;
118
+ default:
119
+ illegalArity(args.length);
127
120
  }
128
- matchRoute(curr, route) {
129
- const match = route.match;
130
- const n = match.length;
131
- if (curr.length === n) {
132
- const params = {};
133
- for (let i = 0; i < n; i++) {
134
- const m = match[i];
135
- if (isRouteParam(m)) {
136
- params[m.substring(1)] = curr[i];
137
- }
138
- else if (curr[i] !== m) {
139
- return;
140
- }
141
- }
142
- if (route.validate &&
143
- !this.validateRouteParams(params, route.validate)) {
144
- return;
145
- }
146
- return route.auth
147
- ? this.config.authenticator(route, curr, params)
148
- : { id: route.id, title: route.title, params };
121
+ const route = this.routeForID(match.id);
122
+ if (route) {
123
+ const params2 = match.params || {};
124
+ return this.config.prefix + route.match.map((x) => {
125
+ if (isRouteParam(x)) {
126
+ const id2 = x.substring(1);
127
+ const p = params2[id2];
128
+ if (p != null)
129
+ return p;
130
+ illegalArgs(`missing value for param '${id2}'`);
149
131
  }
132
+ return x;
133
+ }).join(this.config.separator);
134
+ } else {
135
+ illegalArgs(`invalid route ID: ${match.id}`);
136
+ }
137
+ }
138
+ routeForID(id) {
139
+ return this.routeIndex[id];
140
+ }
141
+ matchRoutes(src) {
142
+ const routes = this.config.routes;
143
+ const curr = src.split(this.config.separator);
144
+ for (let i = 0, n = routes.length; i < n; i++) {
145
+ const match = this.matchRoute(curr, routes[i]);
146
+ if (match) {
147
+ return match;
148
+ }
150
149
  }
151
- validateRouteParams(params, validators) {
152
- for (let id in validators) {
153
- if (params[id] !== undefined) {
154
- const val = validators[id];
155
- if (val.coerce) {
156
- params[id] = val.coerce(params[id]);
157
- }
158
- if (val.check && !val.check(params[id])) {
159
- return false;
160
- }
161
- }
150
+ }
151
+ matchRoute(curr, route) {
152
+ const match = route.match;
153
+ const n = match.length;
154
+ if (curr.length === n) {
155
+ const params = {};
156
+ for (let i = 0; i < n; i++) {
157
+ const m = match[i];
158
+ if (isRouteParam(m)) {
159
+ params[m.substring(1)] = curr[i];
160
+ } else if (curr[i] !== m) {
161
+ return;
162
162
  }
163
- return true;
163
+ }
164
+ if (route.validate && !this.validateRouteParams(params, route.validate)) {
165
+ return;
166
+ }
167
+ return route.auth ? this.config.authenticator(route, curr, params) : { id: route.id, title: route.title, params };
164
168
  }
165
- handleRouteFailure() {
166
- return true;
169
+ }
170
+ validateRouteParams(params, validators) {
171
+ for (let id in validators) {
172
+ if (params[id] !== void 0) {
173
+ const val = validators[id];
174
+ if (val.coerce) {
175
+ params[id] = val.coerce(params[id]);
176
+ }
177
+ if (val.check && !val.check(params[id])) {
178
+ return false;
179
+ }
180
+ }
167
181
  }
182
+ return true;
183
+ }
184
+ handleRouteFailure() {
185
+ return true;
186
+ }
168
187
  };
169
- BasicRouter = __decorate([
170
- INotifyMixin
188
+ BasicRouter = __decorateClass([
189
+ INotifyMixin
171
190
  ], BasicRouter);
172
- export { BasicRouter };
173
191
  const isParametricRoute = (route) => route.match.some(isRouteParam);
174
192
  const isRouteParam = (x) => x[0] === "?";
193
+ export {
194
+ BasicRouter
195
+ };
package/history.js CHANGED
@@ -1,90 +1,96 @@
1
1
  import { equiv } from "@thi.ng/equiv";
2
2
  import { BasicRouter } from "./basic.js";
3
- export class HTMLRouter extends BasicRouter {
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;
3
+ class HTMLRouter extends BasicRouter {
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());
13
18
  }
14
- start() {
15
- window.addEventListener("popstate", this.handlePopChange());
16
- if (this.useFragment) {
17
- window.addEventListener("hashchange", this.handleHashChange());
18
- }
19
- if (this.config.initialRouteID) {
20
- const route = this.routeForID(this.config.initialRouteID);
21
- this.route(this.format({
22
- id: route.id,
23
- title: route.title,
24
- }));
25
- }
26
- else {
27
- this.route(this.useFragment ? location.hash : location.pathname);
28
- }
29
- }
30
- release() {
31
- window.removeEventListener("popstate", this.popHandler);
32
- if (this.useFragment) {
33
- window.removeEventListener("hashchange", this.hashHandler);
34
- }
19
+ if (this.config.initialRouteID) {
20
+ const route = this.routeForID(this.config.initialRouteID);
21
+ this.route(
22
+ this.format({
23
+ id: route.id,
24
+ title: route.title
25
+ })
26
+ );
27
+ } else {
28
+ this.route(this.useFragment ? location.hash : location.pathname);
35
29
  }
36
- /**
37
- * Like `BasicRouter.route()`, but takes additional arg to control
38
- * if this routing operation should manipulate the browser's `history`.
39
- * If called from userland, this normally is true. However, we want
40
- * to avoid this if called from this router's own event handlers.
41
- *
42
- * @param src -
43
- * @param pushState -
44
- */
45
- route(src, pushState = true) {
46
- const old = this.current;
47
- const route = super.route(src);
48
- if (route && !equiv(route, old)) {
49
- this.currentPath = this.format(route);
50
- if (pushState) {
51
- history.pushState(this.currentPath, route.title || window.document.title || "", this.currentPath);
52
- }
53
- }
54
- return route;
55
- }
56
- routeTo(route) {
57
- if (this.useFragment) {
58
- location.hash = route;
59
- }
60
- this.route(route);
30
+ }
31
+ release() {
32
+ window.removeEventListener("popstate", this.popHandler);
33
+ if (this.useFragment) {
34
+ window.removeEventListener("hashchange", this.hashHandler);
61
35
  }
62
- handlePopChange() {
63
- return (this.popHandler =
64
- this.popHandler ||
65
- ((e) => {
66
- this.route(e.state ||
67
- (this.useFragment ? location.hash : location.pathname), false);
68
- }).bind(this));
36
+ }
37
+ /**
38
+ * Like `BasicRouter.route()`, but takes additional arg to control
39
+ * if this routing operation should manipulate the browser's `history`.
40
+ * If called from userland, this normally is true. However, we want
41
+ * to avoid this if called from this router's own event handlers.
42
+ *
43
+ * @param src -
44
+ * @param pushState -
45
+ */
46
+ route(src, pushState = true) {
47
+ const old = this.current;
48
+ const route = super.route(src);
49
+ if (route && !equiv(route, old)) {
50
+ this.currentPath = this.format(route);
51
+ if (pushState) {
52
+ history.pushState(
53
+ this.currentPath,
54
+ route.title || window.document.title || "",
55
+ this.currentPath
56
+ );
57
+ }
69
58
  }
70
- handleHashChange() {
71
- return (this.hashHandler =
72
- this.hashHandler ||
73
- ((e) => {
74
- if (!this.ignoreHashChange) {
75
- const hash = e.newURL.substring(e.newURL.indexOf("#"));
76
- if (hash !== this.currentPath) {
77
- this.route(hash, false);
78
- }
79
- }
80
- }).bind(this));
81
- }
82
- handleRouteFailure() {
83
- this.ignoreHashChange = true;
84
- location.hash = this.format({
85
- id: this.routeForID(this.config.defaultRouteID).id,
86
- });
87
- this.ignoreHashChange = false;
88
- return true;
59
+ return route;
60
+ }
61
+ routeTo(route) {
62
+ if (this.useFragment) {
63
+ location.hash = route;
89
64
  }
65
+ this.route(route);
66
+ }
67
+ handlePopChange() {
68
+ return this.popHandler = this.popHandler || ((e) => {
69
+ this.route(
70
+ e.state || (this.useFragment ? location.hash : location.pathname),
71
+ false
72
+ );
73
+ }).bind(this);
74
+ }
75
+ handleHashChange() {
76
+ return this.hashHandler = this.hashHandler || ((e) => {
77
+ if (!this.ignoreHashChange) {
78
+ const hash = e.newURL.substring(e.newURL.indexOf("#"));
79
+ if (hash !== this.currentPath) {
80
+ this.route(hash, false);
81
+ }
82
+ }
83
+ }).bind(this);
84
+ }
85
+ handleRouteFailure() {
86
+ this.ignoreHashChange = true;
87
+ location.hash = this.format({
88
+ id: this.routeForID(this.config.defaultRouteID).id
89
+ });
90
+ this.ignoreHashChange = false;
91
+ return true;
92
+ }
90
93
  }
94
+ export {
95
+ HTMLRouter
96
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/router",
3
- "version": "3.2.42",
3
+ "version": "3.2.44",
4
4
  "description": "Generic router for browser & non-browser based applications",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -24,7 +24,9 @@
24
24
  "author": "Karsten Schmidt (https://thi.ng)",
25
25
  "license": "Apache-2.0",
26
26
  "scripts": {
27
- "build": "yarn clean && tsc --declaration",
27
+ "build": "yarn build:esbuild && yarn build:decl",
28
+ "build:decl": "tsc --declaration --emitDeclarationOnly",
29
+ "build:esbuild": "esbuild --format=esm --platform=neutral --target=es2022 --tsconfig=tsconfig.json --outdir=. src/**/*.ts",
28
30
  "clean": "rimraf --glob '*.js' '*.d.ts' '*.map' doc",
29
31
  "doc": "typedoc --excludePrivate --excludeInternal --out doc src/index.ts",
30
32
  "doc:ae": "mkdir -p .ae/doc .ae/temp && api-extractor run --local --verbose",
@@ -33,15 +35,15 @@
33
35
  "test": "bun test"
34
36
  },
35
37
  "dependencies": {
36
- "@thi.ng/api": "^8.9.10",
37
- "@thi.ng/checks": "^3.4.10",
38
- "@thi.ng/equiv": "^2.1.35",
39
- "@thi.ng/errors": "^2.4.4",
38
+ "@thi.ng/api": "^8.9.12",
39
+ "@thi.ng/checks": "^3.4.12",
40
+ "@thi.ng/equiv": "^2.1.37",
41
+ "@thi.ng/errors": "^2.4.6",
40
42
  "tslib": "^2.6.2"
41
43
  },
42
44
  "devDependencies": {
43
45
  "@microsoft/api-extractor": "^7.38.3",
44
- "@thi.ng/testament": "^0.4.3",
46
+ "esbuild": "^0.19.8",
45
47
  "rimraf": "^5.0.5",
46
48
  "tools": "^0.0.1",
47
49
  "typedoc": "^0.25.4",
@@ -90,5 +92,5 @@
90
92
  ],
91
93
  "year": 2014
92
94
  },
93
- "gitHead": "04d1de79f256d7a53c6b5fd157b37f49bc88e11d\n"
95
+ "gitHead": "5e7bafedfc3d53bc131469a28de31dd8e5b4a3ff\n"
94
96
  }