@manyducks.co/dolla 2.0.0-alpha.35 → 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 -12
- package/dist/core/context.d.ts +1 -52
- package/dist/core/dolla.d.ts +4 -4
- package/dist/core/nodes/dynamic.d.ts +5 -5
- package/dist/core/nodes/html.d.ts +11 -15
- package/dist/core/nodes/list.d.ts +9 -15
- package/dist/core/nodes/outlet.d.ts +6 -6
- package/dist/core/nodes/portal.d.ts +2 -2
- package/dist/core/nodes/view.d.ts +17 -34
- package/dist/core/ref.d.ts +0 -17
- package/dist/core/signals.d.ts +0 -3
- package/dist/core/store.d.ts +10 -16
- package/dist/core/symbols.d.ts +0 -1
- package/dist/http/index.d.ts +0 -1
- package/dist/index.js +1237 -898
- 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.d.ts +0 -96
- package/dist/typeChecking.test.d.ts +1 -0
- package/dist/types.d.ts +0 -1
- package/dist/utils.d.ts +4 -1
- package/docs/views.md +0 -86
- package/notes/context-routes.md +56 -0
- package/notes/elimination.md +33 -0
- package/package.json +3 -4
- package/vite.config.js +5 -3
- package/dist/core/_batch.d.ts +0 -21
- package/dist/markup-BWJWLvDF.js +0 -1634
- package/dist/markup-BWJWLvDF.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,262 +1,336 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
set _(n) {
|
|
10
|
-
d(i, e, n, t);
|
|
11
|
-
},
|
|
12
|
-
get _() {
|
|
13
|
-
return a(i, e, r);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
import { I as Je, a as Ue, b as Xe, i as Q, c as je, d as Ce, e as C, g as E, s as Me, f as Ye, h as k, j as G, P as De, k as Ze, t as xe, l as Te, m as et, p as tt, n as Ne, S as rt, o as Pe, q as Fe, r as st, u as ce, v as he, V as nt, w as at, x as it } from "./markup-BWJWLvDF.js";
|
|
17
|
-
import { y as Ct, D as Dt, A as It, C as Vt, z as qt, B as Gt } from "./markup-BWJWLvDF.js";
|
|
18
|
-
function Ft(i) {
|
|
19
|
-
function e() {
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
10
|
+
}
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
27
13
|
}
|
|
28
|
-
|
|
29
|
-
|
|
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 !== "");
|
|
30
18
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
i,
|
|
19
|
+
function joinPath(parts) {
|
|
20
|
+
assertArrayOf(
|
|
21
|
+
(part) => isFunction(part?.toString),
|
|
22
|
+
parts,
|
|
36
23
|
"Expected `parts` to be an array of objects with a .toString() method. Got type: %t, value: %v"
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 ?? "";
|
|
45
50
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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(/^\.\.\/?/, "");
|
|
55
67
|
break;
|
|
56
68
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
}
|
|
70
|
+
} else if (part.startsWith(".")) {
|
|
71
|
+
part = part.replace(/^\.\/?/, "");
|
|
72
|
+
} else {
|
|
60
73
|
break;
|
|
61
|
-
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return joinPath([resolved, part]);
|
|
62
77
|
}
|
|
63
|
-
function
|
|
64
|
-
if (!
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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];
|
|
69
95
|
});
|
|
70
|
-
return Object.fromEntries(
|
|
96
|
+
return Object.fromEntries(entries);
|
|
71
97
|
}
|
|
72
|
-
function
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
const { fragments
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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);
|
|
88
121
|
break;
|
|
89
|
-
} else
|
|
90
|
-
continue
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
case 3:
|
|
95
|
-
$.push({ ..._, value: s.slice(c).join("/") });
|
|
96
|
-
break t;
|
|
97
|
-
case 4:
|
|
98
|
-
if (isNaN(Number(T)))
|
|
99
|
-
continue e;
|
|
100
|
-
$.push({ ..._, value: Number(T) });
|
|
122
|
+
} else {
|
|
123
|
+
continue routes;
|
|
124
|
+
}
|
|
125
|
+
case 2 /* Param */:
|
|
126
|
+
matched.push({ ...frag, value: part });
|
|
101
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
|
+
}
|
|
102
138
|
default:
|
|
103
|
-
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);
|
|
104
152
|
}
|
|
105
153
|
}
|
|
106
|
-
const R = {};
|
|
107
|
-
for (const c of $)
|
|
108
|
-
c.type === 2 && (R[c.name] = decodeURIComponent(c.value)), c.type === 4 && (R[c.name] = c.value), c.type === 3 && (R.wildcard = "/" + decodeURIComponent(c.value));
|
|
109
154
|
return {
|
|
110
|
-
path: "/" +
|
|
111
|
-
pattern: "/" +
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
|
115
168
|
};
|
|
116
169
|
}
|
|
117
170
|
}
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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];
|
|
135
200
|
}
|
|
136
|
-
function
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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 */,
|
|
145
212
|
name: "*",
|
|
146
213
|
value: null
|
|
147
214
|
});
|
|
148
|
-
} else
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
}
|
|
157
228
|
}
|
|
158
|
-
return
|
|
229
|
+
return fragments;
|
|
159
230
|
}
|
|
160
|
-
|
|
161
|
-
|
|
231
|
+
|
|
232
|
+
function createRouter(options) {
|
|
233
|
+
return new Router(options);
|
|
162
234
|
}
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
235
|
+
const ROUTER_MOUNT = Symbol.for("DollaRouterMountMethod");
|
|
236
|
+
const ROUTER_UNMOUNT = Symbol.for("DollaRouterUnmountMethod");
|
|
237
|
+
function _isRouter(value) {
|
|
238
|
+
return value?.[IS_ROUTER] === true;
|
|
166
239
|
}
|
|
167
|
-
async function
|
|
168
|
-
return
|
|
240
|
+
async function _mountRouter(router, dolla) {
|
|
241
|
+
return router[ROUTER_MOUNT](dolla);
|
|
169
242
|
}
|
|
170
|
-
async function
|
|
171
|
-
return
|
|
243
|
+
async function _unmountRouter(router) {
|
|
244
|
+
return router[ROUTER_UNMOUNT]();
|
|
172
245
|
}
|
|
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
|
-
*/
|
|
218
|
-
p(this, "query", C(() => {
|
|
219
|
-
var e;
|
|
220
|
-
return ((e = E(a(this, P))) == null ? void 0 : e.query) ?? {};
|
|
221
|
-
}, { equals: Me }));
|
|
222
|
-
Ye(e, "Options must be an object. Got: %t"), e.hash && d(this, D, !0), d(this, z, lt(
|
|
223
|
-
e.routes.flatMap((t) => f(this, m, ve).call(this, t)).map((t) => ({
|
|
224
|
-
pattern: t.pattern,
|
|
225
|
-
meta: t.meta,
|
|
226
|
-
fragments: ct(t.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)
|
|
227
290
|
}))
|
|
228
|
-
)
|
|
291
|
+
);
|
|
292
|
+
assertValidRedirects(this.#routes);
|
|
229
293
|
}
|
|
230
|
-
async [
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
294
|
+
async [ROUTER_MOUNT](dolla) {
|
|
295
|
+
this.#dolla = dolla;
|
|
296
|
+
this.#logger = dolla.createLogger("Dolla.router");
|
|
297
|
+
const onPopState = () => {
|
|
298
|
+
this.#updateRoute();
|
|
234
299
|
};
|
|
235
|
-
window.addEventListener("popstate",
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
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);
|
|
241
311
|
})
|
|
242
|
-
)
|
|
312
|
+
);
|
|
313
|
+
this.#logger.info("will intercept clicks on <a> tags within root element", rootElement);
|
|
314
|
+
this.#isMounted = true;
|
|
315
|
+
await this.#updateRoute();
|
|
243
316
|
}
|
|
244
|
-
async [
|
|
245
|
-
for (const
|
|
246
|
-
|
|
247
|
-
|
|
317
|
+
async [ROUTER_UNMOUNT]() {
|
|
318
|
+
for (const callback of this.#unsubscribers) {
|
|
319
|
+
callback();
|
|
320
|
+
}
|
|
321
|
+
this.#unsubscribers = [];
|
|
248
322
|
}
|
|
249
323
|
/**
|
|
250
324
|
* Navigate backward. Pass a number of steps to hit the back button that many times.
|
|
251
325
|
*/
|
|
252
|
-
back(
|
|
253
|
-
window.history.go(-
|
|
326
|
+
back(steps = 1) {
|
|
327
|
+
window.history.go(-steps);
|
|
254
328
|
}
|
|
255
329
|
/**
|
|
256
330
|
* Navigate forward. Pass a number of steps to hit the forward button that many times.
|
|
257
331
|
*/
|
|
258
|
-
forward(
|
|
259
|
-
window.history.go(
|
|
332
|
+
forward(steps = 1) {
|
|
333
|
+
window.history.go(steps);
|
|
260
334
|
}
|
|
261
335
|
/**
|
|
262
336
|
* Navigates to another route.
|
|
@@ -265,499 +339,741 @@ class ft {
|
|
|
265
339
|
* router.go("/login"); // navigate to `/login`
|
|
266
340
|
* router.go["/users", 215], { replace: true }); // replace current history entry with `/users/215`
|
|
267
341
|
*/
|
|
268
|
-
go(
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
+
}
|
|
273
358
|
}
|
|
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
|
-
|
|
299
|
-
const
|
|
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
|
-
|
|
339
|
-
|
|
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}'` }]
|
|
340
425
|
};
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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.`);
|
|
456
|
+
}
|
|
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
|
+
}
|
|
378
498
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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]));
|
|
397
517
|
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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) {
|
|
408
545
|
return;
|
|
409
|
-
|
|
410
|
-
|
|
546
|
+
}
|
|
547
|
+
const anchor = traverse(e.target);
|
|
548
|
+
if (!anchor) {
|
|
549
|
+
return;
|
|
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);
|
|
411
556
|
}
|
|
412
|
-
|
|
413
|
-
|
|
557
|
+
root.addEventListener("click", handler);
|
|
558
|
+
return function cancel() {
|
|
559
|
+
root.removeEventListener("click", handler);
|
|
414
560
|
};
|
|
415
561
|
}
|
|
416
|
-
function
|
|
417
|
-
for (const
|
|
418
|
-
const
|
|
419
|
-
|
|
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);
|
|
420
566
|
}
|
|
421
|
-
return
|
|
567
|
+
return path;
|
|
422
568
|
}
|
|
423
|
-
function
|
|
424
|
-
for (const
|
|
425
|
-
if (
|
|
426
|
-
let
|
|
427
|
-
if (
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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, {
|
|
576
|
+
willMatch(r) {
|
|
577
|
+
return r !== route;
|
|
431
578
|
}
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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
|
+
}
|
|
436
586
|
}
|
|
587
|
+
}
|
|
437
588
|
}
|
|
438
|
-
class
|
|
589
|
+
class NoRouteError extends Error {
|
|
439
590
|
}
|
|
440
|
-
|
|
441
|
-
class
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
constructor() {
|
|
445
|
-
h(this, S);
|
|
446
|
-
h(this, V, []);
|
|
447
|
-
h(this, de, vt());
|
|
448
|
-
}
|
|
591
|
+
|
|
592
|
+
class HTTP {
|
|
593
|
+
#middleware = [];
|
|
594
|
+
#fetch = getDefaultFetch();
|
|
449
595
|
/**
|
|
450
596
|
* Adds a new middleware that will apply to subsequent requests.
|
|
451
597
|
* Returns a function to remove this middleware.
|
|
452
598
|
*
|
|
453
599
|
* @param middleware - A middleware function that will intercept requests.
|
|
454
600
|
*/
|
|
455
|
-
use(
|
|
456
|
-
|
|
457
|
-
|
|
601
|
+
use(fn) {
|
|
602
|
+
this.#middleware.push(fn);
|
|
603
|
+
return () => {
|
|
604
|
+
this.#middleware.splice(this.#middleware.indexOf(fn), 1);
|
|
458
605
|
};
|
|
459
606
|
}
|
|
460
|
-
async get(
|
|
461
|
-
return
|
|
607
|
+
async get(uri, options) {
|
|
608
|
+
return this.#request("get", uri, options);
|
|
462
609
|
}
|
|
463
|
-
async put(
|
|
464
|
-
return
|
|
610
|
+
async put(uri, options) {
|
|
611
|
+
return this.#request("put", uri, options);
|
|
465
612
|
}
|
|
466
|
-
async patch(
|
|
467
|
-
return
|
|
613
|
+
async patch(uri, options) {
|
|
614
|
+
return this.#request("patch", uri, options);
|
|
468
615
|
}
|
|
469
|
-
async post(
|
|
470
|
-
return
|
|
616
|
+
async post(uri, options) {
|
|
617
|
+
return this.#request("post", uri, options);
|
|
471
618
|
}
|
|
472
|
-
async delete(
|
|
473
|
-
return
|
|
619
|
+
async delete(uri, options) {
|
|
620
|
+
return this.#request("delete", uri, options);
|
|
474
621
|
}
|
|
475
|
-
async head(
|
|
476
|
-
return
|
|
622
|
+
async head(uri, options) {
|
|
623
|
+
return this.#request("head", uri, options);
|
|
477
624
|
}
|
|
478
|
-
async options(
|
|
479
|
-
return
|
|
625
|
+
async options(uri, options) {
|
|
626
|
+
return this.#request("options", uri, options);
|
|
480
627
|
}
|
|
481
|
-
async trace(
|
|
482
|
-
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();
|
|
483
640
|
}
|
|
484
641
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
...r,
|
|
488
|
-
method: e,
|
|
489
|
-
uri: t,
|
|
490
|
-
middleware: a(this, V),
|
|
491
|
-
fetch: a(this, de)
|
|
492
|
-
}).fetch();
|
|
493
|
-
};
|
|
494
|
-
function vt() {
|
|
495
|
-
if (typeof window < "u" && window.fetch)
|
|
642
|
+
function getDefaultFetch() {
|
|
643
|
+
if (typeof window !== "undefined" && window.fetch) {
|
|
496
644
|
return window.fetch.bind(window);
|
|
497
|
-
|
|
645
|
+
}
|
|
646
|
+
if (typeof global !== "undefined" && global.fetch) {
|
|
498
647
|
return global.fetch.bind(global);
|
|
648
|
+
}
|
|
499
649
|
throw new Error("Running in neither browser nor node. Please run this app in one of the supported environments.");
|
|
500
650
|
}
|
|
501
|
-
class
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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;
|
|
507
658
|
}
|
|
508
659
|
}
|
|
509
|
-
class
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
p(this, "body");
|
|
515
|
-
this.method = e.method, this.body = e.body, e.uri.startsWith("http") ? this.url = new URL(e.uri) : this.url = new URL(e.uri, window.location.origin), this._applyHeaders(e.headers), this._applyQueryParams(e.query);
|
|
516
|
-
}
|
|
660
|
+
class Request {
|
|
661
|
+
method;
|
|
662
|
+
url;
|
|
663
|
+
headers = new Headers();
|
|
664
|
+
body;
|
|
517
665
|
get isSameOrigin() {
|
|
518
666
|
return this.url.origin === window.location.origin;
|
|
519
667
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
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));
|
|
530
692
|
}
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
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));
|
|
544
711
|
}
|
|
545
|
-
|
|
546
|
-
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
throw new TypeError(`Unknown query params type. Got: ${query}`);
|
|
715
|
+
}
|
|
547
716
|
}
|
|
548
717
|
}
|
|
549
|
-
class
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
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);
|
|
556
727
|
}
|
|
557
728
|
async fetch() {
|
|
558
729
|
if (this._middleware.length > 0) {
|
|
559
|
-
const
|
|
560
|
-
const
|
|
561
|
-
|
|
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
|
+
});
|
|
562
737
|
};
|
|
563
|
-
await
|
|
564
|
-
} else
|
|
738
|
+
await mount()();
|
|
739
|
+
} else {
|
|
565
740
|
await this._handler();
|
|
566
|
-
|
|
567
|
-
|
|
741
|
+
}
|
|
742
|
+
if (this._response.status < 200 || this._response.status >= 400) {
|
|
743
|
+
throw new HTTPResponseError(this._response);
|
|
744
|
+
}
|
|
568
745
|
return this._response;
|
|
569
746
|
}
|
|
570
747
|
// This is the function that performs the actual request after the final middleware.
|
|
571
748
|
async _handler() {
|
|
572
|
-
let
|
|
573
|
-
const
|
|
574
|
-
!
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
body
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
|
588
778
|
};
|
|
589
779
|
}
|
|
590
780
|
}
|
|
591
|
-
|
|
592
|
-
class
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
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;
|
|
600
790
|
}
|
|
601
791
|
async load() {
|
|
602
|
-
let
|
|
603
|
-
if (!
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
else if (
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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 {
|
|
616
808
|
throw new Error(
|
|
617
|
-
`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}`
|
|
618
810
|
);
|
|
619
|
-
|
|
620
|
-
|
|
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]);
|
|
621
821
|
}
|
|
822
|
+
} else {
|
|
823
|
+
throw new Error(`Language could not be loaded.`);
|
|
622
824
|
}
|
|
623
|
-
if (e) {
|
|
624
|
-
const t = f(this, W, $e).call(this, e);
|
|
625
|
-
for (const r of t)
|
|
626
|
-
a(this, B).set(r[0], r[1]);
|
|
627
|
-
} else
|
|
628
|
-
throw new Error("Language could not be loaded.");
|
|
629
825
|
}
|
|
630
|
-
getTemplate(
|
|
631
|
-
return
|
|
632
|
-
segments: [{ type: 0
|
|
826
|
+
getTemplate(selector) {
|
|
827
|
+
return this.#templates.get(selector) ?? {
|
|
828
|
+
segments: [{ type: 0 /* Static */, text: `[MISSING: ${selector}]` }]
|
|
633
829
|
};
|
|
634
830
|
}
|
|
635
|
-
hasTemplate(
|
|
636
|
-
return
|
|
831
|
+
hasTemplate(selector) {
|
|
832
|
+
return this.#templates.has(selector);
|
|
637
833
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
+
}
|
|
653
849
|
}
|
|
654
|
-
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
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: []
|
|
669
864
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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
|
+
};
|
|
674
877
|
};
|
|
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
|
-
|
|
717
|
-
|
|
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;
|
|
718
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("");
|
|
719
993
|
get locale() {
|
|
720
|
-
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
|
+
});
|
|
721
1013
|
}
|
|
722
1014
|
get locales() {
|
|
723
|
-
return [...
|
|
724
|
-
}
|
|
725
|
-
setup(
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
})
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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("', '")}'`
|
|
735
1030
|
);
|
|
736
1031
|
}
|
|
737
|
-
async setLocale(
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
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;
|
|
745
1056
|
}
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
const
|
|
1057
|
+
}
|
|
1058
|
+
if (realName == null) {
|
|
1059
|
+
const firstLanguage = this.#translations.keys().next().value;
|
|
1060
|
+
if (firstLanguage) {
|
|
1061
|
+
realName = firstLanguage;
|
|
1062
|
+
}
|
|
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);
|
|
757
1068
|
try {
|
|
758
|
-
await
|
|
759
|
-
|
|
760
|
-
|
|
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
|
+
}
|
|
761
1077
|
}
|
|
762
1078
|
}
|
|
763
1079
|
/**
|
|
@@ -769,18 +1085,71 @@ class _t {
|
|
|
769
1085
|
* @example
|
|
770
1086
|
* const $value = t("your.key.here", { count: 5 });
|
|
771
1087
|
*/
|
|
772
|
-
t(
|
|
773
|
-
if (this === void 0)
|
|
1088
|
+
t(selector, options) {
|
|
1089
|
+
if (this === void 0) {
|
|
774
1090
|
throw new Error(
|
|
775
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"'`
|
|
776
1092
|
);
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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);
|
|
782
1100
|
});
|
|
783
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
|
+
}
|
|
784
1153
|
/**
|
|
785
1154
|
* Add a custom format callback.
|
|
786
1155
|
*
|
|
@@ -795,8 +1164,8 @@ class _t {
|
|
|
795
1164
|
*
|
|
796
1165
|
* t("greeting", {name: "world"}); // State<"Hello, WORLD!">
|
|
797
1166
|
*/
|
|
798
|
-
addFormat(
|
|
799
|
-
|
|
1167
|
+
addFormat(name, callback) {
|
|
1168
|
+
this.#formats.set(name, callback);
|
|
800
1169
|
}
|
|
801
1170
|
/**
|
|
802
1171
|
* Creates an `Intl.Collator` configured for the current locale.
|
|
@@ -804,16 +1173,22 @@ class _t {
|
|
|
804
1173
|
*
|
|
805
1174
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#options
|
|
806
1175
|
*/
|
|
807
|
-
collator(
|
|
808
|
-
return new Intl.Collator(
|
|
1176
|
+
collator(options) {
|
|
1177
|
+
return new Intl.Collator(this.#locale.value, options);
|
|
809
1178
|
}
|
|
810
1179
|
/**
|
|
811
1180
|
* Formats a number for the current locale. Uses `Intl.NumberFormat` under the hood.
|
|
812
1181
|
*
|
|
813
1182
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#options
|
|
814
1183
|
*/
|
|
815
|
-
number(
|
|
816
|
-
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);
|
|
817
1192
|
}
|
|
818
1193
|
/**
|
|
819
1194
|
* Formats a date for the current locale. Uses `Intl.DateTimeFormat` under the hood.
|
|
@@ -824,8 +1199,14 @@ class _t {
|
|
|
824
1199
|
* const date = new Date();
|
|
825
1200
|
* const $formatted = Dolla.i18n.dateTime(date, { dateFormat: "short" });
|
|
826
1201
|
*/
|
|
827
|
-
dateTime(
|
|
828
|
-
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);
|
|
829
1210
|
}
|
|
830
1211
|
/**
|
|
831
1212
|
* Formats a list for the current locale. Uses `Intl.ListFormat` under the hood.
|
|
@@ -836,69 +1217,41 @@ class _t {
|
|
|
836
1217
|
* const list = new Date();
|
|
837
1218
|
* const $formatted = Dolla.i18n.list(list, { });
|
|
838
1219
|
*/
|
|
839
|
-
list(
|
|
840
|
-
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
|
+
}
|
|
841
1237
|
}
|
|
842
1238
|
}
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const w = `${t}_ordinal_(=${r.count})`;
|
|
851
|
-
s.hasTemplate(w) ? t = w : t += "_ordinal_" + new Intl.PluralRules(e, { type: "ordinal" }).select(r.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];
|
|
852
1246
|
} else {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
let u = "";
|
|
858
|
-
for (const w of o.segments)
|
|
859
|
-
if (w.type === 0)
|
|
860
|
-
u += w.text;
|
|
861
|
-
else if (w.type === 1) {
|
|
862
|
-
let $ = St(r, w.name);
|
|
863
|
-
const R = ((l = r.formatOverrides) == null ? void 0 : l[w.name]) ?? [...w.formats];
|
|
864
|
-
w.name === "count" && R.length === 0 && R.push({ name: "number", options: {} });
|
|
865
|
-
for (const c of R) {
|
|
866
|
-
const T = a(this, te).get(c.name);
|
|
867
|
-
if (T == null) {
|
|
868
|
-
const _ = new Error(
|
|
869
|
-
`Failed to load format '${c.name}' when processing '${t}', template: ${o}`
|
|
870
|
-
);
|
|
871
|
-
throw a(this, F).crash(_), _;
|
|
872
|
-
}
|
|
873
|
-
$ = T(e, $, c.options);
|
|
874
|
-
}
|
|
875
|
-
u += $;
|
|
876
|
-
}
|
|
877
|
-
return u;
|
|
878
|
-
}, Ee = function(e, t) {
|
|
879
|
-
return new Intl.NumberFormat(a(this, v).value, t).format(e);
|
|
880
|
-
}, ke = function(e, t) {
|
|
881
|
-
return new Intl.DateTimeFormat(a(this, v).value, t).format(k(e) ? new Date(e) : e);
|
|
882
|
-
}, Re = function(e, t) {
|
|
883
|
-
return new Intl.ListFormat(a(this, v).value, t).format(e);
|
|
884
|
-
}, // relativeTime(): State<string> {
|
|
885
|
-
// }
|
|
886
|
-
Be = function(e, t) {
|
|
887
|
-
for (const r of a(this, ee))
|
|
888
|
-
if (r[0] === e && Ze(r[1], t))
|
|
889
|
-
return r[2];
|
|
890
|
-
};
|
|
891
|
-
function St(i, e) {
|
|
892
|
-
const t = String(e).split(/[\.\[\]]/).filter((n) => n.trim() !== "");
|
|
893
|
-
let r = i;
|
|
894
|
-
for (; t.length > 0; ) {
|
|
895
|
-
const n = t.shift();
|
|
896
|
-
r != null ? r = r[n] : r = void 0;
|
|
897
|
-
}
|
|
898
|
-
return r;
|
|
1247
|
+
value = void 0;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return value;
|
|
899
1251
|
}
|
|
900
|
-
|
|
901
|
-
|
|
1252
|
+
|
|
1253
|
+
function DefaultCrashView(props) {
|
|
1254
|
+
return html`
|
|
902
1255
|
<div
|
|
903
1256
|
style=${{
|
|
904
1257
|
backgroundColor: "#880000",
|
|
@@ -911,8 +1264,8 @@ function Mt(i) {
|
|
|
911
1264
|
>
|
|
912
1265
|
<h1 style=${{ marginBottom: "0.5rem" }}>The app has crashed</h1>
|
|
913
1266
|
<p style=${{ marginBottom: "0.25rem" }}>
|
|
914
|
-
<span style=${{ fontFamily: "monospace" }}>${
|
|
915
|
-
${
|
|
1267
|
+
<span style=${{ fontFamily: "monospace" }}>${props.loggerName}</span>
|
|
1268
|
+
${cond(props.uid, html`<span style=${{ fontFamily: "monospace", opacity: 0.5 }}> [uid: ${props.uid}]</span>`)}
|
|
916
1269
|
${" "}says:
|
|
917
1270
|
</p>
|
|
918
1271
|
<blockquote
|
|
@@ -935,290 +1288,276 @@ function Mt(i) {
|
|
|
935
1288
|
fontWeight: "bold"
|
|
936
1289
|
}}
|
|
937
1290
|
>
|
|
938
|
-
${
|
|
1291
|
+
${props.error.name}
|
|
939
1292
|
</span>
|
|
940
|
-
${
|
|
1293
|
+
${props.error.message}
|
|
941
1294
|
</blockquote>
|
|
942
1295
|
|
|
943
1296
|
<p>Please see the browser console for details.</p>
|
|
944
1297
|
</div>
|
|
945
1298
|
`;
|
|
946
1299
|
}
|
|
947
|
-
|
|
948
|
-
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 = [];
|
|
949
1330
|
constructor() {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
p(this, "i18n");
|
|
953
|
-
h(this, L, !1);
|
|
954
|
-
h(this, se, "production");
|
|
955
|
-
h(this, O);
|
|
956
|
-
h(this, q);
|
|
957
|
-
h(this, ne, Mt);
|
|
958
|
-
h(this, K);
|
|
959
|
-
h(this, ae, []);
|
|
960
|
-
h(this, ie, []);
|
|
961
|
-
h(this, oe, []);
|
|
962
|
-
h(this, le, []);
|
|
963
|
-
h(this, U, {
|
|
964
|
-
root: this,
|
|
965
|
-
data: {},
|
|
966
|
-
emitter: new tt(),
|
|
967
|
-
stores: /* @__PURE__ */ new Map(),
|
|
968
|
-
viewName: "Dolla"
|
|
969
|
-
});
|
|
970
|
-
h(this, g, {
|
|
971
|
-
info: "development",
|
|
972
|
-
log: "development",
|
|
973
|
-
warn: "development",
|
|
974
|
-
error: !0
|
|
975
|
-
});
|
|
976
|
-
h(this, j, Ne("*,-Dolla.*"));
|
|
977
|
-
// Registration functions for modules.
|
|
978
|
-
// All modules will be registered before mount.
|
|
979
|
-
h(this, we, []);
|
|
980
|
-
this.http = new bt(), this.i18n = new _t(this);
|
|
1331
|
+
this.http = new HTTP();
|
|
1332
|
+
this.i18n = new I18n(this);
|
|
981
1333
|
}
|
|
982
1334
|
/**
|
|
983
1335
|
* True when the app is connected to a DOM node and displayed to the user.
|
|
984
1336
|
*/
|
|
985
1337
|
get isMounted() {
|
|
986
|
-
return
|
|
1338
|
+
return this.#isMounted;
|
|
987
1339
|
}
|
|
988
1340
|
/**
|
|
989
1341
|
* Get the current environment that this app is running in.
|
|
990
1342
|
* Environment affects which log messages will print and how much debugging info is included in the DOM.
|
|
991
1343
|
*/
|
|
992
1344
|
getEnv() {
|
|
993
|
-
return
|
|
1345
|
+
return this.#env;
|
|
994
1346
|
}
|
|
995
1347
|
/**
|
|
996
1348
|
* Sets the environment that this app is running in.
|
|
997
1349
|
* Environment affects which log messages will print and how much debugging info is included in the DOM.
|
|
998
1350
|
*/
|
|
999
|
-
setEnv(
|
|
1000
|
-
|
|
1351
|
+
setEnv(value) {
|
|
1352
|
+
this.#env = value;
|
|
1001
1353
|
}
|
|
1002
1354
|
/**
|
|
1003
1355
|
* Sets the view that will be shown when the `crash` method is called on any logger.
|
|
1004
1356
|
* When a crash is reported the app will be unmounted and replaced with this crash page.
|
|
1005
1357
|
*/
|
|
1006
|
-
setCrashView(
|
|
1007
|
-
|
|
1358
|
+
setCrashView(view) {
|
|
1359
|
+
this.#crashView = view;
|
|
1008
1360
|
}
|
|
1009
1361
|
/**
|
|
1010
1362
|
* Returns the HTMLElement Dolla is mounted to. This will return undefined until Dolla.mount() is called.
|
|
1011
1363
|
*/
|
|
1012
1364
|
getRootElement() {
|
|
1013
|
-
return
|
|
1365
|
+
return this.#rootElement;
|
|
1014
1366
|
}
|
|
1015
1367
|
/**
|
|
1016
1368
|
* Returns the top level view Dolla is rendering inside the root element. This will return undefined until Dolla.mount() is called.
|
|
1017
1369
|
*/
|
|
1018
1370
|
getRootView() {
|
|
1019
|
-
return
|
|
1020
|
-
}
|
|
1021
|
-
provide(
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
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;
|
|
1028
1382
|
}
|
|
1029
1383
|
}
|
|
1030
1384
|
/**
|
|
1031
1385
|
* Gets the nearest instance of a store. Throws an error if the store isn't provided higher in the tree.
|
|
1032
1386
|
*/
|
|
1033
|
-
get(
|
|
1034
|
-
if (
|
|
1035
|
-
const
|
|
1036
|
-
if (
|
|
1037
|
-
throw new
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
+
}
|
|
1057
1430
|
}
|
|
1058
1431
|
async unmount() {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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();
|
|
1064
1441
|
}
|
|
1065
1442
|
}
|
|
1066
1443
|
/**
|
|
1067
1444
|
* Registers a `callback` to run after `Dolla.mount` is called, before the app is mounted. If `callback` returns a Promise,
|
|
1068
1445
|
* it will be awaited before mounting finishes. Use this to perform initial setup before the app is displayed to the user.
|
|
1069
1446
|
*/
|
|
1070
|
-
beforeMount(
|
|
1071
|
-
|
|
1447
|
+
beforeMount(callback) {
|
|
1448
|
+
this.#beforeMountCallbacks.push(callback);
|
|
1072
1449
|
}
|
|
1073
1450
|
/**
|
|
1074
1451
|
* Registers a `callback` to run after the app is mounted.
|
|
1075
1452
|
*/
|
|
1076
|
-
onMount(
|
|
1077
|
-
|
|
1453
|
+
onMount(callback) {
|
|
1454
|
+
this.#onMountCallbacks.push(callback);
|
|
1078
1455
|
}
|
|
1079
1456
|
/**
|
|
1080
1457
|
* Registers a `callback` to run after `Dolla.unmount` is called, before the app is unmounted. If `callback` returns a Promise,
|
|
1081
1458
|
* it will be awaited before unmounting finishes. Use this to perform cleanup.
|
|
1082
1459
|
*/
|
|
1083
|
-
beforeUnmount(
|
|
1084
|
-
|
|
1460
|
+
beforeUnmount(callback) {
|
|
1461
|
+
this.#beforeUnmountCallbacks.push(callback);
|
|
1085
1462
|
}
|
|
1086
1463
|
/**
|
|
1087
1464
|
* Registers a `callback` to run after the app is unmounted.
|
|
1088
1465
|
*/
|
|
1089
|
-
onUnmount(
|
|
1090
|
-
|
|
1466
|
+
onUnmount(callback) {
|
|
1467
|
+
this.#onUnmountCallbacks.push(callback);
|
|
1091
1468
|
}
|
|
1092
1469
|
/**
|
|
1093
1470
|
* Update log type toggles. Values that are not passed will remain unchanged.
|
|
1094
1471
|
*/
|
|
1095
|
-
setLoggles(
|
|
1096
|
-
for (const
|
|
1097
|
-
const
|
|
1098
|
-
|
|
1472
|
+
setLoggles(options) {
|
|
1473
|
+
for (const key in options) {
|
|
1474
|
+
const value = options[key];
|
|
1475
|
+
if (value) {
|
|
1476
|
+
this.#loggles[key] = value;
|
|
1477
|
+
}
|
|
1099
1478
|
}
|
|
1100
1479
|
}
|
|
1101
|
-
setLogFilter(
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
createLogger(
|
|
1105
|
-
const
|
|
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`
|
|
1504
|
+
);
|
|
1505
|
+
}
|
|
1506
|
+
};
|
|
1106
1507
|
return {
|
|
1107
|
-
setName(
|
|
1108
|
-
|
|
1508
|
+
setName(newName) {
|
|
1509
|
+
name = newName;
|
|
1510
|
+
return this;
|
|
1109
1511
|
},
|
|
1110
1512
|
get info() {
|
|
1111
|
-
|
|
1112
|
-
if (a(n, g).info === !1 || k(a(n, g).info) && a(n, g).info !== n.getEnv() || !a(s = n, j).call(s, e))
|
|
1113
|
-
return ce;
|
|
1114
|
-
{
|
|
1115
|
-
let o = `%c${e}`;
|
|
1116
|
-
return t != null && t.uid ? o += ` %c[uid: %c${t.uid}%c]` : o += "%c%c%c", r.info.bind(
|
|
1117
|
-
r,
|
|
1118
|
-
o,
|
|
1119
|
-
`color:${he(o)};font-weight:bold`,
|
|
1120
|
-
"color:#777",
|
|
1121
|
-
"color:#aaa",
|
|
1122
|
-
"color:#777"
|
|
1123
|
-
);
|
|
1124
|
-
}
|
|
1513
|
+
return bind("info");
|
|
1125
1514
|
},
|
|
1126
1515
|
get log() {
|
|
1127
|
-
|
|
1128
|
-
if (a(n, g).log === !1 || k(a(n, g).log) && a(n, g).log !== n.getEnv() || !a(s = n, j).call(s, e))
|
|
1129
|
-
return ce;
|
|
1130
|
-
{
|
|
1131
|
-
let o = `%c${e}`;
|
|
1132
|
-
return t != null && t.uid ? o += ` %c[uid: %c${t.uid}%c]` : o += "%c%c%c", r.log.bind(
|
|
1133
|
-
r,
|
|
1134
|
-
o,
|
|
1135
|
-
`color:${he(o)};font-weight:bold`,
|
|
1136
|
-
"color:#777",
|
|
1137
|
-
"color:#aaa",
|
|
1138
|
-
"color:#777"
|
|
1139
|
-
);
|
|
1140
|
-
}
|
|
1516
|
+
return bind("log");
|
|
1141
1517
|
},
|
|
1142
1518
|
get warn() {
|
|
1143
|
-
|
|
1144
|
-
if (a(n, g).warn === !1 || k(a(n, g).warn) && a(n, g).warn !== n.getEnv() || !a(s = n, j).call(s, e))
|
|
1145
|
-
return ce;
|
|
1146
|
-
{
|
|
1147
|
-
let o = `%c${e}`;
|
|
1148
|
-
return t != null && t.uid ? o += ` %c[uid: %c${t.uid}%c]` : o += "%c%c%c", r.warn.bind(
|
|
1149
|
-
r,
|
|
1150
|
-
o,
|
|
1151
|
-
`color:${he(o)};font-weight:bold`,
|
|
1152
|
-
"color:#777",
|
|
1153
|
-
"color:#aaa",
|
|
1154
|
-
"color:#777"
|
|
1155
|
-
);
|
|
1156
|
-
}
|
|
1519
|
+
return bind("warn");
|
|
1157
1520
|
},
|
|
1158
1521
|
get error() {
|
|
1159
|
-
|
|
1160
|
-
if (a(n, g).error === !1 || k(a(n, g).error) && a(n, g).error !== n.getEnv() || !a(s = n, j).call(s, e))
|
|
1161
|
-
return ce;
|
|
1162
|
-
{
|
|
1163
|
-
let o = `%c${e}`;
|
|
1164
|
-
return t != null && t.uid ? o += ` %c[uid: %c${t.uid}%c]` : o += "%c%c%c", r.error.bind(
|
|
1165
|
-
r,
|
|
1166
|
-
o,
|
|
1167
|
-
`color:${he(o)};font-weight:bold`,
|
|
1168
|
-
"color:#777",
|
|
1169
|
-
"color:#aaa",
|
|
1170
|
-
"color:#777"
|
|
1171
|
-
);
|
|
1172
|
-
}
|
|
1522
|
+
return bind("error");
|
|
1173
1523
|
},
|
|
1174
|
-
crash(
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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
|
+
}
|
|
1180
1534
|
}
|
|
1181
1535
|
};
|
|
1182
1536
|
}
|
|
1183
1537
|
/**
|
|
1184
1538
|
*
|
|
1185
1539
|
*/
|
|
1186
|
-
constructView(
|
|
1187
|
-
return new
|
|
1540
|
+
constructView(view, props, children = []) {
|
|
1541
|
+
return new View(this.#rootElementContext, view, props, children);
|
|
1188
1542
|
}
|
|
1189
1543
|
/**
|
|
1190
1544
|
*
|
|
1191
1545
|
*/
|
|
1192
|
-
constructMarkup(
|
|
1193
|
-
return
|
|
1546
|
+
constructMarkup(markup) {
|
|
1547
|
+
return groupElements(constructMarkup(this.#rootElementContext, markup));
|
|
1194
1548
|
}
|
|
1195
1549
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
if (typeof window < "u" && window.console)
|
|
1550
|
+
function getDefaultConsole() {
|
|
1551
|
+
if (typeof window !== "undefined" && window.console) {
|
|
1199
1552
|
return window.console;
|
|
1200
|
-
|
|
1553
|
+
}
|
|
1554
|
+
if (typeof global !== "undefined" && global.console) {
|
|
1201
1555
|
return global.console;
|
|
1556
|
+
}
|
|
1202
1557
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
st as createMarkup,
|
|
1209
|
-
Lt as createRouter,
|
|
1210
|
-
Ze as deepEqual,
|
|
1211
|
-
Le as default,
|
|
1212
|
-
Ct as effect,
|
|
1213
|
-
E as get,
|
|
1214
|
-
Te as html,
|
|
1215
|
-
Dt as list,
|
|
1216
|
-
It as peek,
|
|
1217
|
-
Vt as portal,
|
|
1218
|
-
Ft as ref,
|
|
1219
|
-
qt as set,
|
|
1220
|
-
Me as shallowEqual,
|
|
1221
|
-
Gt as strictEqual,
|
|
1222
|
-
Ot as t
|
|
1223
|
-
};
|
|
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 };
|
|
1224
1563
|
//# sourceMappingURL=index.js.map
|