@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,16 +1,26 @@
1
1
  /**
2
2
  * The product scopes and lists on the page: mounting a product root, the live roots a country switch re-renders, and
3
- * the products the server rendered into the page. Shared by `index.js` and the optional markets part.
3
+ * the products the server rendered into the page. Shared by `index.ts` and the optional markets part.
4
4
  */
5
- import { cfg, log, warn, reportError } from './config.js';
6
- import { ProductScope, scopes, rememberOptionNames } from './product.js';
7
- import { listSelector } from './collection.js';
8
- import { getProduct, takeStale, refreshProducts, hydrateProducts } from './loader.js';
9
- import { resolveProductKey, setScopeStatus, isNotFound } from './status.js';
10
- import { attr, get, qsa, emit } from './dom.js';
11
- /** @import { Product } from './fragments.js' */
5
+ import { cfg, log, warn, reportError } from './config.ts';
6
+ import { ProductScope, scopes, rememberOptionNames } from './product.ts';
7
+ import { listSelector } from './collection.ts';
8
+ import { getProduct, takeStale, refreshProduct, hydrateProducts } from './loader.ts';
9
+ import { resolveProductKey, setScopeStatus, isNotFound } from './status.ts';
10
+ import { attr, get, qsa, emit } from './dom.ts';
11
+ import { toCountry, toLanguage } from './market-codes.ts';
12
+ import { isRecord } from '../server/src/shared/json.ts';
13
+ import { isProductShape } from './product-shape.ts';
14
+ import type { Product } from './fragments.ts';
15
+ import type { CountryCode, LanguageCode } from './market-codes.ts';
12
16
 
13
- /** @typedef {{ root: Element, load: (more?: boolean) => Promise<unknown> }} MountedList A mounted `ListScope`. */
17
+ /** A mounted `ListScope`. */
18
+ interface MountedList {
19
+ root: Element;
20
+ load: (more?: boolean) => Promise<unknown>;
21
+ }
22
+
23
+ type KeySource = Pick<ReturnType<typeof resolveProductKey>, 'fromUrl' | 'raw'>;
14
24
 
15
25
  /**
16
26
  * The marker on the `<script type="application/json">` tags `tether/server`'s `hydrate()` writes. It is fixed, not
@@ -18,19 +28,17 @@ import { attr, get, qsa, emit } from './dom.js';
18
28
  */
19
29
  const HYDRATE_SELECTOR = 'script[type="application/json"][data-tether-hydrate]';
20
30
 
21
- /** @type {WeakSet<Element>} */
22
- export const mountedProducts = new WeakSet();
23
- /** @type {WeakSet<Element>} */
24
- export const mountedLists = new WeakSet();
25
- /** @type {Set<HTMLElement>} Product roots to re-render after a country switch; pruned once disconnected. */
26
- export const liveProducts = new Set();
27
- /** @type {Set<MountedList>} Lists to reload after a country switch; pruned once disconnected. */
28
- export const liveLists = new Set();
29
- /** @type {WeakMap<Element, object>} Each product root's latest request, so a slow older response can't win. */
30
- const latestRequest = new WeakMap();
31
+ export const mountedProducts = new WeakSet<Element>();
32
+ export const mountedLists = new WeakSet<Element>();
33
+ /** Product roots to re-render after a country switch; pruned once disconnected. */
34
+ export const liveProducts = new Set<HTMLElement>();
35
+ /** Lists to reload after a country switch; pruned once disconnected. */
36
+ export const liveLists = new Set<MountedList>();
37
+ /** Each product root's latest request, so a slow older response can't win. */
38
+ const latestRequest = new WeakMap<Element, object>();
31
39
 
32
40
  /** Forgets roots a page transition removed, so they aren't kept alive. */
33
- export function pruneLive() {
41
+ export function pruneLive(): void {
34
42
  liveProducts.forEach((el) => {
35
43
  if (!el.isConnected) liveProducts.delete(el);
36
44
  });
@@ -39,23 +47,15 @@ export function pruneLive() {
39
47
  });
40
48
  }
41
49
 
42
- /**
43
- * @param {ParentNode & Partial<Pick<Element, 'matches'>>} root
44
- * @param {string} selector
45
- * @returns {HTMLElement[]} `root` itself when it matches, then the matches inside it.
46
- */
47
- export function within(root, selector) {
48
- const self = root.matches?.(selector) ? [/** @type {HTMLElement} */ (root)] : [];
50
+ /** @returns `root` itself when it matches, then the matches inside it. */
51
+ export function within(root: ParentNode & Partial<Pick<Element, 'matches'>>, selector: string): HTMLElement[] {
52
+ // A root that matches carries Tether's markup, which goes on HTML elements (see `qsa`).
53
+ const self = root.matches?.(selector) ? [root as HTMLElement] : [];
49
54
  return [...self, ...qsa(root, selector)];
50
55
  }
51
56
 
52
- /**
53
- * `tether-page-title="{title} | My shop"` on a product root sets `document.title`, for dynamic product pages.
54
- *
55
- * @param {Element} el
56
- * @param {Product} product
57
- */
58
- function applyPageTitle(el, product) {
57
+ /** `tether-page-title="{title} | My shop"` on a product root sets `document.title`, for dynamic product pages. */
58
+ function applyPageTitle(el: Element, product: Product): void {
59
59
  const template = get(el, 'page-title');
60
60
  if (!template || typeof document === 'undefined') return;
61
61
  document.title = template
@@ -67,11 +67,8 @@ function applyPageTitle(el, product) {
67
67
  /**
68
68
  * A product root without a key: on a dynamic page opened without `?handle=` (or with an empty path) it is a product
69
69
  * that isn't there; inside a list it gets its handle when the list renders.
70
- *
71
- * @param {HTMLElement} el
72
- * @param {{ fromUrl: boolean, raw: string }} source
73
70
  */
74
- function mountWithoutKey(el, { fromUrl, raw }) {
71
+ function mountWithoutKey(el: HTMLElement, { fromUrl, raw }: KeySource): void {
75
72
  if (fromUrl) {
76
73
  mountedProducts.add(el);
77
74
  setScopeStatus(el, 'not-found');
@@ -81,13 +78,7 @@ function mountWithoutKey(el, { fromUrl, raw }) {
81
78
  }
82
79
  }
83
80
 
84
- /**
85
- * @param {HTMLElement} el
86
- * @param {string} key
87
- * @param {{ fromUrl: boolean, raw: string }} source
88
- * @param {unknown} error
89
- */
90
- function showLoadFailure(el, key, { fromUrl, raw }, error) {
81
+ function showLoadFailure(el: HTMLElement, key: string, { fromUrl, raw }: KeySource, error: unknown): void {
91
82
  const notFound = isNotFound(error);
92
83
  setScopeStatus(el, notFound ? 'not-found' : 'error', error);
93
84
  if (!notFound) {
@@ -104,10 +95,9 @@ function showLoadFailure(el, key, { fromUrl, raw }, error) {
104
95
  /**
105
96
  * Mounts one product root.
106
97
  *
107
- * @param {HTMLElement} el
108
- * @returns {boolean} True when a product request was started.
98
+ * @returns True when a product request was started.
109
99
  */
110
- export function mountProduct(el) {
100
+ export function mountProduct(el: HTMLElement): boolean {
111
101
  if (mountedProducts.has(el)) return false;
112
102
  const { key, ...source } = resolveProductKey(el);
113
103
  if (!key) {
@@ -132,14 +122,14 @@ export function mountProduct(el) {
132
122
  applyPageTitle(el, product);
133
123
  if (cfg.revalidate && takeStale(key)) queueRefresh(key);
134
124
  })
135
- .catch((/** @type {unknown} */ error) => {
125
+ .catch((error: unknown) => {
136
126
  if (isLatest()) showLoadFailure(el, key, source, error);
137
127
  });
138
128
  return true;
139
129
  }
140
130
 
141
131
  /** Loads again the product scopes that failed on a network or API error, for when the browser is back online. */
142
- export function retryFailed() {
132
+ export function retryFailed(): void {
143
133
  pruneLive();
144
134
  liveProducts.forEach((el) => {
145
135
  if (el.getAttribute(attr('state')) !== 'error' || el.getAttribute(attr('error')) === 'not-found') return;
@@ -148,20 +138,16 @@ export function retryFailed() {
148
138
  });
149
139
  }
150
140
 
151
- /** @type {Set<string>} Server-rendered products to refresh in the next batch. */
152
- let refreshQueue = new Set();
141
+ /** Server-rendered products to refresh in the next batch. */
142
+ let refreshQueue = new Set<string>();
153
143
 
154
- /** @param {string} key */
155
- function queueRefresh(key) {
144
+ function queueRefresh(key: string): void {
156
145
  if (!refreshQueue.size) setTimeout(flushRefresh, 0);
157
146
  refreshQueue.add(key);
158
147
  }
159
148
 
160
- /**
161
- * @param {string} key The key the refresh was asked for.
162
- * @param {Product} fresh
163
- */
164
- function applyRefresh(key, fresh) {
149
+ /** @param key The key the refresh was asked for. */
150
+ function applyRefresh(key: string, fresh: Product): void {
165
151
  liveProducts.forEach((el) => {
166
152
  const scope = scopes.get(el);
167
153
  if (!scope || !el.isConnected) return;
@@ -175,38 +161,35 @@ function applyRefresh(key, fresh) {
175
161
  function flushRefresh() {
176
162
  const keys = [...refreshQueue];
177
163
  refreshQueue = new Set();
178
- refreshProducts(keys).forEach((promise, index) => {
179
- const key = /** @type {string} */ (keys[index]);
180
- promise
164
+ keys.forEach((key) => {
165
+ refreshProduct(key)
181
166
  .then((fresh) => {
182
167
  applyRefresh(key, fresh);
183
168
  })
184
- .catch((/** @type {unknown} */ error) => {
169
+ .catch((error: unknown) => {
185
170
  reportError(error, `refresh ${key}`);
186
171
  });
187
172
  });
188
173
  }
189
174
 
190
175
  /**
191
- * @type {Set<Promise<unknown>> | null} While the page's server-rendered prices are another market's: the loads to
192
- * wait for before `html.tether-market-pending` goes. `null` when no market is pending.
176
+ * While the page's server-rendered prices are another market's: the loads to wait for before
177
+ * `html.tether-market-pending` goes. `null` when no market is pending.
193
178
  */
194
- let marketPending = null;
179
+ let marketPending: Set<Promise<unknown>> | null = null;
195
180
 
196
181
  /**
197
182
  * Lists, and anything else that loads prices, hold `html.tether-market-pending` until they are in.
198
183
  *
199
- * @template T
200
- * @param {Promise<T>} loading
201
- * @returns {Promise<T>} `loading` itself.
184
+ * @returns `loading` itself.
202
185
  */
203
- export const trackMarket = (loading) => {
186
+ export const trackMarket = <T>(loading: Promise<T>): Promise<T> => {
204
187
  marketPending?.add(loading);
205
188
  return loading;
206
189
  };
207
190
 
208
191
  /** After a mount: drops `html.tether-market-pending` once this page's products are live. */
209
- export function settleMarket() {
192
+ export function settleMarket(): void {
210
193
  if (!marketPending) return;
211
194
  const waits = [...marketPending];
212
195
  void Promise.allSettled(waits).then(() => {
@@ -220,52 +203,56 @@ export function settleMarket() {
220
203
  });
221
204
  }
222
205
 
223
- /**
224
- * What `tether/server`'s `hydrate()` writes. The tag is the site's own markup, so its shape is trusted; a product
225
- * without an id or a handle is skipped.
226
- *
227
- * @typedef {{ country?: string | null, language?: string | null, products?: (Product | null)[] }} HydratePayload
228
- */
229
-
230
- /**
231
- * @param {Product | null} product
232
- * @returns {product is Product}
233
- */
234
- const isUsable = (product) => Boolean(product?.id && product.handle);
235
-
236
- /**
237
- * @param {string | null | undefined} a
238
- * @param {string | null | undefined} b
239
- */
240
- const sameCode = (a, b) => (a ?? '').toUpperCase() === (b ?? '').toUpperCase();
206
+ /** What `tether/server`'s `hydrate()` writes: the market it fetched in, and the products. */
207
+ interface HydratePayload {
208
+ country: CountryCode | undefined;
209
+ language: LanguageCode | undefined;
210
+ products: Product[];
211
+ }
241
212
 
242
- /** @type {WeakSet<Element>} Hydrate tags already read. */
243
- const readScripts = new WeakSet();
213
+ /** @param json A hydrate tag's JSON. */
214
+ function toHydratePayload(json: unknown): HydratePayload {
215
+ if (!isRecord(json)) return { country: undefined, language: undefined, products: [] };
216
+ const { products } = json;
217
+ const entries: unknown[] = Array.isArray(products) ? products : [];
218
+ return {
219
+ country: toCountry(json['country']) ?? undefined,
220
+ language: toLanguage(json['language']) ?? undefined,
221
+ products: entries.filter(isProductShape),
222
+ };
223
+ }
244
224
 
225
+ /** Hydrate tags already read. */
226
+ const readScripts = new WeakSet<Element>();
245
227
  /**
246
228
  * Uses the products in the page's hydrate tags (`tether/server` `hydrate()`) without a request. A tag made for another
247
229
  * market is skipped, and marks the page (`html.tether-market-pending`) until the live prices are in, so CSS can hide
248
230
  * prices meanwhile.
249
231
  *
250
- * @param {Document | Element} [root]
232
+ * Trust: a hydrate tag is part of the page, written by the site's own build like the rest of its HTML and scripts, so
233
+ * its prices and products are shown until the background refresh replaces them. Whoever can write a tag into the page
234
+ * can already write a script there; the shape check (`isProductShape`) is for a truncated or hand-written tag, which
235
+ * is skipped instead of failing a render.
236
+ *
237
+ * @param root Where to look: the document for a mount (a tag may sit in `<head>` or anywhere else on the page), the
238
+ * added elements for `observe`.
251
239
  */
252
- export function hydrateFromPage(root = document) {
253
- qsa(root.ownerDocument ?? root, HYDRATE_SELECTOR).forEach((script) => {
240
+ export function hydrateFromPage(root: Document | Element = document): void {
241
+ within(root, HYDRATE_SELECTOR).forEach((script) => {
254
242
  if (readScripts.has(script)) return;
255
243
  readScripts.add(script);
256
- /** @type {unknown} */
257
- let json;
244
+ let json: unknown;
258
245
  try {
259
246
  json = JSON.parse(script.textContent);
260
247
  } catch (error) {
261
248
  warn('bad data-tether-hydrate', error);
262
249
  return;
263
250
  }
264
- const { country, language, products } = /** @type {HydratePayload | null} */ (json) ?? {};
265
- const usable = Array.isArray(products) ? products.filter(isUsable) : [];
266
- rememberOptionNames(usable); // the build's option names, also for other languages
267
- if (sameCode(country, cfg.country) && sameCode(language, cfg.language)) {
268
- hydrateProducts(usable);
251
+ const { country, language, products } = toHydratePayload(json);
252
+ // The build's option names, also for other languages.
253
+ rememberOptionNames(products);
254
+ if (country === cfg.country && language === cfg.language) {
255
+ hydrateProducts(products);
269
256
  return;
270
257
  }
271
258
  log('hydration skipped: other market');
@@ -3,24 +3,25 @@
3
3
  * saved or detected market, then the configured one. Init applies it to `cfg` once. Astro's ClientRouter keeps the
4
4
  * script running across pages, so each arrival asks again: the next page may be built for another market, or for none.
5
5
  */
6
- import { cfg, log, reportError } from './config.js';
7
- import { loadPart } from './lazy.js';
8
- import { toCountry, toLanguage } from './market-codes.js';
9
- import { readSavedMarket } from './markets.js';
10
- /** @import { TetherOptions } from './config.js' */
11
- /** @import { Market, DetectedMarket } from './markets.js' */
12
- /** @import { SetCountryOptions } from './markets-ui.js' */
13
- /** @import { ShopifyCart } from './cart/store.js' */
6
+ import { cfg, log, reportError } from './config.ts';
7
+ import { loadPart } from './lazy.ts';
8
+ import { toCountry, toLanguage } from './market-codes.ts';
9
+ import { readSavedMarket } from './markets.ts';
10
+ import type { TetherOptions } from './config.ts';
11
+ import type { Market, DetectedMarket } from './markets.ts';
12
+ import type { Parts } from './lazy.ts';
13
+ import type { ShopifyCart } from './cart/store.ts';
14
14
 
15
- /**
16
- * @typedef {object} ConfiguredMarket The market of the config, before a page's or visitor's market applied.
17
- * @property {string | undefined} country
18
- * @property {string | undefined} language
19
- * @property {string | undefined} locale
20
- */
15
+ export type SetCountryOptions = Parameters<Parts['markets']['setCountry']>[1];
16
+
17
+ /** The market of the config, before a page's or visitor's market applied. */
18
+ interface ConfiguredMarket {
19
+ country: string | undefined;
20
+ language: string | undefined;
21
+ locale: string | undefined;
22
+ }
21
23
 
22
- /** @type {ConfiguredMarket} */
23
- let configuredMarket = { country: undefined, language: undefined, locale: undefined };
24
+ let configuredMarket: ConfiguredMarket = { country: undefined, language: undefined, locale: undefined };
24
25
 
25
26
  /** Whether the page arrived at last was built for its own market, which the next page without one switches back from. */
26
27
  let lastPageHadOwnMarket = false;
@@ -29,11 +30,10 @@ let lastPageHadOwnMarket = false;
29
30
  * `<meta name="tether-page-country" content="DE">`, with `tether-page-language` and `tether-page-locale`. Never
30
31
  * remembered: it belongs to the page, not to the visitor.
31
32
  *
32
- * @returns {Market | null} `null` when the page has no valid country of its own.
33
+ * @returns `null` when the page has no valid country of its own.
33
34
  */
34
- function pageMarket() {
35
- /** @param {string} name */
36
- const meta = (name) =>
35
+ function pageMarket(): Market | null {
36
+ const meta = (name: string) =>
37
37
  document.querySelector(`meta[name="${cfg.prefix}-page-${name}"]`)?.getAttribute('content') || undefined;
38
38
  const country = toCountry(meta('country'));
39
39
  if (!country) return null;
@@ -42,10 +42,8 @@ function pageMarket() {
42
42
 
43
43
  /**
44
44
  * Without `markets.detect` only the visitor's saved choice applies, read without loading the markets part.
45
- *
46
- * @returns {Promise<Market | DetectedMarket | null>}
47
45
  */
48
- async function visitorMarket() {
46
+ async function visitorMarket(): Promise<Market | DetectedMarket | null> {
49
47
  const markets = cfg.markets ?? {};
50
48
  if (markets.detect === false) return null;
51
49
  if (!markets.detect) return markets.remember === false ? null : readSavedMarket();
@@ -60,10 +58,9 @@ async function visitorMarket() {
60
58
  /**
61
59
  * Sets `cfg`'s market for the first page, over the configured one.
62
60
  *
63
- * @param {TetherOptions} overrides What `init()` was called with: a `locale` there wins over the visitor's.
64
- * @returns {Promise<void>}
61
+ * @param overrides What `init()` was called with: a `locale` there wins over the visitor's.
65
62
  */
66
- export async function applyStartMarket(overrides) {
63
+ export async function applyStartMarket(overrides: TetherOptions): Promise<void> {
67
64
  configuredMarket = { country: cfg.country, language: cfg.language, locale: cfg.locale };
68
65
  const page = pageMarket();
69
66
  lastPageHadOwnMarket = page !== null;
@@ -81,20 +78,19 @@ export async function applyStartMarket(overrides) {
81
78
  log('market', market);
82
79
  }
83
80
 
84
- /**
85
- * @param {string | undefined} a
86
- * @param {string | undefined} b
87
- */
88
- const sameCode = (a, b) => (a ?? '').toUpperCase() === (b ?? '').toUpperCase();
81
+ const sameCode = (a: string | undefined, b: string | undefined) => (a ?? '').toUpperCase() === (b ?? '').toUpperCase();
89
82
 
90
83
  /**
91
84
  * The market to switch to on arriving at the current page. A page built for a market switches to it; the first page
92
85
  * without one after such a page switches back to the visitor's own choice, else to the configured market.
93
86
  *
94
- * @returns {{ country: string, language: string | undefined, locale: string | undefined } | null} `null` when the page
95
- * stays in the market in use.
87
+ * @returns `null` when the page stays in the market in use.
96
88
  */
97
- export function marketForArrival() {
89
+ export function marketForArrival(): {
90
+ country: string;
91
+ language: string | undefined;
92
+ locale: string | undefined;
93
+ } | null {
98
94
  const page = pageMarket();
99
95
  const leftOwnMarket = page === null && lastPageHadOwnMarket;
100
96
  lastPageHadOwnMarket = page !== null;
@@ -107,10 +103,8 @@ export function marketForArrival() {
107
103
  /**
108
104
  * `Tether.setCountry('DE')`: loads the markets part on first use.
109
105
  *
110
- * @param {string} code
111
- * @param {SetCountryOptions} [options]
112
- * @returns {Promise<ShopifyCart | null>} The cart once everything is in the new market.
106
+ * @returns The cart once everything is in the new market.
113
107
  */
114
- export function setCountry(code, options) {
108
+ export function setCountry(code: string, options?: SetCountryOptions): Promise<ShopifyCart | null> {
115
109
  return loadPart('markets').then((part) => part.setCountry(code, options));
116
110
  }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * The Subresource Integrity hash of each optional part's file, by file name, so the core loads only the parts built
3
+ * with it (src/lazy-iife.ts). Every `<script>` build, watch mode included, puts the hashes of the parts it built into
4
+ * the core in place of this module (scripts/build.ts); the empty map here only lets the source type-check, and a part
5
+ * missing from the map is not loaded.
6
+ */
7
+ export const PART_INTEGRITY: Readonly<Record<string, string>> = {};
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.account-pages.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../account-pages.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.account-pages.js runs after the Tether script, which loads it');
7
+ Tether.__parts['account-pages'] = part;
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.account.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../account.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.account.js runs after the Tether script, which loads it');
7
+ Tether.__parts.account = part;
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.analytics.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../analytics.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.analytics.js runs after the Tether script, which loads it');
7
+ Tether.__parts.analytics = part;
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.diagnose.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../diagnose.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.diagnose.js runs after the Tether script, which loads it');
7
+ Tether.__parts.diagnose = part;
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.extras.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../extras.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.extras.js runs after the Tether script, which loads it');
7
+ Tether.__parts.extras = part;
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.markets.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../markets-ui.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.markets.js runs after the Tether script, which loads it');
7
+ Tether.__parts.markets = part;
@@ -0,0 +1,7 @@
1
+ // The <script> build's tether.search.js: registers the part where the core script's loader (lazy-iife.ts) finds it.
2
+ import * as part from '../search.ts';
3
+ import type { TetherInternals } from '../lazy.ts';
4
+
5
+ const { Tether } = window as { Tether?: TetherInternals };
6
+ if (!Tether) throw new Error('tether.search.js runs after the Tether script, which loads it');
7
+ Tether.__parts.search = part;