@shopify/hydrogen 2026.10.0-preview.4 → 2026.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (437) hide show
  1. package/dist/cli/index.mjs +23 -12
  2. package/dist/client/client.d.mts +18 -15
  3. package/dist/client/client.mjs +8 -3
  4. package/dist/client/client.mjs.map +1 -1
  5. package/dist/client/errors.d.mts +35 -13
  6. package/dist/client/errors.mjs +23 -13
  7. package/dist/client/errors.mjs.map +1 -1
  8. package/dist/client/types.d.mts +140 -90
  9. package/dist/core/analytics/cart-tracker.d.mts +19 -10
  10. package/dist/core/analytics/cart-tracker.mjs +18 -10
  11. package/dist/core/analytics/cart-tracker.mjs.map +1 -1
  12. package/dist/core/analytics/events.d.mts +17 -11
  13. package/dist/core/analytics/events.mjs +13 -4
  14. package/dist/core/analytics/events.mjs.map +1 -1
  15. package/dist/core/analytics/types.d.mts +207 -118
  16. package/dist/core/analytics/utils/flatten-connection.d.mts +11 -9
  17. package/dist/core/analytics/utils/flatten-connection.mjs +7 -6
  18. package/dist/core/analytics/utils/flatten-connection.mjs.map +1 -1
  19. package/dist/core/cache/fetch-with-cache.d.mts +24 -4
  20. package/dist/core/cache/fetch-with-cache.mjs +17 -2
  21. package/dist/core/cache/fetch-with-cache.mjs.map +1 -1
  22. package/dist/core/cache/key.d.mts +2 -0
  23. package/dist/core/cache/key.mjs.map +1 -1
  24. package/dist/core/cache/run-with-cache.d.mts +56 -8
  25. package/dist/core/cache/run-with-cache.mjs +17 -6
  26. package/dist/core/cache/run-with-cache.mjs.map +1 -1
  27. package/dist/core/cache/store.d.mts +8 -6
  28. package/dist/core/cache/store.mjs.map +1 -1
  29. package/dist/core/cache/strategies.d.mts +27 -2
  30. package/dist/core/cache/strategies.mjs +6 -0
  31. package/dist/core/cache/strategies.mjs.map +1 -1
  32. package/dist/core/cart/actions.d.mts +37 -30
  33. package/dist/core/cart/actions.mjs +21 -9
  34. package/dist/core/cart/actions.mjs.map +1 -1
  35. package/dist/core/cart/attach-quantity-input.d.mts +8 -7
  36. package/dist/core/cart/attach-quantity-input.mjs +8 -7
  37. package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
  38. package/dist/core/cart/buyer-identity-sync.d.mts +7 -6
  39. package/dist/core/cart/buyer-identity-sync.mjs.map +1 -1
  40. package/dist/core/cart/cart.d.mts +59 -46
  41. package/dist/core/cart/cart.mjs +46 -29
  42. package/dist/core/cart/cart.mjs.map +1 -1
  43. package/dist/core/cart/cookie.d.mts +8 -4
  44. package/dist/core/cart/cookie.mjs +8 -4
  45. package/dist/core/cart/cookie.mjs.map +1 -1
  46. package/dist/core/cart/form.d.mts +34 -20
  47. package/dist/core/cart/form.mjs +6 -4
  48. package/dist/core/cart/form.mjs.map +1 -1
  49. package/dist/core/cart/get-cart.d.mts +27 -15
  50. package/dist/core/cart/get-cart.mjs +13 -8
  51. package/dist/core/cart/get-cart.mjs.map +1 -1
  52. package/dist/core/cart/quantity.d.mts +16 -5
  53. package/dist/core/cart/quantity.mjs +16 -5
  54. package/dist/core/cart/quantity.mjs.map +1 -1
  55. package/dist/core/cart/queries.d.mts +25 -19
  56. package/dist/core/cart/queries.mjs +15 -1
  57. package/dist/core/cart/queries.mjs.map +1 -1
  58. package/dist/core/cart/server-handlers.d.mts +82 -45
  59. package/dist/core/cart/server-handlers.mjs +4 -0
  60. package/dist/core/cart/server-handlers.mjs.map +1 -1
  61. package/dist/core/cart/state.d.mts +110 -64
  62. package/dist/core/cart/state.mjs +24 -6
  63. package/dist/core/cart/state.mjs.map +1 -1
  64. package/dist/core/collection/collection.d.mts +52 -53
  65. package/dist/core/collection/collection.mjs +5 -3
  66. package/dist/core/collection/collection.mjs.map +1 -1
  67. package/dist/core/collection/reconciler.d.mts +23 -16
  68. package/dist/core/collection/reconciler.mjs +8 -4
  69. package/dist/core/collection/reconciler.mjs.map +1 -1
  70. package/dist/core/collection/state.d.mts +31 -26
  71. package/dist/core/collection/state.mjs +4 -3
  72. package/dist/core/collection/state.mjs.map +1 -1
  73. package/dist/core/collection/url.d.mts +70 -29
  74. package/dist/core/collection/url.mjs +63 -25
  75. package/dist/core/collection/url.mjs.map +1 -1
  76. package/dist/core/constants.d.mts +1 -0
  77. package/dist/core/constants.mjs +1 -0
  78. package/dist/core/constants.mjs.map +1 -1
  79. package/dist/core/logging/logging.d.mts +14 -8
  80. package/dist/core/logging/logging.mjs +15 -10
  81. package/dist/core/logging/logging.mjs.map +1 -1
  82. package/dist/core/logging/types.d.mts +28 -11
  83. package/dist/core/logging/types.mjs.map +1 -1
  84. package/dist/core/money/format.d.mts +6 -4
  85. package/dist/core/money/format.mjs +19 -9
  86. package/dist/core/money/format.mjs.map +1 -1
  87. package/dist/core/money/types.d.mts +41 -25
  88. package/dist/core/predictive-search/form.d.mts +37 -7
  89. package/dist/core/predictive-search/form.mjs +21 -3
  90. package/dist/core/predictive-search/form.mjs.map +1 -1
  91. package/dist/core/predictive-search/queries.d.mts +18 -30
  92. package/dist/core/predictive-search/queries.mjs +7 -3
  93. package/dist/core/predictive-search/queries.mjs.map +1 -1
  94. package/dist/core/predictive-search/search.d.mts +25 -27
  95. package/dist/core/predictive-search/search.mjs +20 -19
  96. package/dist/core/predictive-search/search.mjs.map +1 -1
  97. package/dist/core/predictive-search/server-handlers.d.mts +25 -26
  98. package/dist/core/predictive-search/server-handlers.mjs +4 -0
  99. package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
  100. package/dist/core/predictive-search/store.d.mts +65 -24
  101. package/dist/core/predictive-search/store.mjs +12 -1
  102. package/dist/core/predictive-search/store.mjs.map +1 -1
  103. package/dist/core/predictive-search/url.d.mts +36 -37
  104. package/dist/core/predictive-search/url.mjs +14 -6
  105. package/dist/core/predictive-search/url.mjs.map +1 -1
  106. package/dist/core/product/form.d.mts +63 -16
  107. package/dist/core/product/form.mjs +8 -2
  108. package/dist/core/product/form.mjs.map +1 -1
  109. package/dist/core/product/options.d.mts +8 -10
  110. package/dist/core/product/options.mjs +8 -10
  111. package/dist/core/product/options.mjs.map +1 -1
  112. package/dist/core/product/product-form.d.mts +77 -29
  113. package/dist/core/product/product-form.mjs +32 -10
  114. package/dist/core/product/product-form.mjs.map +1 -1
  115. package/dist/core/product/state.d.mts +55 -32
  116. package/dist/core/product/url.d.mts +19 -13
  117. package/dist/core/product/url.mjs +10 -10
  118. package/dist/core/product/url.mjs.map +1 -1
  119. package/dist/core/request-context.d.mts +77 -19
  120. package/dist/core/request-context.mjs +5 -1
  121. package/dist/core/request-context.mjs.map +1 -1
  122. package/dist/core/request-routing/handle-shopify-redirects.d.mts +12 -4
  123. package/dist/core/request-routing/handle-shopify-redirects.mjs +9 -2
  124. package/dist/core/request-routing/handle-shopify-redirects.mjs.map +1 -1
  125. package/dist/core/request-routing/handle-shopify-routes.d.mts +5 -5
  126. package/dist/core/request-routing/handle-shopify-routes.mjs +7 -5
  127. package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  128. package/dist/core/request-routing/interceptors/proxy.mjs +79 -43
  129. package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
  130. package/dist/core/request-routing/interceptors/ucp-profile-proxy.mjs +69 -0
  131. package/dist/core/request-routing/interceptors/ucp-profile-proxy.mjs.map +1 -0
  132. package/dist/core/request-routing/interceptors/url-redirects.mjs +6 -1
  133. package/dist/core/request-routing/interceptors/url-redirects.mjs.map +1 -1
  134. package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
  135. package/dist/core/request-routing/interceptors/well-known.mjs.map +1 -1
  136. package/dist/core/request-routing/registered-routes.d.mts +12 -0
  137. package/dist/core/request-routing/registered-routes.mjs +12 -0
  138. package/dist/core/request-routing/registered-routes.mjs.map +1 -1
  139. package/dist/core/request-routing/route-types.d.mts +43 -18
  140. package/dist/core/shop-pay/shop-pay.d.mts +71 -35
  141. package/dist/core/shop-pay/shop-pay.mjs +36 -10
  142. package/dist/core/shop-pay/shop-pay.mjs.map +1 -1
  143. package/dist/core/shopify-scripts/index.d.mts +15 -5
  144. package/dist/core/shopify-scripts/index.mjs +15 -5
  145. package/dist/core/shopify-scripts/index.mjs.map +1 -1
  146. package/dist/core/shopify-scripts/initialize.d.mts +12 -4
  147. package/dist/core/shopify-scripts/initialize.mjs +12 -4
  148. package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
  149. package/dist/core/shopify-scripts/types.d.mts +58 -25
  150. package/dist/core/standard-routes/build.d.mts +11 -12
  151. package/dist/core/standard-routes/build.mjs +11 -12
  152. package/dist/core/standard-routes/build.mjs.map +1 -1
  153. package/dist/core/standard-routes/types.d.mts +30 -24
  154. package/dist/core/url.mjs +2 -1
  155. package/dist/core/url.mjs.map +1 -1
  156. package/dist/customer-account/client.d.mts +36 -69
  157. package/dist/customer-account/client.mjs +7 -8
  158. package/dist/customer-account/client.mjs.map +1 -1
  159. package/dist/customer-account/errors.d.mts +45 -36
  160. package/dist/customer-account/errors.mjs +21 -36
  161. package/dist/customer-account/errors.mjs.map +1 -1
  162. package/dist/customer-account/graphql.d.mts +11 -23
  163. package/dist/customer-account/graphql.mjs +3 -9
  164. package/dist/customer-account/graphql.mjs.map +1 -1
  165. package/dist/customer-account/session.d.mts +130 -100
  166. package/dist/customer-account/session.mjs +32 -10
  167. package/dist/customer-account/session.mjs.map +1 -1
  168. package/dist/customer-account/type-resolver.d.mts +7 -8
  169. package/dist/development/client/client.d.mts +18 -15
  170. package/dist/development/client/client.mjs +8 -3
  171. package/dist/development/client/client.mjs.map +1 -1
  172. package/dist/development/client/errors.d.mts +35 -13
  173. package/dist/development/client/errors.mjs +23 -13
  174. package/dist/development/client/errors.mjs.map +1 -1
  175. package/dist/development/client/types.d.mts +140 -90
  176. package/dist/development/core/analytics/cart-tracker.d.mts +19 -10
  177. package/dist/development/core/analytics/cart-tracker.mjs +18 -10
  178. package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
  179. package/dist/development/core/analytics/events.d.mts +17 -11
  180. package/dist/development/core/analytics/events.mjs +13 -4
  181. package/dist/development/core/analytics/events.mjs.map +1 -1
  182. package/dist/development/core/analytics/types.d.mts +207 -118
  183. package/dist/development/core/analytics/utils/flatten-connection.d.mts +11 -9
  184. package/dist/development/core/analytics/utils/flatten-connection.mjs +7 -6
  185. package/dist/development/core/analytics/utils/flatten-connection.mjs.map +1 -1
  186. package/dist/development/core/cache/fetch-with-cache.d.mts +24 -4
  187. package/dist/development/core/cache/fetch-with-cache.mjs +17 -2
  188. package/dist/development/core/cache/fetch-with-cache.mjs.map +1 -1
  189. package/dist/development/core/cache/key.d.mts +2 -0
  190. package/dist/development/core/cache/key.mjs.map +1 -1
  191. package/dist/development/core/cache/run-with-cache.d.mts +56 -8
  192. package/dist/development/core/cache/run-with-cache.mjs +17 -6
  193. package/dist/development/core/cache/run-with-cache.mjs.map +1 -1
  194. package/dist/development/core/cache/store.d.mts +8 -6
  195. package/dist/development/core/cache/store.mjs.map +1 -1
  196. package/dist/development/core/cache/strategies.d.mts +27 -2
  197. package/dist/development/core/cache/strategies.mjs +6 -0
  198. package/dist/development/core/cache/strategies.mjs.map +1 -1
  199. package/dist/development/core/cart/actions.d.mts +37 -30
  200. package/dist/development/core/cart/actions.mjs +21 -9
  201. package/dist/development/core/cart/actions.mjs.map +1 -1
  202. package/dist/development/core/cart/attach-quantity-input.d.mts +8 -7
  203. package/dist/development/core/cart/attach-quantity-input.mjs +8 -7
  204. package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
  205. package/dist/development/core/cart/buyer-identity-sync.d.mts +6 -5
  206. package/dist/development/core/cart/buyer-identity-sync.mjs.map +1 -1
  207. package/dist/development/core/cart/cart.d.mts +59 -46
  208. package/dist/development/core/cart/cart.mjs +46 -29
  209. package/dist/development/core/cart/cart.mjs.map +1 -1
  210. package/dist/development/core/cart/cookie.d.mts +8 -4
  211. package/dist/development/core/cart/cookie.mjs +8 -4
  212. package/dist/development/core/cart/cookie.mjs.map +1 -1
  213. package/dist/development/core/cart/form.d.mts +34 -20
  214. package/dist/development/core/cart/form.mjs +6 -4
  215. package/dist/development/core/cart/form.mjs.map +1 -1
  216. package/dist/development/core/cart/get-cart.d.mts +27 -15
  217. package/dist/development/core/cart/get-cart.mjs +13 -8
  218. package/dist/development/core/cart/get-cart.mjs.map +1 -1
  219. package/dist/development/core/cart/quantity.d.mts +16 -5
  220. package/dist/development/core/cart/quantity.mjs +16 -5
  221. package/dist/development/core/cart/quantity.mjs.map +1 -1
  222. package/dist/development/core/cart/queries.d.mts +25 -19
  223. package/dist/development/core/cart/queries.mjs +15 -1
  224. package/dist/development/core/cart/queries.mjs.map +1 -1
  225. package/dist/development/core/cart/server-handlers.d.mts +82 -45
  226. package/dist/development/core/cart/server-handlers.mjs +4 -0
  227. package/dist/development/core/cart/server-handlers.mjs.map +1 -1
  228. package/dist/development/core/cart/state.d.mts +110 -64
  229. package/dist/development/core/cart/state.mjs +24 -6
  230. package/dist/development/core/cart/state.mjs.map +1 -1
  231. package/dist/development/core/collection/collection.d.mts +52 -53
  232. package/dist/development/core/collection/collection.mjs +5 -3
  233. package/dist/development/core/collection/collection.mjs.map +1 -1
  234. package/dist/development/core/collection/reconciler.d.mts +23 -16
  235. package/dist/development/core/collection/reconciler.mjs +8 -4
  236. package/dist/development/core/collection/reconciler.mjs.map +1 -1
  237. package/dist/development/core/collection/state.d.mts +31 -26
  238. package/dist/development/core/collection/state.mjs +4 -3
  239. package/dist/development/core/collection/state.mjs.map +1 -1
  240. package/dist/development/core/collection/url.d.mts +70 -29
  241. package/dist/development/core/collection/url.mjs +63 -25
  242. package/dist/development/core/collection/url.mjs.map +1 -1
  243. package/dist/development/core/constants.mjs.map +1 -1
  244. package/dist/development/core/logging/logging.d.mts +14 -8
  245. package/dist/development/core/logging/logging.mjs +15 -10
  246. package/dist/development/core/logging/logging.mjs.map +1 -1
  247. package/dist/development/core/logging/types.d.mts +28 -11
  248. package/dist/development/core/logging/types.mjs.map +1 -1
  249. package/dist/development/core/money/format.d.mts +6 -4
  250. package/dist/development/core/money/format.mjs +19 -9
  251. package/dist/development/core/money/format.mjs.map +1 -1
  252. package/dist/development/core/money/types.d.mts +41 -25
  253. package/dist/development/core/predictive-search/form.d.mts +37 -7
  254. package/dist/development/core/predictive-search/form.mjs +21 -3
  255. package/dist/development/core/predictive-search/form.mjs.map +1 -1
  256. package/dist/development/core/predictive-search/queries.d.mts +18 -30
  257. package/dist/development/core/predictive-search/queries.mjs +7 -3
  258. package/dist/development/core/predictive-search/queries.mjs.map +1 -1
  259. package/dist/development/core/predictive-search/search.d.mts +25 -27
  260. package/dist/development/core/predictive-search/search.mjs +20 -19
  261. package/dist/development/core/predictive-search/search.mjs.map +1 -1
  262. package/dist/development/core/predictive-search/server-handlers.d.mts +25 -26
  263. package/dist/development/core/predictive-search/server-handlers.mjs +4 -0
  264. package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
  265. package/dist/development/core/predictive-search/store.d.mts +65 -24
  266. package/dist/development/core/predictive-search/store.mjs +12 -1
  267. package/dist/development/core/predictive-search/store.mjs.map +1 -1
  268. package/dist/development/core/predictive-search/url.d.mts +36 -37
  269. package/dist/development/core/predictive-search/url.mjs +14 -6
  270. package/dist/development/core/predictive-search/url.mjs.map +1 -1
  271. package/dist/development/core/product/form.d.mts +63 -16
  272. package/dist/development/core/product/form.mjs +8 -2
  273. package/dist/development/core/product/form.mjs.map +1 -1
  274. package/dist/development/core/product/options.d.mts +8 -10
  275. package/dist/development/core/product/options.mjs +8 -10
  276. package/dist/development/core/product/options.mjs.map +1 -1
  277. package/dist/development/core/product/product-form.d.mts +77 -29
  278. package/dist/development/core/product/product-form.mjs +32 -10
  279. package/dist/development/core/product/product-form.mjs.map +1 -1
  280. package/dist/development/core/product/state.d.mts +55 -32
  281. package/dist/development/core/product/url.d.mts +19 -13
  282. package/dist/development/core/product/url.mjs +10 -10
  283. package/dist/development/core/product/url.mjs.map +1 -1
  284. package/dist/development/core/request-context.d.mts +77 -19
  285. package/dist/development/core/request-context.mjs +5 -1
  286. package/dist/development/core/request-context.mjs.map +1 -1
  287. package/dist/development/core/request-routing/handle-shopify-redirects.d.mts +12 -4
  288. package/dist/development/core/request-routing/handle-shopify-redirects.mjs +9 -2
  289. package/dist/development/core/request-routing/handle-shopify-redirects.mjs.map +1 -1
  290. package/dist/development/core/request-routing/handle-shopify-routes.development.d.mts +9 -1
  291. package/dist/development/core/request-routing/handle-shopify-routes.development.mjs +7 -0
  292. package/dist/development/core/request-routing/handle-shopify-routes.development.mjs.map +1 -1
  293. package/dist/development/core/request-routing/handle-shopify-routes.mjs +7 -5
  294. package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  295. package/dist/development/core/request-routing/interceptors/proxy.mjs +79 -43
  296. package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
  297. package/dist/development/core/request-routing/interceptors/ucp-profile-proxy.mjs +69 -0
  298. package/dist/development/core/request-routing/interceptors/ucp-profile-proxy.mjs.map +1 -0
  299. package/dist/development/core/request-routing/interceptors/url-redirects.mjs +6 -1
  300. package/dist/development/core/request-routing/interceptors/url-redirects.mjs.map +1 -1
  301. package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
  302. package/dist/development/core/request-routing/interceptors/well-known.mjs.map +1 -1
  303. package/dist/development/core/request-routing/registered-routes.d.mts +12 -0
  304. package/dist/development/core/request-routing/registered-routes.mjs +12 -0
  305. package/dist/development/core/request-routing/registered-routes.mjs.map +1 -1
  306. package/dist/development/core/request-routing/route-types.d.mts +43 -18
  307. package/dist/development/core/shop-pay/shop-pay.d.mts +71 -35
  308. package/dist/development/core/shop-pay/shop-pay.mjs +36 -10
  309. package/dist/development/core/shop-pay/shop-pay.mjs.map +1 -1
  310. package/dist/development/core/shopify-scripts/index.d.mts +15 -5
  311. package/dist/development/core/shopify-scripts/index.mjs +15 -5
  312. package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
  313. package/dist/development/core/shopify-scripts/initialize.d.mts +12 -4
  314. package/dist/development/core/shopify-scripts/initialize.mjs +12 -4
  315. package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
  316. package/dist/development/core/shopify-scripts/types.d.mts +58 -25
  317. package/dist/development/core/standard-routes/build.d.mts +11 -12
  318. package/dist/development/core/standard-routes/build.mjs +11 -12
  319. package/dist/development/core/standard-routes/build.mjs.map +1 -1
  320. package/dist/development/core/standard-routes/types.d.mts +30 -24
  321. package/dist/development/core/url.mjs +2 -1
  322. package/dist/development/core/url.mjs.map +1 -1
  323. package/dist/development/customer-account/session.d.mts +45 -54
  324. package/dist/development/customer-account/session.mjs +20 -4
  325. package/dist/development/customer-account/session.mjs.map +1 -1
  326. package/dist/development/globals.d.mts +20 -11
  327. package/dist/development/graphql/generated/storefront-api-types.d.mts +127 -1
  328. package/dist/development/graphql/graphql.d.mts +17 -16
  329. package/dist/development/graphql/graphql.mjs +7 -8
  330. package/dist/development/graphql/graphql.mjs.map +1 -1
  331. package/dist/development/graphql/type-resolver.d.mts +15 -8
  332. package/dist/development/react/cart.d.mts +78 -32
  333. package/dist/development/react/cart.mjs +56 -29
  334. package/dist/development/react/cart.mjs.map +1 -1
  335. package/dist/development/react/collection.d.mts +40 -22
  336. package/dist/development/react/collection.mjs +27 -7
  337. package/dist/development/react/collection.mjs.map +1 -1
  338. package/dist/development/react/predictive-search.d.mts +65 -50
  339. package/dist/development/react/predictive-search.mjs +23 -21
  340. package/dist/development/react/predictive-search.mjs.map +1 -1
  341. package/dist/development/react/product.d.mts +45 -16
  342. package/dist/development/react/product.mjs +13 -11
  343. package/dist/development/react/product.mjs.map +1 -1
  344. package/dist/development/react/shop-pay.d.mts +15 -0
  345. package/dist/development/react/shop-pay.mjs +9 -0
  346. package/dist/development/react/shop-pay.mjs.map +1 -1
  347. package/dist/development/react/shopify-scripts.d.mts +23 -2
  348. package/dist/development/react/shopify-scripts.mjs +9 -0
  349. package/dist/development/react/shopify-scripts.mjs.map +1 -1
  350. package/dist/development/vue/cart.d.mts +28 -23
  351. package/dist/development/vue/cart.mjs +42 -25
  352. package/dist/development/vue/cart.mjs.map +1 -1
  353. package/dist/development/vue/collection.d.mts +20 -12
  354. package/dist/development/vue/collection.mjs +15 -7
  355. package/dist/development/vue/collection.mjs.map +1 -1
  356. package/dist/development/vue/index.d.mts +2 -2
  357. package/dist/development/vue/index.mjs +2 -2
  358. package/dist/development/vue/predictive-search.d.mts +31 -36
  359. package/dist/development/vue/predictive-search.mjs +16 -22
  360. package/dist/development/vue/predictive-search.mjs.map +1 -1
  361. package/dist/development/vue/product.d.mts +16 -15
  362. package/dist/development/vue/product.mjs +11 -11
  363. package/dist/development/vue/product.mjs.map +1 -1
  364. package/dist/development/vue/shop-pay.d.mts +14 -17
  365. package/dist/development/vue/shop-pay.mjs +8 -0
  366. package/dist/development/vue/shop-pay.mjs.map +1 -1
  367. package/dist/development/vue/shopify-scripts.d.mts +11 -0
  368. package/dist/development/vue/shopify-scripts.mjs +6 -0
  369. package/dist/development/vue/shopify-scripts.mjs.map +1 -1
  370. package/dist/globals.d.mts +20 -11
  371. package/dist/graphql/generated/storefront-api-types.d.mts +127 -1
  372. package/dist/graphql/graphql.d.mts +17 -16
  373. package/dist/graphql/graphql.mjs +7 -8
  374. package/dist/graphql/graphql.mjs.map +1 -1
  375. package/dist/graphql/type-resolver.d.mts +15 -8
  376. package/dist/react/cart.d.mts +78 -32
  377. package/dist/react/cart.mjs +56 -29
  378. package/dist/react/cart.mjs.map +1 -1
  379. package/dist/react/collection.d.mts +40 -22
  380. package/dist/react/collection.mjs +27 -7
  381. package/dist/react/collection.mjs.map +1 -1
  382. package/dist/react/predictive-search.d.mts +65 -50
  383. package/dist/react/predictive-search.mjs +23 -21
  384. package/dist/react/predictive-search.mjs.map +1 -1
  385. package/dist/react/product.d.mts +45 -16
  386. package/dist/react/product.mjs +13 -11
  387. package/dist/react/product.mjs.map +1 -1
  388. package/dist/react/shop-pay.d.mts +15 -0
  389. package/dist/react/shop-pay.mjs +9 -0
  390. package/dist/react/shop-pay.mjs.map +1 -1
  391. package/dist/react/shopify-scripts.d.mts +23 -2
  392. package/dist/react/shopify-scripts.mjs +9 -0
  393. package/dist/react/shopify-scripts.mjs.map +1 -1
  394. package/dist/ts-plugin/index.cjs +6 -0
  395. package/dist/ts-plugin/index.cjs.map +1 -1
  396. package/dist/ts-plugin/index.d.cts +6 -0
  397. package/dist/vite/local-https.d.mts +51 -17
  398. package/dist/vite/local-https.mjs +26 -5
  399. package/dist/vite/local-https.mjs.map +1 -1
  400. package/dist/vue/cart.d.mts +28 -23
  401. package/dist/vue/cart.mjs +42 -25
  402. package/dist/vue/cart.mjs.map +1 -1
  403. package/dist/vue/collection.d.mts +20 -12
  404. package/dist/vue/collection.mjs +15 -7
  405. package/dist/vue/collection.mjs.map +1 -1
  406. package/dist/vue/index.d.mts +2 -2
  407. package/dist/vue/index.mjs +2 -2
  408. package/dist/vue/predictive-search.d.mts +31 -36
  409. package/dist/vue/predictive-search.mjs +16 -22
  410. package/dist/vue/predictive-search.mjs.map +1 -1
  411. package/dist/vue/product.d.mts +16 -15
  412. package/dist/vue/product.mjs +11 -11
  413. package/dist/vue/product.mjs.map +1 -1
  414. package/dist/vue/shop-pay.d.mts +14 -17
  415. package/dist/vue/shop-pay.mjs +8 -0
  416. package/dist/vue/shop-pay.mjs.map +1 -1
  417. package/dist/vue/shopify-scripts.d.mts +11 -0
  418. package/dist/vue/shopify-scripts.mjs +6 -0
  419. package/dist/vue/shopify-scripts.mjs.map +1 -1
  420. package/package.json +3 -1
  421. package/skills/hydrogen-analytics/SKILL.md +1 -1
  422. package/skills/hydrogen-cart-ui/SKILL.md +28 -2
  423. package/skills/hydrogen-collection-browser/SKILL.md +9 -1
  424. package/skills/hydrogen-collection-browser/references/nextjs.md +1 -1
  425. package/skills/hydrogen-collection-browser/references/nuxt.md +1 -1
  426. package/skills/hydrogen-collection-browser/references/react.md +151 -32
  427. package/skills/hydrogen-customer-account/SKILL.md +2 -0
  428. package/skills/hydrogen-request-handlers/SKILL.md +21 -11
  429. package/skills/hydrogen-setup/references/analytics.md +14 -8
  430. package/skills/hydrogen-setup/steps/1-inspect-app.md +2 -2
  431. package/skills/hydrogen-setup/steps/6-product-detail-page.md +21 -11
  432. package/skills/hydrogen-storefront-client/SKILL.md +1 -1
  433. package/skills/hydrogen-storefront-client/references/caching.md +2 -0
  434. package/skills/hydrogen-variant-form/SKILL.md +25 -7
  435. package/skills/hydrogen-variant-form/references/nextjs.md +39 -4
  436. package/skills/hydrogen-variant-form/references/nuxt.md +83 -42
  437. package/skills/hydrogen-variant-form/references/react.md +45 -9
@@ -1 +1 @@
1
- {"version":3,"file":"options.mjs","names":[],"sources":["../../../src/core/product/options.ts"],"sourcesContent":["import { getLogger } from \"../logging\";\nimport type {\n ProductInput,\n ProductOptionValueFrom,\n ProductVariantFrom,\n ProductVariantInput,\n SelectedOption,\n VariantOptionState,\n VariantOptionValueState,\n} from \"./state\";\nimport { VARIANT_SEARCH_PARAM } from \"./url\";\n\nconst log = getLogger(\"product\");\n\nconst INCLUSIVE_RANGE_END_OFFSET = 1;\n\nexport type DecodedVariantCache = Map<string, number[][]>;\n\ntype EncodedVariantConstraint = {\n optionIndex: number;\n valueIndex: number;\n};\n\n/**\n * Extracts selected product options from search params.\n *\n * Each query parameter is treated as an option name/value pair\n * (e.g. `?Color=Red&Size=M` → `[{name:\"Color\",value:\"Red\"},{name:\"Size\",value:\"M\"}]`).\n *\n * The `variant` param is reserved for numeric variant ids (Liquid parity) and is\n * never treated as an option name, even when listed in `allowedOptionNames`.\n *\n * When `allowedOptionNames` is provided, the search params are filtered to only\n * entries whose decoded param name exactly matches a product option name.\n * Passing an empty array filters out every option.\n *\n * Pass the result to `createProductFormStore` as `selectedOptions`. Used as a\n * fallback when the product has no `selectedOrFirstAvailableVariant`.\n *\n * @example\n * ```ts\n * // Loader (React Router / Remix)\n * const selectedOptions = getSelectedProductOptions({\n * searchParams: new URL(request.url).searchParams,\n * });\n *\n * // Filter to known product option names when product data is available\n * const selectedOptions = getSelectedProductOptions({\n * searchParams,\n * allowedOptionNames: product.options.map((option) => option.name),\n * });\n * ```\n */\nexport function getSelectedProductOptions({\n searchParams,\n allowedOptionNames,\n}: {\n searchParams: URLSearchParams;\n allowedOptionNames?: readonly string[];\n}): SelectedOption[] {\n const allowedOptionNameSet = allowedOptionNames ? new Set(allowedOptionNames) : null;\n const selectedOptions: SelectedOption[] = [];\n\n for (const [name, value] of searchParams.entries()) {\n if (name === VARIANT_SEARCH_PARAM) continue;\n if (allowedOptionNameSet && !allowedOptionNameSet.has(name)) continue;\n selectedOptions.push({ name, value });\n }\n\n return selectedOptions;\n}\n\nfunction getAdjacentAndFirstSelectableVariants<TProduct extends ProductInput>(\n product: TProduct,\n): ProductVariantFrom<TProduct>[] {\n // Shopify returns a bounded set, not the whole matrix. Treat it as a concrete-variant cache.\n const variants = new Map<string, ProductVariantFrom<TProduct>>();\n\n for (const option of product.options) {\n for (const value of option.optionValues) {\n if (isConcreteProductVariant<TProduct>(value.firstSelectableVariant)) {\n addVariant(variants, value.firstSelectableVariant, product.options);\n }\n }\n }\n\n for (const variant of product.adjacentVariants) {\n if (isConcreteProductVariant<TProduct>(variant)) addVariant(variants, variant, product.options);\n }\n\n if (isConcreteProductVariant<TProduct>(product.selectedOrFirstAvailableVariant)) {\n addVariant(variants, product.selectedOrFirstAvailableVariant, product.options);\n }\n\n return [...variants.values()];\n}\n\nexport function buildProductOptions<TProduct extends ProductInput>(\n product: TProduct,\n selectedOptions: SelectedOption[],\n cache?: DecodedVariantCache,\n): VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[] {\n const selectedOptionMap = selectedOptionsToMap(selectedOptions);\n const optionValueIndex = buildOptionValueIndex(product);\n const selectableVariants = getAdjacentAndFirstSelectableVariants(product);\n const encodedOptionIndexes = buildEncodedOptionIndexes(product, selectableVariants);\n const variants = mapVariants(product, selectableVariants);\n const currentProductOptionValues = buildCurrentProductOptionValues(product, selectableVariants);\n\n return product.options.map((option) => ({\n name: option.name,\n values: option.optionValues.map((value) =>\n buildProductOptionValue({\n cache,\n currentProductOptionValues,\n encodedOptionIndexes,\n option,\n optionValueIndex,\n product,\n selectedOptionMap,\n value,\n variants,\n }),\n ),\n }));\n}\n\nexport function selectedOptionsToMap(selectedOptions: SelectedOption[]): Record<string, string> {\n const map: Record<string, string> = Object.create(null);\n for (const option of selectedOptions) {\n map[option.name] = option.value;\n }\n return map;\n}\n\nfunction selectedOptionsKey(\n selectedOptions: SelectedOption[],\n productOptions: Array<{ name: string }>,\n): string {\n return JSON.stringify(\n selectedOptionsFromMap({ options: productOptions }, selectedOptionsToMap(selectedOptions)),\n );\n}\n\nexport function selectedOptionsFromMap(\n product: { options: Array<{ name: string }> },\n selectedOptionMap: Record<string, string>,\n): SelectedOption[] {\n const selectedOptions: SelectedOption[] = [];\n for (const option of product.options) {\n const value = selectedOptionMap[option.name];\n if (value !== undefined) selectedOptions.push({ name: option.name, value });\n }\n return selectedOptions;\n}\n\nfunction mapVariants<TProduct extends ProductInput>(\n product: TProduct,\n selectableVariants: ProductVariantFrom<TProduct>[],\n): Map<string, ProductVariantFrom<TProduct>> {\n const variants = new Map<string, ProductVariantFrom<TProduct>>();\n for (const variant of selectableVariants) {\n variants.set(selectedOptionsKey(variant.selectedOptions, product.options), variant);\n }\n return variants;\n}\n\nfunction addVariant<TVariant extends ProductVariantInput>(\n variants: Map<string, TVariant>,\n variant: TVariant,\n productOptions: Array<{ name: string }>,\n): void {\n variants.set(selectedOptionsKey(variant.selectedOptions, productOptions), variant);\n}\n\nfunction buildProductOptionValue<TProduct extends ProductInput>({\n cache,\n currentProductOptionValues,\n encodedOptionIndexes,\n option,\n optionValueIndex,\n product,\n selectedOptionMap,\n value,\n variants,\n}: {\n cache?: DecodedVariantCache;\n currentProductOptionValues: Map<string, Set<string>>;\n encodedOptionIndexes: Map<string, Map<string, number>>;\n option: TProduct[\"options\"][number];\n optionValueIndex: Map<string, Map<string, number>>;\n product: TProduct;\n selectedOptionMap: Record<string, string>;\n value: ProductOptionValueFrom<TProduct>;\n variants: Map<string, ProductVariantFrom<TProduct>>;\n}): VariantOptionValueState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>> {\n const firstSelectableVariant = isConcreteProductVariant<TProduct>(value.firstSelectableVariant)\n ? value.firstSelectableVariant\n : null;\n const selected = selectedOptionMap[option.name] === value.name;\n const crossProduct = isCrossProductOptionValue(\n product,\n option.name,\n value.name,\n firstSelectableVariant,\n currentProductOptionValues,\n );\n const targetOptionMap = buildTargetOptionMap({\n currentProductOptionValues,\n crossProduct,\n firstSelectableVariant,\n optionName: option.name,\n selectedOptionMap,\n valueName: value.name,\n });\n const targetSelectedOptions = selectedOptionsFromMap(product, targetOptionMap);\n const key = selectedOptionsKey(targetSelectedOptions, product.options);\n const variant = variants.get(key) ?? null;\n const targetProductHandle = getTargetProductHandle(product, crossProduct, firstSelectableVariant);\n const encodingConstraints = buildEncodingConstraints(\n targetOptionMap,\n product,\n optionValueIndex,\n encodedOptionIndexes.get(targetProductHandle),\n );\n\n return {\n name: value.name,\n swatch: value.swatch,\n selected,\n exists: resolveEncodedStatus(product.encodedVariantExistence, encodingConstraints, true, cache),\n available: resolveEncodedStatus(\n product.encodedVariantAvailability,\n encodingConstraints,\n variant?.availableForSale ?? false,\n cache,\n ),\n variant,\n selectedOptions: variant?.selectedOptions ?? targetSelectedOptions,\n handle: getOptionValueHandle(selected, product.handle, variant, firstSelectableVariant),\n };\n}\n\nfunction getOptionValueHandle<TVariant extends ProductVariantInput>(\n selected: boolean,\n productHandle: string,\n variant: TVariant | null,\n firstSelectableVariant: TVariant | null,\n): string {\n if (selected) return productHandle;\n return variant?.product?.handle ?? firstSelectableVariant?.product?.handle ?? productHandle;\n}\n\nfunction buildOptionValueIndex<TProduct extends ProductInput>(\n product: TProduct,\n): Map<string, Map<string, number>> {\n return new Map(\n product.options.map((option) => [\n option.name,\n new Map(option.optionValues.map((value, index) => [value.name, index])),\n ]),\n );\n}\n\nfunction getTargetProductHandle<TVariant extends ProductVariantInput>(\n product: ProductInput,\n crossProduct: boolean,\n firstSelectableVariant: TVariant | null,\n): string {\n if (crossProduct && firstSelectableVariant?.product?.handle) {\n return firstSelectableVariant.product.handle;\n }\n return product.handle;\n}\n\nfunction buildEncodedOptionIndexes<TProduct extends ProductInput>(\n product: TProduct,\n selectableVariants: ProductVariantFrom<TProduct>[],\n): Map<string, Map<string, number>> {\n const indexes = new Map<string, Map<string, number>>();\n for (const variant of selectableVariants) {\n const productHandle = variant.product?.handle ?? product.handle;\n addEncodedOptionIndex(indexes, productHandle, product.options, variant.selectedOptions);\n }\n return indexes;\n}\n\nfunction addEncodedOptionIndex(\n indexes: Map<string, Map<string, number>>,\n productHandle: string,\n productOptions: Array<{ name: string }>,\n selectedOptions: SelectedOption[],\n): void {\n const index = indexes.get(productHandle) ?? new Map<string, number>();\n const selectedOptionNames = new Set(selectedOptions.map((option) => option.name));\n\n for (const option of productOptions) {\n if (!selectedOptionNames.has(option.name) || index.has(option.name)) continue;\n index.set(option.name, index.size);\n }\n\n indexes.set(productHandle, index);\n}\n\nfunction buildCurrentProductOptionValues<TProduct extends ProductInput>(\n product: TProduct,\n selectableVariants: ProductVariantFrom<TProduct>[],\n): Map<string, Set<string>> {\n const optionValues = new Map<string, Set<string>>();\n for (const variant of selectableVariants) {\n if (!variantBelongsToProduct(product, variant)) continue;\n for (const option of variant.selectedOptions) {\n addCurrentProductOptionValue(optionValues, option.name, option.value);\n }\n }\n return optionValues;\n}\n\nfunction addCurrentProductOptionValue(\n optionValues: Map<string, Set<string>>,\n optionName: string,\n valueName: string,\n): void {\n const values = optionValues.get(optionName) ?? new Set<string>();\n values.add(valueName);\n optionValues.set(optionName, values);\n}\n\nfunction isCrossProductOptionValue<TVariant extends ProductVariantInput>(\n product: ProductInput,\n optionName: string,\n valueName: string,\n firstSelectableVariant: TVariant | null,\n currentProductOptionValues: Map<string, Set<string>>,\n): boolean {\n if (!firstSelectableVariant || variantBelongsToProduct(product, firstSelectableVariant)) {\n return false;\n }\n return !currentProductOptionValues.get(optionName)?.has(valueName);\n}\n\nfunction variantBelongsToProduct<TVariant extends ProductVariantInput>(\n product: ProductInput,\n variant: TVariant,\n): boolean {\n return variant.product?.handle === undefined || variant.product.handle === product.handle;\n}\n\ntype TargetOptionMapInput<TVariant extends ProductVariantInput> = {\n currentProductOptionValues: Map<string, Set<string>>;\n crossProduct: boolean;\n firstSelectableVariant: TVariant | null;\n optionName: string;\n selectedOptionMap: Record<string, string>;\n valueName: string;\n};\n\nfunction buildTargetOptionMap<TVariant extends ProductVariantInput>({\n currentProductOptionValues,\n crossProduct,\n firstSelectableVariant,\n optionName,\n selectedOptionMap,\n valueName,\n}: TargetOptionMapInput<TVariant>): Record<string, string> {\n if (crossProduct && firstSelectableVariant) {\n return selectedOptionsToMap(firstSelectableVariant.selectedOptions);\n }\n\n const nextOptionMap = { ...selectedOptionMap, [optionName]: valueName };\n if (currentProductOptionValues.size === 0) return nextOptionMap;\n return filterOptionMap(nextOptionMap, currentProductOptionValues);\n}\n\nfunction filterOptionMap(\n selectedOptionMap: Record<string, string>,\n currentProductOptionValues: Map<string, Set<string>>,\n): Record<string, string> {\n const filtered: Record<string, string> = Object.create(null);\n for (const [optionName, values] of currentProductOptionValues) {\n const value = selectedOptionMap[optionName];\n if (value !== undefined && values.has(value)) filtered[optionName] = value;\n }\n return filtered;\n}\n\nfunction buildEncodingConstraints<TProduct extends ProductInput>(\n selectedOptionMap: Record<string, string>,\n product: TProduct,\n optionValueIndex = buildOptionValueIndex(product),\n encodedOptionIndex?: Map<string, number>,\n): EncodedVariantConstraint[] {\n const constraints: EncodedVariantConstraint[] = [];\n\n for (const [productOptionIndex, option] of product.options.entries()) {\n const selectedValue = selectedOptionMap[option.name];\n if (selectedValue === undefined) continue;\n const optionIndex = encodedOptionIndex?.get(option.name) ?? productOptionIndex;\n const valueIndex = optionValueIndex.get(option.name)?.get(selectedValue);\n if (valueIndex === undefined) continue;\n constraints.push({ optionIndex, valueIndex });\n }\n\n return constraints;\n}\n\nfunction resolveEncodedStatus(\n encodedField: string | null | undefined,\n constraints: EncodedVariantConstraint[],\n fallback: boolean,\n cache?: DecodedVariantCache,\n): boolean {\n if (!encodedField) return fallback;\n return isAnyEncodedVariantMatchingConstraints(constraints, encodedField, cache);\n}\n\nfunction isAnyEncodedVariantMatchingConstraints(\n constraints: EncodedVariantConstraint[],\n encodedVariantField: string,\n cache?: DecodedVariantCache,\n): boolean {\n if (constraints.length === 0) return false;\n\n return getDecodedVariantCombinations(encodedVariantField, cache).some((combination) =>\n combinationMatchesConstraints(combination, constraints),\n );\n}\n\nfunction getDecodedVariantCombinations(\n encodedVariantField: string,\n cache?: DecodedVariantCache,\n): number[][] {\n const cached = cache?.get(encodedVariantField);\n if (cached) return cached;\n\n const decoded = decodeEncodedVariant(encodedVariantField);\n cache?.set(encodedVariantField, decoded);\n return decoded;\n}\n\nfunction combinationMatchesConstraints(\n combination: number[],\n constraints: EncodedVariantConstraint[],\n): boolean {\n return constraints.every(\n ({ optionIndex, valueIndex }) => combination[optionIndex] === valueIndex,\n );\n}\n\nfunction decodeEncodedVariant(encodedVariantField: string | null | undefined): number[][] {\n if (!encodedVariantField) return [];\n if (!encodedVariantField.startsWith(\"v1_\")) {\n log.warn(`unsupported variant encoding: \"${encodedVariantField}\"`);\n return [];\n }\n return decodeV1EncodedVariant(encodedVariantField.replace(/^v1_/, \"\"));\n}\n\nfunction decodeV1EncodedVariant(encodedVariantField: string): number[][] {\n const tokenizer = /[ :,-]/g;\n const state: DecodeV1State = {\n currentOptionValue: [],\n decodedOptions: [],\n depth: 0,\n index: 0,\n rangeStart: null,\n };\n let token: RegExpExecArray | null;\n\n while ((token = tokenizer.exec(encodedVariantField))) {\n processDecodeV1Token(encodedVariantField, state, token, tokenizer);\n }\n\n pushFinalDecodeV1Value(encodedVariantField, state);\n\n return state.decodedOptions;\n}\n\ntype DecodeV1State = {\n currentOptionValue: number[];\n decodedOptions: number[][];\n depth: number;\n index: number;\n rangeStart: number | null;\n};\n\nfunction isConcreteProductVariant<TProduct extends ProductInput>(\n variant: ProductVariantInput | null | undefined,\n): variant is ProductVariantFrom<TProduct> {\n return variant !== null && variant !== undefined;\n}\n\nfunction processDecodeV1Token(\n encodedVariantField: string,\n state: DecodeV1State,\n token: RegExpExecArray,\n tokenizer: RegExp,\n): void {\n const operation = token[0];\n const optionValueIndex =\n Number.parseInt(encodedVariantField.slice(state.index, token.index), 10) || 0;\n\n pushDecodeV1Range(state, optionValueIndex);\n state.currentOptionValue[state.depth] = optionValueIndex;\n\n if (operation === \"-\") {\n state.rangeStart = optionValueIndex;\n } else if (operation === \":\") {\n state.depth++;\n } else {\n processDecodeV1Separator(encodedVariantField, state, token, operation);\n }\n\n state.index = tokenizer.lastIndex;\n}\n\nfunction processDecodeV1Separator(\n encodedVariantField: string,\n state: DecodeV1State,\n token: RegExpExecArray,\n operation: string,\n): void {\n if (shouldPushDecodeV1Option(encodedVariantField, token, operation)) {\n state.decodedOptions.push([...state.currentOptionValue]);\n }\n\n if (operation !== \",\") return;\n\n state.currentOptionValue.pop();\n state.depth--;\n}\n\nfunction shouldPushDecodeV1Option(\n encodedVariantField: string,\n token: RegExpExecArray,\n operation: string,\n): boolean {\n return operation === \" \" || (operation === \",\" && encodedVariantField[token.index - 1] !== \",\");\n}\n\nfunction pushFinalDecodeV1Value(encodedVariantField: string, state: DecodeV1State): void {\n const finalIndex = encodedVariantField.match(/\\d+$/g)?.[0];\n if (finalIndex === undefined) return;\n\n const finalValueIndex = Number.parseInt(finalIndex, 10);\n if (state.rangeStart !== null) {\n pushDecodeV1Range(state, finalValueIndex + INCLUSIVE_RANGE_END_OFFSET);\n return;\n }\n\n state.currentOptionValue[state.depth] = finalValueIndex;\n state.decodedOptions.push([...state.currentOptionValue]);\n}\n\nfunction pushDecodeV1Range(state: DecodeV1State, rangeEndExclusive: number): void {\n const rangeStart = state.rangeStart;\n if (rangeStart === null) return;\n\n for (let value = rangeStart; value < rangeEndExclusive; value++) {\n state.currentOptionValue[state.depth] = value;\n state.decodedOptions.push([...state.currentOptionValue]);\n }\n\n state.rangeStart = null;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"options.mjs","names":[],"sources":["../../../src/core/product/options.ts"],"sourcesContent":["import { getLogger } from \"../logging\";\nimport type {\n ProductInput,\n ProductOptionValueFrom,\n ProductVariantFrom,\n ProductVariantInput,\n SelectedOption,\n VariantOptionState,\n VariantOptionValueState,\n} from \"./state\";\nimport { VARIANT_SEARCH_PARAM } from \"./url\";\n\nconst log = getLogger(\"product\");\n\nconst INCLUSIVE_RANGE_END_OFFSET = 1;\n\nexport type DecodedVariantCache = Map<string, number[][]>;\n\ntype EncodedVariantConstraint = {\n optionIndex: number;\n valueIndex: number;\n};\n\n/**\n * Reads the customer's option selection from a product URL's search params.\n *\n * Each param becomes an option name and value. For example, `?Color=Red&Size=M`\n * becomes Color set to Red and Size set to M. The function always skips the `variant` param,\n * which holds a numeric variant ID in Liquid storefront URLs.\n *\n * Pass the result as the `selectedOptions` argument of your Storefront API product query, or as the starting selection of a product form store.\n *\n * @param input The search params to read and an optional list of allowed option names. With `allowedOptionNames`, the function keeps only params whose name exactly matches an allowed name, and an empty list keeps no params. Pass the product's option names to keep unrelated params out of the selection.\n *\n * @returns The selected options, in the order that they appear in the URL.\n *\n * @example\n * ```ts\n * // Loader (React Router / Remix)\n * const selectedOptions = getSelectedProductOptions({\n * searchParams: new URL(request.url).searchParams,\n * });\n *\n * // Filter to known product option names when product data is available\n * const selectedOptions = getSelectedProductOptions({\n * searchParams,\n * allowedOptionNames: product.options.map((option) => option.name),\n * });\n * ```\n * @publicDocs\n */\nexport function getSelectedProductOptions({\n searchParams,\n allowedOptionNames,\n}: {\n searchParams: URLSearchParams;\n allowedOptionNames?: readonly string[];\n}): SelectedOption[] {\n const allowedOptionNameSet = allowedOptionNames ? new Set(allowedOptionNames) : null;\n const selectedOptions: SelectedOption[] = [];\n\n for (const [name, value] of searchParams.entries()) {\n if (name === VARIANT_SEARCH_PARAM) continue;\n if (allowedOptionNameSet && !allowedOptionNameSet.has(name)) continue;\n selectedOptions.push({ name, value });\n }\n\n return selectedOptions;\n}\n\nfunction getAdjacentAndFirstSelectableVariants<TProduct extends ProductInput>(\n product: TProduct,\n): ProductVariantFrom<TProduct>[] {\n // Shopify returns a bounded set, not the whole matrix. Treat it as a concrete-variant cache.\n const variants = new Map<string, ProductVariantFrom<TProduct>>();\n\n for (const option of product.options) {\n for (const value of option.optionValues) {\n if (isConcreteProductVariant<TProduct>(value.firstSelectableVariant)) {\n addVariant(variants, value.firstSelectableVariant, product.options);\n }\n }\n }\n\n for (const variant of product.adjacentVariants) {\n if (isConcreteProductVariant<TProduct>(variant)) addVariant(variants, variant, product.options);\n }\n\n if (isConcreteProductVariant<TProduct>(product.selectedOrFirstAvailableVariant)) {\n addVariant(variants, product.selectedOrFirstAvailableVariant, product.options);\n }\n\n return [...variants.values()];\n}\n\nexport function buildProductOptions<TProduct extends ProductInput>(\n product: TProduct,\n selectedOptions: SelectedOption[],\n cache?: DecodedVariantCache,\n): VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[] {\n const selectedOptionMap = selectedOptionsToMap(selectedOptions);\n const optionValueIndex = buildOptionValueIndex(product);\n const selectableVariants = getAdjacentAndFirstSelectableVariants(product);\n const encodedOptionIndexes = buildEncodedOptionIndexes(product, selectableVariants);\n const variants = mapVariants(product, selectableVariants);\n const currentProductOptionValues = buildCurrentProductOptionValues(product, selectableVariants);\n\n return product.options.map((option) => ({\n name: option.name,\n values: option.optionValues.map((value) =>\n buildProductOptionValue({\n cache,\n currentProductOptionValues,\n encodedOptionIndexes,\n option,\n optionValueIndex,\n product,\n selectedOptionMap,\n value,\n variants,\n }),\n ),\n }));\n}\n\nexport function selectedOptionsToMap(selectedOptions: SelectedOption[]): Record<string, string> {\n const map: Record<string, string> = Object.create(null);\n for (const option of selectedOptions) {\n map[option.name] = option.value;\n }\n return map;\n}\n\nfunction selectedOptionsKey(\n selectedOptions: SelectedOption[],\n productOptions: Array<{ name: string }>,\n): string {\n return JSON.stringify(\n selectedOptionsFromMap({ options: productOptions }, selectedOptionsToMap(selectedOptions)),\n );\n}\n\nexport function selectedOptionsFromMap(\n product: { options: Array<{ name: string }> },\n selectedOptionMap: Record<string, string>,\n): SelectedOption[] {\n const selectedOptions: SelectedOption[] = [];\n for (const option of product.options) {\n const value = selectedOptionMap[option.name];\n if (value !== undefined) selectedOptions.push({ name: option.name, value });\n }\n return selectedOptions;\n}\n\nfunction mapVariants<TProduct extends ProductInput>(\n product: TProduct,\n selectableVariants: ProductVariantFrom<TProduct>[],\n): Map<string, ProductVariantFrom<TProduct>> {\n const variants = new Map<string, ProductVariantFrom<TProduct>>();\n for (const variant of selectableVariants) {\n variants.set(selectedOptionsKey(variant.selectedOptions, product.options), variant);\n }\n return variants;\n}\n\nfunction addVariant<TVariant extends ProductVariantInput>(\n variants: Map<string, TVariant>,\n variant: TVariant,\n productOptions: Array<{ name: string }>,\n): void {\n variants.set(selectedOptionsKey(variant.selectedOptions, productOptions), variant);\n}\n\nfunction buildProductOptionValue<TProduct extends ProductInput>({\n cache,\n currentProductOptionValues,\n encodedOptionIndexes,\n option,\n optionValueIndex,\n product,\n selectedOptionMap,\n value,\n variants,\n}: {\n cache?: DecodedVariantCache;\n currentProductOptionValues: Map<string, Set<string>>;\n encodedOptionIndexes: Map<string, Map<string, number>>;\n option: TProduct[\"options\"][number];\n optionValueIndex: Map<string, Map<string, number>>;\n product: TProduct;\n selectedOptionMap: Record<string, string>;\n value: ProductOptionValueFrom<TProduct>;\n variants: Map<string, ProductVariantFrom<TProduct>>;\n}): VariantOptionValueState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>> {\n const firstSelectableVariant = isConcreteProductVariant<TProduct>(value.firstSelectableVariant)\n ? value.firstSelectableVariant\n : null;\n const selected = selectedOptionMap[option.name] === value.name;\n const crossProduct = isCrossProductOptionValue(\n product,\n option.name,\n value.name,\n firstSelectableVariant,\n currentProductOptionValues,\n );\n const targetOptionMap = buildTargetOptionMap({\n currentProductOptionValues,\n crossProduct,\n firstSelectableVariant,\n optionName: option.name,\n selectedOptionMap,\n valueName: value.name,\n });\n const targetSelectedOptions = selectedOptionsFromMap(product, targetOptionMap);\n const key = selectedOptionsKey(targetSelectedOptions, product.options);\n const variant = variants.get(key) ?? null;\n const targetProductHandle = getTargetProductHandle(product, crossProduct, firstSelectableVariant);\n const encodingConstraints = buildEncodingConstraints(\n targetOptionMap,\n product,\n optionValueIndex,\n encodedOptionIndexes.get(targetProductHandle),\n );\n\n return {\n name: value.name,\n swatch: value.swatch,\n selected,\n exists: resolveEncodedStatus(product.encodedVariantExistence, encodingConstraints, true, cache),\n available: resolveEncodedStatus(\n product.encodedVariantAvailability,\n encodingConstraints,\n variant?.availableForSale ?? false,\n cache,\n ),\n variant,\n selectedOptions: variant?.selectedOptions ?? targetSelectedOptions,\n handle: getOptionValueHandle(selected, product.handle, variant, firstSelectableVariant),\n };\n}\n\nfunction getOptionValueHandle<TVariant extends ProductVariantInput>(\n selected: boolean,\n productHandle: string,\n variant: TVariant | null,\n firstSelectableVariant: TVariant | null,\n): string {\n if (selected) return productHandle;\n return variant?.product?.handle ?? firstSelectableVariant?.product?.handle ?? productHandle;\n}\n\nfunction buildOptionValueIndex<TProduct extends ProductInput>(\n product: TProduct,\n): Map<string, Map<string, number>> {\n return new Map(\n product.options.map((option) => [\n option.name,\n new Map(option.optionValues.map((value, index) => [value.name, index])),\n ]),\n );\n}\n\nfunction getTargetProductHandle<TVariant extends ProductVariantInput>(\n product: ProductInput,\n crossProduct: boolean,\n firstSelectableVariant: TVariant | null,\n): string {\n if (crossProduct && firstSelectableVariant?.product?.handle) {\n return firstSelectableVariant.product.handle;\n }\n return product.handle;\n}\n\nfunction buildEncodedOptionIndexes<TProduct extends ProductInput>(\n product: TProduct,\n selectableVariants: ProductVariantFrom<TProduct>[],\n): Map<string, Map<string, number>> {\n const indexes = new Map<string, Map<string, number>>();\n for (const variant of selectableVariants) {\n const productHandle = variant.product?.handle ?? product.handle;\n addEncodedOptionIndex(indexes, productHandle, product.options, variant.selectedOptions);\n }\n return indexes;\n}\n\nfunction addEncodedOptionIndex(\n indexes: Map<string, Map<string, number>>,\n productHandle: string,\n productOptions: Array<{ name: string }>,\n selectedOptions: SelectedOption[],\n): void {\n const index = indexes.get(productHandle) ?? new Map<string, number>();\n const selectedOptionNames = new Set(selectedOptions.map((option) => option.name));\n\n for (const option of productOptions) {\n if (!selectedOptionNames.has(option.name) || index.has(option.name)) continue;\n index.set(option.name, index.size);\n }\n\n indexes.set(productHandle, index);\n}\n\nfunction buildCurrentProductOptionValues<TProduct extends ProductInput>(\n product: TProduct,\n selectableVariants: ProductVariantFrom<TProduct>[],\n): Map<string, Set<string>> {\n const optionValues = new Map<string, Set<string>>();\n for (const variant of selectableVariants) {\n if (!variantBelongsToProduct(product, variant)) continue;\n for (const option of variant.selectedOptions) {\n addCurrentProductOptionValue(optionValues, option.name, option.value);\n }\n }\n return optionValues;\n}\n\nfunction addCurrentProductOptionValue(\n optionValues: Map<string, Set<string>>,\n optionName: string,\n valueName: string,\n): void {\n const values = optionValues.get(optionName) ?? new Set<string>();\n values.add(valueName);\n optionValues.set(optionName, values);\n}\n\nfunction isCrossProductOptionValue<TVariant extends ProductVariantInput>(\n product: ProductInput,\n optionName: string,\n valueName: string,\n firstSelectableVariant: TVariant | null,\n currentProductOptionValues: Map<string, Set<string>>,\n): boolean {\n if (!firstSelectableVariant || variantBelongsToProduct(product, firstSelectableVariant)) {\n return false;\n }\n return !currentProductOptionValues.get(optionName)?.has(valueName);\n}\n\nfunction variantBelongsToProduct<TVariant extends ProductVariantInput>(\n product: ProductInput,\n variant: TVariant,\n): boolean {\n return variant.product?.handle === undefined || variant.product.handle === product.handle;\n}\n\ntype TargetOptionMapInput<TVariant extends ProductVariantInput> = {\n currentProductOptionValues: Map<string, Set<string>>;\n crossProduct: boolean;\n firstSelectableVariant: TVariant | null;\n optionName: string;\n selectedOptionMap: Record<string, string>;\n valueName: string;\n};\n\nfunction buildTargetOptionMap<TVariant extends ProductVariantInput>({\n currentProductOptionValues,\n crossProduct,\n firstSelectableVariant,\n optionName,\n selectedOptionMap,\n valueName,\n}: TargetOptionMapInput<TVariant>): Record<string, string> {\n if (crossProduct && firstSelectableVariant) {\n return selectedOptionsToMap(firstSelectableVariant.selectedOptions);\n }\n\n const nextOptionMap = { ...selectedOptionMap, [optionName]: valueName };\n if (currentProductOptionValues.size === 0) return nextOptionMap;\n return filterOptionMap(nextOptionMap, currentProductOptionValues);\n}\n\nfunction filterOptionMap(\n selectedOptionMap: Record<string, string>,\n currentProductOptionValues: Map<string, Set<string>>,\n): Record<string, string> {\n const filtered: Record<string, string> = Object.create(null);\n for (const [optionName, values] of currentProductOptionValues) {\n const value = selectedOptionMap[optionName];\n if (value !== undefined && values.has(value)) filtered[optionName] = value;\n }\n return filtered;\n}\n\nfunction buildEncodingConstraints<TProduct extends ProductInput>(\n selectedOptionMap: Record<string, string>,\n product: TProduct,\n optionValueIndex = buildOptionValueIndex(product),\n encodedOptionIndex?: Map<string, number>,\n): EncodedVariantConstraint[] {\n const constraints: EncodedVariantConstraint[] = [];\n\n for (const [productOptionIndex, option] of product.options.entries()) {\n const selectedValue = selectedOptionMap[option.name];\n if (selectedValue === undefined) continue;\n const optionIndex = encodedOptionIndex?.get(option.name) ?? productOptionIndex;\n const valueIndex = optionValueIndex.get(option.name)?.get(selectedValue);\n if (valueIndex === undefined) continue;\n constraints.push({ optionIndex, valueIndex });\n }\n\n return constraints;\n}\n\nfunction resolveEncodedStatus(\n encodedField: string | null | undefined,\n constraints: EncodedVariantConstraint[],\n fallback: boolean,\n cache?: DecodedVariantCache,\n): boolean {\n if (!encodedField) return fallback;\n return isAnyEncodedVariantMatchingConstraints(constraints, encodedField, cache);\n}\n\nfunction isAnyEncodedVariantMatchingConstraints(\n constraints: EncodedVariantConstraint[],\n encodedVariantField: string,\n cache?: DecodedVariantCache,\n): boolean {\n if (constraints.length === 0) return false;\n\n return getDecodedVariantCombinations(encodedVariantField, cache).some((combination) =>\n combinationMatchesConstraints(combination, constraints),\n );\n}\n\nfunction getDecodedVariantCombinations(\n encodedVariantField: string,\n cache?: DecodedVariantCache,\n): number[][] {\n const cached = cache?.get(encodedVariantField);\n if (cached) return cached;\n\n const decoded = decodeEncodedVariant(encodedVariantField);\n cache?.set(encodedVariantField, decoded);\n return decoded;\n}\n\nfunction combinationMatchesConstraints(\n combination: number[],\n constraints: EncodedVariantConstraint[],\n): boolean {\n return constraints.every(\n ({ optionIndex, valueIndex }) => combination[optionIndex] === valueIndex,\n );\n}\n\nfunction decodeEncodedVariant(encodedVariantField: string | null | undefined): number[][] {\n if (!encodedVariantField) return [];\n if (!encodedVariantField.startsWith(\"v1_\")) {\n log.warn(`unsupported variant encoding: \"${encodedVariantField}\"`);\n return [];\n }\n return decodeV1EncodedVariant(encodedVariantField.replace(/^v1_/, \"\"));\n}\n\nfunction decodeV1EncodedVariant(encodedVariantField: string): number[][] {\n const tokenizer = /[ :,-]/g;\n const state: DecodeV1State = {\n currentOptionValue: [],\n decodedOptions: [],\n depth: 0,\n index: 0,\n rangeStart: null,\n };\n let token: RegExpExecArray | null;\n\n while ((token = tokenizer.exec(encodedVariantField))) {\n processDecodeV1Token(encodedVariantField, state, token, tokenizer);\n }\n\n pushFinalDecodeV1Value(encodedVariantField, state);\n\n return state.decodedOptions;\n}\n\ntype DecodeV1State = {\n currentOptionValue: number[];\n decodedOptions: number[][];\n depth: number;\n index: number;\n rangeStart: number | null;\n};\n\nfunction isConcreteProductVariant<TProduct extends ProductInput>(\n variant: ProductVariantInput | null | undefined,\n): variant is ProductVariantFrom<TProduct> {\n return variant !== null && variant !== undefined;\n}\n\nfunction processDecodeV1Token(\n encodedVariantField: string,\n state: DecodeV1State,\n token: RegExpExecArray,\n tokenizer: RegExp,\n): void {\n const operation = token[0];\n const optionValueIndex =\n Number.parseInt(encodedVariantField.slice(state.index, token.index), 10) || 0;\n\n pushDecodeV1Range(state, optionValueIndex);\n state.currentOptionValue[state.depth] = optionValueIndex;\n\n if (operation === \"-\") {\n state.rangeStart = optionValueIndex;\n } else if (operation === \":\") {\n state.depth++;\n } else {\n processDecodeV1Separator(encodedVariantField, state, token, operation);\n }\n\n state.index = tokenizer.lastIndex;\n}\n\nfunction processDecodeV1Separator(\n encodedVariantField: string,\n state: DecodeV1State,\n token: RegExpExecArray,\n operation: string,\n): void {\n if (shouldPushDecodeV1Option(encodedVariantField, token, operation)) {\n state.decodedOptions.push([...state.currentOptionValue]);\n }\n\n if (operation !== \",\") return;\n\n state.currentOptionValue.pop();\n state.depth--;\n}\n\nfunction shouldPushDecodeV1Option(\n encodedVariantField: string,\n token: RegExpExecArray,\n operation: string,\n): boolean {\n return operation === \" \" || (operation === \",\" && encodedVariantField[token.index - 1] !== \",\");\n}\n\nfunction pushFinalDecodeV1Value(encodedVariantField: string, state: DecodeV1State): void {\n const finalIndex = encodedVariantField.match(/\\d+$/g)?.[0];\n if (finalIndex === undefined) return;\n\n const finalValueIndex = Number.parseInt(finalIndex, 10);\n if (state.rangeStart !== null) {\n pushDecodeV1Range(state, finalValueIndex + INCLUSIVE_RANGE_END_OFFSET);\n return;\n }\n\n state.currentOptionValue[state.depth] = finalValueIndex;\n state.decodedOptions.push([...state.currentOptionValue]);\n}\n\nfunction pushDecodeV1Range(state: DecodeV1State, rangeEndExclusive: number): void {\n const rangeStart = state.rangeStart;\n if (rangeStart === null) return;\n\n for (let value = rangeStart; value < rangeEndExclusive; value++) {\n state.currentOptionValue[state.depth] = value;\n state.decodedOptions.push([...state.currentOptionValue]);\n }\n\n state.rangeStart = null;\n}\n"],"mappings":"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"}
@@ -3,28 +3,42 @@ import { CartStore } from "../cart/cart.mjs";
3
3
  import { ProductInput, ProductOptionValueFrom, ProductOptionValueInput, ProductVariantFrom, ProductVariantInput, SelectedOption, VariantOptionState } from "./state.mjs";
4
4
 
5
5
  //#region src/core/product/product-form.d.ts
6
- /** Cart and line-item errors surfaced by a {@link ProductFormStore}. */
6
+ /**
7
+ * Cart errors and warnings to show near the add-to-cart button.
8
+ *
9
+ * Each list starts with cart-level messages, followed by the messages for the cart line that holds the selected variant.
10
+ */
7
11
  interface ProductFormErrors {
12
+ /** User errors for the cart, then for the cart line that holds the selected variant. */
8
13
  userErrors: CartUserError[];
14
+ /** Warnings for the cart, then for the cart line that holds the selected variant. */
9
15
  warnings: CartWarning[];
16
+ /** Failed cart requests. Each entry has a message and, when available, an HTTP status. */
10
17
  networkErrors: CartNetworkEntry[];
11
18
  }
12
- /** Live state snapshot emitted by a {@link ProductFormStore}. */
19
+ /** The product form's option state, selection, cart errors, and matching cart line. */
13
20
  interface ProductFormStoreState<TVariant extends ProductVariantInput = ProductVariantInput, TOptionValue extends ProductOptionValueInput = ProductOptionValueInput> {
21
+ /** Each product option and its values, with the selection, existence, and availability of each value. */
14
22
  options: VariantOptionState<TVariant, TOptionValue>[];
23
+ /** Option names and values in the current selection, which can be partial. */
15
24
  selectedOptions: SelectedOption[];
25
+ /** Variant that matches the current selection, or `null` when the selection is partial or your query didn't load that variant. */
16
26
  selectedVariant: TVariant | null;
27
+ /** Cart errors and warnings to show near the add-to-cart button. */
17
28
  errors: ProductFormErrors;
29
+ /** First cart line that holds the selected variant, or `null` when no cart line holds the variant. */
18
30
  matchedLineItem: CartLine | null;
19
31
  }
20
- /** The reactive options array from a {@link ProductFormStoreState}, typed to a specific product. */
32
+ /** Option state from the product form, typed to your product query. */
21
33
  type ProductFormOptions<TProduct extends ProductInput = ProductInput> = ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>["options"];
22
34
  /**
23
- * Result returned by {@link ProductFormStore.selectOption}.
35
+ * Result of selecting an option value. Build the next product URL from the selected options in the result.
36
+ *
37
+ * - `resolved`: the selection matches a variant that your query loaded.
38
+ * - `unresolved`: the selection is valid, but your query didn't load a matching variant.
39
+ * - `invalid`: the product has no such option name or value, or no variant combines the value with the rest of the selection.
24
40
  *
25
- * - `resolved` — a specific variant was matched for the full selection.
26
- * - `unresolved` — the selection is valid but not yet complete.
27
- * - `invalid` — the option name or value does not exist on the product.
41
+ * When a complete selection returns `unresolved`, query the exact variant from the Storefront API with the returned selected options.
28
42
  */
29
43
  type VariantSelectionResult<TVariant extends ProductVariantInput = ProductVariantInput> = {
30
44
  status: "resolved";
@@ -40,53 +54,86 @@ type VariantSelectionResult<TVariant extends ProductVariantInput = ProductVarian
40
54
  selectedVariant: null;
41
55
  reason: string;
42
56
  };
43
- /** A selection result that is not invalid — either resolved or unresolved. */
57
+ /** A selection result with a `resolved` or `unresolved` status. Selection callbacks receive this result. */
44
58
  type ValidProductSelectionResult<TProduct extends ProductInput = ProductInput> = Exclude<VariantSelectionResult<ProductVariantFrom<TProduct>>, {
45
59
  status: "invalid";
46
60
  }>;
47
- /** Options for {@link createProductFormStore}. */
61
+ /** Options for creating a product form store. */
48
62
  type CreateProductFormStoreOptions = {
49
- /** Fallback selection used when the product has no `selectedOrFirstAvailableVariant`. */selectedOptions?: SelectedOption[];
63
+ /** Selection to start from when the product has no selected or first available variant. */selectedOptions?: SelectedOption[];
50
64
  };
51
- /** Manages variant selection + cart integration for a product form. */
65
+ /** Tracks the customer's option selection for one product and adds the selected variant to the cart. */
52
66
  interface ProductFormStore<TProduct extends ProductInput = ProductInput, TVariant extends ProductVariantInput = ProductVariantFrom<TProduct>> {
53
- /** Returns the current state snapshot. */
67
+ /** Returns the current product form state. */
54
68
  getState(): ProductFormStoreState<TVariant, ProductOptionValueFrom<TProduct>>;
55
- /** Registers a listener invoked on every state change. Returns an unsubscribe function. */
69
+ /** Calls the listener on every state change. Returns a function that unsubscribes the listener. */
56
70
  subscribe(listener: (state: ProductFormStoreState<TVariant, ProductOptionValueFrom<TProduct>>) => void): () => void;
57
- /** Selects an option value and resolves the new variant. */
71
+ /**
72
+ * Selects an option value and returns the result.
73
+ *
74
+ * Resolved and unresolved selections update the state. An invalid selection leaves the state unchanged and returns a reason. Build the next product URL from the selected options in the result.
75
+ */
58
76
  selectOption(name: string, value: string): VariantSelectionResult<TVariant>;
59
- /** Replaces the product data and re-derives state — use after a server-side product reload. */
77
+ /**
78
+ * Loads new product data into the store. Create the store once and call `hydrate()` each time the server reloads the product.
79
+ *
80
+ * The store keeps its subscribers. The new selection comes from the product's selected or first available variant, then from the selected options that you pass, then from the current selection.
81
+ */
60
82
  hydrate(product: TProduct, opts?: {
61
83
  selectedOptions?: SelectedOption[];
62
84
  }): void;
63
- /** Restores the store to its initial product and selection state. */
85
+ /** Restores the product data and the selection that the store started with. */
64
86
  reset(): void;
65
- /** Re-subscribes to the cart store and resyncs cart-derived state. Only needed to reuse the store after `destroy()` (e.g. React StrictMode effects). */
87
+ /** Reconnects the store to the cart after you call `destroy()`, such as in React Strict Mode effects. The store connects to the cart when you create it. */
66
88
  connect(): void;
67
- /** Tears down the store — removes cart subscription and releases resources. */
89
+ /** Disconnects the store from the cart. Call `destroy()` when the product form unmounts. */
68
90
  destroy(): void;
69
- /** Forwards a native `SubmitEvent` to the cart store's form handler (routed by the submitter's `value`, typically `add`), attaching selected variant data as event detail. */
91
+ /**
92
+ * Adds the selected variant to the cart from the product form's native submit event.
93
+ *
94
+ * The cart adds the variant when the clicked button has no value and the form includes a merchandise ID field. The cart uses the variant's title, image, and price to show the change before the server responds. The returned promise rejects when the event target isn't a form or the event has no submitter.
95
+ */
70
96
  handleFormSubmit(event: SubmitEvent): Promise<void>;
71
97
  }
72
- /** Returns the selected variant, or `null` when the selection is partial or the variant wasn't part of the query result. */
98
+ /**
99
+ * Returns the variant that matches the customer's current option selection.
100
+ *
101
+ * Show the product's price range until the selection resolves to a variant. When every option has a selection and the function returns `null`, query the exact variant from the Storefront API.
102
+ *
103
+ * @param options The `options` array from the product form state.
104
+ * @returns The matching variant, or `null` when the selection is partial or your query didn't load that variant.
105
+ * @publicDocs
106
+ */
73
107
  declare function getSelectedVariant<TVariant extends ProductVariantInput>(options: VariantOptionState<TVariant, ProductOptionValueInput>[]): TVariant | null;
74
- /** Guards whether the current selection can be added to cart — a variant must be resolved and available, and the product must not require a selling plan. */
108
+ /**
109
+ * Checks whether the customer can add the current selection to the cart. Use the result to enable or disable the add-to-cart button.
110
+ *
111
+ * @param product The product from your query. A product that requires a selling plan always returns `false`.
112
+ * @param options The `options` array from the product form state.
113
+ * @returns `true` when the selection resolves to a variant that's available for sale and the product doesn't require a selling plan.
114
+ * @publicDocs
115
+ */
75
116
  declare function canAddToCart<TProduct extends ProductInput>(product: TProduct, options: VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[]): boolean;
76
117
  /**
77
- * Finds the first cart line whose merchandise ID matches.
78
- * When selling plans are used, the same merchandise ID can appear on multiple
79
- * cart lines — this returns the first match. Future attribute-based matching
80
- * will narrow to the exact line.
118
+ * Finds the cart line that holds a variant. Use the line to show the quantity that's already in the cart.
119
+ *
120
+ * When several cart lines hold the variant with different selling plans or attributes, the function returns the first line.
121
+ *
122
+ * @param lines The cart lines, such as `data.lines.nodes` from the cart state.
123
+ * @param merchandiseId The variant ID to look for.
124
+ * @returns The first cart line that holds the variant, or `null` when no cart line holds the variant.
125
+ * @publicDocs
81
126
  */
82
127
  declare function findCartLineByMerchandiseId(lines: CartLine[], merchandiseId: string): CartLine | null;
83
128
  /**
84
- * Creates a reactive store that manages variant selection and cart integration for a product form.
129
+ * Creates a store that tracks the customer's option selection for one product and adds the selected variant to the cart.
85
130
  *
86
- * The store subscribes to the {@link CartStore} to keep error and line-item
87
- * state in sync. Call `destroy()` when the form unmounts to clean up the
88
- * subscription.
131
+ * The store resolves variants from the product data that you pass and leaves the URL to you. Build the product URL from the result of `selectOption()`. Call `destroy()` when the product form unmounts. The product provider from `createProductComponents` creates and destroys the store for you.
89
132
  *
133
+ * @param product The product from your Storefront API query.
134
+ * @param cartStore The cart store that the form adds variants to and reads cart errors from.
135
+ * @param options The selection to start from when the product has no selected or first available variant.
136
+ * @returns A product form store. The starting selection comes from the product's selected or first available variant, or else from the selection in the options.
90
137
  * @example
91
138
  * ```ts
92
139
  * const store = createProductFormStore(product, cartStore);
@@ -97,6 +144,7 @@ declare function findCartLineByMerchandiseId(lines: CartLine[], merchandiseId: s
97
144
  *
98
145
  * store.selectOption("Color", "Red");
99
146
  * ```
147
+ * @publicDocs
100
148
  */
101
149
  declare function createProductFormStore<TProduct extends ProductInput>(product: TProduct, cartStore: CartStore, options?: CreateProductFormStoreOptions): ProductFormStore<TProduct, ProductVariantFrom<TProduct>>;
102
150
  //#endregion
@@ -2,32 +2,53 @@ import { createObservable } from "../observable.mjs";
2
2
  import { buildProductOptions, selectedOptionsFromMap, selectedOptionsToMap } from "./options.mjs";
3
3
 
4
4
  //#region src/core/product/product-form.ts
5
- /** Returns the selected variant, or `null` when the selection is partial or the variant wasn't part of the query result. */
5
+ /**
6
+ * Returns the variant that matches the customer's current option selection.
7
+ *
8
+ * Show the product's price range until the selection resolves to a variant. When every option has a selection and the function returns `null`, query the exact variant from the Storefront API.
9
+ *
10
+ * @param options The `options` array from the product form state.
11
+ * @returns The matching variant, or `null` when the selection is partial or your query didn't load that variant.
12
+ * @publicDocs
13
+ */
6
14
  function getSelectedVariant(options) {
7
15
  return options[0]?.values.find((v) => v.selected)?.variant ?? null;
8
16
  }
9
- /** Guards whether the current selection can be added to cart — a variant must be resolved and available, and the product must not require a selling plan. */
17
+ /**
18
+ * Checks whether the customer can add the current selection to the cart. Use the result to enable or disable the add-to-cart button.
19
+ *
20
+ * @param product The product from your query. A product that requires a selling plan always returns `false`.
21
+ * @param options The `options` array from the product form state.
22
+ * @returns `true` when the selection resolves to a variant that's available for sale and the product doesn't require a selling plan.
23
+ * @publicDocs
24
+ */
10
25
  function canAddToCart(product, options) {
11
26
  if (product.requiresSellingPlan) return false;
12
27
  const variant = getSelectedVariant(options);
13
28
  return variant !== null && variant.availableForSale;
14
29
  }
15
30
  /**
16
- * Finds the first cart line whose merchandise ID matches.
17
- * When selling plans are used, the same merchandise ID can appear on multiple
18
- * cart lines — this returns the first match. Future attribute-based matching
19
- * will narrow to the exact line.
31
+ * Finds the cart line that holds a variant. Use the line to show the quantity that's already in the cart.
32
+ *
33
+ * When several cart lines hold the variant with different selling plans or attributes, the function returns the first line.
34
+ *
35
+ * @param lines The cart lines, such as `data.lines.nodes` from the cart state.
36
+ * @param merchandiseId The variant ID to look for.
37
+ * @returns The first cart line that holds the variant, or `null` when no cart line holds the variant.
38
+ * @publicDocs
20
39
  */
21
40
  function findCartLineByMerchandiseId(lines, merchandiseId) {
22
41
  return lines.find((l) => l.merchandise?.id === merchandiseId) ?? null;
23
42
  }
24
43
  /**
25
- * Creates a reactive store that manages variant selection and cart integration for a product form.
44
+ * Creates a store that tracks the customer's option selection for one product and adds the selected variant to the cart.
26
45
  *
27
- * The store subscribes to the {@link CartStore} to keep error and line-item
28
- * state in sync. Call `destroy()` when the form unmounts to clean up the
29
- * subscription.
46
+ * The store resolves variants from the product data that you pass and leaves the URL to you. Build the product URL from the result of `selectOption()`. Call `destroy()` when the product form unmounts. The product provider from `createProductComponents` creates and destroys the store for you.
30
47
  *
48
+ * @param product The product from your Storefront API query.
49
+ * @param cartStore The cart store that the form adds variants to and reads cart errors from.
50
+ * @param options The selection to start from when the product has no selected or first available variant.
51
+ * @returns A product form store. The starting selection comes from the product's selected or first available variant, or else from the selection in the options.
31
52
  * @example
32
53
  * ```ts
33
54
  * const store = createProductFormStore(product, cartStore);
@@ -38,6 +59,7 @@ function findCartLineByMerchandiseId(lines, merchandiseId) {
38
59
  *
39
60
  * store.selectOption("Color", "Red");
40
61
  * ```
62
+ * @publicDocs
41
63
  */
42
64
  function createProductFormStore(product, cartStore, options = {}) {
43
65
  const decodedVariantCache = /* @__PURE__ */ new Map();
@@ -1 +1 @@
1
- {"version":3,"file":"product-form.mjs","names":[],"sources":["../../../src/core/product/product-form.ts"],"sourcesContent":["import type { CartStore } from \"../cart/cart\";\nimport type {\n CartLine,\n CartNetworkEntry,\n CartState,\n CartUserError,\n CartWarning,\n} from \"../cart/state\";\nimport { createObservable } from \"../observable\";\nimport {\n buildProductOptions,\n type DecodedVariantCache,\n selectedOptionsFromMap,\n selectedOptionsToMap,\n} from \"./options\";\nimport type {\n ProductInput,\n ProductOptionValueFrom,\n ProductOptionValueInput,\n ProductVariantFrom,\n ProductVariantInput,\n SelectedOption,\n VariantOptionState,\n} from \"./state\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Cart and line-item errors surfaced by a {@link ProductFormStore}. */\nexport interface ProductFormErrors {\n userErrors: CartUserError[];\n warnings: CartWarning[];\n networkErrors: CartNetworkEntry[];\n}\n\n/** Live state snapshot emitted by a {@link ProductFormStore}. */\nexport interface ProductFormStoreState<\n TVariant extends ProductVariantInput = ProductVariantInput,\n TOptionValue extends ProductOptionValueInput = ProductOptionValueInput,\n> {\n options: VariantOptionState<TVariant, TOptionValue>[];\n selectedOptions: SelectedOption[];\n selectedVariant: TVariant | null;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n}\n\n/** The reactive options array from a {@link ProductFormStoreState}, typed to a specific product. */\nexport type ProductFormOptions<TProduct extends ProductInput = ProductInput> =\n ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[\"options\"];\n\n/**\n * Result returned by {@link ProductFormStore.selectOption}.\n *\n * - `resolved` — a specific variant was matched for the full selection.\n * - `unresolved` — the selection is valid but not yet complete.\n * - `invalid` — the option name or value does not exist on the product.\n */\nexport type VariantSelectionResult<TVariant extends ProductVariantInput = ProductVariantInput> =\n | {\n status: \"resolved\";\n selectedOptions: SelectedOption[];\n selectedVariant: TVariant;\n }\n | {\n status: \"unresolved\";\n selectedOptions: SelectedOption[];\n selectedVariant: null;\n }\n | {\n status: \"invalid\";\n selectedOptions: SelectedOption[];\n selectedVariant: null;\n reason: string;\n };\n\n/** A selection result that is not invalid — either resolved or unresolved. */\nexport type ValidProductSelectionResult<TProduct extends ProductInput = ProductInput> = Exclude<\n VariantSelectionResult<ProductVariantFrom<TProduct>>,\n { status: \"invalid\" }\n>;\n\n/** Options for {@link createProductFormStore}. */\nexport type CreateProductFormStoreOptions = {\n /** Fallback selection used when the product has no `selectedOrFirstAvailableVariant`. */\n selectedOptions?: SelectedOption[];\n};\n\n/** Manages variant selection + cart integration for a product form. */\nexport interface ProductFormStore<\n TProduct extends ProductInput = ProductInput,\n TVariant extends ProductVariantInput = ProductVariantFrom<TProduct>,\n> {\n /** Returns the current state snapshot. */\n getState(): ProductFormStoreState<TVariant, ProductOptionValueFrom<TProduct>>;\n /** Registers a listener invoked on every state change. Returns an unsubscribe function. */\n subscribe(\n listener: (state: ProductFormStoreState<TVariant, ProductOptionValueFrom<TProduct>>) => void,\n ): () => void;\n /** Selects an option value and resolves the new variant. */\n selectOption(name: string, value: string): VariantSelectionResult<TVariant>;\n /** Replaces the product data and re-derives state — use after a server-side product reload. */\n hydrate(product: TProduct, opts?: { selectedOptions?: SelectedOption[] }): void;\n /** Restores the store to its initial product and selection state. */\n reset(): void;\n /** Re-subscribes to the cart store and resyncs cart-derived state. Only needed to reuse the store after `destroy()` (e.g. React StrictMode effects). */\n connect(): void;\n /** Tears down the store — removes cart subscription and releases resources. */\n destroy(): void;\n /** Forwards a native `SubmitEvent` to the cart store's form handler (routed by the submitter's `value`, typically `add`), attaching selected variant data as event detail. */\n handleFormSubmit(event: SubmitEvent): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\n/** Returns the selected variant, or `null` when the selection is partial or the variant wasn't part of the query result. */\nexport function getSelectedVariant<TVariant extends ProductVariantInput>(\n options: VariantOptionState<TVariant, ProductOptionValueInput>[],\n): TVariant | null {\n return options[0]?.values.find((v) => v.selected)?.variant ?? null;\n}\n\n/** Guards whether the current selection can be added to cart — a variant must be resolved and available, and the product must not require a selling plan. */\nexport function canAddToCart<TProduct extends ProductInput>(\n product: TProduct,\n options: VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[],\n): boolean {\n if (product.requiresSellingPlan) return false;\n const variant = getSelectedVariant(options);\n return variant !== null && variant.availableForSale;\n}\n\n/**\n * Finds the first cart line whose merchandise ID matches.\n * When selling plans are used, the same merchandise ID can appear on multiple\n * cart lines — this returns the first match. Future attribute-based matching\n * will narrow to the exact line.\n */\nexport function findCartLineByMerchandiseId(\n lines: CartLine[],\n merchandiseId: string,\n): CartLine | null {\n return lines.find((l) => l.merchandise?.id === merchandiseId) ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal context\n// ---------------------------------------------------------------------------\n\ntype ProductFormStoreContext<TProduct extends ProductInput> = {\n observable: ReturnType<\n typeof createObservable<\n ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>\n >\n >;\n cartStore: CartStore;\n currentProduct: TProduct;\n initialProduct: TProduct;\n initialSelectedOptions: SelectedOption[];\n decodedVariantCache: DecodedVariantCache;\n destroyed: boolean;\n unsubCart: () => void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a reactive store that manages variant selection and cart integration for a product form.\n *\n * The store subscribes to the {@link CartStore} to keep error and line-item\n * state in sync. Call `destroy()` when the form unmounts to clean up the\n * subscription.\n *\n * @example\n * ```ts\n * const store = createProductFormStore(product, cartStore);\n *\n * store.subscribe((state) => {\n * console.log(\"selected variant:\", state.selectedVariant);\n * });\n *\n * store.selectOption(\"Color\", \"Red\");\n * ```\n */\nexport function createProductFormStore<TProduct extends ProductInput>(\n product: TProduct,\n cartStore: CartStore,\n options: CreateProductFormStoreOptions = {},\n): ProductFormStore<TProduct, ProductVariantFrom<TProduct>> {\n const decodedVariantCache: DecodedVariantCache = new Map();\n const initialSelectedOptions = resolveSelectedOptions(product, options.selectedOptions);\n\n const variantState = buildVariantState(product, initialSelectedOptions, decodedVariantCache);\n\n const context: ProductFormStoreContext<TProduct> = {\n observable: createObservable(deriveFullState(variantState, cartStore.getState())),\n cartStore,\n currentProduct: product,\n initialProduct: product,\n initialSelectedOptions,\n decodedVariantCache,\n destroyed: false,\n unsubCart: () => {},\n };\n\n context.unsubCart = cartStore.subscribe(() => {\n if (context.destroyed) return;\n syncFromCart(context);\n });\n\n return {\n getState: () => context.observable.state,\n subscribe: (listener) => context.observable.subscribe(listener),\n selectOption: (name, value) => selectOption(context, name, value),\n hydrate: (nextProduct, opts) => hydrate(context, nextProduct, opts),\n reset: () => reset(context),\n connect: () => connect(context),\n destroy: () => destroy(context),\n handleFormSubmit: (event) => handleFormSubmit(context, event),\n };\n}\n\n// ---------------------------------------------------------------------------\n// State builders\n// ---------------------------------------------------------------------------\n\ntype VariantOnlyState<TProduct extends ProductInput> = {\n options: VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[];\n selectedOptions: SelectedOption[];\n};\n\nfunction buildVariantState<TProduct extends ProductInput>(\n product: TProduct,\n selectedOptions: SelectedOption[],\n cache: DecodedVariantCache,\n): VariantOnlyState<TProduct> {\n return {\n options: buildProductOptions(product, selectedOptions, cache),\n selectedOptions,\n };\n}\n\nfunction deriveFullState<TProduct extends ProductInput>(\n variant: VariantOnlyState<TProduct>,\n cartState: CartState,\n): ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>> {\n const selectedVariant = getSelectedVariant(variant.options);\n const matchedLineItem = selectedVariant\n ? findCartLineByMerchandiseId(cartState.data.lines.nodes, selectedVariant.id)\n : null;\n\n const lineErrors = matchedLineItem ? cartState.errors.lines.get(matchedLineItem.id) : undefined;\n\n return {\n options: variant.options,\n selectedOptions: variant.selectedOptions,\n selectedVariant,\n errors: {\n userErrors: [...cartState.errors.cart.userErrors, ...(lineErrors?.userErrors ?? [])],\n warnings: [...cartState.errors.cart.warnings, ...(lineErrors?.warnings ?? [])],\n networkErrors: cartState.errors.network,\n },\n matchedLineItem,\n };\n}\n\nfunction resolveSelectedOptions<TProduct extends ProductInput>(\n product: TProduct,\n requested?: SelectedOption[],\n): SelectedOption[] {\n return product.selectedOrFirstAvailableVariant?.selectedOptions ?? requested ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\nfunction selectOption<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n name: string,\n value: string,\n): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const { options, selectedOptions } = context.observable.state;\n const option = options.find((o) => o.name === name);\n const optionValue = option?.values.find((v) => v.name === value);\n\n if (!option || !optionValue) {\n return invalid(selectedOptions, `Unknown option \"${name}\" value \"${value}\".`);\n }\n\n if (!optionValue.exists) {\n return invalid(\n selectedOptions,\n `Option \"${name}\" value \"${value}\" does not resolve to a product variant.`,\n );\n }\n\n const nextSelectedOptionMap = {\n ...selectedOptionsToMap(selectedOptions),\n [name]: value,\n };\n const nextSelectedOptions = selectedOptionsFromMap(context.currentProduct, nextSelectedOptionMap);\n\n return applySelection(\n context,\n optionValue.variant\n ? {\n status: \"resolved\",\n selectedOptions: optionValue.variant.selectedOptions,\n selectedVariant: optionValue.variant as ProductVariantFrom<TProduct>,\n }\n : {\n status: \"unresolved\",\n selectedOptions: nextSelectedOptions,\n selectedVariant: null,\n },\n );\n}\n\nfunction hydrate<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n product: TProduct,\n opts: { selectedOptions?: SelectedOption[] } = {},\n): void {\n context.currentProduct = product;\n context.decodedVariantCache.clear();\n const selectedOptions =\n product.selectedOrFirstAvailableVariant?.selectedOptions ??\n opts.selectedOptions ??\n context.observable.state.selectedOptions;\n const variantState = buildVariantState(product, selectedOptions, context.decodedVariantCache);\n context.observable.setState(deriveFullState(variantState, context.cartStore.getState()));\n}\n\nfunction reset<TProduct extends ProductInput>(context: ProductFormStoreContext<TProduct>): void {\n context.currentProduct = context.initialProduct;\n context.decodedVariantCache.clear();\n const variantState = buildVariantState(\n context.initialProduct,\n context.initialSelectedOptions,\n context.decodedVariantCache,\n );\n context.observable.setState(deriveFullState(variantState, context.cartStore.getState()));\n}\n\nfunction connect<TProduct extends ProductInput>(context: ProductFormStoreContext<TProduct>): void {\n context.unsubCart();\n context.destroyed = false;\n context.unsubCart = context.cartStore.subscribe(() => {\n if (context.destroyed) return;\n syncFromCart(context);\n });\n syncFromCart(context);\n}\n\nfunction destroy<TProduct extends ProductInput>(context: ProductFormStoreContext<TProduct>): void {\n context.destroyed = true;\n context.unsubCart();\n context.decodedVariantCache.clear();\n}\n\nfunction buildAddToCartDetail<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n): Record<string, unknown> | undefined {\n const { selectedVariant } = context.observable.state;\n if (!selectedVariant) return undefined;\n\n return {\n products: [\n {\n id: selectedVariant.id,\n title: selectedVariant.title,\n product: selectedVariant.product\n ? { title: selectedVariant.product.title, handle: selectedVariant.product.handle }\n : undefined,\n selectedOptions: selectedVariant.selectedOptions,\n image: selectedVariant.image,\n price: selectedVariant.price,\n },\n ],\n };\n}\n\nasync function handleFormSubmit<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n event: SubmitEvent,\n): Promise<void> {\n const eventDetail = buildAddToCartDetail(context);\n await context.cartStore.handleFormSubmit(event, eventDetail);\n}\n\nfunction applySelection<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n result: Exclude<VariantSelectionResult<ProductVariantFrom<TProduct>>, { status: \"invalid\" }>,\n): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const variantState = buildVariantState(\n context.currentProduct,\n result.selectedOptions,\n context.decodedVariantCache,\n );\n context.observable.setState(deriveFullState(variantState, context.cartStore.getState()));\n return result;\n}\n\nfunction hasCartDerivedFieldsChanged<TVariant extends ProductVariantInput>(\n prev: ProductFormStoreState<TVariant, ProductOptionValueInput>,\n next: ProductFormStoreState<TVariant, ProductOptionValueInput>,\n): boolean {\n return (\n prev.matchedLineItem !== next.matchedLineItem ||\n prev.selectedVariant !== next.selectedVariant ||\n prev.errors.userErrors !== next.errors.userErrors ||\n prev.errors.warnings !== next.errors.warnings ||\n prev.errors.networkErrors !== next.errors.networkErrors\n );\n}\n\nfunction syncFromCart<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n): void {\n const prev = context.observable.state;\n const next = deriveFullState(\n { options: prev.options, selectedOptions: prev.selectedOptions },\n context.cartStore.getState(),\n );\n\n if (hasCartDerivedFieldsChanged(prev, next)) {\n context.observable.setState(next);\n }\n}\n\nfunction invalid<TVariant extends ProductVariantInput>(\n selectedOptions: SelectedOption[],\n reason: string,\n): VariantSelectionResult<TVariant> {\n return {\n status: \"invalid\",\n selectedOptions,\n selectedVariant: null,\n reason,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"product-form.mjs","names":[],"sources":["../../../src/core/product/product-form.ts"],"sourcesContent":["import type { CartStore } from \"../cart/cart\";\nimport type {\n CartLine,\n CartNetworkEntry,\n CartState,\n CartUserError,\n CartWarning,\n} from \"../cart/state\";\nimport { createObservable } from \"../observable\";\nimport {\n buildProductOptions,\n type DecodedVariantCache,\n selectedOptionsFromMap,\n selectedOptionsToMap,\n} from \"./options\";\nimport type {\n ProductInput,\n ProductOptionValueFrom,\n ProductOptionValueInput,\n ProductVariantFrom,\n ProductVariantInput,\n SelectedOption,\n VariantOptionState,\n} from \"./state\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/**\n * Cart errors and warnings to show near the add-to-cart button.\n *\n * Each list starts with cart-level messages, followed by the messages for the cart line that holds the selected variant.\n */\nexport interface ProductFormErrors {\n /** User errors for the cart, then for the cart line that holds the selected variant. */\n userErrors: CartUserError[];\n /** Warnings for the cart, then for the cart line that holds the selected variant. */\n warnings: CartWarning[];\n /** Failed cart requests. Each entry has a message and, when available, an HTTP status. */\n networkErrors: CartNetworkEntry[];\n}\n\n/** The product form's option state, selection, cart errors, and matching cart line. */\nexport interface ProductFormStoreState<\n TVariant extends ProductVariantInput = ProductVariantInput,\n TOptionValue extends ProductOptionValueInput = ProductOptionValueInput,\n> {\n /** Each product option and its values, with the selection, existence, and availability of each value. */\n options: VariantOptionState<TVariant, TOptionValue>[];\n /** Option names and values in the current selection, which can be partial. */\n selectedOptions: SelectedOption[];\n /** Variant that matches the current selection, or `null` when the selection is partial or your query didn't load that variant. */\n selectedVariant: TVariant | null;\n /** Cart errors and warnings to show near the add-to-cart button. */\n errors: ProductFormErrors;\n /** First cart line that holds the selected variant, or `null` when no cart line holds the variant. */\n matchedLineItem: CartLine | null;\n}\n\n/** Option state from the product form, typed to your product query. */\nexport type ProductFormOptions<TProduct extends ProductInput = ProductInput> =\n ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[\"options\"];\n\n/**\n * Result of selecting an option value. Build the next product URL from the selected options in the result.\n *\n * - `resolved`: the selection matches a variant that your query loaded.\n * - `unresolved`: the selection is valid, but your query didn't load a matching variant.\n * - `invalid`: the product has no such option name or value, or no variant combines the value with the rest of the selection.\n *\n * When a complete selection returns `unresolved`, query the exact variant from the Storefront API with the returned selected options.\n */\nexport type VariantSelectionResult<TVariant extends ProductVariantInput = ProductVariantInput> =\n | {\n status: \"resolved\";\n selectedOptions: SelectedOption[];\n selectedVariant: TVariant;\n }\n | {\n status: \"unresolved\";\n selectedOptions: SelectedOption[];\n selectedVariant: null;\n }\n | {\n status: \"invalid\";\n selectedOptions: SelectedOption[];\n selectedVariant: null;\n reason: string;\n };\n\n/** A selection result with a `resolved` or `unresolved` status. Selection callbacks receive this result. */\nexport type ValidProductSelectionResult<TProduct extends ProductInput = ProductInput> = Exclude<\n VariantSelectionResult<ProductVariantFrom<TProduct>>,\n { status: \"invalid\" }\n>;\n\n/** Options for creating a product form store. */\nexport type CreateProductFormStoreOptions = {\n /** Selection to start from when the product has no selected or first available variant. */\n selectedOptions?: SelectedOption[];\n};\n\n/** Tracks the customer's option selection for one product and adds the selected variant to the cart. */\nexport interface ProductFormStore<\n TProduct extends ProductInput = ProductInput,\n TVariant extends ProductVariantInput = ProductVariantFrom<TProduct>,\n> {\n /** Returns the current product form state. */\n getState(): ProductFormStoreState<TVariant, ProductOptionValueFrom<TProduct>>;\n /** Calls the listener on every state change. Returns a function that unsubscribes the listener. */\n subscribe(\n listener: (state: ProductFormStoreState<TVariant, ProductOptionValueFrom<TProduct>>) => void,\n ): () => void;\n /**\n * Selects an option value and returns the result.\n *\n * Resolved and unresolved selections update the state. An invalid selection leaves the state unchanged and returns a reason. Build the next product URL from the selected options in the result.\n */\n selectOption(name: string, value: string): VariantSelectionResult<TVariant>;\n /**\n * Loads new product data into the store. Create the store once and call `hydrate()` each time the server reloads the product.\n *\n * The store keeps its subscribers. The new selection comes from the product's selected or first available variant, then from the selected options that you pass, then from the current selection.\n */\n hydrate(product: TProduct, opts?: { selectedOptions?: SelectedOption[] }): void;\n /** Restores the product data and the selection that the store started with. */\n reset(): void;\n /** Reconnects the store to the cart after you call `destroy()`, such as in React Strict Mode effects. The store connects to the cart when you create it. */\n connect(): void;\n /** Disconnects the store from the cart. Call `destroy()` when the product form unmounts. */\n destroy(): void;\n /**\n * Adds the selected variant to the cart from the product form's native submit event.\n *\n * The cart adds the variant when the clicked button has no value and the form includes a merchandise ID field. The cart uses the variant's title, image, and price to show the change before the server responds. The returned promise rejects when the event target isn't a form or the event has no submitter.\n */\n handleFormSubmit(event: SubmitEvent): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Returns the variant that matches the customer's current option selection.\n *\n * Show the product's price range until the selection resolves to a variant. When every option has a selection and the function returns `null`, query the exact variant from the Storefront API.\n *\n * @param options The `options` array from the product form state.\n * @returns The matching variant, or `null` when the selection is partial or your query didn't load that variant.\n * @publicDocs\n */\nexport function getSelectedVariant<TVariant extends ProductVariantInput>(\n options: VariantOptionState<TVariant, ProductOptionValueInput>[],\n): TVariant | null {\n return options[0]?.values.find((v) => v.selected)?.variant ?? null;\n}\n\n/**\n * Checks whether the customer can add the current selection to the cart. Use the result to enable or disable the add-to-cart button.\n *\n * @param product The product from your query. A product that requires a selling plan always returns `false`.\n * @param options The `options` array from the product form state.\n * @returns `true` when the selection resolves to a variant that's available for sale and the product doesn't require a selling plan.\n * @publicDocs\n */\nexport function canAddToCart<TProduct extends ProductInput>(\n product: TProduct,\n options: VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[],\n): boolean {\n if (product.requiresSellingPlan) return false;\n const variant = getSelectedVariant(options);\n return variant !== null && variant.availableForSale;\n}\n\n/**\n * Finds the cart line that holds a variant. Use the line to show the quantity that's already in the cart.\n *\n * When several cart lines hold the variant with different selling plans or attributes, the function returns the first line.\n *\n * @param lines The cart lines, such as `data.lines.nodes` from the cart state.\n * @param merchandiseId The variant ID to look for.\n * @returns The first cart line that holds the variant, or `null` when no cart line holds the variant.\n * @publicDocs\n */\nexport function findCartLineByMerchandiseId(\n lines: CartLine[],\n merchandiseId: string,\n): CartLine | null {\n return lines.find((l) => l.merchandise?.id === merchandiseId) ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal context\n// ---------------------------------------------------------------------------\n\ntype ProductFormStoreContext<TProduct extends ProductInput> = {\n observable: ReturnType<\n typeof createObservable<\n ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>\n >\n >;\n cartStore: CartStore;\n currentProduct: TProduct;\n initialProduct: TProduct;\n initialSelectedOptions: SelectedOption[];\n decodedVariantCache: DecodedVariantCache;\n destroyed: boolean;\n unsubCart: () => void;\n};\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a store that tracks the customer's option selection for one product and adds the selected variant to the cart.\n *\n * The store resolves variants from the product data that you pass and leaves the URL to you. Build the product URL from the result of `selectOption()`. Call `destroy()` when the product form unmounts. The product provider from `createProductComponents` creates and destroys the store for you.\n *\n * @param product The product from your Storefront API query.\n * @param cartStore The cart store that the form adds variants to and reads cart errors from.\n * @param options The selection to start from when the product has no selected or first available variant.\n * @returns A product form store. The starting selection comes from the product's selected or first available variant, or else from the selection in the options.\n * @example\n * ```ts\n * const store = createProductFormStore(product, cartStore);\n *\n * store.subscribe((state) => {\n * console.log(\"selected variant:\", state.selectedVariant);\n * });\n *\n * store.selectOption(\"Color\", \"Red\");\n * ```\n * @publicDocs\n */\nexport function createProductFormStore<TProduct extends ProductInput>(\n product: TProduct,\n cartStore: CartStore,\n options: CreateProductFormStoreOptions = {},\n): ProductFormStore<TProduct, ProductVariantFrom<TProduct>> {\n const decodedVariantCache: DecodedVariantCache = new Map();\n const initialSelectedOptions = resolveSelectedOptions(product, options.selectedOptions);\n\n const variantState = buildVariantState(product, initialSelectedOptions, decodedVariantCache);\n\n const context: ProductFormStoreContext<TProduct> = {\n observable: createObservable(deriveFullState(variantState, cartStore.getState())),\n cartStore,\n currentProduct: product,\n initialProduct: product,\n initialSelectedOptions,\n decodedVariantCache,\n destroyed: false,\n unsubCart: () => {},\n };\n\n context.unsubCart = cartStore.subscribe(() => {\n if (context.destroyed) return;\n syncFromCart(context);\n });\n\n return {\n getState: () => context.observable.state,\n subscribe: (listener) => context.observable.subscribe(listener),\n selectOption: (name, value) => selectOption(context, name, value),\n hydrate: (nextProduct, opts) => hydrate(context, nextProduct, opts),\n reset: () => reset(context),\n connect: () => connect(context),\n destroy: () => destroy(context),\n handleFormSubmit: (event) => handleFormSubmit(context, event),\n };\n}\n\n// ---------------------------------------------------------------------------\n// State builders\n// ---------------------------------------------------------------------------\n\ntype VariantOnlyState<TProduct extends ProductInput> = {\n options: VariantOptionState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>>[];\n selectedOptions: SelectedOption[];\n};\n\nfunction buildVariantState<TProduct extends ProductInput>(\n product: TProduct,\n selectedOptions: SelectedOption[],\n cache: DecodedVariantCache,\n): VariantOnlyState<TProduct> {\n return {\n options: buildProductOptions(product, selectedOptions, cache),\n selectedOptions,\n };\n}\n\nfunction deriveFullState<TProduct extends ProductInput>(\n variant: VariantOnlyState<TProduct>,\n cartState: CartState,\n): ProductFormStoreState<ProductVariantFrom<TProduct>, ProductOptionValueFrom<TProduct>> {\n const selectedVariant = getSelectedVariant(variant.options);\n const matchedLineItem = selectedVariant\n ? findCartLineByMerchandiseId(cartState.data.lines.nodes, selectedVariant.id)\n : null;\n\n const lineErrors = matchedLineItem ? cartState.errors.lines.get(matchedLineItem.id) : undefined;\n\n return {\n options: variant.options,\n selectedOptions: variant.selectedOptions,\n selectedVariant,\n errors: {\n userErrors: [...cartState.errors.cart.userErrors, ...(lineErrors?.userErrors ?? [])],\n warnings: [...cartState.errors.cart.warnings, ...(lineErrors?.warnings ?? [])],\n networkErrors: cartState.errors.network,\n },\n matchedLineItem,\n };\n}\n\nfunction resolveSelectedOptions<TProduct extends ProductInput>(\n product: TProduct,\n requested?: SelectedOption[],\n): SelectedOption[] {\n return product.selectedOrFirstAvailableVariant?.selectedOptions ?? requested ?? [];\n}\n\n// ---------------------------------------------------------------------------\n// Actions\n// ---------------------------------------------------------------------------\n\nfunction selectOption<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n name: string,\n value: string,\n): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const { options, selectedOptions } = context.observable.state;\n const option = options.find((o) => o.name === name);\n const optionValue = option?.values.find((v) => v.name === value);\n\n if (!option || !optionValue) {\n return invalid(selectedOptions, `Unknown option \"${name}\" value \"${value}\".`);\n }\n\n if (!optionValue.exists) {\n return invalid(\n selectedOptions,\n `Option \"${name}\" value \"${value}\" does not resolve to a product variant.`,\n );\n }\n\n const nextSelectedOptionMap = {\n ...selectedOptionsToMap(selectedOptions),\n [name]: value,\n };\n const nextSelectedOptions = selectedOptionsFromMap(context.currentProduct, nextSelectedOptionMap);\n\n return applySelection(\n context,\n optionValue.variant\n ? {\n status: \"resolved\",\n selectedOptions: optionValue.variant.selectedOptions,\n selectedVariant: optionValue.variant as ProductVariantFrom<TProduct>,\n }\n : {\n status: \"unresolved\",\n selectedOptions: nextSelectedOptions,\n selectedVariant: null,\n },\n );\n}\n\nfunction hydrate<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n product: TProduct,\n opts: { selectedOptions?: SelectedOption[] } = {},\n): void {\n context.currentProduct = product;\n context.decodedVariantCache.clear();\n const selectedOptions =\n product.selectedOrFirstAvailableVariant?.selectedOptions ??\n opts.selectedOptions ??\n context.observable.state.selectedOptions;\n const variantState = buildVariantState(product, selectedOptions, context.decodedVariantCache);\n context.observable.setState(deriveFullState(variantState, context.cartStore.getState()));\n}\n\nfunction reset<TProduct extends ProductInput>(context: ProductFormStoreContext<TProduct>): void {\n context.currentProduct = context.initialProduct;\n context.decodedVariantCache.clear();\n const variantState = buildVariantState(\n context.initialProduct,\n context.initialSelectedOptions,\n context.decodedVariantCache,\n );\n context.observable.setState(deriveFullState(variantState, context.cartStore.getState()));\n}\n\nfunction connect<TProduct extends ProductInput>(context: ProductFormStoreContext<TProduct>): void {\n context.unsubCart();\n context.destroyed = false;\n context.unsubCart = context.cartStore.subscribe(() => {\n if (context.destroyed) return;\n syncFromCart(context);\n });\n syncFromCart(context);\n}\n\nfunction destroy<TProduct extends ProductInput>(context: ProductFormStoreContext<TProduct>): void {\n context.destroyed = true;\n context.unsubCart();\n context.decodedVariantCache.clear();\n}\n\nfunction buildAddToCartDetail<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n): Record<string, unknown> | undefined {\n const { selectedVariant } = context.observable.state;\n if (!selectedVariant) return undefined;\n\n return {\n products: [\n {\n id: selectedVariant.id,\n title: selectedVariant.title,\n product: selectedVariant.product\n ? { title: selectedVariant.product.title, handle: selectedVariant.product.handle }\n : undefined,\n selectedOptions: selectedVariant.selectedOptions,\n image: selectedVariant.image,\n price: selectedVariant.price,\n },\n ],\n };\n}\n\nasync function handleFormSubmit<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n event: SubmitEvent,\n): Promise<void> {\n const eventDetail = buildAddToCartDetail(context);\n await context.cartStore.handleFormSubmit(event, eventDetail);\n}\n\nfunction applySelection<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n result: Exclude<VariantSelectionResult<ProductVariantFrom<TProduct>>, { status: \"invalid\" }>,\n): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const variantState = buildVariantState(\n context.currentProduct,\n result.selectedOptions,\n context.decodedVariantCache,\n );\n context.observable.setState(deriveFullState(variantState, context.cartStore.getState()));\n return result;\n}\n\nfunction hasCartDerivedFieldsChanged<TVariant extends ProductVariantInput>(\n prev: ProductFormStoreState<TVariant, ProductOptionValueInput>,\n next: ProductFormStoreState<TVariant, ProductOptionValueInput>,\n): boolean {\n return (\n prev.matchedLineItem !== next.matchedLineItem ||\n prev.selectedVariant !== next.selectedVariant ||\n prev.errors.userErrors !== next.errors.userErrors ||\n prev.errors.warnings !== next.errors.warnings ||\n prev.errors.networkErrors !== next.errors.networkErrors\n );\n}\n\nfunction syncFromCart<TProduct extends ProductInput>(\n context: ProductFormStoreContext<TProduct>,\n): void {\n const prev = context.observable.state;\n const next = deriveFullState(\n { options: prev.options, selectedOptions: prev.selectedOptions },\n context.cartStore.getState(),\n );\n\n if (hasCartDerivedFieldsChanged(prev, next)) {\n context.observable.setState(next);\n }\n}\n\nfunction invalid<TVariant extends ProductVariantInput>(\n selectedOptions: SelectedOption[],\n reason: string,\n): VariantSelectionResult<TVariant> {\n return {\n status: \"invalid\",\n selectedOptions,\n selectedVariant: null,\n reason,\n };\n}\n\n/**\n * Creates a store that tracks the customer's option selection for one product and adds the selected variant to the cart.\n *\n * The store resolves variants from the product data that you pass and leaves the URL to you. Build the product URL from the result of `selectOption()`. Call `destroy()` when the product form unmounts. The product provider from `createProductComponents` creates and destroys the store for you.\n *\n * @example\n * ```ts\n * const store = createProductFormStore(product, cartStore);\n *\n * store.subscribe((state) => {\n * console.log(\"selected variant:\", state.selectedVariant);\n * });\n *\n * store.selectOption(\"Color\", \"Red\");\n * ```\n * @publicDocs\n */\nexport type CreateProductFormStoreForDocs =\n /**\n * @param product - The product from your Storefront API query.\n * @param cartStore - The cart store that the form adds variants to and reads cart errors from.\n * @param options - The selection to start from when the product has no selected or first available variant.\n * @returns A product form store. The starting selection comes from the product's selected or first available variant, or else from the selection in the options.\n */\n (\n product: ProductInput,\n cartStore: CartStore,\n options?: CreateProductFormStoreOptions,\n ) => ProductFormStore;\n"],"mappings":"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"}