@occluze/tether 0.8.1 → 0.9.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.
Files changed (73) hide show
  1. package/README.md +5 -5
  2. package/dist/esm/astro.js +1 -1
  3. package/dist/esm/chunks/account-FKLGF264.js +1 -0
  4. package/dist/esm/chunks/{account-pages-XR6QZ3DX.js → account-pages-PNORWX72.js} +1 -1
  5. package/dist/esm/chunks/analytics-V24XX2J7.js +1 -0
  6. package/dist/esm/chunks/{chunk-FPJGQ7Z5.js → chunk-2IHA3PVC.js} +1 -1
  7. package/dist/esm/chunks/chunk-2LYWZ72A.js +1 -0
  8. package/dist/esm/chunks/{chunk-A2PCQK2F.js → chunk-2NV5PSJ2.js} +1 -1
  9. package/dist/esm/chunks/chunk-2X5U24IC.js +1 -0
  10. package/dist/esm/chunks/chunk-5B36ISJF.js +1 -0
  11. package/dist/esm/chunks/chunk-6WE7XCHH.js +1 -0
  12. package/dist/esm/chunks/chunk-6ZIOAYKF.js +1 -0
  13. package/dist/esm/chunks/{chunk-FM7ZLLVP.js → chunk-CR5IGHCO.js} +1 -1
  14. package/dist/esm/chunks/{chunk-X2JZPXLF.js → chunk-E3JL6WS3.js} +1 -1
  15. package/dist/esm/chunks/{chunk-COQ6ATC6.js → chunk-F5CLIGSC.js} +1 -1
  16. package/dist/esm/chunks/{chunk-HXZSNZOV.js → chunk-GOKJQGLG.js} +1 -1
  17. package/dist/esm/chunks/chunk-RW23CJDZ.js +1 -0
  18. package/dist/esm/chunks/chunk-TW34GYSL.js +1 -0
  19. package/dist/esm/chunks/chunk-VKKKH75V.js +1 -0
  20. package/dist/esm/chunks/{chunk-A3GXCQJ4.js → chunk-YWQWLKFW.js} +1 -1
  21. package/dist/esm/chunks/chunk-YYNBHXQF.js +1 -0
  22. package/dist/esm/chunks/{diagnose-IFRNCEYG.js → diagnose-3RTKEBNW.js} +1 -1
  23. package/dist/esm/chunks/extras-O54VUXK6.js +1 -0
  24. package/dist/esm/chunks/lists-MRTI5RXY.js +1 -0
  25. package/dist/esm/chunks/markets-ui-P7G33T6Y.js +1 -0
  26. package/dist/esm/chunks/search-YSRDFVD7.js +1 -0
  27. package/dist/esm/index.js +1 -1
  28. package/dist/esm/server.js +2 -2
  29. package/dist/tether.account-pages.js +2 -2
  30. package/dist/tether.account-pages.min.js +1 -1
  31. package/dist/tether.account.js +2 -2
  32. package/dist/tether.account.min.js +1 -1
  33. package/dist/tether.analytics.js +2 -2
  34. package/dist/tether.analytics.min.js +1 -1
  35. package/dist/tether.diagnose.js +4 -4
  36. package/dist/tether.diagnose.min.js +2 -2
  37. package/dist/tether.extras.js +1 -1
  38. package/dist/tether.extras.min.js +1 -1
  39. package/dist/tether.js +75 -200
  40. package/dist/tether.lists.js +268 -0
  41. package/dist/tether.lists.min.js +1 -0
  42. package/dist/tether.markets.js +3 -3
  43. package/dist/tether.markets.min.js +1 -1
  44. package/dist/tether.min.js +1 -1
  45. package/dist/tether.min.js.map +4 -4
  46. package/dist/tether.search.js +2 -2
  47. package/dist/tether.search.min.js +1 -1
  48. package/dist/types/src/client.d.ts +6 -6
  49. package/dist/types/src/collection.d.ts +7 -23
  50. package/dist/types/src/config-sections.d.ts +7 -0
  51. package/dist/types/src/config.d.ts +6 -0
  52. package/dist/types/src/index.d.ts +4 -3
  53. package/dist/types/src/lazy.d.ts +1 -0
  54. package/dist/types/src/lists.d.ts +20 -0
  55. package/dist/types/src/loader.d.ts +7 -27
  56. package/dist/types/src/mounting.d.ts +1 -6
  57. package/dist/types/src/product-shape.d.ts +13 -36
  58. package/dist/types/src/server.d.ts +5 -4
  59. package/dist/types/src/shopify-values.d.ts +6 -0
  60. package/dist/types/src/storefront/catalog.d.ts +2 -4
  61. package/dist/types/src/version.d.ts +1 -1
  62. package/package.json +5 -5
  63. package/dist/esm/chunks/account-BS5MCC2R.js +0 -1
  64. package/dist/esm/chunks/analytics-BYN3QHBA.js +0 -1
  65. package/dist/esm/chunks/chunk-2IVIZDT5.js +0 -1
  66. package/dist/esm/chunks/chunk-EGIDFV3T.js +0 -1
  67. package/dist/esm/chunks/chunk-EW3GKRT4.js +0 -1
  68. package/dist/esm/chunks/chunk-GXCCTPDF.js +0 -1
  69. package/dist/esm/chunks/chunk-MJFKAMFM.js +0 -1
  70. package/dist/esm/chunks/chunk-TE5PNI76.js +0 -1
  71. package/dist/esm/chunks/extras-3477M6Y7.js +0 -1
  72. package/dist/esm/chunks/markets-ui-3L2UZ7PK.js +0 -1
  73. package/dist/esm/chunks/search-YR73GIWY.js +0 -1
package/dist/tether.js CHANGED
@@ -240,6 +240,7 @@
240
240
  sellingPlansFirst: 25,
241
241
  collectionFirst: 24,
242
242
  retries: 2,
243
+ requestTimeout: 15e3,
243
244
  revalidate: true,
244
245
  storageKey: "",
245
246
  cartDrawerOpenClass: "is-open",
@@ -304,6 +305,7 @@
304
305
  "sellingPlansFirst",
305
306
  "collectionFirst",
306
307
  "retries",
308
+ "requestTimeout",
307
309
  "maxAge",
308
310
  "lowStock",
309
311
  "searchDelay"
@@ -338,8 +340,11 @@
338
340
  dropped(name, value);
339
341
  }
340
342
  }
343
+ var DELAY_OPTIONS = /* @__PURE__ */ new Set(["requestTimeout", "searchDelay"]);
344
+ var MAX_TIMER_MS = 2147483647;
341
345
  function countOption(name, value) {
342
- if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
346
+ const maximum = DELAY_OPTIONS.has(name) ? MAX_TIMER_MS : Number.POSITIVE_INFINITY;
347
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= maximum) return value;
343
348
  return invalidOption(name, value, DEFAULTS[name]);
344
349
  }
345
350
  function switchOption(name, value) {
@@ -545,13 +550,27 @@
545
550
  const unclear = res.ok && (serverError || operation === "mutation" && data == null);
546
551
  throw graphQLFailure(res, errors, conf, unclear ? "SERVER_ERROR" : void 0);
547
552
  }
548
- async function attempt(conf, query2, variables, isData, retry, operation) {
553
+ async function withTimeout(ms, send2) {
554
+ if (!ms) return send2(null);
555
+ const controller = new AbortController();
556
+ const timer = setTimeout(() => {
557
+ controller.abort(new DOMException("signal timed out", "TimeoutError"));
558
+ }, ms);
559
+ try {
560
+ return await send2(controller.signal);
561
+ } finally {
562
+ clearTimeout(timer);
563
+ }
564
+ }
565
+ async function attempt(conf, query2, variables, isData, retry, operation, signal) {
549
566
  let res;
550
567
  try {
551
568
  res = await (conf.fetch ?? fetch)(endpoint(conf), {
552
569
  method: "POST",
553
570
  headers: { "Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": conf.token },
554
- body: JSON.stringify({ query: query2, variables })
571
+ body: JSON.stringify({ query: query2, variables }),
572
+ // A `fetch` given to the server helpers has to honour the signal, as the platform's does.
573
+ signal
555
574
  });
556
575
  } catch (error) {
557
576
  return networkFailure(error, retry);
@@ -588,7 +607,10 @@
588
607
  const access = { inventory: !storesWithoutInventory.has(store) };
589
608
  const sent = queryText(conf, query2, access, { context, operation });
590
609
  const retry = retries < conf.retries ? retryWhileLeft : "none";
591
- const outcome = await attempt(conf, sent, variables, isData, retry, operation);
610
+ const outcome = await withTimeout(
611
+ conf.requestTimeout,
612
+ (signal) => attempt(conf, sent, variables, isData, retry, operation, signal)
613
+ );
592
614
  switch (outcome.kind) {
593
615
  case "data":
594
616
  return outcome.data;
@@ -598,9 +620,9 @@
598
620
  storesWithoutInventory.add(store);
599
621
  break;
600
622
  case "retry":
623
+ await sleep(outcome.backoffMs * 2 ** retries);
601
624
  retries += 1;
602
625
  log("retry", retries);
603
- await sleep(outcome.backoffMs * 2 ** retries);
604
626
  break;
605
627
  }
606
628
  }
@@ -1369,6 +1391,13 @@
1369
1391
  };
1370
1392
  var isProductShape = (value) => field(value, "id") !== "" && field(value, "handle") !== "" && matches(value, PRODUCT);
1371
1393
  var PRODUCT_NODE = (node) => isRecord(node) && (node["id"] === void 0 || matches(node, PRODUCT));
1394
+ var isCollectionAnswer = shaped({
1395
+ collection: orNull({ handle: "string", products: PRODUCT_CONNECTION })
1396
+ });
1397
+ var isSearchAnswer = shaped({ products: PRODUCT_CONNECTION });
1398
+ var isRecommendationsAnswer = shaped({
1399
+ productRecommendations: orNull([PRODUCT])
1400
+ });
1372
1401
 
1373
1402
  // src/cart/cart-shape.ts
1374
1403
  var LINE = {
@@ -1623,64 +1652,6 @@
1623
1652
  if (normalized) productCache.delete(normalized);
1624
1653
  return getProduct(key);
1625
1654
  }
1626
- var isCollectionAnswer = shaped({
1627
- collection: orNull({ handle: "string", products: PRODUCT_CONNECTION })
1628
- });
1629
- var isSearchAnswer = shaped({ products: PRODUCT_CONNECTION });
1630
- var isRecommendationsAnswer = shaped({
1631
- productRecommendations: orNull([PRODUCT])
1632
- });
1633
- var COLLECTION_QUERY = `query TetherCollection($handle:String!,$first:Int!,$after:String,$sortKey:ProductCollectionSortKeys,$reverse:Boolean,$filters:[ProductFilter!]){collection(handle:$handle){${collectionFields()} products(first:$first,after:$after,sortKey:$sortKey,reverse:$reverse,filters:$filters){nodes{...TetherProduct}pageInfo{hasNextPage endCursor}}}}`;
1634
- var SEARCH_QUERY = `query TetherSearch($first:Int!,$after:String,$query:String,$sortKey:ProductSortKeys,$reverse:Boolean){products(first:$first,after:$after,query:$query,sortKey:$sortKey,reverse:$reverse){nodes{...TetherProduct}pageInfo{hasNextPage endCursor}}}`;
1635
- async function getProducts({
1636
- collection,
1637
- query: query2,
1638
- first = cfg.collectionFirst,
1639
- after = null,
1640
- sortKey,
1641
- reverse = false,
1642
- filters
1643
- } = {}) {
1644
- if (collection) {
1645
- const data2 = await gql(
1646
- withProductFragment(COLLECTION_QUERY),
1647
- {
1648
- handle: collection,
1649
- first,
1650
- after,
1651
- sortKey: sortKey || void 0,
1652
- reverse,
1653
- filters: filters ?? void 0
1654
- },
1655
- isCollectionAnswer
1656
- );
1657
- if (!data2.collection) throw new TetherError(`Collection not found: ${collection}`, { code: "NOT_FOUND" });
1658
- const { products, ...rest } = data2.collection;
1659
- products.nodes.forEach(primeProduct);
1660
- return { collection: rest, products: products.nodes, pageInfo: products.pageInfo };
1661
- }
1662
- const data = await gql(
1663
- withProductFragment(SEARCH_QUERY),
1664
- { first, after, query: query2 || void 0, sortKey: sortKey || void 0, reverse },
1665
- isSearchAnswer
1666
- );
1667
- data.products.nodes.forEach(primeProduct);
1668
- return { collection: null, products: data.products.nodes, pageInfo: data.products.pageInfo };
1669
- }
1670
- async function getRecommendations(product, intent = "RELATED") {
1671
- const key = normalizeProductKey(typeof product === "object" ? product?.id : product);
1672
- const byId = key?.startsWith("gid://") ?? false;
1673
- const data = await gql(
1674
- withProductFragment(
1675
- `query TetherRecs(${byId ? "$id: ID!" : "$handle: String!"},$intent:ProductRecommendationIntent){productRecommendations(${byId ? "productId: $id" : "productHandle: $handle"},intent:$intent){...TetherProduct}}`
1676
- ),
1677
- byId ? { id: key, intent } : { handle: key, intent },
1678
- isRecommendationsAnswer
1679
- );
1680
- const list = data.productRecommendations ?? [];
1681
- list.forEach(primeProduct);
1682
- return list;
1683
- }
1684
1655
 
1685
1656
  // src/product-render.ts
1686
1657
  var NTH_IMAGE_BIND = /^image:(\d+)$/;
@@ -3217,128 +3188,6 @@
3217
3188
  }
3218
3189
  };
3219
3190
 
3220
- // src/collection.ts
3221
- var URL_QUERY_SOURCE = "url:";
3222
- var listSelector = () => `${sel("collection")},${sel("products")},${sel("recommendations")}`;
3223
- var isElement2 = (target) => target instanceof window.Element;
3224
- var toLimit = (raw) => parseInt(raw ?? "", 10) || void 0;
3225
- var toIntent = (raw) => raw === "COMPLEMENTARY" ? "COMPLEMENTARY" : "RELATED";
3226
- var ListScope = class {
3227
- constructor(root) {
3228
- __publicField(this, "root");
3229
- __publicField(this, "products", []);
3230
- __publicField(this, "pageInfo", null);
3231
- __publicField(this, "collection", null);
3232
- /** Counts loads, so only the latest one renders. */
3233
- __publicField(this, "loadToken", 0);
3234
- this.root = root;
3235
- setScopeStatus(root, "loading");
3236
- root.addEventListener("click", (event) => {
3237
- if (!isElement2(event.target)) return;
3238
- const more = event.target.closest(sel("load-more"));
3239
- if (more?.closest(listSelector()) !== root) return;
3240
- event.preventDefault();
3241
- void busy(more, () => this.load(true));
3242
- });
3243
- }
3244
- $(selector) {
3245
- return scoped(this.root, selector, listSelector());
3246
- }
3247
- /** The collection or search query of the list's attributes. */
3248
- options() {
3249
- const { root } = this;
3250
- const query2 = { reverse: get(root, "reverse") === "true" };
3251
- const first = toLimit(get(root, "limit"));
3252
- if (first) query2.first = first;
3253
- const sortKey = get(root, "sort");
3254
- if (sortKey) query2.sortKey = sortKey;
3255
- if (has(root, "collection")) {
3256
- query2.collection = get(root, "collection") ?? "";
3257
- return query2;
3258
- }
3259
- const raw = get(root, "products") ?? "";
3260
- query2.query = raw.startsWith(URL_QUERY_SOURCE) ? new URL(window.location.href).searchParams.get(raw.slice(URL_QUERY_SOURCE.length)) || "" : raw;
3261
- return query2;
3262
- }
3263
- /**
3264
- * The product of a recommendations list: a handle, `url:…` (one dynamic product page), or empty inside a product
3265
- * scope for that product.
3266
- *
3267
- * @throws {TetherError} `NOT_FOUND` when there is no product to recommend for.
3268
- */
3269
- async loadRecommendations() {
3270
- let { key } = resolveProductKey(this.root, window.location, "recommendations");
3271
- const scope = this.root.closest(sel("product"));
3272
- if (!key && scope) ({ key } = resolveProductKey(scope));
3273
- if (!key) throw new TetherError("No product for recommendations", { code: "NOT_FOUND" });
3274
- return getRecommendations(key, toIntent(get(this.root, "intent")));
3275
- }
3276
- /** @param more Appends the next page instead of replacing the products. */
3277
- async load(more = false) {
3278
- this.loadToken += 1;
3279
- const token = this.loadToken;
3280
- const stale2 = () => token !== this.loadToken;
3281
- try {
3282
- if (has(this.root, "recommendations")) {
3283
- const recommendations = await this.loadRecommendations();
3284
- if (stale2()) return;
3285
- const limit = toLimit(get(this.root, "limit"));
3286
- this.products = limit ? recommendations.slice(0, limit) : recommendations;
3287
- } else {
3288
- const page = await getProducts({ ...this.options(), after: more ? this.pageInfo?.endCursor ?? null : null });
3289
- if (stale2()) return;
3290
- this.collection = page.collection;
3291
- this.pageInfo = page.pageInfo;
3292
- this.products = more ? [...this.products, ...page.products] : page.products;
3293
- }
3294
- this.render();
3295
- setScopeStatus(this.root, "ready");
3296
- emit("list-ready", { products: this.products, collection: this.collection }, this.root);
3297
- } catch (error) {
3298
- if (stale2()) return;
3299
- if (!more) setScopeStatus(this.root, isNotFound(error) ? "not-found" : "error", error);
3300
- if (!(isNotFound(error) && has(this.root, "recommendations"))) reportError(error, "list load failed");
3301
- emit("error", { error, context: "list" }, this.root);
3302
- }
3303
- }
3304
- render() {
3305
- const { collection } = this;
3306
- this.$(sel("collection-bind")).forEach((el) => {
3307
- switch (get(el, "collection-bind") ?? "") {
3308
- case "title":
3309
- setText(el, collection?.title ?? "");
3310
- break;
3311
- case "description":
3312
- setStoreHtml(el, collection?.descriptionHtml);
3313
- break;
3314
- case "image":
3315
- setImage(el, collection?.image?.url, collection?.image?.altText || collection?.title);
3316
- break;
3317
- case "count":
3318
- setText(el, this.products.length);
3319
- break;
3320
- }
3321
- });
3322
- this.$(sel("list")).forEach((container) => {
3323
- repeat(container, this.products, (node, product) => {
3324
- node.setAttribute(attr("product"), product.handle);
3325
- node.removeAttribute(attr("state"));
3326
- });
3327
- emit("list-rendered", { container }, this.root);
3328
- });
3329
- const empty = this.products.length === 0;
3330
- this.$(sel("show")).forEach((el) => {
3331
- const key = get(el, "show");
3332
- if (key === "list-empty") show(el, empty);
3333
- if (key === "list-filled") show(el, !empty);
3334
- });
3335
- this.$(sel("load-more")).forEach((el) => {
3336
- show(el, Boolean(this.pageInfo?.hasNextPage));
3337
- });
3338
- notifyLayoutChanged("list");
3339
- }
3340
- };
3341
-
3342
3191
  // src/mounting.ts
3343
3192
  var HYDRATE_SELECTOR = 'script[type="application/json"][data-tether-hydrate]';
3344
3193
  var mountedProducts = /* @__PURE__ */ new WeakSet();
@@ -3578,7 +3427,7 @@
3578
3427
  }
3579
3428
 
3580
3429
  // src/part-integrity.ts
3581
- var PART_INTEGRITY = { "tether.diagnose.js": "sha384-exWttcEw4tQTiJ4OU0r/Y+0T5vVIYIo3kcmieqDPkUUmJQGr2HZzWxZCOmMhsvaY", "tether.markets.js": "sha384-hn9741hCQun49mIj7A2bazMG2wXxTpMDScOQK+8wMeY6KT1p+ovXcKDi3m6BK04C", "tether.search.js": "sha384-ZrByfRsWNOHbbWOocY4WOD2RWe4QI2qQRZ4Eb+fM5v/UHIQ8mlWxbSwrxJt8yLbc", "tether.extras.js": "sha384-lt7I8DOnCx8wwVHQM4ZT39+Jlv95WVED5eVDezUKchNXo4+j1oBXYtxzPFiPyxgm", "tether.account.js": "sha384-UF36poTg1vnXE7Pg+FYFGMJ4qjKhx9mbOug9bqTYhzgqRppOKS9M/1uY5D/cz9KF", "tether.account-pages.js": "sha384-jJYvZswrRw+pGDYrk0FswWcYqGpZkbR6ooxJGFu0lTYkUlVOcsCXuLLLU6M5kO54", "tether.analytics.js": "sha384-10v5pBMzgfF7BuLGQBg3cdbgMk701Uto5rGjhByiRD2jI9+/WF8DFcSWplIcdRNg" };
3430
+ var PART_INTEGRITY = { "tether.diagnose.js": "sha384-6b98ZqhqrOFydwTyklda6rjvyWrgz1OnmffApLQ3+gg6VPSU1Yy3LhR1OT4lprfa", "tether.markets.js": "sha384-f9ZeUfc/tMICwVFPrC44Sa1oxm9c6jTQASJdNhHWDk55thsEVScodDBKSYUiuDgr", "tether.search.js": "sha384-kNQI7aVmvUyNgV2e6+PRy53xmbKiZi2n1hhYmHUDFCGU3BR6SoGpGKVKVbPE0ZtG", "tether.lists.js": "sha384-MfQgl9RjiTn6vr80fQymnFikrsDXh3wcmT8cu858s7z9p/oByQaJUKZo92qhjX1P", "tether.extras.js": "sha384-H4CWsEibHw/99UiBAi366ctyaNZrVMkK6Y1YJYYcGDbNthy5eQdS+RRcrlm1clmQ", "tether.account.js": "sha384-UeOp/wddJlVjmlYcVqqO9hU7ANLYEtY/TtK/y5cFkQ6J+SPBmqI/VReaAh+reCMC", "tether.account-pages.js": "sha384-jBeU+vF/U13v+9LZxg5R8lQb/PDHTXPVvu1EmsdDUrlh7BmIntbZJh2+iT6YykTy", "tether.analytics.js": "sha384-4gJ/lcu+S+sfpEHu64+0m2zbXpvNsyOxKDQchPEIb1SFg3ky1JlXODNSdRoodST9" };
3582
3431
 
3583
3432
  // src/part-registry.ts
3584
3433
  function isTetherInternals(value) {
@@ -3586,7 +3435,7 @@
3586
3435
  }
3587
3436
 
3588
3437
  // src/version.ts
3589
- var VERSION = "0.8.1";
3438
+ var VERSION = "0.9.0";
3590
3439
 
3591
3440
  // src/lazy-iife.ts
3592
3441
  var coreModules = {
@@ -3594,13 +3443,14 @@
3594
3443
  return [cfg, isConfigured, log, MARKET_SOURCES, parseMarkets, reportError, warn];
3595
3444
  },
3596
3445
  get "client.ts"() {
3597
- return [endpoint, gql, orNull, shaped, TetherError];
3446
+ return [endpoint, gql, matches, orNull, shaped, TetherError];
3598
3447
  },
3599
3448
  get "dom.ts"() {
3600
3449
  return [
3601
3450
  ANALYTICS_EVENTS,
3602
3451
  attr,
3603
3452
  attributesFromJson,
3453
+ busy,
3604
3454
  emit,
3605
3455
  formatDate,
3606
3456
  formatMoney2,
@@ -3608,24 +3458,30 @@
3608
3458
  has,
3609
3459
  hasTemplate,
3610
3460
  isOwnEvent,
3461
+ notifyLayoutChanged,
3611
3462
  parseJSONAttr,
3612
3463
  productUrl2,
3613
3464
  qsa,
3614
3465
  repeat,
3466
+ scoped,
3615
3467
  sel,
3616
3468
  setImage,
3469
+ setStoreHtml,
3617
3470
  setText,
3618
3471
  show
3619
3472
  ];
3620
3473
  },
3621
3474
  get "loader.ts"() {
3622
- return [clearProductCache, getProduct];
3475
+ return [clearProductCache, getProduct, normalizeProductKey, primeProduct, withProductFragment];
3476
+ },
3477
+ get "fragments.ts"() {
3478
+ return [collectionFields];
3623
3479
  },
3624
3480
  get "selling-plans.ts"() {
3625
3481
  return [allPlans];
3626
3482
  },
3627
3483
  get "status.ts"() {
3628
- return [boundary, resolveProductKey];
3484
+ return [boundary, isNotFound, resolveProductKey, setScopeStatus];
3629
3485
  },
3630
3486
  get "cart.ts"() {
3631
3487
  return [Cart];
@@ -3688,12 +3544,16 @@
3688
3544
  return;
3689
3545
  }
3690
3546
  parts.delete(name);
3691
- const problem = isTetherInternals(tether) ? "did not register" : "can't register: window.Tether is no longer this Tether script's";
3547
+ const problem = isTetherInternals(tether) ? "did not register" : "can't register: window.Tether is no longer this Tether script's, another script replaced it";
3692
3548
  reject(new Error(`${url2} ${problem}`));
3693
3549
  };
3694
3550
  script.onerror = () => {
3695
3551
  parts.delete(name);
3696
- reject(new Error(`Could not load ${url2}, or it is not the file built with this Tether script`));
3552
+ reject(
3553
+ new Error(
3554
+ `Could not load ${url2}, or it is not the file built with this Tether script. Host the same release's files next to the Tether script or at partsUrl.`
3555
+ )
3556
+ );
3697
3557
  };
3698
3558
  document.head.appendChild(script);
3699
3559
  });
@@ -3719,6 +3579,19 @@
3719
3579
  return loading;
3720
3580
  }
3721
3581
 
3582
+ // src/collection.ts
3583
+ var listSelector = () => `${sel("collection")},${sel("products")},${sel("recommendations")}`;
3584
+ function mountedList(root) {
3585
+ setScopeStatus(root, "loading");
3586
+ return {
3587
+ root,
3588
+ load: () => loadPart("lists").then((part) => part.loadList(root)).catch((error) => {
3589
+ setScopeStatus(root, "error", error);
3590
+ reportError(error, "lists");
3591
+ })
3592
+ };
3593
+ }
3594
+
3722
3595
  // src/account-marks.ts
3723
3596
  var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
3724
3597
  function removeFromBothAreas(key) {
@@ -3781,7 +3654,7 @@
3781
3654
 
3782
3655
  // src/controls/cart-controls.ts
3783
3656
  var lineKeyAttribute = () => `data-${cfg.prefix}-key`;
3784
- var isElement3 = (target) => target instanceof window.Element;
3657
+ var isElement2 = (target) => target instanceof window.Element;
3785
3658
  function lineOf(target) {
3786
3659
  const line = target.closest(`[${lineKeyAttribute()}]`);
3787
3660
  const id = line?.getAttribute(lineKeyAttribute());
@@ -3814,7 +3687,7 @@
3814
3687
  }
3815
3688
  function onClick2(event) {
3816
3689
  const { target } = event;
3817
- if (!isElement3(target)) return;
3690
+ if (!isElement2(target)) return;
3818
3691
  if (target.closest(sel("cart-open"))) {
3819
3692
  event.preventDefault();
3820
3693
  Cart.toggle();
@@ -3851,7 +3724,7 @@
3851
3724
  }
3852
3725
  function onChange2(event) {
3853
3726
  const { target } = event;
3854
- if (!isElement3(target)) return;
3727
+ if (!isElement2(target)) return;
3855
3728
  const found = lineOf(target);
3856
3729
  if (found && target instanceof window.HTMLInputElement && target.matches(sel("line", "quantity"))) {
3857
3730
  showLineBusy(found.line, Cart.updateLine(found.id, parseInt(target.value, 10) || 0), "cart line");
@@ -3877,7 +3750,7 @@
3877
3750
  }
3878
3751
 
3879
3752
  // src/controls/discount.ts
3880
- var isElement4 = (target) => target instanceof window.Element;
3753
+ var isElement3 = (target) => target instanceof window.Element;
3881
3754
  function codeInputOf(form) {
3882
3755
  const input = form.querySelector(sel("discount-input")) ?? form.querySelector("input");
3883
3756
  return input instanceof window.HTMLInputElement ? input : null;
@@ -3901,7 +3774,7 @@
3901
3774
  }
3902
3775
  function onSubmit(event) {
3903
3776
  const { target } = event;
3904
- const form = isElement4(target) ? target.closest(sel("discount-form")) : null;
3777
+ const form = isElement3(target) ? target.closest(sel("discount-form")) : null;
3905
3778
  if (!form) return;
3906
3779
  event.preventDefault();
3907
3780
  const input = codeInputOf(form);
@@ -3911,7 +3784,7 @@
3911
3784
  }
3912
3785
  function onInput(event) {
3913
3786
  const { target } = event;
3914
- if (!isElement4(target)) return;
3787
+ if (!isElement3(target)) return;
3915
3788
  const form = target.closest(sel("discount-form"));
3916
3789
  if (!form) return;
3917
3790
  form.classList.remove("is-invalid");
@@ -3919,7 +3792,7 @@
3919
3792
  }
3920
3793
  function onClick3(event) {
3921
3794
  const { target } = event;
3922
- const remove = isElement4(target) ? target.closest(sel("discount-remove")) : null;
3795
+ const remove = isElement3(target) ? target.closest(sel("discount-remove")) : null;
3923
3796
  if (!remove) return;
3924
3797
  event.preventDefault();
3925
3798
  const code = remove.closest(sel("discount-code"))?.getAttribute(attr("discount-code"));
@@ -4033,7 +3906,7 @@
4033
3906
  if (mountedLists.has(el)) return;
4034
3907
  mountedLists.add(el);
4035
3908
  count++;
4036
- const list = new ListScope(el);
3909
+ const list = mountedList(el);
4037
3910
  liveLists.add(list);
4038
3911
  el.addEventListener(`${cfg.prefix}:list-rendered`, (event) => {
4039
3912
  if (isOwnEvent(event, "list-rendered")) mount(event.detail.container);
@@ -4165,6 +4038,8 @@
4165
4038
  observer.observe(document.body, { childList: true, subtree: true });
4166
4039
  }
4167
4040
  var detectMarket = (markets) => loadPart("markets").then((part) => part.detectMarket(markets ?? cfg.markets));
4041
+ var getProducts = (query2) => loadPart("lists").then((part) => part.getProducts(query2));
4042
+ var getRecommendations = (product, intent) => loadPart("lists").then((part) => part.getRecommendations(product, intent));
4168
4043
  var getLocalization = () => loadPart("markets").then((part) => part.getLocalization());
4169
4044
  var diagnose = (options) => loadPart("diagnose").then((part) => part.diagnose({ ...options, version: VERSION }));
4170
4045
  function on(name, listener) {