@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":"predictive-search.mjs","names":[],"sources":["../../src/vue/predictive-search.ts"],"sourcesContent":["import {\n defineComponent,\n inject,\n onMounted,\n onUnmounted,\n provide,\n shallowRef,\n watch,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport {\n createPredictiveSearchFormRegister,\n createPredictiveSearchStore,\n getPredictiveSearchFormAttributes,\n readPredictiveSearchFormTerm,\n type CreatePredictiveSearchStoreOptions,\n type PredictiveSearchActions,\n type PredictiveSearchData,\n type PredictiveSearchState,\n type PredictiveSearchStore,\n} from \"../core/predictive-search\";\nimport type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchableField,\n SearchUnavailableProductsType,\n} from \"../graphql/generated/storefront-api-types\";\n\nconst CONFIG_ARRAY_SEPARATOR = \"\\0\";\n\ntype PredictiveSearchContextValue = {\n storeRef: ShallowRef<PredictiveSearchStore>;\n searchActionRef: ShallowRef<string | undefined>;\n};\n\nconst PredictiveSearchKey: InjectionKey<PredictiveSearchContextValue> = Symbol(\"PredictiveSearch\");\n\nexport type { PredictiveSearchActions };\n\n/** Options for the form props builder returned by {@link usePredictiveSearchForm}. */\nexport type PredictiveSearchFormPropsOptions = {\n /** When `true`, prevents the native form submission and triggers a client-side search instead. */\n preventDefault?: boolean;\n /** Called on submit with the submit event and the extracted search term. Calling `event.preventDefault()` cancels both the native submission and Hydrogen's client-side search. */\n onSubmit?: (event: SubmitEvent, term: string) => void;\n [key: string]: unknown;\n};\n\n/** Options for the query input props builder returned by {@link usePredictiveSearchForm}'s `register` method. */\nexport type PredictiveSearchQueryInputPropsOptions = {\n /** Called on input with the input event and the current input value. Call `event.preventDefault()` to skip the automatic search trigger. */\n onInput?: (event: Event, term: string) => void;\n [key: string]: unknown;\n};\n\n/** Return type of {@link usePredictiveSearchForm}, providing methods to build a progressively-enhanced search form. */\nexport type PredictiveSearchFormResult = {\n /** Generates form element attributes including the search action and submit handler. */\n formProps(options?: PredictiveSearchFormPropsOptions): Record<string, unknown>;\n /** Generates input element attributes for a named form field and wires up the search trigger. */\n register: (\n field: \"query\",\n options?: PredictiveSearchQueryInputPropsOptions,\n ) => Record<string, unknown>;\n};\n\n/**\n * Creates and manages a predictive search store, providing it to descendant\n * composables via Vue's provide/inject.\n *\n * Recreates the store when configuration props change. Connects the store\n * on mount and destroys it on unmount.\n */\nexport const PredictiveSearchProvider = defineComponent({\n name: \"PredictiveSearchProvider\",\n props: {\n predictiveSearchEndpoint: { type: String, default: undefined },\n searchAction: { type: String, default: undefined },\n debounceInMs: { type: Number, default: undefined },\n minTermLength: { type: Number, default: undefined },\n fetch: { type: Function as PropType<typeof globalThis.fetch>, default: undefined },\n limit: { type: Number, default: undefined },\n limitScope: { type: String as PropType<PredictiveSearchLimitScope>, default: undefined },\n types: { type: Array as PropType<PredictiveSearchType[]>, default: undefined },\n searchableFields: { type: Array as PropType<SearchableField[]>, default: undefined },\n unavailableProducts: {\n type: String as PropType<SearchUnavailableProductsType>,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n function buildStoreOptions(): CreatePredictiveSearchStoreOptions {\n return {\n predictiveSearchEndpoint: props.predictiveSearchEndpoint,\n debounceInMs: props.debounceInMs,\n minTermLength: props.minTermLength,\n fetch: props.fetch as typeof globalThis.fetch | undefined,\n limit: props.limit,\n limitScope: props.limitScope,\n types: props.types,\n searchableFields: props.searchableFields,\n unavailableProducts: props.unavailableProducts,\n };\n }\n\n const storeRef = shallowRef<PredictiveSearchStore>(\n createPredictiveSearchStore(buildStoreOptions()),\n );\n const searchActionRef = shallowRef(props.searchAction);\n\n provide(PredictiveSearchKey, { storeRef, searchActionRef });\n\n watch(\n () => props.searchAction,\n (next) => {\n searchActionRef.value = next;\n },\n );\n\n watch(\n () => [\n props.predictiveSearchEndpoint,\n props.debounceInMs,\n props.minTermLength,\n props.fetch,\n props.limit,\n props.limitScope,\n getArrayKey(props.types),\n getArrayKey(props.searchableFields),\n props.unavailableProducts,\n ],\n () => {\n storeRef.value.destroy();\n storeRef.value = createPredictiveSearchStore(buildStoreOptions());\n storeRef.value.connect();\n },\n );\n\n onMounted(() => {\n storeRef.value.connect();\n });\n\n onUnmounted(() => {\n storeRef.value.destroy();\n });\n\n return () => slots.default?.();\n },\n});\n\nfunction getArrayKey(values: readonly string[] | undefined): string {\n return values?.join(CONFIG_ARRAY_SEPARATOR) ?? \"\";\n}\n\nfunction useRequiredContext(composableName: string): PredictiveSearchContextValue {\n const context = inject(PredictiveSearchKey, null);\n if (!context) {\n throw new Error(`${composableName} must be used inside a <PredictiveSearchProvider>.`);\n }\n return context;\n}\n\n/**\n * Subscribes to the predictive search store's state as a reactive ref.\n *\n * Without arguments, returns a `ShallowRef` of the full\n * {@link PredictiveSearchState}. With a `selector`, returns a ref of the\n * derived value that only updates when the selected value changes\n * (reference equality by default, or a custom `isEqual`).\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(): Readonly<ShallowRef<PredictiveSearchState<TData>>>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(\n selector: (state: PredictiveSearchState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(\n selector?: (state: PredictiveSearchState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<PredictiveSearchState<TData> | S>> {\n const { storeRef } = useRequiredContext(\"usePredictiveSearch\");\n const resolve = selector ?? ((state: PredictiveSearchState<TData>) => state as unknown as S);\n const selected = shallowRef<PredictiveSearchState<TData> | S>(\n resolve((storeRef.value as PredictiveSearchStore<TData>).getState()),\n );\n\n watch(\n () => storeRef.value,\n (store, _, onCleanup) => {\n const typedStore = store as PredictiveSearchStore<TData>;\n selected.value = resolve(typedStore.getState());\n\n const unsubscribe = typedStore.subscribe(() => {\n const next = resolve(typedStore.getState());\n if (isEqual && selector) {\n if (isEqual(selected.value as S, next)) return;\n }\n selected.value = next;\n });\n\n onCleanup(unsubscribe);\n },\n { immediate: true },\n );\n\n return selected as Readonly<ShallowRef<PredictiveSearchState<TData> | S>>;\n}\n\n/**\n * Returns `search` and `clear` methods that delegate to the current store.\n *\n * These are thin wrappers that read from the reactive store ref on each call,\n * so they always target the active store even after a provider prop change\n * triggers store recreation.\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearchActions(): PredictiveSearchActions {\n const { storeRef } = useRequiredContext(\"usePredictiveSearchActions\");\n\n return {\n search: (term) => storeRef.value.search(term),\n clear: () => storeRef.value.clear(),\n };\n}\n\n/**\n * Returns `formProps` and `register` for building a progressively-enhanced\n * search form.\n *\n * `formProps()` generates form element attributes including the search\n * action URL. `register(\"query\")` generates input attributes and calls the\n * store's search action on every `input` event. The store debounces requests\n * and resets state for terms shorter than `minTermLength`.\n * Both support an optional callback that receives the event and extracted\n * term, and respect `event.preventDefault()` to cancel the automatic behavior.\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearchForm(): PredictiveSearchFormResult {\n const { storeRef, searchActionRef } = useRequiredContext(\"usePredictiveSearchForm\");\n const coreRegister = createPredictiveSearchFormRegister();\n\n function register(\n field: \"query\",\n options: PredictiveSearchQueryInputPropsOptions = {},\n ): Record<string, unknown> {\n const { onInput, ...attributes } = options;\n const coreAttributes = coreRegister(field);\n\n return {\n ...attributes,\n ...coreAttributes,\n onInput: (event: Event) => {\n const term = (event.target as HTMLInputElement).value;\n onInput?.(event, term);\n if (event.defaultPrevented) return;\n void storeRef.value.search(term);\n },\n };\n }\n\n function formProps(options: PredictiveSearchFormPropsOptions = {}): Record<string, unknown> {\n const { onSubmit, preventDefault, ...attributes } = options;\n\n return {\n ...getPredictiveSearchFormAttributes(searchActionRef.value),\n ...attributes,\n onSubmit: (event: SubmitEvent) => {\n const term = readPredictiveSearchFormTerm(\n new FormData(event.currentTarget as HTMLFormElement),\n );\n onSubmit?.(event, term);\n if (event.defaultPrevented) return;\n if (!preventDefault) return;\n event.preventDefault();\n void storeRef.value.search(term);\n },\n };\n }\n\n return { formProps, register };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"predictive-search.mjs","names":[],"sources":["../../src/vue/predictive-search.ts"],"sourcesContent":["import {\n defineComponent,\n inject,\n onMounted,\n onUnmounted,\n provide,\n shallowRef,\n watch,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport {\n createPredictiveSearchFormRegister,\n createPredictiveSearchStore,\n getPredictiveSearchFormAttributes,\n readPredictiveSearchFormTerm,\n type CreatePredictiveSearchStoreOptions,\n type PredictiveSearchActions,\n type PredictiveSearchData,\n type PredictiveSearchState,\n type PredictiveSearchStore,\n} from \"../core/predictive-search\";\nimport type {\n PredictiveSearchLimitScope,\n PredictiveSearchType,\n SearchableField,\n SearchUnavailableProductsType,\n} from \"../graphql/generated/storefront-api-types\";\n\nconst CONFIG_ARRAY_SEPARATOR = \"\\0\";\n\ntype PredictiveSearchContextValue = {\n storeRef: ShallowRef<PredictiveSearchStore>;\n searchActionRef: ShallowRef<string | undefined>;\n};\n\nconst PredictiveSearchKey: InjectionKey<PredictiveSearchContextValue> = Symbol(\"PredictiveSearch\");\n\nexport type { PredictiveSearchActions };\n\n/**\n * Options for the form props function of the predictive search form composable.\n *\n * Accepts any form attribute. Attributes that you pass override the default action, method, and role.\n */\nexport type PredictiveSearchFormPropsOptions = {\n /** Set to `true` to cancel the native submission and run a client-side search with the submitted term. */\n preventDefault?: boolean;\n /** Runs on submit with the submit event and the search term. Calling preventDefault on the event cancels the native submission and the client-side search. */\n onSubmit?: (event: SubmitEvent, term: string) => void;\n [key: string]: unknown;\n};\n\n/** Options for the query input attributes that the register function returns. */\nexport type PredictiveSearchQueryInputPropsOptions = {\n /** Runs on input with the input event and the input value. Calling preventDefault on the event skips the automatic search. */\n onInput?: (event: Event, term: string) => void;\n [key: string]: unknown;\n};\n\n/** Functions that build a predictive search form that works without JavaScript. */\nexport type PredictiveSearchFormResult = {\n /** Returns the form attributes, including the search action and a submit handler. */\n formProps(options?: PredictiveSearchFormPropsOptions): Record<string, unknown>;\n /** Returns the query input attributes, including an input handler that searches on every input event. The function accepts only the `query` field and throws for any other field name. */\n register: (\n field: \"query\",\n options?: PredictiveSearchQueryInputPropsOptions,\n ) => Record<string, unknown>;\n};\n\n/**\n * Creates a predictive search store and shares the store with the predictive search composables in the default slot.\n *\n * The provider connects the store on mount and destroys the store on unmount. A change to a store option prop replaces the store with a new one. The provider compares the types and searchable fields arrays by their values.\n *\n * The provider throws when neither the fetch prop nor a global fetch function exists.\n *\n * @throws {Error} When neither the fetch prop nor a global fetch function exists.\n * @publicDocs\n */\nexport const PredictiveSearchProvider = defineComponent({\n name: \"PredictiveSearchProvider\",\n props: {\n predictiveSearchEndpoint: { type: String, default: undefined },\n searchAction: { type: String, default: undefined },\n debounceInMs: { type: Number, default: undefined },\n minTermLength: { type: Number, default: undefined },\n fetch: { type: Function as PropType<typeof globalThis.fetch>, default: undefined },\n limit: { type: Number, default: undefined },\n limitScope: { type: String as PropType<PredictiveSearchLimitScope>, default: undefined },\n types: { type: Array as PropType<PredictiveSearchType[]>, default: undefined },\n searchableFields: { type: Array as PropType<SearchableField[]>, default: undefined },\n unavailableProducts: {\n type: String as PropType<SearchUnavailableProductsType>,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n function buildStoreOptions(): CreatePredictiveSearchStoreOptions {\n return {\n predictiveSearchEndpoint: props.predictiveSearchEndpoint,\n debounceInMs: props.debounceInMs,\n minTermLength: props.minTermLength,\n fetch: props.fetch as typeof globalThis.fetch | undefined,\n limit: props.limit,\n limitScope: props.limitScope,\n types: props.types,\n searchableFields: props.searchableFields,\n unavailableProducts: props.unavailableProducts,\n };\n }\n\n const storeRef = shallowRef<PredictiveSearchStore>(\n createPredictiveSearchStore(buildStoreOptions()),\n );\n const searchActionRef = shallowRef(props.searchAction);\n\n provide(PredictiveSearchKey, { storeRef, searchActionRef });\n\n watch(\n () => props.searchAction,\n (next) => {\n searchActionRef.value = next;\n },\n );\n\n watch(\n () => [\n props.predictiveSearchEndpoint,\n props.debounceInMs,\n props.minTermLength,\n props.fetch,\n props.limit,\n props.limitScope,\n getArrayKey(props.types),\n getArrayKey(props.searchableFields),\n props.unavailableProducts,\n ],\n () => {\n storeRef.value.destroy();\n storeRef.value = createPredictiveSearchStore(buildStoreOptions());\n storeRef.value.connect();\n },\n );\n\n onMounted(() => {\n storeRef.value.connect();\n });\n\n onUnmounted(() => {\n storeRef.value.destroy();\n });\n\n return () => slots.default?.();\n },\n});\n\nfunction getArrayKey(values: readonly string[] | undefined): string {\n return values?.join(CONFIG_ARRAY_SEPARATOR) ?? \"\";\n}\n\nfunction useRequiredContext(composableName: string): PredictiveSearchContextValue {\n const context = inject(PredictiveSearchKey, null);\n if (!context) {\n throw new Error(`${composableName} must be used inside a <PredictiveSearchProvider>.`);\n }\n return context;\n}\n\n/**\n * Returns the predictive search state as a read-only shallow ref that updates when the state changes.\n *\n * With a selector, the ref holds the selected value and updates only when that value changes.\n * The composable compares values by reference unless you pass an equality function.\n *\n * @throws {Error} When you call it outside a PredictiveSearchProvider.\n * @publicDocs\n */\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(): Readonly<ShallowRef<PredictiveSearchState<TData>>>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(\n selector: (state: PredictiveSearchState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(\n selector?: (state: PredictiveSearchState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<PredictiveSearchState<TData> | S>> {\n const { storeRef } = useRequiredContext(\"usePredictiveSearch\");\n const resolve = selector ?? ((state: PredictiveSearchState<TData>) => state as unknown as S);\n const selected = shallowRef<PredictiveSearchState<TData> | S>(\n resolve((storeRef.value as PredictiveSearchStore<TData>).getState()),\n );\n\n watch(\n () => storeRef.value,\n (store, _, onCleanup) => {\n const typedStore = store as PredictiveSearchStore<TData>;\n selected.value = resolve(typedStore.getState());\n\n const unsubscribe = typedStore.subscribe(() => {\n const next = resolve(typedStore.getState());\n if (isEqual && selector) {\n if (isEqual(selected.value as S, next)) return;\n }\n selected.value = next;\n });\n\n onCleanup(unsubscribe);\n },\n { immediate: true },\n );\n\n return selected as Readonly<ShallowRef<PredictiveSearchState<TData> | S>>;\n}\n\n/**\n * Returns methods that search and clear results in the provider's current store.\n *\n * After a store option prop change replaces the store, the methods act on the new store.\n *\n * @throws {Error} When you call it outside a PredictiveSearchProvider.\n * @publicDocs\n */\nexport function usePredictiveSearchActions(): PredictiveSearchActions {\n const { storeRef } = useRequiredContext(\"usePredictiveSearchActions\");\n\n return {\n search: (term) => storeRef.value.search(term),\n clear: () => storeRef.value.clear(),\n };\n}\n\n/**\n * Returns functions that build a predictive search form that works without JavaScript.\n *\n * The form props function returns the form attributes. The register function returns the query input attributes and searches on every input event.\n * Both functions accept a callback that receives the event and the search term. Calling preventDefault on the event cancels the automatic behavior.\n *\n * @throws {Error} When you call it outside a PredictiveSearchProvider.\n * @publicDocs\n */\nexport function usePredictiveSearchForm(): PredictiveSearchFormResult {\n const { storeRef, searchActionRef } = useRequiredContext(\"usePredictiveSearchForm\");\n const coreRegister = createPredictiveSearchFormRegister();\n\n function register(\n field: \"query\",\n options: PredictiveSearchQueryInputPropsOptions = {},\n ): Record<string, unknown> {\n const { onInput, ...attributes } = options;\n const coreAttributes = coreRegister(field);\n\n return {\n ...attributes,\n ...coreAttributes,\n onInput: (event: Event) => {\n const term = (event.target as HTMLInputElement).value;\n onInput?.(event, term);\n if (event.defaultPrevented) return;\n void storeRef.value.search(term);\n },\n };\n }\n\n function formProps(options: PredictiveSearchFormPropsOptions = {}): Record<string, unknown> {\n const { onSubmit, preventDefault, ...attributes } = options;\n\n return {\n ...getPredictiveSearchFormAttributes(searchActionRef.value),\n ...attributes,\n onSubmit: (event: SubmitEvent) => {\n const term = readPredictiveSearchFormTerm(\n new FormData(event.currentTarget as HTMLFormElement),\n );\n onSubmit?.(event, term);\n if (event.defaultPrevented) return;\n if (!preventDefault) return;\n event.preventDefault();\n void storeRef.value.search(term);\n },\n };\n }\n\n return { formProps, register };\n}\n"],"mappings":"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"}
@@ -5,11 +5,12 @@ import { ProductFormRegister } from "../core/product/form.mjs";
5
5
  import { ShallowRef, defineComponent } from "vue";
6
6
 
7
7
  //#region src/vue/product.d.ts
8
- /** Options for {@link useProductForm}. */
8
+ /** Options for the standalone product form composable. */
9
9
  interface UseProductFormOptions<TProduct extends ProductInput> {
10
+ /** Runs after each resolved or unresolved option selection. The composable captures the callback at setup time. */
10
11
  onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;
11
12
  }
12
- /** Return value of {@link useProductForm}. */
13
+ /** Return value of the product form composables. */
13
14
  interface UseProductFormResult<TProduct extends ProductInput> {
14
15
  options: ProductFormOptions<TProduct>;
15
16
  selectedVariant: ProductVariantFrom<TProduct> | null;
@@ -20,11 +21,11 @@ interface UseProductFormResult<TProduct extends ProductInput> {
20
21
  }) => Record<string, unknown>;
21
22
  errors: ProductFormErrors;
22
23
  matchedLineItem: CartLine | null;
23
- /** Reactive ref that is `true` while a form submission is in flight. */
24
+ /** Shallow ref that holds `true` while a form submission is in flight. */
24
25
  pending: ShallowRef<boolean>;
25
26
  selectOption: (name: string, value: string) => VariantSelectionResult<ProductVariantFrom<TProduct>>;
26
27
  }
27
- /** Return value of the `useProduct` composable from {@link createProductComponents}. */
28
+ /** Return value of the product state composable that the product components factory returns. */
28
29
  interface UseProductResult<TProduct extends ProductInput> {
29
30
  options: ProductFormOptions<TProduct>;
30
31
  selectedVariant: ProductVariantFrom<TProduct> | null;
@@ -33,23 +34,23 @@ interface UseProductResult<TProduct extends ProductInput> {
33
34
  matchedLineItem: CartLine | null;
34
35
  }
35
36
  /**
36
- * Subscribes to a {@link ProductFormStore} and returns form-ready state.
37
+ * Returns form state from a product form store and updates on each store change.
37
38
  *
38
- * This is a pure subscription composable — it does **not** manage store
39
- * lifecycle. Create the store with `createProductFormStore` and manage its
40
- * lifecycle (hydration, destruction) yourself, or use
41
- * `createProductComponents` for a provider-based approach.
39
+ * The composable subscribes to the store and leaves the store lifecycle to you. Create the store with createProductFormStore, then hydrate and destroy the store yourself. For a provider that manages the store, use createProductComponents.
40
+ *
41
+ * Read properties from the returned object. Destructuring reads each getter once and loses reactivity.
42
+ *
43
+ * @publicDocs
42
44
  */
43
45
  declare function useProductForm<TProduct extends ProductInput>(store: ProductFormStore<TProduct>, options?: UseProductFormOptions<TProduct>): UseProductFormResult<TProduct>;
44
46
  /**
45
- * Creates a typed set of product components bound to a specific product type.
47
+ * Returns a product provider and composables typed to your product query.
48
+ *
49
+ * The provider creates a product form store, hydrates the store when the product changes, and destroys the store on unmount. The product composable returns option state and variant selection. The form composable adds field registration, form props, and pending state. Both composables read the latest `onSelect` prop.
46
50
  *
47
- * Returns `{ ProductProvider, useProduct, useProductForm }` where:
48
- * - `ProductProvider` manages store lifecycle (creation, hydration, cleanup)
49
- * - `useProduct` provides read-only state and variant selection
50
- * - `useProductForm` provides form-binding utilities (register, formProps, pending)
51
+ * The provider throws an error without a cart provider ancestor. Each composable throws an error outside the product provider.
51
52
  *
52
- * Requires a `<CartProvider>` ancestor.
53
+ * @publicDocs
53
54
  */
54
55
  declare function createProductComponents<TProduct extends ProductInput>(): {
55
56
  ProductProvider: ReturnType<typeof defineComponent>;
@@ -62,26 +62,26 @@ function useProductFormImpl(store, getOnSelect) {
62
62
  };
63
63
  }
64
64
  /**
65
- * Subscribes to a {@link ProductFormStore} and returns form-ready state.
65
+ * Returns form state from a product form store and updates on each store change.
66
66
  *
67
- * This is a pure subscription composable — it does **not** manage store
68
- * lifecycle. Create the store with `createProductFormStore` and manage its
69
- * lifecycle (hydration, destruction) yourself, or use
70
- * `createProductComponents` for a provider-based approach.
67
+ * The composable subscribes to the store and leaves the store lifecycle to you. Create the store with createProductFormStore, then hydrate and destroy the store yourself. For a provider that manages the store, use createProductComponents.
68
+ *
69
+ * Read properties from the returned object. Destructuring reads each getter once and loses reactivity.
70
+ *
71
+ * @publicDocs
71
72
  */
72
73
  function useProductForm(store, options) {
73
74
  const onSelect = options?.onSelect;
74
75
  return useProductFormImpl(store, () => onSelect);
75
76
  }
76
77
  /**
77
- * Creates a typed set of product components bound to a specific product type.
78
+ * Returns a product provider and composables typed to your product query.
79
+ *
80
+ * The provider creates a product form store, hydrates the store when the product changes, and destroys the store on unmount. The product composable returns option state and variant selection. The form composable adds field registration, form props, and pending state. Both composables read the latest `onSelect` prop.
78
81
  *
79
- * Returns `{ ProductProvider, useProduct, useProductForm }` where:
80
- * - `ProductProvider` manages store lifecycle (creation, hydration, cleanup)
81
- * - `useProduct` provides read-only state and variant selection
82
- * - `useProductForm` provides form-binding utilities (register, formProps, pending)
82
+ * The provider throws an error without a cart provider ancestor. Each composable throws an error outside the product provider.
83
83
  *
84
- * Requires a `<CartProvider>` ancestor.
84
+ * @publicDocs
85
85
  */
86
86
  function createProductComponents() {
87
87
  const ProductStoreKey = Symbol("ProductFormStore");
@@ -1 +1 @@
1
- {"version":3,"file":"product.mjs","names":[],"sources":["../../src/vue/product.ts"],"sourcesContent":["import {\n defineComponent,\n inject,\n onScopeDispose,\n onUnmounted,\n provide,\n shallowRef,\n watch,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport type { CartLine } from \"../core/cart/state\";\nimport { getLogger } from \"../core/logging\";\nimport {\n createProductFormRegister,\n createProductFormStore,\n type ProductFormErrors,\n type ProductFormOptions,\n type ProductFormRegister,\n type ProductFormStore,\n type ProductInput,\n type ProductVariantFrom,\n type ValidProductSelectionResult,\n type VariantSelectionResult,\n} from \"../core/product\";\nimport { getCartEndpoint, useCartStore } from \"./cart\";\n\nconst log = getLogger(\"product\");\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport type { ValidProductSelectionResult } from \"../core/product\";\n\n/** Options for {@link useProductForm}. */\nexport interface UseProductFormOptions<TProduct extends ProductInput> {\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n}\n\n/** Return value of {@link useProductForm}. */\nexport interface UseProductFormResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n register: ProductFormRegister;\n formProps: (opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }) => Record<string, unknown>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n /** Reactive ref that is `true` while a form submission is in flight. */\n pending: ShallowRef<boolean>;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\n/** Return value of the `useProduct` composable from {@link createProductComponents}. */\nexport interface UseProductResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\ninterface ProductContextValue<TProduct extends ProductInput> {\n store: ProductFormStore<TProduct>;\n getOnSelect: () => ((result: ValidProductSelectionResult<TProduct>) => void) | undefined;\n}\n\n/**\n * Shared implementation backing both the standalone composable and the factory\n * composable. Accepts a `getOnSelect` getter so the factory version can always\n * read the latest provider prop, while the standalone version captures the\n * caller's callback at setup time.\n */\nfunction useProductFormImpl<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n getOnSelect: () => ((result: ValidProductSelectionResult<TProduct>) => void) | undefined,\n): UseProductFormResult<TProduct> {\n const state = shallowRef(store.getState());\n const pending = shallowRef(false);\n\n const unsubscribe = store.subscribe(() => {\n state.value = store.getState();\n });\n onScopeDispose(unsubscribe);\n\n function selectOption(\n name: string,\n value: string,\n ): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n getOnSelect()?.(result);\n }\n return result;\n }\n\n function formProps(opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }): Record<string, unknown> {\n return {\n onSubmit: (e: SubmitEvent) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n pending.value = true;\n store\n .handleFormSubmit(e)\n .then(\n () => opts?.afterSubmit?.(e),\n (error: unknown) => {\n log.error(\"form submission error\", { error });\n },\n )\n .finally(() => {\n pending.value = false;\n });\n },\n method: \"post\",\n action: getCartEndpoint(),\n };\n }\n\n return {\n get options() {\n return state.value.options;\n },\n get selectedVariant() {\n return state.value.selectedVariant;\n },\n get register() {\n return createProductFormRegister(state.value.selectedVariant, selectOption);\n },\n formProps,\n get errors() {\n return state.value.errors;\n },\n get matchedLineItem() {\n return state.value.matchedLineItem;\n },\n pending,\n selectOption,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Standalone composable\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribes to a {@link ProductFormStore} and returns form-ready state.\n *\n * This is a pure subscription composable — it does **not** manage store\n * lifecycle. Create the store with `createProductFormStore` and manage its\n * lifecycle (hydration, destruction) yourself, or use\n * `createProductComponents` for a provider-based approach.\n */\nexport function useProductForm<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n options?: UseProductFormOptions<TProduct>,\n): UseProductFormResult<TProduct> {\n const onSelect = options?.onSelect;\n return useProductFormImpl(store, () => onSelect);\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Creates a typed set of product components bound to a specific product type.\n *\n * Returns `{ ProductProvider, useProduct, useProductForm }` where:\n * - `ProductProvider` manages store lifecycle (creation, hydration, cleanup)\n * - `useProduct` provides read-only state and variant selection\n * - `useProductForm` provides form-binding utilities (register, formProps, pending)\n *\n * Requires a `<CartProvider>` ancestor.\n */\nexport function createProductComponents<TProduct extends ProductInput>(): {\n ProductProvider: ReturnType<typeof defineComponent>;\n useProduct: () => UseProductResult<TProduct>;\n useProductForm: () => UseProductFormResult<TProduct>;\n} {\n const ProductStoreKey: InjectionKey<ProductContextValue<TProduct>> = Symbol(\"ProductFormStore\");\n\n function useProductContext(composableName: string): ProductContextValue<TProduct> {\n const ctx = inject(ProductStoreKey);\n if (!ctx) {\n throw new Error(\n `${composableName} must be used inside a <ProductProvider>. ` +\n \"Wrap your component tree with the ProductProvider from createProductComponents(), \" +\n \"or use the standalone useProductForm(store) composable instead.\",\n );\n }\n return ctx;\n }\n\n const ProductProvider = defineComponent({\n name: \"ProductProvider\",\n props: {\n product: {\n type: Object as PropType<TProduct>,\n required: true,\n },\n onSelect: {\n type: Function as PropType<(result: ValidProductSelectionResult<TProduct>) => void>,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n const product = props.product as TProduct;\n const cartStore = useCartStore();\n const store = createProductFormStore<TProduct>(product, cartStore);\n\n provide(ProductStoreKey, {\n store,\n getOnSelect: () =>\n props.onSelect as ((result: ValidProductSelectionResult<TProduct>) => void) | undefined,\n });\n\n let mounted = false;\n watch(\n () => {\n const p = props.product as TProduct;\n return `${p.id}:${p.selectedOrFirstAvailableVariant?.id ?? \"\"}`;\n },\n () => {\n if (!mounted) {\n mounted = true;\n return;\n }\n store.hydrate(props.product as TProduct);\n },\n { immediate: true },\n );\n\n onUnmounted(() => store.destroy());\n\n return () => slots.default?.();\n },\n });\n\n function useProduct(): UseProductResult<TProduct> {\n const ctx = useProductContext(\"useProduct\");\n const state = shallowRef(ctx.store.getState());\n\n const unsubscribe = ctx.store.subscribe(() => {\n state.value = ctx.store.getState();\n });\n onScopeDispose(unsubscribe);\n\n function selectOption(\n name: string,\n value: string,\n ): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const result = ctx.store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n ctx.getOnSelect()?.(result);\n }\n return result;\n }\n\n return {\n get options() {\n return state.value.options;\n },\n get selectedVariant() {\n return state.value.selectedVariant;\n },\n selectOption,\n get errors() {\n return state.value.errors;\n },\n get matchedLineItem() {\n return state.value.matchedLineItem;\n },\n };\n }\n\n function useProductFormHook(): UseProductFormResult<TProduct> {\n const ctx = useProductContext(\"useProductForm\");\n return useProductFormImpl(ctx.store, ctx.getOnSelect);\n }\n\n return {\n ProductProvider,\n useProduct,\n useProductForm: useProductFormHook,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"product.mjs","names":[],"sources":["../../src/vue/product.ts"],"sourcesContent":["import {\n defineComponent,\n inject,\n onScopeDispose,\n onUnmounted,\n provide,\n shallowRef,\n watch,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport type { CartLine } from \"../core/cart/state\";\nimport { getLogger } from \"../core/logging\";\nimport {\n createProductFormRegister,\n createProductFormStore,\n type ProductFormErrors,\n type ProductFormOptions,\n type ProductFormRegister,\n type ProductFormStore,\n type ProductInput,\n type ProductVariantFrom,\n type ValidProductSelectionResult,\n type VariantSelectionResult,\n} from \"../core/product\";\nimport { getCartEndpoint, useCartStore } from \"./cart\";\n\nconst log = getLogger(\"product\");\n\n// ---------------------------------------------------------------------------\n// Shared types\n// ---------------------------------------------------------------------------\n\nexport type { ValidProductSelectionResult } from \"../core/product\";\n\n/** Options for the standalone product form composable. */\nexport interface UseProductFormOptions<TProduct extends ProductInput> {\n /** Runs after each resolved or unresolved option selection. The composable captures the callback at setup time. */\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n}\n\n/** Return value of the product form composables. */\nexport interface UseProductFormResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n register: ProductFormRegister;\n formProps: (opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }) => Record<string, unknown>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n /** Shallow ref that holds `true` while a form submission is in flight. */\n pending: ShallowRef<boolean>;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\n/** Return value of the product state composable that the product components factory returns. */\nexport interface UseProductResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\ninterface ProductContextValue<TProduct extends ProductInput> {\n store: ProductFormStore<TProduct>;\n getOnSelect: () => ((result: ValidProductSelectionResult<TProduct>) => void) | undefined;\n}\n\n/**\n * Shared implementation backing both the standalone composable and the factory\n * composable. Accepts a `getOnSelect` getter so the factory version can always\n * read the latest provider prop, while the standalone version captures the\n * caller's callback at setup time.\n */\nfunction useProductFormImpl<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n getOnSelect: () => ((result: ValidProductSelectionResult<TProduct>) => void) | undefined,\n): UseProductFormResult<TProduct> {\n const state = shallowRef(store.getState());\n const pending = shallowRef(false);\n\n const unsubscribe = store.subscribe(() => {\n state.value = store.getState();\n });\n onScopeDispose(unsubscribe);\n\n function selectOption(\n name: string,\n value: string,\n ): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n getOnSelect()?.(result);\n }\n return result;\n }\n\n function formProps(opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }): Record<string, unknown> {\n return {\n onSubmit: (e: SubmitEvent) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n pending.value = true;\n store\n .handleFormSubmit(e)\n .then(\n () => opts?.afterSubmit?.(e),\n (error: unknown) => {\n log.error(\"form submission error\", { error });\n },\n )\n .finally(() => {\n pending.value = false;\n });\n },\n method: \"post\",\n action: getCartEndpoint(),\n };\n }\n\n return {\n get options() {\n return state.value.options;\n },\n get selectedVariant() {\n return state.value.selectedVariant;\n },\n get register() {\n return createProductFormRegister(state.value.selectedVariant, selectOption);\n },\n formProps,\n get errors() {\n return state.value.errors;\n },\n get matchedLineItem() {\n return state.value.matchedLineItem;\n },\n pending,\n selectOption,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Standalone composable\n// ---------------------------------------------------------------------------\n\n/**\n * Returns form state from a product form store and updates on each store change.\n *\n * The composable subscribes to the store and leaves the store lifecycle to you. Create the store with createProductFormStore, then hydrate and destroy the store yourself. For a provider that manages the store, use createProductComponents.\n *\n * Read properties from the returned object. Destructuring reads each getter once and loses reactivity.\n *\n * @publicDocs\n */\nexport function useProductForm<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n options?: UseProductFormOptions<TProduct>,\n): UseProductFormResult<TProduct> {\n const onSelect = options?.onSelect;\n return useProductFormImpl(store, () => onSelect);\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Returns a product provider and composables typed to your product query.\n *\n * The provider creates a product form store, hydrates the store when the product changes, and destroys the store on unmount. The product composable returns option state and variant selection. The form composable adds field registration, form props, and pending state. Both composables read the latest `onSelect` prop.\n *\n * The provider throws an error without a cart provider ancestor. Each composable throws an error outside the product provider.\n *\n * @publicDocs\n */\nexport function createProductComponents<TProduct extends ProductInput>(): {\n ProductProvider: ReturnType<typeof defineComponent>;\n useProduct: () => UseProductResult<TProduct>;\n useProductForm: () => UseProductFormResult<TProduct>;\n} {\n const ProductStoreKey: InjectionKey<ProductContextValue<TProduct>> = Symbol(\"ProductFormStore\");\n\n function useProductContext(composableName: string): ProductContextValue<TProduct> {\n const ctx = inject(ProductStoreKey);\n if (!ctx) {\n throw new Error(\n `${composableName} must be used inside a <ProductProvider>. ` +\n \"Wrap your component tree with the ProductProvider from createProductComponents(), \" +\n \"or use the standalone useProductForm(store) composable instead.\",\n );\n }\n return ctx;\n }\n\n const ProductProvider = defineComponent({\n name: \"ProductProvider\",\n props: {\n product: {\n type: Object as PropType<TProduct>,\n required: true,\n },\n onSelect: {\n type: Function as PropType<(result: ValidProductSelectionResult<TProduct>) => void>,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n const product = props.product as TProduct;\n const cartStore = useCartStore();\n const store = createProductFormStore<TProduct>(product, cartStore);\n\n provide(ProductStoreKey, {\n store,\n getOnSelect: () =>\n props.onSelect as ((result: ValidProductSelectionResult<TProduct>) => void) | undefined,\n });\n\n let mounted = false;\n watch(\n () => {\n const p = props.product as TProduct;\n return `${p.id}:${p.selectedOrFirstAvailableVariant?.id ?? \"\"}`;\n },\n () => {\n if (!mounted) {\n mounted = true;\n return;\n }\n store.hydrate(props.product as TProduct);\n },\n { immediate: true },\n );\n\n onUnmounted(() => store.destroy());\n\n return () => slots.default?.();\n },\n });\n\n function useProduct(): UseProductResult<TProduct> {\n const ctx = useProductContext(\"useProduct\");\n const state = shallowRef(ctx.store.getState());\n\n const unsubscribe = ctx.store.subscribe(() => {\n state.value = ctx.store.getState();\n });\n onScopeDispose(unsubscribe);\n\n function selectOption(\n name: string,\n value: string,\n ): VariantSelectionResult<ProductVariantFrom<TProduct>> {\n const result = ctx.store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n ctx.getOnSelect()?.(result);\n }\n return result;\n }\n\n return {\n get options() {\n return state.value.options;\n },\n get selectedVariant() {\n return state.value.selectedVariant;\n },\n selectOption,\n get errors() {\n return state.value.errors;\n },\n get matchedLineItem() {\n return state.value.matchedLineItem;\n },\n };\n }\n\n function useProductFormHook(): UseProductFormResult<TProduct> {\n const ctx = useProductContext(\"useProductForm\");\n return useProductFormImpl(ctx.store, ctx.getOnSelect);\n }\n\n return {\n ProductProvider,\n useProduct,\n useProductForm: useProductFormHook,\n };\n}\n"],"mappings":"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"}
@@ -2,24 +2,21 @@ import { ShopPayButtonOptions } from "../core/shop-pay/shop-pay.mjs";
2
2
  import * as _$vue from "vue";
3
3
 
4
4
  //#region src/vue/shop-pay.d.ts
5
+ /**
6
+ * Props for the Vue ShopPayButton component.
7
+ *
8
+ * @publicDocs
9
+ */
5
10
  type ShopPayButtonProps = ShopPayButtonOptions;
6
- declare const ShopPayButton: _$vue.DefineSetupFnComponent<ShopPayButtonOptions, {}, {}, {
7
- checkoutUrl?: string;
8
- paymentOption?: "shop_pay" | "shop_pay_installments";
9
- source?: string;
10
- sourceToken?: string;
11
- nonce?: string;
12
- channel?: "headless" | "hydrogen";
13
- disabled?: boolean;
14
- width?: string;
15
- borderRadius?: string;
16
- accessibilityLabel?: string;
17
- } & {
18
- variants?: readonly string[] | readonly {
19
- id: string;
20
- quantity?: number;
21
- }[];
22
- } & {}, _$vue.PublicProps>;
11
+ /**
12
+ * Renders the Shop Pay button custom element. During server rendering, the component outputs
13
+ * declarative shadow DOM, which displays the button before scripts load.
14
+ *
15
+ * The component takes the createShopPayButton options as Vue props. The component renders no slot content and drops extra attributes, including `class` and `style`. Vue templates can pass the props in kebab-case, such as `border-radius` and `accessibility-label`.
16
+ *
17
+ * @publicDocs
18
+ */
19
+ declare const ShopPayButton: _$vue.DefineSetupFnComponent<ShopPayButtonOptions, {}, {}, ShopPayButtonOptions & {}, _$vue.PublicProps>;
23
20
  //#endregion
24
21
  export { ShopPayButton, ShopPayButtonProps };
25
22
  //# sourceMappingURL=shop-pay.d.mts.map
@@ -4,6 +4,14 @@ import { defineComponent, h } from "vue";
4
4
  //#region src/vue/shop-pay.ts
5
5
  const canUseDom = typeof document !== "undefined";
6
6
  defineShopPayButton();
7
+ /**
8
+ * Renders the Shop Pay button custom element. During server rendering, the component outputs
9
+ * declarative shadow DOM, which displays the button before scripts load.
10
+ *
11
+ * The component takes the createShopPayButton options as Vue props. The component renders no slot content and drops extra attributes, including `class` and `style`. Vue templates can pass the props in kebab-case, such as `border-radius` and `accessibility-label`.
12
+ *
13
+ * @publicDocs
14
+ */
7
15
  const ShopPayButton = defineComponent((props) => {
8
16
  return () => {
9
17
  const attributes = getShopPayButtonElementAttributes(props);
@@ -1 +1 @@
1
- {"version":3,"file":"shop-pay.mjs","names":[],"sources":["../../src/vue/shop-pay.ts"],"sourcesContent":["import { defineComponent, h } from \"vue\";\n\nimport {\n defineShopPayButton,\n getShopPayButtonDeclarativeShadowDomHtml,\n getShopPayButtonElementAttributes,\n SHOP_PAY_BUTTON_TAG_NAME,\n type ShopPayButtonOptions,\n} from \"../core/shop-pay/shop-pay\";\n\nexport type ShopPayButtonProps = ShopPayButtonOptions;\n\ntype CompletePropOptions<T> = {\n [K in keyof T]-?: unknown;\n};\n\nconst canUseDom = typeof document !== \"undefined\";\ndefineShopPayButton();\n\nexport const ShopPayButton = defineComponent(\n (props: ShopPayButtonProps) => {\n return () => {\n const attributes = getShopPayButtonElementAttributes(props);\n return canUseDom\n ? h(SHOP_PAY_BUTTON_TAG_NAME, attributes)\n : h(SHOP_PAY_BUTTON_TAG_NAME, {\n ...attributes,\n innerHTML: getShopPayButtonDeclarativeShadowDomHtml(props),\n });\n };\n },\n {\n name: \"ShopPayButton\",\n inheritAttrs: false,\n props: {\n variants: null,\n channel: null,\n checkoutUrl: String,\n paymentOption: null,\n source: String,\n sourceToken: String,\n nonce: String,\n disabled: Boolean,\n width: String,\n borderRadius: String,\n accessibilityLabel: String,\n } satisfies CompletePropOptions<ShopPayButtonProps>,\n },\n);\n"],"mappings":";;;;AAgBA,MAAM,YAAY,OAAO,aAAa;AACtC,qBAAqB;AAErB,MAAa,gBAAgB,iBAC1B,UAA8B;AAC7B,cAAa;EACX,MAAM,aAAa,kCAAkC,MAAM;AAC3D,SAAO,YACH,EAAE,0BAA0B,WAAW,GACvC,EAAE,0BAA0B;GAC1B,GAAG;GACH,WAAW,yCAAyC,MAAM;GAC3D,CAAC;;GAGV;CACE,MAAM;CACN,cAAc;CACd,OAAO;EACL,UAAU;EACV,SAAS;EACT,aAAa;EACb,eAAe;EACf,QAAQ;EACR,aAAa;EACb,OAAO;EACP,UAAU;EACV,OAAO;EACP,cAAc;EACd,oBAAoB;EACrB;CACF,CACF"}
1
+ {"version":3,"file":"shop-pay.mjs","names":[],"sources":["../../src/vue/shop-pay.ts"],"sourcesContent":["import { defineComponent, h } from \"vue\";\n\nimport {\n defineShopPayButton,\n getShopPayButtonDeclarativeShadowDomHtml,\n getShopPayButtonElementAttributes,\n SHOP_PAY_BUTTON_TAG_NAME,\n type ShopPayButtonOptions,\n} from \"../core/shop-pay/shop-pay\";\n\n/**\n * Props for the Vue ShopPayButton component.\n *\n * @publicDocs\n */\nexport type ShopPayButtonProps = ShopPayButtonOptions;\n\ntype CompletePropOptions<T> = {\n [K in keyof T]-?: unknown;\n};\n\nconst canUseDom = typeof document !== \"undefined\";\ndefineShopPayButton();\n\n/**\n * Renders the Shop Pay button custom element. During server rendering, the component outputs\n * declarative shadow DOM, which displays the button before scripts load.\n *\n * The component takes the createShopPayButton options as Vue props. The component renders no slot content and drops extra attributes, including `class` and `style`. Vue templates can pass the props in kebab-case, such as `border-radius` and `accessibility-label`.\n *\n * @publicDocs\n */\nexport const ShopPayButton = defineComponent(\n (props: ShopPayButtonProps) => {\n return () => {\n const attributes = getShopPayButtonElementAttributes(props);\n return canUseDom\n ? h(SHOP_PAY_BUTTON_TAG_NAME, attributes)\n : h(SHOP_PAY_BUTTON_TAG_NAME, {\n ...attributes,\n innerHTML: getShopPayButtonDeclarativeShadowDomHtml(props),\n });\n };\n },\n {\n name: \"ShopPayButton\",\n inheritAttrs: false,\n props: {\n variants: null,\n channel: null,\n checkoutUrl: String,\n paymentOption: null,\n source: String,\n sourceToken: String,\n nonce: String,\n disabled: Boolean,\n width: String,\n borderRadius: String,\n accessibilityLabel: String,\n } satisfies CompletePropOptions<ShopPayButtonProps>,\n },\n);\n"],"mappings":";;;;AAqBA,MAAM,YAAY,OAAO,aAAa;AACtC,qBAAqB;;;;;;;;;AAUrB,MAAa,gBAAgB,iBAC1B,UAA8B;AAC7B,cAAa;EACX,MAAM,aAAa,kCAAkC,MAAM;AAC3D,SAAO,YACH,EAAE,0BAA0B,WAAW,GACvC,EAAE,0BAA0B;GAC1B,GAAG;GACH,WAAW,yCAAyC,MAAM;GAC3D,CAAC;;GAGV;CACE,MAAM;CACN,cAAc;CACd,OAAO;EACL,UAAU;EACV,SAAS;EACT,aAAa;EACb,eAAe;EACf,QAAQ;EACR,aAAa;EACb,OAAO;EACP,UAAU;EACV,OAAO;EACP,cAAc;EACd,oBAAoB;EACrB;CACF,CACF"}
@@ -3,11 +3,22 @@ import { ConsentConfig, ConsentSetup } from "../core/analytics/types.mjs";
3
3
  import * as _$vue from "vue";
4
4
 
5
5
  //#region src/vue/shopify-scripts.d.ts
6
+ /**
7
+ * Props for the Vue ShopifyScripts component.
8
+ *
9
+ * @publicDocs
10
+ */
6
11
  type ShopifyScriptsProps = ShopifyScriptTagsOptions & {
7
12
  navigate?: ShopifyRoutesOptions["navigate"];
8
13
  routes?: ShopifyRoutesOptions["routes"];
9
14
  webMcp?: boolean;
10
15
  };
16
+ /**
17
+ * Renders Shopify's script tags during server rendering and starts the scripts once, when the
18
+ * component mounts.
19
+ *
20
+ * @publicDocs
21
+ */
11
22
  declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
12
23
  i18n: {
13
24
  type: () => ShopifyScriptsI18n;
@@ -12,6 +12,12 @@ const analyticsProp = Object;
12
12
  const debugProp = Object;
13
13
  const inboxProp = Boolean;
14
14
  const accountProp = Boolean;
15
+ /**
16
+ * Renders Shopify's script tags during server rendering and starts the scripts once, when the
17
+ * component mounts.
18
+ *
19
+ * @publicDocs
20
+ */
15
21
  const ShopifyScripts = defineComponent({
16
22
  name: "ShopifyScripts",
17
23
  props: {
@@ -1 +1 @@
1
- {"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../src/vue/shopify-scripts.ts"],"sourcesContent":["import { defineComponent, h, onMounted, type PropType } from \"vue\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyScriptsShop,\n type ShopifyScriptsI18n,\n type ShopifyScriptTagsOptions,\n type ShopifyRoutesOptions,\n} from \"../core/shopify-scripts/index\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ntype CompletePropOptions<T> = {\n [K in keyof T]-?: unknown;\n};\n\nconst i18nProp: PropType<ShopifyScriptsI18n> = Object;\nconst navigateProp: PropType<NonNullable<ShopifyRoutesOptions[\"navigate\"]>> | null = null;\nconst routesProp: PropType<NonNullable<ShopifyRoutesOptions[\"routes\"]>> | null = null;\nconst shopProp: PropType<ShopifyScriptsShop> = Object;\nconst consentProp: PropType<ShopifyScriptTagsOptions[\"consent\"]> = Object;\nconst analyticsProp: PropType<ShopifyScriptTagsOptions[\"analytics\"]> = Object;\nconst debugProp: PropType<ShopifyScriptTagsOptions[\"debug\"]> = Object;\nconst inboxProp: PropType<ShopifyScriptTagsOptions[\"inbox\"]> = Boolean;\nconst accountProp: PropType<ShopifyScriptTagsOptions[\"account\"]> = Boolean;\n\nexport const ShopifyScripts = defineComponent({\n name: \"ShopifyScripts\",\n props: {\n i18n: {\n type: i18nProp,\n default: undefined,\n },\n nonce: {\n type: String,\n default: undefined,\n },\n routes: {\n type: routesProp,\n default: undefined,\n },\n navigate: {\n type: navigateProp,\n default: undefined,\n },\n consent: {\n type: consentProp,\n default: undefined,\n },\n analytics: {\n type: analyticsProp,\n default: undefined,\n },\n debug: {\n type: debugProp,\n default: undefined,\n },\n webMcp: {\n type: Boolean,\n default: undefined,\n },\n inbox: {\n type: inboxProp,\n default: undefined,\n },\n account: {\n type: accountProp,\n default: undefined,\n },\n shopifyAnalytics: {\n type: Boolean,\n default: undefined,\n },\n shop: {\n type: shopProp,\n required: true as const,\n },\n } satisfies CompletePropOptions<ShopifyScriptsProps>,\n setup(props) {\n onMounted(() => {\n void initializeShopifyScripts({\n consent: props.consent,\n navigate: props.navigate,\n routes: props.routes,\n webMcp: props.webMcp !== false,\n });\n });\n\n return () =>\n getShopifyScriptTags(props).tags.map(({ tagName, attributes, innerHTML }) =>\n h(tagName, {\n ...attributes,\n ...(innerHTML ? { innerHTML } : {}),\n }),\n );\n },\n});\n"],"mappings":";;;;;AAqBA,MAAM,WAAyC;AAC/C,MAAM,eAA+E;AACrF,MAAM,aAA2E;AACjF,MAAM,WAAyC;AAC/C,MAAM,cAA6D;AACnE,MAAM,gBAAiE;AACvE,MAAM,YAAyD;AAC/D,MAAM,YAAyD;AAC/D,MAAM,cAA6D;AAEnE,MAAa,iBAAiB,gBAAgB;CAC5C,MAAM;CACN,OAAO;EACL,MAAM;GACJ,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,UAAU;GACR,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,WAAW;GACT,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,kBAAkB;GAChB,MAAM;GACN,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,UAAU;GACX;EACF;CACD,MAAM,OAAO;AACX,kBAAgB;AACd,GAAK,yBAAyB;IAC5B,SAAS,MAAM;IACf,UAAU,MAAM;IAChB,QAAQ,MAAM;IACd,QAAQ,MAAM,WAAW;IAC1B,CAAC;IACF;AAEF,eACE,qBAAqB,MAAM,CAAC,KAAK,KAAK,EAAE,SAAS,YAAY,gBAC3D,EAAE,SAAS;GACT,GAAG;GACH,GAAI,YAAY,EAAE,WAAW,GAAG,EAAE;GACnC,CAAC,CACH;;CAEN,CAAC"}
1
+ {"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../src/vue/shopify-scripts.ts"],"sourcesContent":["import { defineComponent, h, onMounted, type PropType } from \"vue\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyScriptsShop,\n type ShopifyScriptsI18n,\n type ShopifyScriptTagsOptions,\n type ShopifyRoutesOptions,\n} from \"../core/shopify-scripts/index\";\n\n/**\n * Props for the Vue ShopifyScripts component.\n *\n * @publicDocs\n */\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ntype CompletePropOptions<T> = {\n [K in keyof T]-?: unknown;\n};\n\nconst i18nProp: PropType<ShopifyScriptsI18n> = Object;\nconst navigateProp: PropType<NonNullable<ShopifyRoutesOptions[\"navigate\"]>> | null = null;\nconst routesProp: PropType<NonNullable<ShopifyRoutesOptions[\"routes\"]>> | null = null;\nconst shopProp: PropType<ShopifyScriptsShop> = Object;\nconst consentProp: PropType<ShopifyScriptTagsOptions[\"consent\"]> = Object;\nconst analyticsProp: PropType<ShopifyScriptTagsOptions[\"analytics\"]> = Object;\nconst debugProp: PropType<ShopifyScriptTagsOptions[\"debug\"]> = Object;\nconst inboxProp: PropType<ShopifyScriptTagsOptions[\"inbox\"]> = Boolean;\nconst accountProp: PropType<ShopifyScriptTagsOptions[\"account\"]> = Boolean;\n\n/**\n * Renders Shopify's script tags during server rendering and starts the scripts once, when the\n * component mounts.\n *\n * @publicDocs\n */\nexport const ShopifyScripts = defineComponent({\n name: \"ShopifyScripts\",\n props: {\n i18n: {\n type: i18nProp,\n default: undefined,\n },\n nonce: {\n type: String,\n default: undefined,\n },\n routes: {\n type: routesProp,\n default: undefined,\n },\n navigate: {\n type: navigateProp,\n default: undefined,\n },\n consent: {\n type: consentProp,\n default: undefined,\n },\n analytics: {\n type: analyticsProp,\n default: undefined,\n },\n debug: {\n type: debugProp,\n default: undefined,\n },\n webMcp: {\n type: Boolean,\n default: undefined,\n },\n inbox: {\n type: inboxProp,\n default: undefined,\n },\n account: {\n type: accountProp,\n default: undefined,\n },\n shopifyAnalytics: {\n type: Boolean,\n default: undefined,\n },\n shop: {\n type: shopProp,\n required: true as const,\n },\n } satisfies CompletePropOptions<ShopifyScriptsProps>,\n setup(props) {\n onMounted(() => {\n void initializeShopifyScripts({\n consent: props.consent,\n navigate: props.navigate,\n routes: props.routes,\n webMcp: props.webMcp !== false,\n });\n });\n\n return () =>\n getShopifyScriptTags(props).tags.map(({ tagName, attributes, innerHTML }) =>\n h(tagName, {\n ...attributes,\n ...(innerHTML ? { innerHTML } : {}),\n }),\n );\n },\n});\n"],"mappings":";;;;;AA0BA,MAAM,WAAyC;AAC/C,MAAM,eAA+E;AACrF,MAAM,aAA2E;AACjF,MAAM,WAAyC;AAC/C,MAAM,cAA6D;AACnE,MAAM,gBAAiE;AACvE,MAAM,YAAyD;AAC/D,MAAM,YAAyD;AAC/D,MAAM,cAA6D;;;;;;;AAQnE,MAAa,iBAAiB,gBAAgB;CAC5C,MAAM;CACN,OAAO;EACL,MAAM;GACJ,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,UAAU;GACR,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,WAAW;GACT,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,kBAAkB;GAChB,MAAM;GACN,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,UAAU;GACX;EACF;CACD,MAAM,OAAO;AACX,kBAAgB;AACd,GAAK,yBAAyB;IAC5B,SAAS,MAAM;IACf,UAAU,MAAM;IAChB,QAAQ,MAAM;IACd,QAAQ,MAAM,WAAW;IAC1B,CAAC;IACF;AAEF,eACE,qBAAqB,MAAM,CAAC,KAAK,KAAK,EAAE,SAAS,YAAY,gBAC3D,EAAE,SAAS;GACT,GAAG;GACH,GAAI,YAAY,EAAE,WAAW,GAAG,EAAE;GACnC,CAAC,CACH;;CAEN,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopify/hydrogen",
3
- "version": "2026.10.0-preview.4",
3
+ "version": "2026.10.0",
4
4
  "description": "Framework-agnostic Shopify storefront SDK and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "Shopify",
@@ -90,6 +90,7 @@
90
90
  "@graphql-codegen/cli": "^7.0.0",
91
91
  "@graphql-codegen/introspection": "^6.0.0",
92
92
  "@graphql-codegen/typescript": "^6.0.0",
93
+ "@shopify/generate-docs": "1.2.2",
93
94
  "@testing-library/react": "^16.3.2",
94
95
  "@types/cross-spawn": "6.0.6",
95
96
  "@types/react": "^19.2.14",
@@ -146,6 +147,7 @@
146
147
  },
147
148
  "scripts": {
148
149
  "build": "tsdown && node scripts/copy-generated-graphql-assets.ts",
150
+ "build-docs": "sh ./docs/build-docs.sh",
149
151
  "dev": "tsdown --watch ./src",
150
152
  "typecheck": "tsc --noEmit",
151
153
  "libcheck": "node scripts/libcheck-package.ts",
@@ -58,7 +58,7 @@ Publish these from route/page boundaries:
58
58
  - `CART_VIEWED` when the full cart page or cart drawer is viewed.
59
59
  - Wire cart tracking once per cart store lifecycle with `trackCartAnalytics(cartStore)` — React apps use the `useCartAnalytics()` hook from `@shopify/hydrogen/react` and Vue apps use the `useCartAnalytics()` composable from `@shopify/hydrogen/vue`; both call it with the provider's cart store and clean up on unmount. The tracker subscribes to the cart store itself, skips pending/revalidating/note updates, publishes cart delta events on confirmed cart changes, and returns an unsubscribe function. Call it from a client-only effect (`useEffect` / `onMounted`), never at cart-store creation time — it throws when `window.Shopify.analytics` is missing (SSR). Do not manually publish cart delta events.
60
60
 
61
- The bus defaults `shop` from the top-level `shop` config passed to ShopifyScripts; pass `shop` in an event payload only when intentionally overriding that configured value. Shopify analytics reads language and currency from `window.Shopify.locale` and `window.Shopify.currency.active`.
61
+ The bus defaults `shop` from the top-level `shop` config passed to ShopifyScripts; pass `shop` in an event payload only when intentionally overriding that configured value. Shopify analytics reads its content language from `window.Shopify.locale`. It reads the currency of product events from the event price's `currencyCode` and falls back to `window.Shopify.currency.active`, which `i18n.currency` sets. Page, collection, and search views take only the global. When it is unset, Shopify analytics sends them without a currency.
62
62
 
63
63
  Required product analytics fields include Shopify Product GID, ProductVariant GID when available, title, price, vendor, quantity, and variant title.
64
64
 
@@ -4,8 +4,9 @@ description: >
4
4
  Behavioral guide for building cart UI with @shopify/hydrogen: line items,
5
5
  quantity and remove controls, optimistic updates, discount, note, and cart
6
6
  attribute inputs, and the full-page /cart fallback. Use when writing,
7
- modifying, or reviewing cart line-item UI, quantity/remove controls, or cart
8
- mutation forms. Framework agnostic.
7
+ modifying, or reviewing cart line-item UI, quantity/remove controls, cart
8
+ mutation forms, or code that changes the cart without a form. Framework
9
+ agnostic.
9
10
  ---
10
11
 
11
12
  # Cart Primitive
@@ -28,6 +29,10 @@ When creating a full cart page, use the app's existing route convention when pre
28
29
 
29
30
  **The native no-JS add-to-cart POST must set the cart cookie server-side.** With scripting off, the add-to-cart `<form method="post">` submits natively and the cart server handler must respond with the cart cookie so the next `/cart` request server-renders the seeded cart — not only service the hydrated `fetch`. See the `hydrogen-request-handlers` skill for the endpoint contract.
30
31
 
32
+ ## Prerequisites
33
+
34
+ The store sends cart changes through Shopify's Standard Actions runtime (`window.Shopify.actions`) and listens for its events. Render `ShopifyScripts` once in the document head. When the app deliberately skips Shopify's other scripts, include only `<script type="module" src="https://cdn.shopify.com/storefront/standard-actions.js"></script>`. Without the runtime, cart actions reject with "Standard Actions not available".
35
+
31
36
  ## How the store works
32
37
 
33
38
  The store holds a `CartState` and notifies subscribers when it changes. `state.readyPromise` is present while an applicable full-cart load is pending and resolves after the resulting state is published. Mutations flow through Shopify Standard Actions — the store listens for `shopify:cart:lines-update`, `shopify:cart:discount-update`, `shopify:cart:note-update`, and `shopify:cart:attributes-update` DOM events. Each event carries a `promise` that resolves with the server response.
@@ -45,6 +50,23 @@ App-owned cart mutations outside Standard Actions do not emit the events the sto
45
50
 
46
51
  The store supersedes keyed mutations for the same line, discount batch, note, or complete attribute list. Relative additions remain independent so every submitted quantity reaches the server; their projections are reconciled together without disabling controls.
47
52
 
53
+ The cart store combines its lifecycle, timeout, and caller signals with `AbortSignal.any`, which Safari added in 17.4. The store already requires Safari 16 for `toSorted`, so Safari 16.0 through 17.3 is the gap. To keep add to cart working there, load an `AbortSignal.any` polyfill in the browser bundle before the store is created.
54
+
55
+ ## Code-driven cart changes
56
+
57
+ Some changes have no form: a free gift added when the cart crosses a threshold, removing a line that became unavailable, an attribute written by a script. Send them with `window.Shopify.actions.updateCart(payload, options)`. The store observes the events it dispatches, so these changes get the same optimistic projection, pending state and rollback as form submissions. Do not hand-roll `fetch` calls to the cart route.
58
+
59
+ - **Wait for the store.** Call `updateCart` only after all of these hold:
60
+ - `window.Shopify.actions.updateCart.isDefault()` returns `false`, which happens once a connected cart store has configured it. Before that, Shopify's default handler runs instead of the app's cart route. The framework cart providers also configure the cart endpoint; a store from `createCartStore()` keeps using Shopify's default handler until the app calls `configureCartEndpoint(path)`.
61
+ - The initial cart load has finished: `state.loading` is `false` and `state.readyPromise` is absent (or await it). A change made during that load can hide the shopper's other lines.
62
+ - A cart exists: `state.data.id` is set. While it is `null`, the first add creates the cart. The store queues form adds behind it, but not `updateCart` calls: an add from code sent before the cart exists, alongside another add from code or a form, creates a second cart, and the shopper keeps only one of them. Send the first add alone and wait for it to settle.
63
+ - **One kind of change per call.** Send `lines`, `discountCodes`, `attributes` or `note`, not several. The cart route rejects a combined call and the store rolls back its projections.
64
+ - **Lines.** `{ merchandiseId, quantity }` adds, `{ id, quantity }` updates, `{ id, quantity: 0 }` removes, and `{ id, merchandiseId, quantity }` swaps the line to another variant. Only additions batch: send several adds in one call if needed, but send each update, removal or swap as its own call with one line. The store does not project a call that updates or removes several lines. A swap projects only the quantity; the new variant appears when Shopify responds, possibly under a new line ID, so don't hold on to the old one. When the response returns a new line ID while other changes are in flight, the new variant appears after the cart refresh that follows them.
65
+ - **Previews for added lines.** Pass `{ event: { detail: { products: [{ id: merchandiseId, price, ...merchandise }] } } }` so a new line shows before Shopify responds. Events do not carry line attributes or selling plans, so the projected line does not show them.
66
+ - **Rules that react to the cart.** Evaluate them on settled state only (`loading` false, no pending entries, `revalidating` false), and do not start another change while your own is in flight. Otherwise the rule reacts to its own projection and can loop.
67
+ - **Several attribute writers.** `attributes` replaces the whole list. Build each write from the latest list you sent, falling back to `state.data.attributes` when nothing is in flight, so writers in the same tick keep each other's keys.
68
+ - **Analytics.** `trackCartAnalytics` reports every confirmed change, including code-driven ones. Storefronts that keep system changes out of analytics must track shopper changes themselves.
69
+
48
70
  ## Stable selectors
49
71
 
50
72
  Store selectors should select primitives or stable references from the store. Do not allocate arrays, objects, maps, sets, or derived view models inside a selector unless the framework binding also accepts an equality function.
@@ -213,6 +235,10 @@ Errors survive unrelated cart work and clear when a new mutation begins for the
213
235
 
214
236
  31. **Refresh custom data** — After an app-owned cart mutation succeeds, request a cart refresh. Every cart consumer receives the updated custom fragment data, `revalidating` represents the refresh, and a refresh failure preserves the confirmed cart while appearing in `errors.network`.
215
237
 
238
+ ### Code-driven changes
239
+
240
+ 32. **Rule on settled state** — A threshold rule that adds a line runs once per qualifying settled cart, never before the initial load finishes or on its own pending projection.
241
+
216
242
  ---
217
243
 
218
244
  ## Anti-patterns
@@ -33,6 +33,7 @@ Server data should include:
33
33
  - `dataSearch`: the exact search string used for the server query.
34
34
  - `products`: Storefront API product nodes shaped for the product card.
35
35
  - `availableFilters`: normalized filter metadata from `products.filters` or `search.productFilters`.
36
+ - `currencyCode` for `PRICE_RANGE` inputs, loaded by the same query as the products so it matches product prices: `localization.country.currency.isoCode` under `@inContext` in market-aware stores, or `shop.paymentSettings.currencyCode` in single-currency stores.
36
37
  - Optional `totalCount` and `pageInfo` for search or pagination UI.
37
38
 
38
39
  Use `parseCollectionParams(searchParams)` before Storefront API queries. Pass parsed `filters`, `sortKey`, and `reverse` into `collection.products(...)` or `search(...)`.
@@ -42,7 +43,10 @@ Use `parseCollectionParams(searchParams)` before Storefront API queries. Pass pa
42
43
  - Use the framework binding when a matching reference exists. Otherwise, use the core store directly. Do not hand-roll browse state with component state.
43
44
  - The browse form must carry both `method="get"` **and** an explicit `action` (the collection/search route URL, e.g. `action="/collections/shoes"` or the search route) so filters and sort degrade to a real GET submit without JavaScript. `formProps()` only wires the submit handler — it does not set `method` or `action` — so render both literally; the helper cannot infer the route.
44
45
  - Choose controls at the filter-group level: `PRICE_RANGE` renders min/max number inputs; `LIST` and `BOOLEAN` render value checkboxes. A price value serializes to two params, so never use the number of serialized entries to decide whether to render the group. Use the fixed `filter.v.price.gte` and `filter.v.price.lte` names for range inputs; continue deriving checkbox names and values from `value.input`.
45
- - Use `formProps()` on the browse form: spread it, then add the literal `method="get"` and `action`. Submit checkboxes and `<select>` controls on change. Submit price inputs on blur only when the value changed, and let Enter blur the input. Never submit on every keystroke.
46
+ - Use `formProps()` on the browse form: spread it, then add the literal `method="get"` and `action`. Submit checkboxes and `<select>` controls immediately on change. Submit price inputs on change with a 350 ms debounce: share one timer between the min and max inputs, clear any pending timer before you schedule `form.requestSubmit()`, and clear the timer on unmount. Update the mounted price inputs in place when bounds change externally, and cancel pending drafts on deliberate navigation. Keep newer typing when the URL update from the shopper's own price submission arrives. Preserve native Enter and form keyboard behavior. Cancel the pending timer whenever the form submits, so each submission sends one request.
47
+ - Keep every filter control (checkboxes, sort `<select>`, price inputs) mounted and enabled while `state.status === "loading"`, and show loading with a pending visual state. Keep stable keys based on control identity across filter updates, so the focused control stays in the DOM. Update external changes (chips, clear-all, back/forward) in place by binding checked and selected values to browse state. Keep native keyboard activation, such as Space on checkboxes and arrow keys on the `<select>`. Focus can still be lost if a result update removes the focused option, for example when zero-count options are hidden.
48
+ - Make price inputs currency-aware with the explicit server `currencyCode`. Get the prefix with `formatMoney({ amount: "0", currencyCode }, { locale }).currencyNarrowSymbol` and an explicit, stable `locale` (the resolved market locale, or one fixed locale in single-market stores) so server and client render the same symbol. The narrow symbol drops currency qualifiers (`$` for USD, CAD, and AUD instead of `CA$` or `A$`), so the accessible label carries the currency code. Do not add a separate `Intl.NumberFormat`, hardcode `$`, infer the currency from the locale, or convert amounts.
49
+ - Render each price input as `type="number"`, `min="0"`, `step="any"` in a wrapper with the symbol as an `aria-hidden="true"` prefix. The symbol is not part of the input value or URL params; params carry plain numbers only (`filter.v.price.gte=10.5`). Give each input an accessible label with minimum or maximum and the currency code, e.g. "Minimum price in CAD".
46
50
  - Render a `noscript` submit button for filter sidebars that auto-submit when hydrated.
47
51
  - Render "load more" / pagination as a GET link (the framework's link component) carrying the next-page cursor (e.g. `?after=<endCursor>`), so it works without JavaScript. Hydration may upgrade it to append-in-place; the bare link must still load the next page server-side (it replaces the page rather than appending when JS is off).
48
52
  - Clear both `before` and `after` whenever filters or sorting change so the new browse state starts from its first page.
@@ -75,6 +79,10 @@ Use `parseCollectionParams(searchParams)` before Storefront API queries. Pass pa
75
79
 
76
80
  - Filtering and sorting update the URL without scroll reset when hydrated.
77
81
  - Checkbox filters and `PRICE_RANGE` min/max controls both render, apply, and persist after reloading the URL.
82
+ - Price inputs show the narrow symbol for the loaded currency (e.g. `$` for USD or CAD, `€`, `¥`), and the symbol does not change after hydration.
83
+ - Decimal bounds such as `10.5` apply, and the URL carries only numeric `filter.v.price.gte`/`filter.v.price.lte` values, with no symbol or currency code.
84
+ - The symbol prefix and input stay aligned and usable at desktop and mobile widths.
85
+ - Accessible names identify minimum or maximum and the currency code; screen readers do not announce the symbol prefix.
78
86
  - Reloading the filtered URL server-renders the same filtered state.
79
87
  - With JavaScript disabled, checking filters and submitting the form loads the filtered URL.
80
88
  - With JavaScript disabled, the load-more / pagination link loads the next page server-side.
@@ -73,7 +73,7 @@ export function CollectionBrowser(props: Props) {
73
73
 
74
74
  `router.refresh()` is important when the client URL changes before the React Server Component payload catches up. Under Partial Prerendering, a `<Link>` to a changed `searchParams` (active-filter chips, clear-all, load-more) can leave the server data stale — if it does, add `onClick={() => router.refresh()}` to those links (the `<Link>` stays the no-JS baseline).
75
75
 
76
- Build the actual `BrowserContent` controls from the React reference patterns, including its filter-group dispatch: `PRICE_RANGE` uses min/max inputs, while `LIST` and `BOOLEAN` use checkboxes. Checkbox names/values come from parsing `FilterValue.input` and passing that exact parsed filter through `serializeCollectionParams(...)`. Uncontrolled checkbox reset keys belong on the filter subtree keyed by serialized **filter state** (`serializeCollectionParams({filters: state.filters, sortKey: undefined, reverse: false}).toString()`), not the live URL — the URL clears before the `CollectionProvider` reconciler settles `state.filters`, so a URL-keyed remount bakes in stale `defaultChecked`.
76
+ Build the actual `BrowserContent` controls from the React reference patterns, including its filter-group dispatch: `PRICE_RANGE` uses min/max inputs, while `LIST` and `BOOLEAN` use checkboxes. Checkbox names/values come from parsing `FilterValue.input` and passing that exact parsed filter through `serializeCollectionParams(...)`. Keep filter controls mounted and enabled while loading. Bind checkbox `checked` and sort `value` to browse state, and sync price inputs through refs. Keep the filter subtree key stable across filter updates, so focused controls stay mounted.
77
77
 
78
78
  ```tsx
79
79
  function filterValueInputParamEntries(input: string): Array<{ name: string; value: string }> {
@@ -95,7 +95,7 @@ const isLoading = computed(() => state.value.status === "loading");
95
95
  </template>
96
96
  ```
97
97
 
98
- Apply form helper props with `v-bind`. Branch on `filter.type`: render min/max number inputs for `PRICE_RANGE`, and value checkboxes for `LIST` and `BOOLEAN`. Submit price inputs on blur only when the value changed; let Enter blur the input.
98
+ Apply form helper props with `v-bind`. Branch on `filter.type`: render min/max number inputs for `PRICE_RANGE`, and value checkboxes for `LIST` and `BOOLEAN`. Submit price inputs on change with a 350 ms debounce. Share one timer between the min and max inputs, clear any pending timer before scheduling a submission, and clear the timer when the component unmounts. Preserve native Enter behavior, and cancel the pending timer whenever the form submits. Submit checkboxes and the sort `<select>` immediately on change. Keep all filter controls mounted and enabled while `isLoading` is true, and use it for a pending visual state. Bind `:checked` and the select `:value` to browse state, and keep `:key` values stable across filter updates, so external changes update the controls in place.
99
99
 
100
100
  ## Search Pages
101
101