@manyducks.co/dolla 0.68.1 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -677,6 +677,20 @@ function isReadable(value) {
677
677
  function isWritable(value) {
678
678
  return isReadable(value) && typeof value.set === "function" && typeof value.update === "function";
679
679
  }
680
+ function $$(initialValue, config) {
681
+ if (config) {
682
+ return proxy(initialValue, config);
683
+ } else {
684
+ return writable(initialValue);
685
+ }
686
+ }
687
+ function $(...args) {
688
+ if (args.length > 1) {
689
+ return computed(...args);
690
+ } else {
691
+ return readable(args[0]);
692
+ }
693
+ }
680
694
  function readable(value) {
681
695
  if (isWritable(value)) {
682
696
  return {
@@ -690,89 +704,24 @@ function readable(value) {
690
704
  return {
691
705
  get: () => value,
692
706
  [OBSERVE]: (callback) => {
693
- callback(value, void 0);
707
+ callback(value);
694
708
  return function stop() {
695
709
  };
696
710
  }
697
711
  };
698
712
  }
699
- function writable(value) {
700
- if (isWritable(value)) {
701
- return value;
702
- }
703
- if (isReadable(value)) {
704
- throw new TypeError(`Failed to convert Readable into a Writable; can't add write access to a read-only value.`);
705
- }
706
- const observers = [];
707
- let currentValue = value;
708
- return {
709
- // ----- Readable ----- //
710
- get: () => currentValue,
711
- [OBSERVE]: (callback) => {
712
- observers.push(callback);
713
- function stop() {
714
- observers.splice(observers.indexOf(callback), 1);
715
- }
716
- callback(currentValue, void 0);
717
- return stop;
718
- },
719
- // ----- Writable ----- //
720
- set: (newValue) => {
721
- if (!deepEqual(currentValue, newValue)) {
722
- const previousValue = currentValue;
723
- currentValue = newValue;
724
- for (const callback of observers) {
725
- callback(currentValue, previousValue);
726
- }
727
- }
728
- },
729
- update: (callback) => {
730
- const newValue = callback(currentValue);
731
- if (!deepEqual(currentValue, newValue)) {
732
- const previousValue = currentValue;
733
- currentValue = newValue;
734
- for (const callback2 of observers) {
735
- callback2(currentValue, previousValue);
736
- }
737
- }
738
- }
739
- };
740
- }
741
- function observe(readable2, callback) {
742
- const readables = [];
743
- if (Array.isArray(readable2) && readable2.every(isReadable)) {
744
- readables.push(...readable2);
745
- } else if (isReadable(readable2)) {
746
- readables.push(readable2);
747
- } else {
748
- console.warn(readable2);
749
- throw new TypeError(
750
- `Expected one Readable or an array of Readables as the first argument. Got value: ${readable2}, type: ${typeOf(
751
- readable2
752
- )}`
753
- );
754
- }
755
- if (readables.length === 0) {
756
- throw new TypeError(`Expected at least one readable.`);
713
+ function computed(...args) {
714
+ const compute = args.pop();
715
+ if (typeof compute !== "function") {
716
+ throw new TypeError(`Final argument must be a function. Got ${typeOf(compute)}: ${compute}`);
757
717
  }
758
- if (readables.length > 1) {
759
- return computed(readables, callback)[OBSERVE](() => {
760
- });
761
- } else {
762
- return readables[0][OBSERVE](callback);
718
+ args = args.flat();
719
+ if (args.length < 1) {
720
+ throw new Error(`Must pass at least one state before the callback function.`);
763
721
  }
764
- }
765
- function computed(...args) {
766
- if (isReadable(args[0])) {
767
- if (typeof args[1] !== "function") {
768
- throw new TypeError(
769
- `When first argument is a Readable the second argument must be a callback function. Got type: ${typeOf(
770
- args[1]
771
- )}, value: ${args[1]}`
772
- );
773
- }
774
- const readable2 = args[0];
775
- const compute = args[1];
722
+ const readables = args;
723
+ if (readables.length === 1) {
724
+ const readable2 = readables[0];
776
725
  return {
777
726
  get: () => {
778
727
  const computed2 = compute(readable2.get());
@@ -784,34 +733,28 @@ function computed(...args) {
784
733
  },
785
734
  [OBSERVE]: (callback) => {
786
735
  let lastComputedValue = UNOBSERVED;
787
- let lastObservedValue;
788
736
  return readable2[OBSERVE]((currentValue) => {
789
- const computedValue = compute(currentValue, lastObservedValue);
737
+ const computedValue = compute(currentValue);
790
738
  if (isReadable(computedValue)) {
791
739
  lastComputedValue = computedValue;
792
- return computedValue[OBSERVE]((current, previous) => {
793
- callback(current, previous);
794
- lastObservedValue = current;
740
+ return computedValue[OBSERVE]((current) => {
741
+ callback(current);
795
742
  });
796
743
  }
797
744
  if (!deepEqual(computedValue, lastComputedValue)) {
798
- const previousValue = lastComputedValue === UNOBSERVED ? void 0 : lastComputedValue;
799
- callback(computedValue, previousValue);
745
+ callback(computedValue);
800
746
  lastComputedValue = computedValue;
801
- lastObservedValue = currentValue;
802
747
  }
803
748
  });
804
749
  }
805
750
  };
806
- } else if (Array.isArray(args[0])) {
751
+ } else {
807
752
  let updateValue2 = function() {
808
- const computedValue = compute(observedValues, previousObservedValues);
753
+ const computedValue = compute(...observedValues);
809
754
  if (!deepEqual(computedValue, latestComputedValue)) {
810
- const previousValue = latestComputedValue === UNOBSERVED ? void 0 : latestComputedValue;
811
755
  latestComputedValue = computedValue;
812
- previousObservedValues = observedValues;
813
756
  for (const callback of observers) {
814
- callback(computedValue, previousValue);
757
+ callback(computedValue);
815
758
  }
816
759
  }
817
760
  }, startObserving2 = function() {
@@ -828,7 +771,6 @@ function computed(...args) {
828
771
  })
829
772
  );
830
773
  }
831
- previousObservedValues = new Array().fill(void 0, 0, readables.length);
832
774
  observedValues = readables.map((x) => x.get());
833
775
  isObserving = true;
834
776
  updateValue2();
@@ -840,24 +782,9 @@ function computed(...args) {
840
782
  stopCallbacks = [];
841
783
  };
842
784
  var updateValue = updateValue2, startObserving = startObserving2, stopObserving = stopObserving2;
843
- if (typeof args[1] !== "function") {
844
- throw new TypeError(
845
- `When first argument is an array of Readables the second argument must be a callback function. Got type: ${typeOf(
846
- args[1]
847
- )}, value: ${args[1]}`
848
- );
849
- }
850
- if (!args[0].every(isReadable)) {
851
- throw new TypeError(
852
- `Computed expected an array of Readables. Got: [${args[0].map((x) => isReadable(x) ? `Readable<${typeOf(x.get())}>` : typeof x).join(", ")}]`
853
- );
854
- }
855
- const readables = args[0];
856
- const compute = args[1];
857
785
  const observers = [];
858
786
  let stopCallbacks = [];
859
787
  let isObserving = false;
860
- let previousObservedValues = [];
861
788
  let observedValues = [];
862
789
  let latestComputedValue = UNOBSERVED;
863
790
  return {
@@ -865,17 +792,14 @@ function computed(...args) {
865
792
  if (isObserving) {
866
793
  return latestComputedValue;
867
794
  } else {
868
- return compute(
869
- readables.map((x) => x.get()),
870
- new Array().fill(void 0, 0, readables.length)
871
- );
795
+ return compute(...readables.map((x) => x.get()));
872
796
  }
873
797
  },
874
798
  [OBSERVE]: (callback) => {
875
799
  if (!isObserving) {
876
800
  startObserving2();
877
801
  }
878
- callback(latestComputedValue, void 0);
802
+ callback(latestComputedValue);
879
803
  observers.push(callback);
880
804
  return function stop() {
881
805
  observers.splice(observers.indexOf(callback), 1);
@@ -885,42 +809,88 @@ function computed(...args) {
885
809
  };
886
810
  }
887
811
  };
888
- } else {
889
- throw new TypeError(
890
- `Expected a Readable or array of Readables as a first argument. Got: ${typeOf(args[0])}, value: ${args[0]}`
891
- );
892
812
  }
893
813
  }
814
+ function writable(value) {
815
+ if (isWritable(value)) {
816
+ return value;
817
+ }
818
+ if (isReadable(value)) {
819
+ throw new TypeError(`Failed to convert Readable into a Writable; can't add write access to a read-only value.`);
820
+ }
821
+ const observers = [];
822
+ let currentValue = value;
823
+ return {
824
+ // ----- Readable ----- //
825
+ get: () => currentValue,
826
+ [OBSERVE]: (callback) => {
827
+ observers.push(callback);
828
+ function stop() {
829
+ observers.splice(observers.indexOf(callback), 1);
830
+ }
831
+ callback(currentValue);
832
+ return stop;
833
+ },
834
+ // ----- Writable ----- //
835
+ set: (newValue) => {
836
+ if (!deepEqual(currentValue, newValue)) {
837
+ const previousValue = currentValue;
838
+ currentValue = newValue;
839
+ for (const callback of observers) {
840
+ callback(currentValue, previousValue);
841
+ }
842
+ }
843
+ },
844
+ update: (callback) => {
845
+ const newValue = callback(currentValue);
846
+ if (!deepEqual(currentValue, newValue)) {
847
+ const previousValue = currentValue;
848
+ currentValue = newValue;
849
+ for (const callback2 of observers) {
850
+ callback2(currentValue, previousValue);
851
+ }
852
+ }
853
+ }
854
+ };
855
+ }
894
856
  function proxy(source, config) {
895
857
  if (!isReadable(source)) {
896
858
  throw new TypeError(`Proxy source must be a Readable.`);
897
859
  }
898
- const observers = [];
899
- const currentValue = () => config.get(source);
900
860
  return {
901
861
  // ----- Readable ----- //
902
- get: () => config.get(source),
862
+ get: () => config.get(),
903
863
  [OBSERVE]: (callback) => {
904
864
  let lastComputedValue = UNOBSERVED;
905
- return source[OBSERVE]((_) => {
906
- const computedValue = config.get(source);
865
+ return observe(source, (_) => {
866
+ const computedValue = config.get();
907
867
  if (!deepEqual(computedValue, lastComputedValue)) {
908
- const previousValue = lastComputedValue === UNOBSERVED ? void 0 : lastComputedValue;
909
- callback(computedValue, previousValue);
868
+ callback(computedValue);
910
869
  lastComputedValue = computedValue;
911
870
  }
912
871
  });
913
872
  },
914
873
  // ----- Writable ----- //
915
- set: (newValue) => {
916
- config.set(source, newValue);
874
+ set: (value) => {
875
+ config.set(value);
917
876
  },
918
877
  update: (callback) => {
919
- const newValue = callback(config.get(source));
920
- config.set(source, newValue);
878
+ config.set(callback(config.get()));
921
879
  }
922
880
  };
923
881
  }
882
+ function observe(...args) {
883
+ const callback = args.pop();
884
+ const readables = args.flat();
885
+ if (readables.length === 0) {
886
+ throw new TypeError(`Expected at least one readable.`);
887
+ }
888
+ if (readables.length > 1) {
889
+ return computed(...readables, callback)[OBSERVE](() => null);
890
+ } else {
891
+ return readables[0][OBSERVE](callback);
892
+ }
893
+ }
924
894
  function unwrap(value) {
925
895
  if (isReadable(value)) {
926
896
  return value.get();
@@ -1647,7 +1617,7 @@ function initView(config) {
1647
1617
  stores: /* @__PURE__ */ new Map(),
1648
1618
  parent: config.elementContext
1649
1619
  };
1650
- const $$children = writable(renderMarkupToDOM(config.children ?? [], { appContext, elementContext }));
1620
+ const $$children = $$(renderMarkupToDOM(config.children ?? [], { appContext, elementContext }));
1651
1621
  let isConnected = false;
1652
1622
  const stopObserverCallbacks = [];
1653
1623
  const connectedCallbacks = [];
@@ -1706,19 +1676,20 @@ function initView(config) {
1706
1676
  crash(error) {
1707
1677
  config.appContext.crashCollector.crash({ error, componentName: ctx.name });
1708
1678
  },
1709
- observe(readables, callback) {
1679
+ observe(...args) {
1680
+ const callback = args.pop();
1710
1681
  if (isConnected) {
1711
- const stop = observe(readables, callback);
1682
+ const stop = observe(args, callback);
1712
1683
  stopObserverCallbacks.push(stop);
1713
1684
  } else {
1714
1685
  connectedCallbacks.push(() => {
1715
- const stop = observe(readables, callback);
1686
+ const stop = observe(args, callback);
1716
1687
  stopObserverCallbacks.push(stop);
1717
1688
  });
1718
1689
  }
1719
1690
  },
1720
1691
  outlet() {
1721
- return m("$outlet", { $children: readable($$children) });
1692
+ return m("$outlet", { $children: $($$children) });
1722
1693
  }
1723
1694
  };
1724
1695
  const debugChannel = appContext.debugHub.channel({
@@ -1908,8 +1879,8 @@ var Repeat = class {
1908
1879
  connected.$$index.set(potential.index);
1909
1880
  newItems[potential.index] = connected;
1910
1881
  } else {
1911
- const $$value = writable(potential.value);
1912
- const $$index = writable(potential.index);
1882
+ const $$value = $$(potential.value);
1883
+ const $$index = $$(potential.index);
1913
1884
  newItems[potential.index] = {
1914
1885
  key: potential.key,
1915
1886
  $$value,
@@ -1918,7 +1889,7 @@ var Repeat = class {
1918
1889
  view: RepeatItemView,
1919
1890
  appContext: this.appContext,
1920
1891
  elementContext: this.elementContext,
1921
- props: { $value: readable($$value), $index: readable($$index), renderFn: this.renderFn }
1892
+ props: { $value: $($$value), $index: $($$index), renderFn: this.renderFn }
1922
1893
  })
1923
1894
  };
1924
1895
  }
@@ -2024,7 +1995,7 @@ function m(type, props, ...children) {
2024
1995
  };
2025
1996
  }
2026
1997
  function cond(predicate, thenContent, elseContent) {
2027
- const $predicate = readable(predicate);
1998
+ const $predicate = $(predicate);
2028
1999
  return m("$cond", {
2029
2000
  $predicate,
2030
2001
  thenContent,
@@ -2032,7 +2003,7 @@ function cond(predicate, thenContent, elseContent) {
2032
2003
  });
2033
2004
  }
2034
2005
  function repeat(items, keyFn, renderFn) {
2035
- const $items = readable(items);
2006
+ const $items = $(items);
2036
2007
  return m("$repeat", { $items, keyFn, renderFn });
2037
2008
  }
2038
2009
  function portal(content, parent) {
@@ -2238,7 +2209,9 @@ function initStore(config) {
2238
2209
  crash(error) {
2239
2210
  config.appContext.crashCollector.crash({ error, componentName: ctx.name });
2240
2211
  },
2241
- observe(readables, callback) {
2212
+ observe(...args) {
2213
+ const callback = args.pop();
2214
+ const readables = args.flat();
2242
2215
  if (isConnected) {
2243
2216
  const stop = observe(readables, callback);
2244
2217
  stopObserverCallbacks.push(stop);
@@ -2310,130 +2283,13 @@ function initStore(config) {
2310
2283
  };
2311
2284
  }
2312
2285
 
2313
- // src/stores/dialog.ts
2314
- function DialogStore(ctx) {
2315
- ctx.name = "dolla/dialog";
2316
- const { appContext, elementContext } = getStoreSecrets(ctx);
2317
- const container = document.createElement("div");
2318
- container.style.position = "fixed";
2319
- container.style.top = "0";
2320
- container.style.right = "0";
2321
- container.style.bottom = "0";
2322
- container.style.left = "0";
2323
- container.style.zIndex = "99999";
2324
- const $$dialogs = writable([]);
2325
- let activeDialogs = [];
2326
- function dialogChangedCallback() {
2327
- if (activeDialogs.length > 0) {
2328
- if (!container.parentNode) {
2329
- document.body.appendChild(container);
2330
- }
2331
- } else {
2332
- if (container.parentNode) {
2333
- document.body.removeChild(container);
2334
- }
2335
- }
2336
- }
2337
- ctx.observe($$dialogs, (dialogs) => {
2338
- requestAnimationFrame(() => {
2339
- let removed = [];
2340
- let added = [];
2341
- for (const dialog of activeDialogs) {
2342
- if (!dialogs.includes(dialog)) {
2343
- removed.push(dialog);
2344
- }
2345
- }
2346
- for (const dialog of dialogs) {
2347
- if (!activeDialogs.includes(dialog)) {
2348
- added.push(dialog);
2349
- }
2350
- }
2351
- for (const dialog of removed) {
2352
- if (dialog.transitionOutCallback) {
2353
- dialog.transitionOutCallback().then(() => {
2354
- dialog.instance.disconnect();
2355
- activeDialogs.splice(activeDialogs.indexOf(dialog), 1);
2356
- dialogChangedCallback();
2357
- });
2358
- } else {
2359
- dialog.instance.disconnect();
2360
- activeDialogs.splice(activeDialogs.indexOf(dialog), 1);
2361
- }
2362
- }
2363
- for (const dialog of added) {
2364
- dialog.instance.connect(container);
2365
- if (dialog.transitionInCallback) {
2366
- dialog.transitionInCallback();
2367
- }
2368
- activeDialogs.push(dialog);
2369
- }
2370
- dialogChangedCallback();
2371
- });
2372
- });
2373
- ctx.onDisconnected(() => {
2374
- if (container.parentNode) {
2375
- document.body.removeChild(container);
2376
- }
2377
- });
2378
- function open(view, props) {
2379
- const $$open = writable(true);
2380
- let dialog;
2381
- let transitionInCallback;
2382
- let transitionOutCallback;
2383
- let instance = initView({
2384
- view,
2385
- appContext,
2386
- elementContext,
2387
- props: {
2388
- ...props,
2389
- $$open,
2390
- transitionIn: (callback) => {
2391
- transitionInCallback = callback;
2392
- },
2393
- transitionOut: (callback) => {
2394
- transitionOutCallback = callback;
2395
- }
2396
- }
2397
- });
2398
- dialog = {
2399
- instance,
2400
- // These must be getters because the fns passed to props aren't called until before connect.
2401
- get transitionInCallback() {
2402
- return transitionInCallback;
2403
- },
2404
- get transitionOutCallback() {
2405
- return transitionOutCallback;
2406
- }
2407
- };
2408
- $$dialogs.update((current) => {
2409
- return [...current, dialog];
2410
- });
2411
- const stopObserver = observe($$open, (value) => {
2412
- if (!value) {
2413
- closeDialog();
2414
- }
2415
- });
2416
- function closeDialog() {
2417
- $$dialogs.update((current) => {
2418
- return current.filter((x) => x !== dialog);
2419
- });
2420
- dialog = void 0;
2421
- stopObserver();
2422
- }
2423
- return closeDialog;
2424
- }
2425
- return {
2426
- open
2427
- };
2428
- }
2429
-
2430
2286
  // src/stores/document.ts
2431
2287
  function DocumentStore(ctx) {
2432
2288
  ctx.name = "dolla/document";
2433
- const $$title = writable(document.title);
2434
- const $$visibility = writable(document.visibilityState);
2435
- const $$orientation = writable("landscape");
2436
- const $$colorScheme = writable("light");
2289
+ const $$title = $$(document.title);
2290
+ const $$visibility = $$(document.visibilityState);
2291
+ const $$orientation = $$("landscape");
2292
+ const $$colorScheme = $$("light");
2437
2293
  ctx.observe($$title, (current) => {
2438
2294
  document.title = current;
2439
2295
  });
@@ -2467,337 +2323,12 @@ function DocumentStore(ctx) {
2467
2323
  });
2468
2324
  return {
2469
2325
  $$title,
2470
- $visibility: readable($$visibility),
2471
- $orientation: readable($$orientation),
2472
- $colorScheme: readable($$colorScheme)
2326
+ $visibility: $($$visibility),
2327
+ $orientation: $($$orientation),
2328
+ $colorScheme: $($$colorScheme)
2473
2329
  };
2474
2330
  }
2475
2331
 
2476
- // src/stores/http.ts
2477
- function HTTPStore(ctx) {
2478
- ctx.name = "dolla/http";
2479
- const fetch = ctx.options.fetch ?? getDefaultFetch();
2480
- const middleware = [];
2481
- async function request(method, uri, options) {
2482
- return makeRequest({ ...options, method, uri, middleware, fetch });
2483
- }
2484
- return {
2485
- /**
2486
- * Adds a new middleware that will apply to subsequent requests.
2487
- * Returns a function to remove this middleware.
2488
- *
2489
- * @param middleware - A middleware function that will intercept requests.
2490
- */
2491
- middleware(fn) {
2492
- middleware.push(fn);
2493
- return function remove() {
2494
- middleware.splice(middleware.indexOf(fn), 1);
2495
- };
2496
- },
2497
- async get(uri, options) {
2498
- return request("get", uri, options);
2499
- },
2500
- async put(uri, options) {
2501
- return request("put", uri, options);
2502
- },
2503
- async patch(uri, options) {
2504
- return request("patch", uri, options);
2505
- },
2506
- async post(uri, options) {
2507
- return request("post", uri, options);
2508
- },
2509
- async delete(uri, options) {
2510
- return request("delete", uri, options);
2511
- },
2512
- async head(uri, options) {
2513
- return request("head", uri, options);
2514
- },
2515
- async options(uri, options) {
2516
- return request("options", uri, options);
2517
- },
2518
- async trace(uri, options) {
2519
- return request("trace", uri, options);
2520
- }
2521
- };
2522
- }
2523
- function getDefaultFetch() {
2524
- if (typeof window !== "undefined" && window.fetch) {
2525
- return window.fetch.bind(window);
2526
- }
2527
- if (typeof global !== "undefined" && global.fetch) {
2528
- return global.fetch.bind(global);
2529
- }
2530
- throw new Error("Running in neither browser nor node. Please run this app in one of the supported environments.");
2531
- }
2532
- var HTTPResponseError = class extends Error {
2533
- response;
2534
- constructor(response) {
2535
- const { status, statusText, method, uri } = response;
2536
- const message = `${status} ${statusText}: Request failed (${method.toUpperCase()} ${uri})`;
2537
- super(message);
2538
- this.response = response;
2539
- }
2540
- };
2541
- async function makeRequest(config) {
2542
- const { headers, query, fetch, middleware } = config;
2543
- const request = {
2544
- method: config.method,
2545
- uri: config.uri,
2546
- get sameOrigin() {
2547
- return !request.uri.startsWith("http");
2548
- },
2549
- query: new URLSearchParams(),
2550
- headers: new Headers(),
2551
- body: config.body
2552
- };
2553
- if (headers) {
2554
- if (headers instanceof Map || headers instanceof Headers) {
2555
- headers.forEach((value, key) => {
2556
- request.headers.set(key, value);
2557
- });
2558
- } else if (headers != null && typeof headers === "object" && !Array.isArray(headers)) {
2559
- for (const name in headers) {
2560
- request.headers.set(name, String(headers[name]));
2561
- }
2562
- } else {
2563
- throw new TypeError(`Unknown headers type. Got: ${headers}`);
2564
- }
2565
- }
2566
- if (query) {
2567
- if (query instanceof Map || query instanceof URLSearchParams) {
2568
- query.forEach((value, key) => {
2569
- request.query.set(key, value);
2570
- });
2571
- } else if (query != null && typeof query === "object" && !Array.isArray(query)) {
2572
- for (const name in query) {
2573
- request.query.set(name, String(query[name]));
2574
- }
2575
- } else {
2576
- throw new TypeError(`Unknown query params type. Got: ${query}`);
2577
- }
2578
- }
2579
- let response;
2580
- const handler = async () => {
2581
- const query2 = request.query.toString();
2582
- const fullURL = query2.length > 0 ? request.uri + "?" + query2 : request.uri;
2583
- let reqBody;
2584
- if (!request.headers.has("content-type") && isObject(request.body)) {
2585
- request.headers.set("content-type", "application/json");
2586
- reqBody = JSON.stringify(request.body);
2587
- } else {
2588
- reqBody = request.body;
2589
- }
2590
- const fetched = await fetch(fullURL, {
2591
- method: request.method,
2592
- headers: request.headers,
2593
- body: reqBody
2594
- });
2595
- const headers2 = Object.fromEntries(fetched.headers.entries());
2596
- const contentType = headers2["content-type"];
2597
- let body;
2598
- if (contentType?.includes("application/json")) {
2599
- body = await fetched.json();
2600
- } else if (contentType?.includes("application/x-www-form-urlencoded")) {
2601
- body = await fetched.formData();
2602
- } else {
2603
- body = await fetched.text();
2604
- }
2605
- response = {
2606
- method: request.method,
2607
- uri: request.uri,
2608
- status: fetched.status,
2609
- statusText: fetched.statusText,
2610
- headers: headers2,
2611
- body
2612
- };
2613
- };
2614
- if (middleware.length > 0) {
2615
- const mount = (index = 0) => {
2616
- const current = middleware[index];
2617
- const next = middleware[index + 1] ? mount(index + 1) : handler;
2618
- return async () => current(request, async () => {
2619
- await next();
2620
- return response;
2621
- });
2622
- };
2623
- await mount()();
2624
- } else {
2625
- await handler();
2626
- }
2627
- if (response.status < 200 || response.status >= 400) {
2628
- throw new HTTPResponseError(response);
2629
- }
2630
- return response;
2631
- }
2632
-
2633
- // src/stores/language.ts
2634
- function LanguageStore(ctx) {
2635
- ctx.name = "dolla/language";
2636
- const languages = /* @__PURE__ */ new Map();
2637
- Object.entries(ctx.options.languages).forEach(([tag, config]) => {
2638
- languages.set(tag, new Language(tag, config));
2639
- });
2640
- ctx.info(
2641
- `App supports ${languages.size} language${languages.size === 1 ? "" : "s"}: '${[...languages.keys()].join("', '")}'`
2642
- );
2643
- const $$isLoaded = writable(false);
2644
- const $$language = writable(void 0);
2645
- const $$translation = writable(void 0);
2646
- const $noLanguageValue = readable("[NO LANGUAGE SET]");
2647
- const translationCache = [];
2648
- function getCached(key, values) {
2649
- for (const entry of translationCache) {
2650
- if (entry[0] === key && deepEqual(entry[1], values)) {
2651
- return entry[2];
2652
- }
2653
- }
2654
- }
2655
- function replaceMustaches(template, values) {
2656
- for (const name in values) {
2657
- template = template.replace(`{{${name}}}`, String(values[name]));
2658
- }
2659
- return template;
2660
- }
2661
- const currentLanguage = ctx.options.currentLanguage ? languages.get(ctx.options.currentLanguage) : languages.get([...languages.keys()][0]);
2662
- if (currentLanguage == null) {
2663
- $$isLoaded.set(true);
2664
- } else {
2665
- ctx.info(`Current language is '${currentLanguage.tag}'.`);
2666
- currentLanguage.getTranslation().then((translation) => {
2667
- $$language.set(currentLanguage.tag);
2668
- $$translation.set(translation);
2669
- $$isLoaded.set(true);
2670
- });
2671
- }
2672
- return {
2673
- $isLoaded: readable($$isLoaded),
2674
- $currentLanguage: readable($$language),
2675
- supportedLanguages: [...languages.keys()],
2676
- async setLanguage(tag) {
2677
- if (!languages.has(tag)) {
2678
- throw new Error(`Language '${tag}' is not supported.`);
2679
- }
2680
- const lang = languages.get(tag);
2681
- try {
2682
- const translation = await lang.getTranslation();
2683
- $$translation.set(translation);
2684
- $$language.set(tag);
2685
- ctx.info("set language to " + tag);
2686
- } catch (error) {
2687
- if (error instanceof Error) {
2688
- ctx.crash(error);
2689
- }
2690
- }
2691
- },
2692
- /**
2693
- * Returns a Readable of the translated value.
2694
-
2695
- * @param key - Key to the translated value.
2696
- * @param values - A map of {{placeholder}} names and the values to replace them with.
2697
- */
2698
- translate(key, values) {
2699
- if (!$$language.get()) {
2700
- return $noLanguageValue;
2701
- }
2702
- const cached = getCached(key, values);
2703
- if (cached) {
2704
- return cached;
2705
- }
2706
- if (values) {
2707
- const readableValues = {};
2708
- for (const [key2, value] of Object.entries(values)) {
2709
- if (isReadable(value)) {
2710
- readableValues[key2] = value;
2711
- }
2712
- }
2713
- const readableEntries = Object.entries(readableValues);
2714
- if (readableEntries.length > 0) {
2715
- const $merged = computed([$$translation, ...readableEntries.map((x) => x[1])], ([t, ...entryValues]) => {
2716
- const entries = entryValues.map((_, i) => readableEntries[i]);
2717
- const mergedValues = {
2718
- ...values
2719
- };
2720
- for (let i = 0; i < entries.length; i++) {
2721
- const key2 = entries[i][0];
2722
- mergedValues[key2] = entryValues[i];
2723
- }
2724
- const result = resolve(t, key) || `[NO TRANSLATION: ${key}]`;
2725
- return replaceMustaches(result, mergedValues);
2726
- });
2727
- translationCache.push([key, values, $merged]);
2728
- return $merged;
2729
- }
2730
- }
2731
- const $replaced = computed($$translation, (t) => {
2732
- let result = resolve(t, key) || `[NO TRANSLATION: ${key}]`;
2733
- if (values) {
2734
- result = replaceMustaches(result, values);
2735
- }
2736
- return result;
2737
- });
2738
- translationCache.push([key, values, $replaced]);
2739
- return $replaced;
2740
- }
2741
- };
2742
- }
2743
- function resolve(object, key) {
2744
- const parsed = String(key).split(/[\.\[\]]/).filter((part) => part.trim() !== "");
2745
- let value = object;
2746
- while (parsed.length > 0) {
2747
- const part = parsed.shift();
2748
- if (value != null) {
2749
- value = value[part];
2750
- } else {
2751
- value = void 0;
2752
- }
2753
- }
2754
- return value;
2755
- }
2756
- var Language = class {
2757
- #tag;
2758
- #config;
2759
- #translation;
2760
- get tag() {
2761
- return this.#tag;
2762
- }
2763
- constructor(tag, config) {
2764
- this.#tag = tag;
2765
- this.#config = config;
2766
- }
2767
- async getTranslation() {
2768
- if (!this.#translation) {
2769
- if (isFunction(this.#config.translation)) {
2770
- const result = this.#config.translation();
2771
- if (isPromise(result)) {
2772
- const resolved = await result;
2773
- assertObject(
2774
- resolved,
2775
- `Translation promise of language '${this.#tag}' must resolve to an object of translated strings. Got type: %t, value: %v`
2776
- );
2777
- this.#translation = resolved;
2778
- } else if (isObject(result)) {
2779
- this.#translation = result;
2780
- } else {
2781
- throw new TypeError(
2782
- `Translation function of '${this.#tag}' must return an object or promise. Got type: ${typeOf(
2783
- result
2784
- )}, value: ${result}`
2785
- );
2786
- }
2787
- } else if (isObject(this.#config.translation)) {
2788
- this.#translation = this.#config.translation;
2789
- } else {
2790
- throw new TypeError(
2791
- `Translation of '${this.#tag}' must be an object of translated strings, a function that returns one, or an async function that resolves to one. Got type: ${typeOf(
2792
- this.#config.translation
2793
- )}, value: ${this.#config.translation}`
2794
- );
2795
- }
2796
- }
2797
- return this.#translation;
2798
- }
2799
- };
2800
-
2801
2332
  // src/stores/render.ts
2802
2333
  function RenderStore(ctx) {
2803
2334
  ctx.name = "dolla/render";
@@ -2882,10 +2413,290 @@ function RenderStore(ctx) {
2882
2413
  };
2883
2414
  }
2884
2415
 
2885
- // node_modules/@babel/runtime/helpers/esm/extends.js
2886
- function _extends() {
2887
- _extends = Object.assign || function(target) {
2888
- for (var i = 1; i < arguments.length; i++) {
2416
+ // src/app.ts
2417
+ function DefaultRootView(_, ctx) {
2418
+ return ctx.outlet();
2419
+ }
2420
+ function isAppOptions(value) {
2421
+ return isObject(value);
2422
+ }
2423
+ function App(options) {
2424
+ if (options && !isAppOptions(options)) {
2425
+ throw new TypeError(`App options must be an object. Got: ${options}`);
2426
+ }
2427
+ let isConnected = false;
2428
+ let mainView = m(options?.view ?? DefaultRootView);
2429
+ let configureCallback;
2430
+ const settings = merge(
2431
+ {
2432
+ debug: {
2433
+ filter: "*,-dolla/*",
2434
+ log: "development",
2435
+ // Only print logs in development.
2436
+ warn: "development",
2437
+ // Only print warnings in development.
2438
+ error: true
2439
+ // Always print errors.
2440
+ },
2441
+ mode: "production"
2442
+ },
2443
+ options ?? {}
2444
+ );
2445
+ const stores = /* @__PURE__ */ new Map([
2446
+ ["render", { store: RenderStore }],
2447
+ ["document", { store: DocumentStore }]
2448
+ ]);
2449
+ if (options?.stores) {
2450
+ for (const entry of options.stores) {
2451
+ assertFunction(entry.store, `Expected a store function. Got type: %t, value: %v`);
2452
+ stores.set(entry.store, entry);
2453
+ }
2454
+ }
2455
+ const crashCollector = new CrashCollector();
2456
+ const debugHub = new DebugHub({ ...settings.debug, crashCollector, mode: settings.mode });
2457
+ const debugChannel = debugHub.channel({ name: "dolla/App" });
2458
+ crashCollector.onError(async ({ error, severity, componentName }) => {
2459
+ if (severity === "crash") {
2460
+ await disconnect();
2461
+ const instance = initView({
2462
+ view: DefaultCrashPage,
2463
+ appContext,
2464
+ elementContext,
2465
+ props: {
2466
+ message: error.message,
2467
+ error,
2468
+ componentName
2469
+ }
2470
+ });
2471
+ instance.connect(appContext.rootElement);
2472
+ }
2473
+ });
2474
+ async function connect(selector) {
2475
+ return new Promise(async (resolve2) => {
2476
+ let element = null;
2477
+ if (isString(selector)) {
2478
+ element = document.querySelector(selector);
2479
+ assertInstanceOf(HTMLElement, element, `Selector string '${selector}' did not match any element.`);
2480
+ }
2481
+ assertInstanceOf(HTMLElement, element, "Expected a DOM node or a selector string. Got type: %t, value: %v");
2482
+ appContext.rootElement = element;
2483
+ appContext.rootView = initView({
2484
+ view: mainView.type,
2485
+ props: mainView.props,
2486
+ appContext,
2487
+ elementContext
2488
+ });
2489
+ for (let [key, item] of stores.entries()) {
2490
+ const { store, options: options2 } = item;
2491
+ const channelPrefix = isString(key) ? "dolla/store" : "store";
2492
+ const label = isString(key) ? key : store.name ?? "(anonymous)";
2493
+ const config = {
2494
+ store,
2495
+ appContext,
2496
+ elementContext,
2497
+ channelPrefix,
2498
+ label,
2499
+ options: options2 ?? {}
2500
+ };
2501
+ const instance = initStore(config);
2502
+ instance.setup();
2503
+ stores.set(key, { ...item, instance });
2504
+ }
2505
+ for (const { instance } of stores.values()) {
2506
+ instance.connect();
2507
+ }
2508
+ if (configureCallback) {
2509
+ await configureCallback({
2510
+ // TODO: Add context methods
2511
+ });
2512
+ }
2513
+ const done = () => {
2514
+ appContext.rootView.connect(appContext.rootElement);
2515
+ isConnected = true;
2516
+ resolve2();
2517
+ };
2518
+ done();
2519
+ });
2520
+ }
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
+ );
2653
+ }
2654
+
2655
+ // src/views/fragment.ts
2656
+ function Fragment(_, ctx) {
2657
+ return ctx.outlet();
2658
+ }
2659
+
2660
+ // src/views/store-scope.ts
2661
+ function StoreScope(props, ctx) {
2662
+ const { appContext, elementContext } = getViewSecrets(ctx);
2663
+ const instances = [];
2664
+ for (const config of props.stores) {
2665
+ let store;
2666
+ let options;
2667
+ if (isFunction(config)) {
2668
+ store = config;
2669
+ } else {
2670
+ store = config.store;
2671
+ options = config.options;
2672
+ }
2673
+ const instance = initStore({
2674
+ store,
2675
+ options,
2676
+ appContext,
2677
+ elementContext
2678
+ });
2679
+ instance.setup();
2680
+ elementContext.stores.set(store, { store, options, instance });
2681
+ instances.push(instance);
2682
+ }
2683
+ ctx.beforeConnect(() => {
2684
+ for (const instance of instances) {
2685
+ instance.connect();
2686
+ }
2687
+ });
2688
+ ctx.onDisconnected(() => {
2689
+ for (const instance of instances) {
2690
+ instance.disconnect();
2691
+ }
2692
+ });
2693
+ return ctx.outlet();
2694
+ }
2695
+
2696
+ // node_modules/@babel/runtime/helpers/esm/extends.js
2697
+ function _extends() {
2698
+ _extends = Object.assign || function(target) {
2699
+ for (var i = 1; i < arguments.length; i++) {
2889
2700
  var source = arguments[i];
2890
2701
  for (var key in source) {
2891
2702
  if (Object.prototype.hasOwnProperty.call(source, key)) {
@@ -3336,9 +3147,11 @@ function parsePath(path) {
3336
3147
  }
3337
3148
 
3338
3149
  // src/stores/router.ts
3150
+ var DefaultView = (_, ctx) => ctx.outlet();
3339
3151
  function RouterStore(ctx) {
3340
3152
  ctx.name = "dolla/router";
3341
3153
  const { appContext, elementContext } = getStoreSecrets(ctx);
3154
+ const render = ctx.getStore("render");
3342
3155
  let history;
3343
3156
  if (ctx.options.history) {
3344
3157
  history = ctx.options.history;
@@ -3347,30 +3160,96 @@ function RouterStore(ctx) {
3347
3160
  } else {
3348
3161
  history = createBrowserHistory();
3349
3162
  }
3350
- for (const route of ctx.options.routes) {
3351
- if (route.meta.redirect) {
3352
- let redirectPath;
3353
- if (isFunction(route.meta.redirect)) {
3354
- throw new Error(`Redirect functions are not yet supported.`);
3355
- } else if (isString(route.meta.redirect)) {
3356
- redirectPath = route.meta.redirect;
3357
- } else {
3358
- throw new TypeError(`Expected a string or redirect function. Got: ${route.meta.redirect}`);
3163
+ let layerId = 0;
3164
+ function prepareRoute(route, layers = []) {
3165
+ if (!(typeof route === "object" && !Array.isArray(route)) || !(typeof route.path === "string")) {
3166
+ throw new TypeError(`Route configs must be objects with a 'path' string property. Got: ${route}`);
3167
+ }
3168
+ if (route.redirect && route.routes) {
3169
+ throw new Error(`Route cannot have both a 'redirect' and nested 'routes'.`);
3170
+ } else if (route.redirect && route.view) {
3171
+ throw new Error(`Route cannot have both a 'redirect' and a 'view'.`);
3172
+ } else if (!route.view && !route.routes && !route.redirect) {
3173
+ throw new Error(`Route must have a 'view', a 'redirect', or a set of nested 'routes'.`);
3174
+ }
3175
+ const parts = splitPath(route.path);
3176
+ if (parts[parts.length - 1] === "*") {
3177
+ parts.pop();
3178
+ }
3179
+ const routes2 = [];
3180
+ if (route.redirect) {
3181
+ let redirect = route.redirect;
3182
+ if (isString(redirect)) {
3183
+ redirect = resolvePath(joinPath(parts), redirect);
3184
+ if (!redirect.startsWith("/")) {
3185
+ redirect = "/" + redirect;
3186
+ }
3359
3187
  }
3360
- const match = matchRoutes(ctx.options.routes, redirectPath, {
3361
- willMatch(r) {
3362
- return r !== route;
3188
+ routes2.push({
3189
+ pattern: route.path,
3190
+ meta: {
3191
+ redirect
3363
3192
  }
3364
3193
  });
3365
- if (!match) {
3366
- throw new Error(`Found a redirect to an undefined URL. From '${route.pattern}' to '${route.meta.redirect}'`);
3367
- }
3194
+ return routes2;
3195
+ }
3196
+ let view = DefaultView;
3197
+ if (typeof route.view === "function") {
3198
+ view = route.view;
3199
+ } else if (route.view) {
3200
+ throw new TypeError(`Route '${route.path}' expected a view function or undefined. Got: ${route.view}`);
3201
+ }
3202
+ if (route.routes) {
3203
+ for (const subroute of route.routes) {
3204
+ routes2.push(...prepareRoute(subroute));
3205
+ }
3206
+ } else {
3207
+ const markup = m(view);
3208
+ const layer = { id: layerId++, markup };
3209
+ routes2.push({
3210
+ pattern: route.path,
3211
+ meta: {
3212
+ pattern: route.path,
3213
+ layers: [...layers, layer]
3214
+ }
3215
+ });
3216
+ }
3217
+ return routes2;
3218
+ }
3219
+ const routes = sortRoutes(
3220
+ ctx.options.routes.flatMap((route) => prepareRoute(route)).map((route) => ({
3221
+ pattern: route.pattern,
3222
+ meta: route.meta,
3223
+ fragments: patternToFragments(route.pattern)
3224
+ }))
3225
+ );
3226
+ for (const route of routes) {
3227
+ if (route.meta.redirect) {
3228
+ let redirectPath;
3229
+ if (isFunction(route.meta.redirect)) {
3230
+ throw new Error(`Redirect functions are not yet supported.`);
3231
+ } else if (isString(route.meta.redirect)) {
3232
+ redirectPath = route.meta.redirect;
3233
+ } else {
3234
+ throw new TypeError(`Expected a string or redirect function. Got: ${route.meta.redirect}`);
3235
+ }
3236
+ const match = matchRoutes(routes, redirectPath, {
3237
+ willMatch(r) {
3238
+ return r !== route;
3239
+ }
3240
+ });
3241
+ if (!match) {
3242
+ throw new Error(`Found a redirect to an undefined URL. From '${route.pattern}' to '${route.meta.redirect}'`);
3243
+ }
3368
3244
  }
3369
3245
  }
3370
- const $$pattern = writable(null);
3371
- const $$path = writable("");
3372
- const $$params = writable({});
3373
- const $$query = writable({});
3246
+ ctx.onConnected(() => {
3247
+ ctx.info(`Total routes: ${routes.length}`);
3248
+ });
3249
+ const $$pattern = $$(null);
3250
+ const $$path = $$("");
3251
+ const $$params = $$({});
3252
+ const $$query = $$({});
3374
3253
  let isRouteChange = true;
3375
3254
  ctx.observe($$query, (current) => {
3376
3255
  if (isRouteChange) {
@@ -3405,7 +3284,7 @@ function RouterStore(ctx) {
3405
3284
  isRouteChange = true;
3406
3285
  $$query.set(parseQueryParams(location.search.startsWith("?") ? location.search.slice(1) : location.search));
3407
3286
  }
3408
- const matched = matchRoutes(ctx.options.routes, location.pathname);
3287
+ const matched = matchRoutes(routes, location.pathname);
3409
3288
  if (!matched) {
3410
3289
  $$pattern.set(null);
3411
3290
  $$path.set(location.pathname);
@@ -3444,7 +3323,7 @@ function RouterStore(ctx) {
3444
3323
  const renderContext = { appContext, elementContext };
3445
3324
  const rendered = renderMarkupToDOM(matchedLayer.markup, renderContext);
3446
3325
  const handle = getRenderHandle(rendered);
3447
- requestAnimationFrame(() => {
3326
+ render.update(() => {
3448
3327
  if (activeLayer && activeLayer.handle.connected) {
3449
3328
  activeLayer.handle.disconnect();
3450
3329
  }
@@ -3453,7 +3332,7 @@ function RouterStore(ctx) {
3453
3332
  } else {
3454
3333
  appContext.rootView.setChildren(rendered);
3455
3334
  }
3456
- });
3335
+ }, "dolla-router-change");
3457
3336
  activeLayers.push({ id: matchedLayer.id, handle });
3458
3337
  }
3459
3338
  }
@@ -3478,15 +3357,15 @@ function RouterStore(ctx) {
3478
3357
  /**
3479
3358
  * The currently matched route pattern, if any.
3480
3359
  */
3481
- $pattern: readable($$pattern),
3360
+ $pattern: $($$pattern),
3482
3361
  /**
3483
3362
  * The current URL path.
3484
3363
  */
3485
- $path: readable($$path),
3364
+ $path: $($$path),
3486
3365
  /**
3487
3366
  * The current named path params.
3488
3367
  */
3489
- $params: readable($$params),
3368
+ $params: $($$params),
3490
3369
  /**
3491
3370
  * The current query params. Changes to this object will be reflected in the URL.
3492
3371
  */
@@ -3547,542 +3426,454 @@ function catchLinks(root, callback, _window = window) {
3547
3426
  };
3548
3427
  }
3549
3428
 
3550
- // src/app.ts
3551
- function DefaultRootView(_, ctx) {
3552
- return ctx.outlet();
3553
- }
3554
- function makeApp(options) {
3555
- if (options && !isObject(options)) {
3556
- throw new TypeError(`App options must be an object. Got: ${options}`);
3557
- }
3558
- let isConnected = false;
3559
- let mainView = m(DefaultRootView);
3560
- let configureCallback;
3561
- const settings = merge(
3562
- {
3563
- debug: {
3564
- filter: "*,-dolla/*",
3565
- log: "development",
3566
- // Only print logs in development.
3567
- warn: "development",
3568
- // Only print warnings in development.
3569
- error: true
3570
- // Always print errors.
3571
- },
3572
- router: {
3573
- hash: false
3574
- },
3575
- mode: "production"
3576
- },
3577
- options ?? {}
3578
- );
3579
- const stores = /* @__PURE__ */ new Map([
3580
- ["dialog", { store: DialogStore }],
3581
- ["router", { store: RouterStore }],
3582
- ["document", { store: DocumentStore }],
3583
- ["http", { store: HTTPStore }],
3584
- ["language", { store: LanguageStore }],
3585
- ["render", { store: RenderStore }]
3586
- ]);
3429
+ // src/stores/language.ts
3430
+ function LanguageStore(ctx) {
3431
+ ctx.name = "dolla/language";
3587
3432
  const languages = /* @__PURE__ */ new Map();
3588
- let currentLanguage;
3589
- let layerId = 0;
3590
- let routes = [];
3591
- function prepareRoute(route, layers = []) {
3592
- if (!isObject(route) || !isString(route.pattern)) {
3593
- throw new TypeError(`Route configs must be objects with a 'pattern' string property. Got: ${route}`);
3594
- }
3595
- const parts = splitPath(route.pattern);
3596
- if (parts[parts.length - 1] === "*") {
3597
- parts.pop();
3598
- }
3599
- const routes2 = [];
3600
- if (route.redirect) {
3601
- let redirect = route.redirect;
3602
- if (isString(redirect)) {
3603
- redirect = resolvePath(joinPath(parts), redirect);
3604
- if (!redirect.startsWith("/")) {
3605
- redirect = "/" + redirect;
3606
- }
3433
+ const cache = /* @__PURE__ */ new Map();
3434
+ ctx.options.languages.forEach((entry) => {
3435
+ languages.set(entry.name, entry);
3436
+ });
3437
+ ctx.info(
3438
+ `App supports ${languages.size} language${languages.size === 1 ? "" : "s"}: '${[...languages.keys()].join("', '")}'`
3439
+ );
3440
+ async function getTranslation(config) {
3441
+ if (!cache.has(config.name)) {
3442
+ let fn;
3443
+ if (isString(config.translations)) {
3444
+ fn = async () => {
3445
+ return fetch(config.translations).then((res) => res.json());
3446
+ };
3447
+ } else if (isFunction(config.translations)) {
3448
+ fn = async () => config.translations();
3449
+ } else if (isObject(config.translations)) {
3450
+ fn = async () => config.translations;
3451
+ } else {
3452
+ throw new TypeError(
3453
+ `Translation of '${config.name}' must be an object of translated strings, a path to an object of translated strings, a function that returns one, or an async function that resolves to one. Got type: ${typeOf(
3454
+ config.translations
3455
+ )}, value: ${config.translations}`
3456
+ );
3457
+ }
3458
+ try {
3459
+ const translation = await fn();
3460
+ assertObject(
3461
+ translation,
3462
+ `Expected '${config.name}' translations to resolve to an object. Got type: %t, value: %v`
3463
+ );
3464
+ cache.set(config.name, translation);
3465
+ } catch (err) {
3466
+ throw err;
3607
3467
  }
3608
- routes2.push({
3609
- pattern: route.pattern,
3610
- meta: {
3611
- redirect
3612
- }
3613
- });
3614
- return routes2;
3615
3468
  }
3616
- let view;
3617
- if (!route.view) {
3618
- view = DefaultRootView;
3619
- } else if (typeof route.view === "function") {
3620
- view = route.view;
3621
- } else {
3622
- throw new TypeError(`Route '${route.pattern}' expected a view function. Got: ${route.view}`);
3623
- }
3624
- const markup = m(view);
3625
- const layer = { id: layerId++, markup };
3626
- if (route.subroutes) {
3627
- const router = {
3628
- route: (pattern, view2, subroutes) => {
3629
- pattern = joinPath([...parts, pattern]);
3630
- routes2.push(...prepareRoute({ pattern, view: view2, subroutes }));
3631
- return router;
3632
- },
3633
- redirect: (pattern, redirect) => {
3634
- pattern = joinPath([...parts, pattern]);
3635
- routes2.push(...prepareRoute({ pattern, redirect }));
3636
- return router;
3637
- }
3638
- };
3639
- route.subroutes(router);
3640
- } else {
3641
- routes2.push({
3642
- pattern: route.pattern,
3643
- meta: {
3644
- pattern: route.pattern,
3645
- layers: [...layers, layer]
3646
- }
3647
- });
3469
+ return cache.get(config.name);
3470
+ }
3471
+ const $$isLoaded = $$(false);
3472
+ const $$language = $$();
3473
+ const $$translation = $$();
3474
+ const $noLanguageValue = $("[NO LANGUAGE SET]");
3475
+ const translationCache = [];
3476
+ function getCached(key, values) {
3477
+ for (const entry of translationCache) {
3478
+ if (entry[0] === key && deepEqual(entry[1], values)) {
3479
+ return entry[2];
3480
+ }
3648
3481
  }
3649
- return routes2;
3650
3482
  }
3651
- const crashCollector = new CrashCollector();
3652
- const debugHub = new DebugHub({ ...settings.debug, crashCollector, mode: settings.mode });
3653
- const debugChannel = debugHub.channel({ name: "dolla/App" });
3654
- crashCollector.onError(async ({ error, severity, componentName }) => {
3655
- if (severity === "crash") {
3656
- await disconnect();
3657
- const instance = initView({
3658
- view: DefaultCrashPage,
3659
- appContext,
3660
- elementContext,
3661
- props: {
3662
- message: error.message,
3663
- error,
3664
- componentName
3665
- }
3666
- });
3667
- instance.connect(appContext.rootElement);
3483
+ function replaceMustaches(template, values) {
3484
+ for (const name in values) {
3485
+ template = template.replace(`{{${name}}}`, String(values[name]));
3668
3486
  }
3669
- });
3670
- async function connect(selector) {
3671
- return new Promise(async (resolve2) => {
3672
- let element = null;
3673
- if (isString(selector)) {
3674
- element = document.querySelector(selector);
3675
- assertInstanceOf(HTMLElement, element, `Selector string '${selector}' did not match any element.`);
3487
+ return template;
3488
+ }
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);
3496
+ $$translation.set(translation);
3497
+ $$isLoaded.set(true);
3498
+ });
3499
+ }
3500
+ return {
3501
+ $isLoaded: $($$isLoaded),
3502
+ $currentLanguage: $($$language),
3503
+ supportedLanguages: [...languages.keys()],
3504
+ async setLanguage(tag) {
3505
+ if (!languages.has(tag)) {
3506
+ throw new Error(`Language '${tag}' is not supported.`);
3676
3507
  }
3677
- assertInstanceOf(HTMLElement, element, "Expected a DOM node or a selector string. Got type: %t, value: %v");
3678
- appContext.rootElement = element;
3679
- routes = sortRoutes(routes);
3680
- const language = stores.get("language");
3681
- stores.set("language", {
3682
- ...language,
3683
- options: {
3684
- languages: Object.fromEntries(languages.entries()),
3685
- currentLanguage
3686
- }
3687
- });
3688
- const router = stores.get("router");
3689
- stores.set("router", {
3690
- ...router,
3691
- options: {
3692
- options: settings.router,
3693
- routes
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);
3694
3517
  }
3695
- });
3696
- debugChannel.info(`Total routes: ${routes.length}`);
3697
- appContext.rootView = initView({
3698
- view: mainView.type,
3699
- props: mainView.props,
3700
- appContext,
3701
- elementContext
3702
- });
3703
- for (let [key, item] of stores.entries()) {
3704
- const { store, options: options2 } = item;
3705
- const channelPrefix = isString(key) ? "dolla/store" : "store";
3706
- const label = isString(key) ? key : store.name ?? "(anonymous)";
3707
- const config = {
3708
- store,
3709
- appContext,
3710
- elementContext,
3711
- channelPrefix,
3712
- label,
3713
- options: options2 ?? {}
3714
- };
3715
- const instance = initStore(config);
3716
- instance.setup();
3717
- stores.set(key, { ...item, instance });
3718
3518
  }
3719
- for (const { instance } of stores.values()) {
3720
- instance.connect();
3519
+ },
3520
+ /**
3521
+ * Returns a Readable of the translated value.
3522
+
3523
+ * @param key - Key to the translated value.
3524
+ * @param values - A map of {{placeholder}} names and the values to replace them with.
3525
+ */
3526
+ translate(key, values) {
3527
+ if (!$$language.get()) {
3528
+ return $noLanguageValue;
3721
3529
  }
3722
- if (configureCallback) {
3723
- await configureCallback({
3724
- // TODO: Add context methods
3725
- });
3530
+ const cached = getCached(key, values);
3531
+ if (cached) {
3532
+ return cached;
3726
3533
  }
3727
- const { $isLoaded } = stores.get("language").instance.exports;
3728
- const done = () => {
3729
- appContext.rootView.connect(appContext.rootElement);
3730
- isConnected = true;
3731
- resolve2();
3732
- };
3733
- if ($isLoaded.get()) {
3734
- return done();
3534
+ if (values) {
3535
+ const readableValues = {};
3536
+ for (const [key2, value] of Object.entries(values)) {
3537
+ if (isReadable(value)) {
3538
+ readableValues[key2] = value;
3539
+ }
3540
+ }
3541
+ const readableEntries = Object.entries(readableValues);
3542
+ if (readableEntries.length > 0) {
3543
+ const readables = readableEntries.map((x) => x[1]);
3544
+ const $merged = $([$$translation, ...readables], (t, ...entryValues) => {
3545
+ const entries = entryValues.map((_, i) => readableEntries[i]);
3546
+ const mergedValues = {
3547
+ ...values
3548
+ };
3549
+ for (let i = 0; i < entries.length; i++) {
3550
+ const key2 = entries[i][0];
3551
+ mergedValues[key2] = entryValues[i];
3552
+ }
3553
+ const result = resolve(t, key) || `[NO TRANSLATION: ${key}]`;
3554
+ return replaceMustaches(result, mergedValues);
3555
+ });
3556
+ translationCache.push([key, values, $merged]);
3557
+ return $merged;
3558
+ }
3735
3559
  }
3736
- const stop = observe($isLoaded, (isLoaded) => {
3737
- if (isLoaded) {
3738
- stop();
3739
- done();
3560
+ const $replaced = $($$translation, (t) => {
3561
+ let result = resolve(t, key) || `[NO TRANSLATION: ${key}]`;
3562
+ if (values) {
3563
+ result = replaceMustaches(result, values);
3740
3564
  }
3565
+ return result;
3741
3566
  });
3742
- });
3743
- }
3744
- async function disconnect() {
3745
- if (isConnected) {
3746
- appContext.rootView.disconnect();
3747
- isConnected = false;
3748
- for (const { instance } of stores.values()) {
3749
- instance.disconnect();
3750
- }
3567
+ translationCache.push([key, values, $replaced]);
3568
+ return $replaced;
3751
3569
  }
3752
- }
3753
- const appContext = {
3754
- crashCollector,
3755
- debugHub,
3756
- stores,
3757
- mode: settings.mode ?? "production"
3758
- // $dialogs - added by dialog store
3759
- };
3760
- const elementContext = {
3761
- stores: /* @__PURE__ */ new Map()
3762
3570
  };
3763
- const app = {
3764
- connect,
3765
- disconnect,
3766
- get isConnected() {
3767
- return isConnected;
3571
+ }
3572
+ function resolve(object, key) {
3573
+ const parsed = String(key).split(/[\.\[\]]/).filter((part) => part.trim() !== "");
3574
+ let value = object;
3575
+ while (parsed.length > 0) {
3576
+ const part = parsed.shift();
3577
+ if (value != null) {
3578
+ value = value[part];
3579
+ } else {
3580
+ value = void 0;
3581
+ }
3582
+ }
3583
+ return value;
3584
+ }
3585
+
3586
+ // src/stores/http.ts
3587
+ function HTTPStore(ctx) {
3588
+ ctx.name = "dolla/http";
3589
+ const fetch2 = ctx.options.fetch ?? getDefaultFetch();
3590
+ const middleware = [];
3591
+ async function request(method, uri, options) {
3592
+ return makeRequest({ ...options, method, uri, middleware, fetch: fetch2 });
3593
+ }
3594
+ return {
3595
+ /**
3596
+ * Adds a new middleware that will apply to subsequent requests.
3597
+ * Returns a function to remove this middleware.
3598
+ *
3599
+ * @param middleware - A middleware function that will intercept requests.
3600
+ */
3601
+ middleware(fn) {
3602
+ middleware.push(fn);
3603
+ return function remove() {
3604
+ middleware.splice(middleware.indexOf(fn), 1);
3605
+ };
3768
3606
  },
3769
- main(view, attributes) {
3770
- if (mainView.type !== DefaultRootView) {
3771
- debugChannel.warn(`Root view is already defined. Only the final main call will take effect.`);
3772
- }
3773
- if (typeof view === "function") {
3774
- mainView = m(view, attributes);
3775
- } else {
3776
- throw new TypeError(`Expected a view function. Got type: ${typeOf(view)}, value: ${view}`);
3777
- }
3778
- return app;
3607
+ async get(uri, options) {
3608
+ return request("get", uri, options);
3779
3609
  },
3780
- store(store, options2) {
3781
- let config;
3782
- if (isFunction(store)) {
3783
- config = { store, options: options2 };
3784
- } else {
3785
- throw new TypeError(`Expected a store function. Got type: ${typeOf(store)}, value: ${store}`);
3786
- }
3787
- assertFunction(store, "Expected a store function or a store config object. Got type: %t, value: %v");
3788
- stores.set(store, config);
3789
- return app;
3610
+ async put(uri, options) {
3611
+ return request("put", uri, options);
3790
3612
  },
3791
- getStore(store) {
3792
- const match = stores.get(store);
3793
- const name = isString(store) ? store : store.name;
3794
- if (!match) {
3795
- throw new Error(`Store '${name}' is not registered on this app.`);
3796
- }
3797
- if (!match.instance) {
3798
- throw new Error(`Store '${name}' is not yet initialized. App must be connected first.`);
3799
- }
3800
- return match.instance.exports;
3613
+ async patch(uri, options) {
3614
+ return request("patch", uri, options);
3801
3615
  },
3802
- language(tag, config) {
3803
- languages.set(tag, config);
3804
- return app;
3616
+ async post(uri, options) {
3617
+ return request("post", uri, options);
3805
3618
  },
3806
- setLanguage(tag, fallback) {
3807
- if (tag === "auto") {
3808
- let tags = [];
3809
- if (typeof navigator === "object") {
3810
- const nav = navigator;
3811
- if (nav.languages?.length > 0) {
3812
- tags.push(...nav.languages);
3813
- } else if (nav.language) {
3814
- tags.push(nav.language);
3815
- } else if (nav.browserLanguage) {
3816
- tags.push(nav.browserLanguage);
3817
- } else if (nav.userLanguage) {
3818
- tags.push(nav.userLanguage);
3819
- }
3820
- }
3821
- for (const tag2 of tags) {
3822
- if (languages.has(tag2)) {
3823
- currentLanguage = tag2;
3824
- return this;
3825
- }
3826
- }
3827
- if (!currentLanguage && fallback) {
3828
- if (languages.has(fallback)) {
3829
- currentLanguage = fallback;
3830
- }
3831
- }
3832
- } else {
3833
- if (languages.has(tag)) {
3834
- currentLanguage = tag;
3835
- } else {
3836
- throw new Error(`Language '${tag}' has not been added to this app yet.`);
3837
- }
3838
- }
3839
- return app;
3619
+ async delete(uri, options) {
3620
+ return request("delete", uri, options);
3840
3621
  },
3841
- route(pattern, view, subroutes) {
3842
- assertString(pattern, "Pattern must be a string. Got type: %t, value: %v");
3843
- if (view == null) {
3844
- assertFunction(subroutes, "Sub routes must be defined when `view` is null.");
3845
- }
3846
- prepareRoute({ pattern, view, subroutes }).forEach((route) => {
3847
- routes.push({
3848
- pattern: route.pattern,
3849
- meta: route.meta,
3850
- fragments: patternToFragments(route.pattern)
3851
- });
3852
- });
3853
- return app;
3622
+ async head(uri, options) {
3623
+ return request("head", uri, options);
3854
3624
  },
3855
- redirect(pattern, redirect) {
3856
- if (!isFunction(redirect) && !isString(redirect)) {
3857
- throw new TypeError(`Expected a redirect path or function. Got type: ${typeOf(redirect)}, value: ${redirect}`);
3858
- }
3859
- prepareRoute({ pattern, redirect }).forEach((route) => {
3860
- routes.push({
3861
- pattern: route.pattern,
3862
- meta: route.meta,
3863
- fragments: patternToFragments(route.pattern)
3864
- });
3865
- });
3866
- return app;
3625
+ async options(uri, options) {
3626
+ return request("options", uri, options);
3867
3627
  },
3868
- configure(callback) {
3869
- if (configureCallback !== void 0) {
3870
- debugChannel.warn(`Configure callback is already defined. Only the final configure call will take effect.`);
3871
- }
3872
- configureCallback = callback;
3873
- return app;
3628
+ async trace(uri, options) {
3629
+ return request("trace", uri, options);
3874
3630
  }
3875
3631
  };
3876
- return app;
3877
3632
  }
3878
- function DefaultCrashPage({ message, error, componentName }) {
3879
- return m(
3880
- "div",
3881
- {
3882
- style: {
3883
- backgroundColor: "#880000",
3884
- color: "#fff",
3885
- padding: "2rem",
3886
- position: "fixed",
3887
- inset: 0,
3888
- fontSize: "20px"
3889
- }
3890
- },
3891
- m("h1", { style: { marginBottom: "0.5rem" } }, "The app has crashed"),
3892
- m(
3893
- "p",
3894
- { style: { marginBottom: "0.25rem" } },
3895
- m("span", { style: { fontFamily: "monospace" } }, componentName),
3896
- " says:"
3897
- ),
3898
- m(
3899
- "blockquote",
3900
- {
3901
- style: {
3902
- backgroundColor: "#991111",
3903
- padding: "0.25em",
3904
- borderRadius: "6px",
3905
- fontFamily: "monospace",
3906
- marginBottom: "1rem"
3907
- }
3908
- },
3909
- m(
3910
- "span",
3911
- {
3912
- style: {
3913
- display: "inline-block",
3914
- backgroundColor: "red",
3915
- padding: "0.1em 0.4em",
3916
- marginRight: "0.5em",
3917
- borderRadius: "4px",
3918
- fontSize: "0.9em",
3919
- fontWeight: "bold"
3920
- }
3921
- },
3922
- error.name
3923
- ),
3924
- message
3925
- ),
3926
- m("p", {}, "Please see the browser console for details.")
3927
- );
3633
+ function getDefaultFetch() {
3634
+ if (typeof window !== "undefined" && window.fetch) {
3635
+ return window.fetch.bind(window);
3636
+ }
3637
+ if (typeof global !== "undefined" && global.fetch) {
3638
+ return global.fetch.bind(global);
3639
+ }
3640
+ throw new Error("Running in neither browser nor node. Please run this app in one of the supported environments.");
3928
3641
  }
3929
-
3930
- // src/spring.ts
3931
- function spring(initialValue, options) {
3932
- const mass = options?.mass ?? 2;
3933
- const stiffness = options?.stiffness ?? 1200;
3934
- const damping = options?.damping ?? 160;
3935
- const velocity = options?.velocity ?? 5;
3936
- const endAmplitude = options?.endAmplitude ?? 1e-3;
3937
- const endWindow = options?.endWindow ?? 20;
3938
- const $$currentValue = writable(initialValue);
3939
- let nextId = 0;
3940
- let currentAnimationId;
3941
- const snapTo = (newValue) => {
3942
- currentAnimationId = void 0;
3943
- $$currentValue.set(newValue);
3944
- };
3945
- const animateTo = async (endValue, options2) => {
3946
- const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
3947
- if (mediaQuery.matches) {
3948
- return snapTo(endValue);
3949
- }
3950
- const _endAmplitude = readable(options2?.endAmplitude ?? endAmplitude).get();
3951
- const _endWindow = readable(options2?.endWindow ?? endWindow).get();
3952
- return new Promise((resolve2) => {
3953
- const id = nextId++;
3954
- const amplitude = makeAmplitudeMeasurer(_endWindow);
3955
- const springParams = {
3956
- mass: options2?.mass ?? mass,
3957
- stiffness: options2?.stiffness ?? stiffness,
3958
- damping: options2?.damping ?? damping,
3959
- velocity: options2?.velocity ?? velocity
3960
- };
3961
- const startTime = Date.now();
3962
- const startValue = $$currentValue.get();
3963
- const step = () => {
3964
- if (currentAnimationId !== id) {
3965
- resolve2();
3966
- return;
3967
- }
3968
- const elapsedTime = Date.now() - startTime;
3969
- const proportion = solve(springParams, elapsedTime / 1e3);
3970
- $$currentValue.set(startValue + (endValue - startValue) * proportion);
3971
- amplitude.sample(proportion);
3972
- if (amplitude.value && amplitude.value < _endAmplitude) {
3973
- currentAnimationId = void 0;
3974
- $$currentValue.set(endValue);
3975
- }
3976
- window.requestAnimationFrame(step);
3977
- };
3978
- currentAnimationId = id;
3979
- window.requestAnimationFrame(step);
3980
- });
3981
- };
3982
- return {
3983
- get: $$currentValue.get,
3984
- set: snapTo,
3985
- update: (callback) => {
3986
- const newValue = callback($$currentValue.get());
3987
- snapTo(newValue);
3642
+ var HTTPResponseError = class extends Error {
3643
+ response;
3644
+ constructor(response) {
3645
+ const { status, statusText, method, uri } = response;
3646
+ const message = `${status} ${statusText}: Request failed (${method.toUpperCase()} ${uri})`;
3647
+ super(message);
3648
+ this.response = response;
3649
+ }
3650
+ };
3651
+ async function makeRequest(config) {
3652
+ const { headers, query, fetch: fetch2, middleware } = config;
3653
+ const request = {
3654
+ method: config.method,
3655
+ uri: config.uri,
3656
+ get sameOrigin() {
3657
+ return !request.uri.startsWith("http");
3988
3658
  },
3989
- [OBSERVE]: $$currentValue[OBSERVE],
3990
- animateTo
3659
+ query: new URLSearchParams(),
3660
+ headers: new Headers(),
3661
+ body: config.body
3991
3662
  };
3992
- }
3993
- function solve(parameters, elapsedSeconds) {
3994
- const mass = unwrap(parameters.mass);
3995
- const stiffness = unwrap(parameters.stiffness);
3996
- const damping = unwrap(parameters.damping);
3997
- const initialVelocity = unwrap(parameters.velocity);
3998
- const dampingRatio = damping / (2 * Math.sqrt(stiffness * mass));
3999
- const speed = Math.sqrt(stiffness / mass);
4000
- let B;
4001
- let position;
4002
- if (dampingRatio < 1) {
4003
- const dampedSpeed = speed * Math.sqrt(1 - dampingRatio * dampingRatio);
4004
- B = (dampingRatio * speed + -initialVelocity) / dampedSpeed;
4005
- position = (Math.cos(dampedSpeed * elapsedSeconds) + B * Math.sin(dampedSpeed * elapsedSeconds)) * Math.exp(-elapsedSeconds * speed * dampingRatio);
4006
- } else {
4007
- B = speed + -initialVelocity;
4008
- position = (1 + B * elapsedSeconds) * Math.exp(-elapsedSeconds * speed);
4009
- }
4010
- return 1 - position;
4011
- }
4012
- function makeAmplitudeMeasurer(resolution) {
4013
- const samples = [];
4014
- return {
4015
- sample(value) {
4016
- samples.push(value);
4017
- while (samples.length > resolution) {
4018
- samples.shift();
3663
+ if (headers) {
3664
+ if (headers instanceof Map || headers instanceof Headers) {
3665
+ headers.forEach((value, key) => {
3666
+ request.headers.set(key, value);
3667
+ });
3668
+ } else if (headers != null && typeof headers === "object" && !Array.isArray(headers)) {
3669
+ for (const name in headers) {
3670
+ request.headers.set(name, String(headers[name]));
4019
3671
  }
4020
- },
4021
- get value() {
4022
- if (samples.length < resolution) {
4023
- return null;
3672
+ } else {
3673
+ throw new TypeError(`Unknown headers type. Got: ${headers}`);
3674
+ }
3675
+ }
3676
+ if (query) {
3677
+ if (query instanceof Map || query instanceof URLSearchParams) {
3678
+ query.forEach((value, key) => {
3679
+ request.query.set(key, value);
3680
+ });
3681
+ } else if (query != null && typeof query === "object" && !Array.isArray(query)) {
3682
+ for (const name in query) {
3683
+ request.query.set(name, String(query[name]));
4024
3684
  }
4025
- return Math.max(...samples) - Math.min(...samples);
3685
+ } else {
3686
+ throw new TypeError(`Unknown query params type. Got: ${query}`);
3687
+ }
3688
+ }
3689
+ let response;
3690
+ const handler = async () => {
3691
+ const query2 = request.query.toString();
3692
+ const fullURL = query2.length > 0 ? request.uri + "?" + query2 : request.uri;
3693
+ let reqBody;
3694
+ if (!request.headers.has("content-type") && isObject(request.body)) {
3695
+ request.headers.set("content-type", "application/json");
3696
+ reqBody = JSON.stringify(request.body);
3697
+ } else {
3698
+ reqBody = request.body;
3699
+ }
3700
+ const fetched = await fetch2(fullURL, {
3701
+ method: request.method,
3702
+ headers: request.headers,
3703
+ body: reqBody
3704
+ });
3705
+ const headers2 = Object.fromEntries(fetched.headers.entries());
3706
+ const contentType = headers2["content-type"];
3707
+ let body;
3708
+ if (contentType?.includes("application/json")) {
3709
+ body = await fetched.json();
3710
+ } else if (contentType?.includes("application/x-www-form-urlencoded")) {
3711
+ body = await fetched.formData();
3712
+ } else {
3713
+ body = await fetched.text();
4026
3714
  }
3715
+ response = {
3716
+ method: request.method,
3717
+ uri: request.uri,
3718
+ status: fetched.status,
3719
+ statusText: fetched.statusText,
3720
+ headers: headers2,
3721
+ body
3722
+ };
4027
3723
  };
3724
+ if (middleware.length > 0) {
3725
+ const mount = (index = 0) => {
3726
+ const current = middleware[index];
3727
+ const next = middleware[index + 1] ? mount(index + 1) : handler;
3728
+ return async () => current(request, async () => {
3729
+ await next();
3730
+ return response;
3731
+ });
3732
+ };
3733
+ await mount()();
3734
+ } else {
3735
+ await handler();
3736
+ }
3737
+ if (response.status < 200 || response.status >= 400) {
3738
+ throw new HTTPResponseError(response);
3739
+ }
3740
+ return response;
4028
3741
  }
4029
3742
 
4030
- // src/views/fragment.ts
4031
- function Fragment(_, ctx) {
4032
- return ctx.outlet();
4033
- }
4034
-
4035
- // src/views/store-scope.ts
4036
- function StoreScope(props, ctx) {
4037
- const { appContext, elementContext } = getViewSecrets(ctx);
4038
- const instances = [];
4039
- for (const config of props.stores) {
4040
- let store;
4041
- let options;
4042
- if (isFunction(config)) {
4043
- store = config;
3743
+ // src/stores/dialog.ts
3744
+ function DialogStore(ctx) {
3745
+ ctx.name = "dolla/dialog";
3746
+ const { appContext, elementContext } = getStoreSecrets(ctx);
3747
+ const render = ctx.getStore("render");
3748
+ const container = document.createElement("div");
3749
+ container.style.position = "fixed";
3750
+ container.style.top = "0";
3751
+ container.style.right = "0";
3752
+ container.style.bottom = "0";
3753
+ container.style.left = "0";
3754
+ container.style.zIndex = "99999";
3755
+ const $$dialogs = $$([]);
3756
+ let activeDialogs = [];
3757
+ function dialogChangedCallback() {
3758
+ if (activeDialogs.length > 0) {
3759
+ if (!container.parentNode) {
3760
+ document.body.appendChild(container);
3761
+ }
4044
3762
  } else {
4045
- store = config.store;
4046
- options = config.options;
3763
+ if (container.parentNode) {
3764
+ document.body.removeChild(container);
3765
+ }
4047
3766
  }
4048
- const instance = initStore({
4049
- store,
4050
- options,
4051
- appContext,
4052
- elementContext
4053
- });
4054
- instance.setup();
4055
- elementContext.stores.set(store, { store, options, instance });
4056
- instances.push(instance);
4057
3767
  }
4058
- ctx.beforeConnect(() => {
4059
- for (const instance of instances) {
4060
- instance.connect();
4061
- }
3768
+ ctx.observe($$dialogs, (dialogs) => {
3769
+ render.update(() => {
3770
+ let removed = [];
3771
+ let added = [];
3772
+ for (const dialog of activeDialogs) {
3773
+ if (!dialogs.includes(dialog)) {
3774
+ removed.push(dialog);
3775
+ }
3776
+ }
3777
+ for (const dialog of dialogs) {
3778
+ if (!activeDialogs.includes(dialog)) {
3779
+ added.push(dialog);
3780
+ }
3781
+ }
3782
+ for (const dialog of removed) {
3783
+ if (dialog.transitionOutCallback) {
3784
+ dialog.transitionOutCallback().then(() => {
3785
+ dialog.instance.disconnect();
3786
+ activeDialogs.splice(activeDialogs.indexOf(dialog), 1);
3787
+ dialogChangedCallback();
3788
+ });
3789
+ } else {
3790
+ dialog.instance.disconnect();
3791
+ activeDialogs.splice(activeDialogs.indexOf(dialog), 1);
3792
+ }
3793
+ }
3794
+ for (const dialog of added) {
3795
+ dialog.instance.connect(container);
3796
+ if (dialog.transitionInCallback) {
3797
+ dialog.transitionInCallback();
3798
+ }
3799
+ activeDialogs.push(dialog);
3800
+ }
3801
+ dialogChangedCallback();
3802
+ });
4062
3803
  });
4063
3804
  ctx.onDisconnected(() => {
4064
- for (const instance of instances) {
4065
- instance.disconnect();
3805
+ if (container.parentNode) {
3806
+ document.body.removeChild(container);
4066
3807
  }
4067
3808
  });
4068
- return ctx.outlet();
3809
+ function open(view, props) {
3810
+ const $$open = $$(true);
3811
+ let dialog;
3812
+ let transitionInCallback;
3813
+ let transitionOutCallback;
3814
+ let instance = initView({
3815
+ view,
3816
+ appContext,
3817
+ elementContext,
3818
+ props: {
3819
+ ...props,
3820
+ $$open,
3821
+ transitionIn: (callback) => {
3822
+ transitionInCallback = callback;
3823
+ },
3824
+ transitionOut: (callback) => {
3825
+ transitionOutCallback = callback;
3826
+ }
3827
+ }
3828
+ });
3829
+ dialog = {
3830
+ instance,
3831
+ // These must be getters because the fns passed to props aren't called until before connect.
3832
+ get transitionInCallback() {
3833
+ return transitionInCallback;
3834
+ },
3835
+ get transitionOutCallback() {
3836
+ return transitionOutCallback;
3837
+ }
3838
+ };
3839
+ $$dialogs.update((current) => {
3840
+ return [...current, dialog];
3841
+ });
3842
+ const stopObserver = observe($$open, (value) => {
3843
+ if (!value) {
3844
+ closeDialog();
3845
+ }
3846
+ });
3847
+ function closeDialog() {
3848
+ $$dialogs.update((current) => {
3849
+ return current.filter((x) => x !== dialog);
3850
+ });
3851
+ dialog = void 0;
3852
+ stopObserver();
3853
+ }
3854
+ return closeDialog;
3855
+ }
3856
+ return {
3857
+ open
3858
+ };
4069
3859
  }
4070
3860
  export {
3861
+ $,
3862
+ $$,
3863
+ App,
3864
+ DialogStore,
4071
3865
  Fragment,
3866
+ HTTPStore,
3867
+ LanguageStore,
3868
+ RouterStore,
4072
3869
  StoreScope,
4073
- computed,
4074
3870
  cond,
4075
3871
  isReadable,
4076
3872
  isWritable,
4077
3873
  m,
4078
- makeApp,
4079
3874
  observe,
4080
3875
  portal,
4081
- proxy,
4082
- readable,
4083
3876
  repeat,
4084
- spring,
4085
- unwrap,
4086
- writable
3877
+ unwrap
4087
3878
  };
4088
3879
  //# sourceMappingURL=index.js.map