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