@shopify/hydrogen-react 2026.4.0 → 2026.4.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 (263) hide show
  1. package/dist/browser-dev/AddToCartButton.mjs.map +1 -1
  2. package/dist/browser-dev/BuyNowButton.mjs.map +1 -1
  3. package/dist/browser-dev/CartCheckoutButton.mjs.map +1 -1
  4. package/dist/browser-dev/CartCost.mjs.map +1 -1
  5. package/dist/browser-dev/CartLineProvider.mjs.map +1 -1
  6. package/dist/browser-dev/CartLineQuantity.mjs.map +1 -1
  7. package/dist/browser-dev/CartLineQuantityAdjustButton.mjs.map +1 -1
  8. package/dist/browser-dev/CartProvider.mjs.map +1 -1
  9. package/dist/browser-dev/ExternalVideo.mjs.map +1 -1
  10. package/dist/browser-dev/Image.mjs.map +1 -1
  11. package/dist/browser-dev/MediaFile.mjs.map +1 -1
  12. package/dist/browser-dev/ModelViewer.mjs.map +1 -1
  13. package/dist/browser-dev/Money.mjs.map +1 -1
  14. package/dist/browser-dev/ProductPrice.mjs.map +1 -1
  15. package/dist/browser-dev/ProductProvider.mjs.map +1 -1
  16. package/dist/browser-dev/RichText.mjs.map +1 -1
  17. package/dist/browser-dev/ShopPayButton.mjs.map +1 -1
  18. package/dist/browser-dev/ShopifyProvider.mjs.map +1 -1
  19. package/dist/browser-dev/Video.mjs.map +1 -1
  20. package/dist/browser-dev/analytics-constants.mjs.map +1 -1
  21. package/dist/browser-dev/analytics-utils.mjs.map +1 -1
  22. package/dist/browser-dev/analytics.mjs.map +1 -1
  23. package/dist/browser-dev/cart-queries.mjs +43 -81
  24. package/dist/browser-dev/cart-queries.mjs.map +1 -1
  25. package/dist/browser-dev/codegen.helpers.mjs.map +1 -1
  26. package/dist/browser-dev/cookies-utils.mjs.map +1 -1
  27. package/dist/browser-dev/flatten-connection.mjs.map +1 -1
  28. package/dist/browser-dev/getProductOptions.mjs.map +1 -1
  29. package/dist/browser-dev/load-script.mjs.map +1 -1
  30. package/dist/browser-dev/optionValueDecoder.mjs.map +1 -1
  31. package/dist/browser-dev/packages/hydrogen-react/package.json.mjs +1 -1
  32. package/dist/browser-dev/parse-metafield.mjs.map +1 -1
  33. package/dist/browser-dev/storefront-client.mjs.map +1 -1
  34. package/dist/browser-dev/tracking-utils.mjs.map +1 -1
  35. package/dist/browser-dev/useMoney.mjs.map +1 -1
  36. package/dist/browser-dev/useSelectedOptionInUrlParam.mjs.map +1 -1
  37. package/dist/browser-dev/useShopifyCookies.mjs.map +1 -1
  38. package/dist/browser-prod/AddToCartButton.mjs.map +1 -1
  39. package/dist/browser-prod/BuyNowButton.mjs.map +1 -1
  40. package/dist/browser-prod/CartCheckoutButton.mjs.map +1 -1
  41. package/dist/browser-prod/CartCost.mjs.map +1 -1
  42. package/dist/browser-prod/CartLineProvider.mjs.map +1 -1
  43. package/dist/browser-prod/CartLineQuantity.mjs.map +1 -1
  44. package/dist/browser-prod/CartLineQuantityAdjustButton.mjs.map +1 -1
  45. package/dist/browser-prod/CartProvider.mjs.map +1 -1
  46. package/dist/browser-prod/ExternalVideo.mjs.map +1 -1
  47. package/dist/browser-prod/Image.mjs.map +1 -1
  48. package/dist/browser-prod/MediaFile.mjs.map +1 -1
  49. package/dist/browser-prod/ModelViewer.mjs.map +1 -1
  50. package/dist/browser-prod/Money.mjs.map +1 -1
  51. package/dist/browser-prod/ProductPrice.mjs.map +1 -1
  52. package/dist/browser-prod/ProductProvider.mjs.map +1 -1
  53. package/dist/browser-prod/RichText.mjs.map +1 -1
  54. package/dist/browser-prod/ShopPayButton.mjs.map +1 -1
  55. package/dist/browser-prod/ShopifyProvider.mjs.map +1 -1
  56. package/dist/browser-prod/Video.mjs.map +1 -1
  57. package/dist/browser-prod/analytics-constants.mjs.map +1 -1
  58. package/dist/browser-prod/analytics-utils.mjs.map +1 -1
  59. package/dist/browser-prod/analytics.mjs.map +1 -1
  60. package/dist/browser-prod/cart-queries.mjs +43 -81
  61. package/dist/browser-prod/cart-queries.mjs.map +1 -1
  62. package/dist/browser-prod/codegen.helpers.mjs.map +1 -1
  63. package/dist/browser-prod/cookies-utils.mjs.map +1 -1
  64. package/dist/browser-prod/flatten-connection.mjs.map +1 -1
  65. package/dist/browser-prod/getProductOptions.mjs.map +1 -1
  66. package/dist/browser-prod/load-script.mjs.map +1 -1
  67. package/dist/browser-prod/optionValueDecoder.mjs.map +1 -1
  68. package/dist/browser-prod/packages/hydrogen-react/package.json.mjs +1 -1
  69. package/dist/browser-prod/parse-metafield.mjs.map +1 -1
  70. package/dist/browser-prod/storefront-client.mjs.map +1 -1
  71. package/dist/browser-prod/tracking-utils.mjs.map +1 -1
  72. package/dist/browser-prod/useMoney.mjs.map +1 -1
  73. package/dist/browser-prod/useSelectedOptionInUrlParam.mjs.map +1 -1
  74. package/dist/browser-prod/useShopifyCookies.mjs.map +1 -1
  75. package/dist/node-dev/AddToCartButton.js.map +1 -1
  76. package/dist/node-dev/AddToCartButton.mjs.map +1 -1
  77. package/dist/node-dev/BuyNowButton.js.map +1 -1
  78. package/dist/node-dev/BuyNowButton.mjs.map +1 -1
  79. package/dist/node-dev/CartCheckoutButton.js.map +1 -1
  80. package/dist/node-dev/CartCheckoutButton.mjs.map +1 -1
  81. package/dist/node-dev/CartCost.js.map +1 -1
  82. package/dist/node-dev/CartCost.mjs.map +1 -1
  83. package/dist/node-dev/CartLineProvider.js.map +1 -1
  84. package/dist/node-dev/CartLineProvider.mjs.map +1 -1
  85. package/dist/node-dev/CartLineQuantity.js.map +1 -1
  86. package/dist/node-dev/CartLineQuantity.mjs.map +1 -1
  87. package/dist/node-dev/CartLineQuantityAdjustButton.js.map +1 -1
  88. package/dist/node-dev/CartLineQuantityAdjustButton.mjs.map +1 -1
  89. package/dist/node-dev/CartProvider.js.map +1 -1
  90. package/dist/node-dev/CartProvider.mjs.map +1 -1
  91. package/dist/node-dev/ExternalVideo.js.map +1 -1
  92. package/dist/node-dev/ExternalVideo.mjs.map +1 -1
  93. package/dist/node-dev/Image.js.map +1 -1
  94. package/dist/node-dev/Image.mjs.map +1 -1
  95. package/dist/node-dev/MediaFile.js.map +1 -1
  96. package/dist/node-dev/MediaFile.mjs.map +1 -1
  97. package/dist/node-dev/ModelViewer.js.map +1 -1
  98. package/dist/node-dev/ModelViewer.mjs.map +1 -1
  99. package/dist/node-dev/Money.js.map +1 -1
  100. package/dist/node-dev/Money.mjs.map +1 -1
  101. package/dist/node-dev/ProductPrice.js.map +1 -1
  102. package/dist/node-dev/ProductPrice.mjs.map +1 -1
  103. package/dist/node-dev/ProductProvider.js.map +1 -1
  104. package/dist/node-dev/ProductProvider.mjs.map +1 -1
  105. package/dist/node-dev/RichText.js.map +1 -1
  106. package/dist/node-dev/RichText.mjs.map +1 -1
  107. package/dist/node-dev/ShopPayButton.js.map +1 -1
  108. package/dist/node-dev/ShopPayButton.mjs.map +1 -1
  109. package/dist/node-dev/ShopifyProvider.js.map +1 -1
  110. package/dist/node-dev/ShopifyProvider.mjs.map +1 -1
  111. package/dist/node-dev/Video.js.map +1 -1
  112. package/dist/node-dev/Video.mjs.map +1 -1
  113. package/dist/node-dev/analytics-constants.js.map +1 -1
  114. package/dist/node-dev/analytics-constants.mjs.map +1 -1
  115. package/dist/node-dev/analytics-utils.js.map +1 -1
  116. package/dist/node-dev/analytics-utils.mjs.map +1 -1
  117. package/dist/node-dev/analytics.js.map +1 -1
  118. package/dist/node-dev/analytics.mjs.map +1 -1
  119. package/dist/node-dev/cart-queries.js +43 -81
  120. package/dist/node-dev/cart-queries.js.map +1 -1
  121. package/dist/node-dev/cart-queries.mjs +43 -81
  122. package/dist/node-dev/cart-queries.mjs.map +1 -1
  123. package/dist/node-dev/codegen.helpers.js.map +1 -1
  124. package/dist/node-dev/codegen.helpers.mjs.map +1 -1
  125. package/dist/node-dev/cookies-utils.js.map +1 -1
  126. package/dist/node-dev/cookies-utils.mjs.map +1 -1
  127. package/dist/node-dev/flatten-connection.js.map +1 -1
  128. package/dist/node-dev/flatten-connection.mjs.map +1 -1
  129. package/dist/node-dev/getProductOptions.js.map +1 -1
  130. package/dist/node-dev/getProductOptions.mjs.map +1 -1
  131. package/dist/node-dev/load-script.js.map +1 -1
  132. package/dist/node-dev/load-script.mjs.map +1 -1
  133. package/dist/node-dev/optionValueDecoder.js.map +1 -1
  134. package/dist/node-dev/optionValueDecoder.mjs.map +1 -1
  135. package/dist/node-dev/packages/hydrogen-react/package.json.js +1 -1
  136. package/dist/node-dev/packages/hydrogen-react/package.json.mjs +1 -1
  137. package/dist/node-dev/parse-metafield.js.map +1 -1
  138. package/dist/node-dev/parse-metafield.mjs.map +1 -1
  139. package/dist/node-dev/storefront-client.js.map +1 -1
  140. package/dist/node-dev/storefront-client.mjs.map +1 -1
  141. package/dist/node-dev/tracking-utils.js.map +1 -1
  142. package/dist/node-dev/tracking-utils.mjs.map +1 -1
  143. package/dist/node-dev/useMoney.js.map +1 -1
  144. package/dist/node-dev/useMoney.mjs.map +1 -1
  145. package/dist/node-dev/useSelectedOptionInUrlParam.js.map +1 -1
  146. package/dist/node-dev/useSelectedOptionInUrlParam.mjs.map +1 -1
  147. package/dist/node-dev/useShopifyCookies.js.map +1 -1
  148. package/dist/node-dev/useShopifyCookies.mjs.map +1 -1
  149. package/dist/node-prod/AddToCartButton.js.map +1 -1
  150. package/dist/node-prod/AddToCartButton.mjs.map +1 -1
  151. package/dist/node-prod/BuyNowButton.js.map +1 -1
  152. package/dist/node-prod/BuyNowButton.mjs.map +1 -1
  153. package/dist/node-prod/CartCheckoutButton.js.map +1 -1
  154. package/dist/node-prod/CartCheckoutButton.mjs.map +1 -1
  155. package/dist/node-prod/CartCost.js.map +1 -1
  156. package/dist/node-prod/CartCost.mjs.map +1 -1
  157. package/dist/node-prod/CartLineProvider.js.map +1 -1
  158. package/dist/node-prod/CartLineProvider.mjs.map +1 -1
  159. package/dist/node-prod/CartLineQuantity.js.map +1 -1
  160. package/dist/node-prod/CartLineQuantity.mjs.map +1 -1
  161. package/dist/node-prod/CartLineQuantityAdjustButton.js.map +1 -1
  162. package/dist/node-prod/CartLineQuantityAdjustButton.mjs.map +1 -1
  163. package/dist/node-prod/CartProvider.js.map +1 -1
  164. package/dist/node-prod/CartProvider.mjs.map +1 -1
  165. package/dist/node-prod/ExternalVideo.js.map +1 -1
  166. package/dist/node-prod/ExternalVideo.mjs.map +1 -1
  167. package/dist/node-prod/Image.js.map +1 -1
  168. package/dist/node-prod/Image.mjs.map +1 -1
  169. package/dist/node-prod/MediaFile.js.map +1 -1
  170. package/dist/node-prod/MediaFile.mjs.map +1 -1
  171. package/dist/node-prod/ModelViewer.js.map +1 -1
  172. package/dist/node-prod/ModelViewer.mjs.map +1 -1
  173. package/dist/node-prod/Money.js.map +1 -1
  174. package/dist/node-prod/Money.mjs.map +1 -1
  175. package/dist/node-prod/ProductPrice.js.map +1 -1
  176. package/dist/node-prod/ProductPrice.mjs.map +1 -1
  177. package/dist/node-prod/ProductProvider.js.map +1 -1
  178. package/dist/node-prod/ProductProvider.mjs.map +1 -1
  179. package/dist/node-prod/RichText.js.map +1 -1
  180. package/dist/node-prod/RichText.mjs.map +1 -1
  181. package/dist/node-prod/ShopPayButton.js.map +1 -1
  182. package/dist/node-prod/ShopPayButton.mjs.map +1 -1
  183. package/dist/node-prod/ShopifyProvider.js.map +1 -1
  184. package/dist/node-prod/ShopifyProvider.mjs.map +1 -1
  185. package/dist/node-prod/Video.js.map +1 -1
  186. package/dist/node-prod/Video.mjs.map +1 -1
  187. package/dist/node-prod/analytics-constants.js.map +1 -1
  188. package/dist/node-prod/analytics-constants.mjs.map +1 -1
  189. package/dist/node-prod/analytics-utils.js.map +1 -1
  190. package/dist/node-prod/analytics-utils.mjs.map +1 -1
  191. package/dist/node-prod/analytics.js.map +1 -1
  192. package/dist/node-prod/analytics.mjs.map +1 -1
  193. package/dist/node-prod/cart-queries.js +43 -81
  194. package/dist/node-prod/cart-queries.js.map +1 -1
  195. package/dist/node-prod/cart-queries.mjs +43 -81
  196. package/dist/node-prod/cart-queries.mjs.map +1 -1
  197. package/dist/node-prod/codegen.helpers.js.map +1 -1
  198. package/dist/node-prod/codegen.helpers.mjs.map +1 -1
  199. package/dist/node-prod/cookies-utils.js.map +1 -1
  200. package/dist/node-prod/cookies-utils.mjs.map +1 -1
  201. package/dist/node-prod/flatten-connection.js.map +1 -1
  202. package/dist/node-prod/flatten-connection.mjs.map +1 -1
  203. package/dist/node-prod/getProductOptions.js.map +1 -1
  204. package/dist/node-prod/getProductOptions.mjs.map +1 -1
  205. package/dist/node-prod/load-script.js.map +1 -1
  206. package/dist/node-prod/load-script.mjs.map +1 -1
  207. package/dist/node-prod/optionValueDecoder.js.map +1 -1
  208. package/dist/node-prod/optionValueDecoder.mjs.map +1 -1
  209. package/dist/node-prod/packages/hydrogen-react/package.json.js +1 -1
  210. package/dist/node-prod/packages/hydrogen-react/package.json.mjs +1 -1
  211. package/dist/node-prod/parse-metafield.js.map +1 -1
  212. package/dist/node-prod/parse-metafield.mjs.map +1 -1
  213. package/dist/node-prod/storefront-client.js.map +1 -1
  214. package/dist/node-prod/storefront-client.mjs.map +1 -1
  215. package/dist/node-prod/tracking-utils.js.map +1 -1
  216. package/dist/node-prod/tracking-utils.mjs.map +1 -1
  217. package/dist/node-prod/useMoney.js.map +1 -1
  218. package/dist/node-prod/useMoney.mjs.map +1 -1
  219. package/dist/node-prod/useSelectedOptionInUrlParam.js.map +1 -1
  220. package/dist/node-prod/useSelectedOptionInUrlParam.mjs.map +1 -1
  221. package/dist/node-prod/useShopifyCookies.js.map +1 -1
  222. package/dist/node-prod/useShopifyCookies.mjs.map +1 -1
  223. package/dist/types/AddToCartButton.d.ts +2 -0
  224. package/dist/types/BuyNowButton.d.ts +2 -0
  225. package/dist/types/CartCheckoutButton.d.ts +2 -0
  226. package/dist/types/CartCost.d.ts +2 -0
  227. package/dist/types/CartLineProvider.d.ts +2 -0
  228. package/dist/types/CartLineQuantity.d.ts +2 -0
  229. package/dist/types/CartLineQuantityAdjustButton.d.ts +1 -0
  230. package/dist/types/CartProvider.d.ts +7 -1
  231. package/dist/types/ExternalVideo.d.ts +4 -0
  232. package/dist/types/Image.d.ts +2 -0
  233. package/dist/types/MediaFile.d.ts +4 -0
  234. package/dist/types/ModelViewer.d.ts +1 -0
  235. package/dist/types/Money.d.ts +1 -0
  236. package/dist/types/ProductPrice.d.ts +2 -0
  237. package/dist/types/ProductProvider.d.ts +2 -0
  238. package/dist/types/RichText.d.ts +2 -0
  239. package/dist/types/ShopPayButton.d.ts +1 -0
  240. package/dist/types/ShopifyProvider.d.ts +2 -0
  241. package/dist/types/Video.d.ts +2 -0
  242. package/dist/types/analytics-constants.d.ts +9 -0
  243. package/dist/types/analytics-types.d.ts +1 -0
  244. package/dist/types/analytics-utils.d.ts +1 -0
  245. package/dist/types/analytics.d.ts +6 -1
  246. package/dist/types/cart-queries.d.ts +13 -9
  247. package/dist/types/codegen.helpers.d.ts +2 -0
  248. package/dist/types/cookies-utils.d.ts +2 -1
  249. package/dist/types/flatten-connection.d.ts +3 -0
  250. package/dist/types/getProductOptions.d.ts +3 -0
  251. package/dist/types/load-script.d.ts +3 -1
  252. package/dist/types/optionValueDecoder.d.ts +3 -0
  253. package/dist/types/parse-metafield.d.ts +1 -0
  254. package/dist/types/storefront-client.d.ts +1 -0
  255. package/dist/types/tracking-utils.d.ts +2 -2
  256. package/dist/types/useMoney.d.ts +1 -0
  257. package/dist/types/useSelectedOptionInUrlParam.d.ts +1 -0
  258. package/dist/types/useShopifyCookies.d.ts +1 -0
  259. package/dist/umd/hydrogen-react.dev.js +44 -82
  260. package/dist/umd/hydrogen-react.dev.js.map +1 -1
  261. package/dist/umd/hydrogen-react.prod.js +43 -88
  262. package/dist/umd/hydrogen-react.prod.js.map +1 -1
  263. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"useMoney.mjs","sources":["../../src/useMoney.tsx"],"sourcesContent":["import {useMemo} from 'react';\nimport {useShop} from './ShopifyProvider.js';\nimport {\n CurrencyCode as StorefrontApiCurrencyCode,\n MoneyV2 as StorefrontApiMoneyV2,\n} from './storefront-api-types.js';\nimport type {\n MoneyV2 as CustomerAccountApiMoneyV2,\n CurrencyCode as CustomerAccountApiCurrencyCode,\n} from './customer-account-api-types.js';\n\n// Support MoneyV2 from both Storefront API and Customer Account API\n// The APIs may have different CurrencyCode enums\n/**\n * Supports MoneyV2 from both Storefront API and Customer Account API.\n * The APIs may have different CurrencyCode enums (e.g., Customer Account API added USDC in 2025-10, but Storefront API doesn't support USDC in 2025-10).\n * This union type ensures useMoney works with data from either API.\n */\ntype MoneyV2 = StorefrontApiMoneyV2 | CustomerAccountApiMoneyV2;\n\n/**\n * Supports CurrencyCode from both Storefront API and Customer Account API. The APIs may have different CurrencyCode enums (e.g., Customer Account API added USDC in 2025-10, but Storefront API doesn't support USDC in 2025-10).\n * This union type ensures useMoney works with data from either API.\n */\ntype CurrencyCode = StorefrontApiCurrencyCode | CustomerAccountApiCurrencyCode;\n\nexport type UseMoneyValue = {\n /**\n * The currency code from the `MoneyV2` object.\n */\n currencyCode: CurrencyCode;\n /**\n * The name for the currency code, returned by `Intl.NumberFormat`.\n */\n currencyName?: string;\n /**\n * The currency symbol returned by `Intl.NumberFormat`.\n */\n currencySymbol?: string;\n /**\n * The currency narrow symbol returned by `Intl.NumberFormat`.\n */\n currencyNarrowSymbol?: string;\n /**\n * The localized amount, without any currency symbols or non-number types from the `Intl.NumberFormat.formatToParts` parts.\n */\n amount: string;\n /**\n * All parts returned by `Intl.NumberFormat.formatToParts`.\n */\n parts: Intl.NumberFormatPart[];\n /**\n * A string returned by `new Intl.NumberFormat` for the amount and currency code,\n * using the `locale` value in the [`LocalizationProvider` component](https://shopify.dev/api/hydrogen/components/localization/localizationprovider).\n */\n localizedString: string;\n /**\n * The `MoneyV2` object provided as an argument to the hook.\n */\n original: MoneyV2;\n /**\n * A string with trailing zeros removed from the fractional part, if any exist. If there are no trailing zeros, then the fractional part remains.\n * For example, `$640.00` turns into `$640`.\n * `$640.42` remains `$640.42`.\n */\n withoutTrailingZeros: string;\n /**\n * A string without currency and without trailing zeros removed from the fractional part, if any exist. If there are no trailing zeros, then the fractional part remains.\n * For example, `$640.00` turns into `640`.\n * `$640.42` turns into `640.42`.\n */\n withoutTrailingZerosAndCurrency: string;\n};\n\n/**\n * The `useMoney` hook takes a [MoneyV2 object from the Storefront API](https://shopify.dev/docs/api/storefront/2026-04/objects/MoneyV2)\n * or a [MoneyV2 object from the Customer Account API](https://shopify.dev/docs/api/customer/2026-04/objects/moneyv2) and returns a\n * default-formatted string of the amount with the correct currency indicator, along with some of the parts provided by\n * [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).\n * Uses `locale` from `ShopifyProvider`\n * &nbsp;\n * @see {@link https://shopify.dev/api/hydrogen/hooks/usemoney}\n * @example initialize the money object\n * ```ts\n * const money = useMoney({\n * amount: '100.00',\n * currencyCode: 'USD'\n * })\n * ```\n * &nbsp;\n *\n * @example basic usage, outputs: $100.00\n * ```ts\n * money.localizedString\n * ```\n * &nbsp;\n *\n * @example without currency, outputs: 100.00\n * ```ts\n * money.amount\n * ```\n * &nbsp;\n *\n * @example without trailing zeros, outputs: $100\n * ```ts\n * money.withoutTrailingZeros\n * ```\n * &nbsp;\n *\n * @example currency name, outputs: US dollars\n * ```ts\n * money.currencyCode\n * ```\n * &nbsp;\n *\n * @example currency symbol, outputs: $\n * ```ts\n * money.currencySymbol\n * ```\n * &nbsp;\n *\n * @example without currency and without trailing zeros, outputs: 100\n * ```ts\n * money.withoutTrailingZerosAndCurrency\n * ```\n */\nexport function useMoney(money: MoneyV2): UseMoneyValue {\n const {countryIsoCode, languageIsoCode} = useShop();\n const locale = languageIsoCode.includes('_')\n ? languageIsoCode.replace('_', '-')\n : `${languageIsoCode}-${countryIsoCode}`;\n\n if (!locale) {\n throw new Error(\n `useMoney(): Unable to get 'locale' from 'useShop()', which means that 'locale' was not passed to '<ShopifyProvider/>'. 'locale' is required for 'useMoney()' to work`,\n );\n }\n\n const amount = parseFloat(money.amount);\n\n // Check if the currency code is supported by Intl.NumberFormat\n let isCurrencySupported = true;\n try {\n new Intl.NumberFormat(locale, {\n style: 'currency',\n currency: money.currencyCode,\n });\n } catch (e) {\n if (e instanceof RangeError && e.message.includes('currency')) {\n isCurrencySupported = false;\n }\n }\n\n const {\n defaultFormatter,\n nameFormatter,\n narrowSymbolFormatter,\n withoutTrailingZerosFormatter,\n withoutCurrencyFormatter,\n withoutTrailingZerosOrCurrencyFormatter,\n } = useMemo(() => {\n // For unsupported currencies (like USDC cryptocurrency), use decimal formatting with 2 decimal places\n // We default to 2 decimal places based on research showing USDC displays like USD to reinforce its 1:1 peg\n const options = isCurrencySupported\n ? {\n style: 'currency' as const,\n currency: money.currencyCode,\n }\n : {\n style: 'decimal' as const,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n };\n\n return {\n defaultFormatter: getLazyFormatter(locale, options),\n nameFormatter: getLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'name',\n }),\n narrowSymbolFormatter: getLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'narrowSymbol',\n }),\n withoutTrailingZerosFormatter: getLazyFormatter(locale, {\n ...options,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }),\n withoutCurrencyFormatter: getLazyFormatter(locale, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }),\n withoutTrailingZerosOrCurrencyFormatter: getLazyFormatter(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }),\n };\n }, [money.currencyCode, locale, isCurrencySupported]);\n\n const isPartCurrency = (part: Intl.NumberFormatPart): boolean =>\n part.type === 'currency';\n\n // By wrapping these properties in functions, we only\n // create formatters if they are going to be used.\n const lazyFormatters = useMemo(\n () => ({\n original: (): MoneyV2 => money,\n currencyCode: (): CurrencyCode => money.currencyCode,\n\n localizedString: (): string => {\n const formatted = defaultFormatter().format(amount);\n // For unsupported currencies, append the currency code\n return isCurrencySupported\n ? formatted\n : `${formatted} ${money.currencyCode}`;\n },\n\n parts: (): Intl.NumberFormatPart[] => {\n const parts = defaultFormatter().formatToParts(amount);\n // For unsupported currencies, add currency code as a currency part\n if (!isCurrencySupported) {\n parts.push(\n {type: 'literal', value: ' '},\n {type: 'currency', value: money.currencyCode},\n );\n }\n return parts;\n },\n\n withoutTrailingZeros: (): string => {\n const formatted =\n amount % 1 === 0\n ? withoutTrailingZerosFormatter().format(amount)\n : defaultFormatter().format(amount);\n // For unsupported currencies, append the currency code\n return isCurrencySupported\n ? formatted\n : `${formatted} ${money.currencyCode}`;\n },\n\n withoutTrailingZerosAndCurrency: (): string =>\n amount % 1 === 0\n ? withoutTrailingZerosOrCurrencyFormatter().format(amount)\n : withoutCurrencyFormatter().format(amount),\n\n currencyName: (): string =>\n nameFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"US dollars\"\n\n currencySymbol: (): string =>\n defaultFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"USD\"\n\n currencyNarrowSymbol: (): string =>\n narrowSymbolFormatter().formatToParts(amount).find(isPartCurrency)\n ?.value ?? '', // e.g. \"$\"\n\n amount: (): string =>\n defaultFormatter()\n .formatToParts(amount)\n .filter((part) =>\n ['decimal', 'fraction', 'group', 'integer', 'literal'].includes(\n part.type,\n ),\n )\n .map((part) => part.value)\n .join(''),\n }),\n [\n money,\n amount,\n isCurrencySupported,\n nameFormatter,\n defaultFormatter,\n narrowSymbolFormatter,\n withoutCurrencyFormatter,\n withoutTrailingZerosFormatter,\n withoutTrailingZerosOrCurrencyFormatter,\n ],\n );\n\n // Call functions automatically when the properties are accessed\n // to keep these functions as an implementation detail.\n return useMemo(\n () =>\n new Proxy(lazyFormatters as unknown as UseMoneyValue, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call\n get: (target, key) => Reflect.get(target, key)?.call(null),\n }),\n [lazyFormatters],\n );\n}\n\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\nfunction getLazyFormatter(\n locale: string,\n options?: Intl.NumberFormatOptions,\n): () => Intl.NumberFormat {\n const key = JSON.stringify([locale, options]);\n\n return function (): Intl.NumberFormat {\n let formatter = formatterCache.get(key);\n if (!formatter) {\n try {\n formatter = new Intl.NumberFormat(locale, options);\n } catch (error) {\n // Handle unsupported currency codes (e.g., USDC from Customer Account API)\n // Fall back to formatting without currency\n if (error instanceof RangeError && error.message.includes('currency')) {\n const fallbackOptions = {...options};\n delete fallbackOptions.currency;\n delete fallbackOptions.currencyDisplay;\n delete fallbackOptions.currencySign;\n formatter = new Intl.NumberFormat(locale, fallbackOptions);\n } else {\n throw error;\n }\n }\n formatterCache.set(key, formatter);\n }\n return formatter;\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useMoney.mjs","sources":["../../src/useMoney.tsx"],"sourcesContent":["import {useMemo} from 'react';\nimport {useShop} from './ShopifyProvider.js';\nimport {\n CurrencyCode as StorefrontApiCurrencyCode,\n MoneyV2 as StorefrontApiMoneyV2,\n} from './storefront-api-types.js';\nimport type {\n MoneyV2 as CustomerAccountApiMoneyV2,\n CurrencyCode as CustomerAccountApiCurrencyCode,\n} from './customer-account-api-types.js';\n\n// Support MoneyV2 from both Storefront API and Customer Account API\n// The APIs may have different CurrencyCode enums\n/**\n * Supports MoneyV2 from both Storefront API and Customer Account API.\n * The APIs may have different CurrencyCode enums (e.g., Customer Account API added USDC in 2025-10, but Storefront API doesn't support USDC in 2025-10).\n * This union type ensures useMoney works with data from either API.\n */\ntype MoneyV2 = StorefrontApiMoneyV2 | CustomerAccountApiMoneyV2;\n\n/**\n * Supports CurrencyCode from both Storefront API and Customer Account API. The APIs may have different CurrencyCode enums (e.g., Customer Account API added USDC in 2025-10, but Storefront API doesn't support USDC in 2025-10).\n * This union type ensures useMoney works with data from either API.\n */\ntype CurrencyCode = StorefrontApiCurrencyCode | CustomerAccountApiCurrencyCode;\n\nexport type UseMoneyValue = {\n /**\n * The currency code from the `MoneyV2` object.\n */\n currencyCode: CurrencyCode;\n /**\n * The name for the currency code, returned by `Intl.NumberFormat`.\n */\n currencyName?: string;\n /**\n * The currency symbol returned by `Intl.NumberFormat`.\n */\n currencySymbol?: string;\n /**\n * The currency narrow symbol returned by `Intl.NumberFormat`.\n */\n currencyNarrowSymbol?: string;\n /**\n * The localized amount, without any currency symbols or non-number types from the `Intl.NumberFormat.formatToParts` parts.\n */\n amount: string;\n /**\n * All parts returned by `Intl.NumberFormat.formatToParts`.\n */\n parts: Intl.NumberFormatPart[];\n /**\n * A string returned by `new Intl.NumberFormat` for the amount and currency code,\n * using the `locale` value in the [`LocalizationProvider` component](https://shopify.dev/api/hydrogen/components/localization/localizationprovider).\n */\n localizedString: string;\n /**\n * The `MoneyV2` object provided as an argument to the hook.\n */\n original: MoneyV2;\n /**\n * A string with trailing zeros removed from the fractional part, if any exist. If there are no trailing zeros, then the fractional part remains.\n * For example, `$640.00` turns into `$640`.\n * `$640.42` remains `$640.42`.\n */\n withoutTrailingZeros: string;\n /**\n * A string without currency and without trailing zeros removed from the fractional part, if any exist. If there are no trailing zeros, then the fractional part remains.\n * For example, `$640.00` turns into `640`.\n * `$640.42` turns into `640.42`.\n */\n withoutTrailingZerosAndCurrency: string;\n};\n\n/**\n * The `useMoney` hook takes a [MoneyV2 object from the Storefront API](https://shopify.dev/docs/api/storefront/2026-04/objects/MoneyV2)\n * or a [MoneyV2 object from the Customer Account API](https://shopify.dev/docs/api/customer/2026-04/objects/moneyv2) and returns a\n * default-formatted string of the amount with the correct currency indicator, along with some of the parts provided by\n * [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).\n * Uses `locale` from `ShopifyProvider`\n * &nbsp;\n * @see {@link https://shopify.dev/api/hydrogen/hooks/usemoney}\n * @example initialize the money object\n * ```ts\n * const money = useMoney({\n * amount: '100.00',\n * currencyCode: 'USD'\n * })\n * ```\n * &nbsp;\n *\n * @example basic usage, outputs: $100.00\n * ```ts\n * money.localizedString\n * ```\n * &nbsp;\n *\n * @example without currency, outputs: 100.00\n * ```ts\n * money.amount\n * ```\n * &nbsp;\n *\n * @example without trailing zeros, outputs: $100\n * ```ts\n * money.withoutTrailingZeros\n * ```\n * &nbsp;\n *\n * @example currency name, outputs: US dollars\n * ```ts\n * money.currencyCode\n * ```\n * &nbsp;\n *\n * @example currency symbol, outputs: $\n * ```ts\n * money.currencySymbol\n * ```\n * &nbsp;\n *\n * @example without currency and without trailing zeros, outputs: 100\n * ```ts\n * money.withoutTrailingZerosAndCurrency\n * ```\n * @publicDocs\n */\nexport function useMoney(money: MoneyV2): UseMoneyValue {\n const {countryIsoCode, languageIsoCode} = useShop();\n const locale = languageIsoCode.includes('_')\n ? languageIsoCode.replace('_', '-')\n : `${languageIsoCode}-${countryIsoCode}`;\n\n if (!locale) {\n throw new Error(\n `useMoney(): Unable to get 'locale' from 'useShop()', which means that 'locale' was not passed to '<ShopifyProvider/>'. 'locale' is required for 'useMoney()' to work`,\n );\n }\n\n const amount = parseFloat(money.amount);\n\n // Check if the currency code is supported by Intl.NumberFormat\n let isCurrencySupported = true;\n try {\n new Intl.NumberFormat(locale, {\n style: 'currency',\n currency: money.currencyCode,\n });\n } catch (e) {\n if (e instanceof RangeError && e.message.includes('currency')) {\n isCurrencySupported = false;\n }\n }\n\n const {\n defaultFormatter,\n nameFormatter,\n narrowSymbolFormatter,\n withoutTrailingZerosFormatter,\n withoutCurrencyFormatter,\n withoutTrailingZerosOrCurrencyFormatter,\n } = useMemo(() => {\n // For unsupported currencies (like USDC cryptocurrency), use decimal formatting with 2 decimal places\n // We default to 2 decimal places based on research showing USDC displays like USD to reinforce its 1:1 peg\n const options = isCurrencySupported\n ? {\n style: 'currency' as const,\n currency: money.currencyCode,\n }\n : {\n style: 'decimal' as const,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n };\n\n return {\n defaultFormatter: getLazyFormatter(locale, options),\n nameFormatter: getLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'name',\n }),\n narrowSymbolFormatter: getLazyFormatter(locale, {\n ...options,\n currencyDisplay: 'narrowSymbol',\n }),\n withoutTrailingZerosFormatter: getLazyFormatter(locale, {\n ...options,\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }),\n withoutCurrencyFormatter: getLazyFormatter(locale, {\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }),\n withoutTrailingZerosOrCurrencyFormatter: getLazyFormatter(locale, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n }),\n };\n }, [money.currencyCode, locale, isCurrencySupported]);\n\n const isPartCurrency = (part: Intl.NumberFormatPart): boolean =>\n part.type === 'currency';\n\n // By wrapping these properties in functions, we only\n // create formatters if they are going to be used.\n const lazyFormatters = useMemo(\n () => ({\n original: (): MoneyV2 => money,\n currencyCode: (): CurrencyCode => money.currencyCode,\n\n localizedString: (): string => {\n const formatted = defaultFormatter().format(amount);\n // For unsupported currencies, append the currency code\n return isCurrencySupported\n ? formatted\n : `${formatted} ${money.currencyCode}`;\n },\n\n parts: (): Intl.NumberFormatPart[] => {\n const parts = defaultFormatter().formatToParts(amount);\n // For unsupported currencies, add currency code as a currency part\n if (!isCurrencySupported) {\n parts.push(\n {type: 'literal', value: ' '},\n {type: 'currency', value: money.currencyCode},\n );\n }\n return parts;\n },\n\n withoutTrailingZeros: (): string => {\n const formatted =\n amount % 1 === 0\n ? withoutTrailingZerosFormatter().format(amount)\n : defaultFormatter().format(amount);\n // For unsupported currencies, append the currency code\n return isCurrencySupported\n ? formatted\n : `${formatted} ${money.currencyCode}`;\n },\n\n withoutTrailingZerosAndCurrency: (): string =>\n amount % 1 === 0\n ? withoutTrailingZerosOrCurrencyFormatter().format(amount)\n : withoutCurrencyFormatter().format(amount),\n\n currencyName: (): string =>\n nameFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"US dollars\"\n\n currencySymbol: (): string =>\n defaultFormatter().formatToParts(amount).find(isPartCurrency)?.value ??\n money.currencyCode, // e.g. \"USD\"\n\n currencyNarrowSymbol: (): string =>\n narrowSymbolFormatter().formatToParts(amount).find(isPartCurrency)\n ?.value ?? '', // e.g. \"$\"\n\n amount: (): string =>\n defaultFormatter()\n .formatToParts(amount)\n .filter((part) =>\n ['decimal', 'fraction', 'group', 'integer', 'literal'].includes(\n part.type,\n ),\n )\n .map((part) => part.value)\n .join(''),\n }),\n [\n money,\n amount,\n isCurrencySupported,\n nameFormatter,\n defaultFormatter,\n narrowSymbolFormatter,\n withoutCurrencyFormatter,\n withoutTrailingZerosFormatter,\n withoutTrailingZerosOrCurrencyFormatter,\n ],\n );\n\n // Call functions automatically when the properties are accessed\n // to keep these functions as an implementation detail.\n return useMemo(\n () =>\n new Proxy(lazyFormatters as unknown as UseMoneyValue, {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call\n get: (target, key) => Reflect.get(target, key)?.call(null),\n }),\n [lazyFormatters],\n );\n}\n\nconst formatterCache = new Map<string, Intl.NumberFormat>();\n\nfunction getLazyFormatter(\n locale: string,\n options?: Intl.NumberFormatOptions,\n): () => Intl.NumberFormat {\n const key = JSON.stringify([locale, options]);\n\n return function (): Intl.NumberFormat {\n let formatter = formatterCache.get(key);\n if (!formatter) {\n try {\n formatter = new Intl.NumberFormat(locale, options);\n } catch (error) {\n // Handle unsupported currency codes (e.g., USDC from Customer Account API)\n // Fall back to formatting without currency\n if (error instanceof RangeError && error.message.includes('currency')) {\n const fallbackOptions = {...options};\n delete fallbackOptions.currency;\n delete fallbackOptions.currencyDisplay;\n delete fallbackOptions.currencySign;\n formatter = new Intl.NumberFormat(locale, fallbackOptions);\n } else {\n throw error;\n }\n }\n formatterCache.set(key, formatter);\n }\n return formatter;\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useSelectedOptionInUrlParam.mjs","sources":["../../src/useSelectedOptionInUrlParam.tsx"],"sourcesContent":["import {useEffect} from 'react';\nimport {mapSelectedProductOptionToObject} from './getProductOptions.js';\nimport {SelectedOption} from './storefront-api-types.js';\n\nexport function useSelectedOptionInUrlParam(\n selectedOptions: Pick<SelectedOption, 'name' | 'value'>[],\n): null {\n useEffect(() => {\n const optionsSearchParams = new URLSearchParams(\n mapSelectedProductOptionToObject(selectedOptions || []),\n );\n const currentSearchParams = new URLSearchParams(window.location.search);\n\n // ts ignoring the URLSearchParams not iterable error for now\n // https://stackoverflow.com/questions/72522489/urlsearchparams-not-accepting-string#answer-72522838\n // TODO: update ts lib\n const combinedSearchParams = new URLSearchParams({\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n ...Object.fromEntries(currentSearchParams),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n ...Object.fromEntries(optionsSearchParams),\n });\n\n if (combinedSearchParams.size > 0) {\n window.history.replaceState(\n {},\n '',\n `${window.location.pathname}?${combinedSearchParams.toString()}`,\n );\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(selectedOptions)]);\n\n return null;\n}\n"],"names":[],"mappings":";;AAIO,SAAS,4BACd,iBACM;AACN,YAAU,MAAM;AACd,UAAM,sBAAsB,IAAI;AAAA,MAC9B,iCAAiC,mBAAmB,CAAA,CAAE;AAAA,IAAA;AAExD,UAAM,sBAAsB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AAKtE,UAAM,uBAAuB,IAAI,gBAAgB;AAAA;AAAA;AAAA,MAG/C,GAAG,OAAO,YAAY,mBAAmB;AAAA;AAAA;AAAA,MAGzC,GAAG,OAAO,YAAY,mBAAmB;AAAA,IAAA,CAC1C;AAED,QAAI,qBAAqB,OAAO,GAAG;AACjC,aAAO,QAAQ;AAAA,QACb,CAAA;AAAA,QACA;AAAA,QACA,GAAG,OAAO,SAAS,QAAQ,IAAI,qBAAqB,UAAU;AAAA,MAAA;AAAA,IAElE;AAAA,EAEF,GAAG,CAAC,KAAK,UAAU,eAAe,CAAC,CAAC;AAEpC,SAAO;AACT;"}
1
+ {"version":3,"file":"useSelectedOptionInUrlParam.mjs","sources":["../../src/useSelectedOptionInUrlParam.tsx"],"sourcesContent":["import {useEffect} from 'react';\nimport {mapSelectedProductOptionToObject} from './getProductOptions.js';\nimport {SelectedOption} from './storefront-api-types.js';\n\n/** @publicDocs */\nexport function useSelectedOptionInUrlParam(\n selectedOptions: Pick<SelectedOption, 'name' | 'value'>[],\n): null {\n useEffect(() => {\n const optionsSearchParams = new URLSearchParams(\n mapSelectedProductOptionToObject(selectedOptions || []),\n );\n const currentSearchParams = new URLSearchParams(window.location.search);\n\n // ts ignoring the URLSearchParams not iterable error for now\n // https://stackoverflow.com/questions/72522489/urlsearchparams-not-accepting-string#answer-72522838\n // TODO: update ts lib\n const combinedSearchParams = new URLSearchParams({\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n ...Object.fromEntries(currentSearchParams),\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n ...Object.fromEntries(optionsSearchParams),\n });\n\n if (combinedSearchParams.size > 0) {\n window.history.replaceState(\n {},\n '',\n `${window.location.pathname}?${combinedSearchParams.toString()}`,\n );\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(selectedOptions)]);\n\n return null;\n}\n"],"names":[],"mappings":";;AAKO,SAAS,4BACd,iBACM;AACN,YAAU,MAAM;AACd,UAAM,sBAAsB,IAAI;AAAA,MAC9B,iCAAiC,mBAAmB,CAAA,CAAE;AAAA,IAAA;AAExD,UAAM,sBAAsB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AAKtE,UAAM,uBAAuB,IAAI,gBAAgB;AAAA;AAAA;AAAA,MAG/C,GAAG,OAAO,YAAY,mBAAmB;AAAA;AAAA;AAAA,MAGzC,GAAG,OAAO,YAAY,mBAAmB;AAAA,IAAA,CAC1C;AAED,QAAI,qBAAqB,OAAO,GAAG;AACjC,aAAO,QAAQ;AAAA,QACb,CAAA;AAAA,QACA;AAAA,QACA,GAAG,OAAO,SAAS,QAAQ,IAAI,qBAAqB,UAAU;AAAA,MAAA;AAAA,IAElE;AAAA,EAEF,GAAG,CAAC,KAAK,UAAU,eAAe,CAAC,CAAC;AAEpC,SAAO;AACT;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useShopifyCookies.mjs","sources":["../../src/useShopifyCookies.tsx"],"sourcesContent":["import {useEffect, useRef, useState} from 'react';\n// @ts-ignore - worktop/cookie types not properly exported\nimport {stringify} from 'worktop/cookie';\nimport {SHOPIFY_Y, SHOPIFY_S} from './cart-constants.js';\nimport {buildUUID} from './cookies-utils.js';\nimport {\n getTrackingValues,\n SHOPIFY_UNIQUE_TOKEN_HEADER,\n SHOPIFY_VISIT_TOKEN_HEADER,\n} from './tracking-utils.js';\n\nconst longTermLength = 60 * 60 * 24 * 360 * 1; // ~1 year expiry\nconst shortTermLength = 60 * 30; // 30 mins\n\ntype UseShopifyCookiesOptions = CoreShopifyCookiesOptions & {\n /**\n * If set to `false`, Shopify cookies will be removed.\n * If set to `true`, Shopify unique user token cookie will have cookie expiry of 1 year.\n * Defaults to false.\n **/\n hasUserConsent?: boolean;\n /**\n * The domain scope of the cookie. Defaults to empty string.\n **/\n domain?: string;\n /**\n * The checkout domain of the shop. Defaults to empty string. If set, the cookie domain will check if it can be set with the checkout domain.\n */\n checkoutDomain?: string;\n /**\n * If set to `true`, it skips modifying the deprecated shopify_y and shopify_s cookies.\n */\n ignoreDeprecatedCookies?: boolean;\n};\n\n/**\n * Sets the `shopify_y` and `shopify_s` cookies in the browser based on user consent\n * for backward compatibility support.\n *\n * If `fetchTrackingValues` is true, it makes a request to Storefront API\n * to fetch or refresh Shopiy analytics and marketing cookies and tracking values.\n * Generally speaking, this should only be needed if you're not using Hydrogen's\n * built-in analytics components and hooks that already handle this automatically.\n * For example, set it to `true` if you are using `hydrogen-react` only with\n * a different framework and still need to make a same-domain request to\n * Storefront API to set cookies.\n *\n * If `ignoreDeprecatedCookies` is true, it skips setting the deprecated cookies entirely.\n * Useful when you only want to use the newer tracking values and not rely on the deprecated ones.\n *\n * @returns `true` when cookies are set and ready.\n */\nexport function useShopifyCookies(options?: UseShopifyCookiesOptions): boolean {\n const {\n hasUserConsent,\n domain = '',\n checkoutDomain = '',\n storefrontAccessToken,\n fetchTrackingValues,\n ignoreDeprecatedCookies = false,\n } = options || {};\n\n const coreCookiesReady = useCoreShopifyCookies({\n storefrontAccessToken,\n fetchTrackingValues,\n checkoutDomain,\n });\n\n useEffect(() => {\n // Skip setting JS cookies until http-only cookies and server-timing\n // are ready so that we have values synced in JS and http-only cookies.\n if (ignoreDeprecatedCookies || !coreCookiesReady) return;\n\n /**\n * Setting cookie with domain\n *\n * If no domain is provided, the cookie will be set for the current host.\n * For Shopify, we need to ensure this domain is set with a leading dot.\n */\n\n // Use override domain or current host\n let currentDomain = domain || window.location.host;\n\n if (checkoutDomain) {\n const checkoutDomainParts = checkoutDomain.split('.').reverse();\n const currentDomainParts = currentDomain.split('.').reverse();\n const sameDomainParts: Array<string> = [];\n checkoutDomainParts.forEach((part, index) => {\n if (part === currentDomainParts[index]) {\n sameDomainParts.push(part);\n }\n });\n\n currentDomain = sameDomainParts.reverse().join('.');\n }\n\n // Reset domain if localhost\n if (/^localhost/.test(currentDomain)) currentDomain = '';\n\n // Shopify checkout only consumes cookies set with leading dot domain\n const domainWithLeadingDot = currentDomain\n ? /^\\./.test(currentDomain)\n ? currentDomain\n : `.${currentDomain}`\n : '';\n\n /**\n * Set user and session cookies and refresh the expiry time\n */\n if (hasUserConsent) {\n const trackingValues = getTrackingValues();\n if (\n (\n trackingValues.uniqueToken ||\n trackingValues.visitToken ||\n ''\n ).startsWith('00000000-')\n ) {\n // Skip writing cookies when tracking values signal we don't have consent yet\n return;\n }\n\n setCookie(\n SHOPIFY_Y,\n trackingValues.uniqueToken || buildUUID(),\n longTermLength,\n domainWithLeadingDot,\n );\n setCookie(\n SHOPIFY_S,\n trackingValues.visitToken || buildUUID(),\n shortTermLength,\n domainWithLeadingDot,\n );\n } else {\n setCookie(SHOPIFY_Y, '', 0, domainWithLeadingDot);\n setCookie(SHOPIFY_S, '', 0, domainWithLeadingDot);\n }\n }, [\n coreCookiesReady,\n hasUserConsent,\n domain,\n checkoutDomain,\n ignoreDeprecatedCookies,\n ]);\n\n return coreCookiesReady;\n}\n\nfunction setCookie(\n name: string,\n value: string,\n maxage: number,\n domain: string,\n): void {\n document.cookie = stringify(name, value, {\n maxage,\n domain,\n samesite: 'Lax',\n path: '/',\n });\n}\n\nasync function fetchTrackingValuesFromBrowser(\n storefrontAccessToken?: string,\n storefrontApiDomain = '',\n): Promise<void> {\n // These values might come from server-timing or old cookies.\n // If consent cannot be initially assumed, these tokens\n // will be dropped in SFAPI and it will return a mock token\n // starting with '00000000-'.\n // However, if consent can be assumed initially, these tokens\n // will be used to create proper cookies and continue our flow.\n const {uniqueToken, visitToken} = getTrackingValues();\n\n const response = await fetch(\n // TODO: update this endpoint when it becomes stable\n `${storefrontApiDomain.replace(/\\/+$/, '')}/api/unstable/graphql.json`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(storefrontAccessToken && {\n 'X-Shopify-Storefront-Access-Token': storefrontAccessToken,\n }),\n ...(visitToken || uniqueToken\n ? {\n [SHOPIFY_VISIT_TOKEN_HEADER]: visitToken,\n [SHOPIFY_UNIQUE_TOKEN_HEADER]: uniqueToken,\n }\n : undefined),\n },\n body: JSON.stringify({\n query:\n // This query ensures we get _cmp (consent) server-timing header, which is not available in other queries.\n // This value can be passed later to consent-tracking-api and privacy-banner scripts to avoid extra requests.\n 'query ensureCookies { consentManagement { cookies(visitorConsent:{}) { cookieDomain } } }',\n }),\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch consent from browser: ${response.status} ${response.statusText}`,\n );\n }\n\n // Consume the body to complete the request and\n // ensure server-timing is available in performance API\n await response.json();\n\n // Ensure we cache the latest tracking values from resources timing\n getTrackingValues();\n}\n\ntype CoreShopifyCookiesOptions = {\n storefrontAccessToken?: string;\n fetchTrackingValues?: boolean;\n checkoutDomain?: string;\n};\n\n/**\n * Gets http-only cookies from Storefront API via same-origin fetch request.\n * Falls back to checkout domain if provided to at least obtain the tracking\n * values via server-timing headers.\n */\nfunction useCoreShopifyCookies({\n checkoutDomain,\n storefrontAccessToken,\n fetchTrackingValues = false,\n}: CoreShopifyCookiesOptions) {\n const [cookiesReady, setCookiesReady] = useState(!fetchTrackingValues);\n const hasFetchedTrackingValues = useRef(false);\n\n useEffect(() => {\n if (!fetchTrackingValues) {\n // Backend did the work, or proxy is disabled.\n setCookiesReady(true);\n return;\n }\n\n // React runs effects twice in dev mode, avoid double fetching\n if (hasFetchedTrackingValues.current) return;\n hasFetchedTrackingValues.current = true;\n\n // Fetch consent from browser via proxy\n fetchTrackingValuesFromBrowser(storefrontAccessToken)\n .catch((error) =>\n checkoutDomain\n ? // Retry with checkout domain if available to at least\n // get the server-timing values for tracking.\n fetchTrackingValuesFromBrowser(\n storefrontAccessToken,\n checkoutDomain,\n )\n : Promise.reject(error),\n )\n .catch((error) => {\n console.warn(\n '[h2:warn:useShopifyCookies] Failed to fetch tracking values from browser: ' +\n (error instanceof Error ? error.message : String(error)),\n );\n })\n .finally(() => {\n // Proceed even on errors, degraded tracking is better than no app\n setCookiesReady(true);\n });\n }, [checkoutDomain, fetchTrackingValues, storefrontAccessToken]);\n\n return cookiesReady;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useShopifyCookies.mjs","sources":["../../src/useShopifyCookies.tsx"],"sourcesContent":["import {useEffect, useRef, useState} from 'react';\n// @ts-ignore - worktop/cookie types not properly exported\nimport {stringify} from 'worktop/cookie';\nimport {SHOPIFY_Y, SHOPIFY_S} from './cart-constants.js';\nimport {buildUUID} from './cookies-utils.js';\nimport {\n getTrackingValues,\n SHOPIFY_UNIQUE_TOKEN_HEADER,\n SHOPIFY_VISIT_TOKEN_HEADER,\n} from './tracking-utils.js';\n\nconst longTermLength = 60 * 60 * 24 * 360 * 1; // ~1 year expiry\nconst shortTermLength = 60 * 30; // 30 mins\n\ntype UseShopifyCookiesOptions = CoreShopifyCookiesOptions & {\n /**\n * If set to `false`, Shopify cookies will be removed.\n * If set to `true`, Shopify unique user token cookie will have cookie expiry of 1 year.\n * Defaults to false.\n **/\n hasUserConsent?: boolean;\n /**\n * The domain scope of the cookie. Defaults to empty string.\n **/\n domain?: string;\n /**\n * The checkout domain of the shop. Defaults to empty string. If set, the cookie domain will check if it can be set with the checkout domain.\n */\n checkoutDomain?: string;\n /**\n * If set to `true`, it skips modifying the deprecated shopify_y and shopify_s cookies.\n */\n ignoreDeprecatedCookies?: boolean;\n};\n\n/**\n * Sets the `shopify_y` and `shopify_s` cookies in the browser based on user consent\n * for backward compatibility support.\n *\n * If `fetchTrackingValues` is true, it makes a request to Storefront API\n * to fetch or refresh Shopiy analytics and marketing cookies and tracking values.\n * Generally speaking, this should only be needed if you're not using Hydrogen's\n * built-in analytics components and hooks that already handle this automatically.\n * For example, set it to `true` if you are using `hydrogen-react` only with\n * a different framework and still need to make a same-domain request to\n * Storefront API to set cookies.\n *\n * If `ignoreDeprecatedCookies` is true, it skips setting the deprecated cookies entirely.\n * Useful when you only want to use the newer tracking values and not rely on the deprecated ones.\n *\n * @returns `true` when cookies are set and ready.\n * @publicDocs\n */\nexport function useShopifyCookies(options?: UseShopifyCookiesOptions): boolean {\n const {\n hasUserConsent,\n domain = '',\n checkoutDomain = '',\n storefrontAccessToken,\n fetchTrackingValues,\n ignoreDeprecatedCookies = false,\n } = options || {};\n\n const coreCookiesReady = useCoreShopifyCookies({\n storefrontAccessToken,\n fetchTrackingValues,\n checkoutDomain,\n });\n\n useEffect(() => {\n // Skip setting JS cookies until http-only cookies and server-timing\n // are ready so that we have values synced in JS and http-only cookies.\n if (ignoreDeprecatedCookies || !coreCookiesReady) return;\n\n /**\n * Setting cookie with domain\n *\n * If no domain is provided, the cookie will be set for the current host.\n * For Shopify, we need to ensure this domain is set with a leading dot.\n */\n\n // Use override domain or current host\n let currentDomain = domain || window.location.host;\n\n if (checkoutDomain) {\n const checkoutDomainParts = checkoutDomain.split('.').reverse();\n const currentDomainParts = currentDomain.split('.').reverse();\n const sameDomainParts: Array<string> = [];\n checkoutDomainParts.forEach((part, index) => {\n if (part === currentDomainParts[index]) {\n sameDomainParts.push(part);\n }\n });\n\n currentDomain = sameDomainParts.reverse().join('.');\n }\n\n // Reset domain if localhost\n if (/^localhost/.test(currentDomain)) currentDomain = '';\n\n // Shopify checkout only consumes cookies set with leading dot domain\n const domainWithLeadingDot = currentDomain\n ? /^\\./.test(currentDomain)\n ? currentDomain\n : `.${currentDomain}`\n : '';\n\n /**\n * Set user and session cookies and refresh the expiry time\n */\n if (hasUserConsent) {\n const trackingValues = getTrackingValues();\n if (\n (\n trackingValues.uniqueToken ||\n trackingValues.visitToken ||\n ''\n ).startsWith('00000000-')\n ) {\n // Skip writing cookies when tracking values signal we don't have consent yet\n return;\n }\n\n setCookie(\n SHOPIFY_Y,\n trackingValues.uniqueToken || buildUUID(),\n longTermLength,\n domainWithLeadingDot,\n );\n setCookie(\n SHOPIFY_S,\n trackingValues.visitToken || buildUUID(),\n shortTermLength,\n domainWithLeadingDot,\n );\n } else {\n setCookie(SHOPIFY_Y, '', 0, domainWithLeadingDot);\n setCookie(SHOPIFY_S, '', 0, domainWithLeadingDot);\n }\n }, [\n coreCookiesReady,\n hasUserConsent,\n domain,\n checkoutDomain,\n ignoreDeprecatedCookies,\n ]);\n\n return coreCookiesReady;\n}\n\nfunction setCookie(\n name: string,\n value: string,\n maxage: number,\n domain: string,\n): void {\n document.cookie = stringify(name, value, {\n maxage,\n domain,\n samesite: 'Lax',\n path: '/',\n });\n}\n\nasync function fetchTrackingValuesFromBrowser(\n storefrontAccessToken?: string,\n storefrontApiDomain = '',\n): Promise<void> {\n // These values might come from server-timing or old cookies.\n // If consent cannot be initially assumed, these tokens\n // will be dropped in SFAPI and it will return a mock token\n // starting with '00000000-'.\n // However, if consent can be assumed initially, these tokens\n // will be used to create proper cookies and continue our flow.\n const {uniqueToken, visitToken} = getTrackingValues();\n\n const response = await fetch(\n // TODO: update this endpoint when it becomes stable\n `${storefrontApiDomain.replace(/\\/+$/, '')}/api/unstable/graphql.json`,\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(storefrontAccessToken && {\n 'X-Shopify-Storefront-Access-Token': storefrontAccessToken,\n }),\n ...(visitToken || uniqueToken\n ? {\n [SHOPIFY_VISIT_TOKEN_HEADER]: visitToken,\n [SHOPIFY_UNIQUE_TOKEN_HEADER]: uniqueToken,\n }\n : undefined),\n },\n body: JSON.stringify({\n query:\n // This query ensures we get _cmp (consent) server-timing header, which is not available in other queries.\n // This value can be passed later to consent-tracking-api and privacy-banner scripts to avoid extra requests.\n 'query ensureCookies { consentManagement { cookies(visitorConsent:{}) { cookieDomain } } }',\n }),\n },\n );\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch consent from browser: ${response.status} ${response.statusText}`,\n );\n }\n\n // Consume the body to complete the request and\n // ensure server-timing is available in performance API\n await response.json();\n\n // Ensure we cache the latest tracking values from resources timing\n getTrackingValues();\n}\n\ntype CoreShopifyCookiesOptions = {\n storefrontAccessToken?: string;\n fetchTrackingValues?: boolean;\n checkoutDomain?: string;\n};\n\n/**\n * Gets http-only cookies from Storefront API via same-origin fetch request.\n * Falls back to checkout domain if provided to at least obtain the tracking\n * values via server-timing headers.\n */\nfunction useCoreShopifyCookies({\n checkoutDomain,\n storefrontAccessToken,\n fetchTrackingValues = false,\n}: CoreShopifyCookiesOptions) {\n const [cookiesReady, setCookiesReady] = useState(!fetchTrackingValues);\n const hasFetchedTrackingValues = useRef(false);\n\n useEffect(() => {\n if (!fetchTrackingValues) {\n // Backend did the work, or proxy is disabled.\n setCookiesReady(true);\n return;\n }\n\n // React runs effects twice in dev mode, avoid double fetching\n if (hasFetchedTrackingValues.current) return;\n hasFetchedTrackingValues.current = true;\n\n // Fetch consent from browser via proxy\n fetchTrackingValuesFromBrowser(storefrontAccessToken)\n .catch((error) =>\n checkoutDomain\n ? // Retry with checkout domain if available to at least\n // get the server-timing values for tracking.\n fetchTrackingValuesFromBrowser(\n storefrontAccessToken,\n checkoutDomain,\n )\n : Promise.reject(error),\n )\n .catch((error) => {\n console.warn(\n '[h2:warn:useShopifyCookies] Failed to fetch tracking values from browser: ' +\n (error instanceof Error ? error.message : String(error)),\n );\n })\n .finally(() => {\n // Proceed even on errors, degraded tracking is better than no app\n setCookiesReady(true);\n });\n }, [checkoutDomain, fetchTrackingValues, storefrontAccessToken]);\n\n return cookiesReady;\n}\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AddToCartButton.mjs","sources":["../../src/AddToCartButton.tsx"],"sourcesContent":["import {useCallback, useEffect, useState} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {useProduct} from './ProductProvider.js';\nimport {\n BaseButton,\n type CustomBaseButtonProps,\n type BaseButtonProps,\n} from './BaseButton.js';\nimport * as React from 'react';\nimport {CartLineParentInput} from './storefront-api-types.js';\n\nexport interface AddToCartButtonPropsBase {\n /** An array of cart line attributes that belong to the item being added to the cart. */\n attributes?: {\n key: string;\n value: string;\n }[];\n /** The ID of the variant. */\n variantId?: string | null;\n /** The item quantity. */\n quantity?: number;\n /** The text that is announced by the screen reader when the item is being added to the cart. Used for accessibility purposes only and not displayed on the page. */\n accessibleAddingToCartLabel?: string;\n /** The parent line item of the item being added to the cart. Used for nested cart lines. */\n parent?: CartLineParentInput;\n /** The selling plan ID of the subscription variant */\n sellingPlanId?: string;\n}\n\nexport type AddToCartButtonProps<AsType extends React.ElementType = 'button'> =\n AddToCartButtonPropsBase & BaseButtonProps<AsType>;\n\n/**\n * The `AddToCartButton` component renders a button that adds an item to the cart when pressed.\n * It must be a descendent of the `CartProvider` component.\n */\nexport function AddToCartButton<AsType extends React.ElementType = 'button'>(\n props: AddToCartButtonProps<AsType>,\n): JSX.Element {\n const [addingItem, setAddingItem] = useState<boolean>(false);\n const {\n variantId: explicitVariantId,\n quantity = 1,\n attributes,\n sellingPlanId,\n onClick,\n children,\n accessibleAddingToCartLabel,\n parent,\n ...passthroughProps\n } = props;\n const {status, linesAdd} = useCart();\n const {selectedVariant} = useProduct();\n const variantId = explicitVariantId ?? selectedVariant?.id ?? '';\n const disabled =\n explicitVariantId === null ||\n variantId === '' ||\n selectedVariant === null ||\n addingItem ||\n // Only certain 'as' types such as 'button' contain `disabled`\n (passthroughProps as {disabled?: boolean}).disabled;\n\n useEffect(() => {\n if (addingItem && status === 'idle') {\n setAddingItem(false);\n }\n }, [status, addingItem]);\n\n const handleAddItem = useCallback(() => {\n setAddingItem(true);\n linesAdd([\n {\n quantity,\n merchandiseId: variantId || '',\n attributes,\n parent,\n sellingPlanId,\n },\n ]);\n }, [linesAdd, quantity, variantId, attributes, sellingPlanId, parent]);\n\n return (\n <>\n <BaseButton\n {...passthroughProps}\n disabled={disabled}\n onClick={onClick}\n defaultOnClick={handleAddItem}\n >\n {children}\n </BaseButton>\n {accessibleAddingToCartLabel ? (\n <p\n style={{\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n borderWidth: '0',\n }}\n role=\"alert\"\n aria-live=\"assertive\"\n >\n {addingItem ? accessibleAddingToCartLabel : null}\n </p>\n ) : null}\n </>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\nexport interface AddToCartButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n>\n extends AddToCartButtonPropsBase, CustomBaseButtonProps<AsType> {}\n"],"names":[],"mappings":";;;;;AAoCO,SAAS,gBACd,OACa;AACb,QAAM,CAAC,YAAY,aAAa,IAAI,SAAkB,KAAK;AAC3D,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AACJ,QAAM,EAAC,QAAQ,SAAA,IAAY,QAAA;AAC3B,QAAM,EAAC,gBAAA,IAAmB,WAAA;AAC1B,QAAM,YAAY,sBAAqB,mDAAiB,OAAM;AAC9D,QAAM,WACJ,sBAAsB,QACtB,cAAc,MACd,oBAAoB,QACpB;AAAA,EAEC,iBAA0C;AAE7C,YAAU,MAAM;AACd,QAAI,cAAc,WAAW,QAAQ;AACnC,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAM,gBAAgB,YAAY,MAAM;AACtC,kBAAc,IAAI;AAClB,aAAS;AAAA,MACP;AAAA,QACE;AAAA,QACA,eAAe,aAAa;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF,CACD;AAAA,EACH,GAAG,CAAC,UAAU,UAAU,WAAW,YAAY,eAAe,MAAM,CAAC;AAErE,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAEf;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,8BACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO;AAAA,UACL,UAAU;AAAA,UACV,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,aAAa;AAAA,QAAA;AAAA,QAEf,MAAK;AAAA,QACL,aAAU;AAAA,QAET,uBAAa,8BAA8B;AAAA,MAAA;AAAA,IAAA,IAE5C;AAAA,EAAA,GACN;AAEJ;"}
1
+ {"version":3,"file":"AddToCartButton.mjs","sources":["../../src/AddToCartButton.tsx"],"sourcesContent":["import {useCallback, useEffect, useState} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {useProduct} from './ProductProvider.js';\nimport {\n BaseButton,\n type CustomBaseButtonProps,\n type BaseButtonProps,\n} from './BaseButton.js';\nimport * as React from 'react';\nimport {CartLineParentInput} from './storefront-api-types.js';\n\nexport interface AddToCartButtonPropsBase {\n /** An array of cart line attributes that belong to the item being added to the cart. */\n attributes?: {\n key: string;\n value: string;\n }[];\n /** The ID of the variant. */\n variantId?: string | null;\n /** The item quantity. */\n quantity?: number;\n /** The text that is announced by the screen reader when the item is being added to the cart. Used for accessibility purposes only and not displayed on the page. */\n accessibleAddingToCartLabel?: string;\n /** The parent line item of the item being added to the cart. Used for nested cart lines. */\n parent?: CartLineParentInput;\n /** The selling plan ID of the subscription variant */\n sellingPlanId?: string;\n}\n\nexport type AddToCartButtonProps<AsType extends React.ElementType = 'button'> =\n AddToCartButtonPropsBase & BaseButtonProps<AsType>;\n\n/**\n * The `AddToCartButton` component renders a button that adds an item to the cart when pressed.\n * It must be a descendent of the `CartProvider` component.\n * @publicDocs\n */\nexport function AddToCartButton<AsType extends React.ElementType = 'button'>(\n props: AddToCartButtonProps<AsType>,\n): JSX.Element {\n const [addingItem, setAddingItem] = useState<boolean>(false);\n const {\n variantId: explicitVariantId,\n quantity = 1,\n attributes,\n sellingPlanId,\n onClick,\n children,\n accessibleAddingToCartLabel,\n parent,\n ...passthroughProps\n } = props;\n const {status, linesAdd} = useCart();\n const {selectedVariant} = useProduct();\n const variantId = explicitVariantId ?? selectedVariant?.id ?? '';\n const disabled =\n explicitVariantId === null ||\n variantId === '' ||\n selectedVariant === null ||\n addingItem ||\n // Only certain 'as' types such as 'button' contain `disabled`\n (passthroughProps as {disabled?: boolean}).disabled;\n\n useEffect(() => {\n if (addingItem && status === 'idle') {\n setAddingItem(false);\n }\n }, [status, addingItem]);\n\n const handleAddItem = useCallback(() => {\n setAddingItem(true);\n linesAdd([\n {\n quantity,\n merchandiseId: variantId || '',\n attributes,\n parent,\n sellingPlanId,\n },\n ]);\n }, [linesAdd, quantity, variantId, attributes, sellingPlanId, parent]);\n\n return (\n <>\n <BaseButton\n {...passthroughProps}\n disabled={disabled}\n onClick={onClick}\n defaultOnClick={handleAddItem}\n >\n {children}\n </BaseButton>\n {accessibleAddingToCartLabel ? (\n <p\n style={{\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n borderWidth: '0',\n }}\n role=\"alert\"\n aria-live=\"assertive\"\n >\n {addingItem ? accessibleAddingToCartLabel : null}\n </p>\n ) : null}\n </>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\n/** @publicDocs */\nexport interface AddToCartButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n>\n extends AddToCartButtonPropsBase, CustomBaseButtonProps<AsType> {}\n"],"names":[],"mappings":";;;;;AAqCO,SAAS,gBACd,OACa;AACb,QAAM,CAAC,YAAY,aAAa,IAAI,SAAkB,KAAK;AAC3D,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,WAAW;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AACJ,QAAM,EAAC,QAAQ,SAAA,IAAY,QAAA;AAC3B,QAAM,EAAC,gBAAA,IAAmB,WAAA;AAC1B,QAAM,YAAY,sBAAqB,mDAAiB,OAAM;AAC9D,QAAM,WACJ,sBAAsB,QACtB,cAAc,MACd,oBAAoB,QACpB;AAAA,EAEC,iBAA0C;AAE7C,YAAU,MAAM;AACd,QAAI,cAAc,WAAW,QAAQ;AACnC,oBAAc,KAAK;AAAA,IACrB;AAAA,EACF,GAAG,CAAC,QAAQ,UAAU,CAAC;AAEvB,QAAM,gBAAgB,YAAY,MAAM;AACtC,kBAAc,IAAI;AAClB,aAAS;AAAA,MACP;AAAA,QACE;AAAA,QACA,eAAe,aAAa;AAAA,QAC5B;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IACF,CACD;AAAA,EACH,GAAG,CAAC,UAAU,UAAU,WAAW,YAAY,eAAe,MAAM,CAAC;AAErE,SACE,qBAAA,UAAA,EACE,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ;AAAA,QACA;AAAA,QACA,gBAAgB;AAAA,QAEf;AAAA,MAAA;AAAA,IAAA;AAAA,IAEF,8BACC;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,OAAO;AAAA,UACL,UAAU;AAAA,UACV,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,MAAM;AAAA,UACN,YAAY;AAAA,UACZ,aAAa;AAAA,QAAA;AAAA,QAEf,MAAK;AAAA,QACL,aAAU;AAAA,QAET,uBAAa,8BAA8B;AAAA,MAAA;AAAA,IAAA,IAE5C;AAAA,EAAA,GACN;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"BuyNowButton.mjs","sources":["../../src/BuyNowButton.tsx"],"sourcesContent":["import {useEffect, useState, useCallback} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {\n BaseButton,\n type BaseButtonProps,\n type CustomBaseButtonProps,\n} from './BaseButton.js';\n\ninterface BuyNowButtonPropsBase {\n /** The item quantity. Defaults to 1. */\n quantity?: number;\n /** The ID of the variant. */\n variantId: string;\n /** The selling plan ID of the subscription variant */\n sellingPlanId?: string;\n /** An array of cart line attributes that belong to the item being added to the cart. */\n attributes?: {\n key: string;\n value: string;\n }[];\n}\n\ntype BuyNowButtonProps<AsType extends React.ElementType = 'button'> =\n BuyNowButtonPropsBase & BaseButtonProps<AsType>;\n\n/**\n * The `BuyNowButton` component renders a button that adds an item to the cart and redirects the customer to checkout.\n * Must be a child of a `CartProvider` component.\n */\nexport function BuyNowButton<AsType extends React.ElementType = 'button'>(\n props: BuyNowButtonProps<AsType>,\n): JSX.Element {\n const {cartCreate, checkoutUrl} = useCart();\n const [loading, setLoading] = useState<boolean>(false);\n\n const {\n quantity,\n variantId,\n sellingPlanId,\n onClick,\n attributes,\n children,\n ...passthroughProps\n } = props;\n\n useEffect(() => {\n if (loading && checkoutUrl) {\n window.location.href = checkoutUrl;\n }\n }, [loading, checkoutUrl]);\n\n const handleBuyNow = useCallback(() => {\n setLoading(true);\n cartCreate({\n lines: [\n {\n quantity: quantity ?? 1,\n merchandiseId: variantId,\n attributes,\n sellingPlanId,\n },\n ],\n });\n }, [cartCreate, quantity, variantId, attributes, sellingPlanId]);\n\n return (\n <BaseButton\n // Only certain 'as' types such as 'button' contain `disabled`\n disabled={loading ?? (passthroughProps as {disabled?: boolean}).disabled}\n {...passthroughProps}\n onClick={onClick}\n defaultOnClick={handleBuyNow}\n >\n {children}\n </BaseButton>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\nexport interface BuyNowButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n>\n extends BuyNowButtonPropsBase, CustomBaseButtonProps<AsType> {}\n"],"names":[],"mappings":";;;;AA6BO,SAAS,aACd,OACa;AACb,QAAM,EAAC,YAAY,YAAA,IAAe,QAAA;AAClC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,KAAK;AAErD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AAEJ,YAAU,MAAM;AACd,QAAI,WAAW,aAAa;AAC1B,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,SAAS,WAAW,CAAC;AAEzB,QAAM,eAAe,YAAY,MAAM;AACrC,eAAW,IAAI;AACf,eAAW;AAAA,MACT,OAAO;AAAA,QACL;AAAA,UACE,UAAU,YAAY;AAAA,UACtB,eAAe;AAAA,UACf;AAAA,UACA;AAAA,QAAA;AAAA,MACF;AAAA,IACF,CACD;AAAA,EACH,GAAG,CAAC,YAAY,UAAU,WAAW,YAAY,aAAa,CAAC;AAE/D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,UAAU,WAAY,iBAA0C;AAAA,MAC/D,GAAG;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,MAEf;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"BuyNowButton.mjs","sources":["../../src/BuyNowButton.tsx"],"sourcesContent":["import {useEffect, useState, useCallback} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {\n BaseButton,\n type BaseButtonProps,\n type CustomBaseButtonProps,\n} from './BaseButton.js';\n\ninterface BuyNowButtonPropsBase {\n /** The item quantity. Defaults to 1. */\n quantity?: number;\n /** The ID of the variant. */\n variantId: string;\n /** The selling plan ID of the subscription variant */\n sellingPlanId?: string;\n /** An array of cart line attributes that belong to the item being added to the cart. */\n attributes?: {\n key: string;\n value: string;\n }[];\n}\n\ntype BuyNowButtonProps<AsType extends React.ElementType = 'button'> =\n BuyNowButtonPropsBase & BaseButtonProps<AsType>;\n\n/**\n * The `BuyNowButton` component renders a button that adds an item to the cart and redirects the customer to checkout.\n * Must be a child of a `CartProvider` component.\n * @publicDocs\n */\nexport function BuyNowButton<AsType extends React.ElementType = 'button'>(\n props: BuyNowButtonProps<AsType>,\n): JSX.Element {\n const {cartCreate, checkoutUrl} = useCart();\n const [loading, setLoading] = useState<boolean>(false);\n\n const {\n quantity,\n variantId,\n sellingPlanId,\n onClick,\n attributes,\n children,\n ...passthroughProps\n } = props;\n\n useEffect(() => {\n if (loading && checkoutUrl) {\n window.location.href = checkoutUrl;\n }\n }, [loading, checkoutUrl]);\n\n const handleBuyNow = useCallback(() => {\n setLoading(true);\n cartCreate({\n lines: [\n {\n quantity: quantity ?? 1,\n merchandiseId: variantId,\n attributes,\n sellingPlanId,\n },\n ],\n });\n }, [cartCreate, quantity, variantId, attributes, sellingPlanId]);\n\n return (\n <BaseButton\n // Only certain 'as' types such as 'button' contain `disabled`\n disabled={loading ?? (passthroughProps as {disabled?: boolean}).disabled}\n {...passthroughProps}\n onClick={onClick}\n defaultOnClick={handleBuyNow}\n >\n {children}\n </BaseButton>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\n/** @publicDocs */\nexport interface BuyNowButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n>\n extends BuyNowButtonPropsBase, CustomBaseButtonProps<AsType> {}\n"],"names":[],"mappings":";;;;AA8BO,SAAS,aACd,OACa;AACb,QAAM,EAAC,YAAY,YAAA,IAAe,QAAA;AAClC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAkB,KAAK;AAErD,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EAAA,IACD;AAEJ,YAAU,MAAM;AACd,QAAI,WAAW,aAAa;AAC1B,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,SAAS,WAAW,CAAC;AAEzB,QAAM,eAAe,YAAY,MAAM;AACrC,eAAW,IAAI;AACf,eAAW;AAAA,MACT,OAAO;AAAA,QACL;AAAA,UACE,UAAU,YAAY;AAAA,UACtB,eAAe;AAAA,UACf;AAAA,UACA;AAAA,QAAA;AAAA,MACF;AAAA,IACF,CACD;AAAA,EACH,GAAG,CAAC,YAAY,UAAU,WAAW,YAAY,aAAa,CAAC;AAE/D,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,UAAU,WAAY,iBAA0C;AAAA,MAC/D,GAAG;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,MAEf;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartCheckoutButton.mjs","sources":["../../src/CartCheckoutButton.tsx"],"sourcesContent":["import {ReactNode, useEffect, useState} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {\n BaseButton,\n type BaseButtonProps,\n type CustomBaseButtonProps,\n} from './BaseButton.js';\n\ntype ChildrenProps = {\n /** A `ReactNode` element. */\n children: ReactNode;\n};\ntype CartCheckoutButtonProps = Omit<BaseButtonProps<'button'>, 'onClick'> &\n ChildrenProps;\n\n/**\n * The `CartCheckoutButton` component renders a button that redirects to the checkout URL for the cart.\n * It must be a descendent of a `CartProvider` component.\n */\nexport function CartCheckoutButton(\n props: CartCheckoutButtonProps,\n): JSX.Element {\n const [requestedCheckout, setRequestedCheckout] = useState(false);\n const {status, checkoutUrl} = useCart();\n const {children, ...passthroughProps} = props;\n\n useEffect(() => {\n if (requestedCheckout && checkoutUrl && status === 'idle') {\n window.location.href = checkoutUrl;\n }\n }, [requestedCheckout, status, checkoutUrl]);\n\n return (\n <BaseButton\n {...passthroughProps}\n disabled={requestedCheckout || passthroughProps.disabled}\n onClick={(): void => setRequestedCheckout(true)}\n >\n {children}\n </BaseButton>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\n// we ignore this issue because it makes the documentation look better than the equivalent `type` that it wants us to convert to\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface CartCheckoutButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n> extends Omit<CustomBaseButtonProps<AsType>, 'onClick'> {}\n"],"names":[],"mappings":";;;;AAmBO,SAAS,mBACd,OACa;AACb,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAChE,QAAM,EAAC,QAAQ,YAAA,IAAe,QAAA;AAC9B,QAAM,EAAC,UAAU,GAAG,iBAAA,IAAoB;AAExC,YAAU,MAAM;AACd,QAAI,qBAAqB,eAAe,WAAW,QAAQ;AACzD,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,mBAAmB,QAAQ,WAAW,CAAC;AAE3C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAU,qBAAqB,iBAAiB;AAAA,MAChD,SAAS,MAAY,qBAAqB,IAAI;AAAA,MAE7C;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"CartCheckoutButton.mjs","sources":["../../src/CartCheckoutButton.tsx"],"sourcesContent":["import {ReactNode, useEffect, useState} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {\n BaseButton,\n type BaseButtonProps,\n type CustomBaseButtonProps,\n} from './BaseButton.js';\n\ntype ChildrenProps = {\n /** A `ReactNode` element. */\n children: ReactNode;\n};\ntype CartCheckoutButtonProps = Omit<BaseButtonProps<'button'>, 'onClick'> &\n ChildrenProps;\n\n/**\n * The `CartCheckoutButton` component renders a button that redirects to the checkout URL for the cart.\n * It must be a descendent of a `CartProvider` component.\n * @publicDocs\n */\nexport function CartCheckoutButton(\n props: CartCheckoutButtonProps,\n): JSX.Element {\n const [requestedCheckout, setRequestedCheckout] = useState(false);\n const {status, checkoutUrl} = useCart();\n const {children, ...passthroughProps} = props;\n\n useEffect(() => {\n if (requestedCheckout && checkoutUrl && status === 'idle') {\n window.location.href = checkoutUrl;\n }\n }, [requestedCheckout, status, checkoutUrl]);\n\n return (\n <BaseButton\n {...passthroughProps}\n disabled={requestedCheckout || passthroughProps.disabled}\n onClick={(): void => setRequestedCheckout(true)}\n >\n {children}\n </BaseButton>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\n// we ignore this issue because it makes the documentation look better than the equivalent `type` that it wants us to convert to\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\n/** @publicDocs */\nexport interface CartCheckoutButtonPropsForDocs<\n AsType extends React.ElementType = 'button',\n> extends Omit<CustomBaseButtonProps<AsType>, 'onClick'> {}\n"],"names":[],"mappings":";;;;AAoBO,SAAS,mBACd,OACa;AACb,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,SAAS,KAAK;AAChE,QAAM,EAAC,QAAQ,YAAA,IAAe,QAAA;AAC9B,QAAM,EAAC,UAAU,GAAG,iBAAA,IAAoB;AAExC,YAAU,MAAM;AACd,QAAI,qBAAqB,eAAe,WAAW,QAAQ;AACzD,aAAO,SAAS,OAAO;AAAA,IACzB;AAAA,EACF,GAAG,CAAC,mBAAmB,QAAQ,WAAW,CAAC;AAE3C,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ,UAAU,qBAAqB,iBAAiB;AAAA,MAChD,SAAS,MAAY,qBAAqB,IAAI;AAAA,MAE7C;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartCost.mjs","sources":["../../src/CartCost.tsx"],"sourcesContent":["import {Money, type MoneyPropsBase} from './Money.js';\nimport {useCart} from './CartProvider.js';\n\ninterface CartCostPropsBase {\n /** A string type that defines the type of cost needed. Valid values: `total`, `subtotal`, `tax`, or `duty`. */\n amountType?: 'total' | 'subtotal' | 'tax' | 'duty';\n /** Any `ReactNode` elements. */\n children?: React.ReactNode;\n}\n\ntype CartCostProps = Omit<React.ComponentProps<typeof Money>, 'data'> &\n CartCostPropsBase;\n\n/**\n * The `CartCost` component renders a `Money` component with the cost associated with the `amountType` prop.\n * If no `amountType` prop is specified, then it defaults to `totalAmount`.\n * Depends on `useCart()` and must be a child of `<CartProvider/>`\n */\nexport function CartCost(props: CartCostProps): JSX.Element | null {\n const {cost} = useCart();\n const {amountType = 'total', children, ...passthroughProps} = props;\n let amount;\n\n if (amountType == 'total') {\n amount = cost?.totalAmount;\n } else if (amountType == 'subtotal') {\n amount = cost?.subtotalAmount;\n } else if (amountType == 'tax') {\n amount = cost?.totalTaxAmount;\n } else if (amountType == 'duty') {\n amount = cost?.totalDutyAmount;\n }\n\n if (amount == null) {\n return null;\n }\n\n return (\n <Money {...passthroughProps} data={amount}>\n {children}\n </Money>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\nexport interface CartCostPropsForDocs<AsType extends React.ElementType = 'div'>\n extends Omit<MoneyPropsBase<AsType>, 'data'>, CartCostPropsBase {}\n"],"names":[],"mappings":";;;AAkBO,SAAS,SAAS,OAA0C;AACjE,QAAM,EAAC,KAAA,IAAQ,QAAA;AACf,QAAM,EAAC,aAAa,SAAS,UAAU,GAAG,qBAAoB;AAC9D,MAAI;AAEJ,MAAI,cAAc,SAAS;AACzB,aAAS,6BAAM;AAAA,EACjB,WAAW,cAAc,YAAY;AACnC,aAAS,6BAAM;AAAA,EACjB,WAAW,cAAc,OAAO;AAC9B,aAAS,6BAAM;AAAA,EACjB,WAAW,cAAc,QAAQ;AAC/B,aAAS,6BAAM;AAAA,EACjB;AAEA,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,6BACG,OAAA,EAAO,GAAG,kBAAkB,MAAM,QAChC,UACH;AAEJ;"}
1
+ {"version":3,"file":"CartCost.mjs","sources":["../../src/CartCost.tsx"],"sourcesContent":["import {Money, type MoneyPropsBase} from './Money.js';\nimport {useCart} from './CartProvider.js';\n\ninterface CartCostPropsBase {\n /** A string type that defines the type of cost needed. Valid values: `total`, `subtotal`, `tax`, or `duty`. */\n amountType?: 'total' | 'subtotal' | 'tax' | 'duty';\n /** Any `ReactNode` elements. */\n children?: React.ReactNode;\n}\n\ntype CartCostProps = Omit<React.ComponentProps<typeof Money>, 'data'> &\n CartCostPropsBase;\n\n/**\n * The `CartCost` component renders a `Money` component with the cost associated with the `amountType` prop.\n * If no `amountType` prop is specified, then it defaults to `totalAmount`.\n * Depends on `useCart()` and must be a child of `<CartProvider/>`\n * @publicDocs\n */\nexport function CartCost(props: CartCostProps): JSX.Element | null {\n const {cost} = useCart();\n const {amountType = 'total', children, ...passthroughProps} = props;\n let amount;\n\n if (amountType == 'total') {\n amount = cost?.totalAmount;\n } else if (amountType == 'subtotal') {\n amount = cost?.subtotalAmount;\n } else if (amountType == 'tax') {\n amount = cost?.totalTaxAmount;\n } else if (amountType == 'duty') {\n amount = cost?.totalDutyAmount;\n }\n\n if (amount == null) {\n return null;\n }\n\n return (\n <Money {...passthroughProps} data={amount}>\n {children}\n </Money>\n );\n}\n\n// This is only for documentation purposes, and it is not used in the code.\n/** @publicDocs */\nexport interface CartCostPropsForDocs<AsType extends React.ElementType = 'div'>\n extends Omit<MoneyPropsBase<AsType>, 'data'>, CartCostPropsBase {}\n"],"names":[],"mappings":";;;AAmBO,SAAS,SAAS,OAA0C;AACjE,QAAM,EAAC,KAAA,IAAQ,QAAA;AACf,QAAM,EAAC,aAAa,SAAS,UAAU,GAAG,qBAAoB;AAC9D,MAAI;AAEJ,MAAI,cAAc,SAAS;AACzB,aAAS,6BAAM;AAAA,EACjB,WAAW,cAAc,YAAY;AACnC,aAAS,6BAAM;AAAA,EACjB,WAAW,cAAc,OAAO;AAC9B,aAAS,6BAAM;AAAA,EACjB,WAAW,cAAc,QAAQ;AAC/B,aAAS,6BAAM;AAAA,EACjB;AAEA,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,6BACG,OAAA,EAAO,GAAG,kBAAkB,MAAM,QAChC,UACH;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartLineProvider.mjs","sources":["../../src/CartLineProvider.tsx"],"sourcesContent":["import {useContext, createContext, type ReactNode} from 'react';\nimport {\n ComponentizableCartLine,\n type CartLine,\n} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\ntype CartLinePartialDeep = PartialDeep<\n CartLine | ComponentizableCartLine,\n {recurseIntoArrays: true}\n>;\n\nexport const CartLineContext = createContext<CartLinePartialDeep | null>(null);\n\n/**\n * The `useCartLine` hook provides access to the [CartLine object](https://shopify.dev/api/storefront/2026-04/objects/cartline) from the Storefront API. It must be a descendent of a `CartProvider` component.\n */\nexport function useCartLine(): CartLinePartialDeep {\n const context = useContext(CartLineContext);\n\n if (context == null) {\n throw new Error('Expected a cart line context but none was found');\n }\n\n return context;\n}\n\ntype CartLineProviderProps = {\n /** Any `ReactNode` elements. */\n children: ReactNode;\n /** A cart line object. */\n line: CartLinePartialDeep;\n};\n\n/**\n * The `CartLineProvider` component creates a context for using a cart line.\n */\nexport function CartLineProvider({\n children,\n line,\n}: CartLineProviderProps): JSX.Element {\n return (\n <CartLineContext.Provider value={line}>{children}</CartLineContext.Provider>\n );\n}\n"],"names":[],"mappings":";;AAYO,MAAM,kBAAkB,cAA0C,IAAI;AAKtE,SAAS,cAAmC;AACjD,QAAM,UAAU,WAAW,eAAe;AAE1C,MAAI,WAAW,MAAM;AACnB,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,SAAO;AACT;AAYO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAAuC;AACrC,6BACG,gBAAgB,UAAhB,EAAyB,OAAO,MAAO,UAAS;AAErD;"}
1
+ {"version":3,"file":"CartLineProvider.mjs","sources":["../../src/CartLineProvider.tsx"],"sourcesContent":["import {useContext, createContext, type ReactNode} from 'react';\nimport {\n ComponentizableCartLine,\n type CartLine,\n} from './storefront-api-types.js';\nimport type {PartialDeep} from 'type-fest';\n\ntype CartLinePartialDeep = PartialDeep<\n CartLine | ComponentizableCartLine,\n {recurseIntoArrays: true}\n>;\n\nexport const CartLineContext = createContext<CartLinePartialDeep | null>(null);\n\n/**\n * The `useCartLine` hook provides access to the [CartLine object](https://shopify.dev/api/storefront/2026-04/objects/cartline) from the Storefront API. It must be a descendent of a `CartProvider` component.\n * @publicDocs\n */\nexport function useCartLine(): CartLinePartialDeep {\n const context = useContext(CartLineContext);\n\n if (context == null) {\n throw new Error('Expected a cart line context but none was found');\n }\n\n return context;\n}\n\ntype CartLineProviderProps = {\n /** Any `ReactNode` elements. */\n children: ReactNode;\n /** A cart line object. */\n line: CartLinePartialDeep;\n};\n\n/**\n * The `CartLineProvider` component creates a context for using a cart line.\n * @publicDocs\n */\nexport function CartLineProvider({\n children,\n line,\n}: CartLineProviderProps): JSX.Element {\n return (\n <CartLineContext.Provider value={line}>{children}</CartLineContext.Provider>\n );\n}\n"],"names":[],"mappings":";;AAYO,MAAM,kBAAkB,cAA0C,IAAI;AAMtE,SAAS,cAAmC;AACjD,QAAM,UAAU,WAAW,eAAe;AAE1C,MAAI,WAAW,MAAM;AACnB,UAAM,IAAI,MAAM,iDAAiD;AAAA,EACnE;AAEA,SAAO;AACT;AAaO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AACF,GAAuC;AACrC,6BACG,gBAAgB,UAAhB,EAAyB,OAAO,MAAO,UAAS;AAErD;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartLineQuantity.mjs","sources":["../../src/CartLineQuantity.tsx"],"sourcesContent":["import type {ComponentPropsWithoutRef, ElementType} from 'react';\nimport {useCartLine} from './CartLineProvider.js';\n\ninterface CartLineQuantityBaseProps<\n ComponentGeneric extends ElementType = 'span',\n> {\n /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`. */\n as?: ComponentGeneric;\n}\n\nexport type CartLineQuantityProps<ComponentGeneric extends ElementType> =\n CartLineQuantityBaseProps<ComponentGeneric> &\n Omit<\n ComponentPropsWithoutRef<ComponentGeneric>,\n keyof CartLineQuantityBaseProps<ComponentGeneric>\n >;\n\n/**\n * The `<CartLineQuantity/>` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.\n *\n * It must be a descendent of a `<CartLineProvider/>` component, and uses the `useCartLine()` hook internally.\n */\nexport function CartLineQuantity<ComponentGeneric extends ElementType = 'span'>(\n props: CartLineQuantityProps<ComponentGeneric>,\n): JSX.Element {\n const cartLine = useCartLine();\n const {as, ...passthroughProps} = props;\n\n const Wrapper = as ? as : 'span';\n\n return <Wrapper {...passthroughProps}>{cartLine.quantity}</Wrapper>;\n}\n"],"names":[],"mappings":";;AAsBO,SAAS,iBACd,OACa;AACb,QAAM,WAAW,YAAA;AACjB,QAAM,EAAC,IAAI,GAAG,iBAAA,IAAoB;AAElC,QAAM,UAAU,KAAK,KAAK;AAE1B,SAAO,oBAAC,SAAA,EAAS,GAAG,kBAAmB,mBAAS,UAAS;AAC3D;"}
1
+ {"version":3,"file":"CartLineQuantity.mjs","sources":["../../src/CartLineQuantity.tsx"],"sourcesContent":["import type {ComponentPropsWithoutRef, ElementType} from 'react';\nimport {useCartLine} from './CartLineProvider.js';\n\n/** @publicDocs */\ninterface CartLineQuantityBaseProps<\n ComponentGeneric extends ElementType = 'span',\n> {\n /** An HTML tag or React Component to be rendered as the base element wrapper. The default is `span`. */\n as?: ComponentGeneric;\n}\n\nexport type CartLineQuantityProps<ComponentGeneric extends ElementType> =\n CartLineQuantityBaseProps<ComponentGeneric> &\n Omit<\n ComponentPropsWithoutRef<ComponentGeneric>,\n keyof CartLineQuantityBaseProps<ComponentGeneric>\n >;\n\n/**\n * The `<CartLineQuantity/>` component renders a `span` (or another element / component that can be customized by the `as` prop) with the cart line's quantity.\n *\n * It must be a descendent of a `<CartLineProvider/>` component, and uses the `useCartLine()` hook internally.\n * @publicDocs\n */\nexport function CartLineQuantity<ComponentGeneric extends ElementType = 'span'>(\n props: CartLineQuantityProps<ComponentGeneric>,\n): JSX.Element {\n const cartLine = useCartLine();\n const {as, ...passthroughProps} = props;\n\n const Wrapper = as ? as : 'span';\n\n return <Wrapper {...passthroughProps}>{cartLine.quantity}</Wrapper>;\n}\n"],"names":[],"mappings":";;AAwBO,SAAS,iBACd,OACa;AACb,QAAM,WAAW,YAAA;AACjB,QAAM,EAAC,IAAI,GAAG,iBAAA,IAAoB;AAElC,QAAM,UAAU,KAAK,KAAK;AAE1B,SAAO,oBAAC,SAAA,EAAS,GAAG,kBAAmB,mBAAS,UAAS;AAC3D;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartLineQuantityAdjustButton.mjs","sources":["../../src/CartLineQuantityAdjustButton.tsx"],"sourcesContent":["import {useCallback} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {useCartLine} from './CartLineProvider.js';\nimport {BaseButton, type BaseButtonProps} from './BaseButton.js';\nimport type {CartLineUpdateInput} from './storefront-api-types.js';\n\ninterface CartLineQuantityAdjustButtonBaseProps {\n /** The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`. */\n adjust?: 'increase' | 'decrease' | 'remove';\n}\n\ntype CartLineQuantityAdjustButtonProps<\n AsType extends React.ElementType = 'button',\n> = BaseButtonProps<AsType> & CartLineQuantityAdjustButtonBaseProps;\n\n/**\n * The `<CartLineQuantityAdjustButton />` component renders a button that adjusts the cart line's quantity when pressed.\n *\n * It must be a descendent of `<CartLineProvider/>` and `<CartProvider/>`.\n */\nexport function CartLineQuantityAdjustButton<\n AsType extends React.ElementType = 'button',\n>(props: CartLineQuantityAdjustButtonProps<AsType>): JSX.Element {\n const {status, linesRemove, linesUpdate} = useCart();\n const cartLine = useCartLine();\n const {children, adjust, onClick, ...passthroughProps} = props;\n\n const handleAdjust = useCallback(() => {\n if (adjust === 'remove') {\n linesRemove([cartLine?.id ?? '']);\n return;\n }\n\n const quantity =\n adjust === 'decrease'\n ? (cartLine?.quantity ?? 0) - 1\n : (cartLine?.quantity ?? 0) + 1;\n\n if (quantity <= 0) {\n linesRemove([cartLine?.id ?? '']);\n return;\n }\n\n const lineUpdate = {\n id: cartLine?.id ?? '',\n quantity,\n attributes: (cartLine?.attributes ??\n []) as CartLineUpdateInput['attributes'],\n } satisfies CartLineUpdateInput;\n\n linesUpdate([lineUpdate]);\n }, [\n adjust,\n cartLine?.attributes,\n cartLine?.id,\n cartLine?.quantity,\n linesRemove,\n linesUpdate,\n ]);\n\n // Only certain 'as' types such as 'button' contain `disabled`\n const disabledAttr = (passthroughProps as {disabled?: boolean}).disabled;\n\n return (\n <BaseButton\n {...passthroughProps}\n onClick={onClick}\n defaultOnClick={handleAdjust}\n disabled={\n typeof disabledAttr !== 'undefined' ? disabledAttr : status !== 'idle'\n }\n >\n {children}\n </BaseButton>\n );\n}\n"],"names":[],"mappings":";;;;;AAoBO,SAAS,6BAEd,OAA+D;AAC/D,QAAM,EAAC,QAAQ,aAAa,YAAA,IAAe,QAAA;AAC3C,QAAM,WAAW,YAAA;AACjB,QAAM,EAAC,UAAU,QAAQ,SAAS,GAAG,qBAAoB;AAEzD,QAAM,eAAe,YAAY,MAAM;AACrC,QAAI,WAAW,UAAU;AACvB,kBAAY,EAAC,qCAAU,OAAM,EAAE,CAAC;AAChC;AAAA,IACF;AAEA,UAAM,WACJ,WAAW,eACN,qCAAU,aAAY,KAAK,MAC3B,qCAAU,aAAY,KAAK;AAElC,QAAI,YAAY,GAAG;AACjB,kBAAY,EAAC,qCAAU,OAAM,EAAE,CAAC;AAChC;AAAA,IACF;AAEA,UAAM,aAAa;AAAA,MACjB,KAAI,qCAAU,OAAM;AAAA,MACpB;AAAA,MACA,aAAa,qCAAU,eACrB,CAAA;AAAA,IAAC;AAGL,gBAAY,CAAC,UAAU,CAAC;AAAA,EAC1B,GAAG;AAAA,IACD;AAAA,IACA,qCAAU;AAAA,IACV,qCAAU;AAAA,IACV,qCAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,CACD;AAGD,QAAM,eAAgB,iBAA0C;AAEhE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,MAChB,UACE,OAAO,iBAAiB,cAAc,eAAe,WAAW;AAAA,MAGjE;AAAA,IAAA;AAAA,EAAA;AAGP;"}
1
+ {"version":3,"file":"CartLineQuantityAdjustButton.mjs","sources":["../../src/CartLineQuantityAdjustButton.tsx"],"sourcesContent":["import {useCallback} from 'react';\nimport {useCart} from './CartProvider.js';\nimport {useCartLine} from './CartLineProvider.js';\nimport {BaseButton, type BaseButtonProps} from './BaseButton.js';\nimport type {CartLineUpdateInput} from './storefront-api-types.js';\n\ninterface CartLineQuantityAdjustButtonBaseProps {\n /** The adjustment for a cart line's quantity. Valid values: `increase` (default), `decrease`, or `remove`. */\n adjust?: 'increase' | 'decrease' | 'remove';\n}\n\ntype CartLineQuantityAdjustButtonProps<\n AsType extends React.ElementType = 'button',\n> = BaseButtonProps<AsType> & CartLineQuantityAdjustButtonBaseProps;\n\n/**\n * The `<CartLineQuantityAdjustButton />` component renders a button that adjusts the cart line's quantity when pressed.\n *\n * It must be a descendent of `<CartLineProvider/>` and `<CartProvider/>`.\n * @publicDocs\n */\nexport function CartLineQuantityAdjustButton<\n AsType extends React.ElementType = 'button',\n>(props: CartLineQuantityAdjustButtonProps<AsType>): JSX.Element {\n const {status, linesRemove, linesUpdate} = useCart();\n const cartLine = useCartLine();\n const {children, adjust, onClick, ...passthroughProps} = props;\n\n const handleAdjust = useCallback(() => {\n if (adjust === 'remove') {\n linesRemove([cartLine?.id ?? '']);\n return;\n }\n\n const quantity =\n adjust === 'decrease'\n ? (cartLine?.quantity ?? 0) - 1\n : (cartLine?.quantity ?? 0) + 1;\n\n if (quantity <= 0) {\n linesRemove([cartLine?.id ?? '']);\n return;\n }\n\n const lineUpdate = {\n id: cartLine?.id ?? '',\n quantity,\n attributes: (cartLine?.attributes ??\n []) as CartLineUpdateInput['attributes'],\n } satisfies CartLineUpdateInput;\n\n linesUpdate([lineUpdate]);\n }, [\n adjust,\n cartLine?.attributes,\n cartLine?.id,\n cartLine?.quantity,\n linesRemove,\n linesUpdate,\n ]);\n\n // Only certain 'as' types such as 'button' contain `disabled`\n const disabledAttr = (passthroughProps as {disabled?: boolean}).disabled;\n\n return (\n <BaseButton\n {...passthroughProps}\n onClick={onClick}\n defaultOnClick={handleAdjust}\n disabled={\n typeof disabledAttr !== 'undefined' ? disabledAttr : status !== 'idle'\n }\n >\n {children}\n </BaseButton>\n );\n}\n"],"names":[],"mappings":";;;;;AAqBO,SAAS,6BAEd,OAA+D;AAC/D,QAAM,EAAC,QAAQ,aAAa,YAAA,IAAe,QAAA;AAC3C,QAAM,WAAW,YAAA;AACjB,QAAM,EAAC,UAAU,QAAQ,SAAS,GAAG,qBAAoB;AAEzD,QAAM,eAAe,YAAY,MAAM;AACrC,QAAI,WAAW,UAAU;AACvB,kBAAY,EAAC,qCAAU,OAAM,EAAE,CAAC;AAChC;AAAA,IACF;AAEA,UAAM,WACJ,WAAW,eACN,qCAAU,aAAY,KAAK,MAC3B,qCAAU,aAAY,KAAK;AAElC,QAAI,YAAY,GAAG;AACjB,kBAAY,EAAC,qCAAU,OAAM,EAAE,CAAC;AAChC;AAAA,IACF;AAEA,UAAM,aAAa;AAAA,MACjB,KAAI,qCAAU,OAAM;AAAA,MACpB;AAAA,MACA,aAAa,qCAAU,eACrB,CAAA;AAAA,IAAC;AAGL,gBAAY,CAAC,UAAU,CAAC;AAAA,EAC1B,GAAG;AAAA,IACD;AAAA,IACA,qCAAU;AAAA,IACV,qCAAU;AAAA,IACV,qCAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,CACD;AAGD,QAAM,eAAgB,iBAA0C;AAEhE,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACE,GAAG;AAAA,MACJ;AAAA,MACA,gBAAgB;AAAA,MAChB,UACE,OAAO,iBAAiB,cAAc,eAAe,WAAW;AAAA,MAGjE;AAAA,IAAA;AAAA,EAAA;AAGP;"}
@@ -1 +1 @@
1
- {"version":3,"file":"CartProvider.mjs","sources":["../../src/CartProvider.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useTransition,\n createContext,\n useContext,\n} from 'react';\nimport {\n AttributeInput,\n CartBuyerIdentityInput,\n CartInput,\n CartLineInput,\n CartLineUpdateInput,\n CountryCode,\n LanguageCode,\n Cart as CartType,\n MutationCartNoteUpdateArgs,\n} from './storefront-api-types.js';\nimport {\n BuyerIdentityUpdateEvent,\n CartMachineContext,\n CartMachineEvent,\n CartMachineTypeState,\n CartWithActions,\n CartWithActionsDocs,\n} from './cart-types.js';\nimport {useCartAPIStateMachine} from './useCartAPIStateMachine.js';\nimport {CART_ID_STORAGE_KEY} from './cart-constants.js';\nimport {PartialDeep} from 'type-fest';\nimport {defaultCartFragment} from './cart-queries.js';\nimport {useShop} from './ShopifyProvider.js';\n\nexport const CartContext = createContext<CartWithActions | null>(null);\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\ntype UseCartDocs = () => CartWithActionsDocs;\n\n/**\n * The `useCart` hook provides access to the cart object. It must be a descendent of a `CartProvider` component.\n */\nexport function useCart(): CartWithActions {\n const context = useContext(CartContext);\n\n if (!context) {\n throw new Error('Expected a Cart Context, but no Cart Context was found');\n }\n\n return context;\n}\n\ntype CartProviderProps = {\n /** Any `ReactNode` elements. */\n children: React.ReactNode;\n /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */\n numCartLines?: number;\n /** A callback that is invoked when the process to create a cart begins, but before the cart is created in the Storefront API. */\n onCreate?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart begins, but before the line item is added to the Storefront API. */\n onLineAdd?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart begins, but before the line item is removed from the Storefront API. */\n onLineRemove?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart begins, but before the line item is updated in the Storefront API. */\n onLineUpdate?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart begins, but before the note is added or updated in the Storefront API. */\n onNoteUpdate?: () => void;\n /** A callback that is invoked when the process to update the buyer identity begins, but before the buyer identity is updated in the Storefront API. */\n onBuyerIdentityUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart attributes begins, but before the attributes are updated in the Storefront API. */\n onAttributesUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes begins, but before the discount codes are updated in the Storefront API. */\n onDiscountCodesUpdate?: () => void;\n /** A callback that is invoked when the process to create a cart completes */\n onCreateComplete?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart completes */\n onLineAddComplete?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart completes */\n onLineRemoveComplete?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart completes */\n onLineUpdateComplete?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart completes */\n onNoteUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the buyer identity completes */\n onBuyerIdentityUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart attributes completes */\n onAttributesUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes completes */\n onDiscountCodesUpdateComplete?: () => void;\n /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/2026-04/objects/cart). */\n data?: PartialDeep<CartType, {recurseIntoArrays: true}>;\n /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/2026-04/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */\n cartFragment?: string;\n /** A customer access token that's accessible on the server if there's a customer login. */\n customerAccessToken?: CartBuyerIdentityInput['customerAccessToken'];\n /** The ISO country code for i18n. */\n countryCode?: CountryCode;\n /** The ISO language code for i18n. */\n languageCode?: LanguageCode;\n};\n\n/**\n * The `CartProvider` component synchronizes the state of the Storefront API Cart and a customer's cart,\n * and allows you to more easily manipulate the cart by adding, removing, and updating it.\n * It could be placed at the root of your app so that your whole app is able to use the `useCart()` hook anywhere.\n *\n * There are props that trigger when a call to the Storefront API is made, such as `onLineAdd={}` when a line is added to the cart.\n * There are also props that trigger when a call to the Storefront API is completed, such as `onLineAddComplete={}` when the fetch request for adding a line to the cart completes.\n *\n * The `CartProvider` component must be a descendant of the `ShopifyProvider` component.\n */\nexport function CartProvider({\n children,\n numCartLines,\n onCreate,\n onLineAdd,\n onLineRemove,\n onLineUpdate,\n onNoteUpdate,\n onBuyerIdentityUpdate,\n onAttributesUpdate,\n onDiscountCodesUpdate,\n onCreateComplete,\n onLineAddComplete,\n onLineRemoveComplete,\n onLineUpdateComplete,\n onNoteUpdateComplete,\n onBuyerIdentityUpdateComplete,\n onAttributesUpdateComplete,\n onDiscountCodesUpdateComplete,\n data: cart,\n cartFragment = defaultCartFragment,\n customerAccessToken,\n countryCode,\n languageCode,\n}: CartProviderProps): JSX.Element {\n const shop = useShop();\n\n if (!shop)\n throw new Error(\n '<CartProvider> needs to be a descendant of <ShopifyProvider>',\n );\n\n countryCode = (\n (countryCode as string) ??\n shop.countryIsoCode ??\n 'US'\n ).toUpperCase() as CountryCode;\n\n languageCode = (\n (languageCode as string) ??\n shop.languageIsoCode ??\n 'EN'\n ).toUpperCase() as LanguageCode;\n\n if (countryCode) countryCode = countryCode.toUpperCase() as CountryCode;\n\n const [prevCountryCode, setPrevCountryCode] = useState(countryCode);\n const [prevCustomerAccessToken, setPrevCustomerAccessToken] =\n useState(customerAccessToken);\n const customerOverridesCountryCode = useRef(false);\n\n if (\n prevCountryCode !== countryCode ||\n prevCustomerAccessToken !== customerAccessToken\n ) {\n setPrevCountryCode(countryCode);\n setPrevCustomerAccessToken(customerAccessToken);\n customerOverridesCountryCode.current = false;\n }\n\n const [cartState, cartSend] = useCartAPIStateMachine({\n numCartLines,\n data: cart,\n cartFragment,\n countryCode,\n languageCode,\n onCartActionEntry(_, event) {\n try {\n switch (event.type) {\n case 'CART_CREATE':\n return onCreate?.();\n case 'CARTLINE_ADD':\n return onLineAdd?.();\n case 'CARTLINE_REMOVE':\n return onLineRemove?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdate?.();\n case 'NOTE_UPDATE':\n return onNoteUpdate?.();\n case 'BUYER_IDENTITY_UPDATE':\n return onBuyerIdentityUpdate?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdate?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdate?.();\n }\n } catch (error) {\n console.error('Cart entry action failed', error);\n }\n },\n onCartActionOptimisticUI(context, event) {\n if (!context.cart) return {...context};\n switch (event.type) {\n case 'CARTLINE_REMOVE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.filter(\n (line) => line?.id && !event.payload.lines.includes(line?.id),\n ),\n },\n };\n case 'CARTLINE_UPDATE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.map((line) => {\n const updatedLine = event.payload.lines.find(\n ({id}) => id === line?.id,\n );\n\n if (updatedLine && updatedLine.quantity) {\n return {\n ...line,\n quantity: updatedLine.quantity,\n };\n }\n\n return line;\n }),\n },\n };\n }\n return {...context};\n },\n onCartActionComplete(context, event) {\n const cartActionEvent = event.payload.cartActionEvent;\n try {\n switch (event.type) {\n case 'RESOLVE':\n switch (cartActionEvent.type) {\n case 'CART_CREATE':\n return onCreateComplete?.();\n case 'CARTLINE_ADD':\n return onLineAddComplete?.();\n case 'CARTLINE_REMOVE':\n return onLineRemoveComplete?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdateComplete?.();\n case 'NOTE_UPDATE':\n return onNoteUpdateComplete?.();\n case 'BUYER_IDENTITY_UPDATE':\n if (countryCodeNotUpdated(context, cartActionEvent)) {\n customerOverridesCountryCode.current = true;\n }\n return onBuyerIdentityUpdateComplete?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdateComplete?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdateComplete?.();\n }\n }\n } catch (error) {\n console.error('onCartActionComplete failed', error);\n }\n },\n });\n\n const cartReady = useRef(false);\n const [isCartReady, setIsCartReady] = useState(false);\n const cartCompleted = cartState.matches('cartCompleted');\n\n const countryChanged =\n (cartState.value === 'idle' ||\n cartState.value === 'error' ||\n cartState.value === 'cartCompleted') &&\n countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&\n !cartState.context.errors;\n\n const fetchingFromStorage = useRef(false);\n\n /**\n * Initializes cart with priority in this order:\n * 1. cart props\n * 2. localStorage cartId\n */\n useEffect(() => {\n if (!cartReady.current && !fetchingFromStorage.current) {\n if (!cart && storageAvailable('localStorage')) {\n fetchingFromStorage.current = true;\n try {\n const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);\n if (cartId) {\n cartSend({type: 'CART_FETCH', payload: {cartId}});\n }\n } catch (error) {\n console.warn('error fetching cartId');\n console.warn(error);\n }\n }\n cartReady.current = true;\n // Providing a separate cart ready state variable to avoid re-renders in this logic while still being able to pass the reactive status through context.\n setIsCartReady(true);\n }\n }, [cart, cartReady, cartSend]);\n\n // Update cart country code if cart and props countryCode's as different\n useEffect(() => {\n if (!countryChanged || customerOverridesCountryCode.current) return;\n cartSend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {buyerIdentity: {countryCode, customerAccessToken}},\n });\n }, [\n countryCode,\n customerAccessToken,\n countryChanged,\n customerOverridesCountryCode,\n cartSend,\n ]);\n\n // send cart events when ready\n const onCartReadySend = useCallback(\n (cartEvent: CartMachineEvent) => {\n if (!cartReady.current) {\n return console.warn(\"Cart isn't ready yet\");\n }\n cartSend(cartEvent);\n },\n [cartSend],\n );\n\n // save cart id to local storage\n useEffect(() => {\n if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {\n try {\n window.localStorage.setItem(\n CART_ID_STORAGE_KEY,\n cartState.context.cart?.id,\n );\n } catch (error) {\n console.warn('Failed to save cartId to localStorage', error);\n }\n }\n }, [cartState?.context?.cart?.id]);\n\n // delete cart from local storage if cart fetched has been completed\n useEffect(() => {\n if (cartCompleted && storageAvailable('localStorage')) {\n try {\n window.localStorage.removeItem(CART_ID_STORAGE_KEY);\n } catch (error) {\n console.warn('Failed to delete cartId from localStorage', error);\n }\n }\n }, [cartCompleted]);\n\n const cartCreate = useCallback(\n (cartInput: CartInput) => {\n if (countryCode && !cartInput.buyerIdentity?.countryCode) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.countryCode = countryCode;\n }\n\n if (\n customerAccessToken &&\n !cartInput.buyerIdentity?.customerAccessToken\n ) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.customerAccessToken = customerAccessToken;\n }\n onCartReadySend({\n type: 'CART_CREATE',\n payload: cartInput,\n });\n },\n [countryCode, customerAccessToken, onCartReadySend],\n );\n\n // Delays the cart state in the context if the page is hydrating\n // preventing suspense boundary errors.\n const cartDisplayState = useDelayedStateUntilHydration(cartState);\n\n const cartContextValue = useMemo<CartWithActions>(() => {\n return {\n ...(cartDisplayState?.context?.cart ?? {lines: [], attributes: []}),\n status: transposeStatus(cartDisplayState.value),\n error: cartDisplayState?.context?.errors,\n totalQuantity: cartDisplayState?.context?.cart?.totalQuantity ?? 0,\n cartCreate,\n cartReady: isCartReady,\n linesAdd(lines: CartLineInput[]): void {\n if (cartDisplayState?.context?.cart?.id) {\n onCartReadySend({\n type: 'CARTLINE_ADD',\n payload: {lines},\n });\n } else {\n cartCreate({lines});\n }\n },\n linesRemove(lines: string[]): void {\n onCartReadySend({\n type: 'CARTLINE_REMOVE',\n payload: {\n lines,\n },\n });\n },\n linesUpdate(lines: CartLineUpdateInput[]): void {\n onCartReadySend({\n type: 'CARTLINE_UPDATE',\n payload: {\n lines,\n },\n });\n },\n noteUpdate(note: MutationCartNoteUpdateArgs['note']): void {\n onCartReadySend({\n type: 'NOTE_UPDATE',\n payload: {\n note,\n },\n });\n },\n buyerIdentityUpdate(buyerIdentity: CartBuyerIdentityInput): void {\n onCartReadySend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {\n buyerIdentity,\n },\n });\n },\n cartAttributesUpdate(attributes: AttributeInput[]): void {\n onCartReadySend({\n type: 'CART_ATTRIBUTES_UPDATE',\n payload: {\n attributes,\n },\n });\n },\n discountCodesUpdate(discountCodes: string[]): void {\n onCartReadySend({\n type: 'DISCOUNT_CODES_UPDATE',\n payload: {\n discountCodes,\n },\n });\n },\n cartFragment,\n };\n }, [\n cartCreate,\n isCartReady,\n cartDisplayState?.context?.cart,\n cartDisplayState?.context?.errors,\n cartDisplayState.value,\n cartFragment,\n onCartReadySend,\n ]);\n\n return (\n <CartContext.Provider value={cartContextValue}>\n {children}\n </CartContext.Provider>\n );\n}\n\nfunction transposeStatus(\n status: CartMachineTypeState['value'],\n): CartWithActions['status'] {\n switch (status) {\n case 'uninitialized':\n case 'initializationError':\n return 'uninitialized';\n case 'idle':\n case 'cartCompleted':\n case 'error':\n return 'idle';\n case 'cartFetching':\n return 'fetching';\n case 'cartCreating':\n return 'creating';\n case 'cartLineAdding':\n case 'cartLineRemoving':\n case 'cartLineUpdating':\n case 'noteUpdating':\n case 'buyerIdentityUpdating':\n case 'cartAttributesUpdating':\n case 'discountCodesUpdating':\n return 'updating';\n }\n}\n\n/**\n * Delays a state update until hydration finishes. Useful for preventing suspense boundaries errors when updating a context\n * @remarks this uses startTransition and waits for it to finish.\n */\nfunction useDelayedStateUntilHydration<T>(state: T): T {\n const [isPending, startTransition] = useTransition();\n const [delayedState, setDelayedState] = useState(state);\n\n const firstTimePending = useRef(false);\n if (isPending) {\n firstTimePending.current = true;\n }\n\n const firstTimePendingFinished = useRef(false);\n if (!isPending && firstTimePending.current) {\n firstTimePendingFinished.current = true;\n }\n\n useEffect(() => {\n startTransition(() => {\n if (!firstTimePendingFinished.current) {\n setDelayedState(state);\n }\n });\n }, [state]);\n\n const displayState = firstTimePendingFinished.current ? state : delayedState;\n\n return displayState;\n}\n\n/**\n * Check for storage availability function obtained from\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API\n */\nexport function storageAvailable(\n type: 'localStorage' | 'sessionStorage',\n): boolean {\n let storage;\n try {\n storage = window[type];\n const x = '__storage_test__';\n storage.setItem(x, x);\n storage.removeItem(x);\n return true;\n } catch (e) {\n return !!(\n e instanceof DOMException &&\n // everything except Firefox\n (e.code === 22 ||\n // Firefox\n e.code === 1014 ||\n // test name field too, because code might not be present\n // everything except Firefox\n e.name === 'QuotaExceededError' ||\n // Firefox\n e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&\n // acknowledge QuotaExceededError only if there's something already stored\n storage &&\n storage.length !== 0\n );\n }\n}\n\nfunction countryCodeNotUpdated(\n context: CartMachineContext,\n event: BuyerIdentityUpdateEvent,\n): boolean {\n return !!(\n event.payload.buyerIdentity.countryCode &&\n context.cart?.buyerIdentity?.countryCode !==\n event.payload.buyerIdentity.countryCode\n );\n}\n"],"names":["_b","_a","_d","_c"],"mappings":"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"}
1
+ {"version":3,"file":"CartProvider.mjs","sources":["../../src/CartProvider.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useTransition,\n createContext,\n useContext,\n} from 'react';\nimport {\n AttributeInput,\n CartBuyerIdentityInput,\n CartInput,\n CartLineInput,\n CartLineUpdateInput,\n CountryCode,\n LanguageCode,\n Cart as CartType,\n MutationCartNoteUpdateArgs,\n} from './storefront-api-types.js';\nimport {\n BuyerIdentityUpdateEvent,\n CartMachineContext,\n CartMachineEvent,\n CartMachineTypeState,\n CartWithActions,\n CartWithActionsDocs,\n} from './cart-types.js';\nimport {useCartAPIStateMachine} from './useCartAPIStateMachine.js';\nimport {CART_ID_STORAGE_KEY} from './cart-constants.js';\nimport {PartialDeep} from 'type-fest';\nimport {defaultCartFragment} from './cart-queries.js';\nimport {useShop} from './ShopifyProvider.js';\n\nexport const CartContext = createContext<CartWithActions | null>(null);\n\n// eslint-disable-next-line @typescript-eslint/no-unused-vars\n/**\n * `useCart` hook must be a descendent of a `CartProvider` component.\n * @publicDocs */\nexport type UseCartDocs = () => CartWithActionsDocs;\n\n/**\n * The `useCart` hook provides access to the cart object. It must be a descendent of a `CartProvider` component.\n * @publicDocs\n */\nexport function useCart(): CartWithActions {\n const context = useContext(CartContext);\n\n if (!context) {\n throw new Error('Expected a Cart Context, but no Cart Context was found');\n }\n\n return context;\n}\n\ntype CartProviderProps = {\n /** Any `ReactNode` elements. */\n children: React.ReactNode;\n /** Maximum number of cart lines to fetch. Defaults to 250 cart lines. */\n numCartLines?: number;\n /** A callback that is invoked when the process to create a cart begins, but before the cart is created in the Storefront API. */\n onCreate?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart begins, but before the line item is added to the Storefront API. */\n onLineAdd?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart begins, but before the line item is removed from the Storefront API. */\n onLineRemove?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart begins, but before the line item is updated in the Storefront API. */\n onLineUpdate?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart begins, but before the note is added or updated in the Storefront API. */\n onNoteUpdate?: () => void;\n /** A callback that is invoked when the process to update the buyer identity begins, but before the buyer identity is updated in the Storefront API. */\n onBuyerIdentityUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart attributes begins, but before the attributes are updated in the Storefront API. */\n onAttributesUpdate?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes begins, but before the discount codes are updated in the Storefront API. */\n onDiscountCodesUpdate?: () => void;\n /** A callback that is invoked when the process to create a cart completes */\n onCreateComplete?: () => void;\n /** A callback that is invoked when the process to add a line item to the cart completes */\n onLineAddComplete?: () => void;\n /** A callback that is invoked when the process to remove a line item to the cart completes */\n onLineRemoveComplete?: () => void;\n /** A callback that is invoked when the process to update a line item in the cart completes */\n onLineUpdateComplete?: () => void;\n /** A callback that is invoked when the process to add or update a note in the cart completes */\n onNoteUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the buyer identity completes */\n onBuyerIdentityUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart attributes completes */\n onAttributesUpdateComplete?: () => void;\n /** A callback that is invoked when the process to update the cart discount codes completes */\n onDiscountCodesUpdateComplete?: () => void;\n /** An object with fields that correspond to the Storefront API's [Cart object](https://shopify.dev/api/storefront/2026-04/objects/cart). */\n data?: PartialDeep<CartType, {recurseIntoArrays: true}>;\n /** A fragment used to query the Storefront API's [Cart object](https://shopify.dev/api/storefront/2026-04/objects/cart) for all queries and mutations. A default value is used if no argument is provided. */\n cartFragment?: string;\n /** A customer access token that's accessible on the server if there's a customer login. */\n customerAccessToken?: CartBuyerIdentityInput['customerAccessToken'];\n /** The ISO country code for i18n. */\n countryCode?: CountryCode;\n /** The ISO language code for i18n. */\n languageCode?: LanguageCode;\n};\n\n/**\n * The `CartProvider` component synchronizes the state of the Storefront API Cart and a customer's cart,\n * and allows you to more easily manipulate the cart by adding, removing, and updating it.\n * It could be placed at the root of your app so that your whole app is able to use the `useCart()` hook anywhere.\n *\n * There are props that trigger when a call to the Storefront API is made, such as `onLineAdd={}` when a line is added to the cart.\n * There are also props that trigger when a call to the Storefront API is completed, such as `onLineAddComplete={}` when the fetch request for adding a line to the cart completes.\n *\n * The `CartProvider` component must be a descendant of the `ShopifyProvider` component.\n * @publicDocs\n */\nexport function CartProvider({\n children,\n numCartLines,\n onCreate,\n onLineAdd,\n onLineRemove,\n onLineUpdate,\n onNoteUpdate,\n onBuyerIdentityUpdate,\n onAttributesUpdate,\n onDiscountCodesUpdate,\n onCreateComplete,\n onLineAddComplete,\n onLineRemoveComplete,\n onLineUpdateComplete,\n onNoteUpdateComplete,\n onBuyerIdentityUpdateComplete,\n onAttributesUpdateComplete,\n onDiscountCodesUpdateComplete,\n data: cart,\n cartFragment = defaultCartFragment,\n customerAccessToken,\n countryCode,\n languageCode,\n}: CartProviderProps): JSX.Element {\n const shop = useShop();\n\n if (!shop)\n throw new Error(\n '<CartProvider> needs to be a descendant of <ShopifyProvider>',\n );\n\n countryCode = (\n (countryCode as string) ??\n shop.countryIsoCode ??\n 'US'\n ).toUpperCase() as CountryCode;\n\n languageCode = (\n (languageCode as string) ??\n shop.languageIsoCode ??\n 'EN'\n ).toUpperCase() as LanguageCode;\n\n if (countryCode) countryCode = countryCode.toUpperCase() as CountryCode;\n\n const [prevCountryCode, setPrevCountryCode] = useState(countryCode);\n const [prevCustomerAccessToken, setPrevCustomerAccessToken] =\n useState(customerAccessToken);\n const customerOverridesCountryCode = useRef(false);\n\n if (\n prevCountryCode !== countryCode ||\n prevCustomerAccessToken !== customerAccessToken\n ) {\n setPrevCountryCode(countryCode);\n setPrevCustomerAccessToken(customerAccessToken);\n customerOverridesCountryCode.current = false;\n }\n\n const [cartState, cartSend] = useCartAPIStateMachine({\n numCartLines,\n data: cart,\n cartFragment,\n countryCode,\n languageCode,\n onCartActionEntry(_, event) {\n try {\n switch (event.type) {\n case 'CART_CREATE':\n return onCreate?.();\n case 'CARTLINE_ADD':\n return onLineAdd?.();\n case 'CARTLINE_REMOVE':\n return onLineRemove?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdate?.();\n case 'NOTE_UPDATE':\n return onNoteUpdate?.();\n case 'BUYER_IDENTITY_UPDATE':\n return onBuyerIdentityUpdate?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdate?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdate?.();\n }\n } catch (error) {\n console.error('Cart entry action failed', error);\n }\n },\n onCartActionOptimisticUI(context, event) {\n if (!context.cart) return {...context};\n switch (event.type) {\n case 'CARTLINE_REMOVE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.filter(\n (line) => line?.id && !event.payload.lines.includes(line?.id),\n ),\n },\n };\n case 'CARTLINE_UPDATE':\n return {\n ...context,\n cart: {\n ...context.cart,\n lines: context?.cart?.lines?.map((line) => {\n const updatedLine = event.payload.lines.find(\n ({id}) => id === line?.id,\n );\n\n if (updatedLine && updatedLine.quantity) {\n return {\n ...line,\n quantity: updatedLine.quantity,\n };\n }\n\n return line;\n }),\n },\n };\n }\n return {...context};\n },\n onCartActionComplete(context, event) {\n const cartActionEvent = event.payload.cartActionEvent;\n try {\n switch (event.type) {\n case 'RESOLVE':\n switch (cartActionEvent.type) {\n case 'CART_CREATE':\n return onCreateComplete?.();\n case 'CARTLINE_ADD':\n return onLineAddComplete?.();\n case 'CARTLINE_REMOVE':\n return onLineRemoveComplete?.();\n case 'CARTLINE_UPDATE':\n return onLineUpdateComplete?.();\n case 'NOTE_UPDATE':\n return onNoteUpdateComplete?.();\n case 'BUYER_IDENTITY_UPDATE':\n if (countryCodeNotUpdated(context, cartActionEvent)) {\n customerOverridesCountryCode.current = true;\n }\n return onBuyerIdentityUpdateComplete?.();\n case 'CART_ATTRIBUTES_UPDATE':\n return onAttributesUpdateComplete?.();\n case 'DISCOUNT_CODES_UPDATE':\n return onDiscountCodesUpdateComplete?.();\n }\n }\n } catch (error) {\n console.error('onCartActionComplete failed', error);\n }\n },\n });\n\n const cartReady = useRef(false);\n const [isCartReady, setIsCartReady] = useState(false);\n const cartCompleted = cartState.matches('cartCompleted');\n\n const countryChanged =\n (cartState.value === 'idle' ||\n cartState.value === 'error' ||\n cartState.value === 'cartCompleted') &&\n countryCode !== cartState?.context?.cart?.buyerIdentity?.countryCode &&\n !cartState.context.errors;\n\n const fetchingFromStorage = useRef(false);\n\n /**\n * Initializes cart with priority in this order:\n * 1. cart props\n * 2. localStorage cartId\n */\n useEffect(() => {\n if (!cartReady.current && !fetchingFromStorage.current) {\n if (!cart && storageAvailable('localStorage')) {\n fetchingFromStorage.current = true;\n try {\n const cartId = window.localStorage.getItem(CART_ID_STORAGE_KEY);\n if (cartId) {\n cartSend({type: 'CART_FETCH', payload: {cartId}});\n }\n } catch (error) {\n console.warn('error fetching cartId');\n console.warn(error);\n }\n }\n cartReady.current = true;\n // Providing a separate cart ready state variable to avoid re-renders in this logic while still being able to pass the reactive status through context.\n setIsCartReady(true);\n }\n }, [cart, cartReady, cartSend]);\n\n // Update cart country code if cart and props countryCode's as different\n useEffect(() => {\n if (!countryChanged || customerOverridesCountryCode.current) return;\n cartSend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {buyerIdentity: {countryCode, customerAccessToken}},\n });\n }, [\n countryCode,\n customerAccessToken,\n countryChanged,\n customerOverridesCountryCode,\n cartSend,\n ]);\n\n // send cart events when ready\n const onCartReadySend = useCallback(\n (cartEvent: CartMachineEvent) => {\n if (!cartReady.current) {\n return console.warn(\"Cart isn't ready yet\");\n }\n cartSend(cartEvent);\n },\n [cartSend],\n );\n\n // save cart id to local storage\n useEffect(() => {\n if (cartState?.context?.cart?.id && storageAvailable('localStorage')) {\n try {\n window.localStorage.setItem(\n CART_ID_STORAGE_KEY,\n cartState.context.cart?.id,\n );\n } catch (error) {\n console.warn('Failed to save cartId to localStorage', error);\n }\n }\n }, [cartState?.context?.cart?.id]);\n\n // delete cart from local storage if cart fetched has been completed\n useEffect(() => {\n if (cartCompleted && storageAvailable('localStorage')) {\n try {\n window.localStorage.removeItem(CART_ID_STORAGE_KEY);\n } catch (error) {\n console.warn('Failed to delete cartId from localStorage', error);\n }\n }\n }, [cartCompleted]);\n\n const cartCreate = useCallback(\n (cartInput: CartInput) => {\n if (countryCode && !cartInput.buyerIdentity?.countryCode) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.countryCode = countryCode;\n }\n\n if (\n customerAccessToken &&\n !cartInput.buyerIdentity?.customerAccessToken\n ) {\n if (cartInput.buyerIdentity == null) {\n cartInput.buyerIdentity = {};\n }\n cartInput.buyerIdentity.customerAccessToken = customerAccessToken;\n }\n onCartReadySend({\n type: 'CART_CREATE',\n payload: cartInput,\n });\n },\n [countryCode, customerAccessToken, onCartReadySend],\n );\n\n // Delays the cart state in the context if the page is hydrating\n // preventing suspense boundary errors.\n const cartDisplayState = useDelayedStateUntilHydration(cartState);\n\n const cartContextValue = useMemo<CartWithActions>(() => {\n return {\n ...(cartDisplayState?.context?.cart ?? {lines: [], attributes: []}),\n status: transposeStatus(cartDisplayState.value),\n error: cartDisplayState?.context?.errors,\n totalQuantity: cartDisplayState?.context?.cart?.totalQuantity ?? 0,\n cartCreate,\n cartReady: isCartReady,\n linesAdd(lines: CartLineInput[]): void {\n if (cartDisplayState?.context?.cart?.id) {\n onCartReadySend({\n type: 'CARTLINE_ADD',\n payload: {lines},\n });\n } else {\n cartCreate({lines});\n }\n },\n linesRemove(lines: string[]): void {\n onCartReadySend({\n type: 'CARTLINE_REMOVE',\n payload: {\n lines,\n },\n });\n },\n linesUpdate(lines: CartLineUpdateInput[]): void {\n onCartReadySend({\n type: 'CARTLINE_UPDATE',\n payload: {\n lines,\n },\n });\n },\n noteUpdate(note: MutationCartNoteUpdateArgs['note']): void {\n onCartReadySend({\n type: 'NOTE_UPDATE',\n payload: {\n note,\n },\n });\n },\n buyerIdentityUpdate(buyerIdentity: CartBuyerIdentityInput): void {\n onCartReadySend({\n type: 'BUYER_IDENTITY_UPDATE',\n payload: {\n buyerIdentity,\n },\n });\n },\n cartAttributesUpdate(attributes: AttributeInput[]): void {\n onCartReadySend({\n type: 'CART_ATTRIBUTES_UPDATE',\n payload: {\n attributes,\n },\n });\n },\n discountCodesUpdate(discountCodes: string[]): void {\n onCartReadySend({\n type: 'DISCOUNT_CODES_UPDATE',\n payload: {\n discountCodes,\n },\n });\n },\n cartFragment,\n };\n }, [\n cartCreate,\n isCartReady,\n cartDisplayState?.context?.cart,\n cartDisplayState?.context?.errors,\n cartDisplayState.value,\n cartFragment,\n onCartReadySend,\n ]);\n\n return (\n <CartContext.Provider value={cartContextValue}>\n {children}\n </CartContext.Provider>\n );\n}\n\nfunction transposeStatus(\n status: CartMachineTypeState['value'],\n): CartWithActions['status'] {\n switch (status) {\n case 'uninitialized':\n case 'initializationError':\n return 'uninitialized';\n case 'idle':\n case 'cartCompleted':\n case 'error':\n return 'idle';\n case 'cartFetching':\n return 'fetching';\n case 'cartCreating':\n return 'creating';\n case 'cartLineAdding':\n case 'cartLineRemoving':\n case 'cartLineUpdating':\n case 'noteUpdating':\n case 'buyerIdentityUpdating':\n case 'cartAttributesUpdating':\n case 'discountCodesUpdating':\n return 'updating';\n }\n}\n\n/**\n * Delays a state update until hydration finishes. Useful for preventing suspense boundaries errors when updating a context\n * @remarks this uses startTransition and waits for it to finish.\n */\nfunction useDelayedStateUntilHydration<T>(state: T): T {\n const [isPending, startTransition] = useTransition();\n const [delayedState, setDelayedState] = useState(state);\n\n const firstTimePending = useRef(false);\n if (isPending) {\n firstTimePending.current = true;\n }\n\n const firstTimePendingFinished = useRef(false);\n if (!isPending && firstTimePending.current) {\n firstTimePendingFinished.current = true;\n }\n\n useEffect(() => {\n startTransition(() => {\n if (!firstTimePendingFinished.current) {\n setDelayedState(state);\n }\n });\n }, [state]);\n\n const displayState = firstTimePendingFinished.current ? state : delayedState;\n\n return displayState;\n}\n\n/**\n * Check for storage availability function obtained from\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API\n */\nexport function storageAvailable(\n type: 'localStorage' | 'sessionStorage',\n): boolean {\n let storage;\n try {\n storage = window[type];\n const x = '__storage_test__';\n storage.setItem(x, x);\n storage.removeItem(x);\n return true;\n } catch (e) {\n return !!(\n e instanceof DOMException &&\n // everything except Firefox\n (e.code === 22 ||\n // Firefox\n e.code === 1014 ||\n // test name field too, because code might not be present\n // everything except Firefox\n e.name === 'QuotaExceededError' ||\n // Firefox\n e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&\n // acknowledge QuotaExceededError only if there's something already stored\n storage &&\n storage.length !== 0\n );\n }\n}\n\nfunction countryCodeNotUpdated(\n context: CartMachineContext,\n event: BuyerIdentityUpdateEvent,\n): boolean {\n return !!(\n event.payload.buyerIdentity.countryCode &&\n context.cart?.buyerIdentity?.countryCode !==\n event.payload.buyerIdentity.countryCode\n );\n}\n"],"names":["_b","_a","_d","_c"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ExternalVideo.mjs","sources":["../../src/ExternalVideo.tsx"],"sourcesContent":["import type {ExternalVideo as ExternalVideoType} from './storefront-api-types.js';\nimport type {Entries, PartialDeep} from 'type-fest';\nimport {forwardRef, IframeHTMLAttributes} from 'react';\n\nexport interface ExternalVideoBaseProps {\n /**\n * An object with fields that correspond to the Storefront API's [ExternalVideo object](https://shopify.dev/api/storefront/reference/products/externalvideo).\n */\n data: PartialDeep<ExternalVideoType, {recurseIntoArrays: true}>;\n /** An object containing the options available for either\n * [YouTube](https://developers.google.com/youtube/player_parameters#Parameters) or\n * [Vimeo](https://vimeo.zendesk.com/hc/en-us/articles/360001494447-Using-Player-Parameters).\n */\n options?: YouTube | Vimeo;\n}\n\nexport type ExternalVideoProps = Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n 'src'\n> &\n ExternalVideoBaseProps;\n\n/**\n * The `ExternalVideo` component renders an embedded video for the Storefront\n * API's [ExternalVideo object](https://shopify.dev/api/storefront/reference/products/externalvideo).\n */\nexport const ExternalVideo = forwardRef<HTMLIFrameElement, ExternalVideoProps>(\n (props, ref): JSX.Element => {\n const {\n data,\n options,\n id = data.id,\n frameBorder = '0',\n allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',\n allowFullScreen = true,\n loading = 'lazy',\n ...passthroughProps\n } = props;\n\n if (!data.embedUrl) {\n throw new Error(`<ExternalVideo/> requires the 'embedUrl' property`);\n }\n\n let finalUrl: string = data.embedUrl;\n\n if (options) {\n const urlObject = new URL(data.embedUrl);\n for (const [key, value] of Object.entries(options) as Entries<\n typeof options\n >) {\n if (typeof value === 'undefined') {\n continue;\n }\n\n urlObject.searchParams.set(key, value.toString());\n }\n finalUrl = urlObject.toString();\n }\n\n return (\n <iframe\n {...passthroughProps}\n id={id ?? data.embedUrl}\n title={data.alt ?? data.id ?? 'external video'}\n frameBorder={frameBorder}\n allow={allow}\n allowFullScreen={allowFullScreen}\n src={finalUrl}\n loading={loading}\n ref={ref}\n ></iframe>\n );\n },\n);\n\ninterface YouTube {\n autoplay?: 0 | 1;\n cc_lang_pref?: string;\n cc_load_policy?: 1;\n color?: 'red' | 'white';\n controls?: 0 | 1;\n disablekb?: 0 | 1;\n enablejsapi?: 0 | 1;\n end?: number;\n fs?: 0 | 1;\n hl?: string;\n iv_load_policy?: 1 | 3;\n list?: string;\n list_type?: 'playlist' | 'user_uploads';\n loop?: 0 | 1;\n modest_branding?: 1;\n origin?: string;\n playlist?: string;\n plays_inline?: 0 | 1;\n rel?: 0 | 1;\n start?: number;\n widget_referrer?: string;\n}\n\ntype VimeoBoolean = 0 | 1 | boolean;\n\ninterface Vimeo {\n autopause?: VimeoBoolean;\n autoplay?: VimeoBoolean;\n background?: VimeoBoolean;\n byline?: VimeoBoolean;\n color?: string;\n controls?: VimeoBoolean;\n dnt?: VimeoBoolean;\n loop?: VimeoBoolean;\n muted?: VimeoBoolean;\n pip?: VimeoBoolean;\n playsinline?: VimeoBoolean;\n portrait?: VimeoBoolean;\n quality?: '240p' | '360p' | '540p' | '720p' | '1080p' | '2k' | '4k';\n speed?: VimeoBoolean;\n '#t'?: string;\n texttrack?: string;\n title?: VimeoBoolean;\n transparent?: VimeoBoolean;\n}\n"],"names":[],"mappings":";;AA0BO,MAAM,gBAAgB;AAAA,EAC3B,CAAC,OAAO,QAAqB;AAC3B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,KAAK,KAAK;AAAA,MACV,cAAc;AAAA,MACd,QAAQ;AAAA,MACR,kBAAkB;AAAA,MAClB,UAAU;AAAA,MACV,GAAG;AAAA,IAAA,IACD;AAEJ,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AAEA,QAAI,WAAmB,KAAK;AAE5B,QAAI,SAAS;AACX,YAAM,YAAY,IAAI,IAAI,KAAK,QAAQ;AACvC,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAE9C;AACD,YAAI,OAAO,UAAU,aAAa;AAChC;AAAA,QACF;AAEA,kBAAU,aAAa,IAAI,KAAK,MAAM,UAAU;AAAA,MAClD;AACA,iBAAW,UAAU,SAAA;AAAA,IACvB;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,IAAI,MAAM,KAAK;AAAA,QACf,OAAO,KAAK,OAAO,KAAK,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;"}
1
+ {"version":3,"file":"ExternalVideo.mjs","sources":["../../src/ExternalVideo.tsx"],"sourcesContent":["import type {ExternalVideo as ExternalVideoType} from './storefront-api-types.js';\nimport type {Entries, PartialDeep} from 'type-fest';\nimport {forwardRef, IframeHTMLAttributes} from 'react';\n\n/**\n * Takes in the same props as a native `<iframe>` element, except for `src`.\n * @publicDocs */\nexport interface ExternalVideoBaseProps {\n /**\n * An object with fields that correspond to the Storefront API's [ExternalVideo object](https://shopify.dev/api/storefront/reference/products/externalvideo).\n */\n data: PartialDeep<ExternalVideoType, {recurseIntoArrays: true}>;\n /** An object containing the options available for either\n * [YouTube](https://developers.google.com/youtube/player_parameters#Parameters) or\n * [Vimeo](https://vimeo.zendesk.com/hc/en-us/articles/360001494447-Using-Player-Parameters).\n */\n options?: YouTube | Vimeo;\n}\n\nexport type ExternalVideoProps = Omit<\n IframeHTMLAttributes<HTMLIFrameElement>,\n 'src'\n> &\n ExternalVideoBaseProps;\n\n/**\n * The `ExternalVideo` component renders an embedded video for the Storefront\n * API's [ExternalVideo object](https://shopify.dev/api/storefront/reference/products/externalvideo).\n * @publicDocs\n */\nexport const ExternalVideo = forwardRef<HTMLIFrameElement, ExternalVideoProps>(\n (props, ref): JSX.Element => {\n const {\n data,\n options,\n id = data.id,\n frameBorder = '0',\n allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture',\n allowFullScreen = true,\n loading = 'lazy',\n ...passthroughProps\n } = props;\n\n if (!data.embedUrl) {\n throw new Error(`<ExternalVideo/> requires the 'embedUrl' property`);\n }\n\n let finalUrl: string = data.embedUrl;\n\n if (options) {\n const urlObject = new URL(data.embedUrl);\n for (const [key, value] of Object.entries(options) as Entries<\n typeof options\n >) {\n if (typeof value === 'undefined') {\n continue;\n }\n\n urlObject.searchParams.set(key, value.toString());\n }\n finalUrl = urlObject.toString();\n }\n\n return (\n <iframe\n {...passthroughProps}\n id={id ?? data.embedUrl}\n title={data.alt ?? data.id ?? 'external video'}\n frameBorder={frameBorder}\n allow={allow}\n allowFullScreen={allowFullScreen}\n src={finalUrl}\n loading={loading}\n ref={ref}\n ></iframe>\n );\n },\n);\n\ninterface YouTube {\n autoplay?: 0 | 1;\n cc_lang_pref?: string;\n cc_load_policy?: 1;\n color?: 'red' | 'white';\n controls?: 0 | 1;\n disablekb?: 0 | 1;\n enablejsapi?: 0 | 1;\n end?: number;\n fs?: 0 | 1;\n hl?: string;\n iv_load_policy?: 1 | 3;\n list?: string;\n list_type?: 'playlist' | 'user_uploads';\n loop?: 0 | 1;\n modest_branding?: 1;\n origin?: string;\n playlist?: string;\n plays_inline?: 0 | 1;\n rel?: 0 | 1;\n start?: number;\n widget_referrer?: string;\n}\n\ntype VimeoBoolean = 0 | 1 | boolean;\n\ninterface Vimeo {\n autopause?: VimeoBoolean;\n autoplay?: VimeoBoolean;\n background?: VimeoBoolean;\n byline?: VimeoBoolean;\n color?: string;\n controls?: VimeoBoolean;\n dnt?: VimeoBoolean;\n loop?: VimeoBoolean;\n muted?: VimeoBoolean;\n pip?: VimeoBoolean;\n playsinline?: VimeoBoolean;\n portrait?: VimeoBoolean;\n quality?: '240p' | '360p' | '540p' | '720p' | '1080p' | '2k' | '4k';\n speed?: VimeoBoolean;\n '#t'?: string;\n texttrack?: string;\n title?: VimeoBoolean;\n transparent?: VimeoBoolean;\n}\n"],"names":[],"mappings":";;AA8BO,MAAM,gBAAgB;AAAA,EAC3B,CAAC,OAAO,QAAqB;AAC3B,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,KAAK,KAAK;AAAA,MACV,cAAc;AAAA,MACd,QAAQ;AAAA,MACR,kBAAkB;AAAA,MAClB,UAAU;AAAA,MACV,GAAG;AAAA,IAAA,IACD;AAEJ,QAAI,CAAC,KAAK,UAAU;AAClB,YAAM,IAAI,MAAM,mDAAmD;AAAA,IACrE;AAEA,QAAI,WAAmB,KAAK;AAE5B,QAAI,SAAS;AACX,YAAM,YAAY,IAAI,IAAI,KAAK,QAAQ;AACvC,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAE9C;AACD,YAAI,OAAO,UAAU,aAAa;AAChC;AAAA,QACF;AAEA,kBAAU,aAAa,IAAI,KAAK,MAAM,UAAU;AAAA,MAClD;AACA,iBAAW,UAAU,SAAA;AAAA,IACvB;AAEA,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACE,GAAG;AAAA,QACJ,IAAI,MAAM,KAAK;AAAA,QACf,OAAO,KAAK,OAAO,KAAK,MAAM;AAAA,QAC9B;AAAA,QACA;AAAA,QACA;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QACA;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACF;"}
@@ -1 +1 @@
1
- {"version":3,"file":"Image.mjs","sources":["../../src/Image.tsx"],"sourcesContent":["/* eslint-disable eslint-comments/disable-enable-pair */\n/* eslint-disable @typescript-eslint/explicit-function-return-type */\nimport * as React from 'react';\nimport type {PartialDeep} from 'type-fest';\nimport type {Image as ImageType} from './storefront-api-types.js';\n\n/*\n * An optional prop you can use to change the\n * default srcSet generation behaviour\n */\ntype SrcSetOptions = {\n /** The number of sizes to generate */\n intervals: number;\n /** The smallest image size */\n startingWidth: number;\n /** The increment by which to increase for each size, in pixels */\n incrementSize: number;\n /** The size used for placeholder fallback images */\n placeholderWidth: number;\n};\n\ntype NormalizedProps = {\n alt: string;\n aspectRatio: string | undefined;\n height: string;\n src: string | undefined;\n width: string;\n};\n\nexport type LoaderParams = {\n /** The base URL of the image */\n src?: ImageType['url'];\n /** The URL param that controls width */\n width?: number;\n /** The URL param that controls height */\n height?: number;\n /** The URL param that controls the cropping region */\n crop?: Crop;\n};\n\nexport type Loader = (params: LoaderParams) => string;\n\n/*\n * @TODO: Expand to include focal point support; and/or switch this to be an SF API type\n */\ntype Crop = 'center' | 'top' | 'bottom' | 'left' | 'right';\n\nexport type HydrogenImageProps = React.ComponentPropsWithRef<'img'> &\n HydrogenImageBaseProps;\n\ntype HydrogenImageBaseProps = {\n /** The aspect ratio of the image, in the format of `width/height`.\n *\n * @example\n * ```\n * <Image data={productImage} aspectRatio=\"4/5\" />\n * ```\n */\n aspectRatio?: string;\n /** The crop position of the image.\n *\n * @remarks\n * In the event that AspectRatio is set, without specifying a crop,\n * the Shopify CDN won't return the expected image.\n *\n * @defaultValue `center`\n */\n crop?: Crop;\n /** Data mapping to the [Storefront API `Image`](https://shopify.dev/docs/api/storefront/2026-04/objects/Image) object. Must be an Image object.\n *\n * @example\n * ```\n * import {IMAGE_FRAGMENT, Image} from '@shopify/hydrogen';\n *\n * export const IMAGE_QUERY = `#graphql\n * ${IMAGE_FRAGMENT}\n * query {\n * product {\n * featuredImage {\n * ...Image\n * }\n * }\n * }`\n *\n * <Image\n * data={productImage}\n * sizes=\"(min-width: 45em) 50vw, 100vw\"\n * aspectRatio=\"4/5\"\n * />\n * ```\n *\n * Image: {@link https://shopify.dev/api/storefront/reference/common-objects/image}\n */\n data?: PartialDeep<ImageType, {recurseIntoArrays: true}>;\n /** A function that returns a URL string for an image.\n *\n * @remarks\n * By default, this uses Shopify’s CDN {@link https://cdn.shopify.com/} but you can provide\n * your own function to use a another provider, as long as they support URL based image transformations.\n */\n loader?: Loader;\n /** An optional prop you can use to change the default srcSet generation behaviour */\n srcSetOptions?: SrcSetOptions;\n};\n\n/**\n * A Storefront API GraphQL fragment that can be used to query for an image.\n */\nexport const IMAGE_FRAGMENT = `#graphql\n fragment Image on Image {\n altText\n url\n width\n height\n }\n`;\n\n/**\n * Hydrogen’s Image component is a wrapper around the HTML image element.\n * It supports the same props as the HTML `img` element, but automatically\n * generates the srcSet and sizes attributes for you. For most use cases,\n * you’ll want to set the `aspectRatio` prop to ensure the image is sized\n * correctly.\n *\n * @remarks\n * - `decoding` is set to `async` by default.\n * - `loading` is set to `lazy` by default.\n * - `alt` will automatically be set to the `altText` from the Storefront API if passed in the `data` prop\n * - `src` will automatically be set to the `url` from the Storefront API if passed in the `data` prop\n *\n * @example\n * A responsive image with a 4:5 aspect ratio:\n * ```\n * <Image\n * data={product.featuredImage}\n * aspectRatio=\"4/5\"\n * sizes=\"(min-width: 45em) 40vw, 100vw\"\n * />\n * ```\n * @example\n * A fixed size image:\n * ```\n * <Image\n * data={product.featuredImage}\n * width={100}\n * height={100}\n * />\n * ```\n *\n * {@link https://shopify.dev/docs/api/hydrogen-react/components/image}\n */\nexport const Image = React.forwardRef<HTMLImageElement, HydrogenImageProps>(\n (\n {\n alt,\n aspectRatio,\n crop = 'center',\n data,\n decoding = 'async',\n height = 'auto',\n loader = shopifyLoader,\n loading = 'lazy',\n sizes,\n src,\n srcSetOptions = {\n intervals: 15,\n startingWidth: 200,\n incrementSize: 200,\n placeholderWidth: 100,\n },\n width = '100%',\n ...passthroughProps\n },\n ref,\n ) => {\n /*\n * Gets normalized values for width, height from data prop\n */\n const normalizedData = React.useMemo(() => {\n /* Only use data width if height is also set */\n const dataWidth: number | undefined =\n data?.width && data?.height ? data?.width : undefined;\n\n const dataHeight: number | undefined =\n data?.width && data?.height ? data?.height : undefined;\n\n return {\n width: dataWidth,\n height: dataHeight,\n unitsMatch: Boolean(unitsMatch(dataWidth, dataHeight)),\n };\n }, [data]);\n\n /*\n * Gets normalized values for width, height, src, alt, and aspectRatio props\n * supporting the presence of `data` in addition to flat props.\n */\n const normalizedProps = React.useMemo(() => {\n const nWidthProp: string | number = width || '100%';\n const widthParts = getUnitValueParts(nWidthProp.toString());\n const nWidth = `${widthParts.number}${widthParts.unit}`;\n\n const autoHeight = height === undefined || height === null;\n const heightParts = autoHeight\n ? null\n : getUnitValueParts(height.toString());\n\n const fixedHeight = heightParts\n ? `${heightParts.number}${heightParts.unit}`\n : '';\n\n const nHeight = autoHeight ? 'auto' : fixedHeight;\n\n const nSrc: string | undefined = src || data?.url;\n\n if (__HYDROGEN_DEV__ && !nSrc) {\n console.warn(\n `No src or data.url provided to Image component.`,\n passthroughProps?.key || '',\n );\n }\n\n const nAlt: string = data?.altText && !alt ? data?.altText : alt || '';\n\n const nAspectRatio: string | undefined = aspectRatio\n ? aspectRatio\n : normalizedData.unitsMatch\n ? [\n getNormalizedFixedUnit(normalizedData.width),\n getNormalizedFixedUnit(normalizedData.height),\n ].join('/')\n : undefined;\n\n return {\n width: nWidth,\n height: nHeight,\n src: nSrc,\n alt: nAlt,\n aspectRatio: nAspectRatio,\n };\n }, [\n width,\n height,\n src,\n data,\n alt,\n aspectRatio,\n normalizedData,\n passthroughProps?.key,\n ]);\n\n const {intervals, startingWidth, incrementSize, placeholderWidth} =\n srcSetOptions;\n\n /*\n * This function creates an array of widths to be used in srcSet\n */\n const imageWidths = React.useMemo(() => {\n return generateImageWidths(\n width,\n intervals,\n startingWidth,\n incrementSize,\n );\n }, [width, intervals, startingWidth, incrementSize]);\n\n const fixedWidth = isFixedWidth(normalizedProps.width);\n\n if (__HYDROGEN_DEV__ && !sizes && !fixedWidth) {\n console.warn(\n [\n 'No sizes prop provided to Image component,',\n 'you may be loading unnecessarily large images.',\n `Image used is ${\n src || data?.url || passthroughProps?.key || 'unknown'\n }`,\n ].join(' '),\n );\n }\n\n /*\n * We check to see whether the image is fixed width or not,\n * if fixed, we still provide a srcSet, but only to account for\n * different pixel densities.\n */\n if (fixedWidth) {\n return (\n <FixedWidthImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n height={height}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n ref={ref}\n width={width}\n data={data}\n />\n );\n } else {\n return (\n <FluidImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n placeholderWidth={placeholderWidth}\n ref={ref}\n sizes={sizes}\n data={data}\n />\n );\n }\n },\n);\n\ntype FixedImageExludedProps =\n | 'data'\n | 'loader'\n | 'loaderOptions'\n | 'sizes'\n | 'srcSetOptions'\n | 'widths';\n\ntype FixedWidthImageProps = Omit<HydrogenImageProps, FixedImageExludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n loader: Loader;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n normalizedProps: NormalizedProps;\n imageWidths: number[];\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FixedWidthImage = React.forwardRef<\n HTMLImageElement,\n FixedWidthImageProps\n>(\n (\n {\n aspectRatio,\n crop,\n decoding,\n height,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n width,\n data,\n },\n ref,\n ) => {\n const fixed = React.useMemo(() => {\n const intWidth: number | undefined = getNormalizedFixedUnit(width);\n const intHeight: number | undefined = getNormalizedFixedUnit(height);\n\n /*\n * The aspect ratio for fixed width images is taken from the explicitly\n * set prop, but if that's not present, and both width and height are\n * set, we calculate the aspect ratio from the width and height—as\n * long as they share the same unit type (e.g. both are 'px').\n */\n const fixedAspectRatio = aspectRatio\n ? aspectRatio\n : unitsMatch(normalizedProps.width, normalizedProps.height)\n ? [intWidth, intHeight].join('/')\n : normalizedProps.aspectRatio\n ? normalizedProps.aspectRatio\n : undefined;\n\n /*\n * The Sizes Array generates an array of all the parts\n * that make up the srcSet, including the width, height, and crop\n */\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, fixedAspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const fixedHeight = intHeight\n ? intHeight\n : fixedAspectRatio && intWidth\n ? intWidth * (parseAspectRatio(fixedAspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n const src = loader({\n src: normalizedProps.src,\n width: intWidth,\n height: fixedHeight,\n crop: normalizedProps.height === 'auto' ? undefined : crop,\n });\n\n return {\n width: intWidth,\n aspectRatio: fixedAspectRatio,\n height: fixedHeight,\n srcSet,\n src,\n };\n }, [\n aspectRatio,\n crop,\n data,\n height,\n imageWidths,\n loader,\n normalizedProps,\n width,\n ]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fixed.height}\n loading={loading}\n src={fixed.src}\n srcSet={fixed.srcSet}\n width={fixed.width}\n style={{\n aspectRatio: fixed.aspectRatio,\n ...passthroughProps.style,\n }}\n {...passthroughProps}\n />\n );\n },\n);\n\ntype FluidImageExcludedProps =\n | 'data'\n | 'width'\n | 'height'\n | 'loader'\n | 'loaderOptions'\n | 'srcSetOptions';\n\ntype FluidImageProps = Omit<HydrogenImageProps, FluidImageExcludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n imageWidths: number[];\n loader: Loader;\n normalizedProps: NormalizedProps;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n placeholderWidth: number;\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FluidImage = React.forwardRef<HTMLImageElement, FluidImageProps>(\n (\n {\n crop,\n decoding,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n placeholderWidth,\n sizes,\n data,\n },\n ref,\n ) => {\n const fluid = React.useMemo(() => {\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, normalizedProps.aspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const placeholderHeight =\n normalizedProps.aspectRatio && placeholderWidth\n ? placeholderWidth *\n (parseAspectRatio(normalizedProps.aspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n\n const src = loader({\n src: normalizedProps.src,\n width: placeholderWidth,\n height: placeholderHeight,\n crop,\n });\n\n return {\n placeholderHeight,\n srcSet,\n src,\n };\n }, [crop, data, imageWidths, loader, normalizedProps, placeholderWidth]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fluid.placeholderHeight}\n loading={loading}\n sizes={sizes}\n src={fluid.src}\n srcSet={fluid.srcSet}\n width={placeholderWidth}\n {...passthroughProps}\n style={{\n width: normalizedProps.width,\n aspectRatio: normalizedProps.aspectRatio,\n ...passthroughProps.style,\n }}\n />\n );\n },\n);\n\n/**\n * The shopifyLoader function is a simple utility function that takes a src, width,\n * height, and crop and returns a string that can be used as the src for an image.\n * It can be used with the Hydrogen Image component or with the next/image component.\n * (or any others that accept equivalent configuration)\n * @param src - The source URL of the image, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg`\n * @param width - The width of the image, e.g. `100`\n * @param height - The height of the image, e.g. `100`\n * @param crop - The crop of the image, e.g. `center`\n * @returns A Shopify image URL with the correct query parameters, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=100&height=100&crop=center`\n *\n * @example\n * ```\n * shopifyLoader({\n * src: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',\n * width: 100,\n * height: 100,\n * crop: 'center',\n * })\n * ```\n */\nconst PLACEHOLDER_DOMAIN = 'https://placeholder.shopify.com';\nexport function shopifyLoader({src, width, height, crop}: LoaderParams) {\n if (!src) {\n return '';\n }\n\n const url = new URL(src, PLACEHOLDER_DOMAIN);\n\n if (width) {\n url.searchParams.append('width', Math.round(width).toString());\n }\n\n if (height) {\n url.searchParams.append('height', Math.round(height).toString());\n }\n\n if (crop) {\n url.searchParams.append('crop', crop);\n }\n return url.href.replace(PLACEHOLDER_DOMAIN, '');\n}\n\n/**\n * Checks whether the width and height share the same unit type\n * @param width - The width of the image, e.g. 100% | 10px\n * @param height - The height of the image, e.g. auto | 100px\n * @returns Whether the width and height share the same unit type (boolean)\n */\nfunction unitsMatch(\n width: string | number = '100%',\n height: string | number = 'auto',\n): boolean {\n return (\n getUnitValueParts(width.toString()).unit ===\n getUnitValueParts(height.toString()).unit\n );\n}\n\n/**\n * Given a CSS size, returns the unit and number parts of the value\n * @param value - The CSS size, e.g. 100px\n * @returns The unit and number parts of the value, e.g. \\{unit: 'px', number: 100\\}\n */\nfunction getUnitValueParts(value: string): {unit: string; number: number} {\n const unit = value.replace(/[0-9.]/g, '');\n const number = parseFloat(value.replace(unit, ''));\n\n return {\n unit: unit === '' ? (number === undefined ? 'auto' : 'px') : unit,\n number,\n };\n}\n\n/**\n * Given a value, returns the width of the image as an integer in pixels\n * @param value - The width of the image, e.g. 16px | 1rem | 1em | 16\n * @returns The width of the image in pixels, e.g. 16, or undefined if the value is not a fixed unit\n */\nfunction getNormalizedFixedUnit(value?: string | number): number | undefined {\n if (value === undefined) {\n return;\n }\n\n const {unit, number} = getUnitValueParts(value.toString());\n\n switch (unit) {\n case 'em':\n return number * 16;\n case 'rem':\n return number * 16;\n case 'px':\n return number;\n case '':\n return number;\n default:\n return;\n }\n}\n\n/**\n * This function checks whether a width is fixed or not.\n * @param width - The width of the image, e.g. 100 | '100px' | '100em' | '100rem'\n * @returns Whether the width is fixed or not\n */\nfunction isFixedWidth(width: string | number): boolean {\n const fixedEndings = /\\d(px|em|rem)$/;\n return typeof width === 'number' || fixedEndings.test(width);\n}\n\n/**\n * This function generates a srcSet for Shopify images.\n * @param src - The source URL of the image, e.g. https://cdn.shopify.com/static/sample-images/garnished.jpeg\n * @param sizesArray - An array of objects containing the `width`, `height`, and `crop` of the image, e.g. [\\{width: 200, height: 200, crop: 'center'\\}, \\{width: 400, height: 400, crop: 'center'\\}]\n * @param loader - A function that takes a Shopify image URL and returns a Shopify image URL with the correct query parameters\n * @returns A srcSet for Shopify images, e.g. 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=200&height=200&crop=center 200w, https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=400&height=400&crop=center 400w'\n */\nexport function generateSrcSet(\n src?: string,\n sizesArray?: Array<{width?: number; height?: number; crop?: Crop}>,\n loader: Loader = shopifyLoader,\n): string {\n if (!src) {\n return '';\n }\n\n if (sizesArray?.length === 0 || !sizesArray) {\n return src;\n }\n\n return sizesArray\n .map(\n (size, i) =>\n `${loader({\n src,\n width: size.width,\n height: size.height,\n crop: size.crop,\n })} ${sizesArray.length === 3 ? `${i + 1}x` : `${size.width ?? 0}w`}`,\n )\n .join(`, `);\n}\n\n/**\n * This function generates an array of sizes for Shopify images, for both fixed and responsive images.\n * @param width - The CSS width of the image\n * @param intervals - The number of intervals to generate\n * @param startingWidth - The starting width of the image\n * @param incrementSize - The size of each interval\n * @returns An array of widths\n */\nexport function generateImageWidths(\n width: string | number = '100%',\n intervals: number,\n startingWidth: number,\n incrementSize: number,\n): number[] {\n const responsive = Array.from(\n {length: intervals},\n (_, i) => i * incrementSize + startingWidth,\n );\n\n const fixed = Array.from(\n {length: 3},\n (_, i) => (i + 1) * (getNormalizedFixedUnit(width) ?? 0),\n );\n\n return isFixedWidth(width) ? fixed : responsive;\n}\n\n/**\n * Simple utility function to convert an aspect ratio CSS string to a decimal, currently only supports values like `1/1`, not `0.5`, or `auto`\n * @param aspectRatio - The aspect ratio of the image, e.g. `1/1`\n * @returns The aspect ratio as a number, e.g. `0.5`\n *\n * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio}\n */\nexport function parseAspectRatio(aspectRatio?: string): number | undefined {\n if (!aspectRatio) return;\n const [width, height] = aspectRatio.split('/');\n return 1 / (Number(width) / Number(height));\n}\n\n// Generate data needed for Imagery loader\nexport function generateSizes(\n imageWidths?: number[],\n aspectRatio?: string,\n crop: Crop = 'center',\n sourceDimensions?: {width?: number; height?: number},\n):\n | {\n width: number;\n height: number | undefined;\n crop: Crop;\n }[]\n | undefined {\n if (!imageWidths) return;\n return imageWidths\n .map((width: number) => {\n return {\n width,\n height: aspectRatio\n ? width * (parseAspectRatio(aspectRatio) ?? 1)\n : undefined,\n crop,\n };\n })\n .filter(({width, height}) => {\n if (sourceDimensions?.width && width > sourceDimensions.width) {\n return false;\n }\n\n if (\n sourceDimensions?.height &&\n height &&\n height > sourceDimensions.height\n ) {\n return false;\n }\n\n return true;\n });\n /*\n Given:\n ([100, 200], 1/1, 'center')\n Returns:\n [{width: 100, height: 100, crop: 'center'},\n {width: 200, height: 200, crop: 'center'}]\n */\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Image.mjs","sources":["../../src/Image.tsx"],"sourcesContent":["/* eslint-disable eslint-comments/disable-enable-pair */\n/* eslint-disable @typescript-eslint/explicit-function-return-type */\nimport * as React from 'react';\nimport type {PartialDeep} from 'type-fest';\nimport type {Image as ImageType} from './storefront-api-types.js';\n\n/*\n * An optional prop you can use to change the\n * default srcSet generation behaviour\n */\ntype SrcSetOptions = {\n /** The number of sizes to generate */\n intervals: number;\n /** The smallest image size */\n startingWidth: number;\n /** The increment by which to increase for each size, in pixels */\n incrementSize: number;\n /** The size used for placeholder fallback images */\n placeholderWidth: number;\n};\n\ntype NormalizedProps = {\n alt: string;\n aspectRatio: string | undefined;\n height: string;\n src: string | undefined;\n width: string;\n};\n\nexport type LoaderParams = {\n /** The base URL of the image */\n src?: ImageType['url'];\n /** The URL param that controls width */\n width?: number;\n /** The URL param that controls height */\n height?: number;\n /** The URL param that controls the cropping region */\n crop?: Crop;\n};\n\nexport type Loader = (params: LoaderParams) => string;\n\n/*\n * @TODO: Expand to include focal point support; and/or switch this to be an SF API type\n */\ntype Crop = 'center' | 'top' | 'bottom' | 'left' | 'right';\n\n/** @publicDocs */\nexport type HydrogenImageProps = React.ComponentPropsWithRef<'img'> &\n HydrogenImageBaseProps;\n\ntype HydrogenImageBaseProps = {\n /** The aspect ratio of the image, in the format of `width/height`.\n *\n * @example\n * ```\n * <Image data={productImage} aspectRatio=\"4/5\" />\n * ```\n */\n aspectRatio?: string;\n /** The crop position of the image.\n *\n * @remarks\n * In the event that AspectRatio is set, without specifying a crop,\n * the Shopify CDN won't return the expected image.\n *\n * @defaultValue `center`\n */\n crop?: Crop;\n /** Data mapping to the [Storefront API `Image`](https://shopify.dev/docs/api/storefront/2026-04/objects/Image) object. Must be an Image object.\n *\n * @example\n * ```\n * import {IMAGE_FRAGMENT, Image} from '@shopify/hydrogen';\n *\n * export const IMAGE_QUERY = `#graphql\n * ${IMAGE_FRAGMENT}\n * query {\n * product {\n * featuredImage {\n * ...Image\n * }\n * }\n * }`\n *\n * <Image\n * data={productImage}\n * sizes=\"(min-width: 45em) 50vw, 100vw\"\n * aspectRatio=\"4/5\"\n * />\n * ```\n *\n * Image: {@link https://shopify.dev/api/storefront/reference/common-objects/image}\n */\n data?: PartialDeep<ImageType, {recurseIntoArrays: true}>;\n /** A function that returns a URL string for an image.\n *\n * @remarks\n * By default, this uses Shopify’s CDN {@link https://cdn.shopify.com/} but you can provide\n * your own function to use a another provider, as long as they support URL based image transformations.\n */\n loader?: Loader;\n /** An optional prop you can use to change the default srcSet generation behaviour */\n srcSetOptions?: SrcSetOptions;\n};\n\n/**\n * A Storefront API GraphQL fragment that can be used to query for an image.\n */\nexport const IMAGE_FRAGMENT = `#graphql\n fragment Image on Image {\n altText\n url\n width\n height\n }\n`;\n\n/**\n * Hydrogen’s Image component is a wrapper around the HTML image element.\n * It supports the same props as the HTML `img` element, but automatically\n * generates the srcSet and sizes attributes for you. For most use cases,\n * you’ll want to set the `aspectRatio` prop to ensure the image is sized\n * correctly.\n *\n * @remarks\n * - `decoding` is set to `async` by default.\n * - `loading` is set to `lazy` by default.\n * - `alt` will automatically be set to the `altText` from the Storefront API if passed in the `data` prop\n * - `src` will automatically be set to the `url` from the Storefront API if passed in the `data` prop\n *\n * @example\n * A responsive image with a 4:5 aspect ratio:\n * ```\n * <Image\n * data={product.featuredImage}\n * aspectRatio=\"4/5\"\n * sizes=\"(min-width: 45em) 40vw, 100vw\"\n * />\n * ```\n * @example\n * A fixed size image:\n * ```\n * <Image\n * data={product.featuredImage}\n * width={100}\n * height={100}\n * />\n * ```\n *\n * {@link https://shopify.dev/docs/api/hydrogen-react/components/image}\n * @publicDocs\n */\nexport const Image = React.forwardRef<HTMLImageElement, HydrogenImageProps>(\n (\n {\n alt,\n aspectRatio,\n crop = 'center',\n data,\n decoding = 'async',\n height = 'auto',\n loader = shopifyLoader,\n loading = 'lazy',\n sizes,\n src,\n srcSetOptions = {\n intervals: 15,\n startingWidth: 200,\n incrementSize: 200,\n placeholderWidth: 100,\n },\n width = '100%',\n ...passthroughProps\n },\n ref,\n ) => {\n /*\n * Gets normalized values for width, height from data prop\n */\n const normalizedData = React.useMemo(() => {\n /* Only use data width if height is also set */\n const dataWidth: number | undefined =\n data?.width && data?.height ? data?.width : undefined;\n\n const dataHeight: number | undefined =\n data?.width && data?.height ? data?.height : undefined;\n\n return {\n width: dataWidth,\n height: dataHeight,\n unitsMatch: Boolean(unitsMatch(dataWidth, dataHeight)),\n };\n }, [data]);\n\n /*\n * Gets normalized values for width, height, src, alt, and aspectRatio props\n * supporting the presence of `data` in addition to flat props.\n */\n const normalizedProps = React.useMemo(() => {\n const nWidthProp: string | number = width || '100%';\n const widthParts = getUnitValueParts(nWidthProp.toString());\n const nWidth = `${widthParts.number}${widthParts.unit}`;\n\n const autoHeight = height === undefined || height === null;\n const heightParts = autoHeight\n ? null\n : getUnitValueParts(height.toString());\n\n const fixedHeight = heightParts\n ? `${heightParts.number}${heightParts.unit}`\n : '';\n\n const nHeight = autoHeight ? 'auto' : fixedHeight;\n\n const nSrc: string | undefined = src || data?.url;\n\n if (__HYDROGEN_DEV__ && !nSrc) {\n console.warn(\n `No src or data.url provided to Image component.`,\n passthroughProps?.key || '',\n );\n }\n\n const nAlt: string = data?.altText && !alt ? data?.altText : alt || '';\n\n const nAspectRatio: string | undefined = aspectRatio\n ? aspectRatio\n : normalizedData.unitsMatch\n ? [\n getNormalizedFixedUnit(normalizedData.width),\n getNormalizedFixedUnit(normalizedData.height),\n ].join('/')\n : undefined;\n\n return {\n width: nWidth,\n height: nHeight,\n src: nSrc,\n alt: nAlt,\n aspectRatio: nAspectRatio,\n };\n }, [\n width,\n height,\n src,\n data,\n alt,\n aspectRatio,\n normalizedData,\n passthroughProps?.key,\n ]);\n\n const {intervals, startingWidth, incrementSize, placeholderWidth} =\n srcSetOptions;\n\n /*\n * This function creates an array of widths to be used in srcSet\n */\n const imageWidths = React.useMemo(() => {\n return generateImageWidths(\n width,\n intervals,\n startingWidth,\n incrementSize,\n );\n }, [width, intervals, startingWidth, incrementSize]);\n\n const fixedWidth = isFixedWidth(normalizedProps.width);\n\n if (__HYDROGEN_DEV__ && !sizes && !fixedWidth) {\n console.warn(\n [\n 'No sizes prop provided to Image component,',\n 'you may be loading unnecessarily large images.',\n `Image used is ${\n src || data?.url || passthroughProps?.key || 'unknown'\n }`,\n ].join(' '),\n );\n }\n\n /*\n * We check to see whether the image is fixed width or not,\n * if fixed, we still provide a srcSet, but only to account for\n * different pixel densities.\n */\n if (fixedWidth) {\n return (\n <FixedWidthImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n height={height}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n ref={ref}\n width={width}\n data={data}\n />\n );\n } else {\n return (\n <FluidImage\n aspectRatio={aspectRatio}\n crop={crop}\n decoding={decoding}\n imageWidths={imageWidths}\n loader={loader}\n loading={loading}\n normalizedProps={normalizedProps}\n passthroughProps={passthroughProps}\n placeholderWidth={placeholderWidth}\n ref={ref}\n sizes={sizes}\n data={data}\n />\n );\n }\n },\n);\n\ntype FixedImageExludedProps =\n | 'data'\n | 'loader'\n | 'loaderOptions'\n | 'sizes'\n | 'srcSetOptions'\n | 'widths';\n\ntype FixedWidthImageProps = Omit<HydrogenImageProps, FixedImageExludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n loader: Loader;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n normalizedProps: NormalizedProps;\n imageWidths: number[];\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FixedWidthImage = React.forwardRef<\n HTMLImageElement,\n FixedWidthImageProps\n>(\n (\n {\n aspectRatio,\n crop,\n decoding,\n height,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n width,\n data,\n },\n ref,\n ) => {\n const fixed = React.useMemo(() => {\n const intWidth: number | undefined = getNormalizedFixedUnit(width);\n const intHeight: number | undefined = getNormalizedFixedUnit(height);\n\n /*\n * The aspect ratio for fixed width images is taken from the explicitly\n * set prop, but if that's not present, and both width and height are\n * set, we calculate the aspect ratio from the width and height—as\n * long as they share the same unit type (e.g. both are 'px').\n */\n const fixedAspectRatio = aspectRatio\n ? aspectRatio\n : unitsMatch(normalizedProps.width, normalizedProps.height)\n ? [intWidth, intHeight].join('/')\n : normalizedProps.aspectRatio\n ? normalizedProps.aspectRatio\n : undefined;\n\n /*\n * The Sizes Array generates an array of all the parts\n * that make up the srcSet, including the width, height, and crop\n */\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, fixedAspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const fixedHeight = intHeight\n ? intHeight\n : fixedAspectRatio && intWidth\n ? intWidth * (parseAspectRatio(fixedAspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n const src = loader({\n src: normalizedProps.src,\n width: intWidth,\n height: fixedHeight,\n crop: normalizedProps.height === 'auto' ? undefined : crop,\n });\n\n return {\n width: intWidth,\n aspectRatio: fixedAspectRatio,\n height: fixedHeight,\n srcSet,\n src,\n };\n }, [\n aspectRatio,\n crop,\n data,\n height,\n imageWidths,\n loader,\n normalizedProps,\n width,\n ]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fixed.height}\n loading={loading}\n src={fixed.src}\n srcSet={fixed.srcSet}\n width={fixed.width}\n style={{\n aspectRatio: fixed.aspectRatio,\n ...passthroughProps.style,\n }}\n {...passthroughProps}\n />\n );\n },\n);\n\ntype FluidImageExcludedProps =\n | 'data'\n | 'width'\n | 'height'\n | 'loader'\n | 'loaderOptions'\n | 'srcSetOptions';\n\ntype FluidImageProps = Omit<HydrogenImageProps, FluidImageExcludedProps> &\n Pick<HydrogenImageBaseProps, 'data'> & {\n imageWidths: number[];\n loader: Loader;\n normalizedProps: NormalizedProps;\n passthroughProps: React.ImgHTMLAttributes<HTMLImageElement>;\n placeholderWidth: number;\n ref: React.Ref<HTMLImageElement>;\n };\n\nconst FluidImage = React.forwardRef<HTMLImageElement, FluidImageProps>(\n (\n {\n crop,\n decoding,\n imageWidths,\n loader = shopifyLoader,\n loading,\n normalizedProps,\n passthroughProps,\n placeholderWidth,\n sizes,\n data,\n },\n ref,\n ) => {\n const fluid = React.useMemo(() => {\n const sizesArray =\n imageWidths === undefined\n ? undefined\n : generateSizes(imageWidths, normalizedProps.aspectRatio, crop, {\n width: data?.width ?? undefined,\n height: data?.height ?? undefined,\n });\n\n const placeholderHeight =\n normalizedProps.aspectRatio && placeholderWidth\n ? placeholderWidth *\n (parseAspectRatio(normalizedProps.aspectRatio) ?? 1)\n : undefined;\n\n const srcSet = generateSrcSet(normalizedProps.src, sizesArray, loader);\n\n const src = loader({\n src: normalizedProps.src,\n width: placeholderWidth,\n height: placeholderHeight,\n crop,\n });\n\n return {\n placeholderHeight,\n srcSet,\n src,\n };\n }, [crop, data, imageWidths, loader, normalizedProps, placeholderWidth]);\n\n return (\n <img\n ref={ref}\n alt={normalizedProps.alt}\n decoding={decoding}\n height={fluid.placeholderHeight}\n loading={loading}\n sizes={sizes}\n src={fluid.src}\n srcSet={fluid.srcSet}\n width={placeholderWidth}\n {...passthroughProps}\n style={{\n width: normalizedProps.width,\n aspectRatio: normalizedProps.aspectRatio,\n ...passthroughProps.style,\n }}\n />\n );\n },\n);\n\n/**\n * The shopifyLoader function is a simple utility function that takes a src, width,\n * height, and crop and returns a string that can be used as the src for an image.\n * It can be used with the Hydrogen Image component or with the next/image component.\n * (or any others that accept equivalent configuration)\n * @param src - The source URL of the image, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg`\n * @param width - The width of the image, e.g. `100`\n * @param height - The height of the image, e.g. `100`\n * @param crop - The crop of the image, e.g. `center`\n * @returns A Shopify image URL with the correct query parameters, e.g. `https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=100&height=100&crop=center`\n *\n * @example\n * ```\n * shopifyLoader({\n * src: 'https://cdn.shopify.com/static/sample-images/garnished.jpeg',\n * width: 100,\n * height: 100,\n * crop: 'center',\n * })\n * ```\n */\nconst PLACEHOLDER_DOMAIN = 'https://placeholder.shopify.com';\nexport function shopifyLoader({src, width, height, crop}: LoaderParams) {\n if (!src) {\n return '';\n }\n\n const url = new URL(src, PLACEHOLDER_DOMAIN);\n\n if (width) {\n url.searchParams.append('width', Math.round(width).toString());\n }\n\n if (height) {\n url.searchParams.append('height', Math.round(height).toString());\n }\n\n if (crop) {\n url.searchParams.append('crop', crop);\n }\n return url.href.replace(PLACEHOLDER_DOMAIN, '');\n}\n\n/**\n * Checks whether the width and height share the same unit type\n * @param width - The width of the image, e.g. 100% | 10px\n * @param height - The height of the image, e.g. auto | 100px\n * @returns Whether the width and height share the same unit type (boolean)\n */\nfunction unitsMatch(\n width: string | number = '100%',\n height: string | number = 'auto',\n): boolean {\n return (\n getUnitValueParts(width.toString()).unit ===\n getUnitValueParts(height.toString()).unit\n );\n}\n\n/**\n * Given a CSS size, returns the unit and number parts of the value\n * @param value - The CSS size, e.g. 100px\n * @returns The unit and number parts of the value, e.g. \\{unit: 'px', number: 100\\}\n */\nfunction getUnitValueParts(value: string): {unit: string; number: number} {\n const unit = value.replace(/[0-9.]/g, '');\n const number = parseFloat(value.replace(unit, ''));\n\n return {\n unit: unit === '' ? (number === undefined ? 'auto' : 'px') : unit,\n number,\n };\n}\n\n/**\n * Given a value, returns the width of the image as an integer in pixels\n * @param value - The width of the image, e.g. 16px | 1rem | 1em | 16\n * @returns The width of the image in pixels, e.g. 16, or undefined if the value is not a fixed unit\n */\nfunction getNormalizedFixedUnit(value?: string | number): number | undefined {\n if (value === undefined) {\n return;\n }\n\n const {unit, number} = getUnitValueParts(value.toString());\n\n switch (unit) {\n case 'em':\n return number * 16;\n case 'rem':\n return number * 16;\n case 'px':\n return number;\n case '':\n return number;\n default:\n return;\n }\n}\n\n/**\n * This function checks whether a width is fixed or not.\n * @param width - The width of the image, e.g. 100 | '100px' | '100em' | '100rem'\n * @returns Whether the width is fixed or not\n */\nfunction isFixedWidth(width: string | number): boolean {\n const fixedEndings = /\\d(px|em|rem)$/;\n return typeof width === 'number' || fixedEndings.test(width);\n}\n\n/**\n * This function generates a srcSet for Shopify images.\n * @param src - The source URL of the image, e.g. https://cdn.shopify.com/static/sample-images/garnished.jpeg\n * @param sizesArray - An array of objects containing the `width`, `height`, and `crop` of the image, e.g. [\\{width: 200, height: 200, crop: 'center'\\}, \\{width: 400, height: 400, crop: 'center'\\}]\n * @param loader - A function that takes a Shopify image URL and returns a Shopify image URL with the correct query parameters\n * @returns A srcSet for Shopify images, e.g. 'https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=200&height=200&crop=center 200w, https://cdn.shopify.com/static/sample-images/garnished.jpeg?width=400&height=400&crop=center 400w'\n */\nexport function generateSrcSet(\n src?: string,\n sizesArray?: Array<{width?: number; height?: number; crop?: Crop}>,\n loader: Loader = shopifyLoader,\n): string {\n if (!src) {\n return '';\n }\n\n if (sizesArray?.length === 0 || !sizesArray) {\n return src;\n }\n\n return sizesArray\n .map(\n (size, i) =>\n `${loader({\n src,\n width: size.width,\n height: size.height,\n crop: size.crop,\n })} ${sizesArray.length === 3 ? `${i + 1}x` : `${size.width ?? 0}w`}`,\n )\n .join(`, `);\n}\n\n/**\n * This function generates an array of sizes for Shopify images, for both fixed and responsive images.\n * @param width - The CSS width of the image\n * @param intervals - The number of intervals to generate\n * @param startingWidth - The starting width of the image\n * @param incrementSize - The size of each interval\n * @returns An array of widths\n */\nexport function generateImageWidths(\n width: string | number = '100%',\n intervals: number,\n startingWidth: number,\n incrementSize: number,\n): number[] {\n const responsive = Array.from(\n {length: intervals},\n (_, i) => i * incrementSize + startingWidth,\n );\n\n const fixed = Array.from(\n {length: 3},\n (_, i) => (i + 1) * (getNormalizedFixedUnit(width) ?? 0),\n );\n\n return isFixedWidth(width) ? fixed : responsive;\n}\n\n/**\n * Simple utility function to convert an aspect ratio CSS string to a decimal, currently only supports values like `1/1`, not `0.5`, or `auto`\n * @param aspectRatio - The aspect ratio of the image, e.g. `1/1`\n * @returns The aspect ratio as a number, e.g. `0.5`\n *\n * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/aspect-ratio}\n */\nexport function parseAspectRatio(aspectRatio?: string): number | undefined {\n if (!aspectRatio) return;\n const [width, height] = aspectRatio.split('/');\n return 1 / (Number(width) / Number(height));\n}\n\n// Generate data needed for Imagery loader\nexport function generateSizes(\n imageWidths?: number[],\n aspectRatio?: string,\n crop: Crop = 'center',\n sourceDimensions?: {width?: number; height?: number},\n):\n | {\n width: number;\n height: number | undefined;\n crop: Crop;\n }[]\n | undefined {\n if (!imageWidths) return;\n return imageWidths\n .map((width: number) => {\n return {\n width,\n height: aspectRatio\n ? width * (parseAspectRatio(aspectRatio) ?? 1)\n : undefined,\n crop,\n };\n })\n .filter(({width, height}) => {\n if (sourceDimensions?.width && width > sourceDimensions.width) {\n return false;\n }\n\n if (\n sourceDimensions?.height &&\n height &&\n height > sourceDimensions.height\n ) {\n return false;\n }\n\n return true;\n });\n /*\n Given:\n ([100, 200], 1/1, 'center')\n Returns:\n [{width: 100, height: 100, crop: 'center'},\n {width: 200, height: 200, crop: 'center'}]\n */\n}\n"],"names":[],"mappings":"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"}