@manyducks.co/dolla 0.69.3 → 0.69.5

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/lib/index.js CHANGED
@@ -29,7 +29,7 @@ var require_lib = __commonJS({
29
29
  "node_modules/simple-color-hash/lib/index.js"(exports, module) {
30
30
  "use strict";
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- var _slicedToArray = function() {
32
+ var _slicedToArray = /* @__PURE__ */ function() {
33
33
  function a(a2, b) {
34
34
  var c = [], d = true, e = false, f = void 0;
35
35
  try {
@@ -88,7 +88,55 @@ var require_lib = __commonJS({
88
88
  }
89
89
  });
90
90
 
91
- // node_modules/@borf/bedrock/lib/typeChecking.js
91
+ // src/classes/CrashCollector.ts
92
+ var CrashCollector = class {
93
+ #errors = [];
94
+ #errorCallbacks = [];
95
+ /**
96
+ * Registers a callback to receive all errors that pass through the CrashCollector.
97
+ * Returns a function that cancels this listener when called.
98
+ */
99
+ onError(callback) {
100
+ this.#errorCallbacks.push(callback);
101
+ return () => {
102
+ this.#errorCallbacks.splice(this.#errorCallbacks.indexOf(callback), 1);
103
+ };
104
+ }
105
+ /**
106
+ * Reports an unrecoverable error that requires crashing the whole app.
107
+ */
108
+ crash({ error, componentName }) {
109
+ const ctx = {
110
+ error,
111
+ severity: "crash",
112
+ componentName: componentName ?? "anonymous component"
113
+ };
114
+ this.#errors.push(ctx);
115
+ for (const callback of this.#errorCallbacks) {
116
+ callback(ctx);
117
+ }
118
+ throw error;
119
+ }
120
+ /**
121
+ * Reports a recoverable error.
122
+ */
123
+ error({ error, componentName }) {
124
+ const ctx = {
125
+ error,
126
+ severity: "error",
127
+ componentName: componentName ?? "anonymous component"
128
+ };
129
+ this.#errors.push(ctx);
130
+ for (const callback of this.#errorCallbacks) {
131
+ callback(ctx);
132
+ }
133
+ }
134
+ };
135
+
136
+ // src/classes/DebugHub.ts
137
+ var import_simple_color_hash = __toESM(require_lib(), 1);
138
+
139
+ // src/typeChecking.ts
92
140
  function typeOf(value) {
93
141
  if (value === void 0) {
94
142
  return "undefined";
@@ -209,274 +257,7 @@ function formatError(value, message) {
209
257
  return message.replaceAll("%t", typeName).replaceAll("%v", valueString);
210
258
  }
211
259
 
212
- // node_modules/@borf/bedrock/lib/routing.js
213
- var FragTypes;
214
- (function(FragTypes2) {
215
- FragTypes2[FragTypes2["Literal"] = 1] = "Literal";
216
- FragTypes2[FragTypes2["Param"] = 2] = "Param";
217
- FragTypes2[FragTypes2["Wildcard"] = 3] = "Wildcard";
218
- FragTypes2[FragTypes2["NumericParam"] = 4] = "NumericParam";
219
- })(FragTypes || (FragTypes = {}));
220
- function splitPath(path) {
221
- assertString(path, "Expected `path` to be a string. Got type: %t, value: %v");
222
- return path.split("/").map((f) => f.trim()).filter((f) => f !== "");
223
- }
224
- function joinPath(parts) {
225
- assertArrayOf((part) => isFunction(part?.toString), parts, "Expected `parts` to be an array of objects with a .toString() method. Got type: %t, value: %v");
226
- parts = parts.filter((x) => x).flatMap(String);
227
- let joined = parts.shift()?.toString();
228
- if (joined) {
229
- for (const part of parts.map((p) => p.toString())) {
230
- if (part.startsWith(".")) {
231
- joined = resolvePath(joined, part);
232
- } else if (joined[joined.length - 1] !== "/") {
233
- if (part[0] !== "/") {
234
- joined += "/" + part;
235
- } else {
236
- joined += part;
237
- }
238
- } else {
239
- if (part[0] === "/") {
240
- joined += part.slice(1);
241
- } else {
242
- joined += part;
243
- }
244
- }
245
- }
246
- if (joined && joined !== "/" && joined.endsWith("/")) {
247
- joined = joined.slice(0, joined.length - 1);
248
- }
249
- }
250
- return joined ?? "";
251
- }
252
- function resolvePath(base, part) {
253
- assertString(base, "Expected `base` to be a string. Got type: %t, value: %v");
254
- if (part == null) {
255
- part = base;
256
- base = "";
257
- }
258
- if (part.startsWith("/")) {
259
- return part;
260
- }
261
- let resolved = base;
262
- while (true) {
263
- if (part.startsWith("..")) {
264
- for (let i = resolved.length; i > 0; --i) {
265
- if (resolved[i] === "/" || i === 0) {
266
- resolved = resolved.slice(0, i);
267
- part = part.replace(/^\.\.\/?/, "");
268
- break;
269
- }
270
- }
271
- } else if (part.startsWith(".")) {
272
- part = part.replace(/^\.\/?/, "");
273
- } else {
274
- break;
275
- }
276
- }
277
- return joinPath([resolved, part]);
278
- }
279
- function parseQueryParams(query) {
280
- if (!query)
281
- return {};
282
- const entries = query.split("&").filter((x) => x.trim() !== "").map((entry) => {
283
- const [key, value] = entry.split("=").map((x) => x.trim());
284
- if (value.toLowerCase() === "true") {
285
- return [key, true];
286
- }
287
- if (value.toLowerCase() === "false") {
288
- return [key, false];
289
- }
290
- if (!isNaN(Number(value))) {
291
- return [key, Number(value)];
292
- }
293
- return [key, value];
294
- });
295
- return Object.fromEntries(entries);
296
- }
297
- function matchRoutes(routes, url, options = {}) {
298
- const [path, query] = url.split("?");
299
- const parts = splitPath(path);
300
- routes:
301
- for (const route of routes) {
302
- const { fragments } = route;
303
- const hasWildcard = fragments[fragments.length - 1]?.type === FragTypes.Wildcard;
304
- if (!hasWildcard && fragments.length !== parts.length) {
305
- continue routes;
306
- }
307
- if (options.willMatch && !options.willMatch(route)) {
308
- continue routes;
309
- }
310
- const matched = [];
311
- fragments:
312
- for (let i = 0; i < fragments.length; i++) {
313
- const part = parts[i];
314
- const frag = fragments[i];
315
- if (part == null && frag.type !== FragTypes.Wildcard) {
316
- continue routes;
317
- }
318
- switch (frag.type) {
319
- case FragTypes.Literal:
320
- if (frag.name.toLowerCase() === part.toLowerCase()) {
321
- matched.push(frag);
322
- break;
323
- } else {
324
- continue routes;
325
- }
326
- case FragTypes.Param:
327
- matched.push({ ...frag, value: part });
328
- break;
329
- case FragTypes.Wildcard:
330
- matched.push({ ...frag, value: parts.slice(i).join("/") });
331
- break fragments;
332
- case FragTypes.NumericParam:
333
- if (!isNaN(Number(part))) {
334
- matched.push({ ...frag, value: Number(part) });
335
- break;
336
- } else {
337
- continue routes;
338
- }
339
- default:
340
- throw new Error(`Unknown fragment type: ${frag.type}`);
341
- }
342
- }
343
- const params = /* @__PURE__ */ Object.create(null);
344
- for (const frag of matched) {
345
- if (frag.type === FragTypes.Param) {
346
- params[frag.name] = decodeURIComponent(frag.value);
347
- }
348
- if (frag.type === FragTypes.NumericParam) {
349
- params[frag.name] = frag.value;
350
- }
351
- if (frag.type === FragTypes.Wildcard) {
352
- params.wildcard = "/" + decodeURIComponent(frag.value);
353
- }
354
- }
355
- return {
356
- path: "/" + matched.map((f) => f.value).join("/"),
357
- pattern: "/" + fragments.map((f) => {
358
- if (f.type === FragTypes.Param) {
359
- return `{${f.name}}`;
360
- }
361
- if (f.type === FragTypes.NumericParam) {
362
- return `{#${f.name}}`;
363
- }
364
- return f.name;
365
- }).join("/"),
366
- params,
367
- query: parseQueryParams(query),
368
- meta: route.meta
369
- };
370
- }
371
- }
372
- function sortRoutes(routes) {
373
- const withoutParams = [];
374
- const withNumericParams = [];
375
- const withParams = [];
376
- const wildcard = [];
377
- for (const route of routes) {
378
- const { fragments } = route;
379
- if (fragments.some((f) => f.type === FragTypes.Wildcard)) {
380
- wildcard.push(route);
381
- } else if (fragments.some((f) => f.type === FragTypes.NumericParam)) {
382
- withNumericParams.push(route);
383
- } else if (fragments.some((f) => f.type === FragTypes.Param)) {
384
- withParams.push(route);
385
- } else {
386
- withoutParams.push(route);
387
- }
388
- }
389
- const bySizeDesc = (a, b) => {
390
- if (a.fragments.length > b.fragments.length) {
391
- return -1;
392
- } else {
393
- return 1;
394
- }
395
- };
396
- withoutParams.sort(bySizeDesc);
397
- withNumericParams.sort(bySizeDesc);
398
- withParams.sort(bySizeDesc);
399
- wildcard.sort(bySizeDesc);
400
- return [...withoutParams, ...withNumericParams, ...withParams, ...wildcard];
401
- }
402
- function patternToFragments(pattern) {
403
- const parts = splitPath(pattern);
404
- const fragments = [];
405
- for (let i = 0; i < parts.length; i++) {
406
- const part = parts[i];
407
- if (part === "*") {
408
- if (i !== parts.length - 1) {
409
- throw new Error(`Wildcard must be at the end of a pattern. Received: ${pattern}`);
410
- }
411
- fragments.push({
412
- type: FragTypes.Wildcard,
413
- name: "*",
414
- value: null
415
- });
416
- } else if (part.at(0) === "{" && part.at(-1) === "}") {
417
- fragments.push({
418
- type: part[1] === "#" ? FragTypes.NumericParam : FragTypes.Param,
419
- name: part[1] === "#" ? part.slice(2, -1) : part.slice(1, -1),
420
- value: null
421
- });
422
- } else {
423
- fragments.push({
424
- type: FragTypes.Literal,
425
- name: part,
426
- value: part
427
- });
428
- }
429
- }
430
- return fragments;
431
- }
432
-
433
- // src/classes/CrashCollector.ts
434
- var CrashCollector = class {
435
- #errors = [];
436
- #errorCallbacks = [];
437
- /**
438
- * Registers a callback to receive all errors that pass through the CrashCollector.
439
- * Returns a function that cancels this listener when called.
440
- */
441
- onError(callback) {
442
- this.#errorCallbacks.push(callback);
443
- return () => {
444
- this.#errorCallbacks.splice(this.#errorCallbacks.indexOf(callback), 1);
445
- };
446
- }
447
- /**
448
- * Reports an unrecoverable error that requires crashing the whole app.
449
- */
450
- crash({ error, componentName }) {
451
- const ctx = {
452
- error,
453
- severity: "crash",
454
- componentName: componentName ?? "anonymous component"
455
- };
456
- this.#errors.push(ctx);
457
- for (const callback of this.#errorCallbacks) {
458
- callback(ctx);
459
- }
460
- throw error;
461
- }
462
- /**
463
- * Reports a recoverable error.
464
- */
465
- error({ error, componentName }) {
466
- const ctx = {
467
- error,
468
- severity: "error",
469
- componentName: componentName ?? "anonymous component"
470
- };
471
- this.#errors.push(ctx);
472
- for (const callback of this.#errorCallbacks) {
473
- callback(ctx);
474
- }
475
- }
476
- };
477
-
478
260
  // src/classes/DebugHub.ts
479
- var import_simple_color_hash = __toESM(require_lib(), 1);
480
261
  var DebugHub = class {
481
262
  #filter = "*,-dolla/*";
482
263
  #matcher;
@@ -686,7 +467,9 @@ function $$(initialValue, config) {
686
467
  }
687
468
  function $(...args) {
688
469
  if (args.length > 1) {
689
- return computed(...args);
470
+ const callback = args.pop();
471
+ const readables = args.flat().map(readable);
472
+ return computed(...readables, callback);
690
473
  } else {
691
474
  return readable(args[0]);
692
475
  }
@@ -715,7 +498,6 @@ function computed(...args) {
715
498
  if (typeof compute !== "function") {
716
499
  throw new TypeError(`Final argument must be a function. Got ${typeOf(compute)}: ${compute}`);
717
500
  }
718
- args = args.flat().map(readable);
719
501
  if (args.length < 1) {
720
502
  throw new Error(`Must pass at least one value before the callback function.`);
721
503
  }
@@ -881,7 +663,7 @@ function proxy(source, config) {
881
663
  }
882
664
  function observe(...args) {
883
665
  const callback = args.pop();
884
- const readables = args.flat();
666
+ const readables = args.flat().map(readable);
885
667
  if (readables.length === 0) {
886
668
  throw new TypeError(`Expected at least one readable.`);
887
669
  }
@@ -2413,10 +2195,65 @@ function RenderStore(ctx) {
2413
2195
  };
2414
2196
  }
2415
2197
 
2416
- // src/app.ts
2417
- function DefaultRootView(_, ctx) {
2198
+ // src/views/default-crash-page.ts
2199
+ function DefaultCrashPage({ message, error, componentName }) {
2200
+ return m(
2201
+ "div",
2202
+ {
2203
+ style: {
2204
+ backgroundColor: "#880000",
2205
+ color: "#fff",
2206
+ padding: "2rem",
2207
+ position: "fixed",
2208
+ inset: 0,
2209
+ fontSize: "20px"
2210
+ }
2211
+ },
2212
+ m("h1", { style: { marginBottom: "0.5rem" } }, "The app has crashed"),
2213
+ m(
2214
+ "p",
2215
+ { style: { marginBottom: "0.25rem" } },
2216
+ m("span", { style: { fontFamily: "monospace" } }, componentName),
2217
+ " says:"
2218
+ ),
2219
+ m(
2220
+ "blockquote",
2221
+ {
2222
+ style: {
2223
+ backgroundColor: "#991111",
2224
+ padding: "0.25em",
2225
+ borderRadius: "6px",
2226
+ fontFamily: "monospace",
2227
+ marginBottom: "1rem"
2228
+ }
2229
+ },
2230
+ m(
2231
+ "span",
2232
+ {
2233
+ style: {
2234
+ display: "inline-block",
2235
+ backgroundColor: "red",
2236
+ padding: "0.1em 0.4em",
2237
+ marginRight: "0.5em",
2238
+ borderRadius: "4px",
2239
+ fontSize: "0.9em",
2240
+ fontWeight: "bold"
2241
+ }
2242
+ },
2243
+ error.name
2244
+ ),
2245
+ message
2246
+ ),
2247
+ m("p", {}, "Please see the browser console for details.")
2248
+ );
2249
+ }
2250
+
2251
+ // src/views/default-view.ts
2252
+ function DefaultView(_, ctx) {
2418
2253
  return ctx.outlet();
2419
2254
  }
2255
+
2256
+ // src/app.ts
2420
2257
  function isAppOptions(value) {
2421
2258
  return isObject(value);
2422
2259
  }
@@ -2425,7 +2262,7 @@ function App(options) {
2425
2262
  throw new TypeError(`App options must be an object. Got: ${options}`);
2426
2263
  }
2427
2264
  let isConnected = false;
2428
- let mainView = m(options?.view ?? DefaultRootView);
2265
+ let mainView = m(options?.view ?? DefaultView);
2429
2266
  let configureCallback;
2430
2267
  const settings = merge(
2431
2268
  {
@@ -2510,146 +2347,44 @@ function App(options) {
2510
2347
  // TODO: Add context methods
2511
2348
  });
2512
2349
  }
2513
- const done = () => {
2514
- appContext.rootView.connect(appContext.rootElement);
2515
- isConnected = true;
2516
- resolve2();
2517
- };
2518
- done();
2350
+ appContext.rootView.connect(appContext.rootElement);
2351
+ isConnected = true;
2352
+ resolve2();
2519
2353
  });
2520
2354
  }
2521
- async function disconnect() {
2522
- if (isConnected) {
2523
- appContext.rootView.disconnect();
2524
- isConnected = false;
2525
- for (const { instance } of stores.values()) {
2526
- instance.disconnect();
2527
- }
2528
- }
2529
- }
2530
- const appContext = {
2531
- crashCollector,
2532
- debugHub,
2533
- stores,
2534
- mode: settings.mode ?? "production"
2535
- };
2536
- const elementContext = {
2537
- stores: /* @__PURE__ */ new Map()
2538
- };
2539
- const app = {
2540
- connect,
2541
- disconnect,
2542
- get isConnected() {
2543
- return isConnected;
2544
- },
2545
- // language(tag: string, config: LanguageConfig) {
2546
- // languages.set(tag, config);
2547
- //
2548
- // return app;
2549
- // },
2550
- //
2551
- // setLanguage(tag: string, fallback?: string) {
2552
- // if (tag === "auto") {
2553
- // let tags = [];
2554
- //
2555
- // if (typeof navigator === "object") {
2556
- // const nav = navigator as any;
2557
- //
2558
- // if (nav.languages?.length > 0) {
2559
- // tags.push(...nav.languages);
2560
- // } else if (nav.language) {
2561
- // tags.push(nav.language);
2562
- // } else if (nav.browserLanguage) {
2563
- // tags.push(nav.browserLanguage);
2564
- // } else if (nav.userLanguage) {
2565
- // tags.push(nav.userLanguage);
2566
- // }
2567
- // }
2568
- //
2569
- // for (const tag of tags) {
2570
- // if (languages.has(tag)) {
2571
- // // Found a matching language.
2572
- // currentLanguage = tag;
2573
- // return this;
2574
- // }
2575
- // }
2576
- //
2577
- // if (!currentLanguage && fallback) {
2578
- // if (languages.has(fallback)) {
2579
- // currentLanguage = fallback;
2580
- // }
2581
- // }
2582
- // } else {
2583
- // // Tag is the actual tag to set.
2584
- // if (languages.has(tag)) {
2585
- // currentLanguage = tag;
2586
- // } else {
2587
- // throw new Error(`Language '${tag}' has not been added to this app yet.`);
2588
- // }
2589
- // }
2590
- //
2591
- // return app;
2592
- // },
2593
- configure(callback) {
2594
- if (configureCallback !== void 0) {
2595
- debugChannel.warn(`Configure callback is already defined. Only the final configure call will take effect.`);
2596
- }
2597
- configureCallback = callback;
2598
- return app;
2599
- }
2600
- };
2601
- return app;
2602
- }
2603
- function DefaultCrashPage({ message, error, componentName }) {
2604
- return m(
2605
- "div",
2606
- {
2607
- style: {
2608
- backgroundColor: "#880000",
2609
- color: "#fff",
2610
- padding: "2rem",
2611
- position: "fixed",
2612
- inset: 0,
2613
- fontSize: "20px"
2614
- }
2615
- },
2616
- m("h1", { style: { marginBottom: "0.5rem" } }, "The app has crashed"),
2617
- m(
2618
- "p",
2619
- { style: { marginBottom: "0.25rem" } },
2620
- m("span", { style: { fontFamily: "monospace" } }, componentName),
2621
- " says:"
2622
- ),
2623
- m(
2624
- "blockquote",
2625
- {
2626
- style: {
2627
- backgroundColor: "#991111",
2628
- padding: "0.25em",
2629
- borderRadius: "6px",
2630
- fontFamily: "monospace",
2631
- marginBottom: "1rem"
2632
- }
2633
- },
2634
- m(
2635
- "span",
2636
- {
2637
- style: {
2638
- display: "inline-block",
2639
- backgroundColor: "red",
2640
- padding: "0.1em 0.4em",
2641
- marginRight: "0.5em",
2642
- borderRadius: "4px",
2643
- fontSize: "0.9em",
2644
- fontWeight: "bold"
2645
- }
2646
- },
2647
- error.name
2648
- ),
2649
- message
2650
- ),
2651
- m("p", {}, "Please see the browser console for details.")
2652
- );
2355
+ async function disconnect() {
2356
+ if (isConnected) {
2357
+ appContext.rootView.disconnect();
2358
+ isConnected = false;
2359
+ for (const { instance } of stores.values()) {
2360
+ instance.disconnect();
2361
+ }
2362
+ }
2363
+ }
2364
+ const appContext = {
2365
+ crashCollector,
2366
+ debugHub,
2367
+ stores,
2368
+ mode: settings.mode ?? "production"
2369
+ };
2370
+ const elementContext = {
2371
+ stores: /* @__PURE__ */ new Map()
2372
+ };
2373
+ const app = {
2374
+ connect,
2375
+ disconnect,
2376
+ get isConnected() {
2377
+ return isConnected;
2378
+ },
2379
+ configure(callback) {
2380
+ if (configureCallback !== void 0) {
2381
+ debugChannel.warn(`Configure callback is already defined. Only the final configure call will take effect.`);
2382
+ }
2383
+ configureCallback = callback;
2384
+ return app;
2385
+ }
2386
+ };
2387
+ return app;
2653
2388
  }
2654
2389
 
2655
2390
  // src/views/fragment.ts
@@ -3146,8 +2881,225 @@ function parsePath(path) {
3146
2881
  return parsedPath;
3147
2882
  }
3148
2883
 
2884
+ // src/routing.ts
2885
+ function splitPath(path) {
2886
+ assertString(path, "Expected `path` to be a string. Got type: %t, value: %v");
2887
+ return path.split("/").map((f) => f.trim()).filter((f) => f !== "");
2888
+ }
2889
+ function joinPath(parts) {
2890
+ assertArrayOf(
2891
+ (part) => isFunction(part?.toString),
2892
+ parts,
2893
+ "Expected `parts` to be an array of objects with a .toString() method. Got type: %t, value: %v"
2894
+ );
2895
+ parts = parts.filter((x) => x).flatMap(String);
2896
+ let joined = parts.shift()?.toString();
2897
+ if (joined) {
2898
+ for (const part of parts.map((p) => p.toString())) {
2899
+ if (part.startsWith(".")) {
2900
+ joined = resolvePath(joined, part);
2901
+ } else if (joined[joined.length - 1] !== "/") {
2902
+ if (part[0] !== "/") {
2903
+ joined += "/" + part;
2904
+ } else {
2905
+ joined += part;
2906
+ }
2907
+ } else {
2908
+ if (part[0] === "/") {
2909
+ joined += part.slice(1);
2910
+ } else {
2911
+ joined += part;
2912
+ }
2913
+ }
2914
+ }
2915
+ if (joined && joined !== "/" && joined.endsWith("/")) {
2916
+ joined = joined.slice(0, joined.length - 1);
2917
+ }
2918
+ }
2919
+ return joined ?? "";
2920
+ }
2921
+ function resolvePath(base, part) {
2922
+ assertString(base, "Expected `base` to be a string. Got type: %t, value: %v");
2923
+ if (part == null) {
2924
+ part = base;
2925
+ base = "";
2926
+ }
2927
+ if (part.startsWith("/")) {
2928
+ return part;
2929
+ }
2930
+ let resolved = base;
2931
+ while (true) {
2932
+ if (part.startsWith("..")) {
2933
+ for (let i = resolved.length; i > 0; --i) {
2934
+ if (resolved[i] === "/" || i === 0) {
2935
+ resolved = resolved.slice(0, i);
2936
+ part = part.replace(/^\.\.\/?/, "");
2937
+ break;
2938
+ }
2939
+ }
2940
+ } else if (part.startsWith(".")) {
2941
+ part = part.replace(/^\.\/?/, "");
2942
+ } else {
2943
+ break;
2944
+ }
2945
+ }
2946
+ return joinPath([resolved, part]);
2947
+ }
2948
+ function parseQueryParams(query) {
2949
+ if (!query)
2950
+ return {};
2951
+ const entries = query.split("&").filter((x) => x.trim() !== "").map((entry) => {
2952
+ const [key, value] = entry.split("=").map((x) => x.trim());
2953
+ if (value.toLowerCase() === "true") {
2954
+ return [key, true];
2955
+ }
2956
+ if (value.toLowerCase() === "false") {
2957
+ return [key, false];
2958
+ }
2959
+ if (!isNaN(Number(value))) {
2960
+ return [key, Number(value)];
2961
+ }
2962
+ return [key, value];
2963
+ });
2964
+ return Object.fromEntries(entries);
2965
+ }
2966
+ function matchRoutes(routes, url, options = {}) {
2967
+ const [path, query] = url.split("?");
2968
+ const parts = splitPath(path);
2969
+ routes:
2970
+ for (const route of routes) {
2971
+ const { fragments } = route;
2972
+ const hasWildcard = fragments[fragments.length - 1]?.type === 3 /* Wildcard */;
2973
+ if (!hasWildcard && fragments.length !== parts.length) {
2974
+ continue routes;
2975
+ }
2976
+ if (options.willMatch && !options.willMatch(route)) {
2977
+ continue routes;
2978
+ }
2979
+ const matched = [];
2980
+ fragments:
2981
+ for (let i = 0; i < fragments.length; i++) {
2982
+ const part = parts[i];
2983
+ const frag = fragments[i];
2984
+ if (part == null && frag.type !== 3 /* Wildcard */) {
2985
+ continue routes;
2986
+ }
2987
+ switch (frag.type) {
2988
+ case 1 /* Literal */:
2989
+ if (frag.name.toLowerCase() === part.toLowerCase()) {
2990
+ matched.push(frag);
2991
+ break;
2992
+ } else {
2993
+ continue routes;
2994
+ }
2995
+ case 2 /* Param */:
2996
+ matched.push({ ...frag, value: part });
2997
+ break;
2998
+ case 3 /* Wildcard */:
2999
+ matched.push({ ...frag, value: parts.slice(i).join("/") });
3000
+ break fragments;
3001
+ case 4 /* NumericParam */:
3002
+ if (!isNaN(Number(part))) {
3003
+ matched.push({ ...frag, value: Number(part) });
3004
+ break;
3005
+ } else {
3006
+ continue routes;
3007
+ }
3008
+ default:
3009
+ throw new Error(`Unknown fragment type: ${frag.type}`);
3010
+ }
3011
+ }
3012
+ const params = /* @__PURE__ */ Object.create(null);
3013
+ for (const frag of matched) {
3014
+ if (frag.type === 2 /* Param */) {
3015
+ params[frag.name] = decodeURIComponent(frag.value);
3016
+ }
3017
+ if (frag.type === 4 /* NumericParam */) {
3018
+ params[frag.name] = frag.value;
3019
+ }
3020
+ if (frag.type === 3 /* Wildcard */) {
3021
+ params.wildcard = "/" + decodeURIComponent(frag.value);
3022
+ }
3023
+ }
3024
+ return {
3025
+ path: "/" + matched.map((f) => f.value).join("/"),
3026
+ pattern: "/" + fragments.map((f) => {
3027
+ if (f.type === 2 /* Param */) {
3028
+ return `{${f.name}}`;
3029
+ }
3030
+ if (f.type === 4 /* NumericParam */) {
3031
+ return `{#${f.name}}`;
3032
+ }
3033
+ return f.name;
3034
+ }).join("/"),
3035
+ params,
3036
+ query: parseQueryParams(query),
3037
+ meta: route.meta
3038
+ };
3039
+ }
3040
+ }
3041
+ function sortRoutes(routes) {
3042
+ const withoutParams = [];
3043
+ const withNumericParams = [];
3044
+ const withParams = [];
3045
+ const wildcard = [];
3046
+ for (const route of routes) {
3047
+ const { fragments } = route;
3048
+ if (fragments.some((f) => f.type === 3 /* Wildcard */)) {
3049
+ wildcard.push(route);
3050
+ } else if (fragments.some((f) => f.type === 4 /* NumericParam */)) {
3051
+ withNumericParams.push(route);
3052
+ } else if (fragments.some((f) => f.type === 2 /* Param */)) {
3053
+ withParams.push(route);
3054
+ } else {
3055
+ withoutParams.push(route);
3056
+ }
3057
+ }
3058
+ const bySizeDesc = (a, b) => {
3059
+ if (a.fragments.length > b.fragments.length) {
3060
+ return -1;
3061
+ } else {
3062
+ return 1;
3063
+ }
3064
+ };
3065
+ withoutParams.sort(bySizeDesc);
3066
+ withNumericParams.sort(bySizeDesc);
3067
+ withParams.sort(bySizeDesc);
3068
+ wildcard.sort(bySizeDesc);
3069
+ return [...withoutParams, ...withNumericParams, ...withParams, ...wildcard];
3070
+ }
3071
+ function patternToFragments(pattern) {
3072
+ const parts = splitPath(pattern);
3073
+ const fragments = [];
3074
+ for (let i = 0; i < parts.length; i++) {
3075
+ const part = parts[i];
3076
+ if (part === "*") {
3077
+ if (i !== parts.length - 1) {
3078
+ throw new Error(`Wildcard must be at the end of a pattern. Received: ${pattern}`);
3079
+ }
3080
+ fragments.push({
3081
+ type: 3 /* Wildcard */,
3082
+ name: "*",
3083
+ value: null
3084
+ });
3085
+ } else if (part.at(0) === "{" && part.at(-1) === "}") {
3086
+ fragments.push({
3087
+ type: part[1] === "#" ? 4 /* NumericParam */ : 2 /* Param */,
3088
+ name: part[1] === "#" ? part.slice(2, -1) : part.slice(1, -1),
3089
+ value: null
3090
+ });
3091
+ } else {
3092
+ fragments.push({
3093
+ type: 1 /* Literal */,
3094
+ name: part,
3095
+ value: part
3096
+ });
3097
+ }
3098
+ }
3099
+ return fragments;
3100
+ }
3101
+
3149
3102
  // src/stores/router.ts
3150
- var DefaultView = (_, ctx) => ctx.outlet();
3151
3103
  function RouterStore(ctx) {
3152
3104
  ctx.name = "dolla/router";
3153
3105
  const { appContext, elementContext } = getStoreSecrets(ctx);
@@ -3486,37 +3438,61 @@ function LanguageStore(ctx) {
3486
3438
  }
3487
3439
  return template;
3488
3440
  }
3489
- const currentLanguage = ctx.options.default ? languages.get(ctx.options.default) : languages.get([...languages.keys()][0]);
3490
- if (currentLanguage == null) {
3491
- $$isLoaded.set(true);
3492
- } else {
3493
- ctx.info(`Current language is '${currentLanguage.name}'.`);
3494
- getTranslation(currentLanguage).then((translation) => {
3495
- $$language.set(currentLanguage.name);
3441
+ async function setLanguage(tag) {
3442
+ let realTag;
3443
+ if (tag === "auto") {
3444
+ let tags = [];
3445
+ if (typeof navigator === "object") {
3446
+ const nav = navigator;
3447
+ if (nav.languages?.length > 0) {
3448
+ tags.push(...nav.languages);
3449
+ } else if (nav.language) {
3450
+ tags.push(nav.language);
3451
+ } else if (nav.browserLanguage) {
3452
+ tags.push(nav.browserLanguage);
3453
+ } else if (nav.userLanguage) {
3454
+ tags.push(nav.userLanguage);
3455
+ }
3456
+ }
3457
+ for (const tag2 of tags) {
3458
+ if (languages.has(tag2)) {
3459
+ realTag = tag2;
3460
+ }
3461
+ }
3462
+ } else {
3463
+ if (languages.has(tag)) {
3464
+ realTag = tag;
3465
+ }
3466
+ }
3467
+ if (realTag == null) {
3468
+ const firstLanguage = ctx.options.languages[0];
3469
+ if (firstLanguage) {
3470
+ realTag = firstLanguage.name;
3471
+ }
3472
+ }
3473
+ if (!realTag || !languages.has(tag)) {
3474
+ throw new Error(`Language '${tag}' is not configured for this app.`);
3475
+ }
3476
+ const lang = languages.get(tag);
3477
+ try {
3478
+ const translation = await getTranslation(lang);
3496
3479
  $$translation.set(translation);
3497
- $$isLoaded.set(true);
3498
- });
3480
+ $$language.set(tag);
3481
+ ctx.info("set language to " + tag);
3482
+ } catch (error) {
3483
+ if (error instanceof Error) {
3484
+ ctx.crash(error);
3485
+ }
3486
+ }
3499
3487
  }
3488
+ setLanguage(ctx.options.defaultLanguage ?? "auto").then(() => {
3489
+ $$isLoaded.set(true);
3490
+ });
3500
3491
  return {
3501
3492
  $isLoaded: $($$isLoaded),
3502
3493
  $currentLanguage: $($$language),
3503
3494
  supportedLanguages: [...languages.keys()],
3504
- async setLanguage(tag) {
3505
- if (!languages.has(tag)) {
3506
- throw new Error(`Language '${tag}' is not supported.`);
3507
- }
3508
- const lang = languages.get(tag);
3509
- try {
3510
- const translation = await getTranslation(lang);
3511
- $$translation.set(translation);
3512
- $$language.set(tag);
3513
- ctx.info("set language to " + tag);
3514
- } catch (error) {
3515
- if (error instanceof Error) {
3516
- ctx.crash(error);
3517
- }
3518
- }
3519
- },
3495
+ setLanguage,
3520
3496
  /**
3521
3497
  * Returns a Readable of the translated value.
3522
3498