@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,4 +1,34 @@
1
1
  //#region src/graphql/graphql.ts
2
+ /**
3
+ * Tags a Storefront API query string for type-safe inference.
4
+ *
5
+ * At runtime, returns the source string (concatenated with any fragments).
6
+ * At the type level, the return type carries phantom `Result` and `Variables`
7
+ * types inferred from the Storefront API schema via gql.tada.
8
+ *
9
+ * Pass an array of previously declared `gql()` fragments as the second
10
+ * argument to compose documents while preserving full type inference.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * import { gql } from "@shopify/hydrogen";
15
+ *
16
+ * const PRODUCT_QUERY = gql(`
17
+ * query Product($handle: String!) {
18
+ * product(handle: $handle) { title }
19
+ * }
20
+ * `);
21
+ *
22
+ * const result = await storefront.graphql(PRODUCT_QUERY, {
23
+ * variables: { handle: "snowboard" },
24
+ * });
25
+ *
26
+ * if (!result.errors) {
27
+ * // result.data.product is typed as `{ title: string } | null`
28
+ * console.log(result.data.product?.title);
29
+ * }
30
+ * ```
31
+ */
2
32
  const gql = ((source, fragments) => {
3
33
  let query = source;
4
34
  if (fragments) {
@@ -1 +1 @@
1
- {"version":3,"file":"graphql.mjs","names":[],"sources":["../../../src/graphql/graphql.ts"],"sourcesContent":["import type { DocumentDecoration, initGraphQLTada as InitGraphQLTada } from \"gql.tada\";\n\nimport type { introspection } from \"./generated/graphql-env\";\nimport type { StorefrontScalars } from \"./scalars\";\nimport type { InferResult, InferVariables } from \"./type-resolver\";\n\ntype StorefrontTadaGql = InitGraphQLTada<{\n introspection: introspection;\n scalars: StorefrontScalars;\n}>;\n\ntype StorefrontQueryMetadata<Source extends string = string> = {\n readonly __hydrogenQueryBrand: true;\n readonly __hydrogenQuerySource?: Source;\n};\n\n/**\n * A branded string that carries phantom Result and Variables types.\n *\n * Honest about being a `string` at runtime (unlike `TadaDocumentNode`\n * which claims to be an AST). Implements `DocumentDecoration` so\n * `ResultOf<>` and `VariablesOf<>` work on it.\n */\nexport type StorefrontQueryString<\n Result = any,\n Variables = any,\n Source extends string = string,\n> = string &\n DocumentDecoration<Result, Variables> & {\n readonly __hydrogenQueryBrand: true;\n readonly __hydrogenQuerySource?: Source;\n };\n\nexport type AnyStorefrontQueryString = string & StorefrontQueryMetadata;\n\nexport type SourceOf<Doc> = Doc extends {\n readonly __hydrogenQuerySource?: infer Source extends string;\n}\n ? Source\n : never;\n\nexport type FragmentSources<Fragments extends readonly AnyStorefrontQueryString[]> =\n Fragments extends readonly []\n ? \"\"\n : Fragments extends readonly [infer Only extends AnyStorefrontQueryString]\n ? SourceOf<Only>\n : Fragments extends readonly [\n infer First extends AnyStorefrontQueryString,\n ...infer Rest extends readonly AnyStorefrontQueryString[],\n ]\n ? `${SourceOf<First>}\\n${FragmentSources<Rest>}`\n : string;\n\nexport type ComposedSource<\n Source extends string,\n Fragments extends readonly AnyStorefrontQueryString[],\n> = FragmentSources<Fragments> extends \"\" ? Source : `${Source}\\n${FragmentSources<Fragments>}`;\n\ntype StorefrontGql = {\n // Composes an already-declared document with additional fragments. Declared\n // before the raw-source overloads so branded documents recover their literal\n // source through SourceOf instead of binding Source to the branded type,\n // which would degrade ComposedSource and kill inference.\n <\n const Document extends AnyStorefrontQueryString,\n const Fragments extends readonly AnyStorefrontQueryString[],\n const DocumentSource extends string = ComposedSource<SourceOf<Document>, Fragments>,\n >(\n document: Document,\n fragments: Fragments,\n ): StorefrontQueryString<\n InferResult<DocumentSource>,\n InferVariables<DocumentSource>,\n DocumentSource\n >;\n <const Source extends string>(\n source: Source,\n ): StorefrontQueryString<InferResult<Source>, InferVariables<Source>, Source>;\n <\n const Source extends string,\n const Fragments extends readonly AnyStorefrontQueryString[],\n const DocumentSource extends string = ComposedSource<Source, Fragments>,\n >(\n source: Source,\n fragments: Fragments,\n ): StorefrontQueryString<\n InferResult<DocumentSource>,\n InferVariables<DocumentSource>,\n DocumentSource\n >;\n} & StorefrontTadaGql;\n\n// oxlint-disable-next-line typescript-eslint/consistent-type-assertions -- gql.tada adds phantom helper properties to the function type that are not used at runtime.\nexport const gql = ((source: string, fragments?: Array<string>) => {\n let query = source;\n\n if (fragments) {\n const seen = new Set<string>();\n for (const fragment of fragments) {\n if (!seen.has(fragment)) {\n seen.add(fragment);\n query += \"\\n\" + fragment;\n }\n }\n }\n\n return query;\n}) as unknown as StorefrontGql;\n"],"mappings":";AA6FA,MAAa,QAAQ,QAAgB,cAA8B;CACjE,IAAI,QAAQ;AAEZ,KAAI,WAAW;EACb,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,MAAM,YAAY,UACrB,KAAI,CAAC,KAAK,IAAI,SAAS,EAAE;AACvB,QAAK,IAAI,SAAS;AAClB,YAAS,OAAO;;;AAKtB,QAAO"}
1
+ {"version":3,"file":"graphql.mjs","names":[],"sources":["../../../src/graphql/graphql.ts"],"sourcesContent":["import type { DocumentDecoration, initGraphQLTada as InitGraphQLTada } from \"gql.tada\";\n\nimport type { introspection } from \"./generated/graphql-env\";\nimport type { StorefrontScalars } from \"./scalars\";\nimport type { InferResult, InferVariables } from \"./type-resolver\";\n\ntype StorefrontTadaGql = InitGraphQLTada<{\n introspection: introspection;\n scalars: StorefrontScalars;\n}>;\n\ntype StorefrontQueryMetadata<Source extends string = string> = {\n readonly __hydrogenQueryBrand: true;\n readonly __hydrogenQuerySource?: Source;\n};\n\n/**\n * A branded string that carries phantom Result and Variables types.\n *\n * Honest about being a `string` at runtime (unlike `TadaDocumentNode`\n * which claims to be an AST). Implements `DocumentDecoration` so\n * `ResultOf<>` and `VariablesOf<>` work on it.\n */\nexport type StorefrontQueryString<\n Result = any,\n Variables = any,\n Source extends string = string,\n> = string &\n DocumentDecoration<Result, Variables> & {\n readonly __hydrogenQueryBrand: true;\n readonly __hydrogenQuerySource?: Source;\n };\n\n/** Any `gql()`-branded string, regardless of inferred Result/Variables. Used in constraints that accept any Storefront document. */\nexport type AnyStorefrontQueryString = string & StorefrontQueryMetadata;\n\n/** Extracts the literal source text from a `gql()`-branded document type. Resolves to `never` for unbranded strings. */\nexport type SourceOf<Doc> = Doc extends {\n readonly __hydrogenQuerySource?: infer Source extends string;\n}\n ? Source\n : never;\n\nexport type FragmentSources<Fragments extends readonly AnyStorefrontQueryString[]> =\n Fragments extends readonly []\n ? \"\"\n : Fragments extends readonly [infer Only extends AnyStorefrontQueryString]\n ? SourceOf<Only>\n : Fragments extends readonly [\n infer First extends AnyStorefrontQueryString,\n ...infer Rest extends readonly AnyStorefrontQueryString[],\n ]\n ? `${SourceOf<First>}\\n${FragmentSources<Rest>}`\n : string;\n\n/** Concatenates a query source with its fragment sources into a single literal type, enabling end-to-end type inference for composed documents. */\nexport type ComposedSource<\n Source extends string,\n Fragments extends readonly AnyStorefrontQueryString[],\n> = FragmentSources<Fragments> extends \"\" ? Source : `${Source}\\n${FragmentSources<Fragments>}`;\n\ntype StorefrontGql = {\n // Composes an already-declared document with additional fragments. Declared\n // before the raw-source overloads so branded documents recover their literal\n // source through SourceOf instead of binding Source to the branded type,\n // which would degrade ComposedSource and kill inference.\n <\n const Document extends AnyStorefrontQueryString,\n const Fragments extends readonly AnyStorefrontQueryString[],\n const DocumentSource extends string = ComposedSource<SourceOf<Document>, Fragments>,\n >(\n document: Document,\n fragments: Fragments,\n ): StorefrontQueryString<\n InferResult<DocumentSource>,\n InferVariables<DocumentSource>,\n DocumentSource\n >;\n <const Source extends string>(\n source: Source,\n ): StorefrontQueryString<InferResult<Source>, InferVariables<Source>, Source>;\n <\n const Source extends string,\n const Fragments extends readonly AnyStorefrontQueryString[],\n const DocumentSource extends string = ComposedSource<Source, Fragments>,\n >(\n source: Source,\n fragments: Fragments,\n ): StorefrontQueryString<\n InferResult<DocumentSource>,\n InferVariables<DocumentSource>,\n DocumentSource\n >;\n} & StorefrontTadaGql;\n\n/**\n * Tags a Storefront API query string for type-safe inference.\n *\n * At runtime, returns the source string (concatenated with any fragments).\n * At the type level, the return type carries phantom `Result` and `Variables`\n * types inferred from the Storefront API schema via gql.tada.\n *\n * Pass an array of previously declared `gql()` fragments as the second\n * argument to compose documents while preserving full type inference.\n *\n * @example\n * ```ts\n * import { gql } from \"@shopify/hydrogen\";\n *\n * const PRODUCT_QUERY = gql(`\n * query Product($handle: String!) {\n * product(handle: $handle) { title }\n * }\n * `);\n *\n * const result = await storefront.graphql(PRODUCT_QUERY, {\n * variables: { handle: \"snowboard\" },\n * });\n *\n * if (!result.errors) {\n * // result.data.product is typed as `{ title: string } | null`\n * console.log(result.data.product?.title);\n * }\n * ```\n */\n// oxlint-disable-next-line typescript-eslint/consistent-type-assertions -- gql.tada adds phantom helper properties to the function type that are not used at runtime.\nexport const gql = ((source: string, fragments?: Array<string>) => {\n let query = source;\n\n if (fragments) {\n const seen = new Set<string>();\n for (const fragment of fragments) {\n if (!seen.has(fragment)) {\n seen.add(fragment);\n query += \"\\n\" + fragment;\n }\n }\n }\n\n return query;\n}) as unknown as StorefrontGql;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8HA,MAAa,QAAQ,QAAgB,cAA8B;CACjE,IAAI,QAAQ;AAEZ,KAAI,WAAW;EACb,MAAM,uBAAO,IAAI,KAAa;AAC9B,OAAK,MAAM,YAAY,UACrB,KAAI,CAAC,KAAK,IAAI,SAAS,EAAE;AACvB,QAAK,IAAI,SAAS;AAClB,YAAS,OAAO;;;AAKtB,QAAO"}
@@ -221,7 +221,21 @@ type OperationKindType<Doc> = Doc extends {
221
221
  } ? Operation : "unknown" : "unknown";
222
222
  type InferResultForSchema<T extends string, Schema extends SchemaLike> = parseDocument<T> extends infer Doc ? Doc extends never ? never : DocumentType<Doc, Schema> : never;
223
223
  type InferVariablesForSchema<T extends string, Schema extends SchemaLike> = parseDocument<T> extends infer Doc ? Doc extends never ? never : VariablesType<Doc, Schema> : never;
224
+ /**
225
+ * Infers the result type of a Storefront API query string against the bundled schema.
226
+ *
227
+ * Useful when you need the result shape outside of `storefront.graphql()` — e.g. for
228
+ * a component prop type. Takes a raw query source literal: for `gql()` documents use
229
+ * `StorefrontApi.ResultOf`, since `InferResult<typeof doc>` resolves to `never`.
230
+ */
224
231
  type InferResult<T extends string> = InferResultForSchema<T, StorefrontSchema>;
232
+ /**
233
+ * Infers the variables type of a Storefront API query string against the bundled schema.
234
+ *
235
+ * Useful when you need the variables shape outside of `storefront.graphql()` — e.g. for
236
+ * a loader param type. Takes a raw query source literal: for `gql()` documents use
237
+ * `StorefrontApi.VariablesOf`, since `InferVariables<typeof doc>` resolves to `never`.
238
+ */
225
239
  type InferVariables<T extends string> = InferVariablesForSchema<T, StorefrontSchema>;
226
240
  type InferOperationKind<T extends string> = parseDocument<T> extends infer Doc ? [Doc] extends [never] ? "unknown" : OperationKindType<Doc> : "unknown";
227
241
  //#endregion
@@ -1,18 +1,14 @@
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 { FormHTMLAttributes, ReactNode, SubmitEvent } from "react";
6
- import * as _$react_jsx_runtime0 from "react/jsx-runtime";
7
-
8
6
  //#region src/react/cart.d.ts
9
7
  type TypedCartProviderProps<TData extends CartData> = {
10
8
  initialData?: CartInitialData<TData>;
11
9
  children?: ReactNode;
12
10
  };
13
11
  type CartInitialData<TData extends CartData = CartData> = CreateCartStoreOptions<TData>["initialData"];
14
- /** Actions for reconciling cart state after updates outside Standard Actions. */
15
- type CartActions = Pick<CartStore, "refresh">;
16
12
  type TypedCartComponents<TData extends CartData> = {
17
13
  CartProvider: (props: TypedCartProviderProps<TData>) => ReactNode;
18
14
  useCart: <S>(selector: (state: CartState<TData>) => S, isEqual?: (a: S, b: S) => boolean) => S;
@@ -21,17 +17,92 @@ type TypedCartComponents<TData extends CartData> = {
21
17
  useCartActions: typeof useCartActions;
22
18
  useCartForm: typeof useCartForm;
23
19
  };
20
+ /**
21
+ * Factory that returns typed cart components and hooks matched to your server handler's
22
+ * cart query shape.
23
+ *
24
+ * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
25
+ * call, so every hook's {@link CartState} carries your custom cart fields end-to-end.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * import type { cartServerHandlers } from "./server/cart.server";
30
+ *
31
+ * const {
32
+ * CartProvider,
33
+ * useCart,
34
+ * useSuspenseCart,
35
+ * useOptionalCart,
36
+ * useCartActions,
37
+ * useCartForm,
38
+ * } = createCartComponents<typeof cartServerHandlers>();
39
+ * ```
40
+ */
24
41
  declare function createCartComponents<THandlers>(): TypedCartComponents<CartDataFromHandlers<THandlers>>;
25
- declare function CartProvider({
26
- initialData,
27
- children
28
- }: {
29
- initialData?: CartInitialData;
30
- children?: ReactNode;
31
- }): _$react_jsx_runtime0.JSX.Element;
32
- declare function useCart<TData extends CartData = CartData, S = unknown>(selector: (state: CartState<TData>) => S, isEqual?: (a: S, b: S) => boolean): S;
42
+ /**
43
+ * Returns cart actions for reconciling state after out-of-band mutations.
44
+ *
45
+ * Currently exposes {@link CartStore.refresh} — call it after server-side cart
46
+ * mutations that bypass the form system (e.g. a server action that creates the
47
+ * cart before the client knows about it).
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * const { refresh } = useCartActions();
52
+ *
53
+ * async function handleServerAction() {
54
+ * await createCartOnServer();
55
+ * refresh(); // re-fetch the cart
56
+ * }
57
+ * ```
58
+ */
33
59
  declare function useCartActions(): CartActions;
60
+ /**
61
+ * Subscribes the {@link CartStore} to the analytics event dispatcher.
62
+ *
63
+ * Call once near the root of your app — it subscribes on mount and
64
+ * cleans up on unmount. Publishes `CART_UPDATED`, `PRODUCT_ADD_TO_CART`,
65
+ * and `PRODUCT_REMOVED_FROM_CART` analytics events automatically.
66
+ *
67
+ * @example
68
+ * ```tsx
69
+ * function App() {
70
+ * useCartAnalytics();
71
+ * return <Layout />;
72
+ * }
73
+ * ```
74
+ */
34
75
  declare function useCartAnalytics(): void;
76
+ /**
77
+ * Returns form props and a field register function for building cart forms.
78
+ *
79
+ * `formProps()` returns `<form>` attributes that intercept submission, call
80
+ * {@link CartStore.handleFormSubmit}, and prevent the default browser navigation.
81
+ * `register()` returns the correct HTML attributes for each field type — see
82
+ * `CartFormRegister` for the full field/action list.
83
+ *
84
+ * When `register("quantity", { interactive: true })` is called, the returned
85
+ * attributes include a `ref` callback that attaches {@link attachQuantityInput}
86
+ * for auto-submit-on-change behavior.
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * function LineItem({ line }) {
91
+ * const { formProps, register } = useCartForm();
92
+ *
93
+ * return (
94
+ * <form {...formProps()}>
95
+ * <input {...register("lineId", { value: line.id })} />
96
+ * <input {...register("quantity", { value: line.quantity, interactive: true })} />
97
+ * <button {...register("set")} />
98
+ * <button {...register("increase")}>+</button>
99
+ * <button {...register("decrease")}>−</button>
100
+ * <button {...register("remove")}>Remove</button>
101
+ * </form>
102
+ * );
103
+ * }
104
+ * ```
105
+ */
35
106
  declare function useCartForm(): {
36
107
  formProps: (opts?: {
37
108
  beforeSubmit?: (e: SubmitEvent<HTMLFormElement>) => void;
@@ -40,5 +111,5 @@ declare function useCartForm(): {
40
111
  register: CartFormRegister;
41
112
  };
42
113
  //#endregion
43
- export { CartActions, CartProvider, createCartComponents, useCart, useCartActions, useCartAnalytics, useCartForm };
114
+ export { createCartComponents, useCartAnalytics };
44
115
  //# sourceMappingURL=cart.d.mts.map
@@ -13,6 +13,27 @@ const CartContext = createContext(null);
13
13
  function getCartEndpoint() {
14
14
  return cartEndpoint;
15
15
  }
16
+ /**
17
+ * Factory that returns typed cart components and hooks matched to your server handler's
18
+ * cart query shape.
19
+ *
20
+ * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`
21
+ * call, so every hook's {@link CartState} carries your custom cart fields end-to-end.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * import type { cartServerHandlers } from "./server/cart.server";
26
+ *
27
+ * const {
28
+ * CartProvider,
29
+ * useCart,
30
+ * useSuspenseCart,
31
+ * useOptionalCart,
32
+ * useCartActions,
33
+ * useCartForm,
34
+ * } = createCartComponents<typeof cartServerHandlers>();
35
+ * ```
36
+ */
16
37
  function createCartComponents() {
17
38
  function TypedCartProvider({ initialData, children }) {
18
39
  return /* @__PURE__ */ jsx(CartProvider, {
@@ -48,6 +69,21 @@ function useCartStore(hookName = "useCart") {
48
69
  function useOptionalCartStore() {
49
70
  return useContext(CartContext);
50
71
  }
72
+ /**
73
+ * React context provider that creates and manages a {@link CartStore} instance.
74
+ *
75
+ * Calls {@link CartStore.connect} on mount and {@link CartStore.destroy} on unmount.
76
+ * All cart hooks (`useCart`, `useCartForm`, `useCartActions`) must be rendered
77
+ * inside this provider.
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * // In your root layout
82
+ * <CartProvider initialData={{ cart: loaderData.cart }}>
83
+ * <App />
84
+ * </CartProvider>
85
+ * ```
86
+ */
51
87
  function CartProvider({ initialData, children }) {
52
88
  const store = useMemo(() => createCartStore({ initialData }), []);
53
89
  useEffect(() => {
@@ -62,15 +98,69 @@ function CartProvider({ initialData, children }) {
62
98
  children
63
99
  });
64
100
  }
101
+ /**
102
+ * Subscribes to a slice of {@link CartState} via `useSyncExternalStore`.
103
+ *
104
+ * The component re-renders only when the selected value changes (by reference,
105
+ * or by a custom `isEqual`). Always pass a selector — subscribing to the full
106
+ * state object defeats memoization.
107
+ *
108
+ * @example
109
+ * ```tsx
110
+ * // Select cart lines
111
+ * const lines = useCart((state) => state.data.lines.nodes);
112
+ *
113
+ * // Select pending state for a specific line
114
+ * const isPending = useCart((state) => state.pending.lines.has(lineId));
115
+ *
116
+ * // Custom equality to avoid re-renders on unchanged totals
117
+ * const total = useCart(
118
+ * (state) => state.data.cost.totalAmount,
119
+ * (a, b) => a.amount === b.amount,
120
+ * );
121
+ * ```
122
+ */
65
123
  function useCart(selector, isEqual) {
66
124
  return useCartSelector(useCartStore(), selector, isEqual);
67
125
  }
126
+ /**
127
+ * Returns cart actions for reconciling state after out-of-band mutations.
128
+ *
129
+ * Currently exposes {@link CartStore.refresh} — call it after server-side cart
130
+ * mutations that bypass the form system (e.g. a server action that creates the
131
+ * cart before the client knows about it).
132
+ *
133
+ * @example
134
+ * ```tsx
135
+ * const { refresh } = useCartActions();
136
+ *
137
+ * async function handleServerAction() {
138
+ * await createCartOnServer();
139
+ * refresh(); // re-fetch the cart
140
+ * }
141
+ * ```
142
+ */
68
143
  function useCartActions() {
69
144
  const store = useCartStore("useCartActions");
70
145
  return useMemo(() => ({ refresh: store.refresh }), [store]);
71
146
  }
147
+ /**
148
+ * Subscribes the {@link CartStore} to the analytics event dispatcher.
149
+ *
150
+ * Call once near the root of your app — it subscribes on mount and
151
+ * cleans up on unmount. Publishes `CART_UPDATED`, `PRODUCT_ADD_TO_CART`,
152
+ * and `PRODUCT_REMOVED_FROM_CART` analytics events automatically.
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * function App() {
157
+ * useCartAnalytics();
158
+ * return <Layout />;
159
+ * }
160
+ * ```
161
+ */
72
162
  function useCartAnalytics() {
73
- const store = useCartStore();
163
+ const store = useCartStore("useCartAnalytics");
74
164
  useEffect(() => trackCartAnalytics(store), [store]);
75
165
  }
76
166
  /**
@@ -105,8 +195,38 @@ function useCartSelector(store, selector, isEqual) {
105
195
  };
106
196
  return useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);
107
197
  }
198
+ /**
199
+ * Returns form props and a field register function for building cart forms.
200
+ *
201
+ * `formProps()` returns `<form>` attributes that intercept submission, call
202
+ * {@link CartStore.handleFormSubmit}, and prevent the default browser navigation.
203
+ * `register()` returns the correct HTML attributes for each field type — see
204
+ * `CartFormRegister` for the full field/action list.
205
+ *
206
+ * When `register("quantity", { interactive: true })` is called, the returned
207
+ * attributes include a `ref` callback that attaches {@link attachQuantityInput}
208
+ * for auto-submit-on-change behavior.
209
+ *
210
+ * @example
211
+ * ```tsx
212
+ * function LineItem({ line }) {
213
+ * const { formProps, register } = useCartForm();
214
+ *
215
+ * return (
216
+ * <form {...formProps()}>
217
+ * <input {...register("lineId", { value: line.id })} />
218
+ * <input {...register("quantity", { value: line.quantity, interactive: true })} />
219
+ * <button {...register("set")} />
220
+ * <button {...register("increase")}>+</button>
221
+ * <button {...register("decrease")}>−</button>
222
+ * <button {...register("remove")}>Remove</button>
223
+ * </form>
224
+ * );
225
+ * }
226
+ * ```
227
+ */
108
228
  function useCartForm() {
109
- const store = useCartStore();
229
+ const store = useCartStore("useCartForm");
110
230
  const coreRegister = useMemo(() => createCartFormRegister(), []);
111
231
  const register = useMemo(() => {
112
232
  const wrapped = ((...args) => {
@@ -151,5 +271,5 @@ function useCartForm() {
151
271
  }
152
272
 
153
273
  //#endregion
154
- export { CartProvider, createCartComponents, getCartEndpoint, useCart, useCartActions, useCartAnalytics, useCartForm, useCartStore };
274
+ export { createCartComponents, getCartEndpoint, useCartAnalytics, useCartStore };
155
275
  //# sourceMappingURL=cart.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"cart.mjs","names":[],"sources":["../../../src/react/cart.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type RefCallback,\n type SubmitEvent,\n} from \"react\";\n\nimport { trackCartAnalytics } from \"../core/analytics/cart-tracker\";\nimport { attachQuantityInput } from \"../core/cart/attach-quantity-input\";\nimport {\n configureCartEndpoint as configureCoreCartEndpoint,\n createCartStore,\n type CreateCartStoreOptions,\n type CartStore,\n} from \"../core/cart/cart\";\nimport { createCartFormRegister } from \"../core/cart/form\";\nimport type { CartDataFromHandlers } from \"../core/cart/server-handlers\";\nimport { type CartData, type CartState } from \"../core/cart/state\";\n\nconst DEFAULT_CART_ENDPOINT = \"/api/cart\";\n\nlet cartEndpoint = DEFAULT_CART_ENDPOINT;\n\nconst CartContext = createContext<CartStore | null>(null);\n\nexport function configureCartEndpoint(endpoint: string): void {\n cartEndpoint = endpoint;\n configureCoreCartEndpoint(endpoint);\n}\n\nexport function getCartEndpoint(): string {\n return cartEndpoint;\n}\n\ntype TypedCartProviderProps<TData extends CartData> = {\n initialData?: CartInitialData<TData>;\n children?: ReactNode;\n};\n\ntype CartInitialData<TData extends CartData = CartData> =\n CreateCartStoreOptions<TData>[\"initialData\"];\n\n/** Actions for reconciling cart state after updates outside Standard Actions. */\nexport type CartActions = Pick<CartStore, \"refresh\">;\n\ntype TypedCartComponents<TData extends CartData> = {\n CartProvider: (props: TypedCartProviderProps<TData>) => ReactNode;\n useCart: <S>(selector: (state: CartState<TData>) => S, isEqual?: (a: S, b: S) => boolean) => S;\n useSuspenseCart: <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ) => S;\n useOptionalCart: <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ) => S | undefined;\n useCartActions: typeof useCartActions;\n useCartForm: typeof useCartForm;\n};\n\nexport function createCartComponents<THandlers>(): TypedCartComponents<\n CartDataFromHandlers<THandlers>\n> {\n type TData = CartDataFromHandlers<THandlers>;\n\n function TypedCartProvider({ initialData, children }: TypedCartProviderProps<TData>) {\n return (\n <CartProvider initialData={initialData as CartInitialData | undefined}>\n {children}\n </CartProvider>\n );\n }\n\n function useTypedCart<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): S {\n return useCart<CartData, S>((state) => selector(state as CartState<TData>), isEqual);\n }\n\n function useTypedOptionalCart<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): S | undefined {\n return useOptionalCart<CartData, S>((state) => selector(state as CartState<TData>), isEqual);\n }\n\n function useSuspenseCart<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): S {\n const readyPromise = useCart((state) => state.readyPromise);\n if (readyPromise) throw readyPromise;\n return useCart(selector, isEqual);\n }\n\n return {\n CartProvider: TypedCartProvider,\n useCart: useTypedCart,\n useSuspenseCart,\n useOptionalCart: useTypedOptionalCart,\n useCartActions,\n useCartForm,\n } as const;\n}\n\nexport function useCartStore(hookName = \"useCart\"): CartStore {\n const store = useContext(CartContext);\n if (!store) throw new Error(`${hookName} must be used inside <CartProvider>.`);\n return store;\n}\n\nfunction useOptionalCartStore(): CartStore | null {\n return useContext(CartContext);\n}\n\nexport function CartProvider({\n initialData,\n children,\n}: {\n initialData?: CartInitialData;\n children?: ReactNode;\n}) {\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store is created once with the initial server data\n const store = useMemo(() => createCartStore({ initialData }), []);\n\n useEffect(() => {\n configureCoreCartEndpoint(cartEndpoint);\n store.connect();\n return () => {\n store.destroy();\n };\n }, [store]);\n\n return <CartContext.Provider value={store}>{children}</CartContext.Provider>;\n}\n\nexport function useCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S {\n const store = useCartStore();\n return useCartSelector(store, selector, isEqual) as S;\n}\n\nexport function useCartActions(): CartActions {\n const store = useCartStore(\"useCartActions\");\n\n return useMemo(() => ({ refresh: store.refresh }), [store]);\n}\n\nexport function useCartAnalytics(): void {\n const store = useCartStore();\n\n useEffect(() => trackCartAnalytics(store), [store]);\n}\n\n/**\n * Like `useCart`, but returns `undefined` when rendered outside a `<CartProvider>`.\n * @internal\n */\nexport function useOptionalCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S | undefined {\n const store = useOptionalCartStore();\n if (!store) return undefined;\n return useCartSelector(store, selector, isEqual);\n}\n\nfunction useCartSelector<TData extends CartData = CartData, S = unknown>(\n store: CartStore,\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S {\n const cachedRef = useRef<{ state: unknown; selector: typeof selector; value: S } | null>(null);\n\n const getSnapshot = () => {\n const state = store.getState() as CartState<TData>;\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 const next = selector(state);\n\n if (cachedRef.current && isEqual?.(cachedRef.current.value, 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 useCartForm() {\n const store = useCartStore();\n const coreRegister = useMemo(() => createCartFormRegister(), []);\n\n const register = useMemo(() => {\n type Register = typeof coreRegister;\n const wrapped = ((...args: Parameters<Register>) => {\n const result = (coreRegister as Function)(...args);\n const [field, opts] = args as [string, { interactive?: boolean }?];\n\n if (field === \"quantity\" && opts?.interactive) {\n let cleanup: (() => void) | null = null;\n const ref: RefCallback<HTMLInputElement> = (el) => {\n cleanup?.();\n cleanup = null;\n if (el) {\n const form = el.closest(\"form\");\n if (form) cleanup = attachQuantityInput(el, form);\n }\n };\n const { value: _, ...rest } = result;\n return { ...rest, defaultValue: result.value, ref };\n }\n\n return result;\n }) as Register;\n return wrapped;\n }, [coreRegister]);\n\n const formProps = (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 store.handleFormSubmit(e.nativeEvent).catch(() => {});\n opts?.afterSubmit?.(e);\n },\n method: \"post\",\n action: cartEndpoint,\n });\n\n return { formProps, register };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cart.mjs","names":[],"sources":["../../../src/react/cart.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type RefCallback,\n type SubmitEvent,\n} from \"react\";\n\nimport { trackCartAnalytics } from \"../core/analytics/cart-tracker\";\nimport { attachQuantityInput } from \"../core/cart/attach-quantity-input\";\nimport {\n configureCartEndpoint as configureCoreCartEndpoint,\n createCartStore,\n type CreateCartStoreOptions,\n type CartActions,\n type CartStore,\n} from \"../core/cart/cart\";\nimport { createCartFormRegister } from \"../core/cart/form\";\nimport type { CartDataFromHandlers } from \"../core/cart/server-handlers\";\nimport { type CartData, type CartState } from \"../core/cart/state\";\n\nconst DEFAULT_CART_ENDPOINT = \"/api/cart\";\n\nlet cartEndpoint = DEFAULT_CART_ENDPOINT;\n\nconst CartContext = createContext<CartStore | null>(null);\n\nexport function configureCartEndpoint(endpoint: string): void {\n cartEndpoint = endpoint;\n configureCoreCartEndpoint(endpoint);\n}\n\nexport function getCartEndpoint(): string {\n return cartEndpoint;\n}\n\ntype TypedCartProviderProps<TData extends CartData> = {\n initialData?: CartInitialData<TData>;\n children?: ReactNode;\n};\n\ntype CartInitialData<TData extends CartData = CartData> =\n CreateCartStoreOptions<TData>[\"initialData\"];\n\nexport type { CartActions };\n\ntype TypedCartComponents<TData extends CartData> = {\n CartProvider: (props: TypedCartProviderProps<TData>) => ReactNode;\n useCart: <S>(selector: (state: CartState<TData>) => S, isEqual?: (a: S, b: S) => boolean) => S;\n useSuspenseCart: <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ) => S;\n useOptionalCart: <S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ) => S | undefined;\n useCartActions: typeof useCartActions;\n useCartForm: typeof useCartForm;\n};\n\n/**\n * Factory that returns typed cart components and hooks matched to your server handler's\n * cart query shape.\n *\n * The generic `THandlers` parameter is inferred from your `createCartServerHandlers`\n * call, so every hook's {@link CartState} carries your custom cart fields end-to-end.\n *\n * @example\n * ```tsx\n * import type { cartServerHandlers } from \"./server/cart.server\";\n *\n * const {\n * CartProvider,\n * useCart,\n * useSuspenseCart,\n * useOptionalCart,\n * useCartActions,\n * useCartForm,\n * } = createCartComponents<typeof cartServerHandlers>();\n * ```\n */\nexport function createCartComponents<THandlers>(): TypedCartComponents<\n CartDataFromHandlers<THandlers>\n> {\n type TData = CartDataFromHandlers<THandlers>;\n\n function TypedCartProvider({ initialData, children }: TypedCartProviderProps<TData>) {\n return (\n <CartProvider initialData={initialData as CartInitialData | undefined}>\n {children}\n </CartProvider>\n );\n }\n\n function useTypedCart<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): S {\n return useCart<CartData, S>((state) => selector(state as CartState<TData>), isEqual);\n }\n\n function useTypedOptionalCart<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): S | undefined {\n return useOptionalCart<CartData, S>((state) => selector(state as CartState<TData>), isEqual);\n }\n\n function useSuspenseCart<S>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n ): S {\n const readyPromise = useCart((state) => state.readyPromise);\n if (readyPromise) throw readyPromise;\n return useCart(selector, isEqual);\n }\n\n return {\n CartProvider: TypedCartProvider,\n useCart: useTypedCart,\n useSuspenseCart,\n useOptionalCart: useTypedOptionalCart,\n useCartActions,\n useCartForm,\n } as const;\n}\n\nexport function useCartStore(hookName = \"useCart\"): CartStore {\n const store = useContext(CartContext);\n if (!store) throw new Error(`${hookName} must be used inside <CartProvider>.`);\n return store;\n}\n\nfunction useOptionalCartStore(): CartStore | null {\n return useContext(CartContext);\n}\n\n/**\n * React context provider that creates and manages a {@link CartStore} instance.\n *\n * Calls {@link CartStore.connect} on mount and {@link CartStore.destroy} on unmount.\n * All cart hooks (`useCart`, `useCartForm`, `useCartActions`) must be rendered\n * inside this provider.\n *\n * @example\n * ```tsx\n * // In your root layout\n * <CartProvider initialData={{ cart: loaderData.cart }}>\n * <App />\n * </CartProvider>\n * ```\n */\nexport function CartProvider({\n initialData,\n children,\n}: {\n initialData?: CartInitialData;\n children?: ReactNode;\n}) {\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store is created once with the initial server data\n const store = useMemo(() => createCartStore({ initialData }), []);\n\n useEffect(() => {\n configureCoreCartEndpoint(cartEndpoint);\n store.connect();\n return () => {\n store.destroy();\n };\n }, [store]);\n\n return <CartContext.Provider value={store}>{children}</CartContext.Provider>;\n}\n\n/**\n * Subscribes to a slice of {@link CartState} via `useSyncExternalStore`.\n *\n * The component re-renders only when the selected value changes (by reference,\n * or by a custom `isEqual`). Always pass a selector — subscribing to the full\n * state object defeats memoization.\n *\n * @example\n * ```tsx\n * // Select cart lines\n * const lines = useCart((state) => state.data.lines.nodes);\n *\n * // Select pending state for a specific line\n * const isPending = useCart((state) => state.pending.lines.has(lineId));\n *\n * // Custom equality to avoid re-renders on unchanged totals\n * const total = useCart(\n * (state) => state.data.cost.totalAmount,\n * (a, b) => a.amount === b.amount,\n * );\n * ```\n */\nexport function useCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S {\n const store = useCartStore();\n return useCartSelector(store, selector, isEqual) as S;\n}\n\n/**\n * Returns cart actions for reconciling state after out-of-band mutations.\n *\n * Currently exposes {@link CartStore.refresh} — call it after server-side cart\n * mutations that bypass the form system (e.g. a server action that creates the\n * cart before the client knows about it).\n *\n * @example\n * ```tsx\n * const { refresh } = useCartActions();\n *\n * async function handleServerAction() {\n * await createCartOnServer();\n * refresh(); // re-fetch the cart\n * }\n * ```\n */\nexport function useCartActions(): CartActions {\n const store = useCartStore(\"useCartActions\");\n\n return useMemo(() => ({ refresh: store.refresh }), [store]);\n}\n\n/**\n * Subscribes the {@link CartStore} to the analytics event dispatcher.\n *\n * Call once near the root of your app — it subscribes on mount and\n * cleans up on unmount. Publishes `CART_UPDATED`, `PRODUCT_ADD_TO_CART`,\n * and `PRODUCT_REMOVED_FROM_CART` analytics events automatically.\n *\n * @example\n * ```tsx\n * function App() {\n * useCartAnalytics();\n * return <Layout />;\n * }\n * ```\n */\nexport function useCartAnalytics(): void {\n const store = useCartStore(\"useCartAnalytics\");\n\n useEffect(() => trackCartAnalytics(store), [store]);\n}\n\n/**\n * Like `useCart`, but returns `undefined` when rendered outside a `<CartProvider>`.\n * @internal\n */\nexport function useOptionalCart<TData extends CartData = CartData, S = unknown>(\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S | undefined {\n const store = useOptionalCartStore();\n if (!store) return undefined;\n return useCartSelector(store, selector, isEqual);\n}\n\nfunction useCartSelector<TData extends CartData = CartData, S = unknown>(\n store: CartStore,\n selector: (state: CartState<TData>) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S {\n const cachedRef = useRef<{ state: unknown; selector: typeof selector; value: S } | null>(null);\n\n const getSnapshot = () => {\n const state = store.getState() as CartState<TData>;\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 const next = selector(state);\n\n if (cachedRef.current && isEqual?.(cachedRef.current.value, 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 form props and a field register function for building cart forms.\n *\n * `formProps()` returns `<form>` attributes that intercept submission, call\n * {@link CartStore.handleFormSubmit}, and prevent the default browser navigation.\n * `register()` returns the correct HTML attributes for each field type — see\n * `CartFormRegister` for the full field/action list.\n *\n * When `register(\"quantity\", { interactive: true })` is called, the returned\n * attributes include a `ref` callback that attaches {@link attachQuantityInput}\n * for auto-submit-on-change behavior.\n *\n * @example\n * ```tsx\n * function LineItem({ line }) {\n * const { formProps, register } = useCartForm();\n *\n * return (\n * <form {...formProps()}>\n * <input {...register(\"lineId\", { value: line.id })} />\n * <input {...register(\"quantity\", { value: line.quantity, interactive: true })} />\n * <button {...register(\"set\")} />\n * <button {...register(\"increase\")}>+</button>\n * <button {...register(\"decrease\")}>−</button>\n * <button {...register(\"remove\")}>Remove</button>\n * </form>\n * );\n * }\n * ```\n */\nexport function useCartForm() {\n const store = useCartStore(\"useCartForm\");\n const coreRegister = useMemo(() => createCartFormRegister(), []);\n\n const register = useMemo(() => {\n type Register = typeof coreRegister;\n const wrapped = ((...args: Parameters<Register>) => {\n const result = (coreRegister as Function)(...args);\n const [field, opts] = args as [string, { interactive?: boolean }?];\n\n if (field === \"quantity\" && opts?.interactive) {\n let cleanup: (() => void) | null = null;\n const ref: RefCallback<HTMLInputElement> = (el) => {\n cleanup?.();\n cleanup = null;\n if (el) {\n const form = el.closest(\"form\");\n if (form) cleanup = attachQuantityInput(el, form);\n }\n };\n const { value: _, ...rest } = result;\n return { ...rest, defaultValue: result.value, ref };\n }\n\n return result;\n }) as Register;\n return wrapped;\n }, [coreRegister]);\n\n const formProps = (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 store.handleFormSubmit(e.nativeEvent).catch(() => {});\n opts?.afterSubmit?.(e);\n },\n method: \"post\",\n action: cartEndpoint,\n });\n\n return { formProps, register };\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { CollectionState } from "../core/collection/state.mjs";
2
- import { CollectionData, CollectionStore } from "../core/collection/collection.mjs";
2
+ import { CollectionActions, CollectionData, CollectionStore } from "../core/collection/collection.mjs";
3
3
  import * as _$react from "react";
4
4
  import { FormHTMLAttributes, ReactNode, SubmitEvent } from "react";
5
5
 
@@ -58,8 +58,6 @@ declare function useCollection(): CollectionState;
58
58
  * ```
59
59
  */
60
60
  declare function useCollection<S>(selector: (state: CollectionState) => S, isEqual?: (a: S, b: S) => boolean): S;
61
- /** Mutation methods that update store state and trigger the `onChange` callback. */
62
- type CollectionActions = Pick<CollectionStore, "setFilters" | "toggleFilter" | "toggleFilterInput" | "setSortKey" | "setSortByValue" | "reset" | "handleFormSubmit">;
63
61
  /**
64
62
  * Returns methods that change filters and sort. The store's `onBrowseChange`
65
63
  * callback (set by {@link CollectionProvider}) handles calling `onChange` with
@@ -87,5 +85,5 @@ declare function useCollectionForm(): {
87
85
  }) => FormHTMLAttributes<HTMLFormElement>;
88
86
  };
89
87
  //#endregion
90
- export { CollectionActions, CollectionProvider, CollectionProviderProps, useCollection, useCollectionActions, useCollectionForm };
88
+ export { CollectionProvider, CollectionProviderProps, useCollection, useCollectionActions, useCollectionForm };
91
89
  //# sourceMappingURL=collection.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"collection.mjs","names":[],"sources":["../../../src/react/collection.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport {\n createCollectionStore,\n createCollectionReconciler,\n type CollectionData,\n type CollectionReconciler,\n type CollectionStore,\n} from \"../core/collection\";\nimport type { CollectionState } from \"../core/collection\";\n\nconst CollectionContext = createContext<CollectionStore | null>(null);\n\n/** Props for the {@link CollectionProvider} component. */\nexport interface CollectionProviderProps {\n /** Collection metadata from the framework data fetch (`handle`, `dataSearch`). */\n data: CollectionData;\n /**\n * Live URL search string from the framework router (with or without leading `?`).\n * Seeds the store on mount and keeps browse state in sync with back/forward.\n */\n urlSearch?: string;\n /**\n * Called after user-initiated filter/sort changes with a ready-to-use\n * search string. The consumer's only job is to navigate:\n *\n * ```tsx\n * onChange={(search) =>\n * navigate({ search }, { replace: searchParams.size > 0, preventScrollReset: true })\n * }\n * ```\n */\n onChange?: (searchString: string) => void;\n children?: ReactNode;\n}\n\n/**\n * Manages the lifecycle of a {@link CollectionStore}: creates on mount and\n * syncs with URL changes. Recreates the store when `data.handle` changes\n * (navigating to a different collection).\n */\nexport function CollectionProvider({\n data,\n urlSearch = \"\",\n onChange,\n children,\n}: CollectionProviderProps) {\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const urlSearchRef = useRef(urlSearch);\n urlSearchRef.current = urlSearch;\n\n const dataRef = useRef(data);\n dataRef.current = data;\n\n const { handle: collectionHandle, dataSearch } = data;\n\n const store = useMemo(\n () => createCollectionStore({ data, urlSearch }),\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store recreated only when collection handle changes\n [data.handle],\n );\n\n const reconcilerRef = useRef<CollectionReconciler | null>(null);\n\n useLayoutEffect(() => {\n reconcilerRef.current = createCollectionReconciler(\n {\n getStore: () => store,\n readUrlSearch: () => urlSearchRef.current,\n emitChange: (s) => onChangeRef.current?.(s),\n },\n urlSearchRef.current,\n );\n store.setOnBrowseChange(() => reconcilerRef.current?.handleBrowseChange());\n return () => {\n store.setOnBrowseChange(null);\n };\n }, [store]);\n\n useLayoutEffect(() => {\n reconcilerRef.current?.reconcile(urlSearch, dataRef.current.dataSearch);\n }, [urlSearch, store, collectionHandle, dataSearch]);\n\n return createElement(CollectionContext.Provider, { value: store }, children);\n}\n\nfunction useRequiredStore(hookName: string): CollectionStore {\n const store = useContext(CollectionContext);\n if (!store) {\n throw new Error(`${hookName} must be used inside a <CollectionProvider>.`);\n }\n return store;\n}\n\n/**\n * Subscribes to the collection store and returns the full state snapshot.\n *\n * @example\n * ```tsx\n * const { status, filters } = useCollection();\n * ```\n */\nexport function useCollection(): CollectionState;\n/**\n * Subscribes to the collection store and returns a derived value via `selector`.\n * Optionally accepts an `isEqual` comparator to skip re-renders when the\n * derived value is structurally unchanged.\n *\n * @example\n * ```tsx\n * const status = useCollection(s => s.status);\n * const filters = useCollection(s => s.filters, shallowEqual);\n * ```\n */\nexport function useCollection<S>(\n selector: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S;\nexport function useCollection<S>(\n selector?: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): CollectionState | S {\n const store = useRequiredStore(\"useCollection\");\n const cachedRef = useRef<{\n state: unknown;\n selector: typeof selector;\n value: CollectionState | 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\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/** Mutation methods that update store state and trigger the `onChange` callback. */\nexport type CollectionActions = Pick<\n CollectionStore,\n | \"setFilters\"\n | \"toggleFilter\"\n | \"toggleFilterInput\"\n | \"setSortKey\"\n | \"setSortByValue\"\n | \"reset\"\n | \"handleFormSubmit\"\n>;\n\n/**\n * Returns methods that change filters and sort. The store's `onBrowseChange`\n * callback (set by {@link CollectionProvider}) handles calling `onChange` with\n * a serialized search string.\n */\nexport function useCollectionActions(): CollectionActions {\n const store = useRequiredStore(\"useCollectionActions\");\n\n return useMemo(\n () => ({\n setFilters: store.setFilters,\n toggleFilter: store.toggleFilter,\n toggleFilterInput: store.toggleFilterInput,\n setSortKey: store.setSortKey,\n setSortByValue: store.setSortByValue,\n reset: store.reset,\n handleFormSubmit: store.handleFormSubmit,\n }),\n [store],\n );\n}\n\n/**\n * Returns form props for progressive-enhancement of collection filter forms.\n *\n * @example\n * ```tsx\n * const { formProps } = useCollectionForm();\n * return (\n * <form {...formProps()} action=\"/collections/shoes\">\n * <input type=\"checkbox\" name=\"filter.p.tag\" value=\"sale\" />\n * <button type=\"submit\">Apply</button>\n * </form>\n * );\n * ```\n */\nexport function useCollectionForm() {\n const actions = useCollectionActions();\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 actions.handleFormSubmit(e.nativeEvent);\n opts?.afterSubmit?.(e);\n },\n }),\n [actions],\n );\n\n return { formProps };\n}\n\nexport type { CollectionData };\n"],"mappings":";;;;;;;AAyBA,MAAM,oBAAoB,cAAsC,KAAK;;;;;;AA8BrE,SAAgB,mBAAmB,EACjC,MACA,YAAY,IACZ,UACA,YAC0B;CAC1B,MAAM,cAAc,OAAO,SAAS;AACpC,aAAY,UAAU;CAEtB,MAAM,eAAe,OAAO,UAAU;AACtC,cAAa,UAAU;CAEvB,MAAM,UAAU,OAAO,KAAK;AAC5B,SAAQ,UAAU;CAElB,MAAM,EAAE,QAAQ,kBAAkB,eAAe;CAEjD,MAAM,QAAQ,cACN,sBAAsB;EAAE;EAAM;EAAW,CAAC,EAEhD,CAAC,KAAK,OAAO,CACd;CAED,MAAM,gBAAgB,OAAoC,KAAK;AAE/D,uBAAsB;AACpB,gBAAc,UAAU,2BACtB;GACE,gBAAgB;GAChB,qBAAqB,aAAa;GAClC,aAAa,MAAM,YAAY,UAAU,EAAE;GAC5C,EACD,aAAa,QACd;AACD,QAAM,wBAAwB,cAAc,SAAS,oBAAoB,CAAC;AAC1E,eAAa;AACX,SAAM,kBAAkB,KAAK;;IAE9B,CAAC,MAAM,CAAC;AAEX,uBAAsB;AACpB,gBAAc,SAAS,UAAU,WAAW,QAAQ,QAAQ,WAAW;IACtE;EAAC;EAAW;EAAO;EAAkB;EAAW,CAAC;AAEpD,QAAO,cAAc,kBAAkB,UAAU,EAAE,OAAO,OAAO,EAAE,SAAS;;AAG9E,SAAS,iBAAiB,UAAmC;CAC3D,MAAM,QAAQ,WAAW,kBAAkB;AAC3C,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,GAAG,SAAS,8CAA8C;AAE5E,QAAO;;AA2BT,SAAgB,cACd,UACA,SACqB;CACrB,MAAM,QAAQ,iBAAiB,gBAAgB;CAC/C,MAAM,YAAY,OAIR,KAAK;CAEf,MAAM,oBAAoB;EACxB,MAAM,QAAQ,MAAM,UAAU;AAE9B,MACE,UAAU,WACV,UAAU,QAAQ,UAAU,SAC5B,UAAU,QAAQ,aAAa,SAE/B,QAAO,UAAU,QAAQ;AAG3B,MAAI,CAAC,UAAU;AACb,aAAU,UAAU;IAAE;IAAO;IAAU,OAAO;IAAO;AACrD,UAAO;;EAGT,MAAM,OAAO,SAAS,MAAM;AAE5B,MAAI,UAAU,WAAW,UAAU,UAAU,QAAQ,OAAY,KAAK,EAAE;AACtE,aAAU,UAAU;IAAE;IAAO;IAAU,OAAO,UAAU,QAAQ;IAAO;AACvE,UAAO,UAAU,QAAQ;;AAG3B,YAAU,UAAU;GAAE;GAAO;GAAU,OAAO;GAAM;AACpD,SAAO;;AAGT,QAAO,qBAAqB,MAAM,WAAW,aAAa,YAAY;;;;;;;AAoBxE,SAAgB,uBAA0C;CACxD,MAAM,QAAQ,iBAAiB,uBAAuB;AAEtD,QAAO,eACE;EACL,YAAY,MAAM;EAClB,cAAc,MAAM;EACpB,mBAAmB,MAAM;EACzB,YAAY,MAAM;EAClB,gBAAgB,MAAM;EACtB,OAAO,MAAM;EACb,kBAAkB,MAAM;EACzB,GACD,CAAC,MAAM,CACR;;;;;;;;;;;;;;;;AAiBH,SAAgB,oBAAoB;CAClC,MAAM,UAAU,sBAAsB;AAkBtC,QAAO,EAAE,WAhBS,aACf,UAG2C,EAC1C,WAAW,MAAoC;AAC7C,QAAM,eAAe,EAAE;AACvB,MAAI,EAAE,iBAAkB;AACxB,IAAE,gBAAgB;AAClB,UAAQ,iBAAiB,EAAE,YAAY;AACvC,QAAM,cAAc,EAAE;IAEzB,GACD,CAAC,QAAQ,CAGO,EAAE"}
1
+ {"version":3,"file":"collection.mjs","names":[],"sources":["../../../src/react/collection.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n createContext,\n createElement,\n useCallback,\n useContext,\n useLayoutEffect,\n useMemo,\n useRef,\n useSyncExternalStore,\n type FormHTMLAttributes,\n type ReactNode,\n type SubmitEvent,\n} from \"react\";\n\nimport {\n createCollectionStore,\n createCollectionReconciler,\n type CollectionActions,\n type CollectionData,\n type CollectionReconciler,\n type CollectionStore,\n} from \"../core/collection\";\nimport type { CollectionState } from \"../core/collection\";\n\nconst CollectionContext = createContext<CollectionStore | null>(null);\n\n/** Props for the {@link CollectionProvider} component. */\nexport interface CollectionProviderProps {\n /** Collection metadata from the framework data fetch (`handle`, `dataSearch`). */\n data: CollectionData;\n /**\n * Live URL search string from the framework router (with or without leading `?`).\n * Seeds the store on mount and keeps browse state in sync with back/forward.\n */\n urlSearch?: string;\n /**\n * Called after user-initiated filter/sort changes with a ready-to-use\n * search string. The consumer's only job is to navigate:\n *\n * ```tsx\n * onChange={(search) =>\n * navigate({ search }, { replace: searchParams.size > 0, preventScrollReset: true })\n * }\n * ```\n */\n onChange?: (searchString: string) => void;\n children?: ReactNode;\n}\n\n/**\n * Manages the lifecycle of a {@link CollectionStore}: creates on mount and\n * syncs with URL changes. Recreates the store when `data.handle` changes\n * (navigating to a different collection).\n */\nexport function CollectionProvider({\n data,\n urlSearch = \"\",\n onChange,\n children,\n}: CollectionProviderProps) {\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n const urlSearchRef = useRef(urlSearch);\n urlSearchRef.current = urlSearch;\n\n const dataRef = useRef(data);\n dataRef.current = data;\n\n const { handle: collectionHandle, dataSearch } = data;\n\n const store = useMemo(\n () => createCollectionStore({ data, urlSearch }),\n // oxlint-disable-next-line react-hooks/exhaustive-deps -- store recreated only when collection handle changes\n [data.handle],\n );\n\n const reconcilerRef = useRef<CollectionReconciler | null>(null);\n\n useLayoutEffect(() => {\n reconcilerRef.current = createCollectionReconciler(\n {\n getStore: () => store,\n readUrlSearch: () => urlSearchRef.current,\n emitChange: (s) => onChangeRef.current?.(s),\n },\n urlSearchRef.current,\n );\n store.setOnBrowseChange(() => reconcilerRef.current?.handleBrowseChange());\n return () => {\n store.setOnBrowseChange(null);\n };\n }, [store]);\n\n useLayoutEffect(() => {\n reconcilerRef.current?.reconcile(urlSearch, dataRef.current.dataSearch);\n }, [urlSearch, store, collectionHandle, dataSearch]);\n\n return createElement(CollectionContext.Provider, { value: store }, children);\n}\n\nfunction useRequiredStore(hookName: string): CollectionStore {\n const store = useContext(CollectionContext);\n if (!store) {\n throw new Error(`${hookName} must be used inside a <CollectionProvider>.`);\n }\n return store;\n}\n\n/**\n * Subscribes to the collection store and returns the full state snapshot.\n *\n * @example\n * ```tsx\n * const { status, filters } = useCollection();\n * ```\n */\nexport function useCollection(): CollectionState;\n/**\n * Subscribes to the collection store and returns a derived value via `selector`.\n * Optionally accepts an `isEqual` comparator to skip re-renders when the\n * derived value is structurally unchanged.\n *\n * @example\n * ```tsx\n * const status = useCollection(s => s.status);\n * const filters = useCollection(s => s.filters, shallowEqual);\n * ```\n */\nexport function useCollection<S>(\n selector: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): S;\nexport function useCollection<S>(\n selector?: (state: CollectionState) => S,\n isEqual?: (a: S, b: S) => boolean,\n): CollectionState | S {\n const store = useRequiredStore(\"useCollection\");\n const cachedRef = useRef<{\n state: unknown;\n selector: typeof selector;\n value: CollectionState | 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\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 methods that change filters and sort. The store's `onBrowseChange`\n * callback (set by {@link CollectionProvider}) handles calling `onChange` with\n * a serialized search string.\n */\nexport function useCollectionActions(): CollectionActions {\n const store = useRequiredStore(\"useCollectionActions\");\n\n return useMemo(\n () => ({\n setFilters: store.setFilters,\n toggleFilter: store.toggleFilter,\n toggleFilterInput: store.toggleFilterInput,\n setSortKey: store.setSortKey,\n setSortByValue: store.setSortByValue,\n reset: store.reset,\n handleFormSubmit: store.handleFormSubmit,\n }),\n [store],\n );\n}\n\n/**\n * Returns form props for progressive-enhancement of collection filter forms.\n *\n * @example\n * ```tsx\n * const { formProps } = useCollectionForm();\n * return (\n * <form {...formProps()} action=\"/collections/shoes\">\n * <input type=\"checkbox\" name=\"filter.p.tag\" value=\"sale\" />\n * <button type=\"submit\">Apply</button>\n * </form>\n * );\n * ```\n */\nexport function useCollectionForm() {\n const actions = useCollectionActions();\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 actions.handleFormSubmit(e.nativeEvent);\n opts?.afterSubmit?.(e);\n },\n }),\n [actions],\n );\n\n return { formProps };\n}\n\nexport type { CollectionActions, CollectionData };\n"],"mappings":";;;;;;;AA0BA,MAAM,oBAAoB,cAAsC,KAAK;;;;;;AA8BrE,SAAgB,mBAAmB,EACjC,MACA,YAAY,IACZ,UACA,YAC0B;CAC1B,MAAM,cAAc,OAAO,SAAS;AACpC,aAAY,UAAU;CAEtB,MAAM,eAAe,OAAO,UAAU;AACtC,cAAa,UAAU;CAEvB,MAAM,UAAU,OAAO,KAAK;AAC5B,SAAQ,UAAU;CAElB,MAAM,EAAE,QAAQ,kBAAkB,eAAe;CAEjD,MAAM,QAAQ,cACN,sBAAsB;EAAE;EAAM;EAAW,CAAC,EAEhD,CAAC,KAAK,OAAO,CACd;CAED,MAAM,gBAAgB,OAAoC,KAAK;AAE/D,uBAAsB;AACpB,gBAAc,UAAU,2BACtB;GACE,gBAAgB;GAChB,qBAAqB,aAAa;GAClC,aAAa,MAAM,YAAY,UAAU,EAAE;GAC5C,EACD,aAAa,QACd;AACD,QAAM,wBAAwB,cAAc,SAAS,oBAAoB,CAAC;AAC1E,eAAa;AACX,SAAM,kBAAkB,KAAK;;IAE9B,CAAC,MAAM,CAAC;AAEX,uBAAsB;AACpB,gBAAc,SAAS,UAAU,WAAW,QAAQ,QAAQ,WAAW;IACtE;EAAC;EAAW;EAAO;EAAkB;EAAW,CAAC;AAEpD,QAAO,cAAc,kBAAkB,UAAU,EAAE,OAAO,OAAO,EAAE,SAAS;;AAG9E,SAAS,iBAAiB,UAAmC;CAC3D,MAAM,QAAQ,WAAW,kBAAkB;AAC3C,KAAI,CAAC,MACH,OAAM,IAAI,MAAM,GAAG,SAAS,8CAA8C;AAE5E,QAAO;;AA2BT,SAAgB,cACd,UACA,SACqB;CACrB,MAAM,QAAQ,iBAAiB,gBAAgB;CAC/C,MAAM,YAAY,OAIR,KAAK;CAEf,MAAM,oBAAoB;EACxB,MAAM,QAAQ,MAAM,UAAU;AAE9B,MACE,UAAU,WACV,UAAU,QAAQ,UAAU,SAC5B,UAAU,QAAQ,aAAa,SAE/B,QAAO,UAAU,QAAQ;AAG3B,MAAI,CAAC,UAAU;AACb,aAAU,UAAU;IAAE;IAAO;IAAU,OAAO;IAAO;AACrD,UAAO;;EAGT,MAAM,OAAO,SAAS,MAAM;AAE5B,MAAI,UAAU,WAAW,UAAU,UAAU,QAAQ,OAAY,KAAK,EAAE;AACtE,aAAU,UAAU;IAAE;IAAO;IAAU,OAAO,UAAU,QAAQ;IAAO;AACvE,UAAO,UAAU,QAAQ;;AAG3B,YAAU,UAAU;GAAE;GAAO;GAAU,OAAO;GAAM;AACpD,SAAO;;AAGT,QAAO,qBAAqB,MAAM,WAAW,aAAa,YAAY;;;;;;;AAQxE,SAAgB,uBAA0C;CACxD,MAAM,QAAQ,iBAAiB,uBAAuB;AAEtD,QAAO,eACE;EACL,YAAY,MAAM;EAClB,cAAc,MAAM;EACpB,mBAAmB,MAAM;EACzB,YAAY,MAAM;EAClB,gBAAgB,MAAM;EACtB,OAAO,MAAM;EACb,kBAAkB,MAAM;EACzB,GACD,CAAC,MAAM,CACR;;;;;;;;;;;;;;;;AAiBH,SAAgB,oBAAoB;CAClC,MAAM,UAAU,sBAAsB;AAkBtC,QAAO,EAAE,WAhBS,aACf,UAG2C,EAC1C,WAAW,MAAoC;AAC7C,QAAM,eAAe,EAAE;AACvB,MAAI,EAAE,iBAAkB;AACxB,IAAE,gBAAgB;AAClB,UAAQ,iBAAiB,EAAE,YAAY;AACvC,QAAM,cAAc,EAAE;IAEzB,GACD,CAAC,QAAQ,CAGO,EAAE"}
@@ -1,13 +1,15 @@
1
1
  import { ShopifyStandardActions } from "../vendor/standard-actions.mjs";
2
+ import { CartActions } from "../core/cart/cart.mjs";
2
3
  import { ShopifyGlobal } from "../globals.mjs";
3
4
  import { QuantityInputAttributes, SetButtonAttributes } from "../core/cart/form.mjs";
4
- import { CollectionData } from "../core/collection/collection.mjs";
5
+ import { CollectionActions, CollectionData } from "../core/collection/collection.mjs";
5
6
  import { ValidProductSelectionResult } from "../core/product/product-form.mjs";
6
- import { ProductAddToCartProps, ProductFormRegister, ProductMerchandiseIdProps, ProductOptionValueProps, ProductQuantityDefaultProps, ProductQuantityProps } from "../core/product/form.mjs";
7
- import { CartActions, CartProvider, createCartComponents, useCart, useCartActions, useCartAnalytics, useCartForm } from "./cart.mjs";
8
- import { CollectionActions, CollectionProvider, CollectionProviderProps, useCollection, useCollectionActions, useCollectionForm } from "./collection.mjs";
9
- import { PredictiveSearchActions, PredictiveSearchFormPropsOptions, PredictiveSearchFormRegister, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchProviderProps, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm } from "./predictive-search.mjs";
7
+ import { ProductAddToCartProps, ProductAttributeDefaultValueProps, ProductAttributeValueProps, ProductFormRegister, ProductMerchandiseIdProps, ProductOptionValueProps, ProductQuantityDefaultProps, ProductQuantityProps } from "../core/product/form.mjs";
8
+ import { PredictiveSearchActions } from "../core/predictive-search/store.mjs";
9
+ import { createCartComponents, useCartAnalytics } from "./cart.mjs";
10
+ import { CollectionProvider, CollectionProviderProps, useCollection, useCollectionActions, useCollectionForm } from "./collection.mjs";
11
+ import { PredictiveSearchFormPropsOptions, PredictiveSearchFormRegister, PredictiveSearchFormResult, PredictiveSearchProvider, PredictiveSearchProviderProps, PredictiveSearchQueryInputPropsOptions, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm } from "./predictive-search.mjs";
10
12
  import { ProductProviderProps, UseProductFormOptions, UseProductFormResult, UseProductResult, createProductComponents, useProductForm } from "./product.mjs";
11
13
  import { ShopPayButton, ShopPayButtonProps } from "./shop-pay.mjs";
12
14
  import { ShopifyScripts, ShopifyScriptsProps } from "./shopify-scripts.mjs";
13
- export { type CartActions, CartProvider, type CollectionActions, type CollectionData, CollectionProvider, type CollectionProviderProps, type PredictiveSearchActions, type PredictiveSearchFormPropsOptions, type PredictiveSearchFormRegister, type PredictiveSearchFormResult, PredictiveSearchProvider, type PredictiveSearchProviderProps, type PredictiveSearchQueryInputPropsOptions, type ProductAddToCartProps, type ProductFormRegister, type ProductMerchandiseIdProps, type ProductOptionValueProps, type ProductProviderProps, type ProductQuantityDefaultProps, type ProductQuantityProps, type QuantityInputAttributes, type SetButtonAttributes, ShopPayButton, type ShopPayButtonProps, type ShopifyGlobal, ShopifyScripts, type ShopifyScriptsProps, type ShopifyStandardActions, type UseProductFormOptions, type UseProductFormResult, type UseProductResult, type ValidProductSelectionResult, createCartComponents, createProductComponents, useCart, useCartActions, useCartAnalytics, useCartForm, useCollection, useCollectionActions, useCollectionForm, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm, useProductForm };
15
+ export { type CartActions, type CollectionActions, type CollectionData, CollectionProvider, type CollectionProviderProps, type PredictiveSearchActions, type PredictiveSearchFormPropsOptions, type PredictiveSearchFormRegister, type PredictiveSearchFormResult, PredictiveSearchProvider, type PredictiveSearchProviderProps, type PredictiveSearchQueryInputPropsOptions, type ProductAddToCartProps, type ProductAttributeDefaultValueProps, type ProductAttributeValueProps, type ProductFormRegister, type ProductMerchandiseIdProps, type ProductOptionValueProps, type ProductProviderProps, type ProductQuantityDefaultProps, type ProductQuantityProps, type QuantityInputAttributes, type SetButtonAttributes, ShopPayButton, type ShopPayButtonProps, type ShopifyGlobal, ShopifyScripts, type ShopifyScriptsProps, type ShopifyStandardActions, type UseProductFormOptions, type UseProductFormResult, type UseProductResult, type ValidProductSelectionResult, createCartComponents, createProductComponents, useCartAnalytics, useCollection, useCollectionActions, useCollectionForm, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm, useProductForm };
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { CartProvider, createCartComponents, useCart, useCartActions, useCartAnalytics, useCartForm } from "./cart.mjs";
3
+ import { createCartComponents, useCartAnalytics } from "./cart.mjs";
4
4
  import { CollectionProvider, useCollection, useCollectionActions, useCollectionForm } from "./collection.mjs";
5
5
  import { PredictiveSearchProvider, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm } from "./predictive-search.mjs";
6
6
  import { createProductComponents, useProductForm } from "./product.mjs";
7
7
  import { ShopPayButton } from "./shop-pay.mjs";
8
8
  import { ShopifyScripts } from "./shopify-scripts.mjs";
9
9
 
10
- export { CartProvider, CollectionProvider, PredictiveSearchProvider, ShopPayButton, ShopifyScripts, createCartComponents, createProductComponents, useCart, useCartActions, useCartAnalytics, useCartForm, useCollection, useCollectionActions, useCollectionForm, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm, useProductForm };
10
+ export { CollectionProvider, PredictiveSearchProvider, ShopPayButton, ShopifyScripts, createCartComponents, createProductComponents, useCartAnalytics, useCollection, useCollectionActions, useCollectionForm, usePredictiveSearch, usePredictiveSearchActions, usePredictiveSearchForm, useProductForm };