@occluze/tether 0.6.8 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (215) hide show
  1. package/README.md +28 -28
  2. package/dist/esm/astro.js +2 -2
  3. package/dist/esm/chunks/account-CXMA5RPZ.js +1 -0
  4. package/dist/esm/chunks/account-pages-IZO7FEOZ.js +1 -0
  5. package/dist/esm/chunks/analytics-PG5FPRDN.js +1 -0
  6. package/dist/esm/chunks/chunk-4HD4J5JC.js +1 -0
  7. package/dist/esm/chunks/chunk-6TFMLT4A.js +4 -0
  8. package/dist/esm/chunks/chunk-BBVUVPMO.js +1 -0
  9. package/dist/esm/chunks/chunk-E5YO6274.js +1 -0
  10. package/dist/esm/chunks/chunk-EW5SYO6L.js +1 -0
  11. package/dist/esm/chunks/chunk-HJY2JSNB.js +1 -0
  12. package/dist/esm/chunks/chunk-MQYB6BH4.js +1 -0
  13. package/dist/esm/chunks/chunk-N3V45JPS.js +1 -0
  14. package/dist/esm/chunks/chunk-RPMBCAGS.js +1 -0
  15. package/dist/esm/chunks/chunk-VFNHIGCS.js +1 -0
  16. package/dist/esm/chunks/chunk-WT7RGGJU.js +1 -0
  17. package/dist/esm/chunks/chunk-YN6LEBEN.js +1 -0
  18. package/dist/esm/chunks/chunk-Z6HS3H7K.js +1 -0
  19. package/dist/esm/chunks/diagnose-XCEBQKBO.js +14 -0
  20. package/dist/esm/chunks/extras-4HU3VSBQ.js +1 -0
  21. package/dist/esm/chunks/markets-ui-M4IQ62CT.js +1 -0
  22. package/dist/esm/chunks/search-A5DS2IOS.js +1 -0
  23. package/dist/esm/index.js +1 -1
  24. package/dist/esm/server.js +2 -2
  25. package/dist/presets/barba.esm.js +19 -23
  26. package/dist/tether.account-pages.js +332 -367
  27. package/dist/tether.account-pages.min.js +1 -1
  28. package/dist/tether.account.js +253 -492
  29. package/dist/tether.account.min.js +1 -1
  30. package/dist/tether.analytics.js +82 -89
  31. package/dist/tether.analytics.min.js +1 -1
  32. package/dist/tether.diagnose.js +52 -34
  33. package/dist/tether.diagnose.min.js +2 -2
  34. package/dist/tether.extras.js +42 -37
  35. package/dist/tether.extras.min.js +1 -1
  36. package/dist/tether.js +1361 -935
  37. package/dist/tether.markets.js +89 -91
  38. package/dist/tether.markets.min.js +1 -1
  39. package/dist/tether.min.js +0 -0
  40. package/dist/tether.min.js.map +4 -4
  41. package/dist/tether.search.js +60 -69
  42. package/dist/tether.search.min.js +1 -1
  43. package/dist/types/server/src/shared/account-orders.d.ts +44 -0
  44. package/dist/types/server/src/shared/account-routes.d.ts +153 -0
  45. package/dist/types/server/src/shared/api-versions.d.ts +13 -0
  46. package/dist/types/server/src/shared/cart-id.d.ts +2 -0
  47. package/dist/types/server/src/shared/error-message.d.ts +2 -0
  48. package/dist/types/server/src/shared/json.d.ts +25 -0
  49. package/dist/types/server/src/shared/market-codes.d.ts +16 -0
  50. package/dist/types/src/account/addresses-view.d.ts +5 -0
  51. package/dist/types/src/account/answers.d.ts +40 -0
  52. package/dist/types/src/account/markup.d.ts +19 -0
  53. package/dist/types/src/account/operations.d.ts +41 -0
  54. package/dist/types/src/account/order-lines.d.ts +13 -0
  55. package/dist/types/src/account/order-list.d.ts +12 -0
  56. package/dist/types/src/account/order-page.d.ts +6 -0
  57. package/dist/types/src/account/page-answers.d.ts +182 -0
  58. package/dist/types/src/account/page-events.d.ts +2 -0
  59. package/dist/types/src/account/server-client.d.ts +60 -0
  60. package/dist/types/src/account/subscriptions-view.d.ts +2 -0
  61. package/dist/types/src/account-api.d.ts +18 -0
  62. package/dist/types/src/account-marks.d.ts +15 -0
  63. package/dist/types/src/account-pages.d.ts +8 -0
  64. package/dist/types/src/account.d.ts +64 -0
  65. package/dist/types/src/amounts.d.ts +12 -0
  66. package/dist/types/src/analytics-shopify.d.ts +41 -0
  67. package/dist/types/src/analytics.d.ts +2 -0
  68. package/dist/types/src/api-version.d.ts +28 -0
  69. package/dist/types/src/astro.d.ts +42 -0
  70. package/dist/types/src/cart/cart-shape.d.ts +43 -0
  71. package/dist/types/src/cart/checkout.d.ts +16 -0
  72. package/dist/types/src/cart/drawer.d.ts +18 -0
  73. package/dist/types/src/cart/mutations.d.ts +85 -0
  74. package/dist/types/src/cart/render.d.ts +17 -0
  75. package/dist/types/src/cart/store.d.ts +185 -0
  76. package/dist/types/src/cart.d.ts +106 -0
  77. package/dist/types/src/client.d.ts +134 -0
  78. package/dist/types/src/collection.d.ts +25 -0
  79. package/dist/types/src/config-sections.d.ts +40 -0
  80. package/dist/types/src/config.d.ts +190 -0
  81. package/dist/types/src/controls/cart-controls.d.ts +2 -0
  82. package/dist/types/src/controls/discount.d.ts +2 -0
  83. package/dist/types/src/controls/extras.d.ts +5 -0
  84. package/dist/types/src/controls/navigation.d.ts +12 -0
  85. package/dist/types/src/diagnose-markup.d.ts +19 -0
  86. package/dist/types/src/diagnose.d.ts +60 -0
  87. package/dist/types/src/dom.d.ts +75 -0
  88. package/dist/types/src/extras.d.ts +21 -0
  89. package/dist/types/src/fragments.d.ts +158 -0
  90. package/dist/types/src/globals.d.ts +15 -0
  91. package/dist/types/src/images.d.ts +20 -0
  92. package/dist/types/src/index.d.ts +167 -0
  93. package/dist/types/src/initial.d.ts +56 -0
  94. package/dist/types/src/lazy.d.ts +28 -0
  95. package/dist/types/src/loader.d.ts +84 -0
  96. package/dist/types/src/market-codes.d.ts +6 -0
  97. package/dist/types/src/markets-ui.d.ts +32 -0
  98. package/dist/types/src/markets.d.ts +72 -0
  99. package/dist/types/src/markup.d.ts +131 -0
  100. package/dist/types/src/mounting.d.ts +46 -0
  101. package/dist/types/src/page-market.d.ts +27 -0
  102. package/dist/types/src/presets/barba.d.ts +56 -0
  103. package/dist/types/src/product-events.d.ts +2 -0
  104. package/dist/types/src/product-render.d.ts +17 -0
  105. package/dist/types/src/product-shape.d.ts +90 -0
  106. package/dist/types/src/product-state.d.ts +110 -0
  107. package/dist/types/src/product.d.ts +66 -0
  108. package/dist/types/src/repeaters.d.ts +16 -0
  109. package/dist/types/src/search.d.ts +33 -0
  110. package/dist/types/src/selling-plans.d.ts +64 -0
  111. package/dist/types/src/server.d.ts +88 -0
  112. package/dist/types/src/shopify-values.d.ts +46 -0
  113. package/dist/types/src/status.d.ts +33 -0
  114. package/dist/types/src/storage.d.ts +12 -0
  115. package/dist/types/src/store-html.d.ts +39 -0
  116. package/dist/types/src/storefront/catalog.d.ts +38 -0
  117. package/dist/types/src/storefront/json-ld.d.ts +20 -0
  118. package/dist/types/src/variant-render.d.ts +14 -0
  119. package/package.json +24 -24
  120. package/dist/esm/chunks/account-C3NP6HFB.js +0 -1
  121. package/dist/esm/chunks/account-pages-WLK374FO.js +0 -1
  122. package/dist/esm/chunks/analytics-G3SAWMNL.js +0 -1
  123. package/dist/esm/chunks/chunk-27PJJZGC.js +0 -1
  124. package/dist/esm/chunks/chunk-2FSXS55U.js +0 -1
  125. package/dist/esm/chunks/chunk-FCSWQDVV.js +0 -1
  126. package/dist/esm/chunks/chunk-FOMSMIAM.js +0 -1
  127. package/dist/esm/chunks/chunk-GZJTCLLW.js +0 -1
  128. package/dist/esm/chunks/chunk-HEQILZVR.js +0 -1
  129. package/dist/esm/chunks/chunk-JOOEXYF3.js +0 -4
  130. package/dist/esm/chunks/chunk-MPTQYLSM.js +0 -1
  131. package/dist/esm/chunks/chunk-NEHZ37TU.js +0 -3
  132. package/dist/esm/chunks/chunk-V74KGEJF.js +0 -1
  133. package/dist/esm/chunks/chunk-VT4CDUJJ.js +0 -1
  134. package/dist/esm/chunks/chunk-YJLWX2HK.js +0 -1
  135. package/dist/esm/chunks/diagnose-6VVGZNTY.js +0 -14
  136. package/dist/esm/chunks/extras-GJVRDFM3.js +0 -1
  137. package/dist/esm/chunks/markets-ui-UNB4PTA7.js +0 -1
  138. package/dist/esm/chunks/search-OU75RNQL.js +0 -1
  139. package/src/account/addresses-view.js +0 -92
  140. package/src/account/browser-session.js +0 -359
  141. package/src/account/markup.js +0 -48
  142. package/src/account/operations.js +0 -180
  143. package/src/account/order-lines.js +0 -96
  144. package/src/account/order-list.js +0 -91
  145. package/src/account/order-page.js +0 -192
  146. package/src/account/page-events.js +0 -288
  147. package/src/account/server-client.js +0 -172
  148. package/src/account/shared.js +0 -111
  149. package/src/account/shopify-auth.js +0 -150
  150. package/src/account/subscriptions-view.js +0 -158
  151. package/src/account-api.js +0 -118
  152. package/src/account-links.js +0 -35
  153. package/src/account-pages.js +0 -89
  154. package/src/account.js +0 -342
  155. package/src/amounts.js +0 -36
  156. package/src/analytics-shopify.js +0 -241
  157. package/src/analytics.js +0 -352
  158. package/src/api-version.js +0 -85
  159. package/src/astro.js +0 -266
  160. package/src/cart/checkout.js +0 -97
  161. package/src/cart/drawer.js +0 -138
  162. package/src/cart/mutations.js +0 -234
  163. package/src/cart/render.js +0 -294
  164. package/src/cart/store.js +0 -326
  165. package/src/cart.js +0 -391
  166. package/src/client.js +0 -267
  167. package/src/collection.js +0 -183
  168. package/src/config.js +0 -349
  169. package/src/controls/cart-controls.js +0 -155
  170. package/src/controls/discount.js +0 -96
  171. package/src/controls/extras.js +0 -63
  172. package/src/controls/navigation.js +0 -83
  173. package/src/diagnose-markup.js +0 -333
  174. package/src/diagnose.js +0 -295
  175. package/src/dom.js +0 -287
  176. package/src/extras.js +0 -145
  177. package/src/fragments.js +0 -237
  178. package/src/images.js +0 -104
  179. package/src/index.js +0 -353
  180. package/src/initial.js +0 -87
  181. package/src/lazy-iife.js +0 -116
  182. package/src/lazy.js +0 -39
  183. package/src/loader.js +0 -337
  184. package/src/market-codes.js +0 -35
  185. package/src/markets-ui.js +0 -204
  186. package/src/markets.js +0 -334
  187. package/src/markup.js +0 -64
  188. package/src/mounting.js +0 -277
  189. package/src/page-market.js +0 -116
  190. package/src/parts/account-pages.js +0 -6
  191. package/src/parts/account.js +0 -6
  192. package/src/parts/analytics.js +0 -6
  193. package/src/parts/diagnose.js +0 -6
  194. package/src/parts/extras.js +0 -6
  195. package/src/parts/markets.js +0 -6
  196. package/src/parts/search.js +0 -6
  197. package/src/presets/barba.js +0 -190
  198. package/src/product-events.js +0 -130
  199. package/src/product-render.js +0 -257
  200. package/src/product-state.js +0 -318
  201. package/src/product.js +0 -344
  202. package/src/repeaters.js +0 -173
  203. package/src/search.js +0 -323
  204. package/src/selling-plans.js +0 -154
  205. package/src/server.js +0 -215
  206. package/src/shopify-values.js +0 -89
  207. package/src/status.js +0 -126
  208. package/src/store-html.js +0 -0
  209. package/src/storefront/catalog.js +0 -199
  210. package/src/storefront/json-ld.js +0 -167
  211. package/src/variant-render.js +0 -165
  212. package/types/astro.d.ts +0 -28
  213. package/types/barba.d.ts +0 -16
  214. package/types/index.d.ts +0 -577
  215. package/types/server.d.ts +0 -182
package/src/extras.js DELETED
@@ -1,145 +0,0 @@
1
- /**
2
- * Optional part with features that load when first needed: bundles (on the first "add set" click), tab sync (on the
3
- * first change in another tab) and shareable variant links (on the first variant change, with `variantUrl` or
4
- * `tether-variant-url`).
5
- */
6
- import { cfg, warn, reportError } from './config.js';
7
- import { sel, get, has, qsa, emit, parseJSONAttr, toAttributes } from './dom.js';
8
- import { scopes } from './product.js';
9
- import { Cart } from './cart.js';
10
- /** @import { ShopifyCart } from './cart/store.js' */
11
-
12
- /** @typedef {{ country: string, language: string | null, locale: string | undefined }} StoredMarket */
13
- /**
14
- * @typedef {(country: string, options: { language: string | null, locale: string | undefined, remember: boolean })
15
- * => Promise<unknown>} SetCountry
16
- */
17
-
18
- /**
19
- * @param {Element} group
20
- * @param {string | null} name The group's `tether-group` value.
21
- */
22
- function groupAttributes(group, name) {
23
- const extra = toAttributes(
24
- /** @type {Parameters<typeof toAttributes>[0]} */ (parseJSONAttr(group, 'line-attributes')),
25
- );
26
- return name ? [{ key: '_group', value: name }, ...extra] : extra;
27
- }
28
-
29
- /**
30
- * `[tether-group]` with `[tether-group-add]`: adds every product scope inside the group in one request, for bundles,
31
- * build-a-box and "complete the set". The lines get the attribute `_group` and the group's `tether-line-attributes`.
32
- *
33
- * @param {Element} button
34
- * @returns {Promise<ShopifyCart | null>} The cart, `null` with nothing to add or after a reported failure.
35
- */
36
- export async function addGroup(button) {
37
- const group = button.closest(sel('group'));
38
- if (!group) return null;
39
- const name = get(group, 'group');
40
- const attributes = groupAttributes(group, name);
41
- const lines = qsa(group, sel('product')).flatMap((el) => {
42
- const scope = scopes.get(el);
43
- if (!scope?.variant?.availableForSale || scope.quantity <= 0) return [];
44
- const line = scope.getLine();
45
- return line ? [{ ...line, attributes: [...(line.attributes ?? []), ...attributes] }] : [];
46
- });
47
- if (!lines.length) {
48
- warn('group-add: nothing selected');
49
- return null;
50
- }
51
- try {
52
- const cart = await Cart.add(lines);
53
- emit('group-added', { group: name, lines }, group);
54
- return cart;
55
- } catch (error) {
56
- reportError(error, 'group add');
57
- return null;
58
- }
59
- }
60
-
61
- /**
62
- * Another tab writes the market as JSON; a value that isn't (written by something else under the key) is skipped.
63
- *
64
- * @param {string} text
65
- * @returns {StoredMarket | null}
66
- */
67
- function parseStoredMarket(text) {
68
- /** @type {unknown} */
69
- let value;
70
- try {
71
- value = JSON.parse(text);
72
- } catch {
73
- return null;
74
- }
75
- if (typeof value !== 'object' || value === null) return null;
76
- const { country, language, locale } = /** @type {Record<string, unknown>} */ (value);
77
- if (typeof country !== 'string' || !country) return null;
78
- return {
79
- country,
80
- language: typeof language === 'string' ? language : null,
81
- locale: typeof locale === 'string' ? locale : undefined,
82
- };
83
- }
84
-
85
- /**
86
- * Another tab changed the cart (it is loaded again here) or the visitor's market (this tab follows it).
87
- *
88
- * @param {StorageEvent} event
89
- */
90
- export function onStorage(event) {
91
- if (event.key === cfg.storageKey || event.key === `${cfg.storageKey}:seen`) {
92
- Cart.init().catch((/** @type {unknown} */ error) => {
93
- reportError(error, 'cart from another tab');
94
- });
95
- return;
96
- }
97
- if (event.key !== `${cfg.prefix}:market:${cfg.domain}` || !event.newValue) return;
98
- const market = parseStoredMarket(event.newValue);
99
- const { Tether } = /** @type {Window & { Tether?: { setCountry: SetCountry } }} */ (window);
100
- if (!market || !Tether) return;
101
- const options = { language: market.language, locale: market.locale, remember: false };
102
- Tether.setCountry(market.country, options).catch((/** @type {unknown} */ error) => {
103
- reportError(error, 'market from another tab');
104
- });
105
- }
106
-
107
- /** @type {WeakMap<Element, string>} Scope root → its last variant and plan, so the first render doesn't write. */
108
- const urlKeys = new WeakMap();
109
-
110
- /** @param {string} gid */
111
- const numericId = (gid) => gid.split('/').pop() ?? gid;
112
-
113
- /**
114
- * Safari throws a SecurityError past 100 history changes in 10 seconds; the address then keeps the previous choice
115
- * until the next one.
116
- *
117
- * @param {URL} url
118
- */
119
- function replaceUrlIfAllowed(url) {
120
- try {
121
- window.history.replaceState(window.history.state, '', url);
122
- } catch {
123
- // Too many changes in a row: see the function's comment.
124
- }
125
- }
126
-
127
- /**
128
- * `?variant=` (and `selling_plan=`) follow the visitor's choice.
129
- *
130
- * @param {Element} root The product scope's root.
131
- * @param {{ variant: { id: string }, sellingPlanId: string | null | undefined }} choice
132
- */
133
- export function writeVariantUrl(root, { variant, sellingPlanId }) {
134
- const key = `${variant.id}|${sellingPlanId ?? ''}`;
135
- const before = urlKeys.get(root);
136
- urlKeys.set(root, key);
137
- if (!before || before === key) return;
138
- const isMainProduct = cfg.variantUrl && document.querySelector(sel('product')) === root;
139
- if (!has(root, 'variant-url') && !isMainProduct) return;
140
- const url = new URL(window.location.href);
141
- url.searchParams.set('variant', numericId(variant.id));
142
- if (sellingPlanId) url.searchParams.set('selling_plan', numericId(sellingPlanId));
143
- else url.searchParams.delete('selling_plan');
144
- replaceUrlIfAllowed(url);
145
- }
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/images.js DELETED
@@ -1,104 +0,0 @@
1
- /**
2
- * Shopify images on the page: sized through the CDN's URL parameters, written to an `<img>` or as a CSS background,
3
- * and never downloaded twice when the server already rendered the same image.
4
- */
5
- import { attr } from './markup.js';
6
-
7
- /**
8
- * @typedef {object} ImageSize Shopify CDN parameters.
9
- * @property {string | number | null} [width]
10
- * @property {string | number | null} [height]
11
- * @property {string | null} [crop] `center`, `top`, `bottom`, `left` or `right`; only applies with a width or height.
12
- */
13
-
14
- /**
15
- * Shopify CDN image at a size: `imageUrl(url, { width: 800 })`. Use the same width as the element's max-width
16
- * attribute, so the browser script keeps the image instead of loading it again.
17
- *
18
- * @template {string | null | undefined} Url
19
- * @param {Url} url
20
- * @param {ImageSize} [size]
21
- * @returns {Url} The sized URL; `url` itself when it is empty or not absolute.
22
- */
23
- export function imageUrl(url, { width, height, crop } = {}) {
24
- if (!url) return url;
25
- let parsed;
26
- try {
27
- parsed = new URL(url);
28
- } catch (error) {
29
- // A relative or malformed URL has no CDN parameters to set: it is used as written.
30
- if (error instanceof TypeError) return url;
31
- throw error;
32
- }
33
- if (width) parsed.searchParams.set('width', String(width));
34
- if (height) parsed.searchParams.set('height', String(height));
35
- if (crop) parsed.searchParams.set('crop', crop);
36
- return /** @type {Url} */ (parsed.toString());
37
- }
38
-
39
- /**
40
- * @param {Element} el
41
- * @param {string} name
42
- * @returns {string | null} `name` as Webflow's custom attribute, or in valid HTML as `<prefix>-name` / `data-name`.
43
- */
44
- function sizeAttribute(el, name) {
45
- return el.getAttribute(name) ?? el.getAttribute(attr(name)) ?? el.getAttribute(`data-${name}`);
46
- }
47
-
48
- /**
49
- * @template {string | null | undefined} Url
50
- * @param {Url} url
51
- * @param {Element | null | undefined} el The element whose max-width / max-height / crop attributes size the image.
52
- * @returns {Url}
53
- */
54
- export function cdnUrl(url, el) {
55
- if (!url || !el) return url;
56
- const width = sizeAttribute(el, 'max-width');
57
- const height = sizeAttribute(el, 'max-height');
58
- return imageUrl(url, { width, height, crop: width || height ? sizeAttribute(el, 'crop') : null });
59
- }
60
-
61
- /**
62
- * @param {string | null | undefined} a
63
- * @param {string | null | undefined} b
64
- * @returns {boolean} True for the same image at any size parameters: the server-rendered one is kept.
65
- */
66
- export const sameImage = (a, b) => Boolean(a && b) && a?.split('?')[0] === b?.split('?')[0];
67
-
68
- /**
69
- * A CSS `url()` for an image URL from Shopify. Characters that would end the string are escaped, so the URL can't
70
- * add declarations; only http(s), relative and data:image URLs are used.
71
- *
72
- * @param {string} url
73
- */
74
- function cssUrl(url) {
75
- const hasScheme = /^\s*[a-z][a-z0-9+.-]*:/i.test(url);
76
- if (hasScheme && !/^\s*(https?:|data:image\/)/i.test(url)) return 'none';
77
- const escaped = url.replace(/["\\\n\r\f]/g, (char) => `\\${char.charCodeAt(0).toString(16)} `);
78
- return `url("${escaped}")`;
79
- }
80
-
81
- /**
82
- * Shows `url` in `el`: as the `src` of an `<img>` (lazy unless it says otherwise), else as its background image.
83
- *
84
- * @param {HTMLElement} el
85
- * @param {string | null | undefined} url
86
- * @param {string | null} [alt]
87
- */
88
- export function setImage(el, url, alt) {
89
- if (!url) return;
90
- const src = cdnUrl(url, el);
91
- if (el.tagName !== 'IMG') {
92
- el.style.backgroundImage = cssUrl(src);
93
- return;
94
- }
95
- if (sameImage(el.getAttribute('src'), src)) {
96
- if (alt != null && !el.getAttribute('alt')) el.setAttribute('alt', alt);
97
- return;
98
- }
99
- el.removeAttribute('srcset');
100
- el.removeAttribute('sizes');
101
- el.setAttribute('src', src);
102
- if (alt != null) el.setAttribute('alt', alt);
103
- if (!el.getAttribute('loading')) el.setAttribute('loading', 'lazy');
104
- }