@occluze/tether 0.6.8 → 0.7.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 (259) hide show
  1. package/README.md +11 -11
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-UIPWJQ3Y.js +1 -0
  4. package/dist/esm/chunks/account-pages-6ROA6Y4V.js +1 -0
  5. package/dist/esm/chunks/analytics-2V632IRV.js +1 -0
  6. package/dist/esm/chunks/chunk-4GCXM764.js +1 -0
  7. package/dist/esm/chunks/chunk-56FCLJJD.js +1 -0
  8. package/dist/esm/chunks/chunk-5Z5O3767.js +1 -0
  9. package/dist/esm/chunks/chunk-6OZJCAGZ.js +1 -0
  10. package/dist/esm/chunks/chunk-A5SYCVQM.js +1 -0
  11. package/dist/esm/chunks/chunk-BA4LQJJQ.js +1 -0
  12. package/dist/esm/chunks/chunk-BFTZR4OB.js +4 -0
  13. package/dist/esm/chunks/chunk-C5JYQCWH.js +1 -0
  14. package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
  15. package/dist/esm/chunks/chunk-MPDWM2NN.js +1 -0
  16. package/dist/esm/chunks/chunk-T44MUL4Y.js +1 -0
  17. package/dist/esm/chunks/chunk-YLUULATY.js +1 -0
  18. package/dist/esm/chunks/diagnose-3FEKRIEK.js +14 -0
  19. package/dist/esm/chunks/extras-6POFBNOP.js +1 -0
  20. package/dist/esm/chunks/markets-ui-FD6NI2OG.js +1 -0
  21. package/dist/esm/chunks/search-UJ73YBQ5.js +1 -0
  22. package/dist/esm/index.js +1 -1
  23. package/dist/esm/server.js +2 -2
  24. package/dist/presets/barba.esm.js +19 -23
  25. package/dist/tether.account-pages.js +320 -365
  26. package/dist/tether.account-pages.min.js +1 -1
  27. package/dist/tether.account.js +242 -491
  28. package/dist/tether.account.min.js +1 -1
  29. package/dist/tether.analytics.js +66 -88
  30. package/dist/tether.analytics.min.js +1 -1
  31. package/dist/tether.diagnose.js +36 -33
  32. package/dist/tether.diagnose.min.js +2 -2
  33. package/dist/tether.extras.js +28 -36
  34. package/dist/tether.extras.min.js +1 -1
  35. package/dist/tether.js +1270 -894
  36. package/dist/tether.markets.js +73 -90
  37. package/dist/tether.markets.min.js +1 -1
  38. package/dist/tether.min.js +0 -0
  39. package/dist/tether.min.js.map +4 -4
  40. package/dist/tether.search.js +44 -68
  41. package/dist/tether.search.min.js +1 -1
  42. package/dist/types/server/src/shared/account-orders.d.ts +44 -0
  43. package/dist/types/server/src/shared/account-routes.d.ts +153 -0
  44. package/dist/types/server/src/shared/api-versions.d.ts +13 -0
  45. package/dist/types/server/src/shared/error-message.d.ts +2 -0
  46. package/dist/types/server/src/shared/json.d.ts +25 -0
  47. package/dist/types/server/src/shared/market-codes.d.ts +16 -0
  48. package/dist/types/src/account/addresses-view.d.ts +5 -0
  49. package/dist/types/src/account/answers.d.ts +40 -0
  50. package/dist/types/src/account/markup.d.ts +19 -0
  51. package/dist/types/src/account/operations.d.ts +41 -0
  52. package/dist/types/src/account/order-lines.d.ts +13 -0
  53. package/dist/types/src/account/order-list.d.ts +12 -0
  54. package/dist/types/src/account/order-page.d.ts +6 -0
  55. package/dist/types/src/account/page-answers.d.ts +182 -0
  56. package/dist/types/src/account/page-events.d.ts +2 -0
  57. package/dist/types/src/account/server-client.d.ts +60 -0
  58. package/dist/types/src/account/subscriptions-view.d.ts +2 -0
  59. package/dist/types/src/account-api.d.ts +18 -0
  60. package/dist/types/src/account-marks.d.ts +15 -0
  61. package/dist/types/src/account-pages.d.ts +8 -0
  62. package/dist/types/src/account.d.ts +64 -0
  63. package/dist/types/src/amounts.d.ts +12 -0
  64. package/dist/types/src/analytics-shopify.d.ts +41 -0
  65. package/dist/types/src/analytics.d.ts +2 -0
  66. package/dist/types/src/api-version.d.ts +28 -0
  67. package/dist/types/src/astro.d.ts +42 -0
  68. package/dist/types/src/cart/cart-shape.d.ts +43 -0
  69. package/dist/types/src/cart/checkout.d.ts +16 -0
  70. package/dist/types/src/cart/drawer.d.ts +18 -0
  71. package/dist/types/src/cart/mutations.d.ts +79 -0
  72. package/dist/types/src/cart/render.d.ts +17 -0
  73. package/dist/types/src/cart/store.d.ts +182 -0
  74. package/dist/types/src/cart.d.ts +106 -0
  75. package/dist/types/src/client.d.ts +100 -0
  76. package/dist/types/src/collection.d.ts +25 -0
  77. package/dist/types/src/config-sections.d.ts +40 -0
  78. package/dist/types/src/config.d.ts +185 -0
  79. package/dist/types/src/controls/cart-controls.d.ts +2 -0
  80. package/dist/types/src/controls/discount.d.ts +2 -0
  81. package/dist/types/src/controls/extras.d.ts +5 -0
  82. package/dist/types/src/controls/navigation.d.ts +12 -0
  83. package/dist/types/src/diagnose-markup.d.ts +19 -0
  84. package/dist/types/src/diagnose.d.ts +60 -0
  85. package/dist/types/src/dom.d.ts +75 -0
  86. package/dist/types/src/extras.d.ts +21 -0
  87. package/dist/types/src/fragments.d.ts +158 -0
  88. package/dist/types/src/globals.d.ts +15 -0
  89. package/dist/types/src/images.d.ts +20 -0
  90. package/dist/types/src/index.d.ts +167 -0
  91. package/dist/types/src/initial.d.ts +56 -0
  92. package/dist/types/src/lazy.d.ts +28 -0
  93. package/dist/types/src/loader.d.ts +84 -0
  94. package/dist/types/src/market-codes.d.ts +6 -0
  95. package/dist/types/src/markets-ui.d.ts +32 -0
  96. package/dist/types/src/markets.d.ts +72 -0
  97. package/dist/types/src/markup.d.ts +131 -0
  98. package/dist/types/src/mounting.d.ts +46 -0
  99. package/dist/types/src/page-market.d.ts +27 -0
  100. package/dist/types/src/presets/barba.d.ts +56 -0
  101. package/dist/types/src/product-events.d.ts +2 -0
  102. package/dist/types/src/product-render.d.ts +17 -0
  103. package/dist/types/src/product-shape.d.ts +88 -0
  104. package/dist/types/src/product-state.d.ts +110 -0
  105. package/dist/types/src/product.d.ts +66 -0
  106. package/dist/types/src/repeaters.d.ts +16 -0
  107. package/dist/types/src/search.d.ts +33 -0
  108. package/dist/types/src/selling-plans.d.ts +64 -0
  109. package/dist/types/src/server.d.ts +88 -0
  110. package/dist/types/src/shopify-values.d.ts +46 -0
  111. package/dist/types/src/status.d.ts +33 -0
  112. package/dist/types/src/storage.d.ts +12 -0
  113. package/dist/types/src/store-html.d.ts +39 -0
  114. package/dist/types/src/storefront/catalog.d.ts +38 -0
  115. package/dist/types/src/storefront/json-ld.d.ts +20 -0
  116. package/dist/types/src/variant-render.d.ts +14 -0
  117. package/package.json +24 -23
  118. package/src/account/addresses-view.ts +58 -0
  119. package/src/account/answers.ts +79 -0
  120. package/src/account/markup.ts +36 -0
  121. package/src/account/operations.ts +145 -0
  122. package/src/account/{order-lines.js → order-lines.ts} +20 -41
  123. package/src/account/order-list.ts +107 -0
  124. package/src/account/order-page.ts +137 -0
  125. package/src/account/page-answers.ts +317 -0
  126. package/src/account/{page-events.js → page-events.ts} +55 -90
  127. package/src/account/server-client.ts +183 -0
  128. package/src/account/{subscriptions-view.js → subscriptions-view.ts} +25 -68
  129. package/src/account-api.ts +98 -0
  130. package/src/account-marks.ts +35 -0
  131. package/src/{account-pages.js → account-pages.ts} +29 -37
  132. package/src/account.ts +284 -0
  133. package/src/amounts.ts +64 -0
  134. package/src/{analytics-shopify.js → analytics-shopify.ts} +112 -107
  135. package/src/analytics.ts +315 -0
  136. package/src/{api-version.js → api-version.ts} +25 -31
  137. package/src/{astro.js → astro.ts} +110 -104
  138. package/src/cart/cart-shape.ts +25 -0
  139. package/src/cart/checkout.ts +101 -0
  140. package/src/cart/{drawer.js → drawer.ts} +18 -31
  141. package/src/cart/mutations.ts +237 -0
  142. package/src/cart/{render.js → render.ts} +68 -96
  143. package/src/cart/store.ts +279 -0
  144. package/src/{cart.js → cart.ts} +82 -128
  145. package/src/client.ts +391 -0
  146. package/src/{collection.js → collection.ts} +30 -38
  147. package/src/config-sections.ts +284 -0
  148. package/src/config.ts +480 -0
  149. package/src/controls/{cart-controls.js → cart-controls.ts} +22 -54
  150. package/src/controls/{discount.js → discount.ts} +13 -34
  151. package/src/controls/{extras.js → extras.ts} +15 -24
  152. package/src/controls/{navigation.js → navigation.ts} +24 -30
  153. package/src/{diagnose-markup.js → diagnose-markup.ts} +37 -72
  154. package/src/{diagnose.js → diagnose.ts} +95 -96
  155. package/src/dom.ts +268 -0
  156. package/src/{extras.js → extras.ts} +43 -52
  157. package/src/fragments.ts +261 -0
  158. package/src/globals.d.ts +15 -0
  159. package/src/{images.js → images.ts} +30 -43
  160. package/src/index.ts +415 -0
  161. package/src/initial.ts +101 -0
  162. package/src/lazy-iife.ts +197 -0
  163. package/src/lazy.ts +42 -0
  164. package/src/{loader.js → loader.ts} +149 -123
  165. package/src/market-codes.ts +6 -0
  166. package/src/{markets-ui.js → markets-ui.ts} +91 -68
  167. package/src/markets.ts +246 -0
  168. package/src/markup.ts +127 -0
  169. package/src/{mounting.js → mounting.ts} +88 -101
  170. package/src/{page-market.js → page-market.ts} +32 -38
  171. package/src/part-integrity.ts +7 -0
  172. package/src/parts/account-pages.ts +7 -0
  173. package/src/parts/account.ts +7 -0
  174. package/src/parts/analytics.ts +7 -0
  175. package/src/parts/diagnose.ts +7 -0
  176. package/src/parts/extras.ts +7 -0
  177. package/src/parts/markets.ts +7 -0
  178. package/src/parts/search.ts +7 -0
  179. package/src/presets/barba.ts +194 -0
  180. package/src/{product-events.js → product-events.ts} +33 -48
  181. package/src/{product-render.js → product-render.ts} +38 -77
  182. package/src/product-shape.ts +49 -0
  183. package/src/product-state.ts +318 -0
  184. package/src/{product.js → product.ts} +90 -126
  185. package/src/{repeaters.js → repeaters.ts} +40 -52
  186. package/src/{search.js → search.ts} +85 -103
  187. package/src/selling-plans.ts +145 -0
  188. package/src/server.ts +211 -0
  189. package/src/{shopify-values.js → shopify-values.ts} +24 -22
  190. package/src/{status.js → status.ts} +34 -54
  191. package/src/storage.ts +34 -0
  192. package/src/store-html.ts +359 -0
  193. package/src/storefront/catalog.ts +196 -0
  194. package/src/storefront/{json-ld.js → json-ld.ts} +45 -68
  195. package/src/{variant-render.js → variant-render.ts} +24 -50
  196. package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
  197. package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
  198. package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
  199. package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
  200. package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
  201. package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
  202. package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
  203. package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
  204. package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
  205. package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
  206. package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
  207. package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
  208. package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
  209. package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
  210. package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
  211. package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
  212. package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
  213. package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
  214. package/dist/esm/chunks/search-OU75RNQL.js +0 -1
  215. package/src/account/addresses-view.js +0 -92
  216. package/src/account/browser-session.js +0 -359
  217. package/src/account/markup.js +0 -48
  218. package/src/account/operations.js +0 -180
  219. package/src/account/order-list.js +0 -91
  220. package/src/account/order-page.js +0 -192
  221. package/src/account/server-client.js +0 -172
  222. package/src/account/shared.js +0 -111
  223. package/src/account/shopify-auth.js +0 -150
  224. package/src/account-api.js +0 -118
  225. package/src/account-links.js +0 -35
  226. package/src/account.js +0 -342
  227. package/src/amounts.js +0 -36
  228. package/src/analytics.js +0 -352
  229. package/src/cart/checkout.js +0 -97
  230. package/src/cart/mutations.js +0 -234
  231. package/src/cart/store.js +0 -326
  232. package/src/client.js +0 -267
  233. package/src/config.js +0 -349
  234. package/src/dom.js +0 -287
  235. package/src/fragments.js +0 -237
  236. package/src/index.js +0 -353
  237. package/src/initial.js +0 -87
  238. package/src/lazy-iife.js +0 -116
  239. package/src/lazy.js +0 -39
  240. package/src/market-codes.js +0 -35
  241. package/src/markets.js +0 -334
  242. package/src/markup.js +0 -64
  243. package/src/parts/account-pages.js +0 -6
  244. package/src/parts/account.js +0 -6
  245. package/src/parts/analytics.js +0 -6
  246. package/src/parts/diagnose.js +0 -6
  247. package/src/parts/extras.js +0 -6
  248. package/src/parts/markets.js +0 -6
  249. package/src/parts/search.js +0 -6
  250. package/src/presets/barba.js +0 -190
  251. package/src/product-state.js +0 -318
  252. package/src/selling-plans.js +0 -154
  253. package/src/server.js +0 -215
  254. package/src/store-html.js +0 -0
  255. package/src/storefront/catalog.js +0 -199
  256. package/types/astro.d.ts +0 -28
  257. package/types/barba.d.ts +0 -16
  258. package/types/index.d.ts +0 -577
  259. package/types/server.d.ts +0 -182
@@ -1,34 +1,42 @@
1
- "use strict";
2
1
  (() => {
3
2
  var __defProp = Object.defineProperty;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __export = (target, all) => {
5
5
  for (var name in all)
6
6
  __defProp(target, name, { get: all[name], enumerable: true });
7
7
  };
8
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
8
9
 
9
- // src/search.js
10
+ // src/search.ts
10
11
  var search_exports = {};
11
12
  __export(search_exports, {
12
13
  mountSearch: () => mountSearch
13
14
  });
14
15
 
15
- // tether-core:config.js
16
- var m = window.Tether.__core["config.js"];
17
- var { DEFAULTS, cfg, configure, isConfigured, log, reportError, resolveConfig, toMetafieldIdentifier, warn } = m;
16
+ // tether-core:config.ts
17
+ var m = window.Tether.__core["config.ts"];
18
+ var [cfg, isConfigured, log, MARKET_SOURCES, parseMarkets, reportError, warn] = m;
18
19
 
19
- // tether-core:client.js
20
- var m2 = window.Tether.__core["client.js"];
21
- var { TetherError, endpoint, gql, request, unwrap, withContext } = m2;
20
+ // tether-core:client.ts
21
+ var m2 = window.Tether.__core["client.ts"];
22
+ var [endpoint, gql, orNull, shaped, TetherError] = m2;
22
23
 
23
- // tether-core:dom.js
24
- var m3 = window.Tether.__core["dom.js"];
25
- var { attr, busy, cdnUrl, emit, formatDate, formatMoney, formatPercent, get, has, hasTemplate, imageUrl, notifyLayoutChanged, parseJSONAttr, productUrl, qsa, repeat, repeatKeyed, sameImage, scoped, sel, setImage, setSelected, setStoreHtml, setText, setTextTracked, show, toAttributes, watchImages } = m3;
24
+ // tether-core:dom.ts
25
+ var m3 = window.Tether.__core["dom.ts"];
26
+ var [ANALYTICS_EVENTS, attr, attributesFromJson, emit, formatDate, formatMoney, get, has, hasTemplate, isOwnEvent, parseJSONAttr, productUrl, qsa, repeat, sel, setImage, setText, show] = m3;
26
27
 
27
- // tether-core:mounting.js
28
- var m4 = window.Tether.__core["mounting.js"];
29
- var { hydrateFromPage, liveLists, liveProducts, mountProduct, mountedLists, mountedProducts, pruneLive, retryFailed, settleMarket, trackMarket, within } = m4;
28
+ // tether-core:mounting.ts
29
+ var m4 = window.Tether.__core["mounting.ts"];
30
+ var [liveLists, liveProducts, mountedProducts, mountProduct, pruneLive, within] = m4;
30
31
 
31
- // src/search.js
32
+ // src/search.ts
33
+ var isSearchAnswer = shaped({
34
+ predictiveSearch: orNull({
35
+ products: [{ priceRange: "object" }],
36
+ collections: ["object"],
37
+ queries: [{ text: "string" }]
38
+ })
39
+ });
32
40
  var QUERY = `query TetherPredictive($q:String!,$limit:Int!){predictiveSearch(query:$q,limit:$limit,limitScope:EACH,types:[PRODUCT,COLLECTION,QUERY]){products{id handle title availableForSale featuredImage{url altText}priceRange{minVariantPrice{amount currencyCode}}}collections{handle title image{url altText}}queries{text}}}`;
33
41
  var DEFAULT_LIMIT = 6;
34
42
  var DEFAULT_MIN_CHARACTERS = 2;
@@ -51,16 +59,23 @@
51
59
  function linkOption(node, href) {
52
60
  const link = linkIn(node);
53
61
  if (!link) return;
54
- link.setAttribute("href", href);
62
+ if (href) link.setAttribute("href", href);
55
63
  link.setAttribute("role", "option");
56
64
  }
57
65
  var SearchBox = class {
58
- /**
59
- * @param {HTMLElement} root
60
- * @param {HTMLInputElement} input
61
- * @param {HTMLElement} results
62
- */
63
66
  constructor(root, input, results) {
67
+ __publicField(this, "root");
68
+ __publicField(this, "input");
69
+ __publicField(this, "results");
70
+ __publicField(this, "limit");
71
+ __publicField(this, "minCharacters");
72
+ __publicField(this, "resultsId");
73
+ /** Counts searches, so only the latest one renders. */
74
+ __publicField(this, "searchNumber", 0);
75
+ __publicField(this, "timer");
76
+ __publicField(this, "lastTerm", "");
77
+ /** Results per term, for typing back and forth. */
78
+ __publicField(this, "cache", /* @__PURE__ */ new Map());
64
79
  this.root = root;
65
80
  this.input = input;
66
81
  this.results = results;
@@ -71,10 +86,6 @@
71
86
  results.id = `${cfg.prefix}-search-${searchBoxCount}`;
72
87
  }
73
88
  this.resultsId = results.id;
74
- this.searchNumber = 0;
75
- this.timer = void 0;
76
- this.lastTerm = "";
77
- this.cache = /* @__PURE__ */ new Map();
78
89
  this.setUpAria();
79
90
  this.listen();
80
91
  }
@@ -102,17 +113,12 @@
102
113
  this.onKeydown(event);
103
114
  });
104
115
  document.addEventListener("click", (event) => {
105
- if (!this.root.contains(
106
- /** @type {Node | null} */
107
- event.target
108
- )) this.open(false);
116
+ if (!this.root.contains(event.target)) this.open(false);
109
117
  });
110
118
  }
111
- /** @param {SearchState} state */
112
119
  setState(state) {
113
120
  this.root.setAttribute(attr("search-state"), state);
114
121
  }
115
- /** @param {boolean} open */
116
122
  open(open) {
117
123
  this.root.classList.toggle("is-open", open);
118
124
  this.results.hidden = !open;
@@ -122,18 +128,13 @@
122
128
  options() {
123
129
  return qsa(this.results, '[role="option"]');
124
130
  }
125
- /**
126
- * @param {string} term
127
- * @returns {Promise<SearchResults | null>}
128
- */
129
131
  async fetchResults(term) {
130
132
  const cached = this.cache.get(term);
131
133
  if (cached) return cached;
132
- const data = await gql(QUERY, { q: term, limit: this.limit });
134
+ const data = await gql(QUERY, { q: term, limit: this.limit }, isSearchAnswer);
133
135
  this.cache.set(term, data.predictiveSearch);
134
136
  return data.predictiveSearch;
135
137
  }
136
- /** @param {string} term */
137
138
  async search(term) {
138
139
  this.searchNumber += 1;
139
140
  const searchNumber = this.searchNumber;
@@ -155,28 +156,18 @@
155
156
  reportError(error, "search");
156
157
  }
157
158
  }
158
- /**
159
- * @template Item
160
- * @param {string} name The list attribute, e.g. `search-products`.
161
- * @param {readonly Item[]} items
162
- * @param {(node: HTMLElement, item: Item) => void} render
163
- */
159
+ /** @param name The list attribute, e.g. `search-products`. */
164
160
  fillList(name, items, render) {
165
161
  qsa(this.results, sel(name)).forEach((list) => {
166
162
  repeat(list, items, render);
167
163
  show(list, items.length > 0);
168
164
  });
169
165
  }
170
- /** @param {string} text */
171
166
  queryUrl(text) {
172
167
  const action = this.root.getAttribute("action") || DEFAULT_ACTION;
173
168
  const separator = action.includes("?") ? "&" : "?";
174
169
  return `${action}${separator}${this.input.name || "q"}=${encodeURIComponent(text)}`;
175
170
  }
176
- /**
177
- * @param {SearchResults | null} results
178
- * @param {string} term
179
- */
180
171
  render(results, term) {
181
172
  const products = results?.products ?? [];
182
173
  const collections = results?.collections ?? [];
@@ -201,7 +192,6 @@
201
192
  this.open(true);
202
193
  emit("search", { term, products, collections, queries }, this.root);
203
194
  }
204
- /** @param {number} count */
205
195
  showCount(count) {
206
196
  qsa(this.root, sel("show")).forEach((el) => {
207
197
  const condition = get(el, "show");
@@ -213,10 +203,6 @@
213
203
  option.setAttribute("aria-selected", "false");
214
204
  });
215
205
  }
216
- /**
217
- * @param {HTMLElement[]} options
218
- * @param {number} index
219
- */
220
206
  highlight(options, index) {
221
207
  options.forEach((option2, optionIndex) => {
222
208
  option2.setAttribute("aria-selected", String(optionIndex === index));
@@ -226,7 +212,6 @@
226
212
  this.input.setAttribute("aria-activedescendant", option.id);
227
213
  option.scrollIntoView?.({ block: "nearest" });
228
214
  }
229
- /** @param {KeyboardEvent} event */
230
215
  onKeydown(event) {
231
216
  const options = this.options();
232
217
  const current = options.findIndex((option) => option.getAttribute("aria-selected") === "true");
@@ -249,23 +234,14 @@
249
234
  for (const el of within(root, sel("search"))) {
250
235
  if (mounted.has(el)) continue;
251
236
  mounted.add(el);
252
- const input = (
253
- /** @type {HTMLInputElement | null} */
254
- el.querySelector(sel("search-input")) ?? el.querySelector("input")
255
- );
256
- const results = (
257
- /** @type {HTMLElement | null} */
258
- el.querySelector(sel("search-results"))
259
- );
237
+ const input = el.querySelector(sel("search-input")) ?? el.querySelector("input");
238
+ const results = el.querySelector(sel("search-results"));
260
239
  if (input && results) new SearchBox(el, input, results);
261
240
  }
262
241
  }
263
242
 
264
- // src/parts/search.js
265
- var { Tether } = (
266
- /** @type {Window & { Tether?: { __parts: Record<string, unknown> } }} */
267
- window
268
- );
243
+ // src/parts/search.ts
244
+ var { Tether } = window;
269
245
  if (!Tether) throw new Error("tether.search.js runs after the Tether script, which loads it");
270
- Tether.__parts["search"] = search_exports;
246
+ Tether.__parts.search = search_exports;
271
247
  })();
@@ -1 +1 @@
1
- "use strict";(()=>{var N=Object.defineProperty;var P=(s,t)=>{for(var e in t)N(s,e,{get:t[e],enumerable:!0})};var p={};P(p,{mountSearch:()=>Q});var R=window.Tether.__core["config.js"],{DEFAULTS:J,cfg:l,configure:W,isConfigured:z,log:w,reportError:b,resolveConfig:B,toMetafieldIdentifier:G,warn:X}=R;var F=window.Tether.__core["client.js"],{TetherError:tt,endpoint:et,gql:A,request:it,unwrap:st,withContext:rt}=F;var M=window.Tether.__core["dom.js"],{attr:y,busy:at,cdnUrl:ot,emit:T,formatDate:ht,formatMoney:E,formatPercent:ct,get:u,has:lt,hasTemplate:ut,imageUrl:dt,notifyLayoutChanged:mt,parseJSONAttr:ft,productUrl:S,qsa:c,repeat:x,repeatKeyed:pt,sameImage:gt,scoped:wt,sel:o,setImage:L,setSelected:bt,setStoreHtml:At,setText:C,setTextTracked:yt,show:d,toAttributes:Tt,watchImages:Et}=M;var O=window.Tether.__core["mounting.js"],{hydrateFromPage:xt,liveLists:Lt,liveProducts:Ct,mountProduct:It,mountedLists:qt,mountedProducts:Ut,pruneLive:kt,retryFailed:$t,settleMarket:_t,trackMarket:vt,within:I}=O;var j="query TetherPredictive($q:String!,$limit:Int!){predictiveSearch(query:$q,limit:$limit,limitScope:EACH,types:[PRODUCT,COLLECTION,QUERY]){products{id handle title availableForSale featuredImage{url altText}priceRange{minVariantPrice{amount currencyCode}}}collections{handle title image{url altText}}queries{text}}}",V=6,H=2,K="/search",q=new WeakSet,U=0;function k(s,t,e){return parseInt(u(s,t)||"",10)||e}var _=s=>s.tagName==="A"?s:s.querySelector("a");function m(s,t){c(s,o("search-bind")).forEach(e=>{let i=u(e,"search-bind");i==="image"?L(e,t.image?.url,t.image?.altText||t.title):(i==="title"||i==="price"||i==="text")&&t[i]!=null&&C(e,t[i])})}function $(s,t){let e=_(s);e&&(e.setAttribute("href",t),e.setAttribute("role","option"))}var f=class{constructor(t,e,i){this.root=t,this.input=e,this.results=i,this.limit=k(t,"search-limit",V),this.minCharacters=k(t,"search-min",H),i.id||(U+=1,i.id=`${l.prefix}-search-${U}`),this.resultsId=i.id,this.searchNumber=0,this.timer=void 0,this.lastTerm="",this.cache=new Map,this.setUpAria(),this.listen()}setUpAria(){this.input.setAttribute("role","combobox"),this.input.setAttribute("aria-autocomplete","list"),this.input.setAttribute("aria-controls",this.resultsId),this.input.setAttribute("aria-expanded","false"),this.input.setAttribute("autocomplete","off"),this.results.hasAttribute("role")||this.results.setAttribute("role","listbox"),this.results.hidden=!0}listen(){this.input.addEventListener("input",()=>{let t=this.input.value.trim();t!==this.lastTerm&&(this.lastTerm=t,clearTimeout(this.timer),this.timer=setTimeout(()=>void this.search(t),l.searchDelay))}),this.input.addEventListener("focus",()=>{this.input.value.trim().length>=this.minCharacters&&this.options().length&&this.open(!0)}),this.input.addEventListener("keydown",t=>{this.onKeydown(t)}),document.addEventListener("click",t=>{this.root.contains(t.target)||this.open(!1)})}setState(t){this.root.setAttribute(y("search-state"),t)}open(t){this.root.classList.toggle("is-open",t),this.results.hidden=!t,this.input.setAttribute("aria-expanded",String(t)),t||this.input.removeAttribute("aria-activedescendant")}options(){return c(this.results,'[role="option"]')}async fetchResults(t){let e=this.cache.get(t);if(e)return e;let i=await A(j,{q:t,limit:this.limit});return this.cache.set(t,i.predictiveSearch),i.predictiveSearch}async search(t){this.searchNumber+=1;let e=this.searchNumber;if(t.length<this.minCharacters){this.open(!1),this.setState("idle");return}this.setState("loading");try{let i=await this.fetchResults(t);e===this.searchNumber&&this.render(i,t)}catch(i){if(e!==this.searchNumber){w("a superseded search failed",i);return}this.setState("error"),b(i,"search")}}fillList(t,e,i){c(this.results,o(t)).forEach(a=>{x(a,e,i),d(a,e.length>0)})}queryUrl(t){let e=this.root.getAttribute("action")||K,i=e.includes("?")?"&":"?";return`${e}${i}${this.input.name||"q"}=${encodeURIComponent(t)}`}render(t,e){let i=t?.products??[],a=t?.collections??[],h=(t?.queries??[]).filter(n=>n.text.toLowerCase()!==e.toLowerCase());this.fillList("search-products",i,(n,r)=>{let D=E(r.priceRange.minVariantPrice);m(n,{title:r.title,price:D,image:r.featuredImage}),$(n,S(r.handle,r.id)),n.classList.toggle("is-sold-out",!r.availableForSale)}),this.fillList("search-collections",a,(n,r)=>{m(n,{title:r.title,image:r.image}),$(n,`${(l.collectionPath||"/collection").replace(/\/$/,"")}/${r.handle}`)}),this.fillList("search-queries",h,(n,r)=>{m(n,{text:r.text}),_(n)?.setAttribute("href",this.queryUrl(r.text))});let g=i.length+a.length+h.length;this.showCount(g),this.setState(g?"ready":"empty"),this.open(!0),T("search",{term:e,products:i,collections:a,queries:h},this.root)}showCount(t){c(this.root,o("show")).forEach(e=>{let i=u(e,"show");i==="search-empty"&&d(e,t===0),i==="search-results"&&d(e,t>0)}),this.options().forEach((e,i)=>{e.id||(e.id=`${this.resultsId}-${i}`),e.setAttribute("aria-selected","false")})}highlight(t,e){t.forEach((a,h)=>{a.setAttribute("aria-selected",String(h===e))});let i=t[e];i&&(this.input.setAttribute("aria-activedescendant",i.id),i.scrollIntoView?.({block:"nearest"}))}onKeydown(t){let e=this.options(),i=e.findIndex(a=>a.getAttribute("aria-selected")==="true");t.key==="ArrowDown"&&e.length?(t.preventDefault(),this.open(!0),this.highlight(e,(i+1)%e.length)):t.key==="ArrowUp"&&e.length?(t.preventDefault(),this.highlight(e,i<=0?e.length-1:i-1)):t.key==="Enter"&&i>=0?(t.preventDefault(),e[i]?.click()):t.key==="Escape"&&this.open(!1)}};function Q(s=document){for(let t of I(s,o("search"))){if(q.has(t))continue;q.add(t);let e=t.querySelector(o("search-input"))??t.querySelector("input"),i=t.querySelector(o("search-results"));e&&i&&new f(t,e,i)}}var{Tether:v}=window;if(!v)throw new Error("tether.search.js runs after the Tether script, which loads it");v.__parts.search=p;})();
1
+ (()=>{var T=Object.defineProperty;var D=(i,t,e)=>t in i?T(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e;var V=(i,t)=>{for(var e in t)T(i,e,{get:t[e],enumerable:!0})};var a=(i,t,e)=>D(i,typeof t!="symbol"?t+"":t,e);var g={};V(g,{mountSearch:()=>W});var O=window.Tether.__core["config.ts"],[u,X,b,Z,tt,y,et]=O;var F=window.Tether.__core["client.ts"],[it,E,w,A,st]=F;var K=window.Tether.__core["dom.ts"],[at,I,ot,L,lt,x,m,ct,ht,ut,mt,C,h,q,l,M,v,d]=K;var Q=window.Tether.__core["mounting.ts"],[pt,ft,gt,St,Tt,R]=Q;var j=A({predictiveSearch:w({products:[{priceRange:"object"}],collections:["object"],queries:[{text:"string"}]})}),Y="query TetherPredictive($q:String!,$limit:Int!){predictiveSearch(query:$q,limit:$limit,limitScope:EACH,types:[PRODUCT,COLLECTION,QUERY]){products{id handle title availableForSale featuredImage{url altText}priceRange{minVariantPrice{amount currencyCode}}}collections{handle title image{url altText}}queries{text}}}",B=6,J=2,z="/search",N=new WeakSet,P=0;function _(i,t,e){return parseInt(m(i,t)||"",10)||e}var U=i=>i.tagName==="A"?i:i.querySelector("a");function p(i,t){h(i,l("search-bind")).forEach(e=>{let r=m(e,"search-bind");r==="image"?M(e,t.image?.url,t.image?.altText||t.title):(r==="title"||r==="price"||r==="text")&&t[r]!=null&&v(e,t[r])})}function k(i,t){let e=U(i);e&&(t&&e.setAttribute("href",t),e.setAttribute("role","option"))}var f=class{constructor(t,e,r){a(this,"root");a(this,"input");a(this,"results");a(this,"limit");a(this,"minCharacters");a(this,"resultsId");a(this,"searchNumber",0);a(this,"timer");a(this,"lastTerm","");a(this,"cache",new Map);this.root=t,this.input=e,this.results=r,this.limit=_(t,"search-limit",B),this.minCharacters=_(t,"search-min",J),r.id||(P+=1,r.id=`${u.prefix}-search-${P}`),this.resultsId=r.id,this.setUpAria(),this.listen()}setUpAria(){this.input.setAttribute("role","combobox"),this.input.setAttribute("aria-autocomplete","list"),this.input.setAttribute("aria-controls",this.resultsId),this.input.setAttribute("aria-expanded","false"),this.input.setAttribute("autocomplete","off"),this.results.hasAttribute("role")||this.results.setAttribute("role","listbox"),this.results.hidden=!0}listen(){this.input.addEventListener("input",()=>{let t=this.input.value.trim();t!==this.lastTerm&&(this.lastTerm=t,clearTimeout(this.timer),this.timer=setTimeout(()=>void this.search(t),u.searchDelay))}),this.input.addEventListener("focus",()=>{this.input.value.trim().length>=this.minCharacters&&this.options().length&&this.open(!0)}),this.input.addEventListener("keydown",t=>{this.onKeydown(t)}),document.addEventListener("click",t=>{this.root.contains(t.target)||this.open(!1)})}setState(t){this.root.setAttribute(I("search-state"),t)}open(t){this.root.classList.toggle("is-open",t),this.results.hidden=!t,this.input.setAttribute("aria-expanded",String(t)),t||this.input.removeAttribute("aria-activedescendant")}options(){return h(this.results,'[role="option"]')}async fetchResults(t){let e=this.cache.get(t);if(e)return e;let r=await E(Y,{q:t,limit:this.limit},j);return this.cache.set(t,r.predictiveSearch),r.predictiveSearch}async search(t){this.searchNumber+=1;let e=this.searchNumber;if(t.length<this.minCharacters){this.open(!1),this.setState("idle");return}this.setState("loading");try{let r=await this.fetchResults(t);e===this.searchNumber&&this.render(r,t)}catch(r){if(e!==this.searchNumber){b("a superseded search failed",r);return}this.setState("error"),y(r,"search")}}fillList(t,e,r){h(this.results,l(t)).forEach(o=>{q(o,e,r),d(o,e.length>0)})}queryUrl(t){let e=this.root.getAttribute("action")||z,r=e.includes("?")?"&":"?";return`${e}${r}${this.input.name||"q"}=${encodeURIComponent(t)}`}render(t,e){let r=t?.products??[],o=t?.collections??[],c=(t?.queries??[]).filter(n=>n.text.toLowerCase()!==e.toLowerCase());this.fillList("search-products",r,(n,s)=>{let H=x(s.priceRange.minVariantPrice);p(n,{title:s.title,price:H,image:s.featuredImage}),k(n,C(s.handle,s.id)),n.classList.toggle("is-sold-out",!s.availableForSale)}),this.fillList("search-collections",o,(n,s)=>{p(n,{title:s.title,image:s.image}),k(n,`${(u.collectionPath||"/collection").replace(/\/$/,"")}/${s.handle}`)}),this.fillList("search-queries",c,(n,s)=>{p(n,{text:s.text}),U(n)?.setAttribute("href",this.queryUrl(s.text))});let S=r.length+o.length+c.length;this.showCount(S),this.setState(S?"ready":"empty"),this.open(!0),L("search",{term:e,products:r,collections:o,queries:c},this.root)}showCount(t){h(this.root,l("show")).forEach(e=>{let r=m(e,"show");r==="search-empty"&&d(e,t===0),r==="search-results"&&d(e,t>0)}),this.options().forEach((e,r)=>{e.id||(e.id=`${this.resultsId}-${r}`),e.setAttribute("aria-selected","false")})}highlight(t,e){t.forEach((o,c)=>{o.setAttribute("aria-selected",String(c===e))});let r=t[e];r&&(this.input.setAttribute("aria-activedescendant",r.id),r.scrollIntoView?.({block:"nearest"}))}onKeydown(t){let e=this.options(),r=e.findIndex(o=>o.getAttribute("aria-selected")==="true");t.key==="ArrowDown"&&e.length?(t.preventDefault(),this.open(!0),this.highlight(e,(r+1)%e.length)):t.key==="ArrowUp"&&e.length?(t.preventDefault(),this.highlight(e,r<=0?e.length-1:r-1)):t.key==="Enter"&&r>=0?(t.preventDefault(),e[r]?.click()):t.key==="Escape"&&this.open(!1)}};function W(i=document){for(let t of R(i,l("search"))){if(N.has(t))continue;N.add(t);let e=t.querySelector(l("search-input"))??t.querySelector("input"),r=t.querySelector(l("search-results"));e&&r&&new f(t,e,r)}}var{Tether:$}=window;if(!$)throw new Error("tether.search.js runs after the Tether script, which loads it");$.__parts.search=g;})();
@@ -0,0 +1,44 @@
1
+ /** A `MoneyV2`. */
2
+ interface Money {
3
+ amount: string;
4
+ currencyCode: string;
5
+ }
6
+ export interface AccountImage {
7
+ url: string;
8
+ altText: string | null;
9
+ }
10
+ /** A line of an order. */
11
+ export interface AccountOrderLine {
12
+ title: string | null;
13
+ variantTitle: string | null;
14
+ quantity: number;
15
+ image: AccountImage | null;
16
+ }
17
+ /** An order in the customer's list. */
18
+ export interface AccountOrder {
19
+ id: string;
20
+ name: string | null;
21
+ processedAt: string | null;
22
+ financialStatus: string | null;
23
+ fulfillmentStatus: string | null;
24
+ statusPageUrl: string | null;
25
+ totalPrice: Money | null;
26
+ lineItems: {
27
+ nodes: AccountOrderLine[];
28
+ };
29
+ }
30
+ /** `missing-id` when an order has no id, which each side reports in its own error type. */
31
+ type OrdersReading = {
32
+ kind: 'read';
33
+ orders: AccountOrder[];
34
+ } | {
35
+ kind: 'missing-id';
36
+ };
37
+ export declare function moneyOrNull(value: unknown): Money | null;
38
+ export declare function imageOrNull(value: unknown): AccountImage | null;
39
+ export declare const toOrderLine: (value: unknown) => AccountOrderLine;
40
+ /**
41
+ * @param value A list of orders: Shopify's `orders.nodes`, or the `orders` of the Worker's `/orders`.
42
+ */
43
+ export declare function readOrders(value: unknown): OrdersReading;
44
+ export {};
@@ -0,0 +1,153 @@
1
+ /**
2
+ * The Worker's routes for the customer account operations, the contract between the browser's account part and the
3
+ * Worker: what the browser sends for each operation, and which method and path it has under `/_tether/account`. The
4
+ * operations themselves (their documents and input checks) run only in the Worker, in `account/operations.ts`.
5
+ */
6
+ export type SubscriptionAction = 'pause' | 'resume' | 'skip' | 'unskip' | 'cancel';
7
+ /** Shopify's `CustomerAddressInput` fields; the Worker keeps only these, with bounded lengths. */
8
+ export interface AddressFields {
9
+ firstName?: string;
10
+ lastName?: string;
11
+ company?: string;
12
+ address1?: string;
13
+ address2?: string;
14
+ city?: string;
15
+ zip?: string;
16
+ zoneCode?: string;
17
+ /** Two letters. */
18
+ countryCode?: string;
19
+ phoneNumber?: string;
20
+ }
21
+ export interface ReturnLine {
22
+ lineItemId: string;
23
+ quantity: number;
24
+ reasonId?: string;
25
+ note?: string;
26
+ }
27
+ /** What the browser sends for each operation. Ids are gids, or the number at their end. */
28
+ export interface OperationInputs {
29
+ order: {
30
+ id: string | number;
31
+ };
32
+ addresses: Record<string, never>;
33
+ subscriptions: Record<string, never>;
34
+ profile: {
35
+ firstName?: string | undefined;
36
+ lastName?: string | undefined;
37
+ };
38
+ /** Without `id`, a new address; without fields, only makes the address the default. */
39
+ saveAddress: {
40
+ id?: string | undefined;
41
+ address: AddressFields | null;
42
+ isDefault?: boolean | undefined;
43
+ };
44
+ deleteAddress: {
45
+ id: string;
46
+ };
47
+ subscription: {
48
+ id: string | number;
49
+ action: SubscriptionAction;
50
+ };
51
+ requestReturn: {
52
+ orderId: string | number;
53
+ lines: ReturnLine[];
54
+ };
55
+ }
56
+ export type OperationName = keyof OperationInputs;
57
+ /**
58
+ * Each operation's input fields as they reach the Worker: from a browser, a page script or anyone else, so each value
59
+ * is unchecked until the operation checks it.
60
+ */
61
+ export type UncheckedInputs = {
62
+ [Name in OperationName]: {
63
+ readonly [Field in keyof OperationInputs[Name]]?: unknown;
64
+ };
65
+ };
66
+ /** One operation a Worker request asks for, with the fields its route reads, each one unchecked. */
67
+ export interface OperationCall {
68
+ name: OperationName;
69
+ input: Readonly<Record<string, unknown>>;
70
+ }
71
+ export declare const OPERATION_ROUTES: {
72
+ order: {
73
+ name: "order";
74
+ method: "GET";
75
+ path: string;
76
+ fields: {
77
+ id: true;
78
+ };
79
+ };
80
+ addresses: {
81
+ name: "addresses";
82
+ method: "GET";
83
+ path: string;
84
+ fields: {};
85
+ };
86
+ subscriptions: {
87
+ name: "subscriptions";
88
+ method: "GET";
89
+ path: string;
90
+ fields: {};
91
+ };
92
+ profile: {
93
+ name: "profile";
94
+ method: "POST";
95
+ path: string;
96
+ fields: {
97
+ firstName: true;
98
+ lastName: true;
99
+ };
100
+ };
101
+ saveAddress: {
102
+ name: "saveAddress";
103
+ method: "POST";
104
+ path: string;
105
+ fields: {
106
+ id: true;
107
+ address: true;
108
+ isDefault: true;
109
+ };
110
+ };
111
+ deleteAddress: {
112
+ name: "deleteAddress";
113
+ method: "POST";
114
+ path: string;
115
+ fields: {
116
+ id: true;
117
+ };
118
+ };
119
+ subscription: {
120
+ name: "subscription";
121
+ method: "POST";
122
+ path: string;
123
+ fields: {
124
+ id: true;
125
+ action: true;
126
+ };
127
+ };
128
+ requestReturn: {
129
+ name: "requestReturn";
130
+ method: "POST";
131
+ path: string;
132
+ fields: {
133
+ orderId: true;
134
+ lines: true;
135
+ };
136
+ };
137
+ };
138
+ /**
139
+ * The operation a Worker request asks for, with its input read from the query string or the body.
140
+ *
141
+ * @param path The path under `/_tether/account`.
142
+ * @param body The JSON body of a POST; `{}` for a GET.
143
+ * @returns `null` when no operation has this method and path.
144
+ */
145
+ export declare function operationCall(method: string, path: string, url: URL, body: Record<string, unknown>): OperationCall | null;
146
+ /**
147
+ * The method and path (with its query string, for a GET) the browser requests an operation with. A POST sends the
148
+ * input as its JSON body.
149
+ */
150
+ export declare function operationRequest<Name extends OperationName>(name: Name, input: OperationInputs[Name]): {
151
+ method: 'GET' | 'POST';
152
+ path: string;
153
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The Shopify API versions Tether is built and smoke-tested against, for the browser script and the Worker alike. The
3
+ * three APIs are versioned separately, so each has its own constant even while they hold the same quarter. A new
4
+ * version must pass `npm run smoke` on both the pinned version and the next one.
5
+ */
6
+ /** A dated, quarterly version such as `2026-07`. */
7
+ export type ApiVersion = `${number}-${'01' | '04' | '07' | '10'}`;
8
+ /** The Storefront API: products, carts, cart buyer identity. */
9
+ export declare const STOREFRONT_API_VERSION: ApiVersion;
10
+ /** The Customer Account API: the logged-in customer's orders, addresses and subscriptions. */
11
+ export declare const CUSTOMER_ACCOUNT_API_VERSION: ApiVersion;
12
+ /** The Admin API the sync reads products with. */
13
+ export declare const ADMIN_API_VERSION: ApiVersion;
@@ -0,0 +1,2 @@
1
+ /** The text of anything thrown: an `Error`'s message, or the value itself written as text. */
2
+ export declare function messageOf(error: unknown): string;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Reading JSON that arrives from outside (Shopify's answers, a Worker request body) field by field, for the browser
3
+ * script and the Worker alike. Every reader takes `unknown`, so a missing or mistyped field is `undefined`, never a
4
+ * crash.
5
+ */
6
+ /** A plain object, as parsed JSON holds it. */
7
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
8
+ /** The field `key` of `value`, or `undefined` when `value` is not a plain object. */
9
+ export declare function field(value: unknown, key: string): unknown;
10
+ /** The value when it is an array, else an empty list. */
11
+ export declare function asList(value: unknown): unknown[];
12
+ /** A non-empty string, or `null`: Shopify sends `""` and `null` alike for an unset text field. */
13
+ export declare function textOrNull(value: unknown): string | null;
14
+ export declare const numberOrNull: (value: unknown) => number | null;
15
+ /** A count Shopify always sends; 0 for an answer without it. */
16
+ export declare const countOrZero: (value: unknown) => number;
17
+ export declare const listOf: <T>(value: unknown, read: (item: unknown) => T) => T[];
18
+ /**
19
+ * @param connection A GraphQL connection: `{ nodes: [...] }`.
20
+ */
21
+ export declare const nodesOf: <T>(connection: unknown, read: (item: unknown) => T) => {
22
+ nodes: T[];
23
+ };
24
+ /** The non-empty strings of a list. */
25
+ export declare const textList: (value: unknown) => string[];
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Country and language codes as Shopify takes them, parsed the same way in the browser and the Worker: anything that
3
+ * isn't a code is dropped rather than repaired.
4
+ */
5
+ /** An ISO 3166-1 alpha-2 code, upper case. */
6
+ export type CountryCode = string & {
7
+ readonly __brand: 'CountryCode';
8
+ };
9
+ /** A Shopify `LanguageCode` value. */
10
+ export type LanguageCode = string & {
11
+ readonly __brand: 'LanguageCode';
12
+ };
13
+ /** @returns `'de'` → `'DE'`; `null` for anything that is not a country code. */
14
+ export declare function toCountry(value: unknown): CountryCode | null;
15
+ /** @returns `'de-DE'` → `'DE'`, `'pt-BR'` → `'PT_BR'`, `'en'` → `'EN'`; `null` otherwise. */
16
+ export declare function toLanguage(value: unknown): LanguageCode | null;
@@ -0,0 +1,5 @@
1
+ import type { CustomerAddress } from './page-answers.js';
2
+ /** @returns The address with this id in the list shown last. */
3
+ export declare const shownAddress: (id: string) => CustomerAddress | undefined;
4
+ /** Loads and renders the addresses. A failure is reported and shows `addresses-error`, never "no addresses yet". */
5
+ export declare function renderAddresses(root: ParentNode): Promise<void>;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The Worker's account answers as typed values. The Worker passes on what Shopify answers to its fixed documents, and
3
+ * each answer is read once, field by field. Every field a document selects is kept. One that is missing or of another
4
+ * type reads as `null` (a list as empty, a count as 0): a page with one blank field is better than no page. The ids
5
+ * the pages act on are required, and an answer without one is an error. This module reads what the account part
6
+ * shows (the customer, their orders through the reader the Worker shares, Shopify's `userErrors`); page-answers.ts
7
+ * reads the account pages' answers.
8
+ */
9
+ import { TetherError } from '../client.js';
10
+ import type { UserError } from '../client.js';
11
+ import type { AccountOrder } from '../../server/src/shared/account-orders.js';
12
+ /** The logged-in customer. */
13
+ export interface AccountCustomer {
14
+ /** The customer's gid; left out when the Worker's `/me` doesn't send it. */
15
+ id?: string;
16
+ firstName: string | null;
17
+ lastName: string | null;
18
+ displayName: string | null;
19
+ emailAddress: {
20
+ emailAddress: string;
21
+ } | null;
22
+ defaultAddress: {
23
+ formatted: string[];
24
+ } | null;
25
+ }
26
+ export declare const unexpected: (what: string) => TetherError;
27
+ /**
28
+ * @param what Named in the error.
29
+ * @throws {TetherError} Without an id.
30
+ */
31
+ export declare function requiredId(value: unknown, what: string): string;
32
+ /** @param value The `customer` of the Worker's `/me`, which flattens the email and the default address. */
33
+ export declare function toAccountCustomer(value: unknown): AccountCustomer;
34
+ /**
35
+ * @param value The `orders` of the Worker's `/orders`.
36
+ * @throws {TetherError} For an order without an id.
37
+ */
38
+ export declare function toAccountOrders(value: unknown): AccountOrder[];
39
+ /** @param value Shopify's `userErrors`, as Shopify or the Worker sends them. */
40
+ export declare const toUserErrors: (value: unknown) => UserError[];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shows or hides each `name` element whose value is one of `conditions`' keys; other values are left alone.
3
+ *
4
+ * @param name `show`, `order-show`, `address-show`…
5
+ */
6
+ export declare function showIn(root: ParentNode, name: string, conditions: Readonly<Record<string, boolean>>): void;
7
+ /**
8
+ * @param row A copy of a repeater's template.
9
+ * @returns The `name` elements in `row`, and `row` itself when it carries `name`.
10
+ */
11
+ export declare const bindsIn: (row: HTMLElement, name: string) => HTMLElement[];
12
+ /** @returns A Shopify enum as text: `PARTIALLY_FULFILLED` → `partially fulfilled`. */
13
+ export declare const enumText: (value: string | null | undefined) => string;
14
+ /** @returns Shopify's formatted address on one line. */
15
+ export declare const addressText: (address: {
16
+ formatted?: string[] | null;
17
+ } | null | undefined) => string;
18
+ /** A bind's value, looked up by the attribute's text: only the table's own keys, never `constructor` and the like. */
19
+ export declare const ownValue: <T>(values: Readonly<Record<string, T>>, key: string) => T | undefined;
@@ -0,0 +1,41 @@
1
+ import type { AddressFields, ReturnLine, SubscriptionAction } from '../../server/src/shared/account-routes.js';
2
+ export type { AddressFields, ReturnLine };
3
+ import type { AccountAddress, CustomerAddress, OpenedReturn, OrderDetail, Profile, SubscriptionChange, SubscriptionContract } from './page-answers.js';
4
+ import type { ServerAnswer, ServerRequest } from './server-client.js';
5
+ import type { AccountCustomer } from './answers.js';
6
+ /** The account part's session, shared so both parts act as one customer. */
7
+ interface PagesContext {
8
+ serverFetch: (path: string, request?: ServerRequest) => Promise<ServerAnswer>;
9
+ customer: () => Promise<AccountCustomer | null>;
10
+ bindCustomer: (root: ParentNode) => Promise<void>;
11
+ /** Forgets the cached customer after a change of their data. */
12
+ reset: () => void;
13
+ }
14
+ /** Called by the account part once, when it loads this part. */
15
+ export declare function setup(shared: PagesContext): void;
16
+ /** @throws {Error} Before `setup`: this part is only ever loaded by the account part, which calls it first. */
17
+ export declare function pagesContext(): PagesContext;
18
+ /**
19
+ * One of the customer's orders, in full (lines, totals, addresses, fulfillments, returns).
20
+ *
21
+ * @param id A gid or the numeric order id.
22
+ * @returns `null` when the customer has no such order.
23
+ */
24
+ export declare function order(id: string | number): Promise<OrderDetail | null>;
25
+ export declare function updateProfile({ firstName, lastName, }?: {
26
+ firstName?: string;
27
+ lastName?: string;
28
+ }): Promise<Profile>;
29
+ /** @returns The customer's addresses, the default one marked `isDefault`. */
30
+ export declare function addresses(): Promise<CustomerAddress[]>;
31
+ /** Creates an address, or updates the one with `id`; without fields, only makes it the default. */
32
+ export declare function saveAddress(address: AddressFields | null, { id, isDefault }?: {
33
+ id?: string | undefined;
34
+ isDefault?: boolean;
35
+ }): Promise<AccountAddress>;
36
+ /** @returns The deleted address's id. */
37
+ export declare function deleteAddress(id: string): Promise<string>;
38
+ export declare function subscriptions(): Promise<SubscriptionContract[]>;
39
+ /** @returns The changed contract or billing cycle. */
40
+ export declare function subscriptionAction(id: string | number, action: SubscriptionAction): Promise<SubscriptionChange>;
41
+ export declare function requestReturn(orderId: string | number, lines: ReturnLine[]): Promise<OpenedReturn>;
@@ -0,0 +1,13 @@
1
+ import type { AccountOrderLine } from '../../server/src/shared/account-orders.js';
2
+ import type { OrderDetailLine } from './page-answers.js';
3
+ /** A line of the order list, or of the order page, which has the fields after `image` too. */
4
+ type OrderLine = AccountOrderLine & Partial<Pick<OrderDetailLine, 'id' | 'name' | 'sku' | 'price' | 'totalPrice' | 'suggestedReturnReasonDefinitions'>>;
5
+ /** Line id → how many of it can still be returned. */
6
+ type Returnable = ReadonlyMap<string, number>;
7
+ /**
8
+ * Renders `lines` into a `tether-order-lines` repeater.
9
+ *
10
+ * @param returns `null` for the order list.
11
+ */
12
+ export declare function fillLines(box: HTMLElement, lines: readonly OrderLine[], returns: Returnable | null): void;
13
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { AccountOrder } from '../../server/src/shared/account-orders.js';
2
+ /** How many orders `orders()` answers, and a `tether-orders` list shows, without being told. */
3
+ export declare const DEFAULT_ORDER_COUNT = 10;
4
+ /** The most orders one request asks for: the Worker answers no more. */
5
+ export declare const MAX_ORDER_COUNT = 50;
6
+ /** @returns True for a number of orders one request can ask for: a whole number from 1 to `MAX_ORDER_COUNT`. */
7
+ export declare const isOrderCount: (value: unknown) => value is number;
8
+ /**
9
+ * Fills every `tether-orders` list in `root`. A failure is reported and leaves the lists empty, without claiming the
10
+ * customer has no orders.
11
+ */
12
+ export declare function renderOrderList(root: ParentNode, loadOrders: (first: number) => Promise<AccountOrder[]>): Promise<void>;