@occluze/tether 0.8.1 → 0.9.1

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-LCMJ7QU5.js +1 -0
  4. package/dist/esm/chunks/{account-pages-XR6QZ3DX.js → account-pages-3C5KTPQV.js} +1 -1
  5. package/dist/esm/chunks/analytics-VWSZOX2D.js +1 -0
  6. package/dist/esm/chunks/{chunk-HXZSNZOV.js → chunk-2A2GN3Z7.js} +1 -1
  7. package/dist/esm/chunks/{chunk-A3GXCQJ4.js → chunk-2IGCUTG5.js} +1 -1
  8. package/dist/esm/chunks/{chunk-COQ6ATC6.js → chunk-3UCVDDK6.js} +1 -1
  9. package/dist/esm/chunks/chunk-D3MRWOFI.js +1 -0
  10. package/dist/esm/chunks/chunk-EUCFOBIT.js +1 -0
  11. package/dist/esm/chunks/{chunk-A2PCQK2F.js → chunk-F2BYVA7V.js} +1 -1
  12. package/dist/esm/chunks/chunk-HROQXZYN.js +1 -0
  13. package/dist/esm/chunks/{chunk-X2JZPXLF.js → chunk-MJNPD6LB.js} +1 -1
  14. package/dist/esm/chunks/{chunk-FM7ZLLVP.js → chunk-OURC3H6L.js} +1 -1
  15. package/dist/esm/chunks/chunk-P6DV24RG.js +1 -0
  16. package/dist/esm/chunks/chunk-P73VN3B6.js +1 -0
  17. package/dist/esm/chunks/chunk-S5BUUIB6.js +1 -0
  18. package/dist/esm/chunks/chunk-SOFWDTR7.js +1 -0
  19. package/dist/esm/chunks/{chunk-FPJGQ7Z5.js → chunk-XU27NSXT.js} +1 -1
  20. package/dist/esm/chunks/chunk-YJC3CSEG.js +1 -0
  21. package/dist/esm/chunks/chunk-ZBV2JSHE.js +1 -0
  22. package/dist/esm/chunks/{diagnose-IFRNCEYG.js → diagnose-TE72GNOO.js} +1 -1
  23. package/dist/esm/chunks/extras-7GTMJMOZ.js +1 -0
  24. package/dist/esm/chunks/lists-ZMVRGD6L.js +1 -0
  25. package/dist/esm/chunks/markets-ui-AAQM4PEE.js +1 -0
  26. package/dist/esm/chunks/search-PWF4YZQO.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 +85 -201
  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 +7 -6
  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,16 @@
338
340
  dropped(name, value);
339
341
  }
340
342
  }
343
+ var MAX_TIMER_MS = 2147483647;
344
+ var MAX_RETRIES = 10;
345
+ var COUNT_MAXIMUMS = {
346
+ requestTimeout: MAX_TIMER_MS,
347
+ searchDelay: MAX_TIMER_MS,
348
+ retries: MAX_RETRIES
349
+ };
341
350
  function countOption(name, value) {
342
- if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
351
+ const maximum = COUNT_MAXIMUMS[name] ?? Number.POSITIVE_INFINITY;
352
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= maximum) return value;
343
353
  return invalidOption(name, value, DEFAULTS[name]);
344
354
  }
345
355
  function switchOption(name, value) {
@@ -486,6 +496,7 @@
486
496
  }
487
497
  var RETRY_BACKOFF_MS = 300;
488
498
  var THROTTLE_BACKOFF_MS = 500;
499
+ var MAX_BACKOFF_MS = 3e4;
489
500
  var SECURITY_REJECTION = 430;
490
501
  var MS_PER_SECOND = 1e3;
491
502
  var INVENTORY_FIELD = "quantityAvailable";
@@ -545,13 +556,27 @@
545
556
  const unclear = res.ok && (serverError || operation === "mutation" && data == null);
546
557
  throw graphQLFailure(res, errors, conf, unclear ? "SERVER_ERROR" : void 0);
547
558
  }
548
- async function attempt(conf, query2, variables, isData, retry, operation) {
559
+ async function withTimeout(ms, send2) {
560
+ if (!ms) return send2(null);
561
+ const controller = new AbortController();
562
+ const timer = setTimeout(() => {
563
+ controller.abort(new DOMException("signal timed out", "TimeoutError"));
564
+ }, ms);
565
+ try {
566
+ return await send2(controller.signal);
567
+ } finally {
568
+ clearTimeout(timer);
569
+ }
570
+ }
571
+ async function attempt(conf, query2, variables, isData, retry, operation, signal) {
549
572
  let res;
550
573
  try {
551
574
  res = await (conf.fetch ?? fetch)(endpoint(conf), {
552
575
  method: "POST",
553
576
  headers: { "Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": conf.token },
554
- body: JSON.stringify({ query: query2, variables })
577
+ body: JSON.stringify({ query: query2, variables }),
578
+ // A `fetch` given to the server helpers has to honour the signal, as the platform's does.
579
+ signal
555
580
  });
556
581
  } catch (error) {
557
582
  return networkFailure(error, retry);
@@ -588,7 +613,10 @@
588
613
  const access = { inventory: !storesWithoutInventory.has(store) };
589
614
  const sent = queryText(conf, query2, access, { context, operation });
590
615
  const retry = retries < conf.retries ? retryWhileLeft : "none";
591
- const outcome = await attempt(conf, sent, variables, isData, retry, operation);
616
+ const outcome = await withTimeout(
617
+ conf.requestTimeout,
618
+ (signal) => attempt(conf, sent, variables, isData, retry, operation, signal)
619
+ );
592
620
  switch (outcome.kind) {
593
621
  case "data":
594
622
  return outcome.data;
@@ -598,9 +626,9 @@
598
626
  storesWithoutInventory.add(store);
599
627
  break;
600
628
  case "retry":
629
+ await sleep(Math.min(outcome.backoffMs * 2 ** retries, MAX_BACKOFF_MS));
601
630
  retries += 1;
602
631
  log("retry", retries);
603
- await sleep(outcome.backoffMs * 2 ** retries);
604
632
  break;
605
633
  }
606
634
  }
@@ -805,6 +833,7 @@
805
833
  if (cleaned === "") element.removeAttribute("style");
806
834
  else element.setAttribute("style", cleaned);
807
835
  }
836
+ var SAME_TAB_TARGETS = /* @__PURE__ */ new Set(["", "_self", "_parent", "_top"]);
808
837
  function hardenEmbedsAndLinks(element) {
809
838
  if (element.localName === "iframe") {
810
839
  const allow = allowedFeatures(element.getAttribute("allow") ?? "");
@@ -814,7 +843,9 @@
814
843
  element.setAttribute("referrerpolicy", IFRAME_REFERRER_POLICY);
815
844
  return;
816
845
  }
817
- if (element.localName !== "a" || element.getAttribute("target")?.toLowerCase() !== "_blank") return;
846
+ if (element.localName !== "a") return;
847
+ const target = element.getAttribute("target");
848
+ if (target === null || SAME_TAB_TARGETS.has(target.toLowerCase())) return;
818
849
  const rel = new Set((element.getAttribute("rel") ?? "").split(/\s+/).filter(Boolean));
819
850
  rel.add("noopener");
820
851
  rel.add("noreferrer");
@@ -1369,6 +1400,13 @@
1369
1400
  };
1370
1401
  var isProductShape = (value) => field(value, "id") !== "" && field(value, "handle") !== "" && matches(value, PRODUCT);
1371
1402
  var PRODUCT_NODE = (node) => isRecord(node) && (node["id"] === void 0 || matches(node, PRODUCT));
1403
+ var isCollectionAnswer = shaped({
1404
+ collection: orNull({ handle: "string", products: PRODUCT_CONNECTION })
1405
+ });
1406
+ var isSearchAnswer = shaped({ products: PRODUCT_CONNECTION });
1407
+ var isRecommendationsAnswer = shaped({
1408
+ productRecommendations: orNull([PRODUCT])
1409
+ });
1372
1410
 
1373
1411
  // src/cart/cart-shape.ts
1374
1412
  var LINE = {
@@ -1623,64 +1661,6 @@
1623
1661
  if (normalized) productCache.delete(normalized);
1624
1662
  return getProduct(key);
1625
1663
  }
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
1664
 
1685
1665
  // src/product-render.ts
1686
1666
  var NTH_IMAGE_BIND = /^image:(\d+)$/;
@@ -3217,128 +3197,6 @@
3217
3197
  }
3218
3198
  };
3219
3199
 
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
3200
  // src/mounting.ts
3343
3201
  var HYDRATE_SELECTOR = 'script[type="application/json"][data-tether-hydrate]';
3344
3202
  var mountedProducts = /* @__PURE__ */ new WeakSet();
@@ -3578,7 +3436,7 @@
3578
3436
  }
3579
3437
 
3580
3438
  // 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" };
3439
+ 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
3440
 
3583
3441
  // src/part-registry.ts
3584
3442
  function isTetherInternals(value) {
@@ -3586,7 +3444,7 @@
3586
3444
  }
3587
3445
 
3588
3446
  // src/version.ts
3589
- var VERSION = "0.8.1";
3447
+ var VERSION = "0.9.1";
3590
3448
 
3591
3449
  // src/lazy-iife.ts
3592
3450
  var coreModules = {
@@ -3594,13 +3452,14 @@
3594
3452
  return [cfg, isConfigured, log, MARKET_SOURCES, parseMarkets, reportError, warn];
3595
3453
  },
3596
3454
  get "client.ts"() {
3597
- return [endpoint, gql, orNull, shaped, TetherError];
3455
+ return [endpoint, gql, matches, orNull, shaped, TetherError];
3598
3456
  },
3599
3457
  get "dom.ts"() {
3600
3458
  return [
3601
3459
  ANALYTICS_EVENTS,
3602
3460
  attr,
3603
3461
  attributesFromJson,
3462
+ busy,
3604
3463
  emit,
3605
3464
  formatDate,
3606
3465
  formatMoney2,
@@ -3608,24 +3467,30 @@
3608
3467
  has,
3609
3468
  hasTemplate,
3610
3469
  isOwnEvent,
3470
+ notifyLayoutChanged,
3611
3471
  parseJSONAttr,
3612
3472
  productUrl2,
3613
3473
  qsa,
3614
3474
  repeat,
3475
+ scoped,
3615
3476
  sel,
3616
3477
  setImage,
3478
+ setStoreHtml,
3617
3479
  setText,
3618
3480
  show
3619
3481
  ];
3620
3482
  },
3621
3483
  get "loader.ts"() {
3622
- return [clearProductCache, getProduct];
3484
+ return [clearProductCache, getProduct, normalizeProductKey, primeProduct, withProductFragment];
3485
+ },
3486
+ get "fragments.ts"() {
3487
+ return [collectionFields];
3623
3488
  },
3624
3489
  get "selling-plans.ts"() {
3625
3490
  return [allPlans];
3626
3491
  },
3627
3492
  get "status.ts"() {
3628
- return [boundary, resolveProductKey];
3493
+ return [boundary, isNotFound, resolveProductKey, setScopeStatus];
3629
3494
  },
3630
3495
  get "cart.ts"() {
3631
3496
  return [Cart];
@@ -3688,12 +3553,16 @@
3688
3553
  return;
3689
3554
  }
3690
3555
  parts.delete(name);
3691
- const problem = isTetherInternals(tether) ? "did not register" : "can't register: window.Tether is no longer this Tether script's";
3556
+ const problem = isTetherInternals(tether) ? "did not register" : "can't register: window.Tether is no longer this Tether script's, another script replaced it";
3692
3557
  reject(new Error(`${url2} ${problem}`));
3693
3558
  };
3694
3559
  script.onerror = () => {
3695
3560
  parts.delete(name);
3696
- reject(new Error(`Could not load ${url2}, or it is not the file built with this Tether script`));
3561
+ reject(
3562
+ new Error(
3563
+ `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.`
3564
+ )
3565
+ );
3697
3566
  };
3698
3567
  document.head.appendChild(script);
3699
3568
  });
@@ -3719,6 +3588,19 @@
3719
3588
  return loading;
3720
3589
  }
3721
3590
 
3591
+ // src/collection.ts
3592
+ var listSelector = () => `${sel("collection")},${sel("products")},${sel("recommendations")}`;
3593
+ function mountedList(root) {
3594
+ setScopeStatus(root, "loading");
3595
+ return {
3596
+ root,
3597
+ load: () => loadPart("lists").then((part) => part.loadList(root)).catch((error) => {
3598
+ setScopeStatus(root, "error", error);
3599
+ reportError(error, "lists");
3600
+ })
3601
+ };
3602
+ }
3603
+
3722
3604
  // src/account-marks.ts
3723
3605
  var hintKey = () => `${cfg.prefix}:account-hint:${cfg.domain}`;
3724
3606
  function removeFromBothAreas(key) {
@@ -3781,7 +3663,7 @@
3781
3663
 
3782
3664
  // src/controls/cart-controls.ts
3783
3665
  var lineKeyAttribute = () => `data-${cfg.prefix}-key`;
3784
- var isElement3 = (target) => target instanceof window.Element;
3666
+ var isElement2 = (target) => target instanceof window.Element;
3785
3667
  function lineOf(target) {
3786
3668
  const line = target.closest(`[${lineKeyAttribute()}]`);
3787
3669
  const id = line?.getAttribute(lineKeyAttribute());
@@ -3814,7 +3696,7 @@
3814
3696
  }
3815
3697
  function onClick2(event) {
3816
3698
  const { target } = event;
3817
- if (!isElement3(target)) return;
3699
+ if (!isElement2(target)) return;
3818
3700
  if (target.closest(sel("cart-open"))) {
3819
3701
  event.preventDefault();
3820
3702
  Cart.toggle();
@@ -3851,7 +3733,7 @@
3851
3733
  }
3852
3734
  function onChange2(event) {
3853
3735
  const { target } = event;
3854
- if (!isElement3(target)) return;
3736
+ if (!isElement2(target)) return;
3855
3737
  const found = lineOf(target);
3856
3738
  if (found && target instanceof window.HTMLInputElement && target.matches(sel("line", "quantity"))) {
3857
3739
  showLineBusy(found.line, Cart.updateLine(found.id, parseInt(target.value, 10) || 0), "cart line");
@@ -3877,7 +3759,7 @@
3877
3759
  }
3878
3760
 
3879
3761
  // src/controls/discount.ts
3880
- var isElement4 = (target) => target instanceof window.Element;
3762
+ var isElement3 = (target) => target instanceof window.Element;
3881
3763
  function codeInputOf(form) {
3882
3764
  const input = form.querySelector(sel("discount-input")) ?? form.querySelector("input");
3883
3765
  return input instanceof window.HTMLInputElement ? input : null;
@@ -3901,7 +3783,7 @@
3901
3783
  }
3902
3784
  function onSubmit(event) {
3903
3785
  const { target } = event;
3904
- const form = isElement4(target) ? target.closest(sel("discount-form")) : null;
3786
+ const form = isElement3(target) ? target.closest(sel("discount-form")) : null;
3905
3787
  if (!form) return;
3906
3788
  event.preventDefault();
3907
3789
  const input = codeInputOf(form);
@@ -3911,7 +3793,7 @@
3911
3793
  }
3912
3794
  function onInput(event) {
3913
3795
  const { target } = event;
3914
- if (!isElement4(target)) return;
3796
+ if (!isElement3(target)) return;
3915
3797
  const form = target.closest(sel("discount-form"));
3916
3798
  if (!form) return;
3917
3799
  form.classList.remove("is-invalid");
@@ -3919,7 +3801,7 @@
3919
3801
  }
3920
3802
  function onClick3(event) {
3921
3803
  const { target } = event;
3922
- const remove = isElement4(target) ? target.closest(sel("discount-remove")) : null;
3804
+ const remove = isElement3(target) ? target.closest(sel("discount-remove")) : null;
3923
3805
  if (!remove) return;
3924
3806
  event.preventDefault();
3925
3807
  const code = remove.closest(sel("discount-code"))?.getAttribute(attr("discount-code"));
@@ -4033,7 +3915,7 @@
4033
3915
  if (mountedLists.has(el)) return;
4034
3916
  mountedLists.add(el);
4035
3917
  count++;
4036
- const list = new ListScope(el);
3918
+ const list = mountedList(el);
4037
3919
  liveLists.add(list);
4038
3920
  el.addEventListener(`${cfg.prefix}:list-rendered`, (event) => {
4039
3921
  if (isOwnEvent(event, "list-rendered")) mount(event.detail.container);
@@ -4165,6 +4047,8 @@
4165
4047
  observer.observe(document.body, { childList: true, subtree: true });
4166
4048
  }
4167
4049
  var detectMarket = (markets) => loadPart("markets").then((part) => part.detectMarket(markets ?? cfg.markets));
4050
+ var getProducts = (query2) => loadPart("lists").then((part) => part.getProducts(query2));
4051
+ var getRecommendations = (product, intent) => loadPart("lists").then((part) => part.getRecommendations(product, intent));
4168
4052
  var getLocalization = () => loadPart("markets").then((part) => part.getLocalization());
4169
4053
  var diagnose = (options) => loadPart("diagnose").then((part) => part.diagnose({ ...options, version: VERSION }));
4170
4054
  function on(name, listener) {