@vtex/faststore 0.0.7 → 0.0.10

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 (498) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/dist/actions.json +194 -1
  3. package/dist/catalog.json +3449 -1612
  4. package/dist/cli/index.d.ts +512 -63
  5. package/dist/cli/index.js +2098 -686
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/server-runtime.js +44 -24
  8. package/dist/cli/server-runtime.js.map +1 -1
  9. package/dist/client.d.ts +5 -1
  10. package/dist/client.js +5 -1
  11. package/dist/components.d.ts +42 -10
  12. package/dist/components.js +47 -15
  13. package/dist/content-types.json +823 -0
  14. package/dist/core/dist/define-component.d.ts +12 -2
  15. package/dist/docs/account-access-gate.md +4 -4
  16. package/dist/docs/account-address-book.md +519 -0
  17. package/dist/docs/account-entry-point.md +5 -5
  18. package/dist/docs/account-menu.md +7 -7
  19. package/dist/docs/account-saved-cards.md +301 -0
  20. package/dist/docs/alert-dialog.md +2 -2
  21. package/dist/docs/billing-address.md +17 -9
  22. package/dist/docs/binding-switcher.md +154 -0
  23. package/dist/docs/card-entry.md +38 -7
  24. package/dist/docs/card-summary.md +175 -0
  25. package/dist/docs/cardholder-document.md +16 -14
  26. package/dist/docs/cart-destination.md +238 -0
  27. package/dist/docs/cart-item.md +22 -4
  28. package/dist/docs/cart-totalizer.md +1 -1
  29. package/dist/docs/cart.md +126 -19
  30. package/dist/docs/client-profile-fieldset.md +7 -5
  31. package/dist/docs/copy-text.md +84 -0
  32. package/dist/docs/document-field.md +8 -8
  33. package/dist/docs/footer-link-group.md +2 -2
  34. package/dist/docs/footer-social.md +1 -1
  35. package/dist/docs/free-shipping-progress.md +13 -9
  36. package/dist/docs/header-root.md +1 -1
  37. package/dist/docs/incentives.md +4 -4
  38. package/dist/docs/logo.md +6 -6
  39. package/dist/docs/my-account-profile.md +4 -4
  40. package/dist/docs/navigation-link-list.md +2 -2
  41. package/dist/docs/navigation-link.md +3 -3
  42. package/dist/docs/order-cancel.md +52 -0
  43. package/dist/docs/order-delivery-address.md +63 -0
  44. package/dist/docs/order-delivery-group.md +83 -0
  45. package/dist/docs/order-details-back.md +41 -0
  46. package/dist/docs/order-details-header.md +48 -0
  47. package/dist/docs/order-list-row.md +46 -0
  48. package/dist/docs/order-list-search.md +101 -0
  49. package/dist/docs/order-list-views.md +97 -0
  50. package/dist/docs/order-list.md +94 -0
  51. package/dist/docs/order-payment.md +94 -0
  52. package/dist/docs/order-placed-date.md +49 -0
  53. package/dist/docs/order-placed-payment-summary.md +13 -15
  54. package/dist/docs/order-placed.md +225 -0
  55. package/dist/docs/order-progress.md +88 -0
  56. package/dist/docs/order-status-badge.md +91 -0
  57. package/dist/docs/order-summary.md +53 -0
  58. package/dist/docs/payment-method-badges.md +16 -5
  59. package/dist/docs/payment-methods.md +1 -1
  60. package/dist/docs/place-order.md +13 -9
  61. package/dist/docs/product-information.md +2 -2
  62. package/dist/docs/product-shelf.md +7 -0
  63. package/dist/docs/saved-cards.md +2 -2
  64. package/dist/docs/select.md +22 -10
  65. package/dist/docs/session.md +1 -1
  66. package/dist/docs/shipping-address.md +1 -1
  67. package/dist/docs/sign-in-redirect.md +7 -7
  68. package/dist/docs/toast.md +35 -13
  69. package/dist/index.d.ts +7 -2
  70. package/dist/index.js +2 -1
  71. package/dist/internal/client.d.ts +9 -1
  72. package/dist/internal/client.js +10 -2
  73. package/dist/internal/server.d.ts +8 -2
  74. package/dist/internal/server.js +8 -2
  75. package/dist/matter/account-access-gate/account-access-gate.d.ts +2 -1
  76. package/dist/matter/account-access-gate/account-access-gate.js +19 -16
  77. package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
  78. package/dist/matter/account-address-book/account-address-book-delete.d.ts +60 -0
  79. package/dist/matter/account-address-book/account-address-book-delete.js +184 -0
  80. package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -0
  81. package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +19 -0
  82. package/dist/matter/account-address-book/account-address-book-deliverability.js +50 -0
  83. package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -0
  84. package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +42 -0
  85. package/dist/matter/account-address-book/account-address-book-form-parts.js +146 -0
  86. package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -0
  87. package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +25 -0
  88. package/dist/matter/account-address-book/account-address-book-profile-notice.js +72 -0
  89. package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -0
  90. package/dist/matter/account-address-book/account-address-book.d.ts +120 -0
  91. package/dist/matter/account-address-book/account-address-book.js +469 -0
  92. package/dist/matter/account-address-book/account-address-book.js.map +1 -0
  93. package/dist/matter/account-address-book/account-address-country-switch.js +59 -0
  94. package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -0
  95. package/dist/matter/account-address-book/account-address-country.d.ts +36 -0
  96. package/dist/matter/account-address-book/account-address-country.js +151 -0
  97. package/dist/matter/account-address-book/account-address-country.js.map +1 -0
  98. package/dist/matter/account-address-book/account-address-first-line.js +11 -0
  99. package/dist/matter/account-address-book/account-address-first-line.js.map +1 -0
  100. package/dist/matter/account-address-book/account-address-form-context.d.ts +55 -0
  101. package/dist/matter/account-address-book/account-address-form-context.js +14 -0
  102. package/dist/matter/account-address-book/account-address-form-context.js.map +1 -0
  103. package/dist/matter/account-address-book/account-address-form.d.ts +85 -0
  104. package/dist/matter/account-address-book/account-address-form.js +946 -0
  105. package/dist/matter/account-address-book/account-address-form.js.map +1 -0
  106. package/dist/matter/account-address-book/account-address-postal-autofill.js +189 -0
  107. package/dist/matter/account-address-book/account-address-postal-autofill.js.map +1 -0
  108. package/dist/matter/account-address-book/account-address-record.d.ts +26 -0
  109. package/dist/matter/account-address-book/account-address-record.js +138 -0
  110. package/dist/matter/account-address-book/account-address-record.js.map +1 -0
  111. package/dist/matter/account-entry-point/account-entry-point.js +11 -11
  112. package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
  113. package/dist/matter/account-menu/account-menu.d.ts +7 -3
  114. package/dist/matter/account-menu/account-menu.js +69 -28
  115. package/dist/matter/account-menu/account-menu.js.map +1 -1
  116. package/dist/matter/account-saved-cards/account-saved-cards.d.ts +383 -0
  117. package/dist/matter/account-saved-cards/account-saved-cards.js +956 -0
  118. package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
  119. package/dist/matter/billing-address/billing-address.d.ts +30 -17
  120. package/dist/matter/billing-address/billing-address.js +128 -33
  121. package/dist/matter/billing-address/billing-address.js.map +1 -1
  122. package/dist/matter/breadcrumb/breadcrumb.js +2 -2
  123. package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
  124. package/dist/matter/card-entry/card-capture.js +4 -3
  125. package/dist/matter/card-entry/card-capture.js.map +1 -1
  126. package/dist/matter/card-entry/card-entry.d.ts +5 -3
  127. package/dist/matter/card-entry/card-entry.js +77 -27
  128. package/dist/matter/card-entry/card-entry.js.map +1 -1
  129. package/dist/matter/cardholder-document/cardholder-document.d.ts +10 -9
  130. package/dist/matter/cardholder-document/cardholder-document.js +36 -16
  131. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -1
  132. package/dist/matter/cart/cart-namespace.d.ts +5 -1
  133. package/dist/matter/cart/cart-namespace.js +5 -1
  134. package/dist/matter/cart/cart-namespace.js.map +1 -1
  135. package/dist/matter/cart/cart.d.ts +41 -6
  136. package/dist/matter/cart/cart.js +267 -54
  137. package/dist/matter/cart/cart.js.map +1 -1
  138. package/dist/matter/cart-destination/cart-destination.d.ts +222 -0
  139. package/dist/matter/cart-destination/cart-destination.js +613 -0
  140. package/dist/matter/cart-destination/cart-destination.js.map +1 -0
  141. package/dist/matter/cart-item/cart-item.d.ts +4 -2
  142. package/dist/matter/cart-item/cart-item.js +76 -17
  143. package/dist/matter/cart-item/cart-item.js.map +1 -1
  144. package/dist/matter/cart-totalizer/cart-totalizer.js +3 -2
  145. package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
  146. package/dist/matter/client-preferences/client-preferences-fieldset.js +1 -1
  147. package/dist/matter/client-profile-form/client-profile-fieldset.js +8 -5
  148. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
  149. package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +6 -3
  150. package/dist/matter/free-shipping-progress/free-shipping-progress.js +76 -33
  151. package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
  152. package/dist/matter/identity-edit/identity-edit.js +3 -3
  153. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  154. package/dist/matter/identity-summary/identity-summary.js +2 -2
  155. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  156. package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -3
  157. package/dist/matter/my-account-profile/my-account-profile.js +4 -4
  158. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  159. package/dist/matter/order-cancel/order-cancel.d.ts +110 -0
  160. package/dist/matter/order-cancel/order-cancel.js +344 -0
  161. package/dist/matter/order-cancel/order-cancel.js.map +1 -0
  162. package/dist/matter/order-delivery-address/order-delivery-address.d.ts +73 -0
  163. package/dist/matter/order-delivery-address/order-delivery-address.js +172 -0
  164. package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -0
  165. package/dist/matter/order-delivery-group/order-delivery-group.d.ts +128 -0
  166. package/dist/matter/order-delivery-group/order-delivery-group.js +408 -0
  167. package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -0
  168. package/dist/matter/order-details-header/order-details-header.d.ts +104 -0
  169. package/dist/matter/order-details-header/order-details-header.js +190 -0
  170. package/dist/matter/order-details-header/order-details-header.js.map +1 -0
  171. package/dist/matter/order-list/customer-orders-filter.d.ts +12 -0
  172. package/dist/matter/order-list/customer-orders-filter.js +30 -0
  173. package/dist/matter/order-list/customer-orders-filter.js.map +1 -0
  174. package/dist/matter/order-list/order-list.d.ts +121 -0
  175. package/dist/matter/order-list/order-list.js +395 -0
  176. package/dist/matter/order-list/order-list.js.map +1 -0
  177. package/dist/matter/order-list-row/order-list-row.d.ts +44 -0
  178. package/dist/matter/order-list-row/order-list-row.js +127 -0
  179. package/dist/matter/order-list-row/order-list-row.js.map +1 -0
  180. package/dist/matter/order-list-search/order-list-search.d.ts +72 -0
  181. package/dist/matter/order-list-search/order-list-search.js +296 -0
  182. package/dist/matter/order-list-search/order-list-search.js.map +1 -0
  183. package/dist/matter/order-list-views/order-list-views.d.ts +65 -0
  184. package/dist/matter/order-list-views/order-list-views.js +154 -0
  185. package/dist/matter/order-list-views/order-list-views.js.map +1 -0
  186. package/dist/matter/order-payment/order-payment.d.ts +83 -0
  187. package/dist/matter/order-payment/order-payment.js +258 -0
  188. package/dist/matter/order-payment/order-payment.js.map +1 -0
  189. package/dist/matter/order-placed/order-placed-deliveries.d.ts +145 -0
  190. package/dist/matter/order-placed/order-placed-deliveries.js +380 -0
  191. package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -0
  192. package/dist/matter/order-placed/order-placed.d.ts +122 -0
  193. package/dist/matter/order-placed/order-placed.js +452 -0
  194. package/dist/matter/order-placed/order-placed.js.map +1 -0
  195. package/dist/matter/order-placed-date/order-placed-date.d.ts +45 -0
  196. package/dist/matter/order-placed-date/order-placed-date.js +92 -0
  197. package/dist/matter/order-placed-date/order-placed-date.js.map +1 -0
  198. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +5 -8
  199. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +29 -81
  200. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
  201. package/dist/matter/order-progress/order-progress.d.ts +85 -0
  202. package/dist/matter/order-progress/order-progress.js +203 -0
  203. package/dist/matter/order-progress/order-progress.js.map +1 -0
  204. package/dist/matter/order-summary/order-summary.d.ts +81 -0
  205. package/dist/matter/order-summary/order-summary.js +192 -0
  206. package/dist/matter/order-summary/order-summary.js.map +1 -0
  207. package/dist/matter/payment/cardholder-document-rule.d.ts +35 -0
  208. package/dist/matter/payment/cardholder-document-rule.js +35 -0
  209. package/dist/matter/payment/cardholder-document-rule.js.map +1 -0
  210. package/dist/matter/payment/payment-card-capture.js +38 -23
  211. package/dist/matter/payment/payment-card-capture.js.map +1 -1
  212. package/dist/matter/payment/payment-controller.js +11 -0
  213. package/dist/matter/payment/payment-controller.js.map +1 -1
  214. package/dist/matter/payment/payment-method-badges.js +4 -2
  215. package/dist/matter/payment/payment-method-badges.js.map +1 -1
  216. package/dist/matter/payment/payment.js +11 -0
  217. package/dist/matter/payment/payment.js.map +1 -1
  218. package/dist/matter/payment/use-payment-method-selection.d.ts +39 -0
  219. package/dist/matter/payment/use-payment-method-selection.js +30 -2
  220. package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
  221. package/dist/matter/payment/use-payment-submit-gate.d.ts +16 -2
  222. package/dist/matter/payment/use-payment-submit-gate.js +42 -14
  223. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
  224. package/dist/matter/place-order/place-order.d.ts +12 -7
  225. package/dist/matter/place-order/place-order.js +126 -103
  226. package/dist/matter/place-order/place-order.js.map +1 -1
  227. package/dist/matter/pricing/pricing.js +5 -5
  228. package/dist/matter/pricing/pricing.js.map +1 -1
  229. package/dist/matter/product-shelf/product-shelf.d.ts +7 -0
  230. package/dist/matter/product-shelf/product-shelf.js +115 -1
  231. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  232. package/dist/matter/region/region.js +1 -1
  233. package/dist/matter/saved-cards/saved-cards.js +26 -15
  234. package/dist/matter/saved-cards/saved-cards.js.map +1 -1
  235. package/dist/matter/session/session.js +2 -2
  236. package/dist/matter/session/session.js.map +1 -1
  237. package/dist/matter/shipping-address/country-descriptors.d.ts +1 -0
  238. package/dist/matter/shipping-address/country-descriptors.js +23 -1
  239. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  240. package/dist/matter/shipping-address/shipping-address.js +62 -14
  241. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  242. package/dist/matter/shipping-simulator/shipping-simulator.d.ts +2 -2
  243. package/dist/matter/shipping-simulator/shipping-simulator.js +6 -2
  244. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  245. package/dist/matter/sign-in-redirect/sign-in-redirect.js +48 -10
  246. package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -1
  247. package/dist/matter/variant-selector/variant-selector.js +2 -2
  248. package/dist/matter/variant-selector/variant-selector.js.map +1 -1
  249. package/dist/particles/address-summary/address-summary.js +2 -2
  250. package/dist/particles/address-summary/address-summary.js.map +1 -1
  251. package/dist/particles/alert-dialog/alert-dialog.d.ts +5 -1
  252. package/dist/particles/alert-dialog/alert-dialog.js +5 -2
  253. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -1
  254. package/dist/particles/binding-switcher/binding-switcher.d.ts +38 -0
  255. package/dist/particles/binding-switcher/binding-switcher.js +183 -0
  256. package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
  257. package/dist/particles/card-summary/card-network.d.ts +17 -0
  258. package/dist/particles/card-summary/card-network.js +25 -0
  259. package/dist/particles/card-summary/card-network.js.map +1 -0
  260. package/dist/particles/card-summary/card-summary.d.ts +83 -0
  261. package/dist/particles/card-summary/card-summary.js +188 -0
  262. package/dist/particles/card-summary/card-summary.js.map +1 -0
  263. package/dist/particles/copy-text/copy-text.d.ts +28 -0
  264. package/dist/particles/copy-text/copy-text.js +140 -0
  265. package/dist/particles/copy-text/copy-text.js.map +1 -0
  266. package/dist/particles/document-field/document-field.d.ts +34 -5
  267. package/dist/particles/document-field/document-field.js +115 -19
  268. package/dist/particles/document-field/document-field.js.map +1 -1
  269. package/dist/particles/field/field.js +1 -1
  270. package/dist/particles/field/field.js.map +1 -1
  271. package/dist/particles/footer-link-group/footer-link-group.d.ts +6 -2
  272. package/dist/particles/footer-link-group/footer-link-group.js +18 -7
  273. package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
  274. package/dist/particles/footer-social/footer-social.d.ts +1 -1
  275. package/dist/particles/footer-social/footer-social.js +1 -1
  276. package/dist/particles/footer-social/footer-social.js.map +1 -1
  277. package/dist/particles/incentives/incentives.d.ts +7 -3
  278. package/dist/particles/incentives/incentives.js +8 -3
  279. package/dist/particles/incentives/incentives.js.map +1 -1
  280. package/dist/particles/logo/logo.d.ts +3 -2
  281. package/dist/particles/logo/logo.js +20 -8
  282. package/dist/particles/logo/logo.js.map +1 -1
  283. package/dist/particles/navigation-link/navigation-link.d.ts +2 -3
  284. package/dist/particles/navigation-link/navigation-link.js +4 -4
  285. package/dist/particles/navigation-link/navigation-link.js.map +1 -1
  286. package/dist/particles/navigation-link-list/navigation-link-list.d.ts +6 -2
  287. package/dist/particles/navigation-link-list/navigation-link-list.js +18 -7
  288. package/dist/particles/navigation-link-list/navigation-link-list.js.map +1 -1
  289. package/dist/particles/order-details-back/order-details-back.d.ts +28 -0
  290. package/dist/particles/order-details-back/order-details-back.js +52 -0
  291. package/dist/particles/order-details-back/order-details-back.js.map +1 -0
  292. package/dist/particles/order-status-badge/order-status-badge.d.ts +19 -0
  293. package/dist/particles/order-status-badge/order-status-badge.js +50 -0
  294. package/dist/particles/order-status-badge/order-status-badge.js.map +1 -0
  295. package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
  296. package/dist/particles/payment-methods/payment-methods.js +1 -1
  297. package/dist/particles/payment-methods/payment-methods.js.map +1 -1
  298. package/dist/particles/phone-field/phone-field.js +2 -2
  299. package/dist/particles/phone-field/phone-field.js.map +1 -1
  300. package/dist/particles/select/select.js +6 -6
  301. package/dist/particles/select/select.js.map +1 -1
  302. package/dist/particles/toast/toast.d.ts +21 -7
  303. package/dist/particles/toast/toast.js +76 -8
  304. package/dist/particles/toast/toast.js.map +1 -1
  305. package/dist/schema.graphql +1376 -126
  306. package/dist/sdk/actions/address-action-provider.d.ts +16 -0
  307. package/dist/sdk/actions/address-action-provider.js +20 -0
  308. package/dist/sdk/actions/address-action-provider.js.map +1 -0
  309. package/dist/sdk/actions/analytics-action-provider.js +1 -1
  310. package/dist/sdk/actions/index.d.ts +5 -1
  311. package/dist/sdk/actions/index.js +5 -1
  312. package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
  313. package/dist/sdk/actions/locale-action-provider.js +20 -0
  314. package/dist/sdk/actions/locale-action-provider.js.map +1 -0
  315. package/dist/sdk/actions/order-action-provider.d.ts +12 -0
  316. package/dist/sdk/actions/order-action-provider.js +16 -0
  317. package/dist/sdk/actions/order-action-provider.js.map +1 -0
  318. package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
  319. package/dist/sdk/actions/saved-card-action-provider.js +16 -0
  320. package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
  321. package/dist/sdk/actions/types.d.ts +10 -2
  322. package/dist/sdk/address/address-fields.js +38 -0
  323. package/dist/sdk/address/address-fields.js.map +1 -0
  324. package/dist/sdk/address/checkout-readiness.js +17 -0
  325. package/dist/sdk/address/checkout-readiness.js.map +1 -0
  326. package/dist/sdk/address/constants.js +60 -0
  327. package/dist/sdk/address/constants.js.map +1 -0
  328. package/dist/sdk/address/country-display.js +19 -0
  329. package/dist/sdk/address/country-display.js.map +1 -0
  330. package/dist/sdk/address/country-rules.js +30 -0
  331. package/dist/sdk/address/country-rules.js.map +1 -0
  332. package/dist/sdk/address/format-address.js +18 -0
  333. package/dist/sdk/address/format-address.js.map +1 -0
  334. package/dist/sdk/address/iso-countries.js +262 -0
  335. package/dist/sdk/address/iso-countries.js.map +1 -0
  336. package/dist/sdk/address/layouts.js +56 -0
  337. package/dist/sdk/address/layouts.js.map +1 -0
  338. package/dist/sdk/address/messages.js +97 -0
  339. package/dist/sdk/address/messages.js.map +1 -0
  340. package/dist/sdk/address/operations.d.ts +54 -0
  341. package/dist/sdk/address/operations.js +83 -0
  342. package/dist/sdk/address/operations.js.map +1 -0
  343. package/dist/sdk/address/postal-code-lookup-operations.js +23 -0
  344. package/dist/sdk/address/postal-code-lookup-operations.js.map +1 -0
  345. package/dist/sdk/address/postal-code.js +11 -0
  346. package/dist/sdk/address/postal-code.js.map +1 -0
  347. package/dist/sdk/address/schema.js +14 -0
  348. package/dist/sdk/address/schema.js.map +1 -0
  349. package/dist/sdk/address/server-fields.js +25 -0
  350. package/dist/sdk/address/server-fields.js.map +1 -0
  351. package/dist/sdk/address/switch-country.js +57 -0
  352. package/dist/sdk/address/switch-country.js.map +1 -0
  353. package/dist/sdk/address/types.d.ts +26 -0
  354. package/dist/sdk/address/use-address-actions.d.ts +29 -0
  355. package/dist/sdk/address/use-address-actions.js +247 -0
  356. package/dist/sdk/address/use-address-actions.js.map +1 -0
  357. package/dist/sdk/address/use-address-by-postal-code.js +57 -0
  358. package/dist/sdk/address/use-address-by-postal-code.js.map +1 -0
  359. package/dist/sdk/address/validate-address.js +62 -0
  360. package/dist/sdk/address/validate-address.js.map +1 -0
  361. package/dist/sdk/cache/storefront-cache.js +30 -4
  362. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  363. package/dist/sdk/cart/operations.d.ts +2 -1
  364. package/dist/sdk/cart/operations.js +55 -1
  365. package/dist/sdk/cart/operations.js.map +1 -1
  366. package/dist/sdk/cart/optimistic.d.ts +30 -7
  367. package/dist/sdk/cart/optimistic.js +78 -41
  368. package/dist/sdk/cart/optimistic.js.map +1 -1
  369. package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
  370. package/dist/sdk/cart/use-cart-actions.js +61 -15
  371. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  372. package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
  373. package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
  374. package/dist/sdk/client/fetch-page-data.d.ts +11 -0
  375. package/dist/sdk/client/fetch-page-data.js +12 -1
  376. package/dist/sdk/client/fetch-page-data.js.map +1 -1
  377. package/dist/sdk/content/create-content-runtime.js +1 -8
  378. package/dist/sdk/content/create-content-runtime.js.map +1 -1
  379. package/dist/sdk/data/failed-fields.js +23 -0
  380. package/dist/sdk/data/failed-fields.js.map +1 -0
  381. package/dist/sdk/data/index.js +2 -2
  382. package/dist/sdk/data/page-reactive-data-root.d.ts +45 -7
  383. package/dist/sdk/data/page-reactive-data-root.js +88 -12
  384. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  385. package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
  386. package/dist/sdk/generated/fragment-types.js +2 -0
  387. package/dist/sdk/generated/fragment-types.js.map +1 -1
  388. package/dist/sdk/generated/gql.js +17 -11
  389. package/dist/sdk/generated/gql.js.map +1 -1
  390. package/dist/sdk/generated/graphql.d.ts +185 -46
  391. package/dist/sdk/generated/graphql.js +2587 -155
  392. package/dist/sdk/generated/graphql.js.map +1 -1
  393. package/dist/sdk/i18n/messages/en-US.js +157 -1
  394. package/dist/sdk/i18n/messages/en-US.json +157 -1
  395. package/dist/sdk/i18n/messages/en-US.json.d.ts +156 -0
  396. package/dist/sdk/i18n/messages/es-ES.js +157 -1
  397. package/dist/sdk/i18n/messages/es-ES.json +157 -1
  398. package/dist/sdk/i18n/messages/pt-BR.js +157 -1
  399. package/dist/sdk/i18n/messages/pt-BR.json +157 -1
  400. package/dist/sdk/i18n/provider.js +1 -1
  401. package/dist/sdk/i18n/provider.js.map +1 -1
  402. package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
  403. package/dist/sdk/locale/binding-switch-provider.js +20 -0
  404. package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
  405. package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
  406. package/dist/sdk/locale/use-locale-actions.js +70 -0
  407. package/dist/sdk/locale/use-locale-actions.js.map +1 -0
  408. package/dist/sdk/navigation/binding-switch.d.ts +39 -0
  409. package/dist/sdk/navigation/binding-switch.js +129 -0
  410. package/dist/sdk/navigation/binding-switch.js.map +1 -0
  411. package/dist/sdk/navigation/current-page.js +30 -0
  412. package/dist/sdk/navigation/current-page.js.map +1 -0
  413. package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
  414. package/dist/sdk/navigation/decode-route-params.js +32 -0
  415. package/dist/sdk/navigation/decode-route-params.js.map +1 -0
  416. package/dist/sdk/navigation/not-found-log.d.ts +28 -0
  417. package/dist/sdk/navigation/not-found-log.js +133 -0
  418. package/dist/sdk/navigation/not-found-log.js.map +1 -0
  419. package/dist/sdk/navigation/path.js +25 -0
  420. package/dist/sdk/navigation/path.js.map +1 -0
  421. package/dist/sdk/navigation/redirect.d.ts +11 -0
  422. package/dist/sdk/navigation/redirect.js +22 -0
  423. package/dist/sdk/navigation/redirect.js.map +1 -0
  424. package/dist/sdk/navigation/report.js +70 -0
  425. package/dist/sdk/navigation/report.js.map +1 -0
  426. package/dist/sdk/navigation/resolve-storefront-href.d.ts +12 -0
  427. package/dist/sdk/navigation/resolve-storefront-href.js +214 -0
  428. package/dist/sdk/navigation/resolve-storefront-href.js.map +1 -0
  429. package/dist/sdk/navigation/route-identity-provider.d.ts +11 -0
  430. package/dist/sdk/navigation/route-identity-provider.js +20 -0
  431. package/dist/sdk/navigation/route-identity-provider.js.map +1 -0
  432. package/dist/sdk/navigation/route-map-provider.d.ts +19 -0
  433. package/dist/sdk/navigation/route-map-provider.js +29 -0
  434. package/dist/sdk/navigation/route-map-provider.js.map +1 -0
  435. package/dist/sdk/navigation/storefront-link.d.ts +16 -0
  436. package/dist/sdk/navigation/storefront-link.js +66 -0
  437. package/dist/sdk/navigation/storefront-link.js.map +1 -0
  438. package/dist/sdk/navigation/types.d.ts +45 -0
  439. package/dist/sdk/navigation/use-storefront-navigation.d.ts +26 -0
  440. package/dist/sdk/navigation/use-storefront-navigation.js +73 -0
  441. package/dist/sdk/navigation/use-storefront-navigation.js.map +1 -0
  442. package/dist/sdk/navigation/use-storefront-route.d.ts +26 -0
  443. package/dist/sdk/navigation/use-storefront-route.js +87 -0
  444. package/dist/sdk/navigation/use-storefront-route.js.map +1 -0
  445. package/dist/sdk/navigation/use-storefront-search-params.d.ts +11 -0
  446. package/dist/sdk/navigation/use-storefront-search-params.js +16 -0
  447. package/dist/sdk/navigation/use-storefront-search-params.js.map +1 -0
  448. package/dist/sdk/orders/completion-badge.d.ts +8 -0
  449. package/dist/sdk/orders/completion-badge.js +47 -0
  450. package/dist/sdk/orders/completion-badge.js.map +1 -0
  451. package/dist/sdk/orders/order-operations.d.ts +19 -0
  452. package/dist/sdk/orders/order-operations.js +21 -0
  453. package/dist/sdk/orders/order-operations.js.map +1 -0
  454. package/dist/sdk/orders/use-order-actions.d.ts +23 -0
  455. package/dist/sdk/orders/use-order-actions.js +62 -0
  456. package/dist/sdk/orders/use-order-actions.js.map +1 -0
  457. package/dist/sdk/payment/gateway-origin.js +26 -13
  458. package/dist/sdk/payment/gateway-origin.js.map +1 -1
  459. package/dist/sdk/payment/gateway.d.ts +14 -2
  460. package/dist/sdk/payment/gateway.js +32 -14
  461. package/dist/sdk/payment/gateway.js.map +1 -1
  462. package/dist/sdk/payment/operations.js +11 -0
  463. package/dist/sdk/payment/operations.js.map +1 -1
  464. package/dist/sdk/payment/submission-registry.d.ts +2 -1
  465. package/dist/sdk/payment/submission-registry.js +12 -8
  466. package/dist/sdk/payment/submission-registry.js.map +1 -1
  467. package/dist/sdk/payment/use-payment-actions.d.ts +5 -3
  468. package/dist/sdk/payment/use-payment-actions.js +5 -2
  469. package/dist/sdk/payment/use-payment-actions.js.map +1 -1
  470. package/dist/sdk/payment/use-place-order.d.ts +6 -3
  471. package/dist/sdk/payment/use-place-order.js +333 -143
  472. package/dist/sdk/payment/use-place-order.js.map +1 -1
  473. package/dist/sdk/saved-cards/operations.d.ts +16 -0
  474. package/dist/sdk/saved-cards/operations.js +41 -0
  475. package/dist/sdk/saved-cards/operations.js.map +1 -0
  476. package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
  477. package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
  478. package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
  479. package/dist/sdk/seo/page-alternates.d.ts +57 -0
  480. package/dist/sdk/seo/page-alternates.js +127 -0
  481. package/dist/sdk/seo/page-alternates.js.map +1 -0
  482. package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
  483. package/dist/sdk/shipping/shipping-operations.js +13 -0
  484. package/dist/sdk/shipping/shipping-operations.js.map +1 -1
  485. package/dist/sdk/state/cart.d.ts +9 -2
  486. package/dist/sdk/state/cart.js +9 -2
  487. package/dist/sdk/state/cart.js.map +1 -1
  488. package/dist/utils/own-value.js +19 -0
  489. package/dist/utils/own-value.js.map +1 -0
  490. package/dist/utils/safe-href.d.ts +4 -4
  491. package/dist/utils/safe-href.js +31 -22
  492. package/dist/utils/safe-href.js.map +1 -1
  493. package/package.json +5 -1
  494. package/skills/storefront-asset/SKILL.md +111 -0
  495. package/skills/storefront-asset/references/api-side.md +153 -0
  496. package/skills/storefront-asset/references/findings.md +40 -0
  497. package/skills/storefront-asset/references/ui-side.md +94 -0
  498. package/src/styles/tailwind.css +28 -2
@@ -325,6 +325,113 @@ const ProductShelfRootDefinition = defineComponent({
325
325
  },
326
326
  guideline: "Data-owning root of a facet-sourced product shelf. Owns a products(first:, orderBy:, filter:) fragment whose variables bind to this element: the merchant fills them in the CMS (content model product-shelf) and the props of the same name are the defaults. Receives $data lazily. Compose as: ProductShelfRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext + CarouselBullets)). Compose ProductShelfHeading with the title as its children to render a heading; set the source with categoryId / brandId / collectionId / searchTerm and tune first, sort, availableOnly. Renders a skeleton while loading and nothing when the result is empty. Bind itemsPerPage to the CarouselRoot so paging matches the shelf config. See product-shelf.md."
327
327
  });
328
+ const ProductShelfOnMountRootDefinition = defineComponent({
329
+ name: "ProductShelfOnMountRoot",
330
+ importPath: "@vtex/faststore/components",
331
+ component: ProductShelfOnMountRoot,
332
+ fragment,
333
+ fetchStrategy: "lazy-on-mount",
334
+ variables: { orderBy: {
335
+ from: "sort",
336
+ values: {
337
+ relevance: {
338
+ field: "RELEVANCE",
339
+ direction: "DESC"
340
+ },
341
+ price_asc: {
342
+ field: "PRICE",
343
+ direction: "ASC"
344
+ },
345
+ price_desc: {
346
+ field: "PRICE",
347
+ direction: "DESC"
348
+ },
349
+ name_asc: {
350
+ field: "TITLE",
351
+ direction: "ASC"
352
+ },
353
+ name_desc: {
354
+ field: "TITLE",
355
+ direction: "DESC"
356
+ },
357
+ release: {
358
+ field: "RELEASE_DATE",
359
+ direction: "DESC"
360
+ },
361
+ orders: {
362
+ field: "BEST_SELLING",
363
+ direction: "DESC"
364
+ },
365
+ discount: {
366
+ field: "RELEVANCE",
367
+ direction: "DESC"
368
+ }
369
+ }
370
+ } },
371
+ editor: {
372
+ label: "Product Shelf Root (on mount)",
373
+ category: "commerce"
374
+ },
375
+ providesContext: "product-shelf",
376
+ schema: {
377
+ itemsPerPage: {
378
+ type: "number",
379
+ default: 5,
380
+ label: "Items Per Page"
381
+ },
382
+ usePriceWithTaxes: {
383
+ type: "boolean",
384
+ default: false,
385
+ label: "Use Price With Taxes"
386
+ },
387
+ taxesLabel: {
388
+ type: "string",
389
+ default: "Tax included",
390
+ label: "Taxes Label"
391
+ },
392
+ first: {
393
+ type: "number",
394
+ default: 5,
395
+ label: "Number of Products"
396
+ },
397
+ sort: {
398
+ type: [
399
+ "relevance",
400
+ "price_asc",
401
+ "price_desc",
402
+ "name_asc",
403
+ "name_desc",
404
+ "release",
405
+ "orders",
406
+ "discount"
407
+ ],
408
+ default: "relevance",
409
+ label: "Sort"
410
+ },
411
+ searchTerm: {
412
+ type: "string",
413
+ label: "Search Term"
414
+ },
415
+ categoryId: {
416
+ type: "string",
417
+ label: "Category ID"
418
+ },
419
+ brandId: {
420
+ type: "string",
421
+ label: "Brand ID"
422
+ },
423
+ collectionId: {
424
+ type: "string",
425
+ label: "Collection ID"
426
+ },
427
+ availableOnly: {
428
+ type: "boolean",
429
+ default: false,
430
+ label: "Available Only"
431
+ }
432
+ },
433
+ guideline: "ProductShelfRoot variant that fetches its products on the client when it mounts, in its own request: the products never enter the page query and are absent from the server HTML. Renders nothing while loading, when the request fails and when the result is empty (no skeleton). Meant for a conditional branch such as CartEmpty, never above the fold. Configured by props only (first, sort, availableOnly, categoryId, brandId, collectionId, searchTerm, itemsPerPage). Compose as: ProductShelfOnMountRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext)). See product-shelf.md."
434
+ });
328
435
  const ProductShelfItemsDefinition = defineComponent({
329
436
  name: "ProductShelfItems",
330
437
  importPath: "@vtex/faststore/components",
@@ -424,6 +531,13 @@ function ProductShelfRoot({ first = 5, itemsPerPage = 5, sort = "relevance", sea
424
531
  })
425
532
  });
426
533
  }
534
+ /**
535
+ * `ProductShelfRoot` for a `'lazy-on-mount'` page element: the generator wraps it in a
536
+ * `HotDataProvider` whose `null` fallback reaches the inner root through the data override.
537
+ */
538
+ function ProductShelfOnMountRoot(props) {
539
+ return /* @__PURE__ */ jsx(ProductShelfRoot, { ...props });
540
+ }
427
541
  function ProductShelfHeading({ className, children, ...props }) {
428
542
  const { headingId, registerHeading } = useProductShelfHeading();
429
543
  const hasContent = children != null && children !== "";
@@ -473,6 +587,6 @@ const ProductShelf = {
473
587
  Skeleton: ProductShelfSkeleton
474
588
  };
475
589
  //#endregion
476
- export { PRODUCT_SHELF_SORT_MAP, ProductShelf, ProductShelfContent, ProductShelfHeading, ProductShelfHeadingDefinition, ProductShelfItems, ProductShelfItemsDefinition, ProductShelfRoot, ProductShelfRootDefinition, ProductShelfSkeleton, transformProducts, useProductShelf, useProductShelfItem };
590
+ export { PRODUCT_SHELF_SORT_MAP, ProductShelf, ProductShelfContent, ProductShelfHeading, ProductShelfHeadingDefinition, ProductShelfItems, ProductShelfItemsDefinition, ProductShelfOnMountRoot, ProductShelfOnMountRootDefinition, ProductShelfRoot, ProductShelfRootDefinition, ProductShelfSkeleton, transformProducts, useProductShelf, useProductShelfItem };
477
591
 
478
592
  //# sourceMappingURL=product-shelf.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"product-shelf.js","names":[],"sources":["../../../src/matter/product-shelf/product-shelf.tsx"],"sourcesContent":["'use client';\n\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n} from 'react';\nimport { CarouselTrack } from '../../particles/carousel/carousel.js';\nimport { Text } from '../../particles/text/text.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { isEmptyContentValue } from '../../sdk/data/empty-content-value.js';\nimport {\n ProductListConfigContext,\n type ProductListItem,\n ProductListItemContext,\n} from '../product-list/product-list.js';\n\n// ---------------------------------------------------------------------------\n// Content model — the merchant-facing fields that drive the query. Each one is\n// also a schema prop of the same name on the root: the CMS value wins per\n// field, and the prop is the developer default (the only source in a store\n// with no CMS binding). No field is required, so an empty value relaxes the\n// filter or takes the prop instead of omitting the shelf.\n// ---------------------------------------------------------------------------\n\nexport const ProductShelfContent = defineContentType({\n id: 'product-shelf',\n title: 'Product Shelf',\n description: 'Which products a shelf shows: how many, in which order, and from which facet.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n first: { type: 'integer', title: 'Number of products', minimum: 1, maximum: 100 },\n sort: {\n type: 'string',\n enum: ['relevance', 'price_asc', 'price_desc', 'name_asc', 'name_desc', 'release', 'orders', 'discount'],\n title: 'Sort',\n },\n categoryId: { type: 'string', title: 'Category ID' },\n brandId: { type: 'string', title: 'Brand ID' },\n collectionId: { type: 'string', title: 'Collection ID' },\n searchTerm: { type: 'string', title: 'Search term' },\n availableOnly: { type: 'boolean', title: 'Available only' },\n },\n },\n});\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — product connection from the catalog module. Every\n// argument is a variable the compiler binds from the element's configuration\n// (its content slice at request time, else its prop at compile time), one\n// fragment instance per element. A variable left unbound inside `filter`\n// omits that key, so an unset facet relaxes the filter rather than sending\n// null; `$orderBy` reads `sort` through the `values` table in the definition.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ProductShelfData on Query {\n products(\n first: $first\n orderBy: $orderBy\n filter: {\n categoryIds: $categoryId\n brandIds: $brandId\n collectionId: $collectionId\n searchTerm: $searchTerm\n availableOnly: $availableOnly\n }\n ) {\n edges {\n node {\n id\n title\n slug\n routeSlug\n images(first: 1) {\n edges {\n node {\n url\n altText\n }\n }\n }\n variants(first: 1) {\n edges {\n node {\n availableForSale\n quantityAvailable\n price {\n amount\n currencyCode\n }\n compareAtPrice {\n amount\n currencyCode\n }\n sellers {\n isDefault\n offer {\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n spotPrice {\n amount\n currencyCode\n }\n tax {\n amount\n currencyCode\n }\n taxPercentage\n availableQuantity\n }\n }\n }\n }\n }\n }\n }\n totalCount\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Raw data types (shape of the fragment result)\n// ---------------------------------------------------------------------------\n\ntype RawMoney = { amount: number; currencyCode: string };\n\ntype RawOffer = {\n price: RawMoney;\n listPrice: RawMoney;\n spotPrice: RawMoney;\n tax: RawMoney;\n taxPercentage: number | null;\n availableQuantity: number;\n};\n\ntype RawVariantNode = {\n availableForSale: boolean;\n quantityAvailable: number | null;\n price: RawMoney;\n compareAtPrice: RawMoney | null;\n sellers: Array<{ isDefault: boolean; offer: RawOffer | null }>;\n};\n\ntype RawProductNode = {\n id: string;\n title: string;\n slug: string;\n routeSlug: string;\n images: { edges: Array<{ node: { url: string; altText: string | null } }> };\n variants: { edges: Array<{ node: RawVariantNode }> };\n};\n\n// The `$data` the shelf receives is the unwrapped top-level fragment field:\n// HotDataProvider forwards `data['products']` (the connection value), not the\n// `{ products: ... }` wrapper — same contract as PricingRoot's productBySlug.\nexport type ProductShelfData = {\n edges: Array<{ node: RawProductNode }>;\n totalCount: number;\n};\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n// The per-item value object the card parts (T3–T7) read. It is the widened\n// ProductListItem, so the existing ProductList.Card* leaves render unchanged.\nexport type ProductShelfItem = ProductListItem;\n\nexport type ProductShelfSort =\n | 'relevance'\n | 'price_asc'\n | 'price_desc'\n | 'name_asc'\n | 'name_desc'\n | 'release'\n | 'orders'\n | 'discount';\n\nexport type ProductSortField = 'TITLE' | 'PRICE' | 'RELEASE_DATE' | 'RELEVANCE' | 'BEST_SELLING';\n\nexport type SortDirection = 'ASC' | 'DESC';\n\n/**\n * The query configuration the shelf resolved for this element: the content slice when the\n * merchant filled a field, else the prop. The query has already applied these values; the\n * context republishes them for parts that describe the shelf (a \"sorted by\" label, a facet link).\n */\nexport type ProductShelfConfig = {\n first: number;\n sort: ProductShelfSort;\n searchTerm?: string;\n categoryId?: string;\n brandId?: string;\n collectionId?: string;\n availableOnly: boolean;\n};\n\nexport type ProductShelfContextValue = {\n itemsPerPage: number;\n config: ProductShelfConfig;\n count: number;\n};\n\nexport type ProductShelfRootProps = ComponentPropsWithRef<'section'> &\n Partial<ProductShelfConfig> & {\n itemsPerPage?: number;\n /** When true, card prices render tax-inclusive (from each item's tax data). Default false. */\n usePriceWithTaxes?: boolean;\n /** Text for the tax label part when tax display is on. Default `'Tax included'`. */\n taxesLabel?: string;\n products?: ProductShelfItem[];\n /** Injected by the compiler, sliced from the page's or layout's composite content entry. */\n $content?: ContentValue<typeof ProductShelfContent>;\n $data?: ProductShelfData | null;\n };\n\nexport type ProductShelfItemsProps = ComponentPropsWithRef<'div'>;\n\nexport type ProductShelfHeadingProps = ComponentPropsWithRef<'h2'>;\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ProductShelfProductsContext = createContext<ProductShelfItem[]>([]);\n\nconst ProductShelfContext = createContext<ProductShelfContextValue | null>(null);\n\n// Wires the section ⇄ heading `aria-labelledby` link without the root having to\n// know its descendants: `headingId` is the shared id, and a composed\n// ProductShelfHeading registers its presence so the section points at it only\n// when it exists (never a dangling idref). `registerHeading` is ref-counted and\n// returns its unregister, so the heading can be placed anywhere.\ntype ProductShelfHeadingContextValue = { headingId: string; registerHeading: () => () => void };\n\nconst ProductShelfHeadingContext = createContext<ProductShelfHeadingContextValue | null>(null);\n\nexport function useProductShelf(): ProductShelfContextValue {\n const ctx = useContext(ProductShelfContext);\n if (!ctx) {\n throw new Error('useProductShelf must be used inside ProductShelfRoot.');\n }\n return ctx;\n}\n\nfunction useProductShelfHeading(): ProductShelfHeadingContextValue {\n const ctx = useContext(ProductShelfHeadingContext);\n if (!ctx) {\n throw new Error('ProductShelfHeading must be used inside ProductShelfRoot.');\n }\n return ctx;\n}\n\nexport function useProductShelfItem(): ProductShelfItem {\n const item = useContext(ProductListItemContext);\n if (!item) {\n throw new Error('Product card components must be used inside ProductShelfItems.');\n }\n return item;\n}\n\n// ---------------------------------------------------------------------------\n// `sort` → `$orderBy` (ProductSortingInput). The same table is the `values`\n// block of the definition, so the compiler and the SDK helper apply it; the\n// contract test pins the two to each other.\n// ---------------------------------------------------------------------------\n\nexport const PRODUCT_SHELF_SORT_MAP: Record<ProductShelfSort, { field: ProductSortField; direction: SortDirection }> = {\n relevance: { field: 'RELEVANCE', direction: 'DESC' },\n price_asc: { field: 'PRICE', direction: 'ASC' },\n price_desc: { field: 'PRICE', direction: 'DESC' },\n name_asc: { field: 'TITLE', direction: 'ASC' },\n name_desc: { field: 'TITLE', direction: 'DESC' },\n release: { field: 'RELEASE_DATE', direction: 'DESC' },\n orders: { field: 'BEST_SELLING', direction: 'DESC' },\n // No direct ProductSortField for legacy \"discount\"; fall back to relevance.\n discount: { field: 'RELEVANCE', direction: 'DESC' },\n};\n\n// ---------------------------------------------------------------------------\n// Transform — raw fragment slice → per-item value objects\n// ---------------------------------------------------------------------------\n\nexport function transformProducts(data: ProductShelfData): ProductShelfItem[] {\n return data.edges.map(({ node }): ProductShelfItem => {\n const image = node.images.edges[0]?.node;\n const variant = node.variants.edges[0]?.node;\n const seller = variant?.sellers.find((s) => s.isDefault) ?? variant?.sellers[0];\n const offer = seller?.offer ?? null;\n\n const price = offer?.price.amount ?? variant?.price.amount ?? 0;\n const listPrice = offer?.listPrice.amount ?? variant?.compareAtPrice?.amount;\n const spotPrice = offer?.spotPrice.amount;\n const currency = offer?.price.currencyCode ?? variant?.price.currencyCode;\n\n return {\n id: node.id,\n name: node.title,\n slug: node.slug,\n routeSlug: node.routeSlug,\n image: { src: image?.url ?? '', alt: image?.altText ?? node.title },\n price,\n ...(listPrice !== undefined ? { listPrice } : {}),\n ...(spotPrice !== undefined ? { spotPrice } : {}),\n ...(offer ? { tax: offer.tax.amount, taxPercentage: offer.taxPercentage } : {}),\n ...(variant ? { availableForSale: variant.availableForSale } : {}),\n ...(variant?.quantityAvailable != null ? { availableQuantity: variant.quantityAvailable } : {}),\n ...(currency ? { currency } : {}),\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ProductShelfRootDefinition = defineComponent<ProductShelfRootProps>({\n name: 'ProductShelfRoot',\n importPath: '@vtex/faststore/components',\n component: ProductShelfRoot,\n fragment,\n fetchStrategy: 'lazy',\n skeleton: ProductShelfSkeleton,\n content: ProductShelfContent,\n // The `values` table is authored as a literal: the Catalog Build serializes the block as JSON and\n // rejects identifiers. `PRODUCT_SHELF_SORT_MAP` is pinned equal to it by the contract test.\n variables: {\n orderBy: {\n from: 'sort',\n values: {\n relevance: { field: 'RELEVANCE', direction: 'DESC' },\n price_asc: { field: 'PRICE', direction: 'ASC' },\n price_desc: { field: 'PRICE', direction: 'DESC' },\n name_asc: { field: 'TITLE', direction: 'ASC' },\n name_desc: { field: 'TITLE', direction: 'DESC' },\n release: { field: 'RELEASE_DATE', direction: 'DESC' },\n orders: { field: 'BEST_SELLING', direction: 'DESC' },\n discount: { field: 'RELEVANCE', direction: 'DESC' },\n },\n },\n },\n editor: { label: 'Product Shelf Root', category: 'commerce' },\n providesContext: 'product-shelf',\n schema: {\n itemsPerPage: { type: 'number', default: 5, label: 'Items Per Page' },\n usePriceWithTaxes: { type: 'boolean', default: false, label: 'Use Price With Taxes' },\n taxesLabel: { type: 'string', default: 'Tax included', label: 'Taxes Label' },\n // The content fields again, as props: the developer's defaults, and the only source in a\n // store with no CMS binding (example stores, Storybook, tests).\n first: { type: 'number', default: 5, label: 'Number of Products' },\n sort: {\n type: ['relevance', 'price_asc', 'price_desc', 'name_asc', 'name_desc', 'release', 'orders', 'discount'] as const,\n default: 'relevance',\n label: 'Sort',\n },\n searchTerm: { type: 'string', label: 'Search Term' },\n categoryId: { type: 'string', label: 'Category ID' },\n brandId: { type: 'string', label: 'Brand ID' },\n collectionId: { type: 'string', label: 'Collection ID' },\n availableOnly: { type: 'boolean', default: false, label: 'Available Only' },\n },\n guideline:\n 'Data-owning root of a facet-sourced product shelf. Owns a products(first:, orderBy:, filter:) fragment whose variables bind to this element: the merchant fills them in the CMS (content model product-shelf) and the props of the same name are the defaults. Receives $data lazily. Compose as: ProductShelfRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext + CarouselBullets)). Compose ProductShelfHeading with the title as its children to render a heading; set the source with categoryId / brandId / collectionId / searchTerm and tune first, sort, availableOnly. Renders a skeleton while loading and nothing when the result is empty. Bind itemsPerPage to the CarouselRoot so paging matches the shelf config. See product-shelf.md.',\n});\n\nexport const ProductShelfItemsDefinition = defineComponent<ProductShelfItemsProps>({\n name: 'ProductShelfItems',\n importPath: '@vtex/faststore/components',\n component: ProductShelfItems,\n editor: { label: 'Product Shelf Items', category: 'commerce' },\n requiresContext: 'product-shelf',\n schema: {},\n guideline:\n 'Repeats the shelf products into the carousel track, publishing one product per slide through the per-item context the ProductCard* parts read. Place directly inside CarouselRoot (it renders the CarouselTrack). Put the card template (ProductCardImage, ProductCardName, ProductCardPrice, …) as its children.',\n});\n\nexport const ProductShelfHeadingDefinition = defineComponent<ProductShelfHeadingProps>({\n name: 'ProductShelfHeading',\n importPath: '@vtex/faststore/components',\n component: ProductShelfHeading,\n editor: { label: 'Product Shelf Heading', category: 'commerce' },\n requiresContext: 'product-shelf',\n schema: {},\n guideline:\n \"Renders its children as the shelf title (an <h2>) and provides the section's accessible label. Author the title text as its children (props.children). Place it inside ProductShelfRoot where the title should sit — usually above CarouselRoot. Renders nothing when it has no children.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/** The content value when the merchant filled the field, else the prop (the shared emptiness rule). */\nfunction contentOrProp<T>(content: T | undefined, prop: T): T {\n if (content === undefined || isEmptyContentValue(content)) return prop;\n return content;\n}\n\nexport function ProductShelfRoot({\n first = 5,\n itemsPerPage = 5,\n sort = 'relevance',\n searchTerm,\n categoryId,\n brandId,\n collectionId,\n availableOnly = false,\n usePriceWithTaxes = false,\n taxesLabel = 'Tax included',\n products,\n $content,\n $data: $dataProp,\n className,\n children,\n ...props\n}: ProductShelfRootProps) {\n const $data = useDataOverride<ProductShelfData | null | undefined>($dataProp);\n // The query already applied these values (the compiler binds the same slice and props to the\n // fragment variables); the context only republishes what this element resolved to.\n const shelfConfig = useMemo<ProductShelfConfig>(\n () => ({\n first: contentOrProp($content?.first, first),\n sort: contentOrProp($content?.sort, sort),\n searchTerm: contentOrProp($content?.searchTerm, searchTerm),\n categoryId: contentOrProp($content?.categoryId, categoryId),\n brandId: contentOrProp($content?.brandId, brandId),\n collectionId: contentOrProp($content?.collectionId, collectionId),\n availableOnly: contentOrProp($content?.availableOnly, availableOnly),\n }),\n [$content, first, sort, searchTerm, categoryId, brandId, collectionId, availableOnly],\n );\n const headingId = useId();\n // Ref-counted so a composed ProductShelfHeading flips `hasHeading` wherever it is placed; the\n // section then labels itself only while a heading is mounted, never via a dangling idref.\n const [headingCount, setHeadingCount] = useState(0);\n const registerHeading = useCallback(() => {\n setHeadingCount((count) => count + 1);\n return () => setHeadingCount((count) => count - 1);\n }, []);\n const headingContext = useMemo(() => ({ headingId, registerHeading }), [headingId, registerHeading]);\n const config = useMemo(() => ({ usePriceWithTaxes, taxesLabel }), [usePriceWithTaxes, taxesLabel]);\n const items = useMemo<ProductShelfItem[]>(() => {\n if (products !== undefined) return products;\n return $data ? transformProducts($data) : [];\n }, [products, $data]);\n const shelf = useMemo<ProductShelfContextValue>(\n () => ({ itemsPerPage, config: shelfConfig, count: items.length }),\n [itemsPerPage, shelfConfig, items.length],\n );\n const hasHeading = headingCount > 0;\n\n if (products === undefined && $data === undefined) {\n // Lazy source not resolved yet — show the skeleton (the runtime also renders it).\n return <ProductShelfSkeleton className={className} />;\n }\n\n // Empty result renders nothing (legacy parity) rather than an empty carousel.\n if (items.length === 0) {\n return null;\n }\n\n return (\n <ProductListConfigContext value={config}>\n <ProductShelfProductsContext value={items}>\n <ProductShelfContext value={shelf}>\n <ProductShelfHeadingContext value={headingContext}>\n <section\n {...props}\n className={className}\n data-fs-product-shelf=\"\"\n aria-labelledby={hasHeading ? headingId : undefined}\n >\n {children}\n </section>\n </ProductShelfHeadingContext>\n </ProductShelfContext>\n </ProductShelfProductsContext>\n </ProductListConfigContext>\n );\n}\n\nexport function ProductShelfHeading({ className, children, ...props }: ProductShelfHeadingProps) {\n const { headingId, registerHeading } = useProductShelfHeading();\n const hasContent = children != null && children !== '';\n\n // Announce presence to the root so its section wires `aria-labelledby` to this heading; the\n // registration (and its unregister) is scoped to when the heading actually renders content.\n useEffect(() => {\n if (!hasContent) {\n return;\n }\n return registerHeading();\n }, [hasContent, registerHeading]);\n\n if (!hasContent) {\n return null;\n }\n\n return (\n <Text {...props} as=\"h2\" id={headingId} className={className} data-fs-product-shelf-heading=\"\">\n {children}\n </Text>\n );\n}\n\nexport function ProductShelfItems({ children, ...props }: ProductShelfItemsProps) {\n const items = useContext(ProductShelfProductsContext);\n\n return (\n <CarouselTrack {...props} data-fs-product-shelf-items=\"\">\n {items.map((item) => (\n <ProductListItemContext key={item.id} value={item}>\n {children}\n </ProductListItemContext>\n ))}\n </CarouselTrack>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\nexport function ProductShelfSkeleton({ className, ...props }: ComponentPropsWithRef<'div'>) {\n return (\n <div {...props} className={className} data-fs-product-shelf-skeleton=\"\" aria-hidden=\"true\">\n <span data-fs-product-shelf-skeleton-item=\"\" />\n <span data-fs-product-shelf-skeleton-item=\"\" />\n <span data-fs-product-shelf-skeleton-item=\"\" />\n </div>\n );\n}\n\nProductShelfRoot.Skeleton = ProductShelfSkeleton;\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ProductShelf = {\n Root: ProductShelfRoot,\n Heading: ProductShelfHeading,\n Items: ProductShelfItems,\n Skeleton: ProductShelfSkeleton,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"product-shelf.js","names":[],"sources":["../../../src/matter/product-shelf/product-shelf.tsx"],"sourcesContent":["'use client';\n\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n createContext,\n useCallback,\n useContext,\n useEffect,\n useId,\n useMemo,\n useState,\n} from 'react';\nimport { CarouselTrack } from '../../particles/carousel/carousel.js';\nimport { Text } from '../../particles/text/text.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { isEmptyContentValue } from '../../sdk/data/empty-content-value.js';\nimport {\n ProductListConfigContext,\n type ProductListItem,\n ProductListItemContext,\n} from '../product-list/product-list.js';\n\n// ---------------------------------------------------------------------------\n// Content model — the merchant-facing fields that drive the query. Each one is\n// also a schema prop of the same name on the root: the CMS value wins per\n// field, and the prop is the developer default (the only source in a store\n// with no CMS binding). No field is required, so an empty value relaxes the\n// filter or takes the prop instead of omitting the shelf.\n// ---------------------------------------------------------------------------\n\nexport const ProductShelfContent = defineContentType({\n id: 'product-shelf',\n title: 'Product Shelf',\n description: 'Which products a shelf shows: how many, in which order, and from which facet.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n first: { type: 'integer', title: 'Number of products', minimum: 1, maximum: 100 },\n sort: {\n type: 'string',\n enum: ['relevance', 'price_asc', 'price_desc', 'name_asc', 'name_desc', 'release', 'orders', 'discount'],\n title: 'Sort',\n },\n categoryId: { type: 'string', title: 'Category ID' },\n brandId: { type: 'string', title: 'Brand ID' },\n collectionId: { type: 'string', title: 'Collection ID' },\n searchTerm: { type: 'string', title: 'Search term' },\n availableOnly: { type: 'boolean', title: 'Available only' },\n },\n },\n});\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — product connection from the catalog module. Every\n// argument is a variable the compiler binds from the element's configuration\n// (its content slice at request time, else its prop at compile time), one\n// fragment instance per element. A variable left unbound inside `filter`\n// omits that key, so an unset facet relaxes the filter rather than sending\n// null; `$orderBy` reads `sort` through the `values` table in the definition.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ProductShelfData on Query {\n products(\n first: $first\n orderBy: $orderBy\n filter: {\n categoryIds: $categoryId\n brandIds: $brandId\n collectionId: $collectionId\n searchTerm: $searchTerm\n availableOnly: $availableOnly\n }\n ) {\n edges {\n node {\n id\n title\n slug\n routeSlug\n images(first: 1) {\n edges {\n node {\n url\n altText\n }\n }\n }\n variants(first: 1) {\n edges {\n node {\n availableForSale\n quantityAvailable\n price {\n amount\n currencyCode\n }\n compareAtPrice {\n amount\n currencyCode\n }\n sellers {\n isDefault\n offer {\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n spotPrice {\n amount\n currencyCode\n }\n tax {\n amount\n currencyCode\n }\n taxPercentage\n availableQuantity\n }\n }\n }\n }\n }\n }\n }\n totalCount\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Raw data types (shape of the fragment result)\n// ---------------------------------------------------------------------------\n\ntype RawMoney = { amount: number; currencyCode: string };\n\ntype RawOffer = {\n price: RawMoney;\n listPrice: RawMoney;\n spotPrice: RawMoney;\n tax: RawMoney;\n taxPercentage: number | null;\n availableQuantity: number;\n};\n\ntype RawVariantNode = {\n availableForSale: boolean;\n quantityAvailable: number | null;\n price: RawMoney;\n compareAtPrice: RawMoney | null;\n sellers: Array<{ isDefault: boolean; offer: RawOffer | null }>;\n};\n\ntype RawProductNode = {\n id: string;\n title: string;\n slug: string;\n routeSlug: string;\n images: { edges: Array<{ node: { url: string; altText: string | null } }> };\n variants: { edges: Array<{ node: RawVariantNode }> };\n};\n\n// The `$data` the shelf receives is the unwrapped top-level fragment field:\n// HotDataProvider forwards `data['products']` (the connection value), not the\n// `{ products: ... }` wrapper — same contract as PricingRoot's productBySlug.\nexport type ProductShelfData = {\n edges: Array<{ node: RawProductNode }>;\n totalCount: number;\n};\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n// The per-item value object the card parts (T3–T7) read. It is the widened\n// ProductListItem, so the existing ProductList.Card* leaves render unchanged.\nexport type ProductShelfItem = ProductListItem;\n\nexport type ProductShelfSort =\n | 'relevance'\n | 'price_asc'\n | 'price_desc'\n | 'name_asc'\n | 'name_desc'\n | 'release'\n | 'orders'\n | 'discount';\n\nexport type ProductSortField = 'TITLE' | 'PRICE' | 'RELEASE_DATE' | 'RELEVANCE' | 'BEST_SELLING';\n\nexport type SortDirection = 'ASC' | 'DESC';\n\n/**\n * The query configuration the shelf resolved for this element: the content slice when the\n * merchant filled a field, else the prop. The query has already applied these values; the\n * context republishes them for parts that describe the shelf (a \"sorted by\" label, a facet link).\n */\nexport type ProductShelfConfig = {\n first: number;\n sort: ProductShelfSort;\n searchTerm?: string;\n categoryId?: string;\n brandId?: string;\n collectionId?: string;\n availableOnly: boolean;\n};\n\nexport type ProductShelfContextValue = {\n itemsPerPage: number;\n config: ProductShelfConfig;\n count: number;\n};\n\nexport type ProductShelfRootProps = ComponentPropsWithRef<'section'> &\n Partial<ProductShelfConfig> & {\n itemsPerPage?: number;\n /** When true, card prices render tax-inclusive (from each item's tax data). Default false. */\n usePriceWithTaxes?: boolean;\n /** Text for the tax label part when tax display is on. Default `'Tax included'`. */\n taxesLabel?: string;\n products?: ProductShelfItem[];\n /** Injected by the compiler, sliced from the page's or layout's composite content entry. */\n $content?: ContentValue<typeof ProductShelfContent>;\n $data?: ProductShelfData | null;\n };\n\nexport type ProductShelfOnMountRootProps = Omit<ProductShelfRootProps, '$content'>;\n\nexport type ProductShelfItemsProps = ComponentPropsWithRef<'div'>;\n\nexport type ProductShelfHeadingProps = ComponentPropsWithRef<'h2'>;\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst ProductShelfProductsContext = createContext<ProductShelfItem[]>([]);\n\nconst ProductShelfContext = createContext<ProductShelfContextValue | null>(null);\n\n// Wires the section ⇄ heading `aria-labelledby` link without the root having to\n// know its descendants: `headingId` is the shared id, and a composed\n// ProductShelfHeading registers its presence so the section points at it only\n// when it exists (never a dangling idref). `registerHeading` is ref-counted and\n// returns its unregister, so the heading can be placed anywhere.\ntype ProductShelfHeadingContextValue = { headingId: string; registerHeading: () => () => void };\n\nconst ProductShelfHeadingContext = createContext<ProductShelfHeadingContextValue | null>(null);\n\nexport function useProductShelf(): ProductShelfContextValue {\n const ctx = useContext(ProductShelfContext);\n if (!ctx) {\n throw new Error('useProductShelf must be used inside ProductShelfRoot.');\n }\n return ctx;\n}\n\nfunction useProductShelfHeading(): ProductShelfHeadingContextValue {\n const ctx = useContext(ProductShelfHeadingContext);\n if (!ctx) {\n throw new Error('ProductShelfHeading must be used inside ProductShelfRoot.');\n }\n return ctx;\n}\n\nexport function useProductShelfItem(): ProductShelfItem {\n const item = useContext(ProductListItemContext);\n if (!item) {\n throw new Error('Product card components must be used inside ProductShelfItems.');\n }\n return item;\n}\n\n// ---------------------------------------------------------------------------\n// `sort` → `$orderBy` (ProductSortingInput). The same table is the `values`\n// block of the definition, so the compiler and the SDK helper apply it; the\n// contract test pins the two to each other.\n// ---------------------------------------------------------------------------\n\nexport const PRODUCT_SHELF_SORT_MAP: Record<ProductShelfSort, { field: ProductSortField; direction: SortDirection }> = {\n relevance: { field: 'RELEVANCE', direction: 'DESC' },\n price_asc: { field: 'PRICE', direction: 'ASC' },\n price_desc: { field: 'PRICE', direction: 'DESC' },\n name_asc: { field: 'TITLE', direction: 'ASC' },\n name_desc: { field: 'TITLE', direction: 'DESC' },\n release: { field: 'RELEASE_DATE', direction: 'DESC' },\n orders: { field: 'BEST_SELLING', direction: 'DESC' },\n // No direct ProductSortField for legacy \"discount\"; fall back to relevance.\n discount: { field: 'RELEVANCE', direction: 'DESC' },\n};\n\n// ---------------------------------------------------------------------------\n// Transform — raw fragment slice → per-item value objects\n// ---------------------------------------------------------------------------\n\nexport function transformProducts(data: ProductShelfData): ProductShelfItem[] {\n return data.edges.map(({ node }): ProductShelfItem => {\n const image = node.images.edges[0]?.node;\n const variant = node.variants.edges[0]?.node;\n const seller = variant?.sellers.find((s) => s.isDefault) ?? variant?.sellers[0];\n const offer = seller?.offer ?? null;\n\n const price = offer?.price.amount ?? variant?.price.amount ?? 0;\n const listPrice = offer?.listPrice.amount ?? variant?.compareAtPrice?.amount;\n const spotPrice = offer?.spotPrice.amount;\n const currency = offer?.price.currencyCode ?? variant?.price.currencyCode;\n\n return {\n id: node.id,\n name: node.title,\n slug: node.slug,\n routeSlug: node.routeSlug,\n image: { src: image?.url ?? '', alt: image?.altText ?? node.title },\n price,\n ...(listPrice !== undefined ? { listPrice } : {}),\n ...(spotPrice !== undefined ? { spotPrice } : {}),\n ...(offer ? { tax: offer.tax.amount, taxPercentage: offer.taxPercentage } : {}),\n ...(variant ? { availableForSale: variant.availableForSale } : {}),\n ...(variant?.quantityAvailable != null ? { availableQuantity: variant.quantityAvailable } : {}),\n ...(currency ? { currency } : {}),\n };\n });\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ProductShelfRootDefinition = defineComponent<ProductShelfRootProps>({\n name: 'ProductShelfRoot',\n importPath: '@vtex/faststore/components',\n component: ProductShelfRoot,\n fragment,\n fetchStrategy: 'lazy',\n skeleton: ProductShelfSkeleton,\n content: ProductShelfContent,\n // The `values` table is authored as a literal: the Catalog Build serializes the block as JSON and\n // rejects identifiers. `PRODUCT_SHELF_SORT_MAP` is pinned equal to it by the contract test.\n variables: {\n orderBy: {\n from: 'sort',\n values: {\n relevance: { field: 'RELEVANCE', direction: 'DESC' },\n price_asc: { field: 'PRICE', direction: 'ASC' },\n price_desc: { field: 'PRICE', direction: 'DESC' },\n name_asc: { field: 'TITLE', direction: 'ASC' },\n name_desc: { field: 'TITLE', direction: 'DESC' },\n release: { field: 'RELEASE_DATE', direction: 'DESC' },\n orders: { field: 'BEST_SELLING', direction: 'DESC' },\n discount: { field: 'RELEVANCE', direction: 'DESC' },\n },\n },\n },\n editor: { label: 'Product Shelf Root', category: 'commerce' },\n providesContext: 'product-shelf',\n schema: {\n itemsPerPage: { type: 'number', default: 5, label: 'Items Per Page' },\n usePriceWithTaxes: { type: 'boolean', default: false, label: 'Use Price With Taxes' },\n taxesLabel: { type: 'string', default: 'Tax included', label: 'Taxes Label' },\n // The content fields again, as props: the developer's defaults, and the only source in a\n // store with no CMS binding (example stores, Storybook, tests).\n first: { type: 'number', default: 5, label: 'Number of Products' },\n sort: {\n type: ['relevance', 'price_asc', 'price_desc', 'name_asc', 'name_desc', 'release', 'orders', 'discount'] as const,\n default: 'relevance',\n label: 'Sort',\n },\n searchTerm: { type: 'string', label: 'Search Term' },\n categoryId: { type: 'string', label: 'Category ID' },\n brandId: { type: 'string', label: 'Brand ID' },\n collectionId: { type: 'string', label: 'Collection ID' },\n availableOnly: { type: 'boolean', default: false, label: 'Available Only' },\n },\n guideline:\n 'Data-owning root of a facet-sourced product shelf. Owns a products(first:, orderBy:, filter:) fragment whose variables bind to this element: the merchant fills them in the CMS (content model product-shelf) and the props of the same name are the defaults. Receives $data lazily. Compose as: ProductShelfRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext + CarouselBullets)). Compose ProductShelfHeading with the title as its children to render a heading; set the source with categoryId / brandId / collectionId / searchTerm and tune first, sort, availableOnly. Renders a skeleton while loading and nothing when the result is empty. Bind itemsPerPage to the CarouselRoot so paging matches the shelf config. See product-shelf.md.',\n});\n\nexport const ProductShelfOnMountRootDefinition = defineComponent<ProductShelfOnMountRootProps>({\n name: 'ProductShelfOnMountRoot',\n importPath: '@vtex/faststore/components',\n component: ProductShelfOnMountRoot,\n fragment,\n fetchStrategy: 'lazy-on-mount',\n // Repeats the ProductShelfRoot table as a literal (the Catalog Build rejects identifiers inside\n // `variables`); the contract test pins it equal to ProductShelfRootDefinition's.\n variables: {\n orderBy: {\n from: 'sort',\n values: {\n relevance: { field: 'RELEVANCE', direction: 'DESC' },\n price_asc: { field: 'PRICE', direction: 'ASC' },\n price_desc: { field: 'PRICE', direction: 'DESC' },\n name_asc: { field: 'TITLE', direction: 'ASC' },\n name_desc: { field: 'TITLE', direction: 'DESC' },\n release: { field: 'RELEASE_DATE', direction: 'DESC' },\n orders: { field: 'BEST_SELLING', direction: 'DESC' },\n discount: { field: 'RELEVANCE', direction: 'DESC' },\n },\n },\n },\n editor: { label: 'Product Shelf Root (on mount)', category: 'commerce' },\n providesContext: 'product-shelf',\n schema: {\n itemsPerPage: { type: 'number', default: 5, label: 'Items Per Page' },\n usePriceWithTaxes: { type: 'boolean', default: false, label: 'Use Price With Taxes' },\n taxesLabel: { type: 'string', default: 'Tax included', label: 'Taxes Label' },\n first: { type: 'number', default: 5, label: 'Number of Products' },\n sort: {\n type: ['relevance', 'price_asc', 'price_desc', 'name_asc', 'name_desc', 'release', 'orders', 'discount'] as const,\n default: 'relevance',\n label: 'Sort',\n },\n searchTerm: { type: 'string', label: 'Search Term' },\n categoryId: { type: 'string', label: 'Category ID' },\n brandId: { type: 'string', label: 'Brand ID' },\n collectionId: { type: 'string', label: 'Collection ID' },\n availableOnly: { type: 'boolean', default: false, label: 'Available Only' },\n },\n guideline:\n 'ProductShelfRoot variant that fetches its products on the client when it mounts, in its own request: the products never enter the page query and are absent from the server HTML. Renders nothing while loading, when the request fails and when the result is empty (no skeleton). Meant for a conditional branch such as CartEmpty, never above the fold. Configured by props only (first, sort, availableOnly, categoryId, brandId, collectionId, searchTerm, itemsPerPage). Compose as: ProductShelfOnMountRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext)). See product-shelf.md.',\n});\n\nexport const ProductShelfItemsDefinition = defineComponent<ProductShelfItemsProps>({\n name: 'ProductShelfItems',\n importPath: '@vtex/faststore/components',\n component: ProductShelfItems,\n editor: { label: 'Product Shelf Items', category: 'commerce' },\n requiresContext: 'product-shelf',\n schema: {},\n guideline:\n 'Repeats the shelf products into the carousel track, publishing one product per slide through the per-item context the ProductCard* parts read. Place directly inside CarouselRoot (it renders the CarouselTrack). Put the card template (ProductCardImage, ProductCardName, ProductCardPrice, …) as its children.',\n});\n\nexport const ProductShelfHeadingDefinition = defineComponent<ProductShelfHeadingProps>({\n name: 'ProductShelfHeading',\n importPath: '@vtex/faststore/components',\n component: ProductShelfHeading,\n editor: { label: 'Product Shelf Heading', category: 'commerce' },\n requiresContext: 'product-shelf',\n schema: {},\n guideline:\n \"Renders its children as the shelf title (an <h2>) and provides the section's accessible label. Author the title text as its children (props.children). Place it inside ProductShelfRoot where the title should sit — usually above CarouselRoot. Renders nothing when it has no children.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/** The content value when the merchant filled the field, else the prop (the shared emptiness rule). */\nfunction contentOrProp<T>(content: T | undefined, prop: T): T {\n if (content === undefined || isEmptyContentValue(content)) return prop;\n return content;\n}\n\nexport function ProductShelfRoot({\n first = 5,\n itemsPerPage = 5,\n sort = 'relevance',\n searchTerm,\n categoryId,\n brandId,\n collectionId,\n availableOnly = false,\n usePriceWithTaxes = false,\n taxesLabel = 'Tax included',\n products,\n $content,\n $data: $dataProp,\n className,\n children,\n ...props\n}: ProductShelfRootProps) {\n const $data = useDataOverride<ProductShelfData | null | undefined>($dataProp);\n // The query already applied these values (the compiler binds the same slice and props to the\n // fragment variables); the context only republishes what this element resolved to.\n const shelfConfig = useMemo<ProductShelfConfig>(\n () => ({\n first: contentOrProp($content?.first, first),\n sort: contentOrProp($content?.sort, sort),\n searchTerm: contentOrProp($content?.searchTerm, searchTerm),\n categoryId: contentOrProp($content?.categoryId, categoryId),\n brandId: contentOrProp($content?.brandId, brandId),\n collectionId: contentOrProp($content?.collectionId, collectionId),\n availableOnly: contentOrProp($content?.availableOnly, availableOnly),\n }),\n [$content, first, sort, searchTerm, categoryId, brandId, collectionId, availableOnly],\n );\n const headingId = useId();\n // Ref-counted so a composed ProductShelfHeading flips `hasHeading` wherever it is placed; the\n // section then labels itself only while a heading is mounted, never via a dangling idref.\n const [headingCount, setHeadingCount] = useState(0);\n const registerHeading = useCallback(() => {\n setHeadingCount((count) => count + 1);\n return () => setHeadingCount((count) => count - 1);\n }, []);\n const headingContext = useMemo(() => ({ headingId, registerHeading }), [headingId, registerHeading]);\n const config = useMemo(() => ({ usePriceWithTaxes, taxesLabel }), [usePriceWithTaxes, taxesLabel]);\n const items = useMemo<ProductShelfItem[]>(() => {\n if (products !== undefined) return products;\n return $data ? transformProducts($data) : [];\n }, [products, $data]);\n const shelf = useMemo<ProductShelfContextValue>(\n () => ({ itemsPerPage, config: shelfConfig, count: items.length }),\n [itemsPerPage, shelfConfig, items.length],\n );\n const hasHeading = headingCount > 0;\n\n if (products === undefined && $data === undefined) {\n // Lazy source not resolved yet — show the skeleton (the runtime also renders it).\n return <ProductShelfSkeleton className={className} />;\n }\n\n // Empty result renders nothing (legacy parity) rather than an empty carousel.\n if (items.length === 0) {\n return null;\n }\n\n return (\n <ProductListConfigContext value={config}>\n <ProductShelfProductsContext value={items}>\n <ProductShelfContext value={shelf}>\n <ProductShelfHeadingContext value={headingContext}>\n <section\n {...props}\n className={className}\n data-fs-product-shelf=\"\"\n aria-labelledby={hasHeading ? headingId : undefined}\n >\n {children}\n </section>\n </ProductShelfHeadingContext>\n </ProductShelfContext>\n </ProductShelfProductsContext>\n </ProductListConfigContext>\n );\n}\n\n/**\n * `ProductShelfRoot` for a `'lazy-on-mount'` page element: the generator wraps it in a\n * `HotDataProvider` whose `null` fallback reaches the inner root through the data override.\n */\nexport function ProductShelfOnMountRoot(props: Readonly<ProductShelfOnMountRootProps>) {\n return <ProductShelfRoot {...props} />;\n}\n\nexport function ProductShelfHeading({ className, children, ...props }: ProductShelfHeadingProps) {\n const { headingId, registerHeading } = useProductShelfHeading();\n const hasContent = children != null && children !== '';\n\n // Announce presence to the root so its section wires `aria-labelledby` to this heading; the\n // registration (and its unregister) is scoped to when the heading actually renders content.\n useEffect(() => {\n if (!hasContent) {\n return;\n }\n return registerHeading();\n }, [hasContent, registerHeading]);\n\n if (!hasContent) {\n return null;\n }\n\n return (\n <Text {...props} as=\"h2\" id={headingId} className={className} data-fs-product-shelf-heading=\"\">\n {children}\n </Text>\n );\n}\n\nexport function ProductShelfItems({ children, ...props }: ProductShelfItemsProps) {\n const items = useContext(ProductShelfProductsContext);\n\n return (\n <CarouselTrack {...props} data-fs-product-shelf-items=\"\">\n {items.map((item) => (\n <ProductListItemContext key={item.id} value={item}>\n {children}\n </ProductListItemContext>\n ))}\n </CarouselTrack>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Skeleton\n// ---------------------------------------------------------------------------\n\nexport function ProductShelfSkeleton({ className, ...props }: ComponentPropsWithRef<'div'>) {\n return (\n <div {...props} className={className} data-fs-product-shelf-skeleton=\"\" aria-hidden=\"true\">\n <span data-fs-product-shelf-skeleton-item=\"\" />\n <span data-fs-product-shelf-skeleton-item=\"\" />\n <span data-fs-product-shelf-skeleton-item=\"\" />\n </div>\n );\n}\n\nProductShelfRoot.Skeleton = ProductShelfSkeleton;\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ProductShelf = {\n Root: ProductShelfRoot,\n Heading: ProductShelfHeading,\n Items: ProductShelfItems,\n Skeleton: ProductShelfSkeleton,\n};\n"],"mappings":"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"}
@@ -2,11 +2,11 @@
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import { defineContentType } from "../../core/dist/define-content-type.js";
4
4
  import "../../core/dist/index.js";
5
+ import { resolveSafeHref } from "../../utils/safe-href.js";
5
6
  import { useIntl } from "../../sdk/i18n/client.js";
6
7
  import { graphql } from "../../sdk/generated/gql.js";
7
8
  import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
8
9
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
9
- import { resolveSafeHref } from "../../utils/safe-href.js";
10
10
  import { ReactiveRootContext } from "../../sdk/data/page-reactive-data-root.js";
11
11
  import { PostalCodeField } from "../../particles/postal-code-field/postal-code-field.js";
12
12
  import { cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
@@ -4,15 +4,15 @@ import "../../core/dist/index.js";
4
4
  import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
5
5
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
6
6
  import { resolveAuthoredCopy } from "../../utils/resolve-authored-copy.js";
7
+ import { ListItemRefContext } from "../../sdk/data/list-item-ref-context.js";
7
8
  import { Fieldset } from "../../particles/fieldset/fieldset.js";
8
9
  import { RadioGroup, RadioIndicator, RadioRoot } from "../../particles/radio/radio.js";
9
10
  import { useRegisterPaymentSubmission } from "../../sdk/payment/submission-registry.js";
10
11
  import { PAYMENT_NETWORK_ERROR_CODE } from "../../sdk/payment/operations.js";
11
12
  import { usePayment } from "../payment/payment.js";
12
13
  import { SECURITY_CODE_MASK_STYLE, isSecurityCodeComplete } from "../../sdk/payment/security-code-mask.js";
13
- import { ListItemRefContext } from "../../sdk/data/list-item-ref-context.js";
14
14
  import { createContext, useCallback, useContext, useId, useRef, useState } from "react";
15
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
15
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
16
  //#region src/matter/saved-cards/saved-cards.tsx
17
17
  /**
18
18
  * Pick a saved card and enter only the CVV (specs/saved-cards.md, T12 of the Credit Card epic;
@@ -163,7 +163,8 @@ function resolveEffectiveSelectedAccountId(selectedAccountId, draftPath) {
163
163
  }
164
164
  function SavedCards({ $data: $dataProp, legend, className, children, ...props }) {
165
165
  const $data = useDataOverride($dataProp);
166
- const { selectedAccountId, selectMethod, selectionError, draft, placing, cardPaymentCollapsed } = usePayment();
166
+ const { selectedAccountId, selectMethod, selectionError, draft, placing: placingNow, attempt, cardPaymentCollapsed } = usePayment();
167
+ const placing = placingNow || attempt?.phase === "sent" || attempt?.phase === "unresolved";
167
168
  const loading = $data == null;
168
169
  const savedCards = $data?.savedCards ?? [];
169
170
  const effectiveSelectedAccountId = resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);
@@ -325,17 +326,27 @@ function SavedCardCvvInput({ accountId, bin, securityCodeLabel, securityCodeDesc
325
326
  }, [cvv]);
326
327
  const submit = useCallback(async (transaction) => {
327
328
  const refusals = checkCvv();
328
- if (refusals.length > 0) return refusals;
329
- if (!sendPayments) return [MISSING_ACTION_ERROR];
330
- return sendPayments({
331
- transaction,
332
- card: {
333
- kind: "saved",
334
- accountId,
335
- bin,
336
- securityCode: cvv
337
- }
338
- });
329
+ if (refusals.length > 0) return {
330
+ reachedGateway: false,
331
+ userErrors: refusals
332
+ };
333
+ try {
334
+ if (!sendPayments) return {
335
+ reachedGateway: false,
336
+ userErrors: [MISSING_ACTION_ERROR]
337
+ };
338
+ return await sendPayments({
339
+ transaction,
340
+ card: {
341
+ kind: "saved",
342
+ accountId,
343
+ bin,
344
+ securityCode: cvv
345
+ }
346
+ });
347
+ } finally {
348
+ setCvv("");
349
+ }
339
350
  }, [
340
351
  checkCvv,
341
352
  sendPayments,
@@ -349,7 +360,7 @@ function SavedCardCvvInput({ accountId, bin, securityCodeLabel, securityCodeDesc
349
360
  ...props.style,
350
361
  ...SECURITY_CODE_MASK_STYLE
351
362
  } : props.style;
352
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("input", {
363
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("input", {
353
364
  ...props,
354
365
  ref: setInputRef,
355
366
  type: "text",
@@ -1 +1 @@
1
- {"version":3,"file":"saved-cards.js","names":[],"sources":["../../../src/matter/saved-cards/saved-cards.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useId,\n useRef,\n useState,\n} from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { ListItemRefContext } from '../../sdk/data/list-item-ref-context.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentUserError } from '../../sdk/payment/operations.js';\nimport { isSecurityCodeComplete, SECURITY_CODE_MASK_STYLE } from '../../sdk/payment/security-code-mask.js';\nimport {\n type PaymentPreflightCheck,\n type PaymentSubmission,\n useRegisterPaymentSubmission,\n} from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\n\n/**\n * Pick a saved card and enter only the CVV (specs/saved-cards.md, T12 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D7). Three components, split by data-fetch role rather than by\n * visual grouping (ADR-0012 Option B, per `SavedCard`'s own schema doc comment):\n *\n * - `SavedCards` — the list root. Owns the minimal `{ id, accountId, methodId }` selection needed to\n * iterate and to dispatch selection; publishes each row's entity ref via `ListItemRefContext`,\n * mirroring `CartList`.\n * - `SavedCardOption` — the per-row template. Independently subscribes to its own `SavedCard:<id>`\n * cache entity through the shipped `reactive-list-item` runtime, mirroring `CartItemRoot`.\n * - `SavedCardCvvField` — the one PCI-bearing, action-dispatching leaf. Declares no `fetchStrategy` of\n * its own; it reads its row through `SavedCardOption`'s plain context, mirroring `CartItemRemove`'s\n * split from `CartItemRoot` (spec Decision 1).\n *\n * The submission continuation is the PCI-critical core (epic § \"The card-data submission channel\"):\n * `SavedCardCvvField` (via its internal `SavedCardCvvInput`) registers the `'saved-card'` continuation\n * on T5's submission registry, closing over the re-entered CVV and the account reference, and calls\n * `$actions.payment.sendPayments` itself. T13's `usePlaceOrder` resolves and invokes it; this module\n * never returns the CVV, only `userErrors`.\n *\n * Selecting a row dispatches `usePayment().selectMethod({ methodId, accountId, bin })` — the\n * `bin` is this row's own projected BIN, never `usePayment().draft.bin`. The epic's and the RFC's\n * own text says the saved-card continuation closes over \"the account id and BIN\", which conflates\n * two different calls: `paymentMethodSelect` (this dispatch) sends the saved card's own BIN alongside\n * `accountId` — a `bin` that does not match the saved card's own BIN is rejected with\n * `MISMATCHED_BIN_FOR_SAVED_CARD`, verified against\n * `apps/storefront-api-core/src/graphql/modules/checkout/index.ts`. The gateway send (inside\n * `SavedCardCvvInput`, below) legitimately carries both, because it is a different wire contract\n * (spec Decision 2 has the full correction).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragments — raw template literals, matching the `payment.tsx` sibling: the codegen\n// `documents` glob does not scan `src/matter/saved-cards*`. Both fragments drill the identical\n// `payment.savedCards` path and merge at compile time (ADR-0011/ADR-0012); `data-component-contract.\n// test.ts` validates each against the real schema.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment SavedCardsListData on Query {\n payment {\n __typename\n id\n savedCards {\n __typename\n id\n accountId\n methodId\n bin\n }\n }\n }\n`;\n\nconst optionFragment = /* GraphQL */ `\n fragment SavedCardOptionData on Query {\n payment {\n savedCards {\n __typename\n id\n accountId\n methodId\n network\n lastFourDigits\n bin\n label\n expired\n cvvRequired\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The minimal per-row identifiers `SavedCards` needs to iterate and to dispatch selection. */\nexport type SavedCardsListEntry = Readonly<{\n id: string;\n accountId: string;\n methodId: string;\n bin: string;\n}>;\n\nexport type SavedCardsData = {\n savedCards?: readonly SavedCardsListEntry[];\n} | null;\n\nexport type SavedCardsProps = ComponentPropsWithRef<'div'> & {\n /** Unwrapped `payment` value from the reactive runtime (arity-1 fragment, ADR-0015); `null` until resolved. */\n $data?: SavedCardsData;\n /** The per-row template, typically one `SavedCardOption`, repeated once per saved card. */\n children?: ReactNode;\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n};\n\n/** The full per-row slice `SavedCardOption` independently resolves via `reactive-list-item`. */\nexport type SavedCardOptionSlice = Readonly<{\n accountId: string;\n methodId: string;\n network: string;\n lastFourDigits: string;\n bin: string;\n label: string | null;\n expired: boolean;\n cvvRequired: boolean;\n}>;\n\nexport type SavedCardOptionProps = ComponentPropsWithRef<'div'> & {\n /** Per-row slice from the reactive-list-item runtime; injected/overridden by the wrapper, or passed directly in tests. */\n $data?: SavedCardOptionSlice | null;\n /** Typically a `SavedCardCvvField`. */\n children?: ReactNode;\n};\n\nexport type SavedCardCvvFieldProps = ComponentPropsWithRef<'input'> & {\n /** Compiler-injected action surface; carries `payment.sendPayments`. */\n $actions?: ComponentActions;\n /**\n * Accessible name for the CVV input. No visible `<label>` renders (Accessibility Contract), so this\n * is the input's only accessible name — authorable rather than a hardcoded English string.\n */\n securityCodeLabel?: string;\n /**\n * Helper copy rendered as a sibling `<p>` and linked to the input via `aria-describedby` (this\n * component's root is the input itself, so there is no `Field.Root` to host a `Field.Description` —\n * saved-cards.md Decision 3). An empty string renders nothing. Copy configuration only — never the\n * security code itself.\n */\n securityCodeDescription?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Saved-card-data context — provided by SavedCardOption, read only by SavedCardCvvField. A plain\n// React context, not a data-fetch strategy: SavedCardCvvField has no fragment of its own (Decision 1).\n// ---------------------------------------------------------------------------\n\nconst SavedCardDataContext = createContext<SavedCardOptionSlice | null>(null);\n\nfunction useSavedCardData(): SavedCardOptionSlice {\n const data = useContext(SavedCardDataContext);\n if (!data) {\n throw new Error('SavedCardCvvField must be used inside a <SavedCardOption> subtree.');\n }\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SavedCardsDefinition = defineComponent<SavedCardsProps>({\n name: 'SavedCards',\n importPath: '@vtex/faststore/components',\n component: SavedCards,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'payment',\n providesContext: 'saved-card',\n editor: { label: 'Saved Cards', category: 'commerce' },\n schema: {\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Saved cards', label: 'Legend' },\n },\n guideline:\n 'Radio list of the shopper\\'s saved cards (network, last four, label); expired cards render present but disabled, never selectable. Place a SavedCardOption inside as the per-card template, itself wrapping a SavedCardCvvField so the shopper can complete payment with a stored card — omitting the CVV field means a shopper can select a card but never pay with it. Picking a row dispatches the payment method selection by methodId and accountId only, never a card number or BIN, plus the inline selection error (role=\"alert\") if the server rejects it. \"Use a new card\" is CardEntry composed as a sibling, not a control inside this list. Renders one container div and no chrome of its own; always carries a loading/empty/ready state hook. Must be inside a PaymentSectionRoot or PaymentSectionController. See saved-cards.md.',\n});\n\nexport const SavedCardOptionDefinition = defineComponent<SavedCardOptionProps>({\n name: 'SavedCardOption',\n importPath: '@vtex/faststore/components',\n component: SavedCardOption,\n fragment: optionFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'saved-card',\n providesContext: 'saved-card-data',\n editor: { label: 'Saved Card Option', category: 'commerce' },\n schema: {},\n guideline:\n 'One saved card as a radio row: network, masked last four, and its label when set; disabled when expired. Place a SavedCardCvvField inside so the CVV appears once this row is selected. Must be inside SavedCards.',\n});\n\nexport const SavedCardCvvFieldDefinition = defineComponent<SavedCardCvvFieldProps>({\n name: 'SavedCardCvvField',\n importPath: '@vtex/faststore/components',\n component: SavedCardCvvField,\n requiresContext: 'saved-card-data',\n actions: { payment: ['sendPayments'] },\n hardened: true,\n editor: { label: 'Saved Card CVV Field', category: 'commerce' },\n schema: {\n securityCodeLabel: { type: 'string', default: 'Security code', label: 'Security code label' },\n // en-US, per requirement 12.27's own quoted copy (spec mask-the-security-code-field-and.md,\n // OQ-1 Option B) — matches this component's existing en-US securityCodeLabel default.\n securityCodeDescription: {\n type: 'string',\n default: 'The 3-4 digit number on your card',\n label: 'Security code description',\n },\n },\n guideline:\n \"The CVV re-entry field for its saved card row; renders nothing until that row is selected and requires a CVV. Registers the direct-to-gateway submission for the saved-card path on selection — the CVV never transits our backend, only the gateway leg's userErrors return. Must be inside SavedCardOption.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/** Mirrors `PaymentMethodList`'s own state hook, so an empty or loading region stays swatch-targetable. */\nfunction savedCardsState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The account a row should treat as \"the current selection.\" `selectedAccountId` is server state that\n * `selectNewCard()` never clears — only `draft.path` flips to `'new-card'` (`use-payment-method-\n * selection.ts`) — so reading `selectedAccountId` alone leaves the previously picked row wedged\n * checked, with its CVV field still mounted and registered, after the shopper picks \"use a new card\"\n * instead. Every read site in this module goes through this one function — the radio group's own\n * `value`, a row's `-selected` state, and the CVV field's visibility gate — so the three can never\n * drift out of sync with each other. Reads only `draft.path`, never `draft.bin`: that stays a fully\n * separate concern (Decision 2) that no call site in this module touches.\n */\nfunction resolveEffectiveSelectedAccountId(selectedAccountId: string | null, draftPath: string | null): string | null {\n return draftPath === 'new-card' ? null : selectedAccountId;\n}\n\nexport function SavedCards({ $data: $dataProp, legend, className, children, ...props }: SavedCardsProps) {\n const $data = useDataOverride($dataProp);\n // `draft` here only for `.path`, to resolve the effective selection below — never `.bin`. The\n // dispatch in handleValueChange still never reads `draft` at all; see its own comment.\n const { selectedAccountId, selectMethod, selectionError, draft, placing, cardPaymentCollapsed } = usePayment();\n const loading = $data == null;\n const savedCards = $data?.savedCards ?? [];\n const effectiveSelectedAccountId = resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n const handleValueChange = useCallback(\n (accountId: string) => {\n const card = savedCards.find((entry) => entry.accountId === accountId);\n if (!card) return;\n // methodId, accountId, and this row's own bin — from this list's own read model, never\n // usePayment().draft.bin. draft.bin can be non-null on the saved-card path too (e.g.\n // Installments calling setDraftBin for its own BIN-keyed lookup; use-payment-method-selection.ts\n // does not clear it on this transition), but the bin sent here must be the saved card's own\n // so paymentMethodSelect can persist a BIN-precise selection. A bin that does not match the\n // saved card's own BIN is rejected with MISMATCHED_BIN_FOR_SAVED_CARD (spec Decision 2). The\n // gateway send (SavedCardCvvInput, below) is the different call that legitimately carries both\n // bin and accountId, reading bin from this row's own resolved slice, never the draft.\n selectMethod({\n methodId: card.methodId,\n accountId: card.accountId,\n ...(card.bin ? { bin: card.bin } : {}),\n });\n },\n [savedCards, selectMethod],\n );\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed — the card option brings\n // the list back. Returned after the last hook, so this instance stays mounted.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-saved-cards=\"\"\n data-fs-saved-cards-state={savedCardsState({ loading, count: savedCards.length })}\n {...(placing ? { 'data-fs-saved-cards-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-saved-cards-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {savedCards.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-saved-cards-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Saved cards')}</Fieldset.Legend>\n <RadioGroup value={effectiveSelectedAccountId ?? ''} onValueChange={handleValueChange}>\n {savedCards.map((card) => (\n <ListItemRefContext\n key={card.id}\n // No fallback slice: this list's own fragment selects only { id, accountId, methodId }\n // (Decision 3), so it has no complete row to fall back to. An incomplete cache slice\n // should render nothing until it resolves, not a partial, silently-wrong row.\n value={{ __typename: 'SavedCard', id: card.id, fallbackData: undefined }}\n >\n {children}\n </ListItemRefContext>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n\nexport function SavedCardOption({ $data: $dataProp, className, children, ...props }: SavedCardOptionProps) {\n // Delivery-agnostic: the slice arrives through the DataOverrideContext seam the reactive-list-item\n // wrapper publishes (or via $data in tests); this component calls no data hook of its own.\n const slice = useDataOverride<SavedCardOptionSlice | null | undefined>($dataProp) ?? null;\n const { selectedAccountId, draft } = usePayment();\n\n if (!slice) return null;\n\n // Path-aware, not just account-aware — see resolveEffectiveSelectedAccountId's own comment: without\n // this, a row that was picked before \"use a new card\" stays marked selected forever.\n const isSelected = slice.accountId === resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n return (\n <SavedCardDataContext value={slice}>\n <div\n {...props}\n className={className}\n data-fs-saved-card-option=\"\"\n {...(isSelected ? { 'data-fs-saved-card-option-selected': '' } : {})}\n {...(slice.expired ? { 'data-fs-saved-card-option-expired': '' } : {})}\n >\n <RadioRoot value={slice.accountId} disabled={slice.expired}>\n <RadioIndicator data-fs-saved-card-option-indicator=\"\" />\n <span data-fs-saved-card-option-network=\"\">{slice.network}</span>\n <span data-fs-saved-card-option-last-four=\"\">{`•••• ${slice.lastFourDigits}`}</span>\n {slice.label ? <span data-fs-saved-card-option-label=\"\">{slice.label}</span> : null}\n </RadioRoot>\n {children}\n </div>\n </SavedCardDataContext>\n );\n}\n\nconst MISSING_ACTION_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n/**\n * The saved-card path's own client-synthesized refusal (T27, specs/place-order-preflight-card-\n * validation.md FR-11/FR-12; Decision 8). Distinct from `card-entry`'s `INVALID_CARD_DATA` (a\n * different path, a different set of fields, and importing it here would be a matter → matter\n * dependency) and from the gateway's own codes.\n *\n * Never name this constant, or its string value, inside `sdk/payment/use-place-order.ts`: it contains\n * `security` and would fail `CARD_FIELD_PATTERN`. The pre-flight only ever handles the opaque\n * `PaymentUserError` (spec NFR-1, Risk R9).\n */\nexport const SAVED_CARD_INVALID_SECURITY_CODE = 'INVALID_SECURITY_CODE';\n\nconst INVALID_SECURITY_CODE_ERROR: PaymentUserError = {\n code: SAVED_CARD_INVALID_SECURITY_CODE,\n message: 'Enter a valid security code before placing the order.',\n};\n\n/** VTEX's own CVV upper bound across the networks this codebase's masks cover (Decision 4). */\nconst CVV_MAX_LENGTH = 4;\n\nfunction sanitizeCvv(value: string): string {\n return value.replace(/\\D/g, '').slice(0, CVV_MAX_LENGTH);\n}\n\n/**\n * Three or four digits (T27, FR-11). `sanitizeCvv` already strips non-digits and caps at\n * `CVV_MAX_LENGTH`, so the only rule left is the floor — and three-or-four is `card-entry`'s own\n * fallback for a method that declares no `cvvRegex` (its `isValidCvv`, `/^\\d{3,4}$/`). Requiredness is\n * not checked here: `SavedCardCvvInput` renders only when the row is selected and `cvvRequired` is\n * true, so its presence *is* the requiredness signal.\n */\nconst SAVED_CARD_CVV_PATTERN = /^\\d{3,4}$/;\n\n/**\n * The length at which this field's security code counts as fully typed, and the mask engages (T173,\n * specs/mask-the-cvv-only-once-it.md, FR-3). Three — the floor of `SAVED_CARD_CVV_PATTERN` above,\n * for the same reason that pattern accepts either length: a saved card carries no per-network CVV\n * length. Its slice exposes a display `network` string, not a `cvvMask`, so nothing here tells a\n * 3-digit network apart from Amex's 4 the way `CardEntry` can, and inferring one from the display\n * name would hardcode a network table this package deliberately does not own.\n *\n * Masking at the floor is the side to err on: a complete code is never left legible on either kind\n * of card, and a 4-digit code is simply already masked when its last digit arrives.\n */\nconst SAVED_CARD_CVV_COMPLETE_LENGTH = 3;\n\ntype SavedCardCvvInputProps = ComponentPropsWithRef<'input'> & {\n /** `SavedCard.accountId` from this row's own resolved slice — never `usePayment().draft`. */\n accountId: string;\n /**\n * `SavedCard.bin` from this row's own resolved slice — the one this row is actually being paid\n * with. Deliberately **not** `usePayment().draft.bin`: that field can be populated on the\n * saved-card path too (e.g. Installments calling `setDraftBin` for its own BIN-keyed lookup;\n * `use-payment-method-selection.ts` does not clear it on this transition), so it can carry a\n * different card's BIN, or a stale one, at the moment this continuation runs. Forwarding it here\n * instead of the row's own field would be the same class of bug Decision 2 already closed for the\n * `selectMethod` dispatch — undetectable by type-checking, since both are plain `string`.\n */\n bin: string;\n /** Accessible name applied verbatim as `aria-label` — see `SavedCardCvvFieldProps`'s own doc comment. */\n securityCodeLabel: string;\n /** Helper copy rendered as a sibling `<p>` — see `SavedCardCvvFieldProps`'s own doc comment. Threaded\n * through exactly like `securityCodeLabel`. An empty string renders nothing. */\n securityCodeDescription: string;\n $actions?: ComponentActions;\n};\n\n/** Merges a caller-forwarded `aria-describedby` (from `...props`) with the description `<p>`'s own\n * id, rather than clobbering it — the accessibility wiring `Field.Root` would otherwise provide for\n * free (saved-cards.md Decision 3). */\nfunction mergeDescribedBy(existing: string | undefined, id: string): string {\n return existing ? `${existing} ${id}` : id;\n}\n\n/**\n * The stateful, registering half of the CVV field — split out from `SavedCardCvvField` so this row's\n * conditional visibility never skips a hook call (spec Decision 5). This component mounts wholesale\n * when its row is selected and requires a CVV, and unmounts wholesale otherwise, so its own hooks\n * always run the same number of times across its own lifetime; only its *presence* is conditional,\n * decided by its caller.\n */\nfunction SavedCardCvvInput({\n accountId,\n bin,\n securityCodeLabel,\n securityCodeDescription,\n $actions,\n ref: forwardedRef,\n ...props\n}: SavedCardCvvInputProps) {\n const [cvv, setCvv] = useState('');\n const inputRef = useRef<HTMLInputElement>(null);\n const sendPayments = $actions?.payment?.sendPayments;\n const descriptionId = useId();\n\n // Attaches both this component's own ref (needed to focus the field on refusal, in checkCvv below)\n // and whatever ref a caller already forwarded onto SavedCardCvvField/SavedCardCvvInput — `props`\n // spreads onto the element below and `SavedCardCvvInputProps` is `ComponentPropsWithRef<'input'>`, so\n // writing `ref={inputRef}` after the spread would silently drop a caller-forwarded ref instead.\n const setInputRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n\n if (typeof forwardedRef !== 'function') {\n if (forwardedRef) forwardedRef.current = node;\n return;\n }\n\n // React 19: a ref callback may itself return a cleanup function instead of being re-invoked\n // with `null` on detach. Preserve whichever form the caller used — never call a React-19\n // cleanup-style ref with `null`, a value that form is documented never to receive (fix pass,\n // review F2). A caller using the legacy form (no returned cleanup) keeps today's behaviour:\n // this callback returns nothing either, so React re-invokes it with `null` on detach, which\n // falls into this same branch and calls `forwardedRef(null)`, exactly as before.\n const cleanup = forwardedRef(node);\n if (typeof cleanup === 'function') {\n return () => {\n inputRef.current = null;\n cleanup();\n };\n }\n return;\n },\n [forwardedRef],\n );\n\n // The saved-card check (T27, FR-11/FR-12; Decision 8). Three-or-four digits — see\n // SAVED_CARD_CVV_PATTERN's own doc comment for why nothing narrower is checkable and nothing wider\n // is safe. The only field on this path, so there is no ordering rule and no inline error node to\n // fill (FR-12) — the message reaches the shopper through PlaceOrder's existing role=\"alert\".\n const checkCvv = useCallback<PaymentPreflightCheck>(() => {\n if (SAVED_CARD_CVV_PATTERN.test(cvv)) return [];\n inputRef.current?.focus();\n return [INVALID_SECURITY_CODE_ERROR];\n }, [cvv]);\n\n const submit = useCallback<PaymentSubmission>(\n async (transaction) => {\n // Same function the pre-flight calls (FR-3, applied to this path) — the two moments cannot\n // diverge here either.\n const refusals = checkCvv();\n if (refusals.length > 0) return refusals;\n if (!sendPayments) return [MISSING_ACTION_ERROR];\n // `bin` here is always the `accountId`/`bin` pair from this row's own props — see their doc\n // comments above for why `usePayment().draft.bin` must never substitute for either.\n return sendPayments({ transaction, card: { kind: 'saved', accountId, bin, securityCode: cvv } });\n },\n [checkCvv, sendPayments, accountId, bin, cvv],\n );\n\n useRegisterPaymentSubmission('saved-card', submit, checkCvv);\n\n // Merges with a caller-forwarded aria-describedby rather than clobbering it — mirrors the ref\n // merge above (setInputRef) for the same \"don't drop what a caller already passed\" reason.\n const ariaDescribedBy = securityCodeDescription\n ? mergeDescribedBy(props['aria-describedby'], descriptionId)\n : props['aria-describedby'];\n\n // T101 — the mask merges *over* a caller-forwarded style, inverted from this repo's usual\n // `...props.style`-last idiom (cart-item.tsx, product-list.tsx): deliberately, so a caller can\n // never remove the security-code mask. See security-code-mask.ts.\n //\n // T173 — that merge now happens from the keystroke that completes the code, not from the first\n // one, so a shopper can read back what they typed while they type it. Below the trigger the\n // caller's own style passes through untouched, which is the same thing it did before this field\n // ever had a mask; the guarantee above is about what a caller cannot do once the code is complete.\n const inputStyle = isSecurityCodeComplete(cvv, SAVED_CARD_CVV_COMPLETE_LENGTH)\n ? { ...props.style, ...SECURITY_CODE_MASK_STYLE }\n : props.style;\n\n return (\n <>\n <input\n {...props}\n ref={setInputRef}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n aria-label={securityCodeLabel}\n aria-describedby={ariaDescribedBy}\n value={cvv}\n onChange={(event) => setCvv(sanitizeCvv(event.target.value))}\n data-fs-saved-card-cvv-field=\"\"\n style={inputStyle}\n />\n {securityCodeDescription ? (\n <p id={descriptionId} data-fs-saved-card-cvv-description=\"\">\n {securityCodeDescription}\n </p>\n ) : null}\n </>\n );\n}\n\nexport function SavedCardCvvField({\n $actions: $actionsProp,\n securityCodeLabel = 'Security code',\n securityCodeDescription = 'The 3-4 digit number on your card',\n className,\n ...props\n}: SavedCardCvvFieldProps) {\n const $actions = useActionsOverride($actionsProp);\n const { accountId, bin, cvvRequired } = useSavedCardData();\n const { selectedAccountId, draft } = usePayment();\n\n // Path-aware, matching SavedCardOption's own isSelected — otherwise this field stays mounted and\n // registered after \"use a new card\", with selectedAccountId still equal to this row's own accountId.\n if (accountId !== resolveEffectiveSelectedAccountId(selectedAccountId, draft.path) || !cvvRequired) {\n return null;\n }\n\n return (\n <SavedCardCvvInput\n {...props}\n className={className}\n accountId={accountId}\n bin={bin}\n securityCodeLabel={securityCodeLabel}\n securityCodeDescription={securityCodeDescription}\n $actions={$actions}\n />\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"saved-cards.js","names":[],"sources":["../../../src/matter/saved-cards/saved-cards.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n createContext,\n type ReactNode,\n useCallback,\n useContext,\n useId,\n useRef,\n useState,\n} from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { ListItemRefContext } from '../../sdk/data/list-item-ref-context.js';\nimport { PAYMENT_NETWORK_ERROR_CODE, type PaymentUserError } from '../../sdk/payment/operations.js';\nimport { isSecurityCodeComplete, SECURITY_CODE_MASK_STYLE } from '../../sdk/payment/security-code-mask.js';\nimport {\n type PaymentPreflightCheck,\n type PaymentSubmission,\n useRegisterPaymentSubmission,\n} from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\n\n/**\n * Pick a saved card and enter only the CVV (specs/saved-cards.md, T12 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D7). Three components, split by data-fetch role rather than by\n * visual grouping (ADR-0012 Option B, per `SavedCard`'s own schema doc comment):\n *\n * - `SavedCards` — the list root. Owns the minimal `{ id, accountId, methodId }` selection needed to\n * iterate and to dispatch selection; publishes each row's entity ref via `ListItemRefContext`,\n * mirroring `CartList`.\n * - `SavedCardOption` — the per-row template. Independently subscribes to its own `SavedCard:<id>`\n * cache entity through the shipped `reactive-list-item` runtime, mirroring `CartItemRoot`.\n * - `SavedCardCvvField` — the one PCI-bearing, action-dispatching leaf. Declares no `fetchStrategy` of\n * its own; it reads its row through `SavedCardOption`'s plain context, mirroring `CartItemRemove`'s\n * split from `CartItemRoot` (spec Decision 1).\n *\n * The submission continuation is the PCI-critical core (epic § \"The card-data submission channel\"):\n * `SavedCardCvvField` (via its internal `SavedCardCvvInput`) registers the `'saved-card'` continuation\n * on T5's submission registry, closing over the re-entered CVV and the account reference, and calls\n * `$actions.payment.sendPayments` itself. T13's `usePlaceOrder` resolves and invokes it; this module\n * never returns the CVV, only `userErrors`.\n *\n * Selecting a row dispatches `usePayment().selectMethod({ methodId, accountId, bin })` — the\n * `bin` is this row's own projected BIN, never `usePayment().draft.bin`. The epic's and the RFC's\n * own text says the saved-card continuation closes over \"the account id and BIN\", which conflates\n * two different calls: `paymentMethodSelect` (this dispatch) sends the saved card's own BIN alongside\n * `accountId` — a `bin` that does not match the saved card's own BIN is rejected with\n * `MISMATCHED_BIN_FOR_SAVED_CARD`, verified against\n * `apps/storefront-api-core/src/graphql/modules/checkout/index.ts`. The gateway send (inside\n * `SavedCardCvvInput`, below) legitimately carries both, because it is a different wire contract\n * (spec Decision 2 has the full correction).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragments — raw template literals, matching the `payment.tsx` sibling: the codegen\n// `documents` glob does not scan `src/matter/saved-cards*`. Both fragments drill the identical\n// `payment.savedCards` path and merge at compile time (ADR-0011/ADR-0012); `data-component-contract.\n// test.ts` validates each against the real schema.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment SavedCardsListData on Query {\n payment {\n __typename\n id\n savedCards {\n __typename\n id\n accountId\n methodId\n bin\n }\n }\n }\n`;\n\nconst optionFragment = /* GraphQL */ `\n fragment SavedCardOptionData on Query {\n payment {\n savedCards {\n __typename\n id\n accountId\n methodId\n network\n lastFourDigits\n bin\n label\n expired\n cvvRequired\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The minimal per-row identifiers `SavedCards` needs to iterate and to dispatch selection. */\nexport type SavedCardsListEntry = Readonly<{\n id: string;\n accountId: string;\n methodId: string;\n bin: string;\n}>;\n\nexport type SavedCardsData = {\n savedCards?: readonly SavedCardsListEntry[];\n} | null;\n\nexport type SavedCardsProps = ComponentPropsWithRef<'div'> & {\n /** Unwrapped `payment` value from the reactive runtime (arity-1 fragment, ADR-0015); `null` until resolved. */\n $data?: SavedCardsData;\n /** The per-row template, typically one `SavedCardOption`, repeated once per saved card. */\n children?: ReactNode;\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n};\n\n/** The full per-row slice `SavedCardOption` independently resolves via `reactive-list-item`. */\nexport type SavedCardOptionSlice = Readonly<{\n accountId: string;\n methodId: string;\n network: string;\n lastFourDigits: string;\n bin: string;\n label: string | null;\n expired: boolean;\n cvvRequired: boolean;\n}>;\n\nexport type SavedCardOptionProps = ComponentPropsWithRef<'div'> & {\n /** Per-row slice from the reactive-list-item runtime; injected/overridden by the wrapper, or passed directly in tests. */\n $data?: SavedCardOptionSlice | null;\n /** Typically a `SavedCardCvvField`. */\n children?: ReactNode;\n};\n\nexport type SavedCardCvvFieldProps = ComponentPropsWithRef<'input'> & {\n /** Compiler-injected action surface; carries `payment.sendPayments`. */\n $actions?: ComponentActions;\n /**\n * Accessible name for the CVV input. No visible `<label>` renders (Accessibility Contract), so this\n * is the input's only accessible name — authorable rather than a hardcoded English string.\n */\n securityCodeLabel?: string;\n /**\n * Helper copy rendered as a sibling `<p>` and linked to the input via `aria-describedby` (this\n * component's root is the input itself, so there is no `Field.Root` to host a `Field.Description` —\n * saved-cards.md Decision 3). An empty string renders nothing. Copy configuration only — never the\n * security code itself.\n */\n securityCodeDescription?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Saved-card-data context — provided by SavedCardOption, read only by SavedCardCvvField. A plain\n// React context, not a data-fetch strategy: SavedCardCvvField has no fragment of its own (Decision 1).\n// ---------------------------------------------------------------------------\n\nconst SavedCardDataContext = createContext<SavedCardOptionSlice | null>(null);\n\nfunction useSavedCardData(): SavedCardOptionSlice {\n const data = useContext(SavedCardDataContext);\n if (!data) {\n throw new Error('SavedCardCvvField must be used inside a <SavedCardOption> subtree.');\n }\n return data;\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SavedCardsDefinition = defineComponent<SavedCardsProps>({\n name: 'SavedCards',\n importPath: '@vtex/faststore/components',\n component: SavedCards,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'payment',\n providesContext: 'saved-card',\n editor: { label: 'Saved Cards', category: 'commerce' },\n schema: {\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Saved cards', label: 'Legend' },\n },\n guideline:\n 'Radio list of the shopper\\'s saved cards (network, last four, label); expired cards render present but disabled, never selectable. Place a SavedCardOption inside as the per-card template, itself wrapping a SavedCardCvvField so the shopper can complete payment with a stored card — omitting the CVV field means a shopper can select a card but never pay with it. Picking a row dispatches the payment method selection by methodId and accountId only, never a card number or BIN, plus the inline selection error (role=\"alert\") if the server rejects it. \"Use a new card\" is CardEntry composed as a sibling, not a control inside this list. Renders one container div and no chrome of its own; always carries a loading/empty/ready state hook. Must be inside a PaymentSectionRoot or PaymentSectionController. See saved-cards.md.',\n});\n\nexport const SavedCardOptionDefinition = defineComponent<SavedCardOptionProps>({\n name: 'SavedCardOption',\n importPath: '@vtex/faststore/components',\n component: SavedCardOption,\n fragment: optionFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'saved-card',\n providesContext: 'saved-card-data',\n editor: { label: 'Saved Card Option', category: 'commerce' },\n schema: {},\n guideline:\n 'One saved card as a radio row: network, masked last four, and its label when set; disabled when expired. Place a SavedCardCvvField inside so the CVV appears once this row is selected. Must be inside SavedCards.',\n});\n\nexport const SavedCardCvvFieldDefinition = defineComponent<SavedCardCvvFieldProps>({\n name: 'SavedCardCvvField',\n importPath: '@vtex/faststore/components',\n component: SavedCardCvvField,\n requiresContext: 'saved-card-data',\n actions: { payment: ['sendPayments'] },\n hardened: true,\n editor: { label: 'Saved Card CVV Field', category: 'commerce' },\n schema: {\n securityCodeLabel: { type: 'string', default: 'Security code', label: 'Security code label' },\n // en-US, per requirement 12.27's own quoted copy (spec mask-the-security-code-field-and.md,\n // OQ-1 Option B) — matches this component's existing en-US securityCodeLabel default.\n securityCodeDescription: {\n type: 'string',\n default: 'The 3-4 digit number on your card',\n label: 'Security code description',\n },\n },\n guideline:\n \"The CVV re-entry field for its saved card row; renders nothing until that row is selected and requires a CVV. Registers the direct-to-gateway submission for the saved-card path on selection — the CVV never transits our backend, only the gateway leg's userErrors return. Must be inside SavedCardOption.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/** Mirrors `PaymentMethodList`'s own state hook, so an empty or loading region stays swatch-targetable. */\nfunction savedCardsState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The account a row should treat as \"the current selection.\" `selectedAccountId` is server state that\n * `selectNewCard()` never clears — only `draft.path` flips to `'new-card'` (`use-payment-method-\n * selection.ts`) — so reading `selectedAccountId` alone leaves the previously picked row wedged\n * checked, with its CVV field still mounted and registered, after the shopper picks \"use a new card\"\n * instead. Every read site in this module goes through this one function — the radio group's own\n * `value`, a row's `-selected` state, and the CVV field's visibility gate — so the three can never\n * drift out of sync with each other. Reads only `draft.path`, never `draft.bin`: that stays a fully\n * separate concern (Decision 2) that no call site in this module touches.\n */\nfunction resolveEffectiveSelectedAccountId(selectedAccountId: string | null, draftPath: string | null): string | null {\n return draftPath === 'new-card' ? null : selectedAccountId;\n}\n\nexport function SavedCards({ $data: $dataProp, legend, className, children, ...props }: SavedCardsProps) {\n const $data = useDataOverride($dataProp);\n // `draft` here only for `.path`, to resolve the effective selection below — never `.bin`. The\n // dispatch in handleValueChange still never reads `draft` at all; see its own comment.\n const {\n selectedAccountId,\n selectMethod,\n selectionError,\n draft,\n placing: placingNow,\n attempt,\n cardPaymentCollapsed,\n } = usePayment();\n // T202: frozen while the order attempt is resume-only too (`sent` / `unresolved`): the card may already have\n // been sent and `placeOrder` only resumes, so a security code typed here would never be submitted or cleared.\n const placing = placingNow || attempt?.phase === 'sent' || attempt?.phase === 'unresolved';\n const loading = $data == null;\n const savedCards = $data?.savedCards ?? [];\n const effectiveSelectedAccountId = resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n const handleValueChange = useCallback(\n (accountId: string) => {\n const card = savedCards.find((entry) => entry.accountId === accountId);\n if (!card) return;\n // methodId, accountId, and this row's own bin — from this list's own read model, never\n // usePayment().draft.bin. draft.bin can be non-null on the saved-card path too (e.g.\n // Installments calling setDraftBin for its own BIN-keyed lookup; use-payment-method-selection.ts\n // does not clear it on this transition), but the bin sent here must be the saved card's own\n // so paymentMethodSelect can persist a BIN-precise selection. A bin that does not match the\n // saved card's own BIN is rejected with MISMATCHED_BIN_FOR_SAVED_CARD (spec Decision 2). The\n // gateway send (SavedCardCvvInput, below) is the different call that legitimately carries both\n // bin and accountId, reading bin from this row's own resolved slice, never the draft.\n selectMethod({\n methodId: card.methodId,\n accountId: card.accountId,\n ...(card.bin ? { bin: card.bin } : {}),\n });\n },\n [savedCards, selectMethod],\n );\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed — the card option brings\n // the list back. Returned after the last hook, so this instance stays mounted.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-saved-cards=\"\"\n data-fs-saved-cards-state={savedCardsState({ loading, count: savedCards.length })}\n {...(placing ? { 'data-fs-saved-cards-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-saved-cards-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {savedCards.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-saved-cards-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Saved cards')}</Fieldset.Legend>\n <RadioGroup value={effectiveSelectedAccountId ?? ''} onValueChange={handleValueChange}>\n {savedCards.map((card) => (\n <ListItemRefContext\n key={card.id}\n // No fallback slice: this list's own fragment selects only { id, accountId, methodId }\n // (Decision 3), so it has no complete row to fall back to. An incomplete cache slice\n // should render nothing until it resolves, not a partial, silently-wrong row.\n value={{ __typename: 'SavedCard', id: card.id, fallbackData: undefined }}\n >\n {children}\n </ListItemRefContext>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n\nexport function SavedCardOption({ $data: $dataProp, className, children, ...props }: SavedCardOptionProps) {\n // Delivery-agnostic: the slice arrives through the DataOverrideContext seam the reactive-list-item\n // wrapper publishes (or via $data in tests); this component calls no data hook of its own.\n const slice = useDataOverride<SavedCardOptionSlice | null | undefined>($dataProp) ?? null;\n const { selectedAccountId, draft } = usePayment();\n\n if (!slice) return null;\n\n // Path-aware, not just account-aware — see resolveEffectiveSelectedAccountId's own comment: without\n // this, a row that was picked before \"use a new card\" stays marked selected forever.\n const isSelected = slice.accountId === resolveEffectiveSelectedAccountId(selectedAccountId, draft.path);\n\n return (\n <SavedCardDataContext value={slice}>\n <div\n {...props}\n className={className}\n data-fs-saved-card-option=\"\"\n {...(isSelected ? { 'data-fs-saved-card-option-selected': '' } : {})}\n {...(slice.expired ? { 'data-fs-saved-card-option-expired': '' } : {})}\n >\n <RadioRoot value={slice.accountId} disabled={slice.expired}>\n <RadioIndicator data-fs-saved-card-option-indicator=\"\" />\n <span data-fs-saved-card-option-network=\"\">{slice.network}</span>\n <span data-fs-saved-card-option-last-four=\"\">{`•••• ${slice.lastFourDigits}`}</span>\n {slice.label ? <span data-fs-saved-card-option-label=\"\">{slice.label}</span> : null}\n </RadioRoot>\n {children}\n </div>\n </SavedCardDataContext>\n );\n}\n\nconst MISSING_ACTION_ERROR: PaymentUserError = {\n code: PAYMENT_NETWORK_ERROR_CODE,\n message: 'Unable to complete the request. Please try again.',\n};\n\n/**\n * The saved-card path's own client-synthesized refusal (T27, specs/place-order-preflight-card-\n * validation.md FR-11/FR-12; Decision 8). Distinct from `card-entry`'s `INVALID_CARD_DATA` (a\n * different path, a different set of fields, and importing it here would be a matter → matter\n * dependency) and from the gateway's own codes.\n *\n * Never name this constant, or its string value, inside `sdk/payment/use-place-order.ts`: it contains\n * `security` and would fail `CARD_FIELD_PATTERN`. The pre-flight only ever handles the opaque\n * `PaymentUserError` (spec NFR-1, Risk R9).\n */\nexport const SAVED_CARD_INVALID_SECURITY_CODE = 'INVALID_SECURITY_CODE';\n\nconst INVALID_SECURITY_CODE_ERROR: PaymentUserError = {\n code: SAVED_CARD_INVALID_SECURITY_CODE,\n message: 'Enter a valid security code before placing the order.',\n};\n\n/** VTEX's own CVV upper bound across the networks this codebase's masks cover (Decision 4). */\nconst CVV_MAX_LENGTH = 4;\n\nfunction sanitizeCvv(value: string): string {\n return value.replace(/\\D/g, '').slice(0, CVV_MAX_LENGTH);\n}\n\n/**\n * Three or four digits (T27, FR-11). `sanitizeCvv` already strips non-digits and caps at\n * `CVV_MAX_LENGTH`, so the only rule left is the floor — and three-or-four is `card-entry`'s own\n * fallback for a method that declares no `cvvRegex` (its `isValidCvv`, `/^\\d{3,4}$/`). Requiredness is\n * not checked here: `SavedCardCvvInput` renders only when the row is selected and `cvvRequired` is\n * true, so its presence *is* the requiredness signal.\n */\nconst SAVED_CARD_CVV_PATTERN = /^\\d{3,4}$/;\n\n/**\n * The length at which this field's security code counts as fully typed, and the mask engages (T173,\n * specs/mask-the-cvv-only-once-it.md, FR-3). Three — the floor of `SAVED_CARD_CVV_PATTERN` above,\n * for the same reason that pattern accepts either length: a saved card carries no per-network CVV\n * length. Its slice exposes a display `network` string, not a `cvvMask`, so nothing here tells a\n * 3-digit network apart from Amex's 4 the way `CardEntry` can, and inferring one from the display\n * name would hardcode a network table this package deliberately does not own.\n *\n * Masking at the floor is the side to err on: a complete code is never left legible on either kind\n * of card, and a 4-digit code is simply already masked when its last digit arrives.\n */\nconst SAVED_CARD_CVV_COMPLETE_LENGTH = 3;\n\ntype SavedCardCvvInputProps = ComponentPropsWithRef<'input'> & {\n /** `SavedCard.accountId` from this row's own resolved slice — never `usePayment().draft`. */\n accountId: string;\n /**\n * `SavedCard.bin` from this row's own resolved slice — the one this row is actually being paid\n * with. Deliberately **not** `usePayment().draft.bin`: that field can be populated on the\n * saved-card path too (e.g. Installments calling `setDraftBin` for its own BIN-keyed lookup;\n * `use-payment-method-selection.ts` does not clear it on this transition), so it can carry a\n * different card's BIN, or a stale one, at the moment this continuation runs. Forwarding it here\n * instead of the row's own field would be the same class of bug Decision 2 already closed for the\n * `selectMethod` dispatch — undetectable by type-checking, since both are plain `string`.\n */\n bin: string;\n /** Accessible name applied verbatim as `aria-label` — see `SavedCardCvvFieldProps`'s own doc comment. */\n securityCodeLabel: string;\n /** Helper copy rendered as a sibling `<p>` — see `SavedCardCvvFieldProps`'s own doc comment. Threaded\n * through exactly like `securityCodeLabel`. An empty string renders nothing. */\n securityCodeDescription: string;\n $actions?: ComponentActions;\n};\n\n/** Merges a caller-forwarded `aria-describedby` (from `...props`) with the description `<p>`'s own\n * id, rather than clobbering it — the accessibility wiring `Field.Root` would otherwise provide for\n * free (saved-cards.md Decision 3). */\nfunction mergeDescribedBy(existing: string | undefined, id: string): string {\n return existing ? `${existing} ${id}` : id;\n}\n\n/**\n * The stateful, registering half of the CVV field — split out from `SavedCardCvvField` so this row's\n * conditional visibility never skips a hook call (spec Decision 5). This component mounts wholesale\n * when its row is selected and requires a CVV, and unmounts wholesale otherwise, so its own hooks\n * always run the same number of times across its own lifetime; only its *presence* is conditional,\n * decided by its caller.\n */\nfunction SavedCardCvvInput({\n accountId,\n bin,\n securityCodeLabel,\n securityCodeDescription,\n $actions,\n ref: forwardedRef,\n ...props\n}: SavedCardCvvInputProps) {\n const [cvv, setCvv] = useState('');\n const inputRef = useRef<HTMLInputElement>(null);\n const sendPayments = $actions?.payment?.sendPayments;\n const descriptionId = useId();\n\n // Attaches both this component's own ref (needed to focus the field on refusal, in checkCvv below)\n // and whatever ref a caller already forwarded onto SavedCardCvvField/SavedCardCvvInput — `props`\n // spreads onto the element below and `SavedCardCvvInputProps` is `ComponentPropsWithRef<'input'>`, so\n // writing `ref={inputRef}` after the spread would silently drop a caller-forwarded ref instead.\n const setInputRef = useCallback(\n (node: HTMLInputElement | null) => {\n inputRef.current = node;\n\n if (typeof forwardedRef !== 'function') {\n if (forwardedRef) forwardedRef.current = node;\n return;\n }\n\n // React 19: a ref callback may itself return a cleanup function instead of being re-invoked\n // with `null` on detach. Preserve whichever form the caller used — never call a React-19\n // cleanup-style ref with `null`, a value that form is documented never to receive (fix pass,\n // review F2). A caller using the legacy form (no returned cleanup) keeps today's behaviour:\n // this callback returns nothing either, so React re-invokes it with `null` on detach, which\n // falls into this same branch and calls `forwardedRef(null)`, exactly as before.\n const cleanup = forwardedRef(node);\n if (typeof cleanup === 'function') {\n return () => {\n inputRef.current = null;\n cleanup();\n };\n }\n return;\n },\n [forwardedRef],\n );\n\n // The saved-card check (T27, FR-11/FR-12; Decision 8). Three-or-four digits — see\n // SAVED_CARD_CVV_PATTERN's own doc comment for why nothing narrower is checkable and nothing wider\n // is safe. The only field on this path, so there is no ordering rule and no inline error node to\n // fill (FR-12) — the message reaches the shopper through PlaceOrder's existing role=\"alert\".\n const checkCvv = useCallback<PaymentPreflightCheck>(() => {\n if (SAVED_CARD_CVV_PATTERN.test(cvv)) return [];\n inputRef.current?.focus();\n return [INVALID_SECURITY_CODE_ERROR];\n }, [cvv]);\n\n const submit = useCallback<PaymentSubmission>(\n async (transaction) => {\n // Same function the pre-flight calls (FR-3, applied to this path) — the two moments cannot\n // diverge here either.\n // Both refusals precede any request, so each reports `reachedGateway: false` (T112): the attempt is\n // freed rather than left resume-only on a card that was never sent.\n const refusals = checkCvv();\n if (refusals.length > 0) return { reachedGateway: false, userErrors: refusals };\n // T202: past the field's own check the code is not needed again, whatever the outcome. A throw is\n // re-thrown untouched; the `finally` is the one place certain to run.\n try {\n if (!sendPayments) return { reachedGateway: false, userErrors: [MISSING_ACTION_ERROR] };\n // `bin` here is always the `accountId`/`bin` pair from this row's own props — see their doc\n // comments above for why `usePayment().draft.bin` must never substitute for either.\n return await sendPayments({ transaction, card: { kind: 'saved', accountId, bin, securityCode: cvv } });\n } finally {\n setCvv('');\n }\n },\n [checkCvv, sendPayments, accountId, bin, cvv],\n );\n\n useRegisterPaymentSubmission('saved-card', submit, checkCvv);\n\n // Merges with a caller-forwarded aria-describedby rather than clobbering it — mirrors the ref\n // merge above (setInputRef) for the same \"don't drop what a caller already passed\" reason.\n const ariaDescribedBy = securityCodeDescription\n ? mergeDescribedBy(props['aria-describedby'], descriptionId)\n : props['aria-describedby'];\n\n // T101 — the mask merges *over* a caller-forwarded style, inverted from this repo's usual\n // `...props.style`-last idiom (cart-item.tsx, product-list.tsx): deliberately, so a caller can\n // never remove the security-code mask. See security-code-mask.ts.\n //\n // T173 — that merge now happens from the keystroke that completes the code, not from the first\n // one, so a shopper can read back what they typed while they type it. Below the trigger the\n // caller's own style passes through untouched, which is the same thing it did before this field\n // ever had a mask; the guarantee above is about what a caller cannot do once the code is complete.\n const inputStyle = isSecurityCodeComplete(cvv, SAVED_CARD_CVV_COMPLETE_LENGTH)\n ? { ...props.style, ...SECURITY_CODE_MASK_STYLE }\n : props.style;\n\n return (\n <>\n <input\n {...props}\n ref={setInputRef}\n type=\"text\"\n inputMode=\"numeric\"\n autoComplete=\"off\"\n aria-label={securityCodeLabel}\n aria-describedby={ariaDescribedBy}\n value={cvv}\n onChange={(event) => setCvv(sanitizeCvv(event.target.value))}\n data-fs-saved-card-cvv-field=\"\"\n style={inputStyle}\n />\n {securityCodeDescription ? (\n <p id={descriptionId} data-fs-saved-card-cvv-description=\"\">\n {securityCodeDescription}\n </p>\n ) : null}\n </>\n );\n}\n\nexport function SavedCardCvvField({\n $actions: $actionsProp,\n securityCodeLabel = 'Security code',\n securityCodeDescription = 'The 3-4 digit number on your card',\n className,\n ...props\n}: SavedCardCvvFieldProps) {\n const $actions = useActionsOverride($actionsProp);\n const { accountId, bin, cvvRequired } = useSavedCardData();\n const { selectedAccountId, draft } = usePayment();\n\n // Path-aware, matching SavedCardOption's own isSelected — otherwise this field stays mounted and\n // registered after \"use a new card\", with selectedAccountId still equal to this row's own accountId.\n if (accountId !== resolveEffectiveSelectedAccountId(selectedAccountId, draft.path) || !cvvRequired) {\n return null;\n }\n\n return (\n <SavedCardCvvInput\n {...props}\n className={className}\n accountId={accountId}\n bin={bin}\n securityCodeLabel={securityCodeLabel}\n securityCodeDescription={securityCodeDescription}\n $actions={$actions}\n />\n );\n}\n"],"mappings":"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"}
@@ -4,7 +4,7 @@ import "../../core/dist/index.js";
4
4
  import { graphql } from "../../sdk/generated/gql.js";
5
5
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
6
6
  import { createContext, useContext, useMemo } from "react";
7
- import { Fragment, jsx } from "react/jsx-runtime";
7
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
8
8
  //#region src/matter/session/session.tsx
9
9
  const fragment = graphql(`
10
10
  fragment SessionData on Query {
@@ -100,7 +100,7 @@ function SessionRoot({ $data: $dataProp, children }) {
100
100
  function SessionGuard({ when, children }) {
101
101
  const { state } = useSession();
102
102
  if (!when?.includes(state)) return null;
103
- return /* @__PURE__ */ jsx(Fragment, { children });
103
+ return /* @__PURE__ */ jsx(Fragment$1, { children });
104
104
  }
105
105
  const Session = {
106
106
  Root: SessionRoot,
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","names":[],"sources":["../../../src/matter/session/session.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { SessionEntity } from '../../sdk/session/operations.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — typed via @graphql-codegen client-preset\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment SessionData on Query {\n session {\n ...SessionFragment\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Tri-state derivation — the single home of the truth table (Decision 2).\n// ---------------------------------------------------------------------------\n\nexport type SessionState = 'anonymous' | 'identified' | 'loggedIn';\n\n/**\n * Derive the identity tri-state from the two raw session flags. `loggedIn` takes precedence: an\n * authenticated shopper is always `loggedIn` regardless of `isIdentified`.\n */\nexport function deriveSessionState({\n loggedIn,\n isIdentified,\n}: {\n loggedIn: boolean;\n isIdentified: boolean;\n}): SessionState {\n if (loggedIn) return 'loggedIn';\n if (isIdentified) return 'identified';\n return 'anonymous';\n}\n\n// ---------------------------------------------------------------------------\n// Session context (read-only derived state + raw flags)\n// ---------------------------------------------------------------------------\n\nexport type SessionContextValue = Readonly<{\n /** Derived tri-state: anonymous | identified | loggedIn. */\n state: SessionState;\n /** Raw authenticated-session flag from `session.loggedIn`. */\n loggedIn: boolean;\n /** Raw edit-permission flag from `session.canEditData`; lets Controls render editable vs. masked. */\n canEditData: boolean;\n /** True while the Session entity has not yet resolved. */\n loading: boolean;\n}>;\n\nconst SESSION_DATA_UNSET = Symbol('SessionContextUnset');\n\nconst SessionContextInternal = createContext<SessionContextValue | typeof SESSION_DATA_UNSET>(SESSION_DATA_UNSET);\n\nexport const SessionContext = SessionContextInternal as React.Context<SessionContextValue>;\n\n/**\n * Read the derived session state and raw flags from the nearest `SessionRoot`. Throws when called\n * outside a `SessionRoot` subtree (a misplaced `useSession()` is a composition bug the author should\n * see immediately, not a silent degrade to anonymous), mirroring `useCart()` (Decision 6).\n *\n * While the Session entity is unresolved the context carries `loading: true` and `state:\n * 'anonymous'` (the least-privileged default); consumers that must not flash gate on `loading`.\n */\nexport function useSession(): SessionContextValue {\n const ctx = useContext(SessionContextInternal);\n if (ctx === SESSION_DATA_UNSET) {\n throw new Error('useSession() must be used inside a <SessionRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type SessionRootProps = {\n /**\n * Session entity slice forwarded by the reactive runtime (`ReactiveDataProvider`) — the value of\n * the `session` top-level field on `SessionData on Query`, not the wrapping `{ session: … }` shape.\n * Receives `null` while the entity has not yet resolved.\n */\n $data?: SessionEntity | null;\n children?: ReactNode;\n};\n\nexport type SessionGuardProps = {\n /** The session states under which the children render. Renders nothing for any other state. */\n when?: SessionState[];\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SessionRootDefinition = defineComponent<SessionRootProps>({\n name: 'SessionRoot',\n importPath: '@vtex/faststore/components',\n component: SessionRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Session Root', category: 'identity' },\n schema: {},\n providesContext: 'session',\n guideline:\n 'Read-only identity-state provider, normally the root of the shared layout so the header and every page it serves resolve the same session: reads the Session and broadcasts the anonymous/identified/logged-in tri-state plus the raw loggedIn and canEditData flags. Layout scope fetches once client-side and reuses the cache across navigation; page scope fetches fresh server-side on every request. Exactly one per rendered tree — a page must not add its own when the layout already hosts one. See session.md.',\n});\n\nexport const SessionGuardDefinition = defineComponent<SessionGuardProps>({\n name: 'SessionGuard',\n importPath: '@vtex/faststore/components',\n component: SessionGuard,\n editor: { label: 'Session Guard', category: 'identity' },\n schema: {\n when: { type: 'array', items: ['anonymous', 'identified', 'loggedIn'] as const, label: 'When State' },\n },\n requiresContext: 'session',\n guideline:\n 'Declarative gate: renders its children only when the current session state is one of the values in `when` (e.g. {\"when\":[\"identified\",\"loggedIn\"]}), else renders nothing. Use it in page or layout JSON to show or hide an identity subtree by state without writing the gate into a component. Must be inside SessionRoot, which may be hosted by the page or by its layout.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function SessionRoot({ $data: $dataProp, children }: SessionRootProps) {\n const $data = useDataOverride($dataProp);\n const session = $data ?? null;\n const loading = session == null;\n\n const value: SessionContextValue = useMemo(() => {\n const loggedIn = session?.loggedIn ?? false;\n const isIdentified = session?.isIdentified ?? false;\n return {\n state: deriveSessionState({ loggedIn, isIdentified }),\n loggedIn,\n canEditData: session?.canEditData ?? false,\n loading,\n };\n }, [session?.loggedIn, session?.isIdentified, session?.canEditData, loading]);\n\n return <SessionContextInternal value={value}>{children}</SessionContextInternal>;\n}\n\nexport function SessionGuard({ when, children }: SessionGuardProps) {\n const { state } = useSession();\n if (!when?.includes(state)) return null;\n return <>{children}</>;\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const Session = {\n Root: SessionRoot,\n Guard: SessionGuard,\n};\n"],"mappings":";;;;;;;;AAYA,MAAM,WAAW,QAAQ;;;;;;CAMxB;;;;;AAYD,SAAgB,mBAAmB,EACjC,UACA,gBAIe;CACf,IAAI,UAAU,OAAO;CACrB,IAAI,cAAc,OAAO;CACzB,OAAO;AACT;AAiBA,MAAM,qBAAqB,OAAO,qBAAqB;AAEvD,MAAM,yBAAyB,cAA+D,kBAAkB;AAEhH,MAAa,iBAAiB;;;;;;;;;AAU9B,SAAgB,aAAkC;CAChD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,QAAQ,oBACV,MAAM,IAAI,MAAM,2DAA2D;CAE7E,OAAO;AACT;AA0BA,MAAa,wBAAwB,gBAAkC;CACrE,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,QAAQ;EAAE,OAAO;EAAgB,UAAU;CAAW;CACtD,QAAQ,CAAC;CACT,iBAAiB;CACjB,WACE;AACJ,CAAC;AAED,MAAa,yBAAyB,gBAAmC;CACvE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,QAAQ;EAAE,OAAO;EAAiB,UAAU;CAAW;CACvD,QAAQ,EACN,MAAM;EAAE,MAAM;EAAS,OAAO;GAAC;GAAa;GAAc;EAAU;EAAY,OAAO;CAAa,EACtG;CACA,iBAAiB;CACjB,WACE;AACJ,CAAC;AAMD,SAAgB,YAAY,EAAE,OAAO,WAAW,YAA8B;CAE5E,MAAM,UADQ,gBAAgB,SACV,KAAK;CACzB,MAAM,UAAU,WAAW;CAE3B,MAAM,QAA6B,cAAc;EAC/C,MAAM,WAAW,SAAS,YAAY;EAEtC,OAAO;GACL,OAAO,mBAAmB;IAAE;IAAU,cAFnB,SAAS,gBAAgB;GAEO,CAAC;GACpD;GACA,aAAa,SAAS,eAAe;GACrC;EACF;CACF,GAAG;EAAC,SAAS;EAAU,SAAS;EAAc,SAAS;EAAa;CAAO,CAAC;CAE5E,OAAO,oBAAC,wBAAD;EAA+B;EAAQ;CAAiC,CAAA;AACjF;AAEA,SAAgB,aAAa,EAAE,MAAM,YAA+B;CAClE,MAAM,EAAE,UAAU,WAAW;CAC7B,IAAI,CAAC,MAAM,SAAS,KAAK,GAAG,OAAO;CACnC,OAAO,oBAAA,UAAA,EAAG,SAAW,CAAA;AACvB;AAMA,MAAa,UAAU;CACrB,MAAM;CACN,OAAO;AACT"}
1
+ {"version":3,"file":"session.js","names":[],"sources":["../../../src/matter/session/session.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { SessionEntity } from '../../sdk/session/operations.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — typed via @graphql-codegen client-preset\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment SessionData on Query {\n session {\n ...SessionFragment\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Tri-state derivation — the single home of the truth table (Decision 2).\n// ---------------------------------------------------------------------------\n\nexport type SessionState = 'anonymous' | 'identified' | 'loggedIn';\n\n/**\n * Derive the identity tri-state from the two raw session flags. `loggedIn` takes precedence: an\n * authenticated shopper is always `loggedIn` regardless of `isIdentified`.\n */\nexport function deriveSessionState({\n loggedIn,\n isIdentified,\n}: {\n loggedIn: boolean;\n isIdentified: boolean;\n}): SessionState {\n if (loggedIn) return 'loggedIn';\n if (isIdentified) return 'identified';\n return 'anonymous';\n}\n\n// ---------------------------------------------------------------------------\n// Session context (read-only derived state + raw flags)\n// ---------------------------------------------------------------------------\n\nexport type SessionContextValue = Readonly<{\n /** Derived tri-state: anonymous | identified | loggedIn. */\n state: SessionState;\n /** Raw authenticated-session flag from `session.loggedIn`. */\n loggedIn: boolean;\n /** Raw edit-permission flag from `session.canEditData`; lets Controls render editable vs. masked. */\n canEditData: boolean;\n /** True while the Session entity has not yet resolved. */\n loading: boolean;\n}>;\n\nconst SESSION_DATA_UNSET = Symbol('SessionContextUnset');\n\nconst SessionContextInternal = createContext<SessionContextValue | typeof SESSION_DATA_UNSET>(SESSION_DATA_UNSET);\n\nexport const SessionContext = SessionContextInternal as React.Context<SessionContextValue>;\n\n/**\n * Read the derived session state and raw flags from the nearest `SessionRoot`. Throws when called\n * outside a `SessionRoot` subtree (a misplaced `useSession()` is a composition bug the author should\n * see immediately, not a silent degrade to anonymous), mirroring `useCart()` (Decision 6).\n *\n * While the Session entity is unresolved the context carries `loading: true` and `state:\n * 'anonymous'` (the least-privileged default); consumers that must not flash gate on `loading`.\n */\nexport function useSession(): SessionContextValue {\n const ctx = useContext(SessionContextInternal);\n if (ctx === SESSION_DATA_UNSET) {\n throw new Error('useSession() must be used inside a <SessionRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type SessionRootProps = {\n /**\n * Session entity slice forwarded by the reactive runtime (`ReactiveDataProvider`) — the value of\n * the `session` top-level field on `SessionData on Query`, not the wrapping `{ session: … }` shape.\n * Receives `null` while the entity has not yet resolved.\n */\n $data?: SessionEntity | null;\n children?: ReactNode;\n};\n\nexport type SessionGuardProps = {\n /** The session states under which the children render. Renders nothing for any other state. */\n when?: SessionState[];\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const SessionRootDefinition = defineComponent<SessionRootProps>({\n name: 'SessionRoot',\n importPath: '@vtex/faststore/components',\n component: SessionRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Session Root', category: 'identity' },\n schema: {},\n providesContext: 'session',\n guideline:\n 'Read-only identity-state provider, normally the root of the shared layout so the header and every page it serves resolve the same session: reads the Session and broadcasts the anonymous/identified/logged-in tri-state plus the raw loggedIn and canEditData flags. Layout scope fetches once client-side and reuses the cache across navigation; page scope fetches fresh server-side on every request. Exactly one per rendered tree — a page must not add its own when the layout already hosts one. See session.md.',\n});\n\nexport const SessionGuardDefinition = defineComponent<SessionGuardProps>({\n name: 'SessionGuard',\n importPath: '@vtex/faststore/components',\n component: SessionGuard,\n editor: { label: 'Session Guard', category: 'identity' },\n schema: {\n when: { type: 'array', items: ['anonymous', 'identified', 'loggedIn'] as const, label: 'When State' },\n },\n requiresContext: 'session',\n guideline:\n 'Declarative gate: renders its children only when the current session state is one of the values in `when` (e.g. {\"when\":[\"identified\",\"loggedIn\"]}), else renders nothing. Use it in page or layout JSON to show or hide an identity subtree by state without writing the gate into a component. Must be inside SessionRoot, which may be hosted by the page or by its layout.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function SessionRoot({ $data: $dataProp, children }: SessionRootProps) {\n const $data = useDataOverride($dataProp);\n const session = $data ?? null;\n const loading = session == null;\n\n const value: SessionContextValue = useMemo(() => {\n const loggedIn = session?.loggedIn ?? false;\n const isIdentified = session?.isIdentified ?? false;\n return {\n state: deriveSessionState({ loggedIn, isIdentified }),\n loggedIn,\n canEditData: session?.canEditData ?? false,\n loading,\n };\n }, [session?.loggedIn, session?.isIdentified, session?.canEditData, loading]);\n\n return <SessionContextInternal value={value}>{children}</SessionContextInternal>;\n}\n\nexport function SessionGuard({ when, children }: SessionGuardProps) {\n const { state } = useSession();\n if (!when?.includes(state)) return null;\n return <>{children}</>;\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const Session = {\n Root: SessionRoot,\n Guard: SessionGuard,\n};\n"],"mappings":";;;;;;;;AAYA,MAAM,WAAW,QAAQ;;;;;;CAMxB;;;;;AAYD,SAAgB,mBAAmB,EACjC,UACA,gBAIe;CACf,IAAI,UAAU,OAAO;CACrB,IAAI,cAAc,OAAO;CACzB,OAAO;AACT;AAiBA,MAAM,qBAAqB,OAAO,qBAAqB;AAEvD,MAAM,yBAAyB,cAA+D,kBAAkB;AAEhH,MAAa,iBAAiB;;;;;;;;;AAU9B,SAAgB,aAAkC;CAChD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,QAAQ,oBACV,MAAM,IAAI,MAAM,2DAA2D;CAE7E,OAAO;AACT;AA0BA,MAAa,wBAAwB,gBAAkC;CACrE,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,QAAQ;EAAE,OAAO;EAAgB,UAAU;CAAW;CACtD,QAAQ,CAAC;CACT,iBAAiB;CACjB,WACE;AACJ,CAAC;AAED,MAAa,yBAAyB,gBAAmC;CACvE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,QAAQ;EAAE,OAAO;EAAiB,UAAU;CAAW;CACvD,QAAQ,EACN,MAAM;EAAE,MAAM;EAAS,OAAO;GAAC;GAAa;GAAc;EAAU;EAAY,OAAO;CAAa,EACtG;CACA,iBAAiB;CACjB,WACE;AACJ,CAAC;AAMD,SAAgB,YAAY,EAAE,OAAO,WAAW,YAA8B;CAE5E,MAAM,UADQ,gBAAgB,SACV,KAAK;CACzB,MAAM,UAAU,WAAW;CAE3B,MAAM,QAA6B,cAAc;EAC/C,MAAM,WAAW,SAAS,YAAY;EAEtC,OAAO;GACL,OAAO,mBAAmB;IAAE;IAAU,cAFnB,SAAS,gBAAgB;GAEO,CAAC;GACpD;GACA,aAAa,SAAS,eAAe;GACrC;EACF;CACF,GAAG;EAAC,SAAS;EAAU,SAAS;EAAc,SAAS;EAAa;CAAO,CAAC;CAE5E,OAAO,oBAAC,wBAAD;EAA+B;EAAQ;CAAiC,CAAA;AACjF;AAEA,SAAgB,aAAa,EAAE,MAAM,YAA+B;CAClE,MAAM,EAAE,UAAU,WAAW;CAC7B,IAAI,CAAC,MAAM,SAAS,KAAK,GAAG,OAAO;CACnC,OAAO,oBAAA,YAAA,EAAG,SAAW,CAAA;AACvB;AAMA,MAAa,UAAU;CACrB,MAAM;CACN,OAAO;AACT"}