@shopify/hydrogen 2026.10.0-preview.2 → 2026.10.0-preview.4

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 (404) hide show
  1. package/README.md +34 -3
  2. package/dist/cli/index.mjs +578 -85
  3. package/dist/client/client.mjs +2 -2
  4. package/dist/client/errors.d.mts +19 -19
  5. package/dist/client/errors.mjs +19 -16
  6. package/dist/client/errors.mjs.map +1 -1
  7. package/dist/client/types.d.mts +91 -10
  8. package/dist/core/analytics/cart-tracker.d.mts +14 -0
  9. package/dist/core/analytics/cart-tracker.mjs +15 -1
  10. package/dist/core/analytics/cart-tracker.mjs.map +1 -1
  11. package/dist/core/analytics/custom-consent.mjs +12 -0
  12. package/dist/core/analytics/custom-consent.mjs.map +1 -0
  13. package/dist/core/analytics/events.d.mts +7 -0
  14. package/dist/core/analytics/events.mjs +7 -0
  15. package/dist/core/analytics/events.mjs.map +1 -1
  16. package/dist/core/analytics/index.d.mts +1 -1
  17. package/dist/core/analytics/types.d.mts +152 -16
  18. package/dist/core/cart/actions.d.mts +47 -3
  19. package/dist/core/cart/actions.mjs +29 -0
  20. package/dist/core/cart/actions.mjs.map +1 -1
  21. package/dist/core/cart/attach-quantity-input.d.mts +26 -0
  22. package/dist/core/cart/attach-quantity-input.mjs +26 -0
  23. package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
  24. package/dist/core/cart/cart.d.mts +106 -6
  25. package/dist/core/cart/cart.mjs +57 -6
  26. package/dist/core/cart/cart.mjs.map +1 -1
  27. package/dist/core/cart/cookie.d.mts +15 -0
  28. package/dist/core/cart/cookie.mjs +15 -0
  29. package/dist/core/cart/cookie.mjs.map +1 -1
  30. package/dist/core/cart/form.d.mts +61 -0
  31. package/dist/core/cart/form.mjs +18 -0
  32. package/dist/core/cart/form.mjs.map +1 -1
  33. package/dist/core/cart/get-cart.d.mts +43 -2
  34. package/dist/core/cart/get-cart.mjs +33 -0
  35. package/dist/core/cart/get-cart.mjs.map +1 -1
  36. package/dist/core/cart/index.d.mts +1 -1
  37. package/dist/core/cart/quantity.d.mts +17 -0
  38. package/dist/core/cart/quantity.mjs +17 -0
  39. package/dist/core/cart/quantity.mjs.map +1 -1
  40. package/dist/core/cart/queries.d.mts +402 -369
  41. package/dist/core/cart/queries.mjs +2 -1
  42. package/dist/core/cart/queries.mjs.map +1 -1
  43. package/dist/core/cart/server-handlers.d.mts +69 -0
  44. package/dist/core/cart/server-handlers.mjs +2 -10
  45. package/dist/core/cart/server-handlers.mjs.map +1 -1
  46. package/dist/core/cart/state.d.mts +133 -0
  47. package/dist/core/cart/state.mjs +8 -0
  48. package/dist/core/cart/state.mjs.map +1 -1
  49. package/dist/core/collection/collection.d.mts +18 -2
  50. package/dist/core/collection/collection.mjs +11 -0
  51. package/dist/core/collection/collection.mjs.map +1 -1
  52. package/dist/core/collection/index.d.mts +1 -1
  53. package/dist/core/collection/reconciler.d.mts +13 -4
  54. package/dist/core/collection/reconciler.mjs.map +1 -1
  55. package/dist/core/collection/state.d.mts +7 -2
  56. package/dist/core/collection/state.mjs.map +1 -1
  57. package/dist/core/collection/url.d.mts +3 -0
  58. package/dist/core/collection/url.mjs.map +1 -1
  59. package/dist/core/constants.d.mts +1 -1
  60. package/dist/core/constants.mjs +2 -2
  61. package/dist/core/constants.mjs.map +1 -1
  62. package/dist/core/headers.mjs +2 -2
  63. package/dist/core/headers.mjs.map +1 -1
  64. package/dist/core/index.d.mts +5 -5
  65. package/dist/core/logging/logging.mjs.map +1 -1
  66. package/dist/core/money/format.mjs +6 -3
  67. package/dist/core/money/format.mjs.map +1 -1
  68. package/dist/core/observable.mjs +0 -3
  69. package/dist/core/observable.mjs.map +1 -1
  70. package/dist/core/predictive-search/form.d.mts +1 -0
  71. package/dist/core/predictive-search/form.mjs +1 -0
  72. package/dist/core/predictive-search/form.mjs.map +1 -1
  73. package/dist/core/predictive-search/index.d.mts +1 -1
  74. package/dist/core/predictive-search/queries.d.mts +40 -4
  75. package/dist/core/predictive-search/queries.mjs +7 -1
  76. package/dist/core/predictive-search/queries.mjs.map +1 -1
  77. package/dist/core/predictive-search/search.d.mts +43 -10
  78. package/dist/core/predictive-search/search.mjs +34 -0
  79. package/dist/core/predictive-search/search.mjs.map +1 -1
  80. package/dist/core/predictive-search/server-handlers.d.mts +31 -5
  81. package/dist/core/predictive-search/server-handlers.mjs +5 -3
  82. package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
  83. package/dist/core/predictive-search/store.d.mts +3 -1
  84. package/dist/core/predictive-search/store.mjs.map +1 -1
  85. package/dist/core/predictive-search/url.d.mts +50 -8
  86. package/dist/core/predictive-search/url.mjs +9 -0
  87. package/dist/core/predictive-search/url.mjs.map +1 -1
  88. package/dist/core/product/form.d.mts +21 -2
  89. package/dist/core/product/form.mjs +13 -0
  90. package/dist/core/product/form.mjs.map +1 -1
  91. package/dist/core/product/index.d.mts +1 -1
  92. package/dist/core/product/options.d.mts +2 -2
  93. package/dist/core/product/options.mjs +2 -2
  94. package/dist/core/product/options.mjs.map +1 -1
  95. package/dist/core/product/product-form.d.mts +33 -1
  96. package/dist/core/product/product-form.mjs +30 -0
  97. package/dist/core/product/product-form.mjs.map +1 -1
  98. package/dist/core/product/state.d.mts +41 -0
  99. package/dist/core/product/url.d.mts +6 -0
  100. package/dist/core/product/url.mjs.map +1 -1
  101. package/dist/core/request-context.d.mts +8 -5
  102. package/dist/core/request-context.mjs +17 -39
  103. package/dist/core/request-context.mjs.map +1 -1
  104. package/dist/core/request-routing/handle-shopify-routes.mjs +4 -1
  105. package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  106. package/dist/core/request-routing/interceptors/ajax-api.mjs +1 -1
  107. package/dist/core/request-routing/interceptors/api-proxy.mjs +1 -1
  108. package/dist/core/request-routing/interceptors/checkout.mjs +22 -6
  109. package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -1
  110. package/dist/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
  111. package/dist/core/request-routing/interceptors/proxy.mjs +36 -10
  112. package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
  113. package/dist/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
  114. package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
  115. package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
  116. package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
  117. package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
  118. package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
  119. package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
  120. package/dist/core/shopify-scripts/analytics.mjs +2 -2
  121. package/dist/core/shopify-scripts/analytics.mjs.map +1 -1
  122. package/dist/core/shopify-scripts/constants.mjs +4 -2
  123. package/dist/core/shopify-scripts/constants.mjs.map +1 -1
  124. package/dist/core/shopify-scripts/global.mjs +9 -4
  125. package/dist/core/shopify-scripts/global.mjs.map +1 -1
  126. package/dist/core/shopify-scripts/index.d.mts +1 -0
  127. package/dist/core/shopify-scripts/index.mjs +14 -12
  128. package/dist/core/shopify-scripts/index.mjs.map +1 -1
  129. package/dist/core/shopify-scripts/initialize.d.mts +1 -0
  130. package/dist/core/shopify-scripts/initialize.mjs +10 -3
  131. package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
  132. package/dist/core/shopify-scripts/types.d.mts +6 -0
  133. package/dist/core/url.mjs +21 -2
  134. package/dist/core/url.mjs.map +1 -1
  135. package/dist/customer-account/client.d.mts +128 -4
  136. package/dist/customer-account/client.mjs +27 -1
  137. package/dist/customer-account/client.mjs.map +1 -1
  138. package/dist/customer-account/errors.d.mts +46 -0
  139. package/dist/customer-account/errors.mjs +46 -0
  140. package/dist/customer-account/errors.mjs.map +1 -1
  141. package/dist/customer-account/graphql.d.mts +68 -0
  142. package/dist/customer-account/graphql.mjs +47 -0
  143. package/dist/customer-account/graphql.mjs.map +1 -1
  144. package/dist/customer-account/session.d.mts +114 -23
  145. package/dist/customer-account/session.mjs +27 -12
  146. package/dist/customer-account/session.mjs.map +1 -1
  147. package/dist/customer-account/type-resolver.d.mts +23 -0
  148. package/dist/customer-account-api-types.d.ts +570 -374
  149. package/dist/customer-account.schema.json +2981 -1527
  150. package/dist/development/client/client.mjs +2 -2
  151. package/dist/development/client/errors.d.mts +19 -19
  152. package/dist/development/client/errors.mjs +20 -20
  153. package/dist/development/client/errors.mjs.map +1 -1
  154. package/dist/development/client/types.d.mts +91 -10
  155. package/dist/development/core/analytics/cart-tracker.d.mts +14 -0
  156. package/dist/development/core/analytics/cart-tracker.mjs +15 -1
  157. package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
  158. package/dist/development/core/analytics/custom-consent.mjs +12 -0
  159. package/dist/development/core/analytics/custom-consent.mjs.map +1 -0
  160. package/dist/development/core/analytics/events.d.mts +7 -0
  161. package/dist/development/core/analytics/events.mjs +7 -0
  162. package/dist/development/core/analytics/events.mjs.map +1 -1
  163. package/dist/development/core/analytics/index.d.mts +1 -1
  164. package/dist/development/core/analytics/types.d.mts +152 -16
  165. package/dist/development/core/cart/actions.d.mts +47 -3
  166. package/dist/development/core/cart/actions.mjs +29 -0
  167. package/dist/development/core/cart/actions.mjs.map +1 -1
  168. package/dist/development/core/cart/attach-quantity-input.d.mts +26 -0
  169. package/dist/development/core/cart/attach-quantity-input.mjs +26 -0
  170. package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
  171. package/dist/development/core/cart/cart.d.mts +106 -6
  172. package/dist/development/core/cart/cart.mjs +57 -6
  173. package/dist/development/core/cart/cart.mjs.map +1 -1
  174. package/dist/development/core/cart/cookie.d.mts +15 -0
  175. package/dist/development/core/cart/cookie.mjs +15 -0
  176. package/dist/development/core/cart/cookie.mjs.map +1 -1
  177. package/dist/development/core/cart/form.d.mts +61 -0
  178. package/dist/development/core/cart/form.mjs +18 -0
  179. package/dist/development/core/cart/form.mjs.map +1 -1
  180. package/dist/development/core/cart/get-cart.d.mts +43 -2
  181. package/dist/development/core/cart/get-cart.mjs +33 -0
  182. package/dist/development/core/cart/get-cart.mjs.map +1 -1
  183. package/dist/development/core/cart/index.d.mts +1 -1
  184. package/dist/development/core/cart/quantity.d.mts +17 -0
  185. package/dist/development/core/cart/quantity.mjs +17 -0
  186. package/dist/development/core/cart/quantity.mjs.map +1 -1
  187. package/dist/development/core/cart/queries.d.mts +345 -312
  188. package/dist/development/core/cart/queries.mjs +2 -1
  189. package/dist/development/core/cart/queries.mjs.map +1 -1
  190. package/dist/development/core/cart/server-handlers.d.mts +69 -0
  191. package/dist/development/core/cart/server-handlers.mjs +2 -10
  192. package/dist/development/core/cart/server-handlers.mjs.map +1 -1
  193. package/dist/development/core/cart/state.d.mts +133 -0
  194. package/dist/development/core/cart/state.mjs +8 -0
  195. package/dist/development/core/cart/state.mjs.map +1 -1
  196. package/dist/development/core/collection/collection.d.mts +18 -2
  197. package/dist/development/core/collection/collection.mjs +11 -0
  198. package/dist/development/core/collection/collection.mjs.map +1 -1
  199. package/dist/development/core/collection/index.d.mts +1 -1
  200. package/dist/development/core/collection/reconciler.d.mts +13 -4
  201. package/dist/development/core/collection/reconciler.mjs.map +1 -1
  202. package/dist/development/core/collection/state.d.mts +7 -2
  203. package/dist/development/core/collection/state.mjs.map +1 -1
  204. package/dist/development/core/collection/url.d.mts +3 -0
  205. package/dist/development/core/collection/url.mjs.map +1 -1
  206. package/dist/development/core/constants.mjs +1 -1
  207. package/dist/development/core/constants.mjs.map +1 -1
  208. package/dist/development/core/development.d.mts +5 -5
  209. package/dist/development/core/headers.mjs +2 -2
  210. package/dist/development/core/headers.mjs.map +1 -1
  211. package/dist/development/core/index.d.mts +4 -4
  212. package/dist/development/core/logging/logging.mjs.map +1 -1
  213. package/dist/development/core/money/format.mjs +6 -3
  214. package/dist/development/core/money/format.mjs.map +1 -1
  215. package/dist/development/core/observable.mjs +0 -3
  216. package/dist/development/core/observable.mjs.map +1 -1
  217. package/dist/development/core/predictive-search/form.d.mts +1 -0
  218. package/dist/development/core/predictive-search/form.mjs +1 -0
  219. package/dist/development/core/predictive-search/form.mjs.map +1 -1
  220. package/dist/development/core/predictive-search/index.d.mts +1 -1
  221. package/dist/development/core/predictive-search/queries.d.mts +38 -2
  222. package/dist/development/core/predictive-search/queries.mjs +7 -1
  223. package/dist/development/core/predictive-search/queries.mjs.map +1 -1
  224. package/dist/development/core/predictive-search/search.d.mts +43 -10
  225. package/dist/development/core/predictive-search/search.mjs +34 -0
  226. package/dist/development/core/predictive-search/search.mjs.map +1 -1
  227. package/dist/development/core/predictive-search/server-handlers.d.mts +31 -5
  228. package/dist/development/core/predictive-search/server-handlers.mjs +5 -3
  229. package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
  230. package/dist/development/core/predictive-search/store.d.mts +3 -1
  231. package/dist/development/core/predictive-search/store.mjs.map +1 -1
  232. package/dist/development/core/predictive-search/url.d.mts +50 -8
  233. package/dist/development/core/predictive-search/url.mjs +9 -0
  234. package/dist/development/core/predictive-search/url.mjs.map +1 -1
  235. package/dist/development/core/product/form.d.mts +21 -2
  236. package/dist/development/core/product/form.mjs +13 -0
  237. package/dist/development/core/product/form.mjs.map +1 -1
  238. package/dist/development/core/product/index.d.mts +1 -1
  239. package/dist/development/core/product/options.d.mts +2 -2
  240. package/dist/development/core/product/options.mjs +2 -2
  241. package/dist/development/core/product/options.mjs.map +1 -1
  242. package/dist/development/core/product/product-form.d.mts +33 -1
  243. package/dist/development/core/product/product-form.mjs +30 -0
  244. package/dist/development/core/product/product-form.mjs.map +1 -1
  245. package/dist/development/core/product/state.d.mts +41 -0
  246. package/dist/development/core/product/url.d.mts +6 -0
  247. package/dist/development/core/product/url.mjs.map +1 -1
  248. package/dist/development/core/request-context.d.mts +8 -5
  249. package/dist/development/core/request-context.mjs +17 -39
  250. package/dist/development/core/request-context.mjs.map +1 -1
  251. package/dist/development/core/request-routing/handle-shopify-routes.mjs +4 -1
  252. package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  253. package/dist/development/core/request-routing/interceptors/ajax-api.mjs +1 -1
  254. package/dist/development/core/request-routing/interceptors/api-proxy.mjs +1 -1
  255. package/dist/development/core/request-routing/interceptors/checkout.mjs +22 -6
  256. package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -1
  257. package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
  258. package/dist/development/core/request-routing/interceptors/proxy.mjs +36 -10
  259. package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
  260. package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
  261. package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
  262. package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
  263. package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
  264. package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
  265. package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
  266. package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
  267. package/dist/development/core/shopify-scripts/analytics.mjs +2 -2
  268. package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -1
  269. package/dist/development/core/shopify-scripts/constants.mjs +4 -2
  270. package/dist/development/core/shopify-scripts/constants.mjs.map +1 -1
  271. package/dist/development/core/shopify-scripts/global.mjs +9 -4
  272. package/dist/development/core/shopify-scripts/global.mjs.map +1 -1
  273. package/dist/development/core/shopify-scripts/index.d.mts +1 -0
  274. package/dist/development/core/shopify-scripts/index.mjs +14 -12
  275. package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
  276. package/dist/development/core/shopify-scripts/initialize.d.mts +1 -0
  277. package/dist/development/core/shopify-scripts/initialize.mjs +10 -3
  278. package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
  279. package/dist/development/core/shopify-scripts/types.d.mts +6 -0
  280. package/dist/development/core/url.mjs +21 -2
  281. package/dist/development/core/url.mjs.map +1 -1
  282. package/dist/development/customer-account/session.d.mts +69 -13
  283. package/dist/development/customer-account/session.mjs +4 -0
  284. package/dist/development/customer-account/session.mjs.map +1 -1
  285. package/dist/development/globals.d.mts +6 -5
  286. package/dist/development/graphql/generated/graphql-env.d.mts +459 -16
  287. package/dist/development/graphql/generated/storefront-api-types.d.mts +1 -1
  288. package/dist/development/graphql/graphql.d.mts +33 -0
  289. package/dist/development/graphql/graphql.mjs +30 -0
  290. package/dist/development/graphql/graphql.mjs.map +1 -1
  291. package/dist/development/graphql/type-resolver.d.mts +14 -0
  292. package/dist/development/react/cart.d.mts +85 -14
  293. package/dist/development/react/cart.mjs +123 -3
  294. package/dist/development/react/cart.mjs.map +1 -1
  295. package/dist/development/react/collection.d.mts +2 -4
  296. package/dist/development/react/collection.mjs.map +1 -1
  297. package/dist/development/react/index.d.mts +8 -6
  298. package/dist/development/react/index.mjs +2 -2
  299. package/dist/development/react/predictive-search.d.mts +68 -7
  300. package/dist/development/react/predictive-search.mjs +54 -12
  301. package/dist/development/react/predictive-search.mjs.map +1 -1
  302. package/dist/development/react/product.d.mts +1 -0
  303. package/dist/development/react/product.mjs +5 -2
  304. package/dist/development/react/product.mjs.map +1 -1
  305. package/dist/development/react/shopify-scripts.mjs +1 -0
  306. package/dist/development/react/shopify-scripts.mjs.map +1 -1
  307. package/dist/development/vue/cart.d.mts +85 -6
  308. package/dist/development/vue/cart.mjs +95 -0
  309. package/dist/development/vue/cart.mjs.map +1 -1
  310. package/dist/development/vue/collection.d.mts +83 -5
  311. package/dist/development/vue/collection.mjs +52 -0
  312. package/dist/development/vue/collection.mjs.map +1 -1
  313. package/dist/development/vue/index.d.mts +6 -4
  314. package/dist/development/vue/predictive-search.d.mts +53 -6
  315. package/dist/development/vue/predictive-search.mjs +33 -0
  316. package/dist/development/vue/predictive-search.mjs.map +1 -1
  317. package/dist/development/vue/product.d.mts +6 -2
  318. package/dist/development/vue/product.mjs.map +1 -1
  319. package/dist/development/vue/shopify-scripts.d.mts +30 -3
  320. package/dist/development/vue/shopify-scripts.mjs +6 -0
  321. package/dist/development/vue/shopify-scripts.mjs.map +1 -1
  322. package/dist/globals.d.mts +6 -5
  323. package/dist/graphql/generated/customer-account-graphql-env.d.mts +1717 -1090
  324. package/dist/graphql/generated/graphql-env.d.mts +459 -16
  325. package/dist/graphql/generated/storefront-api-types.d.mts +1 -1
  326. package/dist/graphql/graphql.d.mts +33 -0
  327. package/dist/graphql/graphql.mjs +30 -0
  328. package/dist/graphql/graphql.mjs.map +1 -1
  329. package/dist/graphql/type-resolver.d.mts +14 -0
  330. package/dist/react/cart.d.mts +85 -14
  331. package/dist/react/cart.mjs +123 -3
  332. package/dist/react/cart.mjs.map +1 -1
  333. package/dist/react/collection.d.mts +2 -4
  334. package/dist/react/collection.mjs.map +1 -1
  335. package/dist/react/index.d.mts +8 -6
  336. package/dist/react/index.mjs +2 -2
  337. package/dist/react/predictive-search.d.mts +68 -7
  338. package/dist/react/predictive-search.mjs +54 -12
  339. package/dist/react/predictive-search.mjs.map +1 -1
  340. package/dist/react/product.d.mts +1 -0
  341. package/dist/react/product.mjs +5 -2
  342. package/dist/react/product.mjs.map +1 -1
  343. package/dist/react/shopify-scripts.mjs +1 -0
  344. package/dist/react/shopify-scripts.mjs.map +1 -1
  345. package/dist/storefront-api-types.d.ts +165 -20
  346. package/dist/storefront.schema.json +782 -57
  347. package/dist/vite/customer-account.mjs +191 -0
  348. package/dist/vite/customer-account.mjs.map +1 -0
  349. package/dist/vite/local-https.mjs +13 -29
  350. package/dist/vite/local-https.mjs.map +1 -1
  351. package/dist/vite/mkcert.mjs +1 -1
  352. package/dist/vue/cart.d.mts +85 -6
  353. package/dist/vue/cart.mjs +95 -0
  354. package/dist/vue/cart.mjs.map +1 -1
  355. package/dist/vue/collection.d.mts +83 -5
  356. package/dist/vue/collection.mjs +52 -0
  357. package/dist/vue/collection.mjs.map +1 -1
  358. package/dist/vue/index.d.mts +6 -4
  359. package/dist/vue/predictive-search.d.mts +53 -6
  360. package/dist/vue/predictive-search.mjs +33 -0
  361. package/dist/vue/predictive-search.mjs.map +1 -1
  362. package/dist/vue/product.d.mts +6 -2
  363. package/dist/vue/product.mjs.map +1 -1
  364. package/dist/vue/shopify-scripts.d.mts +31 -4
  365. package/dist/vue/shopify-scripts.mjs +6 -0
  366. package/dist/vue/shopify-scripts.mjs.map +1 -1
  367. package/package.json +5 -2
  368. package/skills/hydrogen-analytics/SKILL.md +6 -5
  369. package/skills/hydrogen-analytics/references/custom-consent.md +52 -0
  370. package/skills/hydrogen-analytics/references/react.md +1 -1
  371. package/skills/hydrogen-cart-metafields/SKILL.md +133 -0
  372. package/skills/hydrogen-cart-ui/SKILL.md +2 -1
  373. package/skills/hydrogen-customer-account/SKILL.md +3 -3
  374. package/skills/hydrogen-local-https/SKILL.md +22 -7
  375. package/skills/hydrogen-request-handlers/SKILL.md +5 -5
  376. package/skills/hydrogen-request-handlers/references/nuxt.md +0 -6
  377. package/skills/hydrogen-setup/references/analytics.md +24 -23
  378. package/skills/hydrogen-setup/steps/1-inspect-app.md +1 -1
  379. package/skills/hydrogen-setup/steps/11-verify.md +1 -0
  380. package/skills/hydrogen-setup/steps/2-scaffold.md +22 -1
  381. package/skills/hydrogen-smoke-test/SKILL.md +2 -2
  382. package/skills/hydrogen-storefront-client/SKILL.md +4 -2
  383. package/skills/hydrogen-variant-form/SKILL.md +10 -4
  384. package/ts-plugin/package.json +5 -0
  385. package/dist/core/shopify-scripts/consent.mjs +0 -17
  386. package/dist/core/shopify-scripts/consent.mjs.map +0 -1
  387. package/dist/core/shopify-scripts/deprecated-cookies.mjs +0 -97
  388. package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
  389. package/dist/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
  390. package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
  391. package/dist/core/shopify-scripts/utils/tracking-values.mjs +0 -65
  392. package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
  393. package/dist/core/shopify-scripts/utils/uuid.mjs +0 -17
  394. package/dist/core/shopify-scripts/utils/uuid.mjs.map +0 -1
  395. package/dist/development/core/shopify-scripts/consent.mjs +0 -17
  396. package/dist/development/core/shopify-scripts/consent.mjs.map +0 -1
  397. package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +0 -97
  398. package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
  399. package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
  400. package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
  401. package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +0 -65
  402. package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
  403. package/dist/development/core/shopify-scripts/utils/uuid.mjs +0 -17
  404. package/dist/development/core/shopify-scripts/utils/uuid.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cart.mjs","names":[],"sources":["../../src/vue/cart.ts"],"sourcesContent":["import {\n computed,\n defineComponent,\n h,\n inject,\n onMounted,\n onScopeDispose,\n onUnmounted,\n provide,\n shallowRef,\n type ComputedRef,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport { trackCartAnalytics } from \"../core/analytics/cart-tracker\";\nimport {\n configureCartEndpoint as configureCoreCartEndpoint,\n createCartStore,\n type CreateCartStoreOptions,\n type CartStore,\n} from \"../core/cart/cart\";\nimport { createCartFormRegister, type CartFormRegister } from \"../core/cart/form\";\nimport type { CartDataFromHandlers } from \"../core/cart/server-handlers\";\nimport type { CartData, CartState } from \"../core/cart/state\";\n\nconst DEFAULT_CART_ENDPOINT = \"/api/cart\";\n\nlet cartEndpoint = DEFAULT_CART_ENDPOINT;\n\nconst CartStoreKey: InjectionKey<CartStore> = Symbol(\"CartStore\");\n\nexport function configureCartEndpoint(endpoint: string): void {\n cartEndpoint = endpoint;\n configureCoreCartEndpoint(endpoint);\n}\n\nexport function getCartEndpoint(): string {\n return cartEndpoint;\n}\n\ntype TypedUseCart<TData extends CartData> = {\n (): Readonly<ShallowRef<CartState<TData>>>;\n <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): Readonly<ShallowRef<S>>;\n};\n\ntype TypedUseOptionalCart<TData extends CartData> = <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n) => Readonly<ShallowRef<S | undefined>>;\n\ntype CartInitialData<TData extends CartData = CartData> =\n CreateCartStoreOptions<TData>[\"initialData\"];\n\ntype TypedCartProvider<TData extends CartData> = {\n new (): { $props: { initialData?: CartInitialData<TData> } };\n};\n\n/** Actions for reconciling cart state after updates outside Standard Actions. */\nexport type CartActions = Pick<CartStore, \"refresh\">;\n\ntype TypedCartComponents<TData extends CartData> = {\n CartProvider: TypedCartProvider<TData>;\n useCart: TypedUseCart<TData>;\n useOptionalCart: TypedUseOptionalCart<TData>;\n useCartActions: typeof useCartActions;\n useCartForm: typeof useCartForm;\n};\n\nfunction createTypedCartProvider<TData extends CartData>(): TypedCartProvider<TData> {\n const TypedCartProvider = defineComponent(\n (props: { initialData?: CartInitialData<TData> }, { slots }) => {\n return () =>\n h(\n CartProvider,\n { initialData: props.initialData as CartInitialData | undefined },\n slots.default,\n );\n },\n { name: \"CartProvider\", props: [\"initialData\"] },\n );\n\n return TypedCartProvider as unknown as TypedCartProvider<TData>;\n}\n\nexport function useCartStore(composableName = \"useCartStore\"): CartStore {\n const store = inject(CartStoreKey, null);\n if (!store) {\n throw new Error(`${composableName} must be used inside a <CartProvider>.`);\n }\n return store;\n}\n\nfunction useOptionalCartStore(): CartStore | null {\n return inject(CartStoreKey, null);\n}\n\nexport const CartProvider = defineComponent({\n name: \"CartProvider\",\n props: {\n initialData: {\n type: Object as PropType<CartInitialData>,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n const store = createCartStore({ initialData: props.initialData });\n provide(CartStoreKey, store);\n\n onMounted(() => {\n configureCoreCartEndpoint(cartEndpoint);\n store.connect();\n });\n\n onUnmounted(() => {\n store.destroy();\n });\n\n return () => slots.default?.();\n },\n});\n\nexport function useCart(): Readonly<ShallowRef<CartState>>;\nexport function useCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>>;\nexport function useCart<TData extends CartData = CartData, S = unknown>(\n selector?: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S | CartState>> {\n const store = useCartStore(\"useCart\");\n const resolve = selector ?? ((state: CartState<TData>) => state as unknown as S);\n return useCartSelector(store, resolve, isEqual) as Readonly<ShallowRef<S>>;\n}\n\nexport function useCartActions(): CartActions {\n const store = useCartStore(\"useCartActions\");\n return { refresh: store.refresh };\n}\n\nexport function useCartAnalytics(): void {\n const store = useCartStore(\"useCartAnalytics\");\n let stopTracking: (() => void) | undefined;\n\n onMounted(() => {\n stopTracking = trackCartAnalytics(store);\n });\n\n onScopeDispose(() => stopTracking?.());\n}\n\n/**\n * Like `useCart`, but returns `undefined` when rendered outside a `<CartProvider>`.\n * @internal\n */\nexport function useOptionalCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S | undefined>> {\n const store = useOptionalCartStore();\n if (!store) return shallowRef<S | undefined>(undefined);\n return useCartSelector(store, selector, isEqual);\n}\n\nfunction useCartSelector<TData extends CartData = CartData, S = unknown>(\n store: CartStore,\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>> {\n const selected = shallowRef<S>(selector(store.getState() as CartState<TData>));\n\n const unsubscribe = store.subscribe(() => {\n const next = selector(store.getState() as CartState<TData>);\n if (isEqual?.(selected.value, next)) return;\n selected.value = next;\n });\n\n onScopeDispose(unsubscribe);\n\n return selected as Readonly<ShallowRef<S>>;\n}\n\nexport function useCartForm(): {\n formProps: (opts?: {\n beforeSubmit?: (e: Event) => void;\n afterSubmit?: (e: Event) => void;\n }) => Record<string, unknown>;\n register: CartFormRegister;\n isPending: {\n initial: ComputedRef<boolean>;\n lines: (lineId?: string) => boolean;\n };\n} {\n const store = useCartStore(\"useCartForm\");\n const loading = useCart((s) => s.loading);\n const pendingLines = useCart((s) => s.pending.lines);\n const register = createCartFormRegister();\n\n const formProps = (opts?: {\n beforeSubmit?: (e: Event) => void;\n afterSubmit?: (e: Event) => void;\n }): Record<string, unknown> => ({\n onSubmit: (e: Event) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n store.handleFormSubmit(e as SubmitEvent).catch(() => {});\n opts?.afterSubmit?.(e);\n },\n method: \"post\",\n action: cartEndpoint,\n });\n\n const isPending = {\n initial: computed(() => loading.value),\n lines: (lineId?: string): boolean =>\n lineId ? pendingLines.value.has(lineId) : pendingLines.value.size > 0,\n };\n\n return { formProps, register, isPending };\n}\n\nexport function createCartComponents<THandlers>(): TypedCartComponents<\n CartDataFromHandlers<THandlers>\n> {\n type TData = CartDataFromHandlers<THandlers>;\n const TypedCartProvider = createTypedCartProvider<TData>();\n\n const useTypedCart = ((\n selector?: (state: CartState<TData>) => unknown,\n isEqual?: (a: unknown, b: unknown) => boolean,\n ) => {\n if (!selector) return useCart() as Readonly<ShallowRef<CartState<TData>>>;\n return useCart<TData, unknown>(selector, isEqual);\n }) as TypedUseCart<TData>;\n\n const useTypedOptionalCart = (<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ) => useOptionalCart<TData, S>(selector, isEqual)) as TypedUseOptionalCart<TData>;\n\n return {\n CartProvider: TypedCartProvider,\n useCart: useTypedCart,\n useOptionalCart: useTypedOptionalCart,\n useCartActions,\n useCartForm,\n };\n}\n"],"mappings":";;;;;;AA6BA,IAAI,eAAe;AAEnB,MAAM,eAAwC,OAAO,YAAY;AAOjE,SAAgB,kBAA0B;AACxC,QAAO;;AAkCT,SAAS,0BAA4E;AAanF,QAZ0B,iBACvB,OAAiD,EAAE,YAAY;AAC9D,eACE,EACE,cACA,EAAE,aAAa,MAAM,aAA4C,EACjE,MAAM,QACP;IAEL;EAAE,MAAM;EAAgB,OAAO,CAAC,cAAc;EAAE,CAG1B;;AAG1B,SAAgB,aAAa,iBAAiB,gBAA2B;CACvE,MAAM,QAAQ,OAAO,cAAc,KAAK;AACxC,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,GAAG,eAAe,wCAAwC;AAE5E,QAAO;;AAGT,SAAS,uBAAyC;AAChD,QAAO,OAAO,cAAc,KAAK;;AAGnC,MAAa,eAAe,gBAAgB;CAC1C,MAAM;CACN,OAAO,EACL,aAAa;EACX,MAAM;EACN,SAAS;EACV,EACF;CACD,MAAM,OAAO,EAAE,SAAS;EACtB,MAAM,QAAQ,gBAAgB,EAAE,aAAa,MAAM,aAAa,CAAC;AACjE,UAAQ,cAAc,MAAM;AAE5B,kBAAgB;AACd,yBAA0B,aAAa;AACvC,SAAM,SAAS;IACf;AAEF,oBAAkB;AAChB,SAAM,SAAS;IACf;AAEF,eAAa,MAAM,WAAW;;CAEjC,CAAC;AAOF,SAAgB,QACd,UACA,SACqC;AAGrC,QAAO,gBAFO,aAAa,UAEC,EADZ,cAAc,UAA4B,QACnB,QAAQ;;AAGjD,SAAgB,iBAA8B;AAE5C,QAAO,EAAE,SADK,aAAa,iBACJ,CAAC,SAAS;;AAGnC,SAAgB,mBAAyB;CACvC,MAAM,QAAQ,aAAa,mBAAmB;CAC9C,IAAI;AAEJ,iBAAgB;AACd,iBAAe,mBAAmB,MAAM;GACxC;AAEF,sBAAqB,gBAAgB,CAAC;;;;;;AAOxC,SAAgB,gBACd,UACA,SACqC;CACrC,MAAM,QAAQ,sBAAsB;AACpC,KAAI,CAAC,MAAO,QAAO,WAA0B,OAAU;AACvD,QAAO,gBAAgB,OAAO,UAAU,QAAQ;;AAGlD,SAAS,gBACP,OACA,UACA,SACyB;CACzB,MAAM,WAAW,WAAc,SAAS,MAAM,UAAU,CAAqB,CAAC;AAQ9E,gBANoB,MAAM,gBAAgB;EACxC,MAAM,OAAO,SAAS,MAAM,UAAU,CAAqB;AAC3D,MAAI,UAAU,SAAS,OAAO,KAAK,CAAE;AACrC,WAAS,QAAQ;GAGO,CAAC;AAE3B,QAAO;;AAGT,SAAgB,cAUd;CACA,MAAM,QAAQ,aAAa,cAAc;CACzC,MAAM,UAAU,SAAS,MAAM,EAAE,QAAQ;CACzC,MAAM,eAAe,SAAS,MAAM,EAAE,QAAQ,MAAM;CACpD,MAAM,WAAW,wBAAwB;CAEzC,MAAM,aAAa,UAGa;EAC9B,WAAW,MAAa;AACtB,SAAM,eAAe,EAAE;AACvB,OAAI,EAAE,iBAAkB;AACxB,KAAE,gBAAgB;AAClB,SAAM,iBAAiB,EAAiB,CAAC,YAAY,GAAG;AACxD,SAAM,cAAc,EAAE;;EAExB,QAAQ;EACR,QAAQ;EACT;AAQD,QAAO;EAAE;EAAW;EAAU;GAL5B,SAAS,eAAe,QAAQ,MAAM;GACtC,QAAQ,WACN,SAAS,aAAa,MAAM,IAAI,OAAO,GAAG,aAAa,MAAM,OAAO;GAGjC;EAAE;;AAG3C,SAAgB,uBAEd;CAEA,MAAM,oBAAoB,yBAAgC;CAE1D,MAAM,iBACJ,UACA,YACG;AACH,MAAI,CAAC,SAAU,QAAO,SAAS;AAC/B,SAAO,QAAwB,UAAU,QAAQ;;CAGnD,MAAM,yBACJ,UACA,YACG,gBAA0B,UAAU,QAAQ;AAEjD,QAAO;EACL,cAAc;EACd,SAAS;EACT,iBAAiB;EACjB;EACA;EACD"}
1
+ {"version":3,"file":"cart.mjs","names":[],"sources":["../../src/vue/cart.ts"],"sourcesContent":["import {\n computed,\n defineComponent,\n h,\n inject,\n onMounted,\n onScopeDispose,\n onUnmounted,\n provide,\n shallowRef,\n type ComputedRef,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport { trackCartAnalytics } from \"../core/analytics/cart-tracker\";\nimport {\n configureCartEndpoint as configureCoreCartEndpoint,\n createCartStore,\n type CreateCartStoreOptions,\n type CartActions,\n type CartStore,\n} from \"../core/cart/cart\";\nimport { createCartFormRegister, type CartFormRegister } from \"../core/cart/form\";\nimport type { CartDataFromHandlers } from \"../core/cart/server-handlers\";\nimport type { CartData, CartState } from \"../core/cart/state\";\n\nconst DEFAULT_CART_ENDPOINT = \"/api/cart\";\n\nlet cartEndpoint = DEFAULT_CART_ENDPOINT;\n\nconst CartStoreKey: InjectionKey<CartStore> = Symbol(\"CartStore\");\n\nexport function configureCartEndpoint(endpoint: string): void {\n cartEndpoint = endpoint;\n configureCoreCartEndpoint(endpoint);\n}\n\nexport function getCartEndpoint(): string {\n return cartEndpoint;\n}\n\ntype TypedUseCart<TData extends CartData> = {\n (): Readonly<ShallowRef<CartState<TData>>>;\n <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): Readonly<ShallowRef<S>>;\n};\n\ntype TypedUseOptionalCart<TData extends CartData> = <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n) => Readonly<ShallowRef<S | undefined>>;\n\ntype CartInitialData<TData extends CartData = CartData> =\n CreateCartStoreOptions<TData>[\"initialData\"];\n\ntype TypedCartProvider<TData extends CartData> = {\n new (): { $props: { initialData?: CartInitialData<TData> } };\n};\n\nexport type { CartActions };\n\ntype TypedCartComponents<TData extends CartData> = {\n CartProvider: TypedCartProvider<TData>;\n useCart: TypedUseCart<TData>;\n useOptionalCart: TypedUseOptionalCart<TData>;\n useCartActions: typeof useCartActions;\n useCartForm: typeof useCartForm;\n};\n\nfunction createTypedCartProvider<TData extends CartData>(): TypedCartProvider<TData> {\n const TypedCartProvider = defineComponent(\n (props: { initialData?: CartInitialData<TData> }, { slots }) => {\n return () =>\n h(\n CartProvider,\n { initialData: props.initialData as CartInitialData | undefined },\n slots.default,\n );\n },\n { name: \"CartProvider\", props: [\"initialData\"] },\n );\n\n return TypedCartProvider as unknown as TypedCartProvider<TData>;\n}\n\nexport function useCartStore(composableName = \"useCartStore\"): CartStore {\n const store = inject(CartStoreKey, null);\n if (!store) {\n throw new Error(`${composableName} must be used inside a <CartProvider>.`);\n }\n return store;\n}\n\nfunction useOptionalCartStore(): CartStore | null {\n return inject(CartStoreKey, null);\n}\n\n/**\n * Vue component that creates and manages a {@link CartStore} instance.\n *\n * Calls {@link CartStore.connect} on mount and {@link CartStore.destroy} on unmount.\n * All cart composables (`useCart`, `useCartForm`, `useCartActions`) must be\n * descendants of this component.\n *\n * @example\n * ```vue\n * <CartProvider :initialData=\"{ cart: loaderData.cart }\">\n * <App />\n * </CartProvider>\n * ```\n */\nexport const CartProvider = defineComponent({\n name: \"CartProvider\",\n props: {\n initialData: {\n type: Object as PropType<CartInitialData>,\n default: undefined,\n },\n },\n setup(props, { slots }) {\n const store = createCartStore({ initialData: props.initialData });\n provide(CartStoreKey, store);\n\n onMounted(() => {\n configureCoreCartEndpoint(cartEndpoint);\n store.connect();\n });\n\n onUnmounted(() => {\n store.destroy();\n });\n\n return () => slots.default?.();\n },\n});\n\n/**\n * Subscribes to {@link CartState} as a Vue `ShallowRef`.\n *\n * Without a selector, returns the full `CartState`. With a selector, returns\n * a `ShallowRef` of the selected slice — the ref updates only when the\n * selected value changes (by reference, or by custom `isEqual`).\n *\n * @example\n * ```vue\n * <script setup>\n * // Full state\n * const state = useCart();\n *\n * // Selected slice — re-renders only when lines change\n * const lines = useCart((s) => s.data.lines.nodes);\n *\n * // Check if a specific line is pending\n * const isPending = useCart((s) => s.pending.lines.has(lineId));\n * </script>\n * ```\n */\nexport function useCart(): Readonly<ShallowRef<CartState>>;\nexport function useCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>>;\nexport function useCart<TData extends CartData = CartData, S = unknown>(\n selector?: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S | CartState>> {\n const store = useCartStore(\"useCart\");\n const resolve = selector ?? ((state: CartState<TData>) => state as unknown as S);\n return useCartSelector(store, resolve, isEqual) as Readonly<ShallowRef<S>>;\n}\n\n/**\n * Returns cart actions for reconciling state after out-of-band mutations.\n *\n * Currently exposes {@link CartStore.refresh} — call it after server-side cart\n * mutations that bypass the form system.\n *\n * @example\n * ```vue\n * <script setup>\n * const { refresh } = useCartActions();\n *\n * async function handleServerAction() {\n * await createCartOnServer();\n * refresh();\n * }\n * </script>\n * ```\n */\nexport function useCartActions(): CartActions {\n const store = useCartStore(\"useCartActions\");\n return { refresh: store.refresh };\n}\n\n/**\n * Subscribes the {@link CartStore} to the analytics event dispatcher.\n *\n * Call once near the root of your app. Starts tracking on mount via `onMounted`\n * and cleans up via `onScopeDispose`.\n *\n * @example\n * ```vue\n * <script setup>\n * useCartAnalytics();\n * </script>\n * ```\n */\nexport function useCartAnalytics(): void {\n const store = useCartStore(\"useCartAnalytics\");\n let stopTracking: (() => void) | undefined;\n\n onMounted(() => {\n stopTracking = trackCartAnalytics(store);\n });\n\n onScopeDispose(() => stopTracking?.());\n}\n\n/**\n * Like `useCart`, but returns `undefined` when rendered outside a `<CartProvider>`.\n * @internal\n */\nexport function useOptionalCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S | undefined>> {\n const store = useOptionalCartStore();\n if (!store) return shallowRef<S | undefined>(undefined);\n return useCartSelector(store, selector, isEqual);\n}\n\nfunction useCartSelector<TData extends CartData = CartData, S = unknown>(\n store: CartStore,\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>> {\n const selected = shallowRef<S>(selector(store.getState() as CartState<TData>));\n\n const unsubscribe = store.subscribe(() => {\n const next = selector(store.getState() as CartState<TData>);\n if (isEqual?.(selected.value, next)) return;\n selected.value = next;\n });\n\n onScopeDispose(unsubscribe);\n\n return selected as Readonly<ShallowRef<S>>;\n}\n\n/**\n * Returns form props, a field register function, and reactive pending state\n * for building cart forms in Vue.\n *\n * Unlike the React hook, the Vue variant includes `isPending` — a reactive\n * object with `initial` (`ComputedRef<boolean>`) for the initial cart load\n * and `lines(lineId?)` for checking per-line or any-line pending state.\n *\n * Note: `interactive: true` returns numeric input attributes but does **not**\n * auto-submit on change — that behavior requires `attachQuantityInput`, which\n * is only wired by the React adapter.\n *\n * @example\n * ```vue\n * <script setup>\n * const { formProps, register, isPending } = useCartForm();\n * </script>\n *\n * <template>\n * <form v-bind=\"formProps()\">\n * <input v-bind=\"register('lineId', { value: line.id })\" />\n * <input v-bind=\"register('quantity', { value: line.quantity })\" />\n * <button v-bind=\"register('set')\" />\n * <button v-bind=\"register('increase')\">+</button>\n * <button v-bind=\"register('decrease')\">−</button>\n * <span v-if=\"isPending.lines(line.id)\">Updating…</span>\n * </form>\n * </template>\n * ```\n */\nexport function useCartForm(): {\n formProps: (opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }) => Record<string, unknown>;\n register: CartFormRegister;\n isPending: {\n initial: ComputedRef<boolean>;\n lines: (lineId?: string) => boolean;\n };\n} {\n const store = useCartStore(\"useCartForm\");\n const loading = useCart((s) => s.loading);\n const pendingLines = useCart((s) => s.pending.lines);\n const register = createCartFormRegister();\n\n const formProps = (opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }): Record<string, unknown> => ({\n onSubmit: (e: SubmitEvent) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n store.handleFormSubmit(e).catch(() => {});\n opts?.afterSubmit?.(e);\n },\n method: \"post\",\n action: cartEndpoint,\n });\n\n const isPending = {\n initial: computed(() => loading.value),\n lines: (lineId?: string): boolean =>\n lineId ? pendingLines.value.has(lineId) : pendingLines.value.size > 0,\n };\n\n return { formProps, register, isPending };\n}\n\n/**\n * Factory that returns typed cart components and composables matched to your\n * server handler's cart query shape.\n *\n * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`\n * call, so every composable's {@link CartState} carries your custom cart fields.\n *\n * @example\n * ```ts\n * import type { cartServerHandlers } from \"./server/cart.server\";\n *\n * const {\n * CartProvider,\n * useCart,\n * useOptionalCart,\n * useCartActions,\n * useCartForm,\n * } = createCartComponents<typeof cartServerHandlers>();\n * ```\n */\nexport function createCartComponents<THandlers>(): TypedCartComponents<\n CartDataFromHandlers<THandlers>\n> {\n type TData = CartDataFromHandlers<THandlers>;\n const TypedCartProvider = createTypedCartProvider<TData>();\n\n const useTypedCart = ((\n selector?: (state: CartState<TData>) => unknown,\n isEqual?: (a: unknown, b: unknown) => boolean,\n ) => {\n if (!selector) return useCart() as Readonly<ShallowRef<CartState<TData>>>;\n return useCart<TData, unknown>(selector, isEqual);\n }) as TypedUseCart<TData>;\n\n const useTypedOptionalCart = (<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ) => useOptionalCart<TData, S>(selector, isEqual)) as TypedUseOptionalCart<TData>;\n\n return {\n CartProvider: TypedCartProvider,\n useCart: useTypedCart,\n useOptionalCart: useTypedOptionalCart,\n useCartActions,\n useCartForm,\n };\n}\n"],"mappings":"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"}
@@ -1,10 +1,39 @@
1
1
  import { CollectionState } from "../core/collection/state.mjs";
2
- import { CollectionData, CollectionStore } from "../core/collection/collection.mjs";
2
+ import { CollectionActions, CollectionData } from "../core/collection/collection.mjs";
3
3
  import * as _$vue from "vue";
4
4
  import { PropType, ShallowRef } from "vue";
5
5
 
6
6
  //#region src/vue/collection.d.ts
7
- type CollectionActions = Pick<CollectionStore, "setFilters" | "toggleFilter" | "toggleFilterInput" | "setSortKey" | "setSortByValue" | "reset" | "handleFormSubmit">;
7
+ /**
8
+ * Manages the lifecycle of a {@link CollectionStore}: creates on mount and syncs
9
+ * with URL changes. Recreates the store when `data.handle` changes
10
+ * (navigating to a different collection).
11
+ *
12
+ * @example
13
+ * ```vue
14
+ * <script setup lang="ts">
15
+ * import { CollectionProvider, type CollectionData } from '@shopify/hydrogen/vue';
16
+ * import { useRoute, useRouter } from 'vue-router';
17
+ *
18
+ * const props = defineProps<{ data: CollectionData; urlSearch: string }>();
19
+ * const route = useRoute();
20
+ * const router = useRouter();
21
+ *
22
+ * // `search` includes the leading `?`, so append it to the path as-is.
23
+ * const onChange = (search: string) => router.replace(`${route.path}${search}`);
24
+ * </script>
25
+ *
26
+ * <template>
27
+ * <CollectionProvider
28
+ * :data="props.data"
29
+ * :url-search="props.urlSearch"
30
+ * @change="onChange"
31
+ * >
32
+ * <slot />
33
+ * </CollectionProvider>
34
+ * </template>
35
+ * ```
36
+ */
8
37
  declare const CollectionProvider: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
9
38
  data: {
10
39
  type: PropType<CollectionData>;
@@ -32,15 +61,64 @@ declare const CollectionProvider: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
32
61
  }>, {
33
62
  urlSearch: string;
34
63
  }, {}, {}, {}, string, _$vue.ComponentProvideOptions, true, {}, any>;
64
+ /**
65
+ * Subscribes to the collection store and returns a reactive ref of the full
66
+ * state snapshot.
67
+ *
68
+ * @example
69
+ * ```vue
70
+ * <script setup lang="ts">
71
+ * const state = useCollection();
72
+ * </script>
73
+ *
74
+ * <template>
75
+ * <p>{{ state.status }}</p>
76
+ * </template>
77
+ * ```
78
+ */
35
79
  declare function useCollection(): Readonly<ShallowRef<CollectionState>>;
80
+ /**
81
+ * Subscribes to the collection store and returns a reactive ref of a derived
82
+ * value via `selector`. Optionally accepts an `isEqual` comparator to skip
83
+ * updates when the derived value is structurally unchanged.
84
+ *
85
+ * @example
86
+ * ```vue
87
+ * <script setup lang="ts">
88
+ * const status = useCollection(s => s.status);
89
+ * </script>
90
+ * ```
91
+ */
36
92
  declare function useCollection<S>(selector: (state: CollectionState) => S, isEqual?: (a: S, b: S) => boolean): Readonly<ShallowRef<S>>;
93
+ /**
94
+ * Returns methods that change filters and sort. The store's `onBrowseChange`
95
+ * callback (set by {@link CollectionProvider}) handles emitting `change` with a
96
+ * serialized search string.
97
+ */
37
98
  declare function useCollectionActions(): CollectionActions;
99
+ /**
100
+ * Returns form props for progressive-enhancement of collection filter forms.
101
+ *
102
+ * @example
103
+ * ```vue
104
+ * <script setup lang="ts">
105
+ * const { formProps } = useCollectionForm();
106
+ * </script>
107
+ *
108
+ * <template>
109
+ * <form v-bind="formProps()" action="/collections/shoes">
110
+ * <input type="checkbox" name="filter.p.tag" value="sale" />
111
+ * <button type="submit">Apply</button>
112
+ * </form>
113
+ * </template>
114
+ * ```
115
+ */
38
116
  declare function useCollectionForm(): {
39
117
  formProps: (opts?: {
40
- beforeSubmit?: (e: Event) => void;
41
- afterSubmit?: (e: Event) => void;
118
+ beforeSubmit?: (e: SubmitEvent) => void;
119
+ afterSubmit?: (e: SubmitEvent) => void;
42
120
  }) => Record<string, unknown>;
43
121
  };
44
122
  //#endregion
45
- export { CollectionActions, CollectionProvider, useCollection, useCollectionActions, useCollectionForm };
123
+ export { CollectionProvider, useCollection, useCollectionActions, useCollectionForm };
46
124
  //# sourceMappingURL=collection.d.mts.map
@@ -4,6 +4,36 @@ import { defineComponent, inject, onUnmounted, provide, shallowRef, toValue, wat
4
4
 
5
5
  //#region src/vue/collection.ts
6
6
  const CollectionStoreKey = Symbol("CollectionStore");
7
+ /**
8
+ * Manages the lifecycle of a {@link CollectionStore}: creates on mount and syncs
9
+ * with URL changes. Recreates the store when `data.handle` changes
10
+ * (navigating to a different collection).
11
+ *
12
+ * @example
13
+ * ```vue
14
+ * <script setup lang="ts">
15
+ * import { CollectionProvider, type CollectionData } from '@shopify/hydrogen/vue';
16
+ * import { useRoute, useRouter } from 'vue-router';
17
+ *
18
+ * const props = defineProps<{ data: CollectionData; urlSearch: string }>();
19
+ * const route = useRoute();
20
+ * const router = useRouter();
21
+ *
22
+ * // `search` includes the leading `?`, so append it to the path as-is.
23
+ * const onChange = (search: string) => router.replace(`${route.path}${search}`);
24
+ * <\/script>
25
+ *
26
+ * <template>
27
+ * <CollectionProvider
28
+ * :data="props.data"
29
+ * :url-search="props.urlSearch"
30
+ * @change="onChange"
31
+ * >
32
+ * <slot />
33
+ * </CollectionProvider>
34
+ * </template>
35
+ * ```
36
+ */
7
37
  const CollectionProvider = defineComponent({
8
38
  name: "CollectionProvider",
9
39
  props: {
@@ -79,6 +109,11 @@ function useCollection(selector, isEqual) {
79
109
  }, { immediate: true });
80
110
  return selected;
81
111
  }
112
+ /**
113
+ * Returns methods that change filters and sort. The store's `onBrowseChange`
114
+ * callback (set by {@link CollectionProvider}) handles emitting `change` with a
115
+ * serialized search string.
116
+ */
82
117
  function useCollectionActions() {
83
118
  const storeRef = useRequiredStoreRef("useCollectionActions");
84
119
  return {
@@ -91,6 +126,23 @@ function useCollectionActions() {
91
126
  handleFormSubmit: (...args) => storeRef.value.handleFormSubmit(...args)
92
127
  };
93
128
  }
129
+ /**
130
+ * Returns form props for progressive-enhancement of collection filter forms.
131
+ *
132
+ * @example
133
+ * ```vue
134
+ * <script setup lang="ts">
135
+ * const { formProps } = useCollectionForm();
136
+ * <\/script>
137
+ *
138
+ * <template>
139
+ * <form v-bind="formProps()" action="/collections/shoes">
140
+ * <input type="checkbox" name="filter.p.tag" value="sale" />
141
+ * <button type="submit">Apply</button>
142
+ * </form>
143
+ * </template>
144
+ * ```
145
+ */
94
146
  function useCollectionForm() {
95
147
  const actions = useCollectionActions();
96
148
  const formProps = (opts) => ({ onSubmit: (e) => {
@@ -1 +1 @@
1
- {"version":3,"file":"collection.mjs","names":[],"sources":["../../src/vue/collection.ts"],"sourcesContent":["import {\n defineComponent,\n inject,\n onUnmounted,\n provide,\n shallowRef,\n toValue,\n watch,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport {\n createCollectionStore,\n createCollectionReconciler,\n type CollectionData,\n type CollectionReconciler,\n type CollectionStore,\n} from \"../core/collection\";\nimport type { CollectionState } from \"../core/collection\";\n\nexport type { CollectionData };\n\nconst CollectionStoreKey: InjectionKey<ShallowRef<CollectionStore>> = Symbol(\"CollectionStore\");\n\nexport type CollectionActions = Pick<\n CollectionStore,\n | \"setFilters\"\n | \"toggleFilter\"\n | \"toggleFilterInput\"\n | \"setSortKey\"\n | \"setSortByValue\"\n | \"reset\"\n | \"handleFormSubmit\"\n>;\n\nexport const CollectionProvider = defineComponent({\n name: \"CollectionProvider\",\n props: {\n data: {\n type: Object as PropType<CollectionData>,\n required: true,\n },\n urlSearch: {\n type: String,\n default: \"\",\n },\n },\n emits: {\n change: (searchString: string) => typeof searchString === \"string\",\n },\n setup(props, { slots, emit }) {\n const readUrlSearch = () => toValue(props.urlSearch);\n\n const initialStore = createCollectionStore({\n data: props.data,\n urlSearch: readUrlSearch(),\n });\n const storeRef = shallowRef<CollectionStore>(initialStore);\n provide(CollectionStoreKey, storeRef);\n\n let reconciler: CollectionReconciler = createCollectionReconciler(\n {\n getStore: () => storeRef.value,\n readUrlSearch,\n emitChange: (s) => emit(\"change\", s),\n },\n readUrlSearch(),\n );\n initialStore.setOnBrowseChange(() => reconciler.handleBrowseChange());\n\n function resetStore() {\n storeRef.value.setOnBrowseChange(null);\n const store = createCollectionStore({\n data: props.data,\n urlSearch: readUrlSearch(),\n });\n storeRef.value = store;\n reconciler.reset(readUrlSearch());\n store.setOnBrowseChange(() => reconciler.handleBrowseChange());\n }\n\n watch(\n () => props.data.handle,\n (newHandle, oldHandle) => {\n if (newHandle === oldHandle) return;\n resetStore();\n },\n );\n\n watch(\n () => [readUrlSearch(), props.data] as const,\n () => {\n reconciler.reconcile(readUrlSearch(), props.data.dataSearch);\n },\n { flush: \"sync\", immediate: true, deep: true },\n );\n\n onUnmounted(() => {\n storeRef.value.setOnBrowseChange(null);\n });\n\n return () => slots.default?.();\n },\n});\n\nfunction useRequiredStoreRef(composableName: string): ShallowRef<CollectionStore> {\n const storeRef = inject(CollectionStoreKey, null);\n if (!storeRef) {\n throw new Error(`${composableName} must be used inside a <CollectionProvider>.`);\n }\n return storeRef;\n}\n\nexport function useCollection(): Readonly<ShallowRef<CollectionState>>;\nexport function useCollection<S>(\n selector: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>>;\nexport function useCollection<S>(\n selector?: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<CollectionState | S>> {\n const storeRef = useRequiredStoreRef(\"useCollection\");\n const resolve = selector ?? ((state: CollectionState) => state as unknown as S);\n const selected = shallowRef<CollectionState | S>(resolve(storeRef.value.getState()));\n\n watch(\n () => storeRef.value,\n (store, _, onCleanup) => {\n selected.value = resolve(store.getState());\n\n const unsubscribe = store.subscribe(() => {\n const next = resolve(store.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<CollectionState | S>>;\n}\n\nexport function useCollectionActions(): CollectionActions {\n const storeRef = useRequiredStoreRef(\"useCollectionActions\");\n\n return {\n setFilters: (...args) => storeRef.value.setFilters(...args),\n toggleFilter: (...args) => storeRef.value.toggleFilter(...args),\n toggleFilterInput: (...args) => storeRef.value.toggleFilterInput(...args),\n setSortKey: (...args) => storeRef.value.setSortKey(...args),\n setSortByValue: (...args) => storeRef.value.setSortByValue(...args),\n reset: (...args) => storeRef.value.reset(...args),\n handleFormSubmit: (...args) => storeRef.value.handleFormSubmit(...args),\n };\n}\n\nexport function useCollectionForm(): {\n formProps: (opts?: {\n beforeSubmit?: (e: Event) => void;\n afterSubmit?: (e: Event) => void;\n }) => Record<string, unknown>;\n} {\n const actions = useCollectionActions();\n\n const formProps = (opts?: {\n beforeSubmit?: (e: Event) => void;\n afterSubmit?: (e: Event) => void;\n }): Record<string, unknown> => ({\n onSubmit: (e: Event) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n actions.handleFormSubmit(e as SubmitEvent);\n opts?.afterSubmit?.(e);\n },\n });\n\n return { formProps };\n}\n"],"mappings":";;;;;AAwBA,MAAM,qBAAgE,OAAO,kBAAkB;AAa/F,MAAa,qBAAqB,gBAAgB;CAChD,MAAM;CACN,OAAO;EACL,MAAM;GACJ,MAAM;GACN,UAAU;GACX;EACD,WAAW;GACT,MAAM;GACN,SAAS;GACV;EACF;CACD,OAAO,EACL,SAAS,iBAAyB,OAAO,iBAAiB,UAC3D;CACD,MAAM,OAAO,EAAE,OAAO,QAAQ;EAC5B,MAAM,sBAAsB,QAAQ,MAAM,UAAU;EAEpD,MAAM,eAAe,sBAAsB;GACzC,MAAM,MAAM;GACZ,WAAW,eAAe;GAC3B,CAAC;EACF,MAAM,WAAW,WAA4B,aAAa;AAC1D,UAAQ,oBAAoB,SAAS;EAErC,IAAI,aAAmC,2BACrC;GACE,gBAAgB,SAAS;GACzB;GACA,aAAa,MAAM,KAAK,UAAU,EAAE;GACrC,EACD,eAAe,CAChB;AACD,eAAa,wBAAwB,WAAW,oBAAoB,CAAC;EAErE,SAAS,aAAa;AACpB,YAAS,MAAM,kBAAkB,KAAK;GACtC,MAAM,QAAQ,sBAAsB;IAClC,MAAM,MAAM;IACZ,WAAW,eAAe;IAC3B,CAAC;AACF,YAAS,QAAQ;AACjB,cAAW,MAAM,eAAe,CAAC;AACjC,SAAM,wBAAwB,WAAW,oBAAoB,CAAC;;AAGhE,cACQ,MAAM,KAAK,SAChB,WAAW,cAAc;AACxB,OAAI,cAAc,UAAW;AAC7B,eAAY;IAEf;AAED,cACQ,CAAC,eAAe,EAAE,MAAM,KAAK,QAC7B;AACJ,cAAW,UAAU,eAAe,EAAE,MAAM,KAAK,WAAW;KAE9D;GAAE,OAAO;GAAQ,WAAW;GAAM,MAAM;GAAM,CAC/C;AAED,oBAAkB;AAChB,YAAS,MAAM,kBAAkB,KAAK;IACtC;AAEF,eAAa,MAAM,WAAW;;CAEjC,CAAC;AAEF,SAAS,oBAAoB,gBAAqD;CAChF,MAAM,WAAW,OAAO,oBAAoB,KAAK;AACjD,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,GAAG,eAAe,8CAA8C;AAElF,QAAO;;AAQT,SAAgB,cACd,UACA,SAC2C;CAC3C,MAAM,WAAW,oBAAoB,gBAAgB;CACrD,MAAM,UAAU,cAAc,UAA2B;CACzD,MAAM,WAAW,WAAgC,QAAQ,SAAS,MAAM,UAAU,CAAC,CAAC;AAEpF,aACQ,SAAS,QACd,OAAO,GAAG,cAAc;AACvB,WAAS,QAAQ,QAAQ,MAAM,UAAU,CAAC;AAU1C,YARoB,MAAM,gBAAgB;GACxC,MAAM,OAAO,QAAQ,MAAM,UAAU,CAAC;AACtC,OAAI,WAAW,UACb;QAAI,QAAQ,SAAS,OAAY,KAAK,CAAE;;AAE1C,YAAS,QAAQ;IAGE,CAAC;IAExB,EAAE,WAAW,MAAM,CACpB;AAED,QAAO;;AAGT,SAAgB,uBAA0C;CACxD,MAAM,WAAW,oBAAoB,uBAAuB;AAE5D,QAAO;EACL,aAAa,GAAG,SAAS,SAAS,MAAM,WAAW,GAAG,KAAK;EAC3D,eAAe,GAAG,SAAS,SAAS,MAAM,aAAa,GAAG,KAAK;EAC/D,oBAAoB,GAAG,SAAS,SAAS,MAAM,kBAAkB,GAAG,KAAK;EACzE,aAAa,GAAG,SAAS,SAAS,MAAM,WAAW,GAAG,KAAK;EAC3D,iBAAiB,GAAG,SAAS,SAAS,MAAM,eAAe,GAAG,KAAK;EACnE,QAAQ,GAAG,SAAS,SAAS,MAAM,MAAM,GAAG,KAAK;EACjD,mBAAmB,GAAG,SAAS,SAAS,MAAM,iBAAiB,GAAG,KAAK;EACxE;;AAGH,SAAgB,oBAKd;CACA,MAAM,UAAU,sBAAsB;CAEtC,MAAM,aAAa,UAGa,EAC9B,WAAW,MAAa;AACtB,QAAM,eAAe,EAAE;AACvB,MAAI,EAAE,iBAAkB;AACxB,IAAE,gBAAgB;AAClB,UAAQ,iBAAiB,EAAiB;AAC1C,QAAM,cAAc,EAAE;IAEzB;AAED,QAAO,EAAE,WAAW"}
1
+ {"version":3,"file":"collection.mjs","names":[],"sources":["../../src/vue/collection.ts"],"sourcesContent":["import {\n defineComponent,\n inject,\n onUnmounted,\n provide,\n shallowRef,\n toValue,\n watch,\n type InjectionKey,\n type PropType,\n type ShallowRef,\n} from \"vue\";\n\nimport {\n createCollectionStore,\n createCollectionReconciler,\n type CollectionActions,\n type CollectionData,\n type CollectionReconciler,\n type CollectionStore,\n} from \"../core/collection\";\nimport type { CollectionState } from \"../core/collection\";\n\nexport type { CollectionActions, CollectionData };\n\nconst CollectionStoreKey: InjectionKey<ShallowRef<CollectionStore>> = Symbol(\"CollectionStore\");\n\n/**\n * Manages the lifecycle of a {@link CollectionStore}: creates on mount and syncs\n * with URL changes. Recreates the store when `data.handle` changes\n * (navigating to a different collection).\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * import { CollectionProvider, type CollectionData } from '@shopify/hydrogen/vue';\n * import { useRoute, useRouter } from 'vue-router';\n *\n * const props = defineProps<{ data: CollectionData; urlSearch: string }>();\n * const route = useRoute();\n * const router = useRouter();\n *\n * // `search` includes the leading `?`, so append it to the path as-is.\n * const onChange = (search: string) => router.replace(`${route.path}${search}`);\n * </script>\n *\n * <template>\n * <CollectionProvider\n * :data=\"props.data\"\n * :url-search=\"props.urlSearch\"\n * @change=\"onChange\"\n * >\n * <slot />\n * </CollectionProvider>\n * </template>\n * ```\n */\nexport const CollectionProvider = defineComponent({\n name: \"CollectionProvider\",\n props: {\n data: {\n type: Object as PropType<CollectionData>,\n required: true,\n },\n urlSearch: {\n type: String,\n default: \"\",\n },\n },\n emits: {\n change: (searchString: string) => typeof searchString === \"string\",\n },\n setup(props, { slots, emit }) {\n const readUrlSearch = () => toValue(props.urlSearch);\n\n const initialStore = createCollectionStore({\n data: props.data,\n urlSearch: readUrlSearch(),\n });\n const storeRef = shallowRef<CollectionStore>(initialStore);\n provide(CollectionStoreKey, storeRef);\n\n let reconciler: CollectionReconciler = createCollectionReconciler(\n {\n getStore: () => storeRef.value,\n readUrlSearch,\n emitChange: (s) => emit(\"change\", s),\n },\n readUrlSearch(),\n );\n initialStore.setOnBrowseChange(() => reconciler.handleBrowseChange());\n\n function resetStore() {\n storeRef.value.setOnBrowseChange(null);\n const store = createCollectionStore({\n data: props.data,\n urlSearch: readUrlSearch(),\n });\n storeRef.value = store;\n reconciler.reset(readUrlSearch());\n store.setOnBrowseChange(() => reconciler.handleBrowseChange());\n }\n\n watch(\n () => props.data.handle,\n (newHandle, oldHandle) => {\n if (newHandle === oldHandle) return;\n resetStore();\n },\n );\n\n watch(\n () => [readUrlSearch(), props.data] as const,\n () => {\n reconciler.reconcile(readUrlSearch(), props.data.dataSearch);\n },\n { flush: \"sync\", immediate: true, deep: true },\n );\n\n onUnmounted(() => {\n storeRef.value.setOnBrowseChange(null);\n });\n\n return () => slots.default?.();\n },\n});\n\nfunction useRequiredStoreRef(composableName: string): ShallowRef<CollectionStore> {\n const storeRef = inject(CollectionStoreKey, null);\n if (!storeRef) {\n throw new Error(`${composableName} must be used inside a <CollectionProvider>.`);\n }\n return storeRef;\n}\n\n/**\n * Subscribes to the collection store and returns a reactive ref of the full\n * state snapshot.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * const state = useCollection();\n * </script>\n *\n * <template>\n * <p>{{ state.status }}</p>\n * </template>\n * ```\n */\nexport function useCollection(): Readonly<ShallowRef<CollectionState>>;\n/**\n * Subscribes to the collection store and returns a reactive ref of a derived\n * value via `selector`. Optionally accepts an `isEqual` comparator to skip\n * updates when the derived value is structurally unchanged.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * const status = useCollection(s => s.status);\n * </script>\n * ```\n */\nexport function useCollection<S>(\n selector: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<S>>;\nexport function useCollection<S>(\n selector?: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): Readonly<ShallowRef<CollectionState | S>> {\n const storeRef = useRequiredStoreRef(\"useCollection\");\n const resolve = selector ?? ((state: CollectionState) => state as unknown as S);\n const selected = shallowRef<CollectionState | S>(resolve(storeRef.value.getState()));\n\n watch(\n () => storeRef.value,\n (store, _, onCleanup) => {\n selected.value = resolve(store.getState());\n\n const unsubscribe = store.subscribe(() => {\n const next = resolve(store.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<CollectionState | S>>;\n}\n\n/**\n * Returns methods that change filters and sort. The store's `onBrowseChange`\n * callback (set by {@link CollectionProvider}) handles emitting `change` with a\n * serialized search string.\n */\nexport function useCollectionActions(): CollectionActions {\n const storeRef = useRequiredStoreRef(\"useCollectionActions\");\n\n return {\n setFilters: (...args) => storeRef.value.setFilters(...args),\n toggleFilter: (...args) => storeRef.value.toggleFilter(...args),\n toggleFilterInput: (...args) => storeRef.value.toggleFilterInput(...args),\n setSortKey: (...args) => storeRef.value.setSortKey(...args),\n setSortByValue: (...args) => storeRef.value.setSortByValue(...args),\n reset: (...args) => storeRef.value.reset(...args),\n handleFormSubmit: (...args) => storeRef.value.handleFormSubmit(...args),\n };\n}\n\n/**\n * Returns form props for progressive-enhancement of collection filter forms.\n *\n * @example\n * ```vue\n * <script setup lang=\"ts\">\n * const { formProps } = useCollectionForm();\n * </script>\n *\n * <template>\n * <form v-bind=\"formProps()\" action=\"/collections/shoes\">\n * <input type=\"checkbox\" name=\"filter.p.tag\" value=\"sale\" />\n * <button type=\"submit\">Apply</button>\n * </form>\n * </template>\n * ```\n */\nexport function useCollectionForm(): {\n formProps: (opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }) => Record<string, unknown>;\n} {\n const actions = useCollectionActions();\n\n const formProps = (opts?: {\n beforeSubmit?: (e: SubmitEvent) => void;\n afterSubmit?: (e: SubmitEvent) => void;\n }): Record<string, unknown> => ({\n onSubmit: (e: SubmitEvent) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n actions.handleFormSubmit(e);\n opts?.afterSubmit?.(e);\n },\n });\n\n return { formProps };\n}\n"],"mappings":"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"}
@@ -1,10 +1,12 @@
1
+ import { CartActions } from "../core/cart/cart.mjs";
1
2
  import { ShopifyGlobal } from "../globals.mjs";
2
- import { CollectionData } from "../core/collection/collection.mjs";
3
+ import { CollectionActions, CollectionData } from "../core/collection/collection.mjs";
3
4
  import { ValidProductSelectionResult } from "../core/product/product-form.mjs";
4
5
  import { ProductAddToCartProps, ProductFormRegister, ProductMerchandiseIdProps, ProductOptionValueProps, ProductQuantityDefaultProps, ProductQuantityProps } from "../core/product/form.mjs";
5
- import { CartActions, createCartComponents, useCartActions, useCartAnalytics } from "./cart.mjs";
6
- import { CollectionActions, CollectionProvider, useCollection, useCollectionActions, useCollectionForm } from "./collection.mjs";
7
- import { PredictiveSearchActions, PredictiveSearchFormPropsOptions, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm } from "./predictive-search.mjs";
6
+ import { PredictiveSearchActions } from "../core/predictive-search/store.mjs";
7
+ import { createCartComponents, useCartActions, useCartAnalytics } from "./cart.mjs";
8
+ import { CollectionProvider, useCollection, useCollectionActions, useCollectionForm } from "./collection.mjs";
9
+ import { PredictiveSearchFormPropsOptions, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm } from "./predictive-search.mjs";
8
10
  import { UseProductFormOptions, UseProductFormResult, UseProductResult, createProductComponents, useProductForm } from "./product.mjs";
9
11
  import { ShopPayButton, ShopPayButtonProps } from "./shop-pay.mjs";
10
12
  import { ShopifyScripts, ShopifyScriptsProps } from "./shopify-scripts.mjs";
@@ -1,24 +1,33 @@
1
1
  import { PredictiveSearchLimitScope, PredictiveSearchType, SearchUnavailableProductsType, SearchableField } from "../graphql/generated/storefront-api-types.mjs";
2
2
  import { PredictiveSearchData } from "../core/predictive-search/search.mjs";
3
- import { PredictiveSearchState, PredictiveSearchStore } from "../core/predictive-search/store.mjs";
3
+ import { PredictiveSearchActions, PredictiveSearchState } from "../core/predictive-search/store.mjs";
4
4
  import * as _$vue from "vue";
5
5
  import { PropType, ShallowRef } from "vue";
6
6
 
7
7
  //#region src/vue/predictive-search.d.ts
8
- type PredictiveSearchActions = Pick<PredictiveSearchStore, "search" | "clear">;
8
+ /** Options for the form props builder returned by {@link usePredictiveSearchForm}. */
9
9
  type PredictiveSearchFormPropsOptions = {
10
- preventDefault?: boolean;
10
+ /** When `true`, prevents the native form submission and triggers a client-side search instead. */preventDefault?: boolean; /** 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. */
11
11
  onSubmit?: (event: SubmitEvent, term: string) => void;
12
12
  [key: string]: unknown;
13
13
  };
14
+ /** Options for the query input props builder returned by {@link usePredictiveSearchForm}'s `register` method. */
14
15
  type PredictiveSearchQueryInputPropsOptions = {
15
- onInput?: (event: Event, term: string) => void;
16
+ /** Called on input with the input event and the current input value. Call `event.preventDefault()` to skip the automatic search trigger. */onInput?: (event: Event, term: string) => void;
16
17
  [key: string]: unknown;
17
18
  };
19
+ /** Return type of {@link usePredictiveSearchForm}, providing methods to build a progressively-enhanced search form. */
18
20
  type PredictiveSearchFormResult = {
19
- formProps(options?: PredictiveSearchFormPropsOptions): Record<string, unknown>;
21
+ /** Generates form element attributes including the search action and submit handler. */formProps(options?: PredictiveSearchFormPropsOptions): Record<string, unknown>; /** Generates input element attributes for a named form field and wires up the search trigger. */
20
22
  register: (field: "query", options?: PredictiveSearchQueryInputPropsOptions) => Record<string, unknown>;
21
23
  };
24
+ /**
25
+ * Creates and manages a predictive search store, providing it to descendant
26
+ * composables via Vue's provide/inject.
27
+ *
28
+ * Recreates the store when configuration props change. Connects the store
29
+ * on mount and destroys it on unmount.
30
+ */
22
31
  declare const PredictiveSearchProvider: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
23
32
  predictiveSearchEndpoint: {
24
33
  type: StringConstructor;
@@ -115,10 +124,48 @@ declare const PredictiveSearchProvider: _$vue.DefineComponent<_$vue.ExtractPropT
115
124
  searchableFields: SearchableField[];
116
125
  unavailableProducts: SearchUnavailableProductsType;
117
126
  }, {}, {}, {}, string, _$vue.ComponentProvideOptions, true, {}, any>;
127
+ /**
128
+ * Subscribes to the predictive search store's state as a reactive ref.
129
+ *
130
+ * Without arguments, returns a `ShallowRef` of the full
131
+ * {@link PredictiveSearchState}. With a `selector`, returns a ref of the
132
+ * derived value that only updates when the selected value changes
133
+ * (reference equality by default, or a custom `isEqual`).
134
+ *
135
+ * Must be used inside a {@link PredictiveSearchProvider}.
136
+ *
137
+ * @throws {Error} When called outside a PredictiveSearchProvider.
138
+ */
118
139
  declare function usePredictiveSearch<TData extends PredictiveSearchData = PredictiveSearchData>(): Readonly<ShallowRef<PredictiveSearchState<TData>>>;
119
140
  declare function usePredictiveSearch<TData extends PredictiveSearchData = PredictiveSearchData, S = PredictiveSearchState<TData>>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): Readonly<ShallowRef<S>>;
141
+ /**
142
+ * Returns `search` and `clear` methods that delegate to the current store.
143
+ *
144
+ * These are thin wrappers that read from the reactive store ref on each call,
145
+ * so they always target the active store even after a provider prop change
146
+ * triggers store recreation.
147
+ *
148
+ * Must be used inside a {@link PredictiveSearchProvider}.
149
+ *
150
+ * @throws {Error} When called outside a PredictiveSearchProvider.
151
+ */
120
152
  declare function usePredictiveSearchActions(): PredictiveSearchActions;
153
+ /**
154
+ * Returns `formProps` and `register` for building a progressively-enhanced
155
+ * search form.
156
+ *
157
+ * `formProps()` generates form element attributes including the search
158
+ * action URL. `register("query")` generates input attributes and calls the
159
+ * store's search action on every `input` event. The store debounces requests
160
+ * and resets state for terms shorter than `minTermLength`.
161
+ * Both support an optional callback that receives the event and extracted
162
+ * term, and respect `event.preventDefault()` to cancel the automatic behavior.
163
+ *
164
+ * Must be used inside a {@link PredictiveSearchProvider}.
165
+ *
166
+ * @throws {Error} When called outside a PredictiveSearchProvider.
167
+ */
121
168
  declare function usePredictiveSearchForm(): PredictiveSearchFormResult;
122
169
  //#endregion
123
- export { PredictiveSearchActions, PredictiveSearchFormPropsOptions, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm };
170
+ export { PredictiveSearchFormPropsOptions, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm };
124
171
  //# sourceMappingURL=predictive-search.d.mts.map
@@ -5,6 +5,13 @@ import { defineComponent, inject, onMounted, onUnmounted, provide, shallowRef, w
5
5
  //#region src/vue/predictive-search.ts
6
6
  const CONFIG_ARRAY_SEPARATOR = "\0";
7
7
  const PredictiveSearchKey = Symbol("PredictiveSearch");
8
+ /**
9
+ * Creates and manages a predictive search store, providing it to descendant
10
+ * composables via Vue's provide/inject.
11
+ *
12
+ * Recreates the store when configuration props change. Connects the store
13
+ * on mount and destroys it on unmount.
14
+ */
8
15
  const PredictiveSearchProvider = defineComponent({
9
16
  name: "PredictiveSearchProvider",
10
17
  props: {
@@ -121,6 +128,17 @@ function usePredictiveSearch(selector, isEqual) {
121
128
  }, { immediate: true });
122
129
  return selected;
123
130
  }
131
+ /**
132
+ * Returns `search` and `clear` methods that delegate to the current store.
133
+ *
134
+ * These are thin wrappers that read from the reactive store ref on each call,
135
+ * so they always target the active store even after a provider prop change
136
+ * triggers store recreation.
137
+ *
138
+ * Must be used inside a {@link PredictiveSearchProvider}.
139
+ *
140
+ * @throws {Error} When called outside a PredictiveSearchProvider.
141
+ */
124
142
  function usePredictiveSearchActions() {
125
143
  const { storeRef } = useRequiredContext("usePredictiveSearchActions");
126
144
  return {
@@ -128,6 +146,21 @@ function usePredictiveSearchActions() {
128
146
  clear: () => storeRef.value.clear()
129
147
  };
130
148
  }
149
+ /**
150
+ * Returns `formProps` and `register` for building a progressively-enhanced
151
+ * search form.
152
+ *
153
+ * `formProps()` generates form element attributes including the search
154
+ * action URL. `register("query")` generates input attributes and calls the
155
+ * store's search action on every `input` event. The store debounces requests
156
+ * and resets state for terms shorter than `minTermLength`.
157
+ * Both support an optional callback that receives the event and extracted
158
+ * term, and respect `event.preventDefault()` to cancel the automatic behavior.
159
+ *
160
+ * Must be used inside a {@link PredictiveSearchProvider}.
161
+ *
162
+ * @throws {Error} When called outside a PredictiveSearchProvider.
163
+ */
131
164
  function usePredictiveSearchForm() {
132
165
  const { storeRef, searchActionRef } = useRequiredContext("usePredictiveSearchForm");
133
166
  const coreRegister = createPredictiveSearchFormRegister();
@@ -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 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 = Pick<PredictiveSearchStore, \"search\" | \"clear\">;\n\nexport type PredictiveSearchFormPropsOptions = {\n preventDefault?: boolean;\n onSubmit?: (event: SubmitEvent, term: string) => void;\n [key: string]: unknown;\n};\n\nexport type PredictiveSearchQueryInputPropsOptions = {\n onInput?: (event: Event, term: string) => void;\n [key: string]: unknown;\n};\n\nexport type PredictiveSearchFormResult = {\n formProps(options?: PredictiveSearchFormPropsOptions): Record<string, unknown>;\n register: (\n field: \"query\",\n options?: PredictiveSearchQueryInputPropsOptions,\n ) => Record<string, unknown>;\n};\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\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\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\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/** 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":";;;;;AA+BA,MAAM,yBAAyB;AAO/B,MAAM,sBAAkE,OAAO,mBAAmB;;;;;;;;AAsClG,MAAa,2BAA2B,gBAAgB;CACtD,MAAM;CACN,OAAO;EACL,0BAA0B;GAAE,MAAM;GAAQ,SAAS;GAAW;EAC9D,cAAc;GAAE,MAAM;GAAQ,SAAS;GAAW;EAClD,cAAc;GAAE,MAAM;GAAQ,SAAS;GAAW;EAClD,eAAe;GAAE,MAAM;GAAQ,SAAS;GAAW;EACnD,OAAO;GAAE,MAAM;GAA+C,SAAS;GAAW;EAClF,OAAO;GAAE,MAAM;GAAQ,SAAS;GAAW;EAC3C,YAAY;GAAE,MAAM;GAAgD,SAAS;GAAW;EACxF,OAAO;GAAE,MAAM;GAA2C,SAAS;GAAW;EAC9E,kBAAkB;GAAE,MAAM;GAAsC,SAAS;GAAW;EACpF,qBAAqB;GACnB,MAAM;GACN,SAAS;GACV;EACF;CACD,MAAM,OAAO,EAAE,SAAS;EACtB,SAAS,oBAAwD;AAC/D,UAAO;IACL,0BAA0B,MAAM;IAChC,cAAc,MAAM;IACpB,eAAe,MAAM;IACrB,OAAO,MAAM;IACb,OAAO,MAAM;IACb,YAAY,MAAM;IAClB,OAAO,MAAM;IACb,kBAAkB,MAAM;IACxB,qBAAqB,MAAM;IAC5B;;EAGH,MAAM,WAAW,WACf,4BAA4B,mBAAmB,CAAC,CACjD;EACD,MAAM,kBAAkB,WAAW,MAAM,aAAa;AAEtD,UAAQ,qBAAqB;GAAE;GAAU;GAAiB,CAAC;AAE3D,cACQ,MAAM,eACX,SAAS;AACR,mBAAgB,QAAQ;IAE3B;AAED,cACQ;GACJ,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,MAAM;GACN,YAAY,MAAM,MAAM;GACxB,YAAY,MAAM,iBAAiB;GACnC,MAAM;GACP,QACK;AACJ,YAAS,MAAM,SAAS;AACxB,YAAS,QAAQ,4BAA4B,mBAAmB,CAAC;AACjE,YAAS,MAAM,SAAS;IAE3B;AAED,kBAAgB;AACd,YAAS,MAAM,SAAS;IACxB;AAEF,oBAAkB;AAChB,YAAS,MAAM,SAAS;IACxB;AAEF,eAAa,MAAM,WAAW;;CAEjC,CAAC;AAEF,SAAS,YAAY,QAA+C;AAClE,QAAO,QAAQ,KAAK,uBAAuB,IAAI;;AAGjD,SAAS,mBAAmB,gBAAsD;CAChF,MAAM,UAAU,OAAO,qBAAqB,KAAK;AACjD,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,GAAG,eAAe,oDAAoD;AAExF,QAAO;;AAyBT,SAAgB,oBAId,UACA,SACwD;CACxD,MAAM,EAAE,aAAa,mBAAmB,sBAAsB;CAC9D,MAAM,UAAU,cAAc,UAAwC;CACtE,MAAM,WAAW,WACf,QAAS,SAAS,MAAuC,UAAU,CAAC,CACrE;AAED,aACQ,SAAS,QACd,OAAO,GAAG,cAAc;EACvB,MAAM,aAAa;AACnB,WAAS,QAAQ,QAAQ,WAAW,UAAU,CAAC;AAU/C,YARoB,WAAW,gBAAgB;GAC7C,MAAM,OAAO,QAAQ,WAAW,UAAU,CAAC;AAC3C,OAAI,WAAW,UACb;QAAI,QAAQ,SAAS,OAAY,KAAK,CAAE;;AAE1C,YAAS,QAAQ;IAGE,CAAC;IAExB,EAAE,WAAW,MAAM,CACpB;AAED,QAAO;;;;;;;;;;;;;AAcT,SAAgB,6BAAsD;CACpE,MAAM,EAAE,aAAa,mBAAmB,6BAA6B;AAErE,QAAO;EACL,SAAS,SAAS,SAAS,MAAM,OAAO,KAAK;EAC7C,aAAa,SAAS,MAAM,OAAO;EACpC;;;;;;;;;;;;;;;;;AAkBH,SAAgB,0BAAsD;CACpE,MAAM,EAAE,UAAU,oBAAoB,mBAAmB,0BAA0B;CACnF,MAAM,eAAe,oCAAoC;CAEzD,SAAS,SACP,OACA,UAAkD,EAAE,EAC3B;EACzB,MAAM,EAAE,SAAS,GAAG,eAAe;EACnC,MAAM,iBAAiB,aAAa,MAAM;AAE1C,SAAO;GACL,GAAG;GACH,GAAG;GACH,UAAU,UAAiB;IACzB,MAAM,OAAQ,MAAM,OAA4B;AAChD,cAAU,OAAO,KAAK;AACtB,QAAI,MAAM,iBAAkB;AAC5B,IAAK,SAAS,MAAM,OAAO,KAAK;;GAEnC;;CAGH,SAAS,UAAU,UAA4C,EAAE,EAA2B;EAC1F,MAAM,EAAE,UAAU,gBAAgB,GAAG,eAAe;AAEpD,SAAO;GACL,GAAG,kCAAkC,gBAAgB,MAAM;GAC3D,GAAG;GACH,WAAW,UAAuB;IAChC,MAAM,OAAO,6BACX,IAAI,SAAS,MAAM,cAAiC,CACrD;AACD,eAAW,OAAO,KAAK;AACvB,QAAI,MAAM,iBAAkB;AAC5B,QAAI,CAAC,eAAgB;AACrB,UAAM,gBAAgB;AACtB,IAAK,SAAS,MAAM,OAAO,KAAK;;GAEnC;;AAGH,QAAO;EAAE;EAAW;EAAU"}
@@ -5,22 +5,26 @@ 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
9
  interface UseProductFormOptions<TProduct extends ProductInput> {
9
10
  onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;
10
11
  }
12
+ /** Return value of {@link useProductForm}. */
11
13
  interface UseProductFormResult<TProduct extends ProductInput> {
12
14
  options: ProductFormOptions<TProduct>;
13
15
  selectedVariant: ProductVariantFrom<TProduct> | null;
14
16
  register: ProductFormRegister;
15
17
  formProps: (opts?: {
16
- beforeSubmit?: (e: Event) => void;
17
- afterSubmit?: (e: Event) => void;
18
+ beforeSubmit?: (e: SubmitEvent) => void;
19
+ afterSubmit?: (e: SubmitEvent) => void;
18
20
  }) => Record<string, unknown>;
19
21
  errors: ProductFormErrors;
20
22
  matchedLineItem: CartLine | null;
23
+ /** Reactive ref that is `true` while a form submission is in flight. */
21
24
  pending: ShallowRef<boolean>;
22
25
  selectOption: (name: string, value: string) => VariantSelectionResult<ProductVariantFrom<TProduct>>;
23
26
  }
27
+ /** Return value of the `useProduct` composable from {@link createProductComponents}. */
24
28
  interface UseProductResult<TProduct extends ProductInput> {
25
29
  options: ProductFormOptions<TProduct>;
26
30
  selectedVariant: ProductVariantFrom<TProduct> | null;
@@ -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\nexport interface UseProductFormOptions<TProduct extends ProductInput> {\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n}\n\nexport interface UseProductFormResult<TProduct extends ProductInput> {\n options: ProductFormOptions<TProduct>;\n selectedVariant: ProductVariantFrom<TProduct> | null;\n register: ProductFormRegister;\n formProps: (opts?: {\n beforeSubmit?: (e: Event) => void;\n afterSubmit?: (e: Event) => void;\n }) => Record<string, unknown>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n pending: ShallowRef<boolean>;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\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: Event) => void;\n afterSubmit?: (e: Event) => void;\n }): Record<string, unknown> {\n return {\n onSubmit: (e: Event) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n pending.value = true;\n store\n .handleFormSubmit(e as SubmitEvent)\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 {@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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"}