@manyducks.co/dolla 2.0.0-alpha.36 → 2.0.0-alpha.37
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 +1 -1
- package/dist/core/dolla.d.ts +1 -1
- package/dist/core/signals.d.ts +0 -3
- package/dist/http/index.d.ts +0 -1
- package/dist/index.js +1234 -854
- package/dist/index.js.map +1 -1
- package/dist/jsx-dev-runtime.js +20 -14
- package/dist/jsx-dev-runtime.js.map +1 -1
- package/dist/jsx-runtime.js +27 -16
- package/dist/jsx-runtime.js.map +1 -1
- package/dist/markup-Px0heVon.js +1465 -0
- package/dist/markup-Px0heVon.js.map +1 -0
- package/dist/typeChecking.test.d.ts +1 -0
- package/dist/types.d.ts +0 -1
- package/package.json +2 -1
- package/vite.config.js +5 -3
- package/dist/core/_batch.d.ts +0 -21
- package/dist/core/_signals_new.d.ts +0 -130
- package/dist/markup-ILMFXzoo.js +0 -1442
- package/dist/markup-ILMFXzoo.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,261 +1,336 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
set _(r) {
|
|
10
|
-
d(i, t, r, e);
|
|
11
|
-
},
|
|
12
|
-
get _() {
|
|
13
|
-
return a(i, t, s);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
import { a as Ft, b as Kt, i as B, I as Ot, c as Ut, d as U, g as E, s as _t, e as Qt, f as M, h as V, P as jt, j as Jt, t as St, k as Mt, l as Xt, m as xt, S as Yt, n as Tt, o as Nt, p as Zt, V as te, q as ee, r as se, u as ne, v as re } from "./markup-ILMFXzoo.js";
|
|
17
|
-
import { w as Ue, B as je, y as Ce, A as De, x as Ie, z as Ve } from "./markup-ILMFXzoo.js";
|
|
18
|
-
function Ne(i) {
|
|
19
|
-
return () => {
|
|
20
|
-
if (arguments.length === 1)
|
|
21
|
-
i = arguments[0];
|
|
22
|
-
else if (arguments.length > 1)
|
|
1
|
+
import { a as assertArrayOf, b as assertString, i as isFunction, I as IS_ROUTER, c as atom, d as compose, g as get, s as shallowEqual, e as assertObject, f as isString, h as isObject, P as Passthrough, j as deepEqual, t as typeOf, k as cond, l as html, m as createMatcher, S as Store, n as StoreError, o as assertInstanceOf, p as createMarkup, V as View, q as groupElements, r as constructMarkup, u as noOp, v as okhash } from './markup-Px0heVon.js';
|
|
2
|
+
export { w as effect, B as list, y as peek, A as portal, x as set, z as strictEqual } from './markup-Px0heVon.js';
|
|
3
|
+
|
|
4
|
+
function ref(value) {
|
|
5
|
+
return function() {
|
|
6
|
+
if (arguments.length === 1) {
|
|
7
|
+
value = arguments[0];
|
|
8
|
+
} else if (arguments.length > 1) {
|
|
23
9
|
throw new Error(`Too many arguments. Expected 0 or 1. Got: ${arguments.length}`);
|
|
24
|
-
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
25
12
|
};
|
|
26
13
|
}
|
|
27
|
-
|
|
28
|
-
|
|
14
|
+
|
|
15
|
+
function splitPath(path) {
|
|
16
|
+
assertString(path, "Expected `path` to be a string. Got type: %t, value: %v");
|
|
17
|
+
return path.split("/").map((f) => f.trim()).filter((f) => f !== "");
|
|
29
18
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
i,
|
|
19
|
+
function joinPath(parts) {
|
|
20
|
+
assertArrayOf(
|
|
21
|
+
(part) => isFunction(part?.toString),
|
|
22
|
+
parts,
|
|
35
23
|
"Expected `parts` to be an array of objects with a .toString() method. Got type: %t, value: %v"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
24
|
+
);
|
|
25
|
+
parts = parts.filter((x) => x).flatMap(String);
|
|
26
|
+
let joined = parts.shift()?.toString();
|
|
27
|
+
if (joined) {
|
|
28
|
+
for (const part of parts.map((p) => p.toString())) {
|
|
29
|
+
if (part.startsWith(".")) {
|
|
30
|
+
joined = resolvePath(joined, part);
|
|
31
|
+
} else if (joined[joined.length - 1] !== "/") {
|
|
32
|
+
if (part[0] !== "/") {
|
|
33
|
+
joined += "/" + part;
|
|
34
|
+
} else {
|
|
35
|
+
joined += part;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
if (part[0] === "/") {
|
|
39
|
+
joined += part.slice(1);
|
|
40
|
+
} else {
|
|
41
|
+
joined += part;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (joined && joined !== "/" && joined.endsWith("/")) {
|
|
46
|
+
joined = joined.slice(0, joined.length - 1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return joined ?? "";
|
|
44
50
|
}
|
|
45
|
-
function
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
function resolvePath(base, part) {
|
|
52
|
+
assertString(base, "Expected `base` to be a string. Got type: %t, value: %v");
|
|
53
|
+
if (part == null) {
|
|
54
|
+
part = base;
|
|
55
|
+
base = "";
|
|
56
|
+
}
|
|
57
|
+
if (part.startsWith("/")) {
|
|
58
|
+
return part;
|
|
59
|
+
}
|
|
60
|
+
let resolved = base;
|
|
61
|
+
while (true) {
|
|
62
|
+
if (part.startsWith("..")) {
|
|
63
|
+
for (let i = resolved.length; i > 0; --i) {
|
|
64
|
+
if (resolved[i] === "/" || i === 0) {
|
|
65
|
+
resolved = resolved.slice(0, i);
|
|
66
|
+
part = part.replace(/^\.\.\/?/, "");
|
|
54
67
|
break;
|
|
55
68
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
}
|
|
70
|
+
} else if (part.startsWith(".")) {
|
|
71
|
+
part = part.replace(/^\.\/?/, "");
|
|
72
|
+
} else {
|
|
59
73
|
break;
|
|
60
|
-
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return joinPath([resolved, part]);
|
|
61
77
|
}
|
|
62
|
-
function
|
|
63
|
-
if (!
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
78
|
+
function parseQueryParams(query) {
|
|
79
|
+
if (!query) return {};
|
|
80
|
+
if (query.startsWith("?")) {
|
|
81
|
+
query = query.slice(1);
|
|
82
|
+
}
|
|
83
|
+
const entries = query.split("&").filter((x) => x.trim() !== "").map((entry) => {
|
|
84
|
+
const [key, value] = entry.split("=").map((x) => x.trim());
|
|
85
|
+
if (value.toLowerCase() === "true") {
|
|
86
|
+
return [key, true];
|
|
87
|
+
}
|
|
88
|
+
if (value.toLowerCase() === "false") {
|
|
89
|
+
return [key, false];
|
|
90
|
+
}
|
|
91
|
+
if (!isNaN(Number(value))) {
|
|
92
|
+
return [key, Number(value)];
|
|
93
|
+
}
|
|
94
|
+
return [key, value];
|
|
68
95
|
});
|
|
69
|
-
return Object.fromEntries(
|
|
96
|
+
return Object.fromEntries(entries);
|
|
70
97
|
}
|
|
71
|
-
function
|
|
72
|
-
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const { fragments
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
function matchRoutes(routes, url, options = {}) {
|
|
99
|
+
const [path, query] = url.split("?");
|
|
100
|
+
const parts = splitPath(path);
|
|
101
|
+
routes: for (const route of routes) {
|
|
102
|
+
const { fragments } = route;
|
|
103
|
+
const hasWildcard = fragments[fragments.length - 1]?.type === 3 /* Wildcard */;
|
|
104
|
+
if (!hasWildcard && fragments.length !== parts.length) {
|
|
105
|
+
continue routes;
|
|
106
|
+
}
|
|
107
|
+
if (options.willMatch && !options.willMatch(route)) {
|
|
108
|
+
continue routes;
|
|
109
|
+
}
|
|
110
|
+
const matched = [];
|
|
111
|
+
fragments: for (let i = 0; i < fragments.length; i++) {
|
|
112
|
+
const part = parts[i];
|
|
113
|
+
const frag = fragments[i];
|
|
114
|
+
if (part == null && frag.type !== 3 /* Wildcard */) {
|
|
115
|
+
continue routes;
|
|
116
|
+
}
|
|
117
|
+
switch (frag.type) {
|
|
118
|
+
case 1 /* Literal */:
|
|
119
|
+
if (frag.name.toLowerCase() === part.toLowerCase()) {
|
|
120
|
+
matched.push(frag);
|
|
87
121
|
break;
|
|
88
|
-
} else
|
|
89
|
-
continue
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
case 3:
|
|
94
|
-
v.push({ ...$, value: n.slice(h).join("/") });
|
|
95
|
-
break e;
|
|
96
|
-
case 4:
|
|
97
|
-
if (isNaN(Number(x)))
|
|
98
|
-
continue t;
|
|
99
|
-
v.push({ ...$, value: Number(x) });
|
|
122
|
+
} else {
|
|
123
|
+
continue routes;
|
|
124
|
+
}
|
|
125
|
+
case 2 /* Param */:
|
|
126
|
+
matched.push({ ...frag, value: part });
|
|
100
127
|
break;
|
|
128
|
+
case 3 /* Wildcard */:
|
|
129
|
+
matched.push({ ...frag, value: parts.slice(i).join("/") });
|
|
130
|
+
break fragments;
|
|
131
|
+
case 4 /* NumericParam */:
|
|
132
|
+
if (!isNaN(Number(part))) {
|
|
133
|
+
matched.push({ ...frag, value: Number(part) });
|
|
134
|
+
break;
|
|
135
|
+
} else {
|
|
136
|
+
continue routes;
|
|
137
|
+
}
|
|
101
138
|
default:
|
|
102
|
-
throw new Error(`Unknown fragment type: ${
|
|
139
|
+
throw new Error(`Unknown fragment type: ${frag.type}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const params = {};
|
|
143
|
+
for (const frag of matched) {
|
|
144
|
+
if (frag.type === 2 /* Param */) {
|
|
145
|
+
params[frag.name] = decodeURIComponent(frag.value);
|
|
146
|
+
}
|
|
147
|
+
if (frag.type === 4 /* NumericParam */) {
|
|
148
|
+
params[frag.name] = frag.value;
|
|
149
|
+
}
|
|
150
|
+
if (frag.type === 3 /* Wildcard */) {
|
|
151
|
+
params.wildcard = "/" + decodeURIComponent(frag.value);
|
|
103
152
|
}
|
|
104
153
|
}
|
|
105
|
-
const k = {};
|
|
106
|
-
for (const h of v)
|
|
107
|
-
h.type === 2 && (k[h.name] = decodeURIComponent(h.value)), h.type === 4 && (k[h.name] = h.value), h.type === 3 && (k.wildcard = "/" + decodeURIComponent(h.value));
|
|
108
154
|
return {
|
|
109
|
-
path: "/" +
|
|
110
|
-
pattern: "/" +
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
path: "/" + matched.map((f) => f.value).join("/"),
|
|
156
|
+
pattern: "/" + fragments.map((f) => {
|
|
157
|
+
if (f.type === 2 /* Param */) {
|
|
158
|
+
return `{${f.name}}`;
|
|
159
|
+
}
|
|
160
|
+
if (f.type === 4 /* NumericParam */) {
|
|
161
|
+
return `{#${f.name}}`;
|
|
162
|
+
}
|
|
163
|
+
return f.name;
|
|
164
|
+
}).join("/"),
|
|
165
|
+
params,
|
|
166
|
+
query: parseQueryParams(query),
|
|
167
|
+
meta: route.meta
|
|
114
168
|
};
|
|
115
169
|
}
|
|
116
170
|
}
|
|
117
|
-
function
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
(
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
171
|
+
function sortRoutes(routes) {
|
|
172
|
+
const withoutParams = [];
|
|
173
|
+
const withNumericParams = [];
|
|
174
|
+
const withParams = [];
|
|
175
|
+
const wildcard = [];
|
|
176
|
+
for (const route of routes) {
|
|
177
|
+
const { fragments } = route;
|
|
178
|
+
if (fragments.some((f) => f.type === 3 /* Wildcard */)) {
|
|
179
|
+
wildcard.push(route);
|
|
180
|
+
} else if (fragments.some((f) => f.type === 4 /* NumericParam */)) {
|
|
181
|
+
withNumericParams.push(route);
|
|
182
|
+
} else if (fragments.some((f) => f.type === 2 /* Param */)) {
|
|
183
|
+
withParams.push(route);
|
|
184
|
+
} else {
|
|
185
|
+
withoutParams.push(route);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const bySizeDesc = (a, b) => {
|
|
189
|
+
if (a.fragments.length > b.fragments.length) {
|
|
190
|
+
return -1;
|
|
191
|
+
} else {
|
|
192
|
+
return 1;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
withoutParams.sort(bySizeDesc);
|
|
196
|
+
withNumericParams.sort(bySizeDesc);
|
|
197
|
+
withParams.sort(bySizeDesc);
|
|
198
|
+
wildcard.sort(bySizeDesc);
|
|
199
|
+
return [...withoutParams, ...withNumericParams, ...withParams, ...wildcard];
|
|
134
200
|
}
|
|
135
|
-
function
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
201
|
+
function patternToFragments(pattern) {
|
|
202
|
+
const parts = splitPath(pattern);
|
|
203
|
+
const fragments = [];
|
|
204
|
+
for (let i = 0; i < parts.length; i++) {
|
|
205
|
+
const part = parts[i];
|
|
206
|
+
if (part === "*") {
|
|
207
|
+
if (i !== parts.length - 1) {
|
|
208
|
+
throw new Error(`Wildcard must be at the end of a pattern. Received: ${pattern}`);
|
|
209
|
+
}
|
|
210
|
+
fragments.push({
|
|
211
|
+
type: 3 /* Wildcard */,
|
|
144
212
|
name: "*",
|
|
145
213
|
value: null
|
|
146
214
|
});
|
|
147
|
-
} else
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
215
|
+
} else if (part.at(0) === "{" && part.at(-1) === "}") {
|
|
216
|
+
fragments.push({
|
|
217
|
+
type: part[1] === "#" ? 4 /* NumericParam */ : 2 /* Param */,
|
|
218
|
+
name: part[1] === "#" ? part.slice(2, -1) : part.slice(1, -1),
|
|
219
|
+
value: null
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
fragments.push({
|
|
223
|
+
type: 1 /* Literal */,
|
|
224
|
+
name: part,
|
|
225
|
+
value: part
|
|
226
|
+
});
|
|
227
|
+
}
|
|
156
228
|
}
|
|
157
|
-
return
|
|
229
|
+
return fragments;
|
|
158
230
|
}
|
|
159
|
-
|
|
160
|
-
|
|
231
|
+
|
|
232
|
+
function createRouter(options) {
|
|
233
|
+
return new Router(options);
|
|
161
234
|
}
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
235
|
+
const ROUTER_MOUNT = Symbol.for("DollaRouterMountMethod");
|
|
236
|
+
const ROUTER_UNMOUNT = Symbol.for("DollaRouterUnmountMethod");
|
|
237
|
+
function _isRouter(value) {
|
|
238
|
+
return value?.[IS_ROUTER] === true;
|
|
165
239
|
}
|
|
166
|
-
async function
|
|
167
|
-
return
|
|
240
|
+
async function _mountRouter(router, dolla) {
|
|
241
|
+
return router[ROUTER_MOUNT](dolla);
|
|
168
242
|
}
|
|
169
|
-
async function
|
|
170
|
-
return
|
|
243
|
+
async function _unmountRouter(router) {
|
|
244
|
+
return router[ROUTER_UNMOUNT]();
|
|
171
245
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
*/
|
|
217
|
-
w(this, "query", U(() => {
|
|
218
|
-
var t;
|
|
219
|
-
return ((t = E(a(this, N))) == null ? void 0 : t.query) ?? {};
|
|
220
|
-
}, { equals: _t }));
|
|
221
|
-
Qt(t, "Options must be an object. Got: %t"), t.hash && d(this, j, !0), d(this, A, ie(
|
|
222
|
-
t.routes.flatMap((e) => f(this, m, yt).call(this, e)).map((e) => ({
|
|
223
|
-
pattern: e.pattern,
|
|
224
|
-
meta: e.meta,
|
|
225
|
-
fragments: oe(e.pattern)
|
|
246
|
+
class Router {
|
|
247
|
+
[IS_ROUTER] = true;
|
|
248
|
+
#dolla;
|
|
249
|
+
#logger;
|
|
250
|
+
#layerId = 0;
|
|
251
|
+
#activeLayers = [];
|
|
252
|
+
#routes = [];
|
|
253
|
+
#isMounted = false;
|
|
254
|
+
/**
|
|
255
|
+
* Use hash routing when true. Configured in router options.
|
|
256
|
+
*/
|
|
257
|
+
#hash = false;
|
|
258
|
+
// Callbacks that need to be called on unmount.
|
|
259
|
+
#unsubscribers = [];
|
|
260
|
+
/**
|
|
261
|
+
* The current match object.
|
|
262
|
+
*/
|
|
263
|
+
#match = atom();
|
|
264
|
+
/**
|
|
265
|
+
* The currently matched route pattern, if any.
|
|
266
|
+
*/
|
|
267
|
+
pattern = compose(() => get(this.#match)?.pattern);
|
|
268
|
+
/**
|
|
269
|
+
* The current URL path.
|
|
270
|
+
*/
|
|
271
|
+
path = compose(() => get(this.#match)?.path ?? window.location.pathname);
|
|
272
|
+
/**
|
|
273
|
+
* The current named path params.
|
|
274
|
+
*/
|
|
275
|
+
params = compose(() => get(this.#match)?.params ?? {}, { equals: shallowEqual });
|
|
276
|
+
/**
|
|
277
|
+
* The current query params. Changes to this object will be reflected in the URL.
|
|
278
|
+
*/
|
|
279
|
+
query = compose(() => get(this.#match)?.query ?? {}, { equals: shallowEqual });
|
|
280
|
+
constructor(options) {
|
|
281
|
+
assertObject(options, "Options must be an object. Got: %t");
|
|
282
|
+
if (options.hash) {
|
|
283
|
+
this.#hash = true;
|
|
284
|
+
}
|
|
285
|
+
this.#routes = sortRoutes(
|
|
286
|
+
options.routes.flatMap((route) => this.#prepareRoute(route)).map((route) => ({
|
|
287
|
+
pattern: route.pattern,
|
|
288
|
+
meta: route.meta,
|
|
289
|
+
fragments: patternToFragments(route.pattern)
|
|
226
290
|
}))
|
|
227
|
-
)
|
|
291
|
+
);
|
|
292
|
+
assertValidRedirects(this.#routes);
|
|
228
293
|
}
|
|
229
|
-
async [
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
294
|
+
async [ROUTER_MOUNT](dolla) {
|
|
295
|
+
this.#dolla = dolla;
|
|
296
|
+
this.#logger = dolla.createLogger("Dolla.router");
|
|
297
|
+
const onPopState = () => {
|
|
298
|
+
this.#updateRoute();
|
|
233
299
|
};
|
|
234
|
-
window.addEventListener("popstate",
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
300
|
+
window.addEventListener("popstate", onPopState);
|
|
301
|
+
this.#unsubscribers.push(() => window.removeEventListener("popstate", onPopState));
|
|
302
|
+
const rootElement = dolla.getRootElement();
|
|
303
|
+
this.#unsubscribers.push(
|
|
304
|
+
catchLinks(rootElement, (anchor) => {
|
|
305
|
+
let href = anchor.getAttribute("href");
|
|
306
|
+
this.#logger.info("intercepted click on <a> tag", anchor);
|
|
307
|
+
if (!/^https?:\/\/|^\//.test(href)) {
|
|
308
|
+
href = joinPath([window.location.pathname, href]);
|
|
309
|
+
}
|
|
310
|
+
this.#push(href);
|
|
240
311
|
})
|
|
241
|
-
)
|
|
312
|
+
);
|
|
313
|
+
this.#logger.info("will intercept clicks on <a> tags within root element", rootElement);
|
|
314
|
+
this.#isMounted = true;
|
|
315
|
+
await this.#updateRoute();
|
|
242
316
|
}
|
|
243
|
-
async [
|
|
244
|
-
for (const
|
|
245
|
-
|
|
246
|
-
|
|
317
|
+
async [ROUTER_UNMOUNT]() {
|
|
318
|
+
for (const callback of this.#unsubscribers) {
|
|
319
|
+
callback();
|
|
320
|
+
}
|
|
321
|
+
this.#unsubscribers = [];
|
|
247
322
|
}
|
|
248
323
|
/**
|
|
249
324
|
* Navigate backward. Pass a number of steps to hit the back button that many times.
|
|
250
325
|
*/
|
|
251
|
-
back(
|
|
252
|
-
window.history.go(-
|
|
326
|
+
back(steps = 1) {
|
|
327
|
+
window.history.go(-steps);
|
|
253
328
|
}
|
|
254
329
|
/**
|
|
255
330
|
* Navigate forward. Pass a number of steps to hit the forward button that many times.
|
|
256
331
|
*/
|
|
257
|
-
forward(
|
|
258
|
-
window.history.go(
|
|
332
|
+
forward(steps = 1) {
|
|
333
|
+
window.history.go(steps);
|
|
259
334
|
}
|
|
260
335
|
/**
|
|
261
336
|
* Navigates to another route.
|
|
@@ -264,499 +339,741 @@ class he {
|
|
|
264
339
|
* router.go("/login"); // navigate to `/login`
|
|
265
340
|
* router.go["/users", 215], { replace: true }); // replace current history entry with `/users/215`
|
|
266
341
|
*/
|
|
267
|
-
go(
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
342
|
+
go(path, options = {}) {
|
|
343
|
+
let joined;
|
|
344
|
+
if (Array.isArray(path)) {
|
|
345
|
+
joined = joinPath(path);
|
|
346
|
+
} else {
|
|
347
|
+
joined = path.toString();
|
|
348
|
+
}
|
|
349
|
+
joined = resolvePath(window.location.pathname, joined);
|
|
350
|
+
if (options.preserveQuery) {
|
|
351
|
+
joined += window.location.search;
|
|
352
|
+
}
|
|
353
|
+
if (options.replace) {
|
|
354
|
+
this.#replace(joined);
|
|
355
|
+
} else {
|
|
356
|
+
this.#push(joined);
|
|
357
|
+
}
|
|
272
358
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
359
|
+
#push(href, state) {
|
|
360
|
+
this.#logger?.info("(push)", href);
|
|
361
|
+
window.history.pushState(state, "", this.#hash ? "/#" + href : href);
|
|
362
|
+
this.#updateRoute(href);
|
|
363
|
+
}
|
|
364
|
+
#replace(href, state) {
|
|
365
|
+
this.#logger?.info("(replace)", href);
|
|
366
|
+
window.history.replaceState(state, "", this.#hash ? "/#" + href : href);
|
|
367
|
+
this.#updateRoute(href);
|
|
368
|
+
}
|
|
369
|
+
#getCurrentURL() {
|
|
370
|
+
if (this.#hash) {
|
|
371
|
+
return new URL(window.location.hash.slice(1), window.location.origin);
|
|
372
|
+
} else {
|
|
373
|
+
return new URL(window.location.pathname, window.location.origin);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Run when the location changes. Diffs and mounts new routes and updates
|
|
378
|
+
* the $path, $route, $params and $query states accordingly.
|
|
379
|
+
*/
|
|
380
|
+
async #updateRoute(href) {
|
|
381
|
+
const logger = this.#logger;
|
|
382
|
+
const rootView = this.#dolla?.getRootView();
|
|
383
|
+
const url = href ? new URL(href, window.location.origin) : this.#getCurrentURL();
|
|
384
|
+
const { match, journey } = await this.#resolveRoute(url);
|
|
385
|
+
if (match) {
|
|
386
|
+
const oldPattern = this.pattern.value;
|
|
387
|
+
this.#match.value = match;
|
|
388
|
+
if (rootView && match.pattern !== oldPattern) {
|
|
389
|
+
this.#mountRoute(rootView, match);
|
|
390
|
+
}
|
|
391
|
+
} else {
|
|
392
|
+
if (this.#isMounted) {
|
|
393
|
+
logger.crash(new NoRouteError(`Failed to match route '${url.pathname}'`));
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return { match, journey };
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Takes a matched route and mounts it.
|
|
400
|
+
*/
|
|
401
|
+
#mountRoute(rootView, match) {
|
|
402
|
+
const layers = match.meta.layers;
|
|
403
|
+
for (let i = 0; i < layers.length; i++) {
|
|
404
|
+
const matchedLayer = layers[i];
|
|
405
|
+
const activeLayer = this.#activeLayers[i];
|
|
406
|
+
if (activeLayer?.id !== matchedLayer.id) {
|
|
407
|
+
this.#activeLayers = this.#activeLayers.slice(0, i);
|
|
408
|
+
activeLayer?.view.unmount();
|
|
409
|
+
const parentLayer = this.#activeLayers.at(-1);
|
|
410
|
+
const parent2 = parentLayer?.view ?? rootView;
|
|
411
|
+
const view = parent2.setChildView(matchedLayer.view);
|
|
412
|
+
this.#activeLayers.push({ id: matchedLayer.id, view });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Takes a URL and finds a match, following redirects.
|
|
418
|
+
*/
|
|
419
|
+
async #resolveRoute(url, journey = []) {
|
|
420
|
+
const match = matchRoutes(this.#routes, url.pathname);
|
|
421
|
+
if (!match) {
|
|
422
|
+
return {
|
|
423
|
+
match: null,
|
|
424
|
+
journey: [...journey, { kind: "miss", message: `no match for '${url.pathname}'` }]
|
|
339
425
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const n = [];
|
|
371
|
-
if (t.redirect) {
|
|
372
|
-
let l = t.redirect;
|
|
373
|
-
return M(l) && (l = ct(G(r), l), l.startsWith("/") || (l = "/" + l)), n.push({
|
|
374
|
-
pattern: "/" + G([...r, ...Q(t.path)]),
|
|
375
|
-
meta: {
|
|
376
|
-
redirect: l
|
|
426
|
+
}
|
|
427
|
+
let redirect = match.meta.redirect;
|
|
428
|
+
if (match.meta.beforeMatch) {
|
|
429
|
+
await match.meta.beforeMatch({
|
|
430
|
+
// TODO: Allow setting context variables from here? Would apply to the context of the matched view.
|
|
431
|
+
redirect: (path) => {
|
|
432
|
+
redirect = path;
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (redirect != null) {
|
|
437
|
+
let path;
|
|
438
|
+
if (isString(redirect)) {
|
|
439
|
+
path = replaceParams(redirect, match.params);
|
|
440
|
+
} else if (isFunction(redirect)) {
|
|
441
|
+
const redirectContext = {
|
|
442
|
+
path: match.path,
|
|
443
|
+
pattern: match.pattern,
|
|
444
|
+
params: match.params,
|
|
445
|
+
query: match.query
|
|
446
|
+
};
|
|
447
|
+
path = await redirect(redirectContext);
|
|
448
|
+
if (!isString(path)) {
|
|
449
|
+
throw new Error(`Redirect function must return a path to redirect to.`);
|
|
450
|
+
}
|
|
451
|
+
if (!path.startsWith("/")) {
|
|
452
|
+
path = resolvePath(match.path, path);
|
|
453
|
+
}
|
|
454
|
+
} else {
|
|
455
|
+
throw new TypeError(`Redirect must either be a path string or a function.`);
|
|
377
456
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
457
|
+
return this.#resolveRoute(new URL(path, window.location.origin), [
|
|
458
|
+
...journey,
|
|
459
|
+
{ kind: "redirect", message: `redirecting '${match.path}' -> '${path}'` }
|
|
460
|
+
]);
|
|
461
|
+
} else {
|
|
462
|
+
return { match, journey: [...journey, { kind: "match", message: `matched route '${match.path}'` }] };
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Parses a route definition object into a set of matchable routes.
|
|
467
|
+
*
|
|
468
|
+
* @param route - Route config object.
|
|
469
|
+
* @param layers - Array of parent layers. Passed when this function calls itself on nested routes.
|
|
470
|
+
*/
|
|
471
|
+
#prepareRoute(route, parents = [], layers = []) {
|
|
472
|
+
if (!isObject(route) || !isString(route.path)) {
|
|
473
|
+
throw new TypeError(`Route configs must be objects with a 'path' string property. Got: ${route}`);
|
|
474
|
+
}
|
|
475
|
+
if (route.redirect && route.routes) {
|
|
476
|
+
throw new Error(`Route cannot have both a 'redirect' and nested 'routes'.`);
|
|
477
|
+
} else if (route.redirect && route.view) {
|
|
478
|
+
throw new Error(`Route cannot have both a 'redirect' and a 'view'.`);
|
|
479
|
+
} else if (!route.view && !route.routes && !route.redirect) {
|
|
480
|
+
throw new Error(`Route must have a 'view', a 'redirect', or a set of nested 'routes'.`);
|
|
481
|
+
}
|
|
482
|
+
let parts = [];
|
|
483
|
+
for (const parent2 of parents) {
|
|
484
|
+
parts.push(...splitPath(parent2.path));
|
|
485
|
+
}
|
|
486
|
+
parts.push(...splitPath(route.path));
|
|
487
|
+
if (parts[parts.length - 1] === "*") {
|
|
488
|
+
parts.pop();
|
|
489
|
+
}
|
|
490
|
+
const routes = [];
|
|
491
|
+
if (route.redirect) {
|
|
492
|
+
let redirect = route.redirect;
|
|
493
|
+
if (isString(redirect)) {
|
|
494
|
+
redirect = resolvePath(joinPath(parts), redirect);
|
|
495
|
+
if (!redirect.startsWith("/")) {
|
|
496
|
+
redirect = "/" + redirect;
|
|
497
|
+
}
|
|
396
498
|
}
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if (
|
|
499
|
+
routes.push({
|
|
500
|
+
pattern: "/" + joinPath([...parts, ...splitPath(route.path)]),
|
|
501
|
+
meta: {
|
|
502
|
+
redirect
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
return routes;
|
|
506
|
+
}
|
|
507
|
+
let view = Passthrough;
|
|
508
|
+
if (isFunction(route.view)) {
|
|
509
|
+
view = route.view;
|
|
510
|
+
} else if (route.view) {
|
|
511
|
+
throw new TypeError(`Route '${route.path}' expected a view function or undefined. Got: ${route.view}`);
|
|
512
|
+
}
|
|
513
|
+
const layer = { id: this.#layerId++, view };
|
|
514
|
+
if (route.routes) {
|
|
515
|
+
for (const subroute of route.routes) {
|
|
516
|
+
routes.push(...this.#prepareRoute(subroute, [...parents, route], [...layers, layer]));
|
|
517
|
+
}
|
|
518
|
+
} else {
|
|
519
|
+
routes.push({
|
|
520
|
+
pattern: parent ? joinPath([...parents.map((p) => p.path), route.path]) : route.path,
|
|
521
|
+
meta: {
|
|
522
|
+
pattern: route.path,
|
|
523
|
+
layers: [...layers, layer],
|
|
524
|
+
beforeMatch: route.beforeMatch
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
return routes;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const safeExternalLink = /(noopener|noreferrer) (noopener|noreferrer)/;
|
|
532
|
+
const protocolLink = /^[\w-_]+:/;
|
|
533
|
+
function catchLinks(root, callback, _window = window) {
|
|
534
|
+
function traverse(node) {
|
|
535
|
+
if (!node || node === root) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
if (node.localName !== "a" || node.href === void 0) {
|
|
539
|
+
return traverse(node.parentNode);
|
|
540
|
+
}
|
|
541
|
+
return node;
|
|
542
|
+
}
|
|
543
|
+
function handler(e) {
|
|
544
|
+
if (e.button && e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.defaultPrevented) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const anchor = traverse(e.target);
|
|
548
|
+
if (!anchor) {
|
|
407
549
|
return;
|
|
408
|
-
|
|
409
|
-
|
|
550
|
+
}
|
|
551
|
+
if (_window.location.protocol !== anchor.protocol || _window.location.hostname !== anchor.hostname || _window.location.port !== anchor.port || anchor.hasAttribute("data-router-ignore") || anchor.hasAttribute("download") || anchor.getAttribute("target") === "_blank" && safeExternalLink.test(anchor.getAttribute("rel")) || protocolLink.test(anchor.getAttribute("href"))) {
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
e.preventDefault();
|
|
555
|
+
callback(anchor);
|
|
410
556
|
}
|
|
411
|
-
|
|
412
|
-
|
|
557
|
+
root.addEventListener("click", handler);
|
|
558
|
+
return function cancel() {
|
|
559
|
+
root.removeEventListener("click", handler);
|
|
413
560
|
};
|
|
414
561
|
}
|
|
415
|
-
function
|
|
416
|
-
for (const
|
|
417
|
-
const
|
|
418
|
-
|
|
562
|
+
function replaceParams(path, params) {
|
|
563
|
+
for (const key in params) {
|
|
564
|
+
const value = params[key].toString();
|
|
565
|
+
path = path.replace(`{${key}}`, value).replace(`{#${key}}`, value);
|
|
419
566
|
}
|
|
420
|
-
return
|
|
567
|
+
return path;
|
|
421
568
|
}
|
|
422
|
-
function
|
|
423
|
-
for (const
|
|
424
|
-
if (
|
|
425
|
-
let
|
|
426
|
-
if (
|
|
427
|
-
|
|
569
|
+
function assertValidRedirects(routes) {
|
|
570
|
+
for (const route of routes) {
|
|
571
|
+
if (route.meta.redirect) {
|
|
572
|
+
let redirectPath;
|
|
573
|
+
if (isFunction(route.meta.redirect)) ; else if (isString(route.meta.redirect)) {
|
|
574
|
+
redirectPath = route.meta.redirect;
|
|
575
|
+
const match = matchRoutes(routes, redirectPath, {
|
|
428
576
|
willMatch(r) {
|
|
429
|
-
return r !==
|
|
577
|
+
return r !== route;
|
|
430
578
|
}
|
|
431
|
-
})
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
579
|
+
});
|
|
580
|
+
if (!match) {
|
|
581
|
+
throw new Error(`Found a redirect to an undefined URL. From '${route.pattern}' to '${route.meta.redirect}'`);
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
throw new TypeError(`Expected a string or redirect function. Got: ${route.meta.redirect}`);
|
|
585
|
+
}
|
|
435
586
|
}
|
|
587
|
+
}
|
|
436
588
|
}
|
|
437
|
-
class
|
|
589
|
+
class NoRouteError extends Error {
|
|
438
590
|
}
|
|
439
|
-
|
|
440
|
-
class
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
constructor() {
|
|
444
|
-
u(this, R);
|
|
445
|
-
u(this, D, []);
|
|
446
|
-
u(this, ut, ye());
|
|
447
|
-
}
|
|
591
|
+
|
|
592
|
+
class HTTP {
|
|
593
|
+
#middleware = [];
|
|
594
|
+
#fetch = getDefaultFetch();
|
|
448
595
|
/**
|
|
449
596
|
* Adds a new middleware that will apply to subsequent requests.
|
|
450
597
|
* Returns a function to remove this middleware.
|
|
451
598
|
*
|
|
452
599
|
* @param middleware - A middleware function that will intercept requests.
|
|
453
600
|
*/
|
|
454
|
-
use(
|
|
455
|
-
|
|
456
|
-
|
|
601
|
+
use(fn) {
|
|
602
|
+
this.#middleware.push(fn);
|
|
603
|
+
return () => {
|
|
604
|
+
this.#middleware.splice(this.#middleware.indexOf(fn), 1);
|
|
457
605
|
};
|
|
458
606
|
}
|
|
459
|
-
async get(
|
|
460
|
-
return
|
|
607
|
+
async get(uri, options) {
|
|
608
|
+
return this.#request("get", uri, options);
|
|
461
609
|
}
|
|
462
|
-
async put(
|
|
463
|
-
return
|
|
610
|
+
async put(uri, options) {
|
|
611
|
+
return this.#request("put", uri, options);
|
|
464
612
|
}
|
|
465
|
-
async patch(
|
|
466
|
-
return
|
|
613
|
+
async patch(uri, options) {
|
|
614
|
+
return this.#request("patch", uri, options);
|
|
467
615
|
}
|
|
468
|
-
async post(
|
|
469
|
-
return
|
|
616
|
+
async post(uri, options) {
|
|
617
|
+
return this.#request("post", uri, options);
|
|
470
618
|
}
|
|
471
|
-
async delete(
|
|
472
|
-
return
|
|
619
|
+
async delete(uri, options) {
|
|
620
|
+
return this.#request("delete", uri, options);
|
|
473
621
|
}
|
|
474
|
-
async head(
|
|
475
|
-
return
|
|
622
|
+
async head(uri, options) {
|
|
623
|
+
return this.#request("head", uri, options);
|
|
476
624
|
}
|
|
477
|
-
async options(
|
|
478
|
-
return
|
|
625
|
+
async options(uri, options) {
|
|
626
|
+
return this.#request("options", uri, options);
|
|
479
627
|
}
|
|
480
|
-
async trace(
|
|
481
|
-
return
|
|
628
|
+
async trace(uri, options) {
|
|
629
|
+
return this.#request("trace", uri, options);
|
|
630
|
+
}
|
|
631
|
+
async #request(method, uri, options) {
|
|
632
|
+
const runner = new Runner({
|
|
633
|
+
...options,
|
|
634
|
+
method,
|
|
635
|
+
uri,
|
|
636
|
+
middleware: this.#middleware,
|
|
637
|
+
fetch: this.#fetch
|
|
638
|
+
});
|
|
639
|
+
return runner.fetch();
|
|
482
640
|
}
|
|
483
641
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
...s,
|
|
487
|
-
method: t,
|
|
488
|
-
uri: e,
|
|
489
|
-
middleware: a(this, D),
|
|
490
|
-
fetch: a(this, ut)
|
|
491
|
-
}).fetch();
|
|
492
|
-
};
|
|
493
|
-
function ye() {
|
|
494
|
-
if (typeof window < "u" && window.fetch)
|
|
642
|
+
function getDefaultFetch() {
|
|
643
|
+
if (typeof window !== "undefined" && window.fetch) {
|
|
495
644
|
return window.fetch.bind(window);
|
|
496
|
-
|
|
645
|
+
}
|
|
646
|
+
if (typeof global !== "undefined" && global.fetch) {
|
|
497
647
|
return global.fetch.bind(global);
|
|
648
|
+
}
|
|
498
649
|
throw new Error("Running in neither browser nor node. Please run this app in one of the supported environments.");
|
|
499
650
|
}
|
|
500
|
-
class
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
651
|
+
class HTTPResponseError extends Error {
|
|
652
|
+
response;
|
|
653
|
+
constructor(response) {
|
|
654
|
+
const { status, statusText, method, url } = response;
|
|
655
|
+
const message = `${status} ${statusText}: Request failed (${method.toUpperCase()} ${url.toString()})`;
|
|
656
|
+
super(message);
|
|
657
|
+
this.response = response;
|
|
506
658
|
}
|
|
507
659
|
}
|
|
508
|
-
class
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
w(this, "body");
|
|
514
|
-
this.method = t.method, this.body = t.body, t.uri.startsWith("http") ? this.url = new URL(t.uri) : this.url = new URL(t.uri, window.location.origin), this._applyHeaders(t.headers), this._applyQueryParams(t.query);
|
|
515
|
-
}
|
|
660
|
+
class Request {
|
|
661
|
+
method;
|
|
662
|
+
url;
|
|
663
|
+
headers = new Headers();
|
|
664
|
+
body;
|
|
516
665
|
get isSameOrigin() {
|
|
517
666
|
return this.url.origin === window.location.origin;
|
|
518
667
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
668
|
+
constructor(config) {
|
|
669
|
+
this.method = config.method;
|
|
670
|
+
this.body = config.body;
|
|
671
|
+
if (config.uri.startsWith("http")) {
|
|
672
|
+
this.url = new URL(config.uri);
|
|
673
|
+
} else {
|
|
674
|
+
this.url = new URL(config.uri, window.location.origin);
|
|
675
|
+
}
|
|
676
|
+
this._applyHeaders(config.headers);
|
|
677
|
+
this._applyQueryParams(config.query);
|
|
678
|
+
}
|
|
679
|
+
_applyHeaders(headers) {
|
|
680
|
+
if (headers == null) return;
|
|
681
|
+
if (headers instanceof Map || headers instanceof Headers) {
|
|
682
|
+
headers.forEach((value, key) => {
|
|
683
|
+
this.headers.set(key, value);
|
|
684
|
+
});
|
|
685
|
+
} else if (isObject(headers)) {
|
|
686
|
+
for (const name in headers) {
|
|
687
|
+
const value = headers[name];
|
|
688
|
+
if (value instanceof Date) {
|
|
689
|
+
this.headers.set(name, value.toISOString());
|
|
690
|
+
} else if (value != null) {
|
|
691
|
+
this.headers.set(name, String(value));
|
|
529
692
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
693
|
+
}
|
|
694
|
+
} else {
|
|
695
|
+
throw new TypeError(`Unknown headers type. Got: ${headers}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
_applyQueryParams(query) {
|
|
699
|
+
if (query == null) return;
|
|
700
|
+
if (query instanceof Map || query instanceof URLSearchParams) {
|
|
701
|
+
query.forEach((value, key) => {
|
|
702
|
+
this.url.searchParams.set(key, value);
|
|
703
|
+
});
|
|
704
|
+
} else if (isObject(query)) {
|
|
705
|
+
for (const name in query) {
|
|
706
|
+
const value = query[name];
|
|
707
|
+
if (value instanceof Date) {
|
|
708
|
+
this.url.searchParams.set(name, value.toISOString());
|
|
709
|
+
} else if (value != null) {
|
|
710
|
+
this.url.searchParams.set(name, String(value));
|
|
543
711
|
}
|
|
544
|
-
|
|
545
|
-
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
throw new TypeError(`Unknown query params type. Got: ${query}`);
|
|
715
|
+
}
|
|
546
716
|
}
|
|
547
717
|
}
|
|
548
|
-
class
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
this._middleware =
|
|
718
|
+
class Runner {
|
|
719
|
+
_middleware;
|
|
720
|
+
_fetch;
|
|
721
|
+
_request;
|
|
722
|
+
_response;
|
|
723
|
+
constructor(config) {
|
|
724
|
+
this._middleware = config.middleware;
|
|
725
|
+
this._fetch = config.fetch;
|
|
726
|
+
this._request = new Request(config);
|
|
555
727
|
}
|
|
556
728
|
async fetch() {
|
|
557
729
|
if (this._middleware.length > 0) {
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
|
|
730
|
+
const mount = (index = 0) => {
|
|
731
|
+
const current = this._middleware[index];
|
|
732
|
+
const next = this._middleware[index + 1] ? mount(index + 1) : this._handler.bind(this);
|
|
733
|
+
return async () => current(this._request, async () => {
|
|
734
|
+
await next();
|
|
735
|
+
return this._response;
|
|
736
|
+
});
|
|
561
737
|
};
|
|
562
|
-
await
|
|
563
|
-
} else
|
|
738
|
+
await mount()();
|
|
739
|
+
} else {
|
|
564
740
|
await this._handler();
|
|
565
|
-
|
|
566
|
-
|
|
741
|
+
}
|
|
742
|
+
if (this._response.status < 200 || this._response.status >= 400) {
|
|
743
|
+
throw new HTTPResponseError(this._response);
|
|
744
|
+
}
|
|
567
745
|
return this._response;
|
|
568
746
|
}
|
|
569
747
|
// This is the function that performs the actual request after the final middleware.
|
|
570
748
|
async _handler() {
|
|
571
|
-
let
|
|
572
|
-
const
|
|
573
|
-
!
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
body
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
749
|
+
let reqBody;
|
|
750
|
+
const req = this._request;
|
|
751
|
+
if (!req.headers.has("content-type") && isObject(req.body)) {
|
|
752
|
+
req.headers.set("content-type", "application/json");
|
|
753
|
+
reqBody = JSON.stringify(req.body);
|
|
754
|
+
} else {
|
|
755
|
+
reqBody = req.body;
|
|
756
|
+
}
|
|
757
|
+
const fetched = await this._fetch(req.url.toString(), {
|
|
758
|
+
method: req.method,
|
|
759
|
+
headers: req.headers,
|
|
760
|
+
body: reqBody
|
|
761
|
+
});
|
|
762
|
+
const contentType = fetched.headers.get("content-type");
|
|
763
|
+
let body;
|
|
764
|
+
if (contentType?.includes("application/json")) {
|
|
765
|
+
body = await fetched.json();
|
|
766
|
+
} else if (contentType?.includes("application/x-www-form-urlencoded")) {
|
|
767
|
+
body = await fetched.formData();
|
|
768
|
+
} else {
|
|
769
|
+
body = await fetched.text();
|
|
770
|
+
}
|
|
771
|
+
this._response = {
|
|
772
|
+
method: req.method,
|
|
773
|
+
url: req.url,
|
|
774
|
+
status: fetched.status,
|
|
775
|
+
statusText: fetched.statusText,
|
|
776
|
+
headers: fetched.headers,
|
|
777
|
+
body
|
|
587
778
|
};
|
|
588
779
|
}
|
|
589
780
|
}
|
|
590
|
-
|
|
591
|
-
class
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
this.
|
|
781
|
+
|
|
782
|
+
class Translation {
|
|
783
|
+
dolla;
|
|
784
|
+
config;
|
|
785
|
+
#isLoaded = false;
|
|
786
|
+
#templates = /* @__PURE__ */ new Map();
|
|
787
|
+
constructor(config, dolla) {
|
|
788
|
+
this.config = config;
|
|
789
|
+
this.dolla = dolla;
|
|
599
790
|
}
|
|
600
791
|
async load() {
|
|
601
|
-
let
|
|
602
|
-
if (!
|
|
603
|
-
if (
|
|
604
|
-
|
|
605
|
-
else if (
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
792
|
+
let strings;
|
|
793
|
+
if (!this.#isLoaded) {
|
|
794
|
+
if (isObject(this.config.strings)) {
|
|
795
|
+
strings = this.config.strings;
|
|
796
|
+
} else if (isFunction(this.config.fetch)) {
|
|
797
|
+
strings = await this.config.fetch();
|
|
798
|
+
if (!isObject(strings)) {
|
|
799
|
+
throw new Error(`Fetch function did not return an object of language strings: ${strings}`);
|
|
800
|
+
}
|
|
801
|
+
} else if (isString(this.config.path)) {
|
|
802
|
+
const res = await fetch(this.config.path);
|
|
803
|
+
if (res.ok) {
|
|
804
|
+
const body = await res.json();
|
|
805
|
+
if (isObject(body)) {
|
|
806
|
+
strings = body;
|
|
807
|
+
} else {
|
|
615
808
|
throw new Error(
|
|
616
|
-
`Language path '${this.config.path}' did not return an object of language strings: ${
|
|
809
|
+
`Language path '${this.config.path}' did not return an object of language strings: ${body}`
|
|
617
810
|
);
|
|
618
|
-
|
|
619
|
-
|
|
811
|
+
}
|
|
812
|
+
} else {
|
|
813
|
+
throw new Error(`HTTP request failed.`);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
if (strings) {
|
|
818
|
+
const entries = this.#compile(strings);
|
|
819
|
+
for (const entry of entries) {
|
|
820
|
+
this.#templates.set(entry[0], entry[1]);
|
|
620
821
|
}
|
|
822
|
+
} else {
|
|
823
|
+
throw new Error(`Language could not be loaded.`);
|
|
621
824
|
}
|
|
622
|
-
if (t) {
|
|
623
|
-
const e = f(this, q, bt).call(this, t);
|
|
624
|
-
for (const s of e)
|
|
625
|
-
a(this, H).set(s[0], s[1]);
|
|
626
|
-
} else
|
|
627
|
-
throw new Error("Language could not be loaded.");
|
|
628
825
|
}
|
|
629
|
-
getTemplate(
|
|
630
|
-
return
|
|
631
|
-
segments: [{ type: 0
|
|
826
|
+
getTemplate(selector) {
|
|
827
|
+
return this.#templates.get(selector) ?? {
|
|
828
|
+
segments: [{ type: 0 /* Static */, text: `[MISSING: ${selector}]` }]
|
|
632
829
|
};
|
|
633
830
|
}
|
|
634
|
-
hasTemplate(
|
|
635
|
-
return
|
|
831
|
+
hasTemplate(selector) {
|
|
832
|
+
return this.#templates.has(selector);
|
|
636
833
|
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
834
|
+
#compile(strings, path = []) {
|
|
835
|
+
const entries = [];
|
|
836
|
+
for (const key in strings) {
|
|
837
|
+
switch (typeOf(strings[key])) {
|
|
838
|
+
case "string":
|
|
839
|
+
entries.push([[...path, key].join("."), this.#parseTemplate(strings[key])]);
|
|
840
|
+
break;
|
|
841
|
+
case "object":
|
|
842
|
+
entries.push(...this.#compile(strings[key], [...path, key]));
|
|
843
|
+
break;
|
|
844
|
+
default:
|
|
845
|
+
throw new Error(
|
|
846
|
+
`Expected to find a string or object at ${[...path, key].join(".")}. Got: ${typeOf(strings[key])}`
|
|
847
|
+
);
|
|
848
|
+
}
|
|
652
849
|
}
|
|
653
|
-
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
formats: []
|
|
850
|
+
return entries;
|
|
851
|
+
}
|
|
852
|
+
#parseTemplate(template) {
|
|
853
|
+
let Loc;
|
|
854
|
+
((Loc2) => {
|
|
855
|
+
Loc2[Loc2["Static"] = 0] = "Static";
|
|
856
|
+
Loc2[Loc2["ValueName"] = 1] = "ValueName";
|
|
857
|
+
Loc2[Loc2["FormatName"] = 2] = "FormatName";
|
|
858
|
+
Loc2[Loc2["FormatOptionName"] = 3] = "FormatOptionName";
|
|
859
|
+
Loc2[Loc2["FormatOptionValue"] = 4] = "FormatOptionValue";
|
|
860
|
+
Loc2[Loc2["FormatOptionEnd"] = 5] = "FormatOptionEnd";
|
|
861
|
+
})(Loc || (Loc = {}));
|
|
862
|
+
const parsed = {
|
|
863
|
+
segments: []
|
|
668
864
|
};
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
865
|
+
let buffer = "";
|
|
866
|
+
let i = 0;
|
|
867
|
+
let loc = 0 /* Static */;
|
|
868
|
+
let segment;
|
|
869
|
+
let format;
|
|
870
|
+
let formatOptionName;
|
|
871
|
+
const startSegment = () => {
|
|
872
|
+
segment = {
|
|
873
|
+
type: 1 /* Variable */,
|
|
874
|
+
name: "",
|
|
875
|
+
formats: []
|
|
876
|
+
};
|
|
673
877
|
};
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
878
|
+
const startFormat = () => {
|
|
879
|
+
format = {
|
|
880
|
+
name: "",
|
|
881
|
+
options: {}
|
|
882
|
+
};
|
|
883
|
+
};
|
|
884
|
+
while (i < template.length) {
|
|
885
|
+
if (loc !== 0 /* Static */ && template[i] === " ") {
|
|
886
|
+
i++;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
switch (loc) {
|
|
890
|
+
case 0 /* Static */:
|
|
891
|
+
if (template[i] === "{" && template[i + 1] === "{") {
|
|
892
|
+
loc = 1 /* ValueName */;
|
|
893
|
+
i += 2;
|
|
894
|
+
if (buffer.length > 0) {
|
|
895
|
+
parsed.segments.push({ type: 0 /* Static */, text: buffer });
|
|
896
|
+
buffer = "";
|
|
897
|
+
}
|
|
898
|
+
startSegment();
|
|
899
|
+
} else {
|
|
900
|
+
buffer += template[i];
|
|
901
|
+
i++;
|
|
902
|
+
}
|
|
903
|
+
break;
|
|
904
|
+
case 1 /* ValueName */:
|
|
905
|
+
if (template[i] === "|") {
|
|
906
|
+
loc = 2 /* FormatName */;
|
|
907
|
+
i += 1;
|
|
908
|
+
segment.name = buffer;
|
|
909
|
+
buffer = "";
|
|
910
|
+
startFormat();
|
|
911
|
+
} else if (template[i] === "}" && template[i + 1] === "}") {
|
|
912
|
+
loc = 0 /* Static */;
|
|
913
|
+
i += 2;
|
|
914
|
+
segment.name = buffer;
|
|
915
|
+
buffer = "";
|
|
916
|
+
parsed.segments.push(segment);
|
|
917
|
+
} else {
|
|
918
|
+
buffer += template[i];
|
|
919
|
+
i++;
|
|
920
|
+
}
|
|
921
|
+
break;
|
|
922
|
+
case 2 /* FormatName */:
|
|
923
|
+
if (template[i] === "(") {
|
|
924
|
+
loc = 3 /* FormatOptionName */;
|
|
925
|
+
i += 1;
|
|
926
|
+
format.name = buffer;
|
|
927
|
+
buffer = "";
|
|
928
|
+
} else if (template[i] === "}" && template[i + 1] === "}") {
|
|
929
|
+
loc = 0 /* Static */;
|
|
930
|
+
i += 2;
|
|
931
|
+
segment.formats.push(format);
|
|
932
|
+
parsed.segments.push(segment);
|
|
933
|
+
} else {
|
|
934
|
+
buffer += template[i];
|
|
935
|
+
i++;
|
|
936
|
+
}
|
|
937
|
+
break;
|
|
938
|
+
case 3 /* FormatOptionName */:
|
|
939
|
+
if (template[i] === ")") ; else if (template[i] === ":") {
|
|
940
|
+
loc = 4 /* FormatOptionValue */;
|
|
941
|
+
i += 1;
|
|
942
|
+
formatOptionName = buffer;
|
|
943
|
+
buffer = "";
|
|
944
|
+
} else if (template[i] === "}" && template[i + 1] === "}") ; else {
|
|
945
|
+
buffer += template[i];
|
|
946
|
+
i++;
|
|
947
|
+
}
|
|
948
|
+
break;
|
|
949
|
+
case 4 /* FormatOptionValue */:
|
|
950
|
+
if (template[i] === ")") {
|
|
951
|
+
loc = 5 /* FormatOptionEnd */;
|
|
952
|
+
i += 1;
|
|
953
|
+
format.options[formatOptionName] = buffer;
|
|
954
|
+
buffer = "";
|
|
955
|
+
segment.formats.push(format);
|
|
956
|
+
} else if (template[i] === ",") {
|
|
957
|
+
loc = 3 /* FormatOptionName */;
|
|
958
|
+
i += 1;
|
|
959
|
+
format.options[formatOptionName] = buffer;
|
|
960
|
+
buffer = "";
|
|
961
|
+
} else if (template[i] === "}" && template[i + 1] === "}") ; else {
|
|
962
|
+
buffer += template[i];
|
|
963
|
+
i++;
|
|
964
|
+
}
|
|
965
|
+
break;
|
|
966
|
+
case 5 /* FormatOptionEnd */:
|
|
967
|
+
if (template[i] === "|") {
|
|
968
|
+
loc = 2 /* FormatName */;
|
|
969
|
+
i += 1;
|
|
970
|
+
startFormat();
|
|
971
|
+
} else if (template[i] === "}" && template[i + 1] === "}") {
|
|
972
|
+
loc = 0 /* Static */;
|
|
973
|
+
i += 2;
|
|
974
|
+
parsed.segments.push(segment);
|
|
975
|
+
} else ;
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
if (loc === 0 /* Static */ && buffer.length > 0) {
|
|
980
|
+
parsed.segments.push({ type: 0 /* Static */, text: buffer });
|
|
981
|
+
}
|
|
982
|
+
return parsed;
|
|
717
983
|
}
|
|
984
|
+
}
|
|
985
|
+
class I18n {
|
|
986
|
+
#dolla;
|
|
987
|
+
#logger;
|
|
988
|
+
#translations = /* @__PURE__ */ new Map();
|
|
989
|
+
#cache = [];
|
|
990
|
+
#formats = /* @__PURE__ */ new Map();
|
|
991
|
+
#initialLocale = "auto";
|
|
992
|
+
#locale = atom("");
|
|
718
993
|
get locale() {
|
|
719
|
-
return
|
|
994
|
+
return this.#locale;
|
|
995
|
+
}
|
|
996
|
+
constructor(dolla) {
|
|
997
|
+
this.#dolla = dolla;
|
|
998
|
+
this.#logger = dolla.createLogger("Dolla.i18n");
|
|
999
|
+
this.addFormat("number", (_, value, options) => {
|
|
1000
|
+
return this.#formatNumber(Number(value), options);
|
|
1001
|
+
});
|
|
1002
|
+
this.addFormat("datetime", (_, value, options) => {
|
|
1003
|
+
return this.#formatDateTime(value, options);
|
|
1004
|
+
});
|
|
1005
|
+
this.addFormat("list", (_, value, options) => {
|
|
1006
|
+
return this.#formatList(value, options);
|
|
1007
|
+
});
|
|
1008
|
+
dolla.beforeMount(async () => {
|
|
1009
|
+
if (this.#translations.size > 0) {
|
|
1010
|
+
await this.setLocale(this.#initialLocale);
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
720
1013
|
}
|
|
721
1014
|
get locales() {
|
|
722
|
-
return [...
|
|
723
|
-
}
|
|
724
|
-
setup(
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
})
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
1015
|
+
return [...this.#translations.keys()];
|
|
1016
|
+
}
|
|
1017
|
+
setup(options) {
|
|
1018
|
+
options.translations.forEach((entry) => {
|
|
1019
|
+
this.#translations.set(entry.locale, new Translation(entry, this.#dolla));
|
|
1020
|
+
});
|
|
1021
|
+
if (options.locale && options.locale !== "auto") {
|
|
1022
|
+
const isRegistered = options.translations.some((entry) => entry.locale === options.locale);
|
|
1023
|
+
if (!isRegistered) {
|
|
1024
|
+
throw new Error(`Initial locale '${options.locale}' is not registered in the locales array.`);
|
|
1025
|
+
}
|
|
1026
|
+
this.#initialLocale = options.locale;
|
|
1027
|
+
}
|
|
1028
|
+
this.#logger.info(
|
|
1029
|
+
`${this.#translations.size} language${this.#translations.size === 1 ? "" : "s"} supported: '${[...this.#translations.keys()].join("', '")}'`
|
|
734
1030
|
);
|
|
735
1031
|
}
|
|
736
|
-
async setLocale(
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1032
|
+
async setLocale(name) {
|
|
1033
|
+
let realName;
|
|
1034
|
+
if (name === "auto") {
|
|
1035
|
+
let names = [];
|
|
1036
|
+
if (typeof navigator !== "undefined") {
|
|
1037
|
+
const nav = navigator;
|
|
1038
|
+
if (nav.languages?.length > 0) {
|
|
1039
|
+
names.push(...nav.languages);
|
|
1040
|
+
} else if (nav.language) {
|
|
1041
|
+
names.push(nav.language);
|
|
1042
|
+
} else if (nav.browserLanguage) {
|
|
1043
|
+
names.push(nav.browserLanguage);
|
|
1044
|
+
} else if (nav.userLanguage) {
|
|
1045
|
+
names.push(nav.userLanguage);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
for (const name2 of names) {
|
|
1049
|
+
if (this.#translations.has(name2)) {
|
|
1050
|
+
realName = name2;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
if (this.#translations.has(name)) {
|
|
1055
|
+
realName = name;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
if (realName == null) {
|
|
1059
|
+
const firstLanguage = this.#translations.keys().next().value;
|
|
1060
|
+
if (firstLanguage) {
|
|
1061
|
+
realName = firstLanguage;
|
|
744
1062
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
const n = a(this, g).keys().next().value;
|
|
751
|
-
n && (e = n);
|
|
752
|
-
}
|
|
753
|
-
if (!e || !a(this, g).has(e))
|
|
754
|
-
throw new Error(`Locale '${t}' has no translation.`);
|
|
755
|
-
const s = a(this, g).get(e);
|
|
1063
|
+
}
|
|
1064
|
+
if (!realName || !this.#translations.has(realName)) {
|
|
1065
|
+
throw new Error(`Locale '${name}' has no translation.`);
|
|
1066
|
+
}
|
|
1067
|
+
const translation = this.#translations.get(realName);
|
|
756
1068
|
try {
|
|
757
|
-
await
|
|
758
|
-
|
|
759
|
-
|
|
1069
|
+
await translation.load();
|
|
1070
|
+
this.#cache = [];
|
|
1071
|
+
this.#locale.value = realName;
|
|
1072
|
+
this.#logger.info("set language to " + realName);
|
|
1073
|
+
} catch (error) {
|
|
1074
|
+
if (error instanceof Error) {
|
|
1075
|
+
this.#logger.crash(error);
|
|
1076
|
+
}
|
|
760
1077
|
}
|
|
761
1078
|
}
|
|
762
1079
|
/**
|
|
@@ -768,18 +1085,71 @@ class $e {
|
|
|
768
1085
|
* @example
|
|
769
1086
|
* const $value = t("your.key.here", { count: 5 });
|
|
770
1087
|
*/
|
|
771
|
-
t(
|
|
772
|
-
if (this === void 0)
|
|
1088
|
+
t(selector, options) {
|
|
1089
|
+
if (this === void 0) {
|
|
773
1090
|
throw new Error(
|
|
774
1091
|
`The 't' function cannot be destructured. If you need a standalone version you can import it like so: 'import { t } from "@manyducks.co/dolla"'`
|
|
775
1092
|
);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1093
|
+
}
|
|
1094
|
+
return compose(() => {
|
|
1095
|
+
const values = {};
|
|
1096
|
+
for (const key in options) {
|
|
1097
|
+
values[key] = get(options[key]);
|
|
1098
|
+
}
|
|
1099
|
+
return this.#getValue(get(this.#locale), selector, values);
|
|
781
1100
|
});
|
|
782
1101
|
}
|
|
1102
|
+
#getValue(locale, selector, options) {
|
|
1103
|
+
const cached = this.#getCached(selector, options);
|
|
1104
|
+
if (cached) return cached;
|
|
1105
|
+
const translation = this.#translations.get(locale);
|
|
1106
|
+
if (options.context != null) {
|
|
1107
|
+
selector += "_" + options.context;
|
|
1108
|
+
}
|
|
1109
|
+
if (options.count != null) {
|
|
1110
|
+
if (options.ordinal) {
|
|
1111
|
+
const exact = `${selector}_ordinal_(=${options.count})`;
|
|
1112
|
+
if (translation.hasTemplate(exact)) {
|
|
1113
|
+
selector = exact;
|
|
1114
|
+
} else {
|
|
1115
|
+
selector += "_ordinal_" + new Intl.PluralRules(locale, { type: "ordinal" }).select(options.count);
|
|
1116
|
+
}
|
|
1117
|
+
} else {
|
|
1118
|
+
const exact = `${selector}_(=${options.count})`;
|
|
1119
|
+
if (translation.hasTemplate(exact)) {
|
|
1120
|
+
selector = exact;
|
|
1121
|
+
} else {
|
|
1122
|
+
selector += "_" + new Intl.PluralRules(locale).select(options.count);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const template = translation.getTemplate(selector);
|
|
1127
|
+
let output = "";
|
|
1128
|
+
for (const segment of template.segments) {
|
|
1129
|
+
if (segment.type === 0 /* Static */) {
|
|
1130
|
+
output += segment.text;
|
|
1131
|
+
} else if (segment.type === 1 /* Variable */) {
|
|
1132
|
+
let value = resolve(options, segment.name);
|
|
1133
|
+
const formats = options.formatOverrides?.[segment.name] ?? [...segment.formats];
|
|
1134
|
+
if (segment.name === "count" && formats.length === 0) {
|
|
1135
|
+
formats.push({ name: "number", options: {} });
|
|
1136
|
+
}
|
|
1137
|
+
for (const format of formats) {
|
|
1138
|
+
const fn = this.#formats.get(format.name);
|
|
1139
|
+
if (fn == null) {
|
|
1140
|
+
const error = new Error(
|
|
1141
|
+
`Failed to load format '${format.name}' when processing '${selector}', template: ${template}`
|
|
1142
|
+
);
|
|
1143
|
+
this.#logger.crash(error);
|
|
1144
|
+
throw error;
|
|
1145
|
+
}
|
|
1146
|
+
value = fn(locale, value, format.options);
|
|
1147
|
+
}
|
|
1148
|
+
output += value;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
return output;
|
|
1152
|
+
}
|
|
783
1153
|
/**
|
|
784
1154
|
* Add a custom format callback.
|
|
785
1155
|
*
|
|
@@ -794,8 +1164,8 @@ class $e {
|
|
|
794
1164
|
*
|
|
795
1165
|
* t("greeting", {name: "world"}); // State<"Hello, WORLD!">
|
|
796
1166
|
*/
|
|
797
|
-
addFormat(
|
|
798
|
-
|
|
1167
|
+
addFormat(name, callback) {
|
|
1168
|
+
this.#formats.set(name, callback);
|
|
799
1169
|
}
|
|
800
1170
|
/**
|
|
801
1171
|
* Creates an `Intl.Collator` configured for the current locale.
|
|
@@ -803,16 +1173,22 @@ class $e {
|
|
|
803
1173
|
*
|
|
804
1174
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#options
|
|
805
1175
|
*/
|
|
806
|
-
collator(
|
|
807
|
-
return new Intl.Collator(
|
|
1176
|
+
collator(options) {
|
|
1177
|
+
return new Intl.Collator(this.#locale.value, options);
|
|
808
1178
|
}
|
|
809
1179
|
/**
|
|
810
1180
|
* Formats a number for the current locale. Uses `Intl.NumberFormat` under the hood.
|
|
811
1181
|
*
|
|
812
1182
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options
|
|
813
1183
|
*/
|
|
814
|
-
number(
|
|
815
|
-
return
|
|
1184
|
+
number(count, options) {
|
|
1185
|
+
return compose(() => {
|
|
1186
|
+
get(this.#locale);
|
|
1187
|
+
return this.#formatNumber(get(count), options);
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
#formatNumber(count, options) {
|
|
1191
|
+
return new Intl.NumberFormat(this.#locale.value, options).format(count);
|
|
816
1192
|
}
|
|
817
1193
|
/**
|
|
818
1194
|
* Formats a date for the current locale. Uses `Intl.DateTimeFormat` under the hood.
|
|
@@ -823,8 +1199,14 @@ class $e {
|
|
|
823
1199
|
* const date = new Date();
|
|
824
1200
|
* const $formatted = Dolla.i18n.dateTime(date, { dateFormat: "short" });
|
|
825
1201
|
*/
|
|
826
|
-
dateTime(
|
|
827
|
-
return
|
|
1202
|
+
dateTime(date, options) {
|
|
1203
|
+
return compose(() => {
|
|
1204
|
+
get(this.#locale);
|
|
1205
|
+
return this.#formatDateTime(get(date), options);
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
#formatDateTime(date, options) {
|
|
1209
|
+
return new Intl.DateTimeFormat(this.#locale.value, options).format(isString(date) ? new Date(date) : date);
|
|
828
1210
|
}
|
|
829
1211
|
/**
|
|
830
1212
|
* Formats a list for the current locale. Uses `Intl.ListFormat` under the hood.
|
|
@@ -835,69 +1217,41 @@ class $e {
|
|
|
835
1217
|
* const list = new Date();
|
|
836
1218
|
* const $formatted = Dolla.i18n.list(list, { });
|
|
837
1219
|
*/
|
|
838
|
-
list(
|
|
839
|
-
return
|
|
1220
|
+
list(list, options) {
|
|
1221
|
+
return compose(() => {
|
|
1222
|
+
get(this.#locale);
|
|
1223
|
+
return this.#formatList(get(list), options);
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
#formatList(list, options) {
|
|
1227
|
+
return new Intl.ListFormat(this.#locale.value, options).format(list);
|
|
1228
|
+
}
|
|
1229
|
+
// relativeTime(): State<string> {
|
|
1230
|
+
// }
|
|
1231
|
+
#getCached(key, values) {
|
|
1232
|
+
for (const entry of this.#cache) {
|
|
1233
|
+
if (entry[0] === key && deepEqual(entry[1], values)) {
|
|
1234
|
+
return entry[2];
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
840
1237
|
}
|
|
841
1238
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
const p = `${e}_ordinal_(=${s.count})`;
|
|
850
|
-
n.hasTemplate(p) ? e = p : e += "_ordinal_" + new Intl.PluralRules(t, { type: "ordinal" }).select(s.count);
|
|
1239
|
+
function resolve(object, key) {
|
|
1240
|
+
const parsed = String(key).split(/[\.\[\]]/).filter((part) => part.trim() !== "");
|
|
1241
|
+
let value = object;
|
|
1242
|
+
while (parsed.length > 0) {
|
|
1243
|
+
const part = parsed.shift();
|
|
1244
|
+
if (value != null) {
|
|
1245
|
+
value = value[part];
|
|
851
1246
|
} else {
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
let c = "";
|
|
857
|
-
for (const p of o.segments)
|
|
858
|
-
if (p.type === 0)
|
|
859
|
-
c += p.text;
|
|
860
|
-
else if (p.type === 1) {
|
|
861
|
-
let v = Re(s, p.name);
|
|
862
|
-
const k = ((l = s.formatOverrides) == null ? void 0 : l[p.name]) ?? [...p.formats];
|
|
863
|
-
p.name === "count" && k.length === 0 && k.push({ name: "number", options: {} });
|
|
864
|
-
for (const h of k) {
|
|
865
|
-
const x = a(this, Z).get(h.name);
|
|
866
|
-
if (x == null) {
|
|
867
|
-
const $ = new Error(
|
|
868
|
-
`Failed to load format '${h.name}' when processing '${e}', template: ${o}`
|
|
869
|
-
);
|
|
870
|
-
throw a(this, P).crash($), $;
|
|
871
|
-
}
|
|
872
|
-
v = x(t, v, h.options);
|
|
873
|
-
}
|
|
874
|
-
c += v;
|
|
875
|
-
}
|
|
876
|
-
return c;
|
|
877
|
-
}, vt = function(t, e) {
|
|
878
|
-
return new Intl.NumberFormat(a(this, b).value, e).format(t);
|
|
879
|
-
}, Et = function(t, e) {
|
|
880
|
-
return new Intl.DateTimeFormat(a(this, b).value, e).format(M(t) ? new Date(t) : t);
|
|
881
|
-
}, kt = function(t, e) {
|
|
882
|
-
return new Intl.ListFormat(a(this, b).value, e).format(t);
|
|
883
|
-
}, // relativeTime(): State<string> {
|
|
884
|
-
// }
|
|
885
|
-
Ht = function(t, e) {
|
|
886
|
-
for (const s of a(this, Y))
|
|
887
|
-
if (s[0] === t && Jt(s[1], e))
|
|
888
|
-
return s[2];
|
|
889
|
-
};
|
|
890
|
-
function Re(i, t) {
|
|
891
|
-
const e = String(t).split(/[\.\[\]]/).filter((r) => r.trim() !== "");
|
|
892
|
-
let s = i;
|
|
893
|
-
for (; e.length > 0; ) {
|
|
894
|
-
const r = e.shift();
|
|
895
|
-
s != null ? s = s[r] : s = void 0;
|
|
896
|
-
}
|
|
897
|
-
return s;
|
|
1247
|
+
value = void 0;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return value;
|
|
898
1251
|
}
|
|
899
|
-
|
|
900
|
-
|
|
1252
|
+
|
|
1253
|
+
function DefaultCrashView(props) {
|
|
1254
|
+
return html`
|
|
901
1255
|
<div
|
|
902
1256
|
style=${{
|
|
903
1257
|
backgroundColor: "#880000",
|
|
@@ -910,8 +1264,8 @@ function _e(i) {
|
|
|
910
1264
|
>
|
|
911
1265
|
<h1 style=${{ marginBottom: "0.5rem" }}>The app has crashed</h1>
|
|
912
1266
|
<p style=${{ marginBottom: "0.25rem" }}>
|
|
913
|
-
<span style=${{ fontFamily: "monospace" }}>${
|
|
914
|
-
${
|
|
1267
|
+
<span style=${{ fontFamily: "monospace" }}>${props.loggerName}</span>
|
|
1268
|
+
${cond(props.uid, html`<span style=${{ fontFamily: "monospace", opacity: 0.5 }}> [uid: ${props.uid}]</span>`)}
|
|
915
1269
|
${" "}says:
|
|
916
1270
|
</p>
|
|
917
1271
|
<blockquote
|
|
@@ -934,250 +1288,276 @@ function _e(i) {
|
|
|
934
1288
|
fontWeight: "bold"
|
|
935
1289
|
}}
|
|
936
1290
|
>
|
|
937
|
-
${
|
|
1291
|
+
${props.error.name}
|
|
938
1292
|
</span>
|
|
939
|
-
${
|
|
1293
|
+
${props.error.message}
|
|
940
1294
|
</blockquote>
|
|
941
1295
|
|
|
942
1296
|
<p>Please see the browser console for details.</p>
|
|
943
1297
|
</div>
|
|
944
1298
|
`;
|
|
945
1299
|
}
|
|
946
|
-
|
|
947
|
-
class
|
|
1300
|
+
|
|
1301
|
+
class Dolla {
|
|
1302
|
+
// TODO: Take these off the global Dolla object.
|
|
1303
|
+
http;
|
|
1304
|
+
i18n;
|
|
1305
|
+
#isMounted = false;
|
|
1306
|
+
#env = "production";
|
|
1307
|
+
#rootElement;
|
|
1308
|
+
#rootView;
|
|
1309
|
+
#crashView = DefaultCrashView;
|
|
1310
|
+
#router;
|
|
1311
|
+
#beforeMountCallbacks = [];
|
|
1312
|
+
#onMountCallbacks = [];
|
|
1313
|
+
#beforeUnmountCallbacks = [];
|
|
1314
|
+
#onUnmountCallbacks = [];
|
|
1315
|
+
#rootElementContext = {
|
|
1316
|
+
root: this,
|
|
1317
|
+
stores: /* @__PURE__ */ new Map(),
|
|
1318
|
+
viewName: "Dolla"
|
|
1319
|
+
};
|
|
1320
|
+
#loggles = {
|
|
1321
|
+
info: "development",
|
|
1322
|
+
log: "development",
|
|
1323
|
+
warn: "development",
|
|
1324
|
+
error: true
|
|
1325
|
+
};
|
|
1326
|
+
#match = createMatcher("*,-Dolla.*");
|
|
1327
|
+
// Registration functions for modules.
|
|
1328
|
+
// All modules will be registered before mount.
|
|
1329
|
+
#modules = [];
|
|
948
1330
|
constructor() {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
w(this, "i18n");
|
|
952
|
-
u(this, L, !1);
|
|
953
|
-
u(this, et, "production");
|
|
954
|
-
u(this, F);
|
|
955
|
-
u(this, I);
|
|
956
|
-
u(this, st, _e);
|
|
957
|
-
u(this, z);
|
|
958
|
-
u(this, nt, []);
|
|
959
|
-
u(this, rt, []);
|
|
960
|
-
u(this, at, []);
|
|
961
|
-
u(this, it, []);
|
|
962
|
-
u(this, O, {
|
|
963
|
-
root: this,
|
|
964
|
-
stores: /* @__PURE__ */ new Map(),
|
|
965
|
-
viewName: "Dolla"
|
|
966
|
-
});
|
|
967
|
-
u(this, ot, {
|
|
968
|
-
info: "development",
|
|
969
|
-
log: "development",
|
|
970
|
-
warn: "development",
|
|
971
|
-
error: !0
|
|
972
|
-
});
|
|
973
|
-
u(this, lt, xt("*,-Dolla.*"));
|
|
974
|
-
// Registration functions for modules.
|
|
975
|
-
// All modules will be registered before mount.
|
|
976
|
-
u(this, dt, []);
|
|
977
|
-
this.http = new ge(), this.i18n = new $e(this);
|
|
1331
|
+
this.http = new HTTP();
|
|
1332
|
+
this.i18n = new I18n(this);
|
|
978
1333
|
}
|
|
979
1334
|
/**
|
|
980
1335
|
* True when the app is connected to a DOM node and displayed to the user.
|
|
981
1336
|
*/
|
|
982
1337
|
get isMounted() {
|
|
983
|
-
return
|
|
1338
|
+
return this.#isMounted;
|
|
984
1339
|
}
|
|
985
1340
|
/**
|
|
986
1341
|
* Get the current environment that this app is running in.
|
|
987
1342
|
* Environment affects which log messages will print and how much debugging info is included in the DOM.
|
|
988
1343
|
*/
|
|
989
1344
|
getEnv() {
|
|
990
|
-
return
|
|
1345
|
+
return this.#env;
|
|
991
1346
|
}
|
|
992
1347
|
/**
|
|
993
1348
|
* Sets the environment that this app is running in.
|
|
994
1349
|
* Environment affects which log messages will print and how much debugging info is included in the DOM.
|
|
995
1350
|
*/
|
|
996
|
-
setEnv(
|
|
997
|
-
|
|
1351
|
+
setEnv(value) {
|
|
1352
|
+
this.#env = value;
|
|
998
1353
|
}
|
|
999
1354
|
/**
|
|
1000
1355
|
* Sets the view that will be shown when the `crash` method is called on any logger.
|
|
1001
1356
|
* When a crash is reported the app will be unmounted and replaced with this crash page.
|
|
1002
1357
|
*/
|
|
1003
|
-
setCrashView(
|
|
1004
|
-
|
|
1358
|
+
setCrashView(view) {
|
|
1359
|
+
this.#crashView = view;
|
|
1005
1360
|
}
|
|
1006
1361
|
/**
|
|
1007
1362
|
* Returns the HTMLElement Dolla is mounted to. This will return undefined until Dolla.mount() is called.
|
|
1008
1363
|
*/
|
|
1009
1364
|
getRootElement() {
|
|
1010
|
-
return
|
|
1365
|
+
return this.#rootElement;
|
|
1011
1366
|
}
|
|
1012
1367
|
/**
|
|
1013
1368
|
* Returns the top level view Dolla is rendering inside the root element. This will return undefined until Dolla.mount() is called.
|
|
1014
1369
|
*/
|
|
1015
1370
|
getRootView() {
|
|
1016
|
-
return
|
|
1017
|
-
}
|
|
1018
|
-
provide(
|
|
1019
|
-
const
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
return
|
|
1371
|
+
return this.#rootView;
|
|
1372
|
+
}
|
|
1373
|
+
provide(store, options) {
|
|
1374
|
+
const instance = new Store(store, options);
|
|
1375
|
+
const attached = instance.attach(this.#rootElementContext);
|
|
1376
|
+
if (!attached) {
|
|
1377
|
+
let name = store.name ? `'${store.name}'` : "this store";
|
|
1378
|
+
console.warn(`An instance of ${name} was already attached to this context.`);
|
|
1379
|
+
return this.get(store);
|
|
1380
|
+
} else {
|
|
1381
|
+
return instance.value;
|
|
1025
1382
|
}
|
|
1026
1383
|
}
|
|
1027
1384
|
/**
|
|
1028
1385
|
* Gets the nearest instance of a store. Throws an error if the store isn't provided higher in the tree.
|
|
1029
1386
|
*/
|
|
1030
|
-
get(
|
|
1031
|
-
if (
|
|
1032
|
-
const
|
|
1033
|
-
if (
|
|
1034
|
-
throw new
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1387
|
+
get(store) {
|
|
1388
|
+
if (isFunction(store)) {
|
|
1389
|
+
const instance = this.#rootElementContext.stores.get(store);
|
|
1390
|
+
if (instance == null) {
|
|
1391
|
+
throw new StoreError(`Store not found on this context.`);
|
|
1392
|
+
} else {
|
|
1393
|
+
return instance.value;
|
|
1394
|
+
}
|
|
1395
|
+
} else {
|
|
1396
|
+
throw new StoreError(`Invalid store.`);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
async mount(target, root) {
|
|
1400
|
+
if (this.#isMounted) {
|
|
1401
|
+
throw new Error(`Dolla is already mounted.`);
|
|
1402
|
+
}
|
|
1403
|
+
if (isString(target)) {
|
|
1404
|
+
const match = document.querySelector(target);
|
|
1405
|
+
assertInstanceOf(HTMLElement, match, `Selector '${target}' did not match any element.`);
|
|
1406
|
+
this.#rootElement = match;
|
|
1407
|
+
} else {
|
|
1408
|
+
assertInstanceOf(HTMLElement, target, "Expected an HTML element or a selector string. Got type: %t, value: %v");
|
|
1409
|
+
this.#rootElement = target;
|
|
1410
|
+
}
|
|
1411
|
+
if (_isRouter(root)) {
|
|
1412
|
+
this.#router = root;
|
|
1413
|
+
}
|
|
1414
|
+
const view = _isRouter(root) ? Passthrough : root;
|
|
1415
|
+
const rootViewMarkup = createMarkup(view);
|
|
1416
|
+
this.#rootView = this.constructView(rootViewMarkup.type, rootViewMarkup.props);
|
|
1417
|
+
await Promise.all(this.#modules.map((register) => register()));
|
|
1418
|
+
if (_isRouter(root)) {
|
|
1419
|
+
await _mountRouter(root, this);
|
|
1420
|
+
}
|
|
1421
|
+
await Promise.all(this.#beforeMountCallbacks.map((callback) => callback()));
|
|
1422
|
+
this.#rootView.mount(this.#rootElement);
|
|
1423
|
+
this.#isMounted = true;
|
|
1424
|
+
for (const store of this.#rootElementContext.stores.values()) {
|
|
1425
|
+
store.handleMount();
|
|
1426
|
+
}
|
|
1427
|
+
for (const callback of this.#onMountCallbacks) {
|
|
1428
|
+
callback();
|
|
1429
|
+
}
|
|
1054
1430
|
}
|
|
1055
1431
|
async unmount() {
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1432
|
+
if (!this.#isMounted) return;
|
|
1433
|
+
await Promise.all(this.#beforeUnmountCallbacks.map((callback) => callback()));
|
|
1434
|
+
this.#rootView?.unmount(false);
|
|
1435
|
+
if (this.#router) {
|
|
1436
|
+
await _unmountRouter(this.#router);
|
|
1437
|
+
}
|
|
1438
|
+
this.#isMounted = false;
|
|
1439
|
+
for (const callback of this.#onUnmountCallbacks) {
|
|
1440
|
+
callback();
|
|
1061
1441
|
}
|
|
1062
1442
|
}
|
|
1063
1443
|
/**
|
|
1064
1444
|
* Registers a `callback` to run after `Dolla.mount` is called, before the app is mounted. If `callback` returns a Promise,
|
|
1065
1445
|
* it will be awaited before mounting finishes. Use this to perform initial setup before the app is displayed to the user.
|
|
1066
1446
|
*/
|
|
1067
|
-
beforeMount(
|
|
1068
|
-
|
|
1447
|
+
beforeMount(callback) {
|
|
1448
|
+
this.#beforeMountCallbacks.push(callback);
|
|
1069
1449
|
}
|
|
1070
1450
|
/**
|
|
1071
1451
|
* Registers a `callback` to run after the app is mounted.
|
|
1072
1452
|
*/
|
|
1073
|
-
onMount(
|
|
1074
|
-
|
|
1453
|
+
onMount(callback) {
|
|
1454
|
+
this.#onMountCallbacks.push(callback);
|
|
1075
1455
|
}
|
|
1076
1456
|
/**
|
|
1077
1457
|
* Registers a `callback` to run after `Dolla.unmount` is called, before the app is unmounted. If `callback` returns a Promise,
|
|
1078
1458
|
* it will be awaited before unmounting finishes. Use this to perform cleanup.
|
|
1079
1459
|
*/
|
|
1080
|
-
beforeUnmount(
|
|
1081
|
-
|
|
1460
|
+
beforeUnmount(callback) {
|
|
1461
|
+
this.#beforeUnmountCallbacks.push(callback);
|
|
1082
1462
|
}
|
|
1083
1463
|
/**
|
|
1084
1464
|
* Registers a `callback` to run after the app is unmounted.
|
|
1085
1465
|
*/
|
|
1086
|
-
onUnmount(
|
|
1087
|
-
|
|
1466
|
+
onUnmount(callback) {
|
|
1467
|
+
this.#onUnmountCallbacks.push(callback);
|
|
1088
1468
|
}
|
|
1089
1469
|
/**
|
|
1090
1470
|
* Update log type toggles. Values that are not passed will remain unchanged.
|
|
1091
1471
|
*/
|
|
1092
|
-
setLoggles(
|
|
1093
|
-
for (const
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1472
|
+
setLoggles(options) {
|
|
1473
|
+
for (const key in options) {
|
|
1474
|
+
const value = options[key];
|
|
1475
|
+
if (value) {
|
|
1476
|
+
this.#loggles[key] = value;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
setLogFilter(filter) {
|
|
1481
|
+
this.#match = createMatcher(filter);
|
|
1482
|
+
}
|
|
1483
|
+
createLogger(name, options) {
|
|
1484
|
+
const _console = options?.console ?? getDefaultConsole();
|
|
1485
|
+
const self = this;
|
|
1486
|
+
const loggles = this.#loggles;
|
|
1487
|
+
const bind = (method) => {
|
|
1488
|
+
if (loggles[method] === false || isString(loggles[method]) && loggles[method] !== self.getEnv() || !this.#match(name)) {
|
|
1489
|
+
return noOp;
|
|
1490
|
+
} else {
|
|
1491
|
+
let label = `%c${name}`;
|
|
1492
|
+
if (options?.uid) {
|
|
1493
|
+
label += ` %c[uid: %c${options.uid}%c]`;
|
|
1494
|
+
} else {
|
|
1495
|
+
label += `%c%c%c`;
|
|
1496
|
+
}
|
|
1497
|
+
return _console[method].bind(
|
|
1498
|
+
_console,
|
|
1499
|
+
label,
|
|
1500
|
+
`color:${okhash(label)};font-weight:bold`,
|
|
1501
|
+
`color:#777`,
|
|
1502
|
+
`color:#aaa`,
|
|
1503
|
+
`color:#777`
|
|
1114
1504
|
);
|
|
1115
1505
|
}
|
|
1116
1506
|
};
|
|
1117
1507
|
return {
|
|
1118
|
-
setName(
|
|
1119
|
-
|
|
1508
|
+
setName(newName) {
|
|
1509
|
+
name = newName;
|
|
1510
|
+
return this;
|
|
1120
1511
|
},
|
|
1121
1512
|
get info() {
|
|
1122
|
-
return
|
|
1513
|
+
return bind("info");
|
|
1123
1514
|
},
|
|
1124
1515
|
get log() {
|
|
1125
|
-
return
|
|
1516
|
+
return bind("log");
|
|
1126
1517
|
},
|
|
1127
1518
|
get warn() {
|
|
1128
|
-
return
|
|
1519
|
+
return bind("warn");
|
|
1129
1520
|
},
|
|
1130
1521
|
get error() {
|
|
1131
|
-
return
|
|
1522
|
+
return bind("error");
|
|
1132
1523
|
},
|
|
1133
|
-
crash(
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1524
|
+
crash(error) {
|
|
1525
|
+
if (self.isMounted) {
|
|
1526
|
+
self.unmount();
|
|
1527
|
+
const crashPage = self.constructView(self.#crashView, {
|
|
1528
|
+
error,
|
|
1529
|
+
loggerName: name,
|
|
1530
|
+
uid: options?.uid
|
|
1531
|
+
});
|
|
1532
|
+
crashPage.mount(self.#rootElement);
|
|
1533
|
+
}
|
|
1139
1534
|
}
|
|
1140
1535
|
};
|
|
1141
1536
|
}
|
|
1142
1537
|
/**
|
|
1143
1538
|
*
|
|
1144
1539
|
*/
|
|
1145
|
-
constructView(
|
|
1146
|
-
return new
|
|
1540
|
+
constructView(view, props, children = []) {
|
|
1541
|
+
return new View(this.#rootElementContext, view, props, children);
|
|
1147
1542
|
}
|
|
1148
1543
|
/**
|
|
1149
1544
|
*
|
|
1150
1545
|
*/
|
|
1151
|
-
constructMarkup(
|
|
1152
|
-
return
|
|
1546
|
+
constructMarkup(markup) {
|
|
1547
|
+
return groupElements(constructMarkup(this.#rootElementContext, markup));
|
|
1153
1548
|
}
|
|
1154
1549
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
if (typeof window < "u" && window.console)
|
|
1550
|
+
function getDefaultConsole() {
|
|
1551
|
+
if (typeof window !== "undefined" && window.console) {
|
|
1158
1552
|
return window.console;
|
|
1159
|
-
|
|
1553
|
+
}
|
|
1554
|
+
if (typeof global !== "undefined" && global.console) {
|
|
1160
1555
|
return global.console;
|
|
1556
|
+
}
|
|
1161
1557
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
Zt as createMarkup,
|
|
1168
|
-
Pe as createRouter,
|
|
1169
|
-
Jt as deepEqual,
|
|
1170
|
-
Pt as default,
|
|
1171
|
-
Ue as effect,
|
|
1172
|
-
E as get,
|
|
1173
|
-
Mt as html,
|
|
1174
|
-
je as list,
|
|
1175
|
-
Ce as peek,
|
|
1176
|
-
De as portal,
|
|
1177
|
-
Ne as ref,
|
|
1178
|
-
Ie as set,
|
|
1179
|
-
_t as shallowEqual,
|
|
1180
|
-
Ve as strictEqual,
|
|
1181
|
-
Le as t
|
|
1182
|
-
};
|
|
1558
|
+
|
|
1559
|
+
const dolla = new Dolla();
|
|
1560
|
+
const t = dolla.i18n.t.bind(dolla.i18n);
|
|
1561
|
+
|
|
1562
|
+
export { atom, compose, cond, createMarkup, createRouter, deepEqual, dolla as default, get, html, ref, shallowEqual, t };
|
|
1183
1563
|
//# sourceMappingURL=index.js.map
|