@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
package/src/fragments.js DELETED
@@ -1,237 +0,0 @@
1
- /**
2
- * The GraphQL fragments of what Tether renders. They are built at call time because the list sizes and metafields come
3
- * from the config; the browser passes its validated `cfg`, the server helpers their own. Operations put these after
4
- * the operation, where `withContext` expects them.
5
- */
6
- import { cfg, toMetafieldIdentifier } from './config.js';
7
- /** @import { TetherConfig, MetafieldIdentifier } from './config.js' */
8
- /** @import { Money } from './shopify-values.js' */
9
-
10
- /** @typedef {{ url: string, altText: string | null, width: number | null, height: number | null }} Image */
11
- /**
12
- * @typedef {{ __typename: 'SellingPlanPercentagePriceAdjustment', adjustmentPercentage: number }
13
- * | { __typename: 'SellingPlanFixedAmountPriceAdjustment', adjustmentAmount: Money }
14
- * | { __typename: 'SellingPlanFixedPriceAdjustment', price: Money }} SellingPlanAdjustmentValue
15
- */
16
- /**
17
- * @typedef {object} SellingPlan
18
- * @property {string} id
19
- * @property {string} name
20
- * @property {string | null} description
21
- * @property {boolean} recurringDeliveries
22
- * @property {{ name: string, value: string }[]} options
23
- * @property {{ orderCount: number | null, adjustmentValue: SellingPlanAdjustmentValue }[]} priceAdjustments
24
- */
25
- /**
26
- * @typedef {object} SellingPlanGroup
27
- * @property {string} name
28
- * @property {string | null} appName
29
- * @property {{ name: string, values: string[] }[]} options
30
- * @property {{ nodes: SellingPlan[] }} sellingPlans
31
- */
32
- /**
33
- * @typedef {object} SellingPlanAllocation A variant's price on one plan.
34
- * @property {{ id: string }} sellingPlan
35
- * @property {Money} checkoutChargeAmount What the customer pays at checkout (more for a prepaid plan).
36
- * @property {Money} remainingBalanceChargeAmount
37
- * @property {{ price: Money, compareAtPrice: Money, perDeliveryPrice: Money, unitPrice: Money | null }[]}
38
- * priceAdjustments
39
- */
40
- /**
41
- * @typedef {object} ProductVariant
42
- * @property {string} id
43
- * @property {string} title
44
- * @property {boolean} availableForSale
45
- * @property {boolean} currentlyNotInStock
46
- * @property {number | null} [quantityAvailable] Absent when the store's token can't read inventory.
47
- * @property {string | null} sku
48
- * @property {string | null} barcode
49
- * @property {{ name: string, value: string }[]} selectedOptions
50
- * @property {Image | null} image
51
- * @property {Money} price
52
- * @property {Money | null} compareAtPrice
53
- * @property {Money | null} unitPrice
54
- * @property {{ nodes: SellingPlanAllocation[] }} sellingPlanAllocations
55
- */
56
- /**
57
- * @typedef {object} Metafield
58
- * @property {string} namespace
59
- * @property {string} key
60
- * @property {string} type
61
- * @property {string} value
62
- * @property {{ image?: Image } | null} reference A file reference's image.
63
- */
64
- /**
65
- * @typedef {object} Product The fields of `fragment TetherProduct`.
66
- * @property {string} id
67
- * @property {string} handle
68
- * @property {string} title
69
- * @property {string} description
70
- * @property {string} descriptionHtml
71
- * @property {string} productType
72
- * @property {string} vendor
73
- * @property {string[]} tags
74
- * @property {boolean} availableForSale
75
- * @property {string | null} publishedAt
76
- * @property {string | null} onlineStoreUrl
77
- * @property {boolean} requiresSellingPlan
78
- * @property {{ title: string | null, description: string | null }} seo
79
- * @property {Image | null} featuredImage
80
- * @property {{ nodes: Image[] }} images
81
- * @property {{ name: string, optionValues: { name: string, swatch: unknown }[] }[]} options
82
- * @property {{ minVariantPrice: Money, maxVariantPrice: Money }} priceRange
83
- * @property {{ minVariantPrice: Money, maxVariantPrice: Money }} compareAtPriceRange
84
- * @property {(Metafield | null)[]} [metafields] Present when metafields are configured; `null` for one that isn't set.
85
- * @property {{ nodes: SellingPlanGroup[] }} sellingPlanGroups
86
- * @property {{ nodes: ProductVariant[] }} variants
87
- */
88
-
89
- /**
90
- * @typedef {Pick<TetherConfig, 'imagesFirst' | 'variantsFirst' | 'sellingPlanGroupsFirst' | 'sellingPlansFirst'
91
- * | 'metafields'>} ProductFragmentConfig
92
- */
93
-
94
- const MONEY = 'amount currencyCode';
95
- const IMAGE = 'url altText width height';
96
-
97
- /**
98
- * The `metafields(identifiers:)` field. A fragment can't declare variables, so the identifiers are written into the
99
- * text: only valid ones (`toMetafieldIdentifier`), each quoted as a string literal.
100
- *
101
- * @param {ProductFragmentConfig['metafields']} metafields
102
- */
103
- function metafieldsField(metafields) {
104
- /** @type {MetafieldIdentifier[]} */
105
- const identifiers = [];
106
- for (const ref of metafields) {
107
- const identifier = toMetafieldIdentifier(ref);
108
- if (identifier) identifiers.push(identifier);
109
- }
110
- if (!identifiers.length) return '';
111
- const list = identifiers
112
- .map(({ namespace, key }) => `{namespace: ${JSON.stringify(namespace)}, key: ${JSON.stringify(key)}}`)
113
- .join(', ');
114
- return `metafields(identifiers: [${list}]) {
115
- namespace key type value
116
- reference { ... on MediaImage { image { ${IMAGE} } } }
117
- }`;
118
- }
119
-
120
- const SELLING_PLAN_FIELDS = `
121
- id name description recurringDeliveries
122
- options { name value }
123
- priceAdjustments {
124
- orderCount
125
- adjustmentValue {
126
- __typename
127
- ... on SellingPlanPercentagePriceAdjustment { adjustmentPercentage }
128
- ... on SellingPlanFixedAmountPriceAdjustment { adjustmentAmount { ${MONEY} } }
129
- ... on SellingPlanFixedPriceAdjustment { price { ${MONEY} } }
130
- }
131
- }`;
132
-
133
- const ALLOCATION_FIELDS = `
134
- sellingPlan { id }
135
- checkoutChargeAmount { ${MONEY} }
136
- remainingBalanceChargeAmount { ${MONEY} }
137
- priceAdjustments {
138
- price { ${MONEY} }
139
- compareAtPrice { ${MONEY} }
140
- perDeliveryPrice { ${MONEY} }
141
- unitPrice { ${MONEY} }
142
- }`;
143
-
144
- /**
145
- * @param {ProductFragmentConfig} [o] The list sizes and metafields.
146
- * @returns {string} `fragment TetherProduct on Product`.
147
- */
148
- export function productFragment(o = cfg) {
149
- return `
150
- fragment TetherProduct on Product {
151
- id handle title description descriptionHtml productType vendor tags
152
- availableForSale publishedAt onlineStoreUrl requiresSellingPlan
153
- seo { title description }
154
- featuredImage { ${IMAGE} }
155
- images(first: ${o.imagesFirst}) { nodes { ${IMAGE} } }
156
- options { name optionValues { name swatch { color image { previewImage { ${IMAGE} } } } } }
157
- priceRange { minVariantPrice { ${MONEY} } maxVariantPrice { ${MONEY} } }
158
- compareAtPriceRange { minVariantPrice { ${MONEY} } maxVariantPrice { ${MONEY} } }
159
- ${metafieldsField(o.metafields)}
160
- sellingPlanGroups(first: ${o.sellingPlanGroupsFirst}) {
161
- nodes {
162
- name appName
163
- options { name values }
164
- sellingPlans(first: ${o.sellingPlansFirst}) { nodes { ${SELLING_PLAN_FIELDS} } }
165
- }
166
- }
167
- variants(first: ${o.variantsFirst}) {
168
- nodes {
169
- id title availableForSale currentlyNotInStock quantityAvailable sku barcode
170
- selectedOptions { name value }
171
- image { ${IMAGE} }
172
- price { ${MONEY} }
173
- compareAtPrice { ${MONEY} }
174
- unitPrice { ${MONEY} }
175
- sellingPlanAllocations(first: ${o.sellingPlansFirst}) { nodes { ${ALLOCATION_FIELDS} } }
176
- }
177
- }
178
- }`;
179
- }
180
-
181
- /** @returns {string} `fragment TetherCart on Cart`. */
182
- export function cartFragment() {
183
- return `
184
- fragment TetherCart on Cart {
185
- id checkoutUrl totalQuantity note createdAt updatedAt
186
- attributes { key value }
187
- buyerIdentity { countryCode email }
188
- discountCodes { code applicable }
189
- cost {
190
- subtotalAmount { ${MONEY} }
191
- totalAmount { ${MONEY} }
192
- checkoutChargeAmount { ${MONEY} }
193
- }
194
- lines(first: 100) {
195
- nodes {
196
- id quantity
197
- attributes { key value }
198
- cost {
199
- totalAmount { ${MONEY} }
200
- subtotalAmount { ${MONEY} }
201
- amountPerQuantity { ${MONEY} }
202
- compareAtAmountPerQuantity { ${MONEY} }
203
- }
204
- discountAllocations { discountedAmount { ${MONEY} } }
205
- sellingPlanAllocation {
206
- sellingPlan { id name description recurringDeliveries options { name value } }
207
- checkoutChargeAmount { ${MONEY} }
208
- priceAdjustments { price { ${MONEY} } compareAtPrice { ${MONEY} } perDeliveryPrice { ${MONEY} } }
209
- }
210
- merchandise {
211
- ... on ProductVariant {
212
- id title sku availableForSale requiresShipping quantityAvailable
213
- image { ${IMAGE} }
214
- price { ${MONEY} }
215
- compareAtPrice { ${MONEY} }
216
- selectedOptions { name value }
217
- product { id title handle vendor featuredImage { ${IMAGE} } }
218
- }
219
- }
220
- ... on ComponentizableCartLine {
221
- lineComponents {
222
- id quantity
223
- merchandise { ... on ProductVariant { id title product { title handle } } }
224
- }
225
- }
226
- }
227
- }
228
- }`;
229
- }
230
-
231
- /** @returns {string} The collection fields Tether renders, for inside a `collection { }` selection. */
232
- export function collectionFields() {
233
- return `
234
- id handle title description descriptionHtml
235
- image { ${IMAGE} }
236
- seo { title description }`;
237
- }
package/src/index.js DELETED
@@ -1,353 +0,0 @@
1
- /**
2
- * Tether's entry: `window.Tether`, `init()`, `mount()` and the start of the plain `<script>` setup.
3
- *
4
- * <script>window.TETHER_CONFIG = { domain: 'x.myshopify.com', token: '...' }</script>
5
- * <script src="tether.min.js" defer></script>
6
- *
7
- * With page transitions (Barba, Swup): set `autoMount: false`, call `Tether.init()` once and
8
- * `Tether.mount(container)` after every page enter.
9
- */
10
- import { cfg, configure, isConfigured, log, warn, reportError } from './config.js';
11
- import { gql, TetherError, withContext } from './client.js';
12
- import { Cart } from './cart.js';
13
- import { scopes } from './product.js';
14
- import { ListScope, listSelector } from './collection.js';
15
- import {
16
- getProduct,
17
- getProducts,
18
- getRecommendations,
19
- clearProductCache,
20
- primeProduct,
21
- normalizeProductKey,
22
- } from './loader.js';
23
- import * as plans from './selling-plans.js';
24
- import { resolveProductKey } from './status.js';
25
- import { sel, emit, formatMoney, imageUrl } from './dom.js';
26
- import { loadPart, coreModules } from './lazy.js';
27
- import {
28
- mountedLists,
29
- liveLists,
30
- pruneLive,
31
- within,
32
- mountProduct,
33
- hydrateFromPage,
34
- settleMarket,
35
- trackMarket,
36
- } from './mounting.js';
37
- import { applyStartMarket, setCountry } from './page-market.js';
38
- import { accountApi, mountAccountIfUsed } from './account-api.js';
39
- import { bindCartControls } from './controls/cart-controls.js';
40
- import { bindDiscountControls } from './controls/discount.js';
41
- import { addGroup, bindExtrasControls } from './controls/extras.js';
42
- import { bindNavigation, rememberDocumentMount } from './controls/navigation.js';
43
- /** @import { TetherConfig, TetherOptions, MarketsConfig } from './config.js' */
44
- /** @import { ShopifyCart } from './cart/store.js' */
45
- /** @import { ProductScope } from './product.js' */
46
- /** @import { DiagnoseOptions } from './diagnose.js' */
47
-
48
- export { addGroup, setCountry };
49
-
50
- export const VERSION = '0.6.8';
51
-
52
- /** The events the analytics part reports, buffered from `init()` until the part has loaded. */
53
- const ANALYTICS_EVENTS = ['product-ready', 'list-ready', 'cart-updated', 'cart-toggled', 'checkout', 'search'];
54
-
55
- /** Milliseconds `observe` waits after the last added node, so a burst of additions mounts once. */
56
- const OBSERVE_DEBOUNCE_MS = 50;
57
-
58
- /**
59
- * @typedef {object} TetherGlobals What the page sets on `window` for Tether, and `window.Tether` itself.
60
- * @property {TetherOptions} [TETHER_CONFIG] The site's config; `configure()` validates it.
61
- * @property {string} [TETHER_INTEGRATION] Set by a framework integration (Astro) that starts Tether itself.
62
- * @property {TetherApi} [Tether]
63
- */
64
-
65
- /** @returns {Window & TetherGlobals} */
66
- const pageGlobals = () => window;
67
-
68
- /** @type {Promise<TetherApi | null> | null} */
69
- let initPromise = null;
70
-
71
- /** @type {MutationObserver | null} */
72
- let observer = null;
73
-
74
- /** @type {Event[] | null} The analytics part's queue, created once by the first `init()` with `analytics`. */
75
- let analyticsQueue = null;
76
-
77
- /** @type {boolean} The document-wide listeners are bound once, by the first configured `init()`. */
78
- let controlsBound = false;
79
-
80
- /**
81
- * Mounts the lists of `root` and the product scopes in them as they render.
82
- *
83
- * @param {ParentNode} root
84
- * @returns {number} The lists mounted.
85
- */
86
- function mountLists(root) {
87
- let count = 0;
88
- within(root, listSelector()).forEach((el) => {
89
- if (mountedLists.has(el)) return;
90
- mountedLists.add(el);
91
- count++;
92
- const list = new ListScope(el);
93
- liveLists.add(list);
94
- el.addEventListener(`${cfg.prefix}:list-rendered`, (event) => {
95
- // The list emits this CustomEvent once its products are in the page.
96
- mount(/** @type {CustomEvent<{ container: ParentNode }>} */ (event).detail.container);
97
- });
98
- void trackMarket(list.load());
99
- });
100
- return count;
101
- }
102
-
103
- /**
104
- * Loads each optional part `root` uses: the country select, customer accounts and search as you type.
105
- *
106
- * @param {ParentNode} root
107
- */
108
- function mountParts(root) {
109
- if (within(root, `${sel('country-select')},${sel('country-bind')}`).length) {
110
- loadPart('markets')
111
- .then((part) => part.renderCountrySelects(root))
112
- .catch((/** @type {unknown} */ error) => {
113
- reportError(error, 'markets');
114
- });
115
- }
116
- mountAccountIfUsed(root);
117
- if (within(root, sel('search')).length) {
118
- loadPart('search')
119
- .then((part) => {
120
- part.mountSearch(root);
121
- })
122
- .catch((/** @type {unknown} */ error) => {
123
- reportError(error, 'search');
124
- });
125
- }
126
- }
127
-
128
- /**
129
- * Mounts every product scope and product list inside `root`. Safe to call repeatedly.
130
- *
131
- * @param {ParentNode} [root]
132
- * @returns {number} The scopes and lists newly mounted.
133
- */
134
- export function mount(root = document) {
135
- if (!isConfigured()) return 0;
136
- if (root === document) rememberDocumentMount();
137
- // Page transitions detach old roots: they aren't kept alive.
138
- pruneLive();
139
- // Products the server rendered into the page need no request.
140
- hydrateFromPage(root.ownerDocument ?? document);
141
- let count = mountLists(root);
142
- within(root, sel('product')).forEach((el) => {
143
- if (mountProduct(el)) count++;
144
- });
145
- mountParts(root);
146
- settleMarket();
147
- Cart.render();
148
- log(`mounted ${count} scope(s)`, root);
149
- return count;
150
- }
151
-
152
- /** Call before a page transition removes the page: closes the cart drawer. */
153
- export function unmount() {
154
- Cart.close();
155
- }
156
-
157
- /**
158
- * @param {Element | null | undefined} el
159
- * @returns {ProductScope | null} The product scope `el` is in.
160
- */
161
- export function getScope(el) {
162
- const root = el?.closest(sel('product'));
163
- return root ? (scopes.get(root) ?? null) : null;
164
- }
165
-
166
- function bindControls() {
167
- if (controlsBound) return;
168
- controlsBound = true;
169
- bindCartControls();
170
- bindDiscountControls();
171
- bindExtrasControls();
172
- bindNavigation({ ready: () => initPromise ?? Promise.resolve(null), mount, unmount });
173
- }
174
-
175
- /** Buffers Tether's events until the analytics part has loaded, so none are missed. */
176
- function startAnalyticsQueue() {
177
- /** @type {Event[]} */
178
- const queue = [];
179
- analyticsQueue = queue;
180
- ANALYTICS_EVENTS.forEach((name) => {
181
- document.addEventListener(`${cfg.prefix}:${name}`, (event) => queue.push(event));
182
- });
183
- document.addEventListener('astro:page-load', () => queue.push(new CustomEvent(`${cfg.prefix}:page`)));
184
- loadPart('analytics')
185
- .then((part) => part.startAnalytics(queue))
186
- .catch((/** @type {unknown} */ error) => {
187
- reportError(error, 'analytics');
188
- });
189
- }
190
-
191
- /**
192
- * A saved cart from another market moves to this one, so its prices match the page's.
193
- *
194
- * @param {ShopifyCart | null} cart
195
- * @returns {Promise<ShopifyCart | null>}
196
- */
197
- async function cartInPageMarket(cart) {
198
- const country = cfg.country?.toUpperCase();
199
- const cartCountry = cart?.buyerIdentity.countryCode;
200
- if (!cart || !country || !cartCountry || cartCountry === country) return cart;
201
- try {
202
- return await Cart.setBuyerIdentity({ countryCode: country }, { create: false });
203
- } catch (error) {
204
- reportError(error, 'cart country');
205
- return cart;
206
- }
207
- }
208
-
209
- /**
210
- * Configures Tether (`window.TETHER_CONFIG` with `overrides` over it), applies the visitor's market and loads the
211
- * cart. A second call without overrides returns the first one's promise.
212
- *
213
- * @param {TetherOptions} [overrides]
214
- * @returns {Promise<TetherApi | null>} `null` without a domain and token, when Tether stays idle.
215
- */
216
- export function init(overrides = {}) {
217
- if (initPromise && !Object.keys(overrides).length) return initPromise;
218
- configure({ ...pageGlobals().TETHER_CONFIG, ...overrides });
219
- if (!isConfigured()) {
220
- warn('TETHER_CONFIG is missing domain or token. Tether is idle.');
221
- initPromise = Promise.resolve(null);
222
- return initPromise;
223
- }
224
- bindControls();
225
- if (cfg.observe) startObserver();
226
- if (cfg.analytics && !analyticsQueue) startAnalyticsQueue();
227
- initPromise = applyStartMarket(overrides)
228
- .then(() => Cart.init())
229
- .then(cartInPageMarket)
230
- .then((cart) => {
231
- emit('ready', { cart });
232
- return api;
233
- });
234
- return initPromise;
235
- }
236
-
237
- /** `observe`: mounts the elements added to the page later. */
238
- function startObserver() {
239
- if (observer || typeof MutationObserver === 'undefined') return;
240
- /** @type {ReturnType<typeof setTimeout> | undefined} */
241
- let timer;
242
- /** @type {Set<Element>} */
243
- const queued = new Set();
244
- observer = new MutationObserver((mutations) => {
245
- mutations.forEach((mutation) => {
246
- mutation.addedNodes.forEach((node) => {
247
- if (node instanceof window.Element) queued.add(node);
248
- });
249
- });
250
- clearTimeout(timer);
251
- timer = setTimeout(() => {
252
- queued.forEach((node) => {
253
- if (node.isConnected) mount(node);
254
- });
255
- queued.clear();
256
- }, OBSERVE_DEBOUNCE_MS);
257
- });
258
- observer.observe(document.body, { childList: true, subtree: true });
259
- }
260
-
261
- /**
262
- * @param {MarketsConfig | null} [markets] Default `markets` of the config.
263
- */
264
- const detectMarket = (markets) => loadPart('markets').then((part) => part.detectMarket(markets ?? cfg.markets));
265
-
266
- const getLocalization = () => loadPart('markets').then((part) => part.getLocalization());
267
-
268
- /** @param {Omit<DiagnoseOptions, 'version'>} [options] */
269
- const diagnose = (options) => loadPart('diagnose').then((part) => part.diagnose({ ...options, version: VERSION }));
270
-
271
- /**
272
- * Listens to the Tether event `<prefix>:<name>`.
273
- *
274
- * @param {string} name
275
- * @param {(detail: unknown, event: CustomEvent) => void} listener
276
- * @returns {() => void} Stops listening.
277
- */
278
- function on(name, listener) {
279
- /** @param {Event} event */
280
- const handler = (event) => {
281
- const custom = /** @type {CustomEvent} Tether's events are CustomEvents. */ (event);
282
- listener(custom.detail, custom);
283
- };
284
- document.addEventListener(`${cfg.prefix}:${name}`, handler);
285
- return () => {
286
- document.removeEventListener(`${cfg.prefix}:${name}`, handler);
287
- };
288
- }
289
-
290
- export const api = {
291
- VERSION,
292
- init,
293
- mount,
294
- unmount,
295
- /** @returns {TetherConfig} */
296
- get config() {
297
- return cfg;
298
- },
299
- /** @returns {Promise<TetherApi | null>} */
300
- get ready() {
301
- return initPromise ?? Promise.resolve(null);
302
- },
303
- query: gql,
304
- withContext,
305
- cart: Cart,
306
- getProduct,
307
- getProducts,
308
- getRecommendations,
309
- getScope,
310
- addGroup,
311
- account: accountApi,
312
- primeProduct,
313
- normalizeProductKey,
314
- clearProductCache,
315
- formatMoney,
316
- imageUrl,
317
- plans,
318
- TetherError,
319
- resolveProductKey,
320
- setCountry,
321
- detectMarket,
322
- getLocalization,
323
- diagnose,
324
- __core: coreModules,
325
- // The `<script>` build's parts register here when their file has loaded.
326
- __parts: /** @type {Record<string, unknown>} */ ({}),
327
- on,
328
- };
329
-
330
- /** @typedef {typeof api} TetherApi */
331
-
332
- export default api;
333
-
334
- /**
335
- * The plain `<script>` setup starts by itself, unless `autoMount: false` or a framework integration (Astro), which
336
- * starts Tether itself.
337
- */
338
- function autoStart() {
339
- const globals = pageGlobals();
340
- globals.Tether = api;
341
- if (!globals.TETHER_CONFIG || globals.TETHER_CONFIG.autoMount === false || globals.TETHER_INTEGRATION) return;
342
- const start = () => {
343
- init()
344
- .then(() => mount(document))
345
- .catch((/** @type {unknown} */ error) => {
346
- reportError(error, 'init');
347
- });
348
- };
349
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, { once: true });
350
- else start();
351
- }
352
-
353
- if (typeof window !== 'undefined' && typeof document !== 'undefined') autoStart();
package/src/initial.js DELETED
@@ -1,87 +0,0 @@
1
- /**
2
- * What the browser script shows first for a product, as plain data, so a server renders the same and nothing flashes
3
- * when Tether takes over. Public as `initialState()` of `@occluze/tether/server`; the rules are `product-state.js`'s,
4
- * the ones the browser scope uses.
5
- */
6
- import { DEFAULTS } from './config.js';
7
- import { initialChoice, isValueAvailable, productView } from './product-state.js';
8
- import { allPlans, allocationFor, planPrice, planSavings } from './selling-plans.js';
9
- /** @import { Money } from './shopify-values.js' */
10
- /** @import { Product, ProductVariant } from './fragments.js' */
11
- /** @import { PlanInGroup } from './selling-plans.js' */
12
- /** @import { Selection, ShowCondition } from './product-state.js' */
13
-
14
- /**
15
- * @typedef {object} InitialOptions
16
- * @property {string | null | undefined} [variant] The page's `?variant=`; the first variant for sale without.
17
- * @property {string | null | undefined} [sellingPlan] The page's `?selling_plan=`: subscribes on that plan.
18
- * @property {string | null | undefined} [defaultPurchase] `subscription` subscribes on the first plan.
19
- * @property {number | undefined} [lowStock] `low-stock` at this many or fewer.
20
- */
21
- /**
22
- * @typedef {object} InitialPlan A plan in the plan list (`tether-plan-list`).
23
- * @property {string} id
24
- * @property {string} name
25
- * @property {Money | null} price On this plan, for the variant.
26
- * @property {number} savings In %, 0 without savings.
27
- * @property {boolean} selected `is-selected`: the line is bought on this plan.
28
- * @property {boolean} available The variant is on this plan; `is-unavailable` when not.
29
- */
30
- /**
31
- * @typedef {object} InitialState
32
- * @property {ProductVariant} variant
33
- * @property {Selection} selected Option name → value of the variant: `is-selected` on those buttons.
34
- * @property {'one-time' | 'subscription'} purchase
35
- * @property {PlanInGroup | null} plan The plan the line is bought on, `null` when bought once.
36
- * @property {InitialPlan[]} plans
37
- * @property {number} savings The offered plan's savings in % (`tether-bind="subscription-savings"`), 0 without.
38
- * @property {Money} price
39
- * @property {Money | null} compareAtPrice Only when on sale.
40
- * @property {boolean} available
41
- * @property {Record<ShowCondition, boolean>} show The `tether-show` conditions: `hidden={!state.show['out-of-stock']}`.
42
- * @property {Record<string, string[]>} unavailable Per option, the values without a variant for sale (given the other
43
- * selected values), as plain data for frameworks that serialise props.
44
- * @property {(name: string, value: string) => boolean} isUnavailable The same as a function: `is-unavailable`.
45
- */
46
-
47
- /**
48
- * @param {Product | null | undefined} product
49
- * @param {InitialOptions} [options]
50
- * @returns {InitialState | null} `null` without a product or variants.
51
- */
52
- export function initialState(product, options = {}) {
53
- if (!product) return null;
54
- const { variant, sellingPlan, defaultPurchase = DEFAULTS.defaultPurchase, lowStock = DEFAULTS.lowStock } = options;
55
- const choice = initialChoice(product, { variant, sellingPlan, defaultPurchase });
56
- const view = productView(product, { ...choice, quantity: 1 }, lowStock);
57
- if (!view) return null;
58
- const { selected, isSubscription } = view;
59
- const isUnavailable = (/** @type {string} */ name, /** @type {string} */ value) =>
60
- !isValueAvailable(product, selected, name, value);
61
- return {
62
- variant: view.variant,
63
- selected,
64
- purchase: view.purchase.kind,
65
- plan: isSubscription ? view.plan : null,
66
- plans: allPlans(product).map((plan) => ({
67
- id: plan.id,
68
- name: plan.name,
69
- price: planPrice(view.variant, plan.id),
70
- savings: planSavings(view.variant, plan.id).percentage,
71
- selected: isSubscription && plan.id === view.purchase.planId,
72
- available: Boolean(allocationFor(view.variant, plan.id)),
73
- })),
74
- savings: view.savings.percentage,
75
- price: view.price,
76
- compareAtPrice: view.compareAtPrice,
77
- available: view.variant.availableForSale,
78
- show: view.show,
79
- unavailable: Object.fromEntries(
80
- product.options.map((option) => [
81
- option.name,
82
- option.optionValues.map((value) => value.name).filter((value) => isUnavailable(option.name, value)),
83
- ]),
84
- ),
85
- isUnavailable,
86
- };
87
- }