@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,32 +1,57 @@
1
1
  import { PredictiveSearchFormRegister as PredictiveSearchFormRegister$1 } from "../core/predictive-search/form.mjs";
2
2
  import { PredictiveSearchData } from "../core/predictive-search/search.mjs";
3
- import { CreatePredictiveSearchStoreOptions, PredictiveSearchState, PredictiveSearchStore } from "../core/predictive-search/store.mjs";
3
+ import { CreatePredictiveSearchStoreOptions, PredictiveSearchActions, PredictiveSearchState, PredictiveSearchStore } from "../core/predictive-search/store.mjs";
4
4
  import * as _$react from "react";
5
5
  import { ChangeEvent, FormHTMLAttributes, InputHTMLAttributes, ReactNode, SubmitEvent } from "react";
6
6
 
7
7
  //#region src/react/predictive-search.d.ts
8
8
  type PredictiveSearchContextValue = {
9
9
  store: PredictiveSearchStore;
10
+ actions: PredictiveSearchActions;
10
11
  searchAction?: string;
11
12
  };
13
+ /** Props for the {@link PredictiveSearchProvider} component. */
12
14
  type PredictiveSearchProviderProps = CreatePredictiveSearchStoreOptions & {
13
- children?: ReactNode;
15
+ children?: ReactNode; /** Form action URL for progressive enhancement. Used by {@link usePredictiveSearchForm} to set the form's `action` attribute so the search works without JavaScript. Falls back to `"/search"` when omitted. */
14
16
  searchAction?: string;
15
17
  };
16
- type PredictiveSearchActions = Pick<PredictiveSearchStore, "search" | "clear">;
18
+ /**
19
+ * Options for the form props builder returned by {@link usePredictiveSearchForm}.
20
+ *
21
+ * Accepts all standard form HTML attributes except `onSubmit`, which is
22
+ * replaced by a version that provides the extracted search term.
23
+ */
17
24
  type PredictiveSearchFormPropsOptions = Omit<FormHTMLAttributes<HTMLFormElement>, "onSubmit"> & {
18
- preventDefault?: boolean;
25
+ /** 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. */
19
26
  onSubmit?: (event: SubmitEvent<HTMLFormElement>, term: string) => void;
20
27
  };
28
+ /**
29
+ * Options for the query input props builder returned by
30
+ * {@link usePredictiveSearchForm}'s `register` method.
31
+ *
32
+ * Accepts all standard input HTML attributes except those controlled by the
33
+ * form registration.
34
+ */
21
35
  type PredictiveSearchQueryInputPropsOptions = Omit<InputHTMLAttributes<HTMLInputElement>, "autoCapitalize" | "autoComplete" | "name" | "onChange" | "spellCheck" | "type"> & {
22
- onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;
36
+ /** Called on change with the change event and the current input value. Call `event.preventDefault()` to skip the automatic search trigger. */onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;
23
37
  };
24
38
  type PredictiveSearchFormField = Parameters<PredictiveSearchFormRegister$1>[0];
39
+ /** Generates input element attributes for a named form field. */
25
40
  type PredictiveSearchFormRegister = (field: PredictiveSearchFormField, options?: PredictiveSearchQueryInputPropsOptions) => InputHTMLAttributes<HTMLInputElement>;
41
+ /** Return type of {@link usePredictiveSearchForm}, providing methods to build a progressively-enhanced search form. */
26
42
  type PredictiveSearchFormResult = {
27
- formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>;
43
+ /** Generates form element attributes including the search action and submit handler. */formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>; /** Generates input element attributes for a named form field and wires up the search trigger. */
28
44
  register: PredictiveSearchFormRegister;
29
45
  };
46
+ /**
47
+ * Creates and manages a predictive search store, providing it to descendant
48
+ * hooks via React context.
49
+ *
50
+ * Recreates the store when configuration props change. Connects the store
51
+ * on mount and destroys it on unmount.
52
+ *
53
+ * @throws {Error} When no `fetch` implementation is available (neither passed as a prop nor available on `globalThis`).
54
+ */
30
55
  declare function PredictiveSearchProvider({
31
56
  children,
32
57
  predictiveSearchEndpoint,
@@ -40,10 +65,46 @@ declare function PredictiveSearchProvider({
40
65
  searchableFields,
41
66
  unavailableProducts
42
67
  }: PredictiveSearchProviderProps): _$react.FunctionComponentElement<_$react.ProviderProps<PredictiveSearchContextValue | null>>;
68
+ /**
69
+ * Subscribes to the predictive search store's state.
70
+ *
71
+ * Without arguments, returns the full {@link PredictiveSearchState}. With a
72
+ * `selector`, returns a derived value that only triggers re-renders when the
73
+ * selected value changes (reference equality by default, or a custom
74
+ * `isEqual`).
75
+ *
76
+ * Must be used inside a {@link PredictiveSearchProvider}.
77
+ *
78
+ * @throws {Error} When called outside a PredictiveSearchProvider.
79
+ */
43
80
  declare function usePredictiveSearch<TData extends PredictiveSearchData = PredictiveSearchData>(): PredictiveSearchState<TData>;
44
81
  declare function usePredictiveSearch<TData extends PredictiveSearchData = PredictiveSearchData, S = PredictiveSearchState<TData>>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): S;
82
+ /**
83
+ * Returns `search` and `clear` methods that always target the provider's
84
+ * current store. Their identity is stable across re-renders, even
85
+ * when a config prop change recreates the store.
86
+ *
87
+ * Must be used inside a {@link PredictiveSearchProvider}.
88
+ *
89
+ * @throws {Error} When called outside a PredictiveSearchProvider.
90
+ */
45
91
  declare function usePredictiveSearchActions(): PredictiveSearchActions;
92
+ /**
93
+ * Returns `formProps` and `register` for building a progressively-enhanced
94
+ * search form.
95
+ *
96
+ * `formProps()` generates form element attributes including the search
97
+ * action URL. `register("query")` generates input attributes and calls the
98
+ * store's search action on every change event. The store debounces requests
99
+ * and resets state for terms shorter than `minTermLength`.
100
+ * Both support an optional callback that receives the event and extracted
101
+ * term, and respect `event.preventDefault()` to cancel the automatic behavior.
102
+ *
103
+ * Must be used inside a {@link PredictiveSearchProvider}.
104
+ *
105
+ * @throws {Error} When called outside a PredictiveSearchProvider.
106
+ */
46
107
  declare function usePredictiveSearchForm(): PredictiveSearchFormResult;
47
108
  //#endregion
48
- export { PredictiveSearchActions, PredictiveSearchFormPropsOptions, PredictiveSearchFormRegister, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchProviderProps, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm };
109
+ export { PredictiveSearchFormPropsOptions, PredictiveSearchFormRegister, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchProviderProps, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm };
49
110
  //# sourceMappingURL=predictive-search.d.mts.map
@@ -2,11 +2,20 @@
2
2
 
3
3
  import { createPredictiveSearchFormRegister, getPredictiveSearchFormAttributes, readPredictiveSearchFormTerm } from "../core/predictive-search/form.mjs";
4
4
  import { createPredictiveSearchStore } from "../core/predictive-search/store.mjs";
5
- import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useSyncExternalStore } from "react";
5
+ import { createContext, createElement, useCallback, useContext, useEffect, useInsertionEffect, useMemo, useRef, useSyncExternalStore } from "react";
6
6
 
7
7
  //#region src/react/predictive-search.tsx
8
8
  const CONFIG_ARRAY_SEPARATOR = "\0";
9
9
  const PredictiveSearchContext = createContext(null);
10
+ /**
11
+ * Creates and manages a predictive search store, providing it to descendant
12
+ * hooks via React context.
13
+ *
14
+ * Recreates the store when configuration props change. Connects the store
15
+ * on mount and destroys it on unmount.
16
+ *
17
+ * @throws {Error} When no `fetch` implementation is available (neither passed as a prop nor available on `globalThis`).
18
+ */
10
19
  function PredictiveSearchProvider({ children, predictiveSearchEndpoint, searchAction, debounceInMs, minTermLength, fetch, limit, limitScope, types, searchableFields, unavailableProducts }) {
11
20
  const store = useMemo(() => createPredictiveSearchStore({
12
21
  predictiveSearchEndpoint,
@@ -29,14 +38,27 @@ function PredictiveSearchProvider({ children, predictiveSearchEndpoint, searchAc
29
38
  getArrayKey(searchableFields),
30
39
  unavailableProducts
31
40
  ]);
41
+ const storeRef = useRef(store);
42
+ useInsertionEffect(() => {
43
+ storeRef.current = store;
44
+ }, [store]);
45
+ const actions = useMemo(() => ({
46
+ search: (term) => storeRef.current.search(term),
47
+ clear: () => storeRef.current.clear()
48
+ }), []);
32
49
  useEffect(() => {
33
50
  store.connect();
34
51
  return () => store.destroy();
35
52
  }, [store]);
36
53
  const contextValue = useMemo(() => ({
37
54
  store,
55
+ actions,
38
56
  searchAction
39
- }), [store, searchAction]);
57
+ }), [
58
+ store,
59
+ actions,
60
+ searchAction
61
+ ]);
40
62
  return createElement(PredictiveSearchContext.Provider, { value: contextValue }, children);
41
63
  }
42
64
  function getArrayKey(values) {
@@ -84,15 +106,35 @@ function usePredictiveSearch(selector, isEqual) {
84
106
  };
85
107
  return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
86
108
  }
109
+ /**
110
+ * Returns `search` and `clear` methods that always target the provider's
111
+ * current store. Their identity is stable across re-renders, even
112
+ * when a config prop change recreates the store.
113
+ *
114
+ * Must be used inside a {@link PredictiveSearchProvider}.
115
+ *
116
+ * @throws {Error} When called outside a PredictiveSearchProvider.
117
+ */
87
118
  function usePredictiveSearchActions() {
88
- const store = useRequiredStore("usePredictiveSearchActions");
89
- return useMemo(() => ({
90
- search: store.search,
91
- clear: store.clear
92
- }), [store]);
119
+ return useRequiredContext("usePredictiveSearchActions").actions;
93
120
  }
121
+ /**
122
+ * Returns `formProps` and `register` for building a progressively-enhanced
123
+ * search form.
124
+ *
125
+ * `formProps()` generates form element attributes including the search
126
+ * action URL. `register("query")` generates input attributes and calls the
127
+ * store's search action on every change event. The store debounces requests
128
+ * and resets state for terms shorter than `minTermLength`.
129
+ * Both support an optional callback that receives the event and extracted
130
+ * term, and respect `event.preventDefault()` to cancel the automatic behavior.
131
+ *
132
+ * Must be used inside a {@link PredictiveSearchProvider}.
133
+ *
134
+ * @throws {Error} When called outside a PredictiveSearchProvider.
135
+ */
94
136
  function usePredictiveSearchForm() {
95
- const { searchAction, store } = useRequiredContext("usePredictiveSearchForm");
137
+ const { searchAction, actions } = useRequiredContext("usePredictiveSearchForm");
96
138
  const coreRegister = useMemo(() => createPredictiveSearchFormRegister(), []);
97
139
  const register = useCallback((field, props = {}) => {
98
140
  const { onChange, ...attributes } = props;
@@ -104,10 +146,10 @@ function usePredictiveSearchForm() {
104
146
  const term = event.currentTarget.value;
105
147
  onChange?.(event, term);
106
148
  if (event.defaultPrevented) return;
107
- store.search(term);
149
+ actions.search(term);
108
150
  }
109
151
  };
110
- }, [coreRegister, store]);
152
+ }, [coreRegister, actions]);
111
153
  return {
112
154
  formProps: useCallback((props = {}) => {
113
155
  const { onSubmit, preventDefault, ...attributes } = props;
@@ -120,10 +162,10 @@ function usePredictiveSearchForm() {
120
162
  if (event.defaultPrevented) return;
121
163
  if (!preventDefault) return;
122
164
  event.preventDefault();
123
- store.search(term);
165
+ actions.search(term);
124
166
  }
125
167
  };
126
- }, [searchAction, store]),
168
+ }, [searchAction, actions]),
127
169
  register
128
170
  };
129
171
  }
@@ -1 +1 @@
1
- {"version":3,"file":"predictive-search.mjs","names":[],"sources":["../../../src/react/predictive-search.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type ChangeEvent,\n type FormHTMLAttributes,\n type InputHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport {\n createPredictiveSearchFormRegister,\n createPredictiveSearchStore,\n getPredictiveSearchFormAttributes,\n readPredictiveSearchFormTerm,\n type CreatePredictiveSearchStoreOptions,\n type PredictiveSearchFormRegister as CorePredictiveSearchFormRegister,\n type PredictiveSearchData,\n type PredictiveSearchState,\n type PredictiveSearchStore,\n} from \"../core/predictive-search\";\n\nconst CONFIG_ARRAY_SEPARATOR = \"\\u0000\";\n\ntype PredictiveSearchContextValue = {\n store: PredictiveSearchStore;\n searchAction?: string;\n};\n\nconst PredictiveSearchContext = createContext<PredictiveSearchContextValue | null>(null);\n\nexport type PredictiveSearchProviderProps = CreatePredictiveSearchStoreOptions & {\n children?: ReactNode;\n searchAction?: string;\n};\n\nexport type PredictiveSearchActions = Pick<PredictiveSearchStore, \"search\" | \"clear\">;\n\nexport type PredictiveSearchFormPropsOptions = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"onSubmit\"\n> & {\n preventDefault?: boolean;\n onSubmit?: (event: SubmitEvent<HTMLFormElement>, term: string) => void;\n};\n\nexport type PredictiveSearchQueryInputPropsOptions = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"autoCapitalize\" | \"autoComplete\" | \"name\" | \"onChange\" | \"spellCheck\" | \"type\"\n> & {\n onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;\n};\n\ntype PredictiveSearchFormField = Parameters<CorePredictiveSearchFormRegister>[0];\n\nexport type PredictiveSearchFormRegister = (\n field: PredictiveSearchFormField,\n options?: PredictiveSearchQueryInputPropsOptions,\n) => InputHTMLAttributes<HTMLInputElement>;\n\nexport type PredictiveSearchFormResult = {\n formProps(options?: PredictiveSearchFormPropsOptions): FormHTMLAttributes<HTMLFormElement>;\n register: PredictiveSearchFormRegister;\n};\n\nexport function PredictiveSearchProvider({\n children,\n predictiveSearchEndpoint,\n searchAction,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n}: PredictiveSearchProviderProps) {\n const typesKey = getArrayKey(types);\n const searchableFieldsKey = getArrayKey(searchableFields);\n\n const store = useMemo(\n () =>\n createPredictiveSearchStore({\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n }),\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- array keys are the semantic dependencies\n [\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n typesKey,\n searchableFieldsKey,\n unavailableProducts,\n ],\n );\n\n useEffect(() => {\n store.connect();\n return () => store.destroy();\n }, [store]);\n\n const contextValue = useMemo(() => ({ store, searchAction }), [store, searchAction]);\n\n return createElement(PredictiveSearchContext.Provider, { value: contextValue }, children);\n}\n\nfunction getArrayKey(values: readonly string[] | undefined): string {\n return values?.join(CONFIG_ARRAY_SEPARATOR) ?? \"\";\n}\n\nfunction useRequiredStore<TData extends PredictiveSearchData>(\n hookName: string,\n): PredictiveSearchStore<TData> {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n // oxlint-disable-next-line @typescript-eslint/consistent-type-assertions -- context stores the runtime object while hooks preserve caller-provided result typing\n return context.store as PredictiveSearchStore<TData>;\n}\n\nfunction useRequiredContext(hookName: string): PredictiveSearchContextValue {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n return context;\n}\n\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n>(): PredictiveSearchState<TData>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): 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): PredictiveSearchState<TData> | S {\n const store = useRequiredStore<TData>(\"usePredictiveSearch\");\n const cachedRef = useRef<{\n state: unknown;\n selector: typeof selector;\n value: PredictiveSearchState<TData> | S;\n } | null>(null);\n\n const getSnapshot = () => {\n const state = store.getState();\n\n if (\n cachedRef.current &&\n cachedRef.current.state === state &&\n cachedRef.current.selector === selector\n ) {\n return cachedRef.current.value;\n }\n\n if (!selector) {\n cachedRef.current = { state, selector, value: state };\n return state;\n }\n\n const next = selector(state);\n if (cachedRef.current && isEqual?.(cachedRef.current.value as S, next)) {\n cachedRef.current = { state, selector, value: cachedRef.current.value };\n return cachedRef.current.value;\n }\n\n cachedRef.current = { state, selector, value: next };\n return next;\n };\n\n return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\nexport function usePredictiveSearchActions(): PredictiveSearchActions {\n const store = useRequiredStore(\"usePredictiveSearchActions\");\n\n return useMemo(\n () => ({\n search: store.search,\n clear: store.clear,\n }),\n [store],\n );\n}\n\nexport function usePredictiveSearchForm(): PredictiveSearchFormResult {\n const { searchAction, store } = useRequiredContext(\"usePredictiveSearchForm\");\n const coreRegister = useMemo(() => createPredictiveSearchFormRegister(), []);\n\n const register = useCallback<PredictiveSearchFormRegister>(\n (field, props = {}) => {\n const { onChange, ...attributes } = props;\n const coreAttributes = coreRegister(field);\n\n return {\n ...attributes,\n ...coreAttributes,\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n const term = event.currentTarget.value;\n onChange?.(event, term);\n if (event.defaultPrevented) return;\n void store.search(term);\n },\n };\n },\n [coreRegister, store],\n );\n\n const formProps = useCallback(\n (props: PredictiveSearchFormPropsOptions = {}): FormHTMLAttributes<HTMLFormElement> => {\n const { onSubmit, preventDefault, ...attributes } = props;\n\n return {\n ...getPredictiveSearchFormAttributes(searchAction),\n ...attributes,\n onSubmit: (event: SubmitEvent<HTMLFormElement>) => {\n const term = readPredictiveSearchFormTerm(new FormData(event.currentTarget));\n onSubmit?.(event, term);\n if (event.defaultPrevented) return;\n if (!preventDefault) return;\n event.preventDefault();\n void store.search(term);\n },\n };\n },\n [searchAction, store],\n );\n\n return { formProps, register };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"predictive-search.mjs","names":[],"sources":["../../../src/react/predictive-search.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useInsertionEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type ChangeEvent,\n type FormHTMLAttributes,\n type InputHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport {\n createPredictiveSearchFormRegister,\n createPredictiveSearchStore,\n getPredictiveSearchFormAttributes,\n readPredictiveSearchFormTerm,\n type CreatePredictiveSearchStoreOptions,\n type PredictiveSearchActions,\n type PredictiveSearchFormRegister as CorePredictiveSearchFormRegister,\n type PredictiveSearchData,\n type PredictiveSearchState,\n type PredictiveSearchStore,\n} from \"../core/predictive-search\";\n\nconst CONFIG_ARRAY_SEPARATOR = \"\\u0000\";\n\ntype PredictiveSearchContextValue = {\n store: PredictiveSearchStore;\n actions: PredictiveSearchActions;\n searchAction?: string;\n};\n\nconst PredictiveSearchContext = createContext<PredictiveSearchContextValue | null>(null);\n\n/** Props for the {@link PredictiveSearchProvider} component. */\nexport type PredictiveSearchProviderProps = CreatePredictiveSearchStoreOptions & {\n children?: ReactNode;\n /** Form action URL for progressive enhancement. Used by {@link usePredictiveSearchForm} to set the form's `action` attribute so the search works without JavaScript. Falls back to `\"/search\"` when omitted. */\n searchAction?: string;\n};\n\nexport type { PredictiveSearchActions };\n\n/**\n * Options for the form props builder returned by {@link usePredictiveSearchForm}.\n *\n * Accepts all standard form HTML attributes except `onSubmit`, which is\n * replaced by a version that provides the extracted search term.\n */\nexport type PredictiveSearchFormPropsOptions = Omit<\n FormHTMLAttributes<HTMLFormElement>,\n \"onSubmit\"\n> & {\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<HTMLFormElement>, term: string) => void;\n};\n\n/**\n * Options for the query input props builder returned by\n * {@link usePredictiveSearchForm}'s `register` method.\n *\n * Accepts all standard input HTML attributes except those controlled by the\n * form registration.\n */\nexport type PredictiveSearchQueryInputPropsOptions = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"autoCapitalize\" | \"autoComplete\" | \"name\" | \"onChange\" | \"spellCheck\" | \"type\"\n> & {\n /** Called on change with the change event and the current input value. Call `event.preventDefault()` to skip the automatic search trigger. */\n onChange?: (event: ChangeEvent<HTMLInputElement>, term: string) => void;\n};\n\ntype PredictiveSearchFormField = Parameters<CorePredictiveSearchFormRegister>[0];\n\n/** Generates input element attributes for a named form field. */\nexport type PredictiveSearchFormRegister = (\n field: PredictiveSearchFormField,\n options?: PredictiveSearchQueryInputPropsOptions,\n) => InputHTMLAttributes<HTMLInputElement>;\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): FormHTMLAttributes<HTMLFormElement>;\n /** Generates input element attributes for a named form field and wires up the search trigger. */\n register: PredictiveSearchFormRegister;\n};\n\n/**\n * Creates and manages a predictive search store, providing it to descendant\n * hooks via React context.\n *\n * Recreates the store when configuration props change. Connects the store\n * on mount and destroys it on unmount.\n *\n * @throws {Error} When no `fetch` implementation is available (neither passed as a prop nor available on `globalThis`).\n */\nexport function PredictiveSearchProvider({\n children,\n predictiveSearchEndpoint,\n searchAction,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n}: PredictiveSearchProviderProps) {\n const typesKey = getArrayKey(types);\n const searchableFieldsKey = getArrayKey(searchableFields);\n\n const store = useMemo(\n () =>\n createPredictiveSearchStore({\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n types,\n searchableFields,\n unavailableProducts,\n }),\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- array keys are the semantic dependencies\n [\n predictiveSearchEndpoint,\n debounceInMs,\n minTermLength,\n fetch,\n limit,\n limitScope,\n typesKey,\n searchableFieldsKey,\n unavailableProducts,\n ],\n );\n\n const storeRef = useRef(store);\n // Actions outlive store swaps by reading the committed store at call time. Only an insertion\n // effect is both commit-only and early enough: a render-phase write can point actions at a\n // store from a render React never commits, and layout and passive effects run child-first,\n // so a child effect searching in the same commit would still see the old store.\n useInsertionEffect(() => {\n storeRef.current = store;\n }, [store]);\n const actions = useMemo<PredictiveSearchActions>(\n () => ({\n search: (term) => storeRef.current.search(term),\n clear: () => storeRef.current.clear(),\n }),\n [],\n );\n\n useEffect(() => {\n store.connect();\n return () => store.destroy();\n }, [store]);\n\n const contextValue = useMemo(\n () => ({ store, actions, searchAction }),\n [store, actions, searchAction],\n );\n\n return createElement(PredictiveSearchContext.Provider, { value: contextValue }, children);\n}\n\nfunction getArrayKey(values: readonly string[] | undefined): string {\n return values?.join(CONFIG_ARRAY_SEPARATOR) ?? \"\";\n}\n\nfunction useRequiredStore<TData extends PredictiveSearchData>(\n hookName: string,\n): PredictiveSearchStore<TData> {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n // oxlint-disable-next-line @typescript-eslint/consistent-type-assertions -- context stores the runtime object while hooks preserve caller-provided result typing\n return context.store as PredictiveSearchStore<TData>;\n}\n\nfunction useRequiredContext(hookName: string): PredictiveSearchContextValue {\n const context = useContext(PredictiveSearchContext);\n if (!context) {\n throw new Error(`${hookName} must be used inside a <PredictiveSearchProvider>.`);\n }\n\n return context;\n}\n\n/**\n * Subscribes to the predictive search store's state.\n *\n * Without arguments, returns the full {@link PredictiveSearchState}. With a\n * `selector`, returns a derived value that only triggers re-renders when the\n * selected value changes (reference equality by default, or a custom\n * `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>(): PredictiveSearchState<TData>;\nexport function usePredictiveSearch<\n TData extends PredictiveSearchData = PredictiveSearchData,\n S = PredictiveSearchState<TData>,\n>(selector: (state: PredictiveSearchState<TData>) => S, isEqual?: (a: S, b: S) => boolean): 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): PredictiveSearchState<TData> | S {\n const store = useRequiredStore<TData>(\"usePredictiveSearch\");\n const cachedRef = useRef<{\n state: unknown;\n selector: typeof selector;\n value: PredictiveSearchState<TData> | S;\n } | null>(null);\n\n const getSnapshot = () => {\n const state = store.getState();\n\n if (\n cachedRef.current &&\n cachedRef.current.state === state &&\n cachedRef.current.selector === selector\n ) {\n return cachedRef.current.value;\n }\n\n if (!selector) {\n cachedRef.current = { state, selector, value: state };\n return state;\n }\n\n const next = selector(state);\n if (cachedRef.current && isEqual?.(cachedRef.current.value as S, next)) {\n cachedRef.current = { state, selector, value: cachedRef.current.value };\n return cachedRef.current.value;\n }\n\n cachedRef.current = { state, selector, value: next };\n return next;\n };\n\n return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Returns `search` and `clear` methods that always target the provider's\n * current store. Their identity is stable across re-renders, even\n * when a config prop change recreates the store.\n *\n * Must be used inside a {@link PredictiveSearchProvider}.\n *\n * @throws {Error} When called outside a PredictiveSearchProvider.\n */\nexport function usePredictiveSearchActions(): PredictiveSearchActions {\n return useRequiredContext(\"usePredictiveSearchActions\").actions;\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 change 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 { searchAction, actions } = useRequiredContext(\"usePredictiveSearchForm\");\n const coreRegister = useMemo(() => createPredictiveSearchFormRegister(), []);\n\n const register = useCallback<PredictiveSearchFormRegister>(\n (field, props = {}) => {\n const { onChange, ...attributes } = props;\n const coreAttributes = coreRegister(field);\n\n return {\n ...attributes,\n ...coreAttributes,\n onChange: (event: ChangeEvent<HTMLInputElement>) => {\n const term = event.currentTarget.value;\n onChange?.(event, term);\n if (event.defaultPrevented) return;\n void actions.search(term);\n },\n };\n },\n [coreRegister, actions],\n );\n\n const formProps = useCallback(\n (props: PredictiveSearchFormPropsOptions = {}): FormHTMLAttributes<HTMLFormElement> => {\n const { onSubmit, preventDefault, ...attributes } = props;\n\n return {\n ...getPredictiveSearchFormAttributes(searchAction),\n ...attributes,\n onSubmit: (event: SubmitEvent<HTMLFormElement>) => {\n const term = readPredictiveSearchFormTerm(new FormData(event.currentTarget));\n onSubmit?.(event, term);\n if (event.defaultPrevented) return;\n if (!preventDefault) return;\n event.preventDefault();\n void actions.search(term);\n },\n };\n },\n [searchAction, actions],\n );\n\n return { formProps, register };\n}\n"],"mappings":"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"}
@@ -20,6 +20,7 @@ interface UseProductFormResult<TProduct extends ProductInput> {
20
20
  }) => FormHTMLAttributes<HTMLFormElement>;
21
21
  errors: ProductFormErrors;
22
22
  matchedLineItem: CartLine | null;
23
+ /** Whether a form submission is currently in flight. */
23
24
  pending: boolean;
24
25
  selectOption: (name: string, value: string) => VariantSelectionResult<ProductVariantFrom<TProduct>>;
25
26
  }
@@ -75,9 +75,12 @@ function createProductComponents() {
75
75
  return ctx;
76
76
  }
77
77
  function ProductProvider({ product, onSelect, children }) {
78
- const cartStore = useCartStore();
78
+ const cartStore = useCartStore("ProductProvider");
79
79
  const store = useMemo(() => createProductFormStore(product, cartStore), []);
80
- useEffect(() => () => store.destroy(), [store]);
80
+ useEffect(() => {
81
+ store.connect();
82
+ return () => store.destroy();
83
+ }, [store]);
81
84
  const productKey = `${product.id}:${product.selectedOrFirstAvailableVariant?.id ?? ""}`;
82
85
  const mountedRef = useRef(false);
83
86
  useEffect(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"product.mjs","names":[],"sources":["../../../src/react/product.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\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<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }) => FormHTMLAttributes<HTMLFormElement>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n pending: boolean;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\n/** Props for the `ProductProvider` returned by {@link createProductComponents}. */\nexport interface ProductProviderProps<TProduct extends ProductInput> {\n product: TProduct;\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n children?: ReactNode;\n}\n\n/** Return value of the `useProduct` hook 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\n// ---------------------------------------------------------------------------\n// Standalone hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribes to a {@link ProductFormStore} and returns form-ready state.\n *\n * This is a pure subscription hook — it does **not** manage store lifecycle.\n * Create the store with `createProductFormStore` and manage its lifecycle\n * (hydration, destruction) yourself, or use `createProductComponents` for a\n * provider-based approach.\n */\nexport function useProductForm<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n options?: UseProductFormOptions<TProduct>,\n): UseProductFormResult<TProduct> {\n const onSelectRef = useRef(options?.onSelect);\n onSelectRef.current = options?.onSelect;\n\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n const [pending, setPending] = useState(false);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store],\n );\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- state is the full reactive snapshot; selectOption is stable\n const register = useMemo(\n () => createProductFormRegister(state.selectedVariant, selectOption),\n [state, selectOption],\n );\n\n const formProps = useCallback(\n (opts?: {\n beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }): FormHTMLAttributes<HTMLFormElement> => ({\n onSubmit: (e: SubmitEvent<HTMLFormElement>) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n setPending(true);\n store\n .handleFormSubmit(e.nativeEvent)\n .then(\n () => opts?.afterSubmit?.(e),\n (error: unknown) => {\n // Cart user/network errors are surfaced via the reactive `errors`\n // state. Thrown exceptions (missing Shopify script, invalid form\n // target) are programming errors — log them for dev visibility.\n log.error(\"form submission error\", { error });\n },\n )\n .finally(() => setPending(false));\n },\n method: \"post\",\n action: getCartEndpoint(),\n }),\n [store],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n register,\n formProps,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n pending,\n selectOption,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\ninterface ProductContextValue<TProduct extends ProductInput> {\n store: ProductFormStore<TProduct>;\n onSelectRef: React.RefObject<\n ((result: ValidProductSelectionResult<TProduct>) => void) | undefined\n >;\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 *\n * @example\n * ```ts\n * const { ProductProvider, useProduct, useProductForm } =\n * createProductComponents<MyProductType>();\n * ```\n */\nexport function createProductComponents<TProduct extends ProductInput>(): {\n ProductProvider: (props: ProductProviderProps<TProduct>) => ReactNode;\n useProduct: () => UseProductResult<TProduct>;\n useProductForm: () => UseProductFormResult<TProduct>;\n} {\n const Context = createContext<ProductContextValue<TProduct> | null>(null);\n\n function useProductContext(hookName: string): ProductContextValue<TProduct> {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `${hookName} must be used inside a <ProductProvider>. ` +\n \"Wrap your component tree with the ProductProvider from createProductComponents(), \" +\n \"or use the standalone useProductForm(store) hook instead.\",\n );\n }\n return ctx;\n }\n\n function ProductProvider({ product, onSelect, children }: ProductProviderProps<TProduct>) {\n const cartStore = useCartStore();\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store is intentionally created once\n const store = useMemo(() => createProductFormStore<TProduct>(product, cartStore), []);\n\n useEffect(() => () => store.destroy(), [store]);\n\n const productKey = `${product.id}:${product.selectedOrFirstAvailableVariant?.id ?? \"\"}`;\n const mountedRef = useRef(false);\n useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n store.hydrate(product);\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- productKey is the semantic dep\n }, [productKey]);\n\n const onSelectRef = useRef(onSelect);\n onSelectRef.current = onSelect;\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- onSelectRef is a stable ref object\n const value = useMemo<ProductContextValue<TProduct>>(() => ({ store, onSelectRef }), [store]);\n\n return createElement(Context.Provider, { value }, children);\n }\n\n function useProductHook(): UseProductResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProduct\");\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store, onSelectRef],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n selectOption,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n };\n }\n\n function useProductFormHook(): UseProductFormResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProductForm\");\n return useProductForm<TProduct>(store, { onSelect: onSelectRef.current });\n }\n\n return {\n ProductProvider,\n useProduct: useProductHook,\n useProductForm: useProductFormHook,\n };\n}\n"],"mappings":";;;;;;;;;AAiCA,MAAM,MAAM,UAAU,UAAU;;;;;;;;;AAkEhC,SAAgB,eACd,OACA,SACgC;CAChC,MAAM,cAAc,OAAO,SAAS,SAAS;AAC7C,aAAY,UAAU,SAAS;CAE/B,MAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,UAAU,MAAM,SAAS;CACnF,MAAM,CAAC,SAAS,cAAc,SAAS,MAAM;CAE7C,MAAM,eAAe,aAClB,MAAc,UAAkB;EAC/B,MAAM,SAAS,MAAM,aAAa,MAAM,MAAM;AAC9C,MAAI,OAAO,WAAW,UACpB,aAAY,UAAU,OAAO;AAE/B,SAAO;IAET,CAAC,MAAM,CACR;CAGD,MAAM,WAAW,cACT,0BAA0B,MAAM,iBAAiB,aAAa,EACpE,CAAC,OAAO,aAAa,CACtB;CAED,MAAM,YAAY,aACf,UAG2C;EAC1C,WAAW,MAAoC;AAC7C,SAAM,eAAe,EAAE;AACvB,OAAI,EAAE,iBAAkB;AACxB,KAAE,gBAAgB;AAClB,cAAW,KAAK;AAChB,SACG,iBAAiB,EAAE,YAAY,CAC/B,WACO,MAAM,cAAc,EAAE,GAC3B,UAAmB;AAIlB,QAAI,MAAM,yBAAyB,EAAE,OAAO,CAAC;KAEhD,CACA,cAAc,WAAW,MAAM,CAAC;;EAErC,QAAQ;EACR,QAAQ,iBAAiB;EAC1B,GACD,CAAC,MAAM,CACR;AAED,QAAO;EACL,SAAS,MAAM;EACf,iBAAiB,MAAM;EACvB;EACA;EACA,QAAQ,MAAM;EACd,iBAAiB,MAAM;EACvB;EACA;EACD;;;;;;;;;;;;;;;;;;AA8BH,SAAgB,0BAId;CACA,MAAM,UAAU,cAAoD,KAAK;CAEzE,SAAS,kBAAkB,UAAiD;EAC1E,MAAM,MAAM,WAAW,QAAQ;AAC/B,MAAI,CAAC,IACH,OAAM,IAAI,MACR,GAAG,SAAS,uLAGb;AAEH,SAAO;;CAGT,SAAS,gBAAgB,EAAE,SAAS,UAAU,YAA4C;EACxF,MAAM,YAAY,cAAc;EAGhC,MAAM,QAAQ,cAAc,uBAAiC,SAAS,UAAU,EAAE,EAAE,CAAC;AAErF,wBAAsB,MAAM,SAAS,EAAE,CAAC,MAAM,CAAC;EAE/C,MAAM,aAAa,GAAG,QAAQ,GAAG,GAAG,QAAQ,iCAAiC,MAAM;EACnF,MAAM,aAAa,OAAO,MAAM;AAChC,kBAAgB;AACd,OAAI,CAAC,WAAW,SAAS;AACvB,eAAW,UAAU;AACrB;;AAEF,SAAM,QAAQ,QAAQ;KAErB,CAAC,WAAW,CAAC;EAEhB,MAAM,cAAc,OAAO,SAAS;AACpC,cAAY,UAAU;EAGtB,MAAM,QAAQ,eAA8C;GAAE;GAAO;GAAa,GAAG,CAAC,MAAM,CAAC;AAE7F,SAAO,cAAc,QAAQ,UAAU,EAAE,OAAO,EAAE,SAAS;;CAG7D,SAAS,iBAA6C;EACpD,MAAM,EAAE,OAAO,gBAAgB,kBAAkB,aAAa;EAC9D,MAAM,QAAQ,qBAAqB,MAAM,WAAW,MAAM,UAAU,MAAM,SAAS;EAEnF,MAAM,eAAe,aAClB,MAAc,UAAkB;GAC/B,MAAM,SAAS,MAAM,aAAa,MAAM,MAAM;AAC9C,OAAI,OAAO,WAAW,UACpB,aAAY,UAAU,OAAO;AAE/B,UAAO;KAET,CAAC,OAAO,YAAY,CACrB;AAED,SAAO;GACL,SAAS,MAAM;GACf,iBAAiB,MAAM;GACvB;GACA,QAAQ,MAAM;GACd,iBAAiB,MAAM;GACxB;;CAGH,SAAS,qBAAqD;EAC5D,MAAM,EAAE,OAAO,gBAAgB,kBAAkB,iBAAiB;AAClE,SAAO,eAAyB,OAAO,EAAE,UAAU,YAAY,SAAS,CAAC;;AAG3E,QAAO;EACL;EACA,YAAY;EACZ,gBAAgB;EACjB"}
1
+ {"version":3,"file":"product.mjs","names":[],"sources":["../../../src/react/product.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\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<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }) => FormHTMLAttributes<HTMLFormElement>;\n errors: ProductFormErrors;\n matchedLineItem: CartLine | null;\n /** Whether a form submission is currently in flight. */\n pending: boolean;\n selectOption: (\n name: string,\n value: string,\n ) => VariantSelectionResult<ProductVariantFrom<TProduct>>;\n}\n\n/** Props for the `ProductProvider` returned by {@link createProductComponents}. */\nexport interface ProductProviderProps<TProduct extends ProductInput> {\n product: TProduct;\n onSelect?: (result: ValidProductSelectionResult<TProduct>) => void;\n children?: ReactNode;\n}\n\n/** Return value of the `useProduct` hook 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\n// ---------------------------------------------------------------------------\n// Standalone hook\n// ---------------------------------------------------------------------------\n\n/**\n * Subscribes to a {@link ProductFormStore} and returns form-ready state.\n *\n * This is a pure subscription hook — it does **not** manage store lifecycle.\n * Create the store with `createProductFormStore` and manage its lifecycle\n * (hydration, destruction) yourself, or use `createProductComponents` for a\n * provider-based approach.\n */\nexport function useProductForm<TProduct extends ProductInput>(\n store: ProductFormStore<TProduct>,\n options?: UseProductFormOptions<TProduct>,\n): UseProductFormResult<TProduct> {\n const onSelectRef = useRef(options?.onSelect);\n onSelectRef.current = options?.onSelect;\n\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n const [pending, setPending] = useState(false);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store],\n );\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- state is the full reactive snapshot; selectOption is stable\n const register = useMemo(\n () => createProductFormRegister(state.selectedVariant, selectOption),\n [state, selectOption],\n );\n\n const formProps = useCallback(\n (opts?: {\n beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n afterSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;\n }): FormHTMLAttributes<HTMLFormElement> => ({\n onSubmit: (e: SubmitEvent<HTMLFormElement>) => {\n opts?.beforeSubmit?.(e);\n if (e.defaultPrevented) return;\n e.preventDefault();\n setPending(true);\n store\n .handleFormSubmit(e.nativeEvent)\n .then(\n () => opts?.afterSubmit?.(e),\n (error: unknown) => {\n // Cart user/network errors are surfaced via the reactive `errors`\n // state. Thrown exceptions (missing Shopify script, invalid form\n // target) are programming errors — log them for dev visibility.\n log.error(\"form submission error\", { error });\n },\n )\n .finally(() => setPending(false));\n },\n method: \"post\",\n action: getCartEndpoint(),\n }),\n [store],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n register,\n formProps,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n pending,\n selectOption,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\ninterface ProductContextValue<TProduct extends ProductInput> {\n store: ProductFormStore<TProduct>;\n onSelectRef: React.RefObject<\n ((result: ValidProductSelectionResult<TProduct>) => void) | undefined\n >;\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 *\n * @example\n * ```ts\n * const { ProductProvider, useProduct, useProductForm } =\n * createProductComponents<MyProductType>();\n * ```\n */\nexport function createProductComponents<TProduct extends ProductInput>(): {\n ProductProvider: (props: ProductProviderProps<TProduct>) => ReactNode;\n useProduct: () => UseProductResult<TProduct>;\n useProductForm: () => UseProductFormResult<TProduct>;\n} {\n const Context = createContext<ProductContextValue<TProduct> | null>(null);\n\n function useProductContext(hookName: string): ProductContextValue<TProduct> {\n const ctx = useContext(Context);\n if (!ctx) {\n throw new Error(\n `${hookName} must be used inside a <ProductProvider>. ` +\n \"Wrap your component tree with the ProductProvider from createProductComponents(), \" +\n \"or use the standalone useProductForm(store) hook instead.\",\n );\n }\n return ctx;\n }\n\n function ProductProvider({ product, onSelect, children }: ProductProviderProps<TProduct>) {\n const cartStore = useCartStore(\"ProductProvider\");\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store is intentionally created once\n const store = useMemo(() => createProductFormStore<TProduct>(product, cartStore), []);\n\n useEffect(() => {\n store.connect();\n return () => store.destroy();\n }, [store]);\n\n const productKey = `${product.id}:${product.selectedOrFirstAvailableVariant?.id ?? \"\"}`;\n const mountedRef = useRef(false);\n useEffect(() => {\n if (!mountedRef.current) {\n mountedRef.current = true;\n return;\n }\n store.hydrate(product);\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- productKey is the semantic dep\n }, [productKey]);\n\n const onSelectRef = useRef(onSelect);\n onSelectRef.current = onSelect;\n\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- onSelectRef is a stable ref object\n const value = useMemo<ProductContextValue<TProduct>>(() => ({ store, onSelectRef }), [store]);\n\n return createElement(Context.Provider, { value }, children);\n }\n\n function useProductHook(): UseProductResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProduct\");\n const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);\n\n const selectOption = useCallback(\n (name: string, value: string) => {\n const result = store.selectOption(name, value);\n if (result.status !== \"invalid\") {\n onSelectRef.current?.(result);\n }\n return result;\n },\n [store, onSelectRef],\n );\n\n return {\n options: state.options,\n selectedVariant: state.selectedVariant,\n selectOption,\n errors: state.errors,\n matchedLineItem: state.matchedLineItem,\n };\n }\n\n function useProductFormHook(): UseProductFormResult<TProduct> {\n const { store, onSelectRef } = useProductContext(\"useProductForm\");\n return useProductForm<TProduct>(store, { onSelect: onSelectRef.current });\n }\n\n return {\n ProductProvider,\n useProduct: useProductHook,\n useProductForm: useProductFormHook,\n };\n}\n"],"mappings":"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"}
@@ -9,6 +9,7 @@ function ShopifyScripts(options) {
9
9
  const { consent, navigate, routes, webMcp = true, ...scriptOptions } = options;
10
10
  useEffect(() => {
11
11
  initializeShopifyScripts({
12
+ consent,
12
13
  navigate,
13
14
  routes,
14
15
  webMcp
@@ -1 +1 @@
1
- {"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../../src/react/shopify-scripts.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, Fragment, useEffect } from \"react\";\nimport type * as React from \"react\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyRoutesOptions,\n type ShopifyScriptTagsOptions,\n} from \"../core/shopify-scripts\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ndeclare module \"react\" {\n namespace JSX {\n interface IntrinsicElements {\n \"shopify-chat\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;\n }\n }\n}\n\nexport function ShopifyScripts(options: ShopifyScriptsProps) {\n const { consent, navigate, routes, webMcp = true, ...scriptOptions } = options;\n\n useEffect(() => {\n void initializeShopifyScripts({ navigate, routes, webMcp });\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- ShopifyScripts browser startup is initialized once from initial props.\n }, []);\n\n return createElement(\n Fragment,\n null,\n getShopifyScriptTags({ ...scriptOptions, consent }).tags.map(\n ({ tagName, attributes, innerHTML }, index) =>\n createElement(tagName, {\n key: index,\n ...getReactAttributes(attributes),\n ...(innerHTML ? { dangerouslySetInnerHTML: { __html: innerHTML } } : {}),\n }),\n ),\n );\n}\n\nfunction getReactAttributes(attributes: Record<string, string | boolean> = {}) {\n const reactAttributes = Object.fromEntries(\n Object.entries(attributes).map(([name, value]) => [\n name === \"crossorigin\" ? \"crossOrigin\" : name,\n value,\n ]),\n );\n\n if (attributes.nonce !== undefined) {\n // Browsers intentionally hide nonce content attributes from getAttribute(),\n // which can make React report a false hydration mismatch for SSR scripts.\n reactAttributes.suppressHydrationWarning = true;\n }\n\n if (attributes.async === true && typeof attributes.src === \"string\") {\n // React hoists async scripts without handlers, which can let them execute before\n // the inline Shopify bootstrap scripts that configure their globals.\n return { ...reactAttributes, onLoad: disableReactScriptHoisting };\n }\n\n return reactAttributes;\n}\n\nconst disableReactScriptHoisting = () => {};\n"],"mappings":";;;;;;;AA0BA,SAAgB,eAAe,SAA8B;CAC3D,MAAM,EAAE,SAAS,UAAU,QAAQ,SAAS,MAAM,GAAG,kBAAkB;AAEvE,iBAAgB;AACd,EAAK,yBAAyB;GAAE;GAAU;GAAQ;GAAQ,CAAC;IAE1D,EAAE,CAAC;AAEN,QAAO,cACL,UACA,MACA,qBAAqB;EAAE,GAAG;EAAe;EAAS,CAAC,CAAC,KAAK,KACtD,EAAE,SAAS,YAAY,aAAa,UACnC,cAAc,SAAS;EACrB,KAAK;EACL,GAAG,mBAAmB,WAAW;EACjC,GAAI,YAAY,EAAE,yBAAyB,EAAE,QAAQ,WAAW,EAAE,GAAG,EAAE;EACxE,CAAC,CACL,CACF;;AAGH,SAAS,mBAAmB,aAA+C,EAAE,EAAE;CAC7E,MAAM,kBAAkB,OAAO,YAC7B,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,MAAM,WAAW,CAChD,SAAS,gBAAgB,gBAAgB,MACzC,MACD,CAAC,CACH;AAED,KAAI,WAAW,UAAU,OAGvB,iBAAgB,2BAA2B;AAG7C,KAAI,WAAW,UAAU,QAAQ,OAAO,WAAW,QAAQ,SAGzD,QAAO;EAAE,GAAG;EAAiB,QAAQ;EAA4B;AAGnE,QAAO;;AAGT,MAAM,mCAAmC"}
1
+ {"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../../src/react/shopify-scripts.tsx"],"sourcesContent":["\"use client\";\n\nimport { createElement, Fragment, useEffect } from \"react\";\nimport type * as React from \"react\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyRoutesOptions,\n type ShopifyScriptTagsOptions,\n} from \"../core/shopify-scripts\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ndeclare module \"react\" {\n namespace JSX {\n interface IntrinsicElements {\n \"shopify-chat\": React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>;\n }\n }\n}\n\nexport function ShopifyScripts(options: ShopifyScriptsProps) {\n const { consent, navigate, routes, webMcp = true, ...scriptOptions } = options;\n\n useEffect(() => {\n void initializeShopifyScripts({ consent, navigate, routes, webMcp });\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- ShopifyScripts browser startup is initialized once from initial props.\n }, []);\n\n return createElement(\n Fragment,\n null,\n getShopifyScriptTags({ ...scriptOptions, consent }).tags.map(\n ({ tagName, attributes, innerHTML }, index) =>\n createElement(tagName, {\n key: index,\n ...getReactAttributes(attributes),\n ...(innerHTML ? { dangerouslySetInnerHTML: { __html: innerHTML } } : {}),\n }),\n ),\n );\n}\n\nfunction getReactAttributes(attributes: Record<string, string | boolean> = {}) {\n const reactAttributes = Object.fromEntries(\n Object.entries(attributes).map(([name, value]) => [\n name === \"crossorigin\" ? \"crossOrigin\" : name,\n value,\n ]),\n );\n\n if (attributes.nonce !== undefined) {\n // Browsers intentionally hide nonce content attributes from getAttribute(),\n // which can make React report a false hydration mismatch for SSR scripts.\n reactAttributes.suppressHydrationWarning = true;\n }\n\n if (attributes.async === true && typeof attributes.src === \"string\") {\n // React hoists async scripts without handlers, which can let them execute before\n // the inline Shopify bootstrap scripts that configure their globals.\n return { ...reactAttributes, onLoad: disableReactScriptHoisting };\n }\n\n return reactAttributes;\n}\n\nconst disableReactScriptHoisting = () => {};\n"],"mappings":";;;;;;;AA0BA,SAAgB,eAAe,SAA8B;CAC3D,MAAM,EAAE,SAAS,UAAU,QAAQ,SAAS,MAAM,GAAG,kBAAkB;AAEvE,iBAAgB;AACd,EAAK,yBAAyB;GAAE;GAAS;GAAU;GAAQ;GAAQ,CAAC;IAEnE,EAAE,CAAC;AAEN,QAAO,cACL,UACA,MACA,qBAAqB;EAAE,GAAG;EAAe;EAAS,CAAC,CAAC,KAAK,KACtD,EAAE,SAAS,YAAY,aAAa,UACnC,cAAc,SAAS;EACrB,KAAK;EACL,GAAG,mBAAmB,WAAW;EACjC,GAAI,YAAY,EAAE,yBAAyB,EAAE,QAAQ,WAAW,EAAE,GAAG,EAAE;EACxE,CAAC,CACL,CACF;;AAGH,SAAS,mBAAmB,aAA+C,EAAE,EAAE;CAC7E,MAAM,kBAAkB,OAAO,YAC7B,OAAO,QAAQ,WAAW,CAAC,KAAK,CAAC,MAAM,WAAW,CAChD,SAAS,gBAAgB,gBAAgB,MACzC,MACD,CAAC,CACH;AAED,KAAI,WAAW,UAAU,OAGvB,iBAAgB,2BAA2B;AAG7C,KAAI,WAAW,UAAU,QAAQ,OAAO,WAAW,QAAQ,SAGzD,QAAO;EAAE,GAAG;EAAiB,QAAQ;EAA4B;AAGnE,QAAO;;AAGT,MAAM,mCAAmC"}
@@ -1,5 +1,5 @@
1
1
  import { CartData, CartState } from "../core/cart/state.mjs";
2
- import { CartStore, CreateCartStoreOptions } from "../core/cart/cart.mjs";
2
+ import { CartActions, CreateCartStoreOptions } from "../core/cart/cart.mjs";
3
3
  import { CartFormRegister } from "../core/cart/form.mjs";
4
4
  import { CartDataFromHandlers } from "../core/cart/server-handlers.mjs";
5
5
  import { ComputedRef, PropType, ShallowRef } from "vue";
@@ -18,8 +18,6 @@ type TypedCartProvider<TData extends CartData> = {
18
18
  };
19
19
  };
20
20
  };
21
- /** Actions for reconciling cart state after updates outside Standard Actions. */
22
- type CartActions = Pick<CartStore, "refresh">;
23
21
  type TypedCartComponents<TData extends CartData> = {
24
22
  CartProvider: TypedCartProvider<TData>;
25
23
  useCart: TypedUseCart<TData>;
@@ -27,12 +25,73 @@ type TypedCartComponents<TData extends CartData> = {
27
25
  useCartActions: typeof useCartActions;
28
26
  useCartForm: typeof useCartForm;
29
27
  };
28
+ /**
29
+ * Returns cart actions for reconciling state after out-of-band mutations.
30
+ *
31
+ * Currently exposes {@link CartStore.refresh} — call it after server-side cart
32
+ * mutations that bypass the form system.
33
+ *
34
+ * @example
35
+ * ```vue
36
+ * <script setup>
37
+ * const { refresh } = useCartActions();
38
+ *
39
+ * async function handleServerAction() {
40
+ * await createCartOnServer();
41
+ * refresh();
42
+ * }
43
+ * </script>
44
+ * ```
45
+ */
30
46
  declare function useCartActions(): CartActions;
47
+ /**
48
+ * Subscribes the {@link CartStore} to the analytics event dispatcher.
49
+ *
50
+ * Call once near the root of your app. Starts tracking on mount via `onMounted`
51
+ * and cleans up via `onScopeDispose`.
52
+ *
53
+ * @example
54
+ * ```vue
55
+ * <script setup>
56
+ * useCartAnalytics();
57
+ * </script>
58
+ * ```
59
+ */
31
60
  declare function useCartAnalytics(): void;
61
+ /**
62
+ * Returns form props, a field register function, and reactive pending state
63
+ * for building cart forms in Vue.
64
+ *
65
+ * Unlike the React hook, the Vue variant includes `isPending` — a reactive
66
+ * object with `initial` (`ComputedRef<boolean>`) for the initial cart load
67
+ * and `lines(lineId?)` for checking per-line or any-line pending state.
68
+ *
69
+ * Note: `interactive: true` returns numeric input attributes but does **not**
70
+ * auto-submit on change — that behavior requires `attachQuantityInput`, which
71
+ * is only wired by the React adapter.
72
+ *
73
+ * @example
74
+ * ```vue
75
+ * <script setup>
76
+ * const { formProps, register, isPending } = useCartForm();
77
+ * </script>
78
+ *
79
+ * <template>
80
+ * <form v-bind="formProps()">
81
+ * <input v-bind="register('lineId', { value: line.id })" />
82
+ * <input v-bind="register('quantity', { value: line.quantity })" />
83
+ * <button v-bind="register('set')" />
84
+ * <button v-bind="register('increase')">+</button>
85
+ * <button v-bind="register('decrease')">−</button>
86
+ * <span v-if="isPending.lines(line.id)">Updating…</span>
87
+ * </form>
88
+ * </template>
89
+ * ```
90
+ */
32
91
  declare function useCartForm(): {
33
92
  formProps: (opts?: {
34
- beforeSubmit?: (e: Event) => void;
35
- afterSubmit?: (e: Event) => void;
93
+ beforeSubmit?: (e: SubmitEvent) => void;
94
+ afterSubmit?: (e: SubmitEvent) => void;
36
95
  }) => Record<string, unknown>;
37
96
  register: CartFormRegister;
38
97
  isPending: {
@@ -40,7 +99,27 @@ declare function useCartForm(): {
40
99
  lines: (lineId?: string) => boolean;
41
100
  };
42
101
  };
102
+ /**
103
+ * Factory that returns typed cart components and composables matched to your
104
+ * server handler's cart query shape.
105
+ *
106
+ * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
107
+ * call, so every composable's {@link CartState} carries your custom cart fields.
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * import type { cartServerHandlers } from "./server/cart.server";
112
+ *
113
+ * const {
114
+ * CartProvider,
115
+ * useCart,
116
+ * useOptionalCart,
117
+ * useCartActions,
118
+ * useCartForm,
119
+ * } = createCartComponents<typeof cartServerHandlers>();
120
+ * ```
121
+ */
43
122
  declare function createCartComponents<THandlers>(): TypedCartComponents<CartDataFromHandlers<THandlers>>;
44
123
  //#endregion
45
- export { CartActions, createCartComponents, useCartActions, useCartAnalytics };
124
+ export { createCartComponents, useCartActions, useCartAnalytics };
46
125
  //# sourceMappingURL=cart.d.mts.map
@@ -25,6 +25,20 @@ function useCartStore(composableName = "useCartStore") {
25
25
  function useOptionalCartStore() {
26
26
  return inject(CartStoreKey, null);
27
27
  }
28
+ /**
29
+ * Vue component that creates and manages a {@link CartStore} instance.
30
+ *
31
+ * Calls {@link CartStore.connect} on mount and {@link CartStore.destroy} on unmount.
32
+ * All cart composables (`useCart`, `useCartForm`, `useCartActions`) must be
33
+ * descendants of this component.
34
+ *
35
+ * @example
36
+ * ```vue
37
+ * <CartProvider :initialData="{ cart: loaderData.cart }">
38
+ * <App />
39
+ * </CartProvider>
40
+ * ```
41
+ */
28
42
  const CartProvider = defineComponent({
29
43
  name: "CartProvider",
30
44
  props: { initialData: {
@@ -47,9 +61,40 @@ const CartProvider = defineComponent({
47
61
  function useCart(selector, isEqual) {
48
62
  return useCartSelector(useCartStore("useCart"), selector ?? ((state) => state), isEqual);
49
63
  }
64
+ /**
65
+ * Returns cart actions for reconciling state after out-of-band mutations.
66
+ *
67
+ * Currently exposes {@link CartStore.refresh} — call it after server-side cart
68
+ * mutations that bypass the form system.
69
+ *
70
+ * @example
71
+ * ```vue
72
+ * <script setup>
73
+ * const { refresh } = useCartActions();
74
+ *
75
+ * async function handleServerAction() {
76
+ * await createCartOnServer();
77
+ * refresh();
78
+ * }
79
+ * <\/script>
80
+ * ```
81
+ */
50
82
  function useCartActions() {
51
83
  return { refresh: useCartStore("useCartActions").refresh };
52
84
  }
85
+ /**
86
+ * Subscribes the {@link CartStore} to the analytics event dispatcher.
87
+ *
88
+ * Call once near the root of your app. Starts tracking on mount via `onMounted`
89
+ * and cleans up via `onScopeDispose`.
90
+ *
91
+ * @example
92
+ * ```vue
93
+ * <script setup>
94
+ * useCartAnalytics();
95
+ * <\/script>
96
+ * ```
97
+ */
53
98
  function useCartAnalytics() {
54
99
  const store = useCartStore("useCartAnalytics");
55
100
  let stopTracking;
@@ -76,6 +121,36 @@ function useCartSelector(store, selector, isEqual) {
76
121
  }));
77
122
  return selected;
78
123
  }
124
+ /**
125
+ * Returns form props, a field register function, and reactive pending state
126
+ * for building cart forms in Vue.
127
+ *
128
+ * Unlike the React hook, the Vue variant includes `isPending` — a reactive
129
+ * object with `initial` (`ComputedRef<boolean>`) for the initial cart load
130
+ * and `lines(lineId?)` for checking per-line or any-line pending state.
131
+ *
132
+ * Note: `interactive: true` returns numeric input attributes but does **not**
133
+ * auto-submit on change — that behavior requires `attachQuantityInput`, which
134
+ * is only wired by the React adapter.
135
+ *
136
+ * @example
137
+ * ```vue
138
+ * <script setup>
139
+ * const { formProps, register, isPending } = useCartForm();
140
+ * <\/script>
141
+ *
142
+ * <template>
143
+ * <form v-bind="formProps()">
144
+ * <input v-bind="register('lineId', { value: line.id })" />
145
+ * <input v-bind="register('quantity', { value: line.quantity })" />
146
+ * <button v-bind="register('set')" />
147
+ * <button v-bind="register('increase')">+</button>
148
+ * <button v-bind="register('decrease')">−</button>
149
+ * <span v-if="isPending.lines(line.id)">Updating…</span>
150
+ * </form>
151
+ * </template>
152
+ * ```
153
+ */
79
154
  function useCartForm() {
80
155
  const store = useCartStore("useCartForm");
81
156
  const loading = useCart((s) => s.loading);
@@ -101,6 +176,26 @@ function useCartForm() {
101
176
  }
102
177
  };
103
178
  }
179
+ /**
180
+ * Factory that returns typed cart components and composables matched to your
181
+ * server handler's cart query shape.
182
+ *
183
+ * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
184
+ * call, so every composable's {@link CartState} carries your custom cart fields.
185
+ *
186
+ * @example
187
+ * ```ts
188
+ * import type { cartServerHandlers } from "./server/cart.server";
189
+ *
190
+ * const {
191
+ * CartProvider,
192
+ * useCart,
193
+ * useOptionalCart,
194
+ * useCartActions,
195
+ * useCartForm,
196
+ * } = createCartComponents<typeof cartServerHandlers>();
197
+ * ```
198
+ */
104
199
  function createCartComponents() {
105
200
  const TypedCartProvider = createTypedCartProvider();
106
201
  const useTypedCart = ((selector, isEqual) => {