@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
@@ -1 +1 @@
1
- {"version":3,"file":"cart-totalizer.js","names":[],"sources":["../../../src/matter/cart-totalizer/cart-totalizer.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext, useMemo } from 'react';\nimport type { Money, CartTotalizer as Totalizer } from '../../sdk/cart/operations.js';\nimport { useStorefrontContext } from '../../sdk/context/storefront-context.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport { CartTotalValueContext, formatPrice } from '../cart/cart.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — narrow cart slice (totalizers + total)\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment CartTotalizerData on Query {\n cart {\n __typename\n id\n totalizers {\n __typename\n id\n name\n value {\n ...MoneyFragment\n }\n }\n total {\n ...MoneyFragment\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Context — totalizers list, provided by CartTotalizerRoot for CartTotalizerLine\n// ---------------------------------------------------------------------------\n\ntype CartTotalizerContextValue = Readonly<{\n totalizers: readonly Totalizer[];\n}>;\n\nconst CART_TOTALIZER_UNSET = Symbol('CartTotalizerContextUnset');\n\nconst CartTotalizerContextInternal = createContext<CartTotalizerContextValue | typeof CART_TOTALIZER_UNSET>(\n CART_TOTALIZER_UNSET,\n);\n\nexport function useCartTotalizers(): CartTotalizerContextValue {\n const ctx = useContext(CartTotalizerContextInternal);\n if (ctx === CART_TOTALIZER_UNSET) {\n throw new Error('useCartTotalizers() must be used inside a <CartTotalizerRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type CartTotalizerRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Cart totalizer slice forwarded by the reactive runtime (`ReactiveDataProvider`) — it is the\n * value of the `cart` top-level field on `CartTotalizerData on Query`, not the wrapping\n * `{ cart: … }` shape. Receives `null` when the cart has not yet resolved.\n */\n $data?: { totalizers: readonly Totalizer[]; total: Money } | null;\n};\n\nexport type CartTotalizerLineProps = ComponentPropsWithRef<'div'> & {\n /** Which `cart.totalizers` entry to render, selected by `id` (e.g. \"Items\", \"Shipping\", \"Discounts\"). */\n totalizerId: string;\n /** Optional label override; defaults to the totalizer's `name`. */\n label?: string;\n /** Styling hook → `data-fs-cart-totalizer-line-variant`. The F9 discounts line uses `'discount'`. */\n variant?: 'default' | 'discount';\n /** Text rendered in place of the value when the amount is exactly zero (e.g. \"Free\" for shipping — Decision 5). */\n zeroLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const CartTotalizerRootDefinition = defineComponent<CartTotalizerRootProps>({\n name: 'CartTotalizerRoot',\n importPath: '@vtex/faststore/components',\n component: CartTotalizerRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Cart Totalizer Root', category: 'commerce' },\n schema: {},\n guideline:\n 'Focused reactive provider for cart totalizers + total (narrower than CartRoot); merges with CartRoot into one cart query when both are present on the page (e.g. CartRoot wrapping the mini-cart drawer, CartTotalizerRoot composing its footer). Use it to compose an order summary — in a mini-cart drawer footer or a checkout summary panel: wrap a CartTotalizerLine totalizerId=\"Items\" (subtotal), a CartTotalizerLine totalizerId=\"Discounts\" variant=\"discount\" (auto-hides at zero), a CartTotalizerLine totalizerId=\"Shipping\" zeroLabel=\"Grátis\", and a CartTotal for the grand total (set CartTotal label for a labeled total row). CartTotal is root-agnostic and reads the total from this root — there is no CartTotalizerTotal.',\n});\n\nexport const CartTotalizerLineDefinition = defineComponent<CartTotalizerLineProps>({\n name: 'CartTotalizerLine',\n importPath: '@vtex/faststore/components',\n component: CartTotalizerLine,\n editor: { label: 'Cart Totalizer Line', category: 'commerce' },\n schema: {\n totalizerId: { type: 'string', label: 'Totalizer Id' },\n label: { type: 'string', label: 'Label' },\n variant: { type: ['default', 'discount'] as const, default: 'default', label: 'Variant' },\n zeroLabel: { type: 'string', label: 'Zero Label' },\n },\n guideline:\n 'Renders one cart totalizer row (label + value) selected by totalizerId. The canonical order-summary lines are totalizerId=\"Items\" (the subtotal), totalizerId=\"Discounts\" (the discount), and totalizerId=\"Shipping\" (the shipping cost). For the discounts line set variant=\"discount\" — it hides itself when the value is zero (no active discount). The Core API returns a negative amount for discounts; the formatted value displays as-is (e.g. \"-R$ 40,00\"). Set zeroLabel=\"Grátis\" for the shipping line so a zero amount renders the label instead of R$ 0,00. A line whose totalizerId is absent from the cart renders nothing (no empty row). Must be inside CartTotalizerRoot.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function CartTotalizerRoot({ $data: $dataProp, className, children, ...props }: CartTotalizerRootProps) {\n const $data = useDataOverride($dataProp);\n\n const totalizerValue: CartTotalizerContextValue = useMemo(\n () => ({ totalizers: $data?.totalizers ?? [] }),\n [$data?.totalizers],\n );\n\n return (\n <CartTotalValueContext value={$data?.total ?? null}>\n <CartTotalizerContextInternal value={totalizerValue}>\n <div {...props} className={className} data-fs-cart-totalizer-root=\"\">\n {children}\n </div>\n </CartTotalizerContextInternal>\n </CartTotalValueContext>\n );\n}\n\nexport function CartTotalizerLine({\n totalizerId,\n label,\n variant = 'default',\n zeroLabel,\n className,\n children,\n ...props\n}: CartTotalizerLineProps) {\n const { totalizers } = useCartTotalizers();\n const { locale, currency } = useStorefrontContext();\n const totalizer = totalizers.find((t) => t.id === totalizerId);\n\n if (!totalizer) return null;\n\n const amount = totalizer.value.amount;\n\n if (variant === 'discount' && amount === 0) return null;\n\n const valueContent = amount === 0 && zeroLabel != null ? zeroLabel : formatPrice(amount, locale, currency);\n\n return (\n <div {...props} className={className} data-fs-cart-totalizer-line=\"\" data-fs-cart-totalizer-line-variant={variant}>\n <span>{label ?? totalizer.name}</span>\n <span>{children ?? valueContent}</span>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CartDiscountLine — itemized discount breakdown (F11)\n// ---------------------------------------------------------------------------\n\n// Owns its own `Cart.discounts` slice (ADR-0011 data ownership): the field is fetched only when this\n// leaf is placed and merges with any CartTotalizerRoot / CartRoot cart selection into one query. The\n// entries are the itemized companion to the aggregate \"Discounts\" totalizer and sum to it, so a store\n// composes this OR CartTotalizerLine totalizerId=\"Discounts\", never both.\nconst cartDiscountLinesFragment = graphql(`\n fragment CartDiscountLinesData on Query {\n cart {\n __typename\n id\n discounts {\n name\n value {\n ...MoneyFragment\n }\n kind\n }\n }\n }\n`);\n\nexport type CartDiscountKind = 'PROMOTION' | 'COUPON' | 'OTHER';\n\n/**\n * One cart-level discount line from `Cart.discounts`. `value` is the absolute amount discounted\n * (a positive `Money`); the negative sign is applied on render by the locale's currency format.\n * `amount` is typed `number` but is a string at runtime, so it is `Number()`-coerced before formatting.\n * `name` is null for the OTHER residual and for an unnamed promotion — the `otherLabel` prop names those.\n */\nexport type CartDiscountEntry = {\n name: string | null;\n value: { amount: number; currencyCode: string };\n kind: CartDiscountKind;\n};\n\n/** Cart slice forwarded by the reactive runtime — the value of the top-level `cart` field. */\ntype CartDiscountLinesSlice = { discounts: CartDiscountEntry[] };\n\nexport type CartDiscountLineProps = ComponentPropsWithRef<'div'> & {\n /** Label for lines the API cannot name: the OTHER residual and an unnamed featured promotion. */\n otherLabel?: string;\n /** Cart discount slice forwarded by the reactive runtime; injected/overridden by the runtime or in tests. */\n $data?: CartDiscountLinesSlice | null;\n};\n\nexport const CartDiscountLineDefinition = defineComponent<CartDiscountLineProps>({\n name: 'CartDiscountLine',\n importPath: '@vtex/faststore/components',\n component: CartDiscountLine,\n fragment: cartDiscountLinesFragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Cart Discount Line', category: 'commerce' },\n schema: {\n otherLabel: { type: 'string', default: 'Other discounts', label: 'Unnamed discount label' },\n },\n guideline:\n 'Itemized order-summary discounts: renders one line per entry in cart.discounts (one per promotion ' +\n 'and per coupon, plus a residual \"other\" line), each as label + negative amount. Renders nothing ' +\n 'when the cart has no discounts. This is the itemized alternative to CartTotalizerLine ' +\n 'totalizerId=\"Discounts\": the entries sum to that same aggregate, so compose this OR that line, ' +\n 'never both (both would double-count the same money). Place it in the order summary, typically ' +\n 'inside CartTotalizerRoot. Set otherLabel for the residual and any unnamed promotion. See cart-totalizer.md.',\n});\n\nexport function CartDiscountLine({\n otherLabel = 'Other discounts',\n className,\n $data: $dataProp,\n ...props\n}: CartDiscountLineProps) {\n const slice = useDataOverride<CartDiscountLinesSlice | null | undefined>($dataProp);\n const { locale, currency } = useStorefrontContext();\n const discounts = slice?.discounts;\n\n if (!discounts || discounts.length === 0) {\n return null;\n }\n\n return (\n <div {...props} className={className} data-fs-cart-discount-lines=\"\">\n {discounts.map((discount, index) => {\n const amount = Number(discount.value.amount);\n const label = discount.name ?? otherLabel;\n\n return (\n <div\n key={`${discount.kind}-${discount.name}-${index}`}\n data-fs-cart-discount-line=\"\"\n data-fs-cart-discount-line-kind={discount.kind.toLowerCase()}\n data-fs-text=\"\"\n >\n <span data-fs-cart-discount-line-label=\"\">{label}</span>\n {/* Negate the positive magnitude and let the currency format sign it — no hardcoded glyph. */}\n <span data-fs-cart-discount-line-value=\"\">{formatPrice(-amount, locale, currency)}</span>\n </div>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CartMarkdownLine — aggregate item-level catalog markdown (F11 scenario 1)\n// ---------------------------------------------------------------------------\n\n// Renders Cart.itemMarkdownTotal — Σ(listPrice − price) × qty across non-gift lines, computed\n// server-side — as one negative summary line, self-hiding when zero. This is the catalog \"de/por\"\n// markdown that sits ABOVE the Items subtotal, so it is DISTINCT money from the cart-level promotions\n// on the Discounts totalizer / CartDiscountLine (price − sellingPrice): the two coexist in a summary,\n// they do not substitute. Owns its own slice (ADR-0011): the field is fetched only when this leaf is\n// placed and merges with any CartTotalizerRoot / CartRoot cart selection into one query.\nconst cartMarkdownFragment = graphql(`\n fragment CartMarkdownData on Query {\n cart {\n __typename\n id\n itemMarkdownTotal {\n ...MoneyFragment\n }\n }\n }\n`);\n\n/** Cart slice forwarded by the reactive runtime — the value of the top-level `cart` field. */\ntype CartMarkdownSlice = { itemMarkdownTotal: { amount: number; currencyCode: string } };\n\nexport type CartMarkdownLineProps = ComponentPropsWithRef<'div'> & {\n /** Row label for the aggregate item-level markdown. Interim operational default until the i18n catalog lands. */\n label?: string;\n /** Cart markdown slice forwarded by the reactive runtime; injected/overridden by the runtime or in tests. */\n $data?: CartMarkdownSlice | null;\n};\n\nexport const CartMarkdownLineDefinition = defineComponent<CartMarkdownLineProps>({\n name: 'CartMarkdownLine',\n importPath: '@vtex/faststore/components',\n component: CartMarkdownLine,\n fragment: cartMarkdownFragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Cart Markdown Line', category: 'commerce' },\n schema: {\n label: { type: 'string', default: 'Item discounts', label: 'Markdown label' },\n },\n guideline:\n 'Aggregate item-level catalog markdown: renders Cart.itemMarkdownTotal (the sum of listPrice − price ' +\n 'across the cart, the \"de/por\" catalog savings) as a single negative line. Renders nothing when the ' +\n 'cart has no markdown. This is DISTINCT money from CartDiscountLine and CartTotalizerLine ' +\n 'totalizerId=\"Discounts\" (those are cart-level promotions/coupons, price − sellingPrice) — it may be ' +\n 'shown ALONGSIDE them, not instead of them, and does not double-count. Place it in the order summary, ' +\n 'typically inside CartTotalizerRoot. Set label to word the row. See cart-totalizer.md.',\n});\n\nexport function CartMarkdownLine({\n label = 'Item discounts',\n className,\n $data: $dataProp,\n ...props\n}: CartMarkdownLineProps) {\n const slice = useDataOverride<CartMarkdownSlice | null | undefined>($dataProp);\n const { locale, currency } = useStorefrontContext();\n // `amount` is a string at runtime; coerce before checking/formatting. A zero markdown self-hides.\n const amount = Number(slice?.itemMarkdownTotal?.amount ?? 0);\n\n if (!amount) {\n return null;\n }\n\n return (\n <div {...props} className={className} data-fs-cart-markdown-line=\"\" data-fs-text=\"\">\n <span data-fs-cart-markdown-line-label=\"\">{label}</span>\n {/* Negate the positive magnitude and let the currency format sign it — no hardcoded glyph. */}\n <span data-fs-cart-markdown-line-value=\"\">{formatPrice(-amount, locale, currency)}</span>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cart-totalizer.js","names":[],"sources":["../../../src/matter/cart-totalizer/cart-totalizer.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext, useMemo } from 'react';\nimport type { Money, CartTotalizer as Totalizer } from '../../sdk/cart/operations.js';\nimport { useStorefrontContext } from '../../sdk/context/storefront-context.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport { CartTotalValueContext, formatPrice, isFiniteAmount } from '../cart/cart.js';\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — narrow cart slice (totalizers + total)\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment CartTotalizerData on Query {\n cart {\n __typename\n id\n totalizers {\n __typename\n id\n name\n value {\n ...MoneyFragment\n }\n }\n total {\n ...MoneyFragment\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Context — totalizers list, provided by CartTotalizerRoot for CartTotalizerLine\n// ---------------------------------------------------------------------------\n\ntype CartTotalizerContextValue = Readonly<{\n totalizers: readonly Totalizer[];\n}>;\n\nconst CART_TOTALIZER_UNSET = Symbol('CartTotalizerContextUnset');\n\nconst CartTotalizerContextInternal = createContext<CartTotalizerContextValue | typeof CART_TOTALIZER_UNSET>(\n CART_TOTALIZER_UNSET,\n);\n\nexport function useCartTotalizers(): CartTotalizerContextValue {\n const ctx = useContext(CartTotalizerContextInternal);\n if (ctx === CART_TOTALIZER_UNSET) {\n throw new Error('useCartTotalizers() must be used inside a <CartTotalizerRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type CartTotalizerRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Cart totalizer slice forwarded by the reactive runtime (`ReactiveDataProvider`) — it is the\n * value of the `cart` top-level field on `CartTotalizerData on Query`, not the wrapping\n * `{ cart: … }` shape. Receives `null` when the cart has not yet resolved.\n */\n $data?: { totalizers: readonly Totalizer[]; total: Money } | null;\n};\n\nexport type CartTotalizerLineProps = ComponentPropsWithRef<'div'> & {\n /** Which `cart.totalizers` entry to render, selected by `id` (e.g. \"Items\", \"Shipping\", \"Discounts\"). */\n totalizerId: string;\n /** Optional label override; defaults to the totalizer's `name`. */\n label?: string;\n /** Styling hook → `data-fs-cart-totalizer-line-variant`. The F9 discounts line uses `'discount'`. */\n variant?: 'default' | 'discount';\n /** Text rendered in place of the value when the amount is exactly zero (e.g. \"Free\" for shipping — Decision 5). */\n zeroLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const CartTotalizerRootDefinition = defineComponent<CartTotalizerRootProps>({\n name: 'CartTotalizerRoot',\n importPath: '@vtex/faststore/components',\n component: CartTotalizerRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Cart Totalizer Root', category: 'commerce' },\n schema: {},\n guideline:\n 'Focused reactive provider for cart totalizers + total (narrower than CartRoot); merges with CartRoot into one cart query when both are present on the page (e.g. CartRoot wrapping the mini-cart drawer, CartTotalizerRoot composing its footer). Use it to compose an order summary — in a mini-cart drawer footer or a checkout summary panel: wrap a CartTotalizerLine totalizerId=\"Items\" (subtotal), a CartTotalizerLine totalizerId=\"Discounts\" variant=\"discount\" (auto-hides at zero), a CartTotalizerLine totalizerId=\"Shipping\" zeroLabel=\"Grátis\", and a CartTotal for the grand total (set CartTotal label for a labeled total row). CartTotal is root-agnostic and reads the total from this root — there is no CartTotalizerTotal.',\n});\n\nexport const CartTotalizerLineDefinition = defineComponent<CartTotalizerLineProps>({\n name: 'CartTotalizerLine',\n importPath: '@vtex/faststore/components',\n component: CartTotalizerLine,\n editor: { label: 'Cart Totalizer Line', category: 'commerce' },\n schema: {\n totalizerId: { type: 'string', label: 'Totalizer Id' },\n label: { type: 'string', label: 'Label' },\n variant: { type: ['default', 'discount'] as const, default: 'default', label: 'Variant' },\n zeroLabel: { type: 'string', label: 'Zero Label' },\n },\n guideline:\n 'Renders one cart totalizer row (label + value) selected by totalizerId. The canonical order-summary lines are totalizerId=\"Items\" (the subtotal), totalizerId=\"Discounts\" (the discount), and totalizerId=\"Shipping\" (the shipping cost). For the discounts line set variant=\"discount\" — it hides itself when the value is zero (no active discount). The Core API returns a negative amount for discounts; the formatted value displays as-is (e.g. \"-R$ 40,00\"). Set zeroLabel=\"Grátis\" for the shipping line so a zero amount renders the label instead of R$ 0,00. A line whose totalizerId is absent from the cart renders nothing (no empty row). Must be inside CartTotalizerRoot.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function CartTotalizerRoot({ $data: $dataProp, className, children, ...props }: CartTotalizerRootProps) {\n const $data = useDataOverride($dataProp);\n\n const totalizerValue: CartTotalizerContextValue = useMemo(\n () => ({ totalizers: $data?.totalizers ?? [] }),\n [$data?.totalizers],\n );\n\n return (\n <CartTotalValueContext value={$data?.total ?? null}>\n <CartTotalizerContextInternal value={totalizerValue}>\n <div {...props} className={className} data-fs-cart-totalizer-root=\"\">\n {children}\n </div>\n </CartTotalizerContextInternal>\n </CartTotalValueContext>\n );\n}\n\nexport function CartTotalizerLine({\n totalizerId,\n label,\n variant = 'default',\n zeroLabel,\n className,\n children,\n ...props\n}: CartTotalizerLineProps) {\n const { totalizers } = useCartTotalizers();\n const { locale, currency } = useStorefrontContext();\n const totalizer = totalizers.find((t) => t.id === totalizerId);\n\n if (!totalizer) return null;\n\n if (!isFiniteAmount(totalizer.value.amount)) return null;\n\n const amount = Number(totalizer.value.amount);\n\n if (variant === 'discount' && amount === 0) return null;\n\n const valueContent = amount === 0 && zeroLabel != null ? zeroLabel : formatPrice(amount, locale, currency);\n\n return (\n <div {...props} className={className} data-fs-cart-totalizer-line=\"\" data-fs-cart-totalizer-line-variant={variant}>\n <span>{label ?? totalizer.name}</span>\n <span>{children ?? valueContent}</span>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CartDiscountLine — itemized discount breakdown (F11)\n// ---------------------------------------------------------------------------\n\n// Owns its own `Cart.discounts` slice (ADR-0011 data ownership): the field is fetched only when this\n// leaf is placed and merges with any CartTotalizerRoot / CartRoot cart selection into one query. The\n// entries are the itemized companion to the aggregate \"Discounts\" totalizer and sum to it, so a store\n// composes this OR CartTotalizerLine totalizerId=\"Discounts\", never both.\nconst cartDiscountLinesFragment = graphql(`\n fragment CartDiscountLinesData on Query {\n cart {\n __typename\n id\n discounts {\n name\n value {\n ...MoneyFragment\n }\n kind\n }\n }\n }\n`);\n\nexport type CartDiscountKind = 'PROMOTION' | 'COUPON' | 'OTHER';\n\n/**\n * One cart-level discount line from `Cart.discounts`. `value` is the absolute amount discounted\n * (a positive `Money`); the negative sign is applied on render by the locale's currency format.\n * `amount` is typed `number` but is a string at runtime, so it is `Number()`-coerced before formatting.\n * `name` is null for the OTHER residual and for an unnamed promotion — the `otherLabel` prop names those.\n */\nexport type CartDiscountEntry = {\n name: string | null;\n value: { amount: number; currencyCode: string };\n kind: CartDiscountKind;\n};\n\n/** Cart slice forwarded by the reactive runtime — the value of the top-level `cart` field. */\ntype CartDiscountLinesSlice = { discounts: CartDiscountEntry[] };\n\nexport type CartDiscountLineProps = ComponentPropsWithRef<'div'> & {\n /** Label for lines the API cannot name: the OTHER residual and an unnamed featured promotion. */\n otherLabel?: string;\n /** Cart discount slice forwarded by the reactive runtime; injected/overridden by the runtime or in tests. */\n $data?: CartDiscountLinesSlice | null;\n};\n\nexport const CartDiscountLineDefinition = defineComponent<CartDiscountLineProps>({\n name: 'CartDiscountLine',\n importPath: '@vtex/faststore/components',\n component: CartDiscountLine,\n fragment: cartDiscountLinesFragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Cart Discount Line', category: 'commerce' },\n schema: {\n otherLabel: { type: 'string', default: 'Other discounts', label: 'Unnamed discount label' },\n },\n guideline:\n 'Itemized order-summary discounts: renders one line per entry in cart.discounts (one per promotion ' +\n 'and per coupon, plus a residual \"other\" line), each as label + negative amount. Renders nothing ' +\n 'when the cart has no discounts. This is the itemized alternative to CartTotalizerLine ' +\n 'totalizerId=\"Discounts\": the entries sum to that same aggregate, so compose this OR that line, ' +\n 'never both (both would double-count the same money). Place it in the order summary, typically ' +\n 'inside CartTotalizerRoot. Set otherLabel for the residual and any unnamed promotion. See cart-totalizer.md.',\n});\n\nexport function CartDiscountLine({\n otherLabel = 'Other discounts',\n className,\n $data: $dataProp,\n ...props\n}: CartDiscountLineProps) {\n const slice = useDataOverride<CartDiscountLinesSlice | null | undefined>($dataProp);\n const { locale, currency } = useStorefrontContext();\n const discounts = slice?.discounts;\n\n if (!discounts || discounts.length === 0) {\n return null;\n }\n\n return (\n <div {...props} className={className} data-fs-cart-discount-lines=\"\">\n {discounts.map((discount, index) => {\n const amount = Number(discount.value.amount);\n const label = discount.name ?? otherLabel;\n\n return (\n <div\n key={`${discount.kind}-${discount.name}-${index}`}\n data-fs-cart-discount-line=\"\"\n data-fs-cart-discount-line-kind={discount.kind.toLowerCase()}\n data-fs-text=\"\"\n >\n <span data-fs-cart-discount-line-label=\"\">{label}</span>\n {/* Negate the positive magnitude and let the currency format sign it — no hardcoded glyph. */}\n <span data-fs-cart-discount-line-value=\"\">{formatPrice(-amount, locale, currency)}</span>\n </div>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CartMarkdownLine — aggregate item-level catalog markdown (F11 scenario 1)\n// ---------------------------------------------------------------------------\n\n// Renders Cart.itemMarkdownTotal — Σ(listPrice − price) × qty across non-gift lines, computed\n// server-side — as one negative summary line, self-hiding when zero. This is the catalog \"de/por\"\n// markdown that sits ABOVE the Items subtotal, so it is DISTINCT money from the cart-level promotions\n// on the Discounts totalizer / CartDiscountLine (price − sellingPrice): the two coexist in a summary,\n// they do not substitute. Owns its own slice (ADR-0011): the field is fetched only when this leaf is\n// placed and merges with any CartTotalizerRoot / CartRoot cart selection into one query.\nconst cartMarkdownFragment = graphql(`\n fragment CartMarkdownData on Query {\n cart {\n __typename\n id\n itemMarkdownTotal {\n ...MoneyFragment\n }\n }\n }\n`);\n\n/** Cart slice forwarded by the reactive runtime — the value of the top-level `cart` field. */\ntype CartMarkdownSlice = { itemMarkdownTotal: { amount: number; currencyCode: string } };\n\nexport type CartMarkdownLineProps = ComponentPropsWithRef<'div'> & {\n /** Row label for the aggregate item-level markdown. Interim operational default until the i18n catalog lands. */\n label?: string;\n /** Cart markdown slice forwarded by the reactive runtime; injected/overridden by the runtime or in tests. */\n $data?: CartMarkdownSlice | null;\n};\n\nexport const CartMarkdownLineDefinition = defineComponent<CartMarkdownLineProps>({\n name: 'CartMarkdownLine',\n importPath: '@vtex/faststore/components',\n component: CartMarkdownLine,\n fragment: cartMarkdownFragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Cart Markdown Line', category: 'commerce' },\n schema: {\n label: { type: 'string', default: 'Item discounts', label: 'Markdown label' },\n },\n guideline:\n 'Aggregate item-level catalog markdown: renders Cart.itemMarkdownTotal (the sum of listPrice − price ' +\n 'across the cart, the \"de/por\" catalog savings) as a single negative line. Renders nothing when the ' +\n 'cart has no markdown. This is DISTINCT money from CartDiscountLine and CartTotalizerLine ' +\n 'totalizerId=\"Discounts\" (those are cart-level promotions/coupons, price − sellingPrice) — it may be ' +\n 'shown ALONGSIDE them, not instead of them, and does not double-count. Place it in the order summary, ' +\n 'typically inside CartTotalizerRoot. Set label to word the row. See cart-totalizer.md.',\n});\n\nexport function CartMarkdownLine({\n label = 'Item discounts',\n className,\n $data: $dataProp,\n ...props\n}: CartMarkdownLineProps) {\n const slice = useDataOverride<CartMarkdownSlice | null | undefined>($dataProp);\n const { locale, currency } = useStorefrontContext();\n // `amount` is a string at runtime; coerce before checking/formatting. A zero markdown self-hides.\n const amount = Number(slice?.itemMarkdownTotal?.amount ?? 0);\n\n if (!amount) {\n return null;\n }\n\n return (\n <div {...props} className={className} data-fs-cart-markdown-line=\"\" data-fs-text=\"\">\n <span data-fs-cart-markdown-line-label=\"\">{label}</span>\n {/* Negate the positive magnitude and let the currency format sign it — no hardcoded glyph. */}\n <span data-fs-cart-markdown-line-value=\"\">{formatPrice(-amount, locale, currency)}</span>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAcA,MAAM,WAAW,QAAQ;;;;;;;;;;;;;;;;;;CAkBxB;AAUD,MAAM,uBAAuB,OAAO,2BAA2B;AAE/D,MAAM,+BAA+B,cACnC,oBACF;AAEA,SAAgB,oBAA+C;CAC7D,MAAM,MAAM,WAAW,4BAA4B;CACnD,IAAI,QAAQ,sBACV,MAAM,IAAI,MAAM,wEAAwE;CAE1F,OAAO;AACT;AA8BA,MAAa,8BAA8B,gBAAwC;CACjF,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,QAAQ;EAAE,OAAO;EAAuB,UAAU;CAAW;CAC7D,QAAQ,CAAC;CACT,WACE;AACJ,CAAC;AAED,MAAa,8BAA8B,gBAAwC;CACjF,MAAM;CACN,YAAY;CACZ,WAAW;CACX,QAAQ;EAAE,OAAO;EAAuB,UAAU;CAAW;CAC7D,QAAQ;EACN,aAAa;GAAE,MAAM;GAAU,OAAO;EAAe;EACrD,OAAO;GAAE,MAAM;GAAU,OAAO;EAAQ;EACxC,SAAS;GAAE,MAAM,CAAC,WAAW,UAAU;GAAY,SAAS;GAAW,OAAO;EAAU;EACxF,WAAW;GAAE,MAAM;GAAU,OAAO;EAAa;CACnD;CACA,WACE;AACJ,CAAC;AAMD,SAAgB,kBAAkB,EAAE,OAAO,WAAW,WAAW,UAAU,GAAG,SAAiC;CAC7G,MAAM,QAAQ,gBAAgB,SAAS;CAEvC,MAAM,iBAA4C,eACzC,EAAE,YAAY,OAAO,cAAc,CAAC,EAAE,IAC7C,CAAC,OAAO,UAAU,CACpB;CAEA,OACE,oBAAC,uBAAD;EAAuB,OAAO,OAAO,SAAS;EAC5C,UAAA,oBAAC,8BAAD;GAA8B,OAAO;GACnC,UAAA,oBAAC,OAAD;IAAK,GAAI;IAAkB;IAAW,+BAA4B;IAC/D;GACE,CAAA;EACuB,CAAA;CACT,CAAA;AAE3B;AAEA,SAAgB,kBAAkB,EAChC,aACA,OACA,UAAU,WACV,WACA,WACA,UACA,GAAG,SACsB;CACzB,MAAM,EAAE,eAAe,kBAAkB;CACzC,MAAM,EAAE,QAAQ,aAAa,qBAAqB;CAClD,MAAM,YAAY,WAAW,MAAM,MAAM,EAAE,OAAO,WAAW;CAE7D,IAAI,CAAC,WAAW,OAAO;CAEvB,IAAI,CAAC,eAAe,UAAU,MAAM,MAAM,GAAG,OAAO;CAEpD,MAAM,SAAS,OAAO,UAAU,MAAM,MAAM;CAE5C,IAAI,YAAY,cAAc,WAAW,GAAG,OAAO;CAEnD,MAAM,eAAe,WAAW,KAAK,aAAa,OAAO,YAAY,YAAY,QAAQ,QAAQ,QAAQ;CAEzG,OACE,qBAAC,OAAD;EAAK,GAAI;EAAkB;EAAW,+BAA4B;EAAG,uCAAqC;EAA1G,UAAA,CACE,oBAAC,QAAD,EAAA,UAAO,SAAS,UAAU,KAAW,CAAA,GACrC,oBAAC,QAAD,EAAA,UAAO,YAAY,aAAmB,CAAA,CACnC;;AAET;AAUA,MAAM,4BAA4B,QAAQ;;;;;;;;;;;;;;CAczC;AA0BD,MAAa,6BAA6B,gBAAuC;CAC/E,MAAM;CACN,YAAY;CACZ,WAAW;CACX,UAAU;CACV,eAAe;CACf,QAAQ;EAAE,OAAO;EAAsB,UAAU;CAAW;CAC5D,QAAQ,EACN,YAAY;EAAE,MAAM;EAAU,SAAS;EAAmB,OAAO;CAAyB,EAC5F;CACA,WACE;AAMJ,CAAC;AAED,SAAgB,iBAAiB,EAC/B,aAAa,mBACb,WACA,OAAO,WACP,GAAG,SACqB;CACxB,MAAM,QAAQ,gBAA2D,SAAS;CAClF,MAAM,EAAE,QAAQ,aAAa,qBAAqB;CAClD,MAAM,YAAY,OAAO;CAEzB,IAAI,CAAC,aAAa,UAAU,WAAW,GACrC,OAAO;CAGT,OACE,oBAAC,OAAD;EAAK,GAAI;EAAkB;EAAW,+BAA4B;EAC/D,UAAA,UAAU,KAAK,UAAU,UAAU;GAClC,MAAM,SAAS,OAAO,SAAS,MAAM,MAAM;GAC3C,MAAM,QAAQ,SAAS,QAAQ;GAE/B,OACE,qBAAC,OAAD;IAEE,8BAA2B;IAC3B,mCAAiC,SAAS,KAAK,YAAY;IAC3D,gBAAa;IAJf,UAAA,CAME,oBAAC,QAAD;KAAM,oCAAiC;KAAI,UAAA;IAAY,CAAA,GAEvD,oBAAC,QAAD;KAAM,oCAAiC;KAAI,UAAA,YAAY,CAAC,QAAQ,QAAQ,QAAQ;IAAQ,CAAA,CACrF;GARE,GAAA,GAAG,SAAS,KAAK,GAAG,SAAS,KAAK,GAAG,OAQvC;EAET,CAAC;CACE,CAAA;AAET;AAYA,MAAM,uBAAuB,QAAQ;;;;;;;;;;CAUpC;AAYD,MAAa,6BAA6B,gBAAuC;CAC/E,MAAM;CACN,YAAY;CACZ,WAAW;CACX,UAAU;CACV,eAAe;CACf,QAAQ;EAAE,OAAO;EAAsB,UAAU;CAAW;CAC5D,QAAQ,EACN,OAAO;EAAE,MAAM;EAAU,SAAS;EAAkB,OAAO;CAAiB,EAC9E;CACA,WACE;AAMJ,CAAC;AAED,SAAgB,iBAAiB,EAC/B,QAAQ,kBACR,WACA,OAAO,WACP,GAAG,SACqB;CACxB,MAAM,QAAQ,gBAAsD,SAAS;CAC7E,MAAM,EAAE,QAAQ,aAAa,qBAAqB;CAElD,MAAM,SAAS,OAAO,OAAO,mBAAmB,UAAU,CAAC;CAE3D,IAAI,CAAC,QACH,OAAO;CAGT,OACE,qBAAC,OAAD;EAAK,GAAI;EAAkB;EAAW,8BAA2B;EAAG,gBAAa;EAAjF,UAAA,CACE,oBAAC,QAAD;GAAM,oCAAiC;GAAI,UAAA;EAAY,CAAA,GAEvD,oBAAC,QAAD;GAAM,oCAAiC;GAAI,UAAA,YAAY,CAAC,QAAQ,QAAQ,QAAQ;EAAQ,CAAA,CACrF;;AAET"}
@@ -6,11 +6,11 @@ import { Field, FieldBinding, FieldsetFieldContext, useFieldsetField, useFieldse
6
6
  import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
7
7
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
8
8
  import { useSession } from "../session/session.js";
9
+ import { useFormRegistry } from "../../particles/form/form.js";
9
10
  import { Icon } from "../../particles/icon/icon.js";
10
11
  import { useEntityDataResolved } from "../../sdk/data/entity-resolution-context.js";
11
12
  import { useReactiveDataSettled } from "../../sdk/data/use-reactive-data-settled.js";
12
13
  import { Fieldset } from "../../particles/fieldset/fieldset.js";
13
- import { useFormRegistry } from "../../particles/form/form.js";
14
14
  import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
15
15
  import { jsx, jsxs } from "react/jsx-runtime";
16
16
  import { useFormContext } from "react-hook-form";
@@ -5,14 +5,14 @@ import { Field, FieldBinding, FieldsetFieldContext, useFieldsetField, useFieldse
5
5
  import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
6
6
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
7
7
  import { useSession } from "../session/session.js";
8
+ import { useFormRegistry } from "../../particles/form/form.js";
8
9
  import { useEntityDataResolved } from "../../sdk/data/entity-resolution-context.js";
9
10
  import { useReactiveDataSettled } from "../../sdk/data/use-reactive-data-settled.js";
10
11
  import { Fieldset } from "../../particles/fieldset/fieldset.js";
11
12
  import { DOCUMENTS, DocumentField, isSupportedDocumentType } from "../../particles/document-field/document-field.js";
12
- import { useFormRegistry } from "../../particles/form/form.js";
13
13
  import { PhoneField, toE164 } from "../../particles/phone-field/phone-field.js";
14
14
  import { createContext, useCallback, useContext, useEffect, useMemo, 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
  import { useFormContext } from "react-hook-form";
17
17
  //#region src/matter/client-profile-form/client-profile-fieldset.tsx
18
18
  /**
@@ -284,7 +284,7 @@ function ClientProfileFieldset({ country = "US", documentType, skeletonCount = C
284
284
  return update(payload);
285
285
  }, []);
286
286
  const toPayload = useCallback((values) => {
287
- const { country: activeCountry, getFieldState } = latest.current;
287
+ const { country: activeCountry, documentType: activeDocumentType, getFieldState } = latest.current;
288
288
  const isDirty = (key) => getFieldState(`clientProfile.${key}`).isDirty;
289
289
  const payload = {};
290
290
  if ("firstName" in values && isDirty("firstName")) payload.firstName = String(values.firstName ?? "").trim();
@@ -293,7 +293,10 @@ function ClientProfileFieldset({ country = "US", documentType, skeletonCount = C
293
293
  const raw = String(values.phone ?? "").trim();
294
294
  payload.phone = raw ? toE164(raw, activeCountry) : "";
295
295
  }
296
- if ("document" in values && isDirty("document")) payload.document = String(values.document ?? "").replace(/\D/g, "");
296
+ if ("document" in values && isDirty("document") && isSupportedDocumentType(activeDocumentType)) {
297
+ const rawDocument = typeof values.document === "string" ? values.document : "";
298
+ payload.document = DOCUMENTS[activeDocumentType].sanitize(rawDocument);
299
+ }
297
300
  return payload;
298
301
  }, []);
299
302
  useEffect(() => registerFieldset({
@@ -382,7 +385,7 @@ function ProfileTextControl({ fieldKey, attr, autoComplete, defaultLabel, label,
382
385
  children: /* @__PURE__ */ jsx(Field.Root, {
383
386
  className,
384
387
  "data-fs-client-profile-fieldset-field": attr,
385
- children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
388
+ children: children ?? /* @__PURE__ */ jsxs(Fragment$1, { children: [
386
389
  /* @__PURE__ */ jsx(Field.Label, {}),
387
390
  /* @__PURE__ */ jsx(Field.Control, {}),
388
391
  /* @__PURE__ */ jsx(Field.Error, {})
@@ -1 +1 @@
1
- {"version":3,"file":"client-profile-fieldset.js","names":[],"sources":["../../../src/matter/client-profile-form/client-profile-fieldset.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 useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport type { DocumentType } from '../../particles/document-field/document-field.js';\nimport { DOCUMENTS, DocumentField, isSupportedDocumentType } from '../../particles/document-field/document-field.js';\nimport type { FieldRule } from '../../particles/field/field.js';\nimport {\n Field,\n FieldBinding,\n FieldsetFieldContext,\n useFieldsetField,\n useFieldsetFieldNames,\n useNoAncestorFieldset,\n} from '../../particles/field/field.js';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { useFormRegistry } from '../../particles/form/form.js';\nimport type { PhoneCountry } from '../../particles/phone-field/phone-field.js';\nimport { PhoneField, toE164 } from '../../particles/phone-field/phone-field.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 { useEntityDataResolved } from '../../sdk/data/entity-resolution-context.js';\nimport { useReactiveDataSettled } from '../../sdk/data/use-reactive-data-settled.js';\nimport type { ClientProfileUpdateInput } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * `ClientProfileFieldset` — the composable, one-Save write of the `ClientProfile` sibling entity\n * (RFC: Composable Entity Forms, `Proposal > Design > The fieldset`). It registers itself with the\n * enclosing `FormRoot` under the `clientProfile` namespace and dispatches\n * `$actions.identity.clientProfileUpdate` for its own submit slot; it renders no `<form>` of its own.\n *\n * The four controls below are the smallest placeable unit: each calls `useFieldsetField` for its full\n * field name, renders a `FieldBinding` carrying the field's static facts, and — inside it — either the\n * default anatomy or the author's own children. No control's field contract carries a blocking\n * `RegisterOptions` key (no `required`, `min`, `max`, `minLength`, `maxLength`, `pattern`); requiredness\n * on the phone control rides on its own `requirement` prop instead, which only ever gates required\n * validation — a field the store does not want is omitted from the page, not switched off by a prop.\n *\n * The fieldset's context name is `client-profile-fieldset`, not `client-profile`: the shipped\n * `ClientProfilePhoneControl` (`matter/client-profile/client-profile.tsx`) already requires\n * `client-profile` and writes on blur, so reusing that name here would let an on-blur control compile\n * inside this batched fieldset and write immediately. `ClientProfileFieldsetPhoneControl` carries the\n * same container prefix as the context it requires, naming the collision rather than hiding it.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on the fieldset, its own data root\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientProfileFieldsetData on Query {\n clientProfile {\n __typename\n id\n firstName\n lastName\n phone\n document\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the ClientProfile sibling entity, unwrapped. */\nexport type ClientProfileFieldsetData = {\n firstName?: string | null;\n lastName?: string | null;\n phone?: string | null;\n document?: string | null;\n} | null;\n\n/** The four keys this fieldset registers under its namespace. */\ntype ProfileFieldKey = 'firstName' | 'lastName' | 'phone' | 'document';\n\n/** What the fieldset publishes to its controls. Carries no live value — react-hook-form owns those. */\ntype ClientProfileFieldsetContextValue = Readonly<{\n namespace: 'clientProfile';\n /** Seeds, resolved from `$data`. Empty string, never null, so a control never seeds `undefined`. */\n saved: Readonly<Record<ProfileFieldKey, string>>;\n /** True when the session reports `canEditData: false`. */\n readOnly: boolean;\n /** Phone mask/validation country, from the fieldset's own prop. */\n country: PhoneCountry;\n /** Locale document type; the document control renders only when supported. */\n documentType: string | undefined;\n /** The fieldset's own validation rules, per key. Present and empty today (Decision 6). */\n rules: Readonly<Partial<Record<ProfileFieldKey, FieldRule>>>;\n}>;\n\n/** No control publishes a rule today — a blocking `RegisterOptions` key would reopen the same submit\n * block for a read-only session (Decision 6): `readOnly` disables a control's rendered `<input>` via a\n * plain HTML attribute, never react-hook-form's own `disabled` registration option (`field.tsx`'s\n * `useController` call takes no `disabled`), so a required field would stay fully registered and\n * validated while the shopper has no way to focus or edit it. Kept as a stable empty object so the\n * context value's identity does not churn on every render. */\nconst NO_RULES: Readonly<Partial<Record<ProfileFieldKey, FieldRule>>> = {};\n\n/**\n * Rows `ClientProfileFieldsetSkeleton` draws when nobody says otherwise: one per control in the full\n * anatomy — first name, last name, phone, document. It is a default and not a derivation, because\n * the composed children are opaque here. A page that composes fewer sets `skeletonCount` to what it\n * composed, or the form jumps by the difference when the controls take the skeleton's place.\n */\nconst CLIENT_PROFILE_SKELETON_ROWS = 4;\n\nconst CLIENT_PROFILE_FIELDSET_UNSET = Symbol('ClientProfileFieldsetContextUnset');\n\nconst ClientProfileFieldsetContext = createContext<\n ClientProfileFieldsetContextValue | typeof CLIENT_PROFILE_FIELDSET_UNSET\n>(CLIENT_PROFILE_FIELDSET_UNSET);\n\nfunction useClientProfileFieldsetContext(): ClientProfileFieldsetContextValue {\n const ctx = useContext(ClientProfileFieldsetContext);\n if (ctx === CLIENT_PROFILE_FIELDSET_UNSET) {\n throw new Error('ClientProfileFieldset controls must be used inside ClientProfileFieldset.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileFieldsetProps = {\n /** Phone mask/validation country, provided to the phone control via context. */\n country?: PhoneCountry;\n /** Locale document type; the document control renders only when supported. */\n documentType?: string;\n /** Placeholder rows the loading skeleton draws. Set it to the number of controls composed here. */\n skeletonCount?: number;\n /** Announced politely while the profile resolves. Blank means no announcement. */\n loadingLabel?: string;\n /** Announced politely once the controls mount. Blank means no announcement. */\n loadedLabel?: string;\n /** Accessible name of the `<fieldset>` group. Blank means no name is exposed. */\n groupLabel?: string;\n className?: string;\n children?: ReactNode;\n /** `ClientProfileFieldsetData` slice forwarded by the reactive runtime. */\n $data?: ClientProfileFieldsetData;\n /** Compiler-injected; carries `identity.clientProfileUpdate`. */\n $actions?: ComponentActions;\n};\n\ntype ProfileTextControlProps = { label?: string; placeholder?: string; className?: string; children?: ReactNode };\n\nexport type ClientProfileFirstNameControlProps = ProfileTextControlProps;\nexport type ClientProfileLastNameControlProps = ProfileTextControlProps;\n\nexport type ClientProfileFieldsetPhoneControlProps = {\n label?: string;\n placeholder?: string;\n /**\n * Raises the field to required. The composite owns it; the contract carries no rule (Decision 6),\n * so here the raise is the whole of the resolution. Unset is no opinion, which leaves the phone\n * optional.\n */\n requirement?: 'mandatory' | 'optional';\n errorInvalid?: string;\n errorRequired?: string;\n className?: string;\n /** Given, replaces PhoneField's default Field.Label + PhoneFieldInput + Field.Error anatomy. */\n children?: ReactNode;\n};\n\nexport type ClientProfileDocumentControlProps = {\n label?: string;\n placeholder?: string;\n errorMessage?: string;\n className?: string;\n /** Given, replaces DocumentField's default Field.Label + DocumentFieldInput + Field.Error anatomy. */\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientProfileFieldsetDefinition = defineComponent<ClientProfileFieldsetProps>({\n name: 'ClientProfileFieldset',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFieldset,\n fragment,\n fetchStrategy: 'reactive',\n skeleton: ClientProfileFieldsetSkeleton,\n requiresContext: ['form', 'session'],\n providesContext: 'client-profile-fieldset',\n actions: { identity: ['clientProfileUpdate'] },\n editor: { label: 'Client Profile Fieldset', category: 'identity' },\n schema: {\n country: { type: 'string', default: 'US', label: 'Phone country (US or BR)' },\n documentType: { type: 'string', label: 'Document type (e.g. CPF)' },\n skeletonCount: { type: 'number', default: 4, label: 'Skeleton rows' },\n loadingLabel: { type: 'string', default: 'Loading your details', label: 'Loading announcement' },\n loadedLabel: { type: 'string', default: 'Your details are ready', label: 'Loaded announcement' },\n groupLabel: { type: 'string', default: 'Your details', label: 'Accessible group name' },\n },\n guideline:\n 'Registers a batched write of the ClientProfile entity with the enclosing FormRoot; renders one Fieldset.Root and no <form>. Must be inside a FormRoot with a SessionRoot ancestor. Compose ClientProfileFirstNameControl / ClientProfileLastNameControl / ClientProfileFieldsetPhoneControl / ClientProfileDocumentControl as children, in any order. The controls mount only once the profile has resolved, with skeletonCount placeholder rows in their place until then — set it to the number of controls composed, or the form jumps by the difference. Style the announcement sr-only. Dispatches only the fields the shopper changed. groupLabel names the group as its accessible name; blank emits no aria-label, and a composed FieldsetLegend wins over it.',\n});\n\nexport const ClientProfileFirstNameControlDefinition = defineComponent<ClientProfileFirstNameControlProps>({\n name: 'ClientProfileFirstNameControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFirstNameControl,\n requiresContext: 'client-profile-fieldset',\n providesContext: 'field',\n editor: { label: 'Client Profile First Name Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n },\n guideline:\n \"The bound first-name control; must be a descendant of ClientProfileFieldset. Renders Field.Root > Field.Label + Field.Control + Field.Error by default, or the composed children in the author's own order.\",\n});\n\nexport const ClientProfileLastNameControlDefinition = defineComponent<ClientProfileLastNameControlProps>({\n name: 'ClientProfileLastNameControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileLastNameControl,\n requiresContext: 'client-profile-fieldset',\n providesContext: 'field',\n editor: { label: 'Client Profile Last Name Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n },\n guideline:\n \"The bound last-name control; must be a descendant of ClientProfileFieldset. Renders Field.Root > Field.Label + Field.Control + Field.Error by default, or the composed children in the author's own order.\",\n});\n\nexport const ClientProfileFieldsetPhoneControlDefinition = defineComponent<ClientProfileFieldsetPhoneControlProps>({\n name: 'ClientProfileFieldsetPhoneControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFieldsetPhoneControl,\n requiresContext: 'client-profile-fieldset',\n // Provides both names (mirrors shipping-address.tsx:595's Decision 7): 'field' is what every\n // fieldset Control declares (RFC:503), but this control also composes PhoneField, whose\n // PhoneFieldInput part requires 'phone-field' — a page composing\n // ClientProfileFieldsetPhoneControl > PhoneFieldInput would otherwise fail the build with a\n // context-constraint violation, since PhoneField itself (the only 'phone-field' provider) never\n // appears in the page schema (finding F1).\n providesContext: ['field', 'phone-field'],\n editor: { label: 'Client Profile Fieldset Phone Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n // No default: an unset requirement is no opinion, not a raise to mandatory.\n requirement: { type: ['mandatory', 'optional'] as const, label: 'Requirement' },\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n errorInvalid: { type: 'string', label: 'Invalid error message' },\n errorRequired: { type: 'string', label: 'Required error message' },\n },\n guideline:\n \"The bound phone control; must be a descendant of ClientProfileFieldset. Distinct from ClientProfilePhoneControl (matter/client-profile), which writes on blur and requires the client-profile context — the two cannot compile into each other's container. Composes the PhoneField particle: recomposable (RFC:503) — given children, they replace PhoneField's default Field.Label + PhoneFieldInput + Field.Error anatomy, in any order; childless keeps today's order. Its className styles PhoneField's own root, the node that lays out label, input and error — not the wrapper, which carries only the data-fs-client-profile-fieldset-field=\\\"phone\\\" styling hook. A store that does not want a phone field omits this element from the page: requirement only governs required validation, never visibility.\",\n});\n\nexport const ClientProfileDocumentControlDefinition = defineComponent<ClientProfileDocumentControlProps>({\n name: 'ClientProfileDocumentControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileDocumentControl,\n requiresContext: 'client-profile-fieldset',\n // Provides both names, same reasoning as ClientProfileFieldsetPhoneControlDefinition above: this\n // control composes DocumentField, whose DocumentFieldInput part requires 'document-field' — a page\n // composing ClientProfileDocumentControl > DocumentFieldInput would otherwise fail the build\n // (finding F1).\n providesContext: ['field', 'document-field'],\n editor: { label: 'Client Profile Document Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n errorMessage: { type: 'string', label: 'Error message' },\n },\n guideline:\n \"The bound tax-document control; must be a descendant of ClientProfileFieldset. Renders nothing, and registers no field name, when the fieldset's documentType is unsupported. Composes the DocumentField particle: recomposable (RFC:503) — given children, they replace DocumentField's default Field.Label + DocumentFieldInput + Field.Error anatomy, in any order; childless keeps today's order.\",\n});\n\n// ---------------------------------------------------------------------------\n// Fieldset root\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a batched write of the `ClientProfile` entity with the enclosing `FormRoot`\n * (`registerFieldset`, namespace `clientProfile`) on mount, unconditionally — so this fieldset keeps its\n * original dispatch slot even while its own query is still resolving. Its children — the seed values and\n * the field-name registry its controls need — render only once the entity has resolved; until then the\n * `Fieldset.Root` carries `-pending` and renders `ClientProfileFieldsetSkeleton` (Decision 5).\n * `dispatch` and `toPayload` are `useCallback([])`-stable and read `$actions`/`country`/`documentType`\n * through a ref, so the registration effect runs exactly once per mount regardless of how often the\n * parent re-renders.\n *\n * \"Resolved\" is two conditions, because `$data` alone cannot answer it. Under the reactive runtime the\n * slice is defined from the first render — `useReactiveEntity` hands down `fallbackData` while the cache\n * read is incomplete — so an empty server answer looks exactly like a resolved one, and the controls\n * would mount and seed `''` before the shopper's real profile arrives. `useSeedAdoption` re-seeds a\n * clean field with `defaultValue` on every render, but once the field turns dirty no later seed\n * ever reaches it again: a shopper who starts typing before the real profile arrives locks that\n * empty seed in for good, and even a shopper who does not type sees the field flash from `''` to\n * the real data. `useReactiveDataSettled` supplies the missing half: the page query's own\n * in-flight state, latched so it only ever goes pending → settled. The `$data === undefined` half still\n * carries trees with no reactive root above them, where the slice arrives as a plain prop.\n *\n * Both ends of that wait are spoken: the fieldset carries `aria-busy` while it lasts and holds a polite\n * live region that announces `loadingLabel` and then `loadedLabel`. Style the region `sr-only` — it is\n * text meant for a screen reader, not for the page.\n */\nexport function ClientProfileFieldset({\n country = 'US',\n documentType,\n skeletonCount = CLIENT_PROFILE_SKELETON_ROWS,\n loadingLabel = 'Loading your details',\n loadedLabel = 'Your details are ready',\n groupLabel = 'Your details',\n className,\n children,\n $data: $dataProp,\n $actions: $actionsProp,\n}: ClientProfileFieldsetProps) {\n // Nesting fieldsets has no legitimate use: useFieldsetField resolves the *nearest*\n // FieldsetFieldContext, not the one owning a control's actual namespace, so a control nested two\n // fieldsets deep would silently register under the wrong one and its write would be discarded on\n // submit. Runtime registry integrity, mirroring the namespace-collision throw FormRoot already ships\n // (form.tsx:280-288).\n useNoAncestorFieldset('ClientProfileFieldset');\n\n const $data = useDataOverride($dataProp);\n\n const $actions = useActionsOverride($actionsProp);\n // Three conditions, not two. `useReactiveDataSettled` latches one way and never re-arms, so on its\n // own it answers \"has this page ever resolved\", not \"is what I am holding right now the server's\n // answer\" — after the page query's entity ref moves to one this cache never stored, or its root is\n // skipped for want of a variable, the slice silently regresses to `fallbackData` while the latch\n // still reads settled. `useEntityDataResolved` is the per-entity half: unlatched, so it follows the\n // slice back down and returns this fieldset to its skeleton instead of leaving live controls seeded\n // from a placeholder.\n const settled = useReactiveDataSettled();\n const sliceResolved = useEntityDataResolved();\n const resolved = settled && sliceResolved && $data !== undefined;\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n // Runs before useFormContext() below: useFormRegistry() throws the friendly \"Form parts must be used\n // inside FormRoot.\" error when no FormRoot is above, so a FormRoot-less render fails there rather than\n // reaching the getFieldState destructure, which throws an unhelpful TypeError instead (useFormContext()\n // resolves to null outside a FormRoot, and destructuring null throws).\n const { registerFieldset } = useFormRegistry();\n\n // Decision 3, as later narrowed: this fieldset still reads dirtiness imperatively, but the rule is no\n // longer the form's. `FormRoot` subscribes `dirtyFields` in render and decides which fieldsets to\n // dispatch from it, by the fields each one has mounted, on react-hook-form's documented contract.\n //\n // The imperative read survives here because it asks a different question in a different place: which\n // of *this* fieldset's own fields changed, resolved inside `toPayload` at submit time, by full\n // registered field name — the shape `getFieldState` documents. `useFormContext()` adds no\n // `formState` subscription of its own; one is created only when a caller reads a formState key off\n // the `useForm`/`useFormState` proxy (or hands that already-subscribed object to `getFieldState`'s\n // second argument). Calling `getFieldState(name)` with no second argument reads the current internal\n // state directly, so this fieldset root still does not re-render on a keystroke.\n const { getFieldState } = useFormContext();\n\n const latest = useRef({ $actions, country, documentType, readOnly, getFieldState });\n useEffect(() => {\n latest.current = { $actions, country, documentType, readOnly, getFieldState };\n }, [$actions, country, documentType, readOnly, getFieldState]);\n\n const { registry: fieldsetRegistry, getFieldNames } = useFieldsetFieldNames('clientProfile');\n\n const dispatch = useCallback(async (payload: Record<string, unknown>) => {\n // Defence-in-depth: the UI disables every input while read-only, but `FormClear` sets values (and\n // marks them dirty) on every registered field regardless of that disabled attribute — react-hook-\n // form's own `useController` calls never receive a `disabled` registration option, so it has no\n // notion of \"disabled\" of its own. Guarding here keeps the write from reaching the network even if\n // some other composition dirties this fieldset while canEditData is false (the server also refuses,\n // but a client-side guard should not depend on that).\n if (latest.current.readOnly) {\n throw new Error('ClientProfileFieldset cannot dispatch while the session is read-only.');\n }\n const update = latest.current.$actions?.identity?.clientProfileUpdate;\n // Throwing is the contract's failure path: FormRoot catches it and records this fieldset's failure\n // (form.tsx:264-266). Returning [] would report an unqualified success for a write that never\n // happened.\n if (!update) throw new Error('ClientProfileFieldset requires $actions.identity.clientProfileUpdate.');\n return update(payload as ClientProfileUpdateInput);\n }, []);\n\n const toPayload = useCallback((values: Record<string, unknown>): Record<string, unknown> => {\n const { country: activeCountry, getFieldState } = latest.current;\n // FR-5: a key is emitted only when the shopper actually changed it — presence in `values` alone\n // (every registered field is always present) is not enough. `dirtyFields` is not read directly;\n // `getFieldState` is called with no second argument, which reads react-hook-form's current\n // internal state without subscribing to it (Decision 3).\n const isDirty = (key: ProfileFieldKey) => getFieldState(`clientProfile.${key}`).isDirty;\n const payload: Record<string, string> = {};\n if ('firstName' in values && isDirty('firstName')) payload.firstName = String(values.firstName ?? '').trim();\n if ('lastName' in values && isDirty('lastName')) payload.lastName = String(values.lastName ?? '').trim();\n if ('phone' in values && isDirty('phone')) {\n const raw = String(values.phone ?? '').trim();\n // toE164 on a blank value returns the bare country code, which would write \"+55\" over the\n // shopper's number and fail every later save.\n payload.phone = raw ? toE164(raw, activeCountry) : '';\n }\n if ('document' in values && isDirty('document')) {\n payload.document = String(values.document ?? '').replace(/\\D/g, '');\n }\n return payload;\n }, []);\n\n useEffect(\n () =>\n registerFieldset({\n namespace: 'clientProfile',\n getFieldNames,\n dispatch,\n toPayload,\n // Decision 1: the register is a machine identifier, the kebab-case ASCII rendering of this\n // registration's own `namespace` (`clientProfile` → `client-profile`) — not authored per\n // page (Decision 2).\n label: 'client-profile',\n }),\n [registerFieldset, getFieldNames, dispatch, toPayload],\n );\n\n const saved = useMemo<Readonly<Record<ProfileFieldKey, string>>>(\n () => ({\n firstName: $data?.firstName ?? '',\n lastName: $data?.lastName ?? '',\n phone: $data?.phone ?? '',\n document: $data?.document ?? '',\n }),\n [$data],\n );\n\n const contextValue = useMemo<ClientProfileFieldsetContextValue>(\n () => ({ namespace: 'clientProfile', saved, readOnly, country, documentType, rules: NO_RULES }),\n [saved, readOnly, country, documentType],\n );\n\n // Assistive technology only reports a change inside a region that was already in the tree, so a\n // region mounted together with its text announces nothing. The region therefore sits in both\n // branches and starts empty, with the wait written on the pass after mount and replaced — not\n // cleared — once the controls are up: the skeleton is `aria-hidden`, so without this second\n // message the form would materialise in silence (WCAG 2.1 SC 4.1.3).\n const [announcement, setAnnouncement] = useState('');\n const announced = resolved ? loadedLabel : loadingLabel;\n useEffect(() => {\n setAnnouncement(announced);\n }, [announced]);\n\n return (\n <Fieldset.Root\n className={className}\n disabled={readOnly}\n aria-busy={resolved ? undefined : true}\n // FR-12: blank (or whitespace-only) means no name is exposed, mirroring the loadingLabel/\n // loadedLabel convention. On the root itself (FR-13), so it is identical whether the fieldset\n // is pending or resolved; a page-composed FieldsetLegend still wins via aria-labelledby.\n {...(groupLabel?.trim() ? { 'aria-label': groupLabel } : {})}\n data-fs-client-profile-fieldset=\"\"\n {...(resolved ? {} : { 'data-fs-client-profile-fieldset-pending': '' })}\n {...(readOnly ? { 'data-fs-client-profile-fieldset-readonly': '' } : {})}\n >\n {resolved ? (\n <FieldsetFieldContext.Provider value={fieldsetRegistry}>\n <ClientProfileFieldsetContext.Provider value={contextValue}>{children}</ClientProfileFieldsetContext.Provider>\n </FieldsetFieldContext.Provider>\n ) : (\n <ClientProfileFieldsetSkeleton count={skeletonCount} />\n )}\n <output aria-live=\"polite\" data-fs-client-profile-fieldset-announcement=\"\">\n {announcement}\n </output>\n </Fieldset.Root>\n );\n}\n\n/**\n * The loading state of the fieldset, rendered in place of the controls until the entity resolves and\n * declared as the definition's `skeleton`. Purely visual: the wait is spoken by the fieldset's own live\n * region, so this is hidden from assistive technology rather than announced twice.\n */\nexport function ClientProfileFieldsetSkeleton({\n count = CLIENT_PROFILE_SKELETON_ROWS,\n className,\n ...props\n}: ComponentPropsWithRef<'div'> & { count?: number }) {\n return (\n <div {...props} className={className} data-fs-client-profile-fieldset-skeleton=\"\" aria-hidden=\"true\">\n {Array.from({ length: count }, (_, i) => (\n <span key={i} data-fs-client-profile-fieldset-skeleton-field=\"\" />\n ))}\n </div>\n );\n}\n\nClientProfileFieldset.Skeleton = ClientProfileFieldsetSkeleton;\n\n// ---------------------------------------------------------------------------\n// Text controls — first name, last name\n// ---------------------------------------------------------------------------\n\nfunction ProfileTextControl({\n fieldKey,\n attr,\n autoComplete,\n defaultLabel,\n label,\n placeholder,\n className,\n children,\n}: {\n fieldKey: 'firstName' | 'lastName';\n attr: string;\n autoComplete: string;\n defaultLabel: string;\n label?: string;\n placeholder?: string;\n className?: string;\n children?: ReactNode;\n}) {\n const { namespace, saved, readOnly, rules } = useClientProfileFieldsetContext();\n const name = useFieldsetField(namespace, fieldKey);\n\n return (\n <FieldBinding\n name={name}\n label={label ?? defaultLabel}\n placeholder={placeholder}\n defaultValue={saved[fieldKey]}\n rule={rules[fieldKey]}\n disabled={readOnly}\n autoComplete={autoComplete}\n >\n <Field.Root className={className} data-fs-client-profile-fieldset-field={attr}>\n {children ?? (\n <>\n <Field.Label />\n <Field.Control />\n <Field.Error />\n </>\n )}\n </Field.Root>\n </FieldBinding>\n );\n}\n\nexport function ClientProfileFirstNameControl({\n label,\n placeholder,\n className,\n children,\n}: ClientProfileFirstNameControlProps) {\n return (\n <ProfileTextControl\n fieldKey=\"firstName\"\n attr=\"first-name\"\n autoComplete=\"given-name\"\n defaultLabel=\"First name\"\n label={label}\n placeholder={placeholder}\n className={className}\n >\n {children}\n </ProfileTextControl>\n );\n}\n\nexport function ClientProfileLastNameControl({\n label,\n placeholder,\n className,\n children,\n}: ClientProfileLastNameControlProps) {\n return (\n <ProfileTextControl\n fieldKey=\"lastName\"\n attr=\"last-name\"\n autoComplete=\"family-name\"\n defaultLabel=\"Last name\"\n label={label}\n placeholder={placeholder}\n className={className}\n >\n {children}\n </ProfileTextControl>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Phone control — the fieldset-bound counterpart of ClientProfilePhoneControl\n// ---------------------------------------------------------------------------\n\n/**\n * Composes `PhoneField` inside a `FieldBinding`, wrapped in a `data-fs-client-profile-fieldset-field=\"phone\"`\n * node — the same styling hook attribute the text controls carry on their own `Field.Root`.\n * `useFieldsetField` runs unconditionally, before `requirement` is even read — there is no suppressed\n * state for this control to second-guess; `requirement` only ever raises the field to required,\n * and left unset it raises nothing.\n */\nexport function ClientProfileFieldsetPhoneControl({\n label,\n placeholder,\n requirement,\n errorInvalid,\n errorRequired,\n className,\n children,\n}: ClientProfileFieldsetPhoneControlProps) {\n const { namespace, saved, readOnly, rules, country } = useClientProfileFieldsetContext();\n const name = useFieldsetField(namespace, 'phone');\n\n const binding = (\n <FieldBinding\n name={name}\n label={label}\n placeholder={placeholder}\n defaultValue={saved.phone}\n rule={rules.phone}\n disabled={readOnly}\n >\n <PhoneField\n country={country}\n requirement={requirement}\n errorInvalid={errorInvalid}\n errorRequired={errorRequired}\n className={className}\n >\n {children}\n </PhoneField>\n </FieldBinding>\n );\n\n // The wrapper carries no className of its own (Decision 1): className lands on PhoneField's own\n // Field.Root, the node that actually stacks label, input and error, so it needs no flex slot here.\n return <div data-fs-client-profile-fieldset-field=\"phone\">{binding}</div>;\n}\n\n// ---------------------------------------------------------------------------\n// Document control — self-gates on documentType, through an inner component\n// ---------------------------------------------------------------------------\n\ntype ClientProfileDocumentControlInnerProps = ClientProfileDocumentControlProps & { documentType: DocumentType };\n\n/**\n * `useFieldsetField` (through `useClientProfileFieldsetContext`'s namespace) lives only in this inner\n * component, mounted only once `ClientProfileDocumentControl` has already decided the document type is\n * supported. Splitting the decision from the registration this way (Decision 8) keeps a control that\n * renders nothing from registering a field name `FormClear` would otherwise dirty and dispatch.\n */\nfunction ClientProfileDocumentControlInner({\n documentType,\n label,\n placeholder,\n errorMessage,\n className,\n children,\n}: ClientProfileDocumentControlInnerProps) {\n const { namespace, saved, readOnly, rules } = useClientProfileFieldsetContext();\n const name = useFieldsetField(namespace, 'document');\n\n return (\n <FieldBinding\n name={name}\n label={label ?? DOCUMENTS[documentType].name}\n placeholder={placeholder}\n defaultValue={saved.document}\n rule={rules.document}\n disabled={readOnly}\n >\n <DocumentField\n documentType={documentType}\n errorMessage={errorMessage}\n className={className}\n data-fs-client-profile-fieldset-field=\"document\"\n >\n {children}\n </DocumentField>\n </FieldBinding>\n );\n}\n\nexport function ClientProfileDocumentControl({\n label,\n placeholder,\n errorMessage,\n className,\n children,\n}: ClientProfileDocumentControlProps) {\n const { documentType } = useClientProfileFieldsetContext();\n if (!isSupportedDocumentType(documentType)) return null;\n\n return (\n <ClientProfileDocumentControlInner\n documentType={documentType}\n label={label}\n placeholder={placeholder}\n errorMessage={errorMessage}\n className={className}\n >\n {children}\n </ClientProfileDocumentControlInner>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-profile-fieldset.js","names":[],"sources":["../../../src/matter/client-profile-form/client-profile-fieldset.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 useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport { useFormContext } from 'react-hook-form';\nimport type { DocumentType } from '../../particles/document-field/document-field.js';\nimport { DOCUMENTS, DocumentField, isSupportedDocumentType } from '../../particles/document-field/document-field.js';\nimport type { FieldRule } from '../../particles/field/field.js';\nimport {\n Field,\n FieldBinding,\n FieldsetFieldContext,\n useFieldsetField,\n useFieldsetFieldNames,\n useNoAncestorFieldset,\n} from '../../particles/field/field.js';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { useFormRegistry } from '../../particles/form/form.js';\nimport type { PhoneCountry } from '../../particles/phone-field/phone-field.js';\nimport { PhoneField, toE164 } from '../../particles/phone-field/phone-field.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 { useEntityDataResolved } from '../../sdk/data/entity-resolution-context.js';\nimport { useReactiveDataSettled } from '../../sdk/data/use-reactive-data-settled.js';\nimport type { ClientProfileUpdateInput } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * `ClientProfileFieldset` — the composable, one-Save write of the `ClientProfile` sibling entity\n * (RFC: Composable Entity Forms, `Proposal > Design > The fieldset`). It registers itself with the\n * enclosing `FormRoot` under the `clientProfile` namespace and dispatches\n * `$actions.identity.clientProfileUpdate` for its own submit slot; it renders no `<form>` of its own.\n *\n * The four controls below are the smallest placeable unit: each calls `useFieldsetField` for its full\n * field name, renders a `FieldBinding` carrying the field's static facts, and — inside it — either the\n * default anatomy or the author's own children. No control's field contract carries a blocking\n * `RegisterOptions` key (no `required`, `min`, `max`, `minLength`, `maxLength`, `pattern`); requiredness\n * on the phone control rides on its own `requirement` prop instead, which only ever gates required\n * validation — a field the store does not want is omitted from the page, not switched off by a prop.\n *\n * The fieldset's context name is `client-profile-fieldset`, not `client-profile`: the shipped\n * `ClientProfilePhoneControl` (`matter/client-profile/client-profile.tsx`) already requires\n * `client-profile` and writes on blur, so reusing that name here would let an on-blur control compile\n * inside this batched fieldset and write immediately. `ClientProfileFieldsetPhoneControl` carries the\n * same container prefix as the context it requires, naming the collision rather than hiding it.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on the fieldset, its own data root\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientProfileFieldsetData on Query {\n clientProfile {\n __typename\n id\n firstName\n lastName\n phone\n document\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the ClientProfile sibling entity, unwrapped. */\nexport type ClientProfileFieldsetData = {\n firstName?: string | null;\n lastName?: string | null;\n phone?: string | null;\n document?: string | null;\n} | null;\n\n/** The four keys this fieldset registers under its namespace. */\ntype ProfileFieldKey = 'firstName' | 'lastName' | 'phone' | 'document';\n\n/** What the fieldset publishes to its controls. Carries no live value — react-hook-form owns those. */\ntype ClientProfileFieldsetContextValue = Readonly<{\n namespace: 'clientProfile';\n /** Seeds, resolved from `$data`. Empty string, never null, so a control never seeds `undefined`. */\n saved: Readonly<Record<ProfileFieldKey, string>>;\n /** True when the session reports `canEditData: false`. */\n readOnly: boolean;\n /** Phone mask/validation country, from the fieldset's own prop. */\n country: PhoneCountry;\n /** Locale document type; the document control renders only when supported. */\n documentType: string | undefined;\n /** The fieldset's own validation rules, per key. Present and empty today (Decision 6). */\n rules: Readonly<Partial<Record<ProfileFieldKey, FieldRule>>>;\n}>;\n\n/** No control publishes a rule today — a blocking `RegisterOptions` key would reopen the same submit\n * block for a read-only session (Decision 6): `readOnly` disables a control's rendered `<input>` via a\n * plain HTML attribute, never react-hook-form's own `disabled` registration option (`field.tsx`'s\n * `useController` call takes no `disabled`), so a required field would stay fully registered and\n * validated while the shopper has no way to focus or edit it. Kept as a stable empty object so the\n * context value's identity does not churn on every render. */\nconst NO_RULES: Readonly<Partial<Record<ProfileFieldKey, FieldRule>>> = {};\n\n/**\n * Rows `ClientProfileFieldsetSkeleton` draws when nobody says otherwise: one per control in the full\n * anatomy — first name, last name, phone, document. It is a default and not a derivation, because\n * the composed children are opaque here. A page that composes fewer sets `skeletonCount` to what it\n * composed, or the form jumps by the difference when the controls take the skeleton's place.\n */\nconst CLIENT_PROFILE_SKELETON_ROWS = 4;\n\nconst CLIENT_PROFILE_FIELDSET_UNSET = Symbol('ClientProfileFieldsetContextUnset');\n\nconst ClientProfileFieldsetContext = createContext<\n ClientProfileFieldsetContextValue | typeof CLIENT_PROFILE_FIELDSET_UNSET\n>(CLIENT_PROFILE_FIELDSET_UNSET);\n\nfunction useClientProfileFieldsetContext(): ClientProfileFieldsetContextValue {\n const ctx = useContext(ClientProfileFieldsetContext);\n if (ctx === CLIENT_PROFILE_FIELDSET_UNSET) {\n throw new Error('ClientProfileFieldset controls must be used inside ClientProfileFieldset.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileFieldsetProps = {\n /** Phone mask/validation country, provided to the phone control via context. */\n country?: PhoneCountry;\n /** Locale document type; the document control renders only when supported. */\n documentType?: string;\n /** Placeholder rows the loading skeleton draws. Set it to the number of controls composed here. */\n skeletonCount?: number;\n /** Announced politely while the profile resolves. Blank means no announcement. */\n loadingLabel?: string;\n /** Announced politely once the controls mount. Blank means no announcement. */\n loadedLabel?: string;\n /** Accessible name of the `<fieldset>` group. Blank means no name is exposed. */\n groupLabel?: string;\n className?: string;\n children?: ReactNode;\n /** `ClientProfileFieldsetData` slice forwarded by the reactive runtime. */\n $data?: ClientProfileFieldsetData;\n /** Compiler-injected; carries `identity.clientProfileUpdate`. */\n $actions?: ComponentActions;\n};\n\ntype ProfileTextControlProps = { label?: string; placeholder?: string; className?: string; children?: ReactNode };\n\nexport type ClientProfileFirstNameControlProps = ProfileTextControlProps;\nexport type ClientProfileLastNameControlProps = ProfileTextControlProps;\n\nexport type ClientProfileFieldsetPhoneControlProps = {\n label?: string;\n placeholder?: string;\n /**\n * Raises the field to required. The composite owns it; the contract carries no rule (Decision 6),\n * so here the raise is the whole of the resolution. Unset is no opinion, which leaves the phone\n * optional.\n */\n requirement?: 'mandatory' | 'optional';\n errorInvalid?: string;\n errorRequired?: string;\n className?: string;\n /** Given, replaces PhoneField's default Field.Label + PhoneFieldInput + Field.Error anatomy. */\n children?: ReactNode;\n};\n\nexport type ClientProfileDocumentControlProps = {\n label?: string;\n placeholder?: string;\n errorMessage?: string;\n className?: string;\n /** Given, replaces DocumentField's default Field.Label + DocumentFieldInput + Field.Error anatomy. */\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientProfileFieldsetDefinition = defineComponent<ClientProfileFieldsetProps>({\n name: 'ClientProfileFieldset',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFieldset,\n fragment,\n fetchStrategy: 'reactive',\n skeleton: ClientProfileFieldsetSkeleton,\n requiresContext: ['form', 'session'],\n providesContext: 'client-profile-fieldset',\n actions: { identity: ['clientProfileUpdate'] },\n editor: { label: 'Client Profile Fieldset', category: 'identity' },\n schema: {\n country: { type: 'string', default: 'US', label: 'Phone country (US or BR)' },\n documentType: { type: 'string', label: 'Document type (e.g. CPF)' },\n skeletonCount: { type: 'number', default: 4, label: 'Skeleton rows' },\n loadingLabel: { type: 'string', default: 'Loading your details', label: 'Loading announcement' },\n loadedLabel: { type: 'string', default: 'Your details are ready', label: 'Loaded announcement' },\n groupLabel: { type: 'string', default: 'Your details', label: 'Accessible group name' },\n },\n guideline:\n 'Registers a batched write of the ClientProfile entity with the enclosing FormRoot; renders one Fieldset.Root and no <form>. Must be inside a FormRoot with a SessionRoot ancestor. Compose ClientProfileFirstNameControl / ClientProfileLastNameControl / ClientProfileFieldsetPhoneControl / ClientProfileDocumentControl as children, in any order. The controls mount only once the profile has resolved, with skeletonCount placeholder rows in their place until then — set it to the number of controls composed, or the form jumps by the difference. Style the announcement sr-only. Dispatches only the fields the shopper changed. groupLabel names the group as its accessible name; blank emits no aria-label, and a composed FieldsetLegend wins over it.',\n});\n\nexport const ClientProfileFirstNameControlDefinition = defineComponent<ClientProfileFirstNameControlProps>({\n name: 'ClientProfileFirstNameControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFirstNameControl,\n requiresContext: 'client-profile-fieldset',\n providesContext: 'field',\n editor: { label: 'Client Profile First Name Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n },\n guideline:\n \"The bound first-name control; must be a descendant of ClientProfileFieldset. Renders Field.Root > Field.Label + Field.Control + Field.Error by default, or the composed children in the author's own order.\",\n});\n\nexport const ClientProfileLastNameControlDefinition = defineComponent<ClientProfileLastNameControlProps>({\n name: 'ClientProfileLastNameControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileLastNameControl,\n requiresContext: 'client-profile-fieldset',\n providesContext: 'field',\n editor: { label: 'Client Profile Last Name Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n },\n guideline:\n \"The bound last-name control; must be a descendant of ClientProfileFieldset. Renders Field.Root > Field.Label + Field.Control + Field.Error by default, or the composed children in the author's own order.\",\n});\n\nexport const ClientProfileFieldsetPhoneControlDefinition = defineComponent<ClientProfileFieldsetPhoneControlProps>({\n name: 'ClientProfileFieldsetPhoneControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFieldsetPhoneControl,\n requiresContext: 'client-profile-fieldset',\n // Provides both names (mirrors shipping-address.tsx:595's Decision 7): 'field' is what every\n // fieldset Control declares (RFC:503), but this control also composes PhoneField, whose\n // PhoneFieldInput part requires 'phone-field' — a page composing\n // ClientProfileFieldsetPhoneControl > PhoneFieldInput would otherwise fail the build with a\n // context-constraint violation, since PhoneField itself (the only 'phone-field' provider) never\n // appears in the page schema (finding F1).\n providesContext: ['field', 'phone-field'],\n editor: { label: 'Client Profile Fieldset Phone Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n // No default: an unset requirement is no opinion, not a raise to mandatory.\n requirement: { type: ['mandatory', 'optional'] as const, label: 'Requirement' },\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n errorInvalid: { type: 'string', label: 'Invalid error message' },\n errorRequired: { type: 'string', label: 'Required error message' },\n },\n guideline:\n \"The bound phone control; must be a descendant of ClientProfileFieldset. Distinct from ClientProfilePhoneControl (matter/client-profile), which writes on blur and requires the client-profile context — the two cannot compile into each other's container. Composes the PhoneField particle: recomposable (RFC:503) — given children, they replace PhoneField's default Field.Label + PhoneFieldInput + Field.Error anatomy, in any order; childless keeps today's order. Its className styles PhoneField's own root, the node that lays out label, input and error — not the wrapper, which carries only the data-fs-client-profile-fieldset-field=\\\"phone\\\" styling hook. A store that does not want a phone field omits this element from the page: requirement only governs required validation, never visibility.\",\n});\n\nexport const ClientProfileDocumentControlDefinition = defineComponent<ClientProfileDocumentControlProps>({\n name: 'ClientProfileDocumentControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfileDocumentControl,\n requiresContext: 'client-profile-fieldset',\n // Provides both names, same reasoning as ClientProfileFieldsetPhoneControlDefinition above: this\n // control composes DocumentField, whose DocumentFieldInput part requires 'document-field' — a page\n // composing ClientProfileDocumentControl > DocumentFieldInput would otherwise fail the build\n // (finding F1).\n providesContext: ['field', 'document-field'],\n editor: { label: 'Client Profile Document Control', category: 'identity' },\n schema: {\n // No default (Decision 1): absence means the platform's value, applied at render time by\n // the control itself, not baked into the schema.\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n errorMessage: { type: 'string', label: 'Error message' },\n },\n guideline:\n \"The bound tax-document control; must be a descendant of ClientProfileFieldset. Renders nothing, and registers no field name, when the fieldset's documentType is unsupported. Composes the DocumentField particle: recomposable (RFC:503) — given children, they replace DocumentField's default Field.Label + DocumentFieldInput + Field.Error anatomy, in any order; childless keeps today's order.\",\n});\n\n// ---------------------------------------------------------------------------\n// Fieldset root\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a batched write of the `ClientProfile` entity with the enclosing `FormRoot`\n * (`registerFieldset`, namespace `clientProfile`) on mount, unconditionally — so this fieldset keeps its\n * original dispatch slot even while its own query is still resolving. Its children — the seed values and\n * the field-name registry its controls need — render only once the entity has resolved; until then the\n * `Fieldset.Root` carries `-pending` and renders `ClientProfileFieldsetSkeleton` (Decision 5).\n * `dispatch` and `toPayload` are `useCallback([])`-stable and read `$actions`/`country`/`documentType`\n * through a ref, so the registration effect runs exactly once per mount regardless of how often the\n * parent re-renders.\n *\n * \"Resolved\" is two conditions, because `$data` alone cannot answer it. Under the reactive runtime the\n * slice is defined from the first render — `useReactiveEntity` hands down `fallbackData` while the cache\n * read is incomplete — so an empty server answer looks exactly like a resolved one, and the controls\n * would mount and seed `''` before the shopper's real profile arrives. `useSeedAdoption` re-seeds a\n * clean field with `defaultValue` on every render, but once the field turns dirty no later seed\n * ever reaches it again: a shopper who starts typing before the real profile arrives locks that\n * empty seed in for good, and even a shopper who does not type sees the field flash from `''` to\n * the real data. `useReactiveDataSettled` supplies the missing half: the page query's own\n * in-flight state, latched so it only ever goes pending → settled. The `$data === undefined` half still\n * carries trees with no reactive root above them, where the slice arrives as a plain prop.\n *\n * Both ends of that wait are spoken: the fieldset carries `aria-busy` while it lasts and holds a polite\n * live region that announces `loadingLabel` and then `loadedLabel`. Style the region `sr-only` — it is\n * text meant for a screen reader, not for the page.\n */\nexport function ClientProfileFieldset({\n country = 'US',\n documentType,\n skeletonCount = CLIENT_PROFILE_SKELETON_ROWS,\n loadingLabel = 'Loading your details',\n loadedLabel = 'Your details are ready',\n groupLabel = 'Your details',\n className,\n children,\n $data: $dataProp,\n $actions: $actionsProp,\n}: ClientProfileFieldsetProps) {\n // Nesting fieldsets has no legitimate use: useFieldsetField resolves the *nearest*\n // FieldsetFieldContext, not the one owning a control's actual namespace, so a control nested two\n // fieldsets deep would silently register under the wrong one and its write would be discarded on\n // submit. Runtime registry integrity, mirroring the namespace-collision throw FormRoot already ships\n // (form.tsx:280-288).\n useNoAncestorFieldset('ClientProfileFieldset');\n\n const $data = useDataOverride($dataProp);\n\n const $actions = useActionsOverride($actionsProp);\n // Three conditions, not two. `useReactiveDataSettled` latches one way and never re-arms, so on its\n // own it answers \"has this page ever resolved\", not \"is what I am holding right now the server's\n // answer\" — after the page query's entity ref moves to one this cache never stored, or its root is\n // skipped for want of a variable, the slice silently regresses to `fallbackData` while the latch\n // still reads settled. `useEntityDataResolved` is the per-entity half: unlatched, so it follows the\n // slice back down and returns this fieldset to its skeleton instead of leaving live controls seeded\n // from a placeholder.\n const settled = useReactiveDataSettled();\n const sliceResolved = useEntityDataResolved();\n const resolved = settled && sliceResolved && $data !== undefined;\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n // Runs before useFormContext() below: useFormRegistry() throws the friendly \"Form parts must be used\n // inside FormRoot.\" error when no FormRoot is above, so a FormRoot-less render fails there rather than\n // reaching the getFieldState destructure, which throws an unhelpful TypeError instead (useFormContext()\n // resolves to null outside a FormRoot, and destructuring null throws).\n const { registerFieldset } = useFormRegistry();\n\n // Decision 3, as later narrowed: this fieldset still reads dirtiness imperatively, but the rule is no\n // longer the form's. `FormRoot` subscribes `dirtyFields` in render and decides which fieldsets to\n // dispatch from it, by the fields each one has mounted, on react-hook-form's documented contract.\n //\n // The imperative read survives here because it asks a different question in a different place: which\n // of *this* fieldset's own fields changed, resolved inside `toPayload` at submit time, by full\n // registered field name — the shape `getFieldState` documents. `useFormContext()` adds no\n // `formState` subscription of its own; one is created only when a caller reads a formState key off\n // the `useForm`/`useFormState` proxy (or hands that already-subscribed object to `getFieldState`'s\n // second argument). Calling `getFieldState(name)` with no second argument reads the current internal\n // state directly, so this fieldset root still does not re-render on a keystroke.\n const { getFieldState } = useFormContext();\n\n const latest = useRef({ $actions, country, documentType, readOnly, getFieldState });\n useEffect(() => {\n latest.current = { $actions, country, documentType, readOnly, getFieldState };\n }, [$actions, country, documentType, readOnly, getFieldState]);\n\n const { registry: fieldsetRegistry, getFieldNames } = useFieldsetFieldNames('clientProfile');\n\n const dispatch = useCallback(async (payload: Record<string, unknown>) => {\n // Defence-in-depth: the UI disables every input while read-only, but `FormClear` sets values (and\n // marks them dirty) on every registered field regardless of that disabled attribute — react-hook-\n // form's own `useController` calls never receive a `disabled` registration option, so it has no\n // notion of \"disabled\" of its own. Guarding here keeps the write from reaching the network even if\n // some other composition dirties this fieldset while canEditData is false (the server also refuses,\n // but a client-side guard should not depend on that).\n if (latest.current.readOnly) {\n throw new Error('ClientProfileFieldset cannot dispatch while the session is read-only.');\n }\n const update = latest.current.$actions?.identity?.clientProfileUpdate;\n // Throwing is the contract's failure path: FormRoot catches it and records this fieldset's failure\n // (form.tsx:264-266). Returning [] would report an unqualified success for a write that never\n // happened.\n if (!update) throw new Error('ClientProfileFieldset requires $actions.identity.clientProfileUpdate.');\n return update(payload as ClientProfileUpdateInput);\n }, []);\n\n const toPayload = useCallback((values: Record<string, unknown>): Record<string, unknown> => {\n const { country: activeCountry, documentType: activeDocumentType, getFieldState } = latest.current;\n // FR-5: a key is emitted only when the shopper actually changed it — presence in `values` alone\n // (every registered field is always present) is not enough. `dirtyFields` is not read directly;\n // `getFieldState` is called with no second argument, which reads react-hook-form's current\n // internal state without subscribing to it (Decision 3).\n const isDirty = (key: ProfileFieldKey) => getFieldState(`clientProfile.${key}`).isDirty;\n const payload: Record<string, string> = {};\n if ('firstName' in values && isDirty('firstName')) payload.firstName = String(values.firstName ?? '').trim();\n if ('lastName' in values && isDirty('lastName')) payload.lastName = String(values.lastName ?? '').trim();\n if ('phone' in values && isDirty('phone')) {\n const raw = String(values.phone ?? '').trim();\n // toE164 on a blank value returns the bare country code, which would write \"+55\" over the\n // shopper's number and fail every later save.\n payload.phone = raw ? toE164(raw, activeCountry) : '';\n }\n // The type's own sanitiser (digits for CPF, SSN and SIN, letters kept for RFC). Omitted when the type is\n // not supported: a value for a control that is no longer shown is never sent.\n if ('document' in values && isDirty('document') && isSupportedDocumentType(activeDocumentType)) {\n // The control only ever writes a string (the masked input's own value); `values` is typed `unknown`\n // because the fieldset contract carries every field's value, so it is narrowed here, not stringified.\n const rawDocument = typeof values.document === 'string' ? values.document : '';\n payload.document = DOCUMENTS[activeDocumentType].sanitize(rawDocument);\n }\n return payload;\n }, []);\n\n useEffect(\n () =>\n registerFieldset({\n namespace: 'clientProfile',\n getFieldNames,\n dispatch,\n toPayload,\n // Decision 1: the register is a machine identifier, the kebab-case ASCII rendering of this\n // registration's own `namespace` (`clientProfile` → `client-profile`) — not authored per\n // page (Decision 2).\n label: 'client-profile',\n }),\n [registerFieldset, getFieldNames, dispatch, toPayload],\n );\n\n const saved = useMemo<Readonly<Record<ProfileFieldKey, string>>>(\n () => ({\n firstName: $data?.firstName ?? '',\n lastName: $data?.lastName ?? '',\n phone: $data?.phone ?? '',\n document: $data?.document ?? '',\n }),\n [$data],\n );\n\n const contextValue = useMemo<ClientProfileFieldsetContextValue>(\n () => ({ namespace: 'clientProfile', saved, readOnly, country, documentType, rules: NO_RULES }),\n [saved, readOnly, country, documentType],\n );\n\n // Assistive technology only reports a change inside a region that was already in the tree, so a\n // region mounted together with its text announces nothing. The region therefore sits in both\n // branches and starts empty, with the wait written on the pass after mount and replaced — not\n // cleared — once the controls are up: the skeleton is `aria-hidden`, so without this second\n // message the form would materialise in silence (WCAG 2.1 SC 4.1.3).\n const [announcement, setAnnouncement] = useState('');\n const announced = resolved ? loadedLabel : loadingLabel;\n useEffect(() => {\n setAnnouncement(announced);\n }, [announced]);\n\n return (\n <Fieldset.Root\n className={className}\n disabled={readOnly}\n aria-busy={resolved ? undefined : true}\n // FR-12: blank (or whitespace-only) means no name is exposed, mirroring the loadingLabel/\n // loadedLabel convention. On the root itself (FR-13), so it is identical whether the fieldset\n // is pending or resolved; a page-composed FieldsetLegend still wins via aria-labelledby.\n {...(groupLabel?.trim() ? { 'aria-label': groupLabel } : {})}\n data-fs-client-profile-fieldset=\"\"\n {...(resolved ? {} : { 'data-fs-client-profile-fieldset-pending': '' })}\n {...(readOnly ? { 'data-fs-client-profile-fieldset-readonly': '' } : {})}\n >\n {resolved ? (\n <FieldsetFieldContext.Provider value={fieldsetRegistry}>\n <ClientProfileFieldsetContext.Provider value={contextValue}>{children}</ClientProfileFieldsetContext.Provider>\n </FieldsetFieldContext.Provider>\n ) : (\n <ClientProfileFieldsetSkeleton count={skeletonCount} />\n )}\n <output aria-live=\"polite\" data-fs-client-profile-fieldset-announcement=\"\">\n {announcement}\n </output>\n </Fieldset.Root>\n );\n}\n\n/**\n * The loading state of the fieldset, rendered in place of the controls until the entity resolves and\n * declared as the definition's `skeleton`. Purely visual: the wait is spoken by the fieldset's own live\n * region, so this is hidden from assistive technology rather than announced twice.\n */\nexport function ClientProfileFieldsetSkeleton({\n count = CLIENT_PROFILE_SKELETON_ROWS,\n className,\n ...props\n}: ComponentPropsWithRef<'div'> & { count?: number }) {\n return (\n <div {...props} className={className} data-fs-client-profile-fieldset-skeleton=\"\" aria-hidden=\"true\">\n {Array.from({ length: count }, (_, i) => (\n <span key={i} data-fs-client-profile-fieldset-skeleton-field=\"\" />\n ))}\n </div>\n );\n}\n\nClientProfileFieldset.Skeleton = ClientProfileFieldsetSkeleton;\n\n// ---------------------------------------------------------------------------\n// Text controls — first name, last name\n// ---------------------------------------------------------------------------\n\nfunction ProfileTextControl({\n fieldKey,\n attr,\n autoComplete,\n defaultLabel,\n label,\n placeholder,\n className,\n children,\n}: {\n fieldKey: 'firstName' | 'lastName';\n attr: string;\n autoComplete: string;\n defaultLabel: string;\n label?: string;\n placeholder?: string;\n className?: string;\n children?: ReactNode;\n}) {\n const { namespace, saved, readOnly, rules } = useClientProfileFieldsetContext();\n const name = useFieldsetField(namespace, fieldKey);\n\n return (\n <FieldBinding\n name={name}\n label={label ?? defaultLabel}\n placeholder={placeholder}\n defaultValue={saved[fieldKey]}\n rule={rules[fieldKey]}\n disabled={readOnly}\n autoComplete={autoComplete}\n >\n <Field.Root className={className} data-fs-client-profile-fieldset-field={attr}>\n {children ?? (\n <>\n <Field.Label />\n <Field.Control />\n <Field.Error />\n </>\n )}\n </Field.Root>\n </FieldBinding>\n );\n}\n\nexport function ClientProfileFirstNameControl({\n label,\n placeholder,\n className,\n children,\n}: ClientProfileFirstNameControlProps) {\n return (\n <ProfileTextControl\n fieldKey=\"firstName\"\n attr=\"first-name\"\n autoComplete=\"given-name\"\n defaultLabel=\"First name\"\n label={label}\n placeholder={placeholder}\n className={className}\n >\n {children}\n </ProfileTextControl>\n );\n}\n\nexport function ClientProfileLastNameControl({\n label,\n placeholder,\n className,\n children,\n}: ClientProfileLastNameControlProps) {\n return (\n <ProfileTextControl\n fieldKey=\"lastName\"\n attr=\"last-name\"\n autoComplete=\"family-name\"\n defaultLabel=\"Last name\"\n label={label}\n placeholder={placeholder}\n className={className}\n >\n {children}\n </ProfileTextControl>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Phone control — the fieldset-bound counterpart of ClientProfilePhoneControl\n// ---------------------------------------------------------------------------\n\n/**\n * Composes `PhoneField` inside a `FieldBinding`, wrapped in a `data-fs-client-profile-fieldset-field=\"phone\"`\n * node — the same styling hook attribute the text controls carry on their own `Field.Root`.\n * `useFieldsetField` runs unconditionally, before `requirement` is even read — there is no suppressed\n * state for this control to second-guess; `requirement` only ever raises the field to required,\n * and left unset it raises nothing.\n */\nexport function ClientProfileFieldsetPhoneControl({\n label,\n placeholder,\n requirement,\n errorInvalid,\n errorRequired,\n className,\n children,\n}: ClientProfileFieldsetPhoneControlProps) {\n const { namespace, saved, readOnly, rules, country } = useClientProfileFieldsetContext();\n const name = useFieldsetField(namespace, 'phone');\n\n const binding = (\n <FieldBinding\n name={name}\n label={label}\n placeholder={placeholder}\n defaultValue={saved.phone}\n rule={rules.phone}\n disabled={readOnly}\n >\n <PhoneField\n country={country}\n requirement={requirement}\n errorInvalid={errorInvalid}\n errorRequired={errorRequired}\n className={className}\n >\n {children}\n </PhoneField>\n </FieldBinding>\n );\n\n // The wrapper carries no className of its own (Decision 1): className lands on PhoneField's own\n // Field.Root, the node that actually stacks label, input and error, so it needs no flex slot here.\n return <div data-fs-client-profile-fieldset-field=\"phone\">{binding}</div>;\n}\n\n// ---------------------------------------------------------------------------\n// Document control — self-gates on documentType, through an inner component\n// ---------------------------------------------------------------------------\n\ntype ClientProfileDocumentControlInnerProps = ClientProfileDocumentControlProps & { documentType: DocumentType };\n\n/**\n * `useFieldsetField` (through `useClientProfileFieldsetContext`'s namespace) lives only in this inner\n * component, mounted only once `ClientProfileDocumentControl` has already decided the document type is\n * supported. Splitting the decision from the registration this way (Decision 8) keeps a control that\n * renders nothing from registering a field name `FormClear` would otherwise dirty and dispatch.\n */\nfunction ClientProfileDocumentControlInner({\n documentType,\n label,\n placeholder,\n errorMessage,\n className,\n children,\n}: ClientProfileDocumentControlInnerProps) {\n const { namespace, saved, readOnly, rules } = useClientProfileFieldsetContext();\n const name = useFieldsetField(namespace, 'document');\n\n return (\n <FieldBinding\n name={name}\n label={label ?? DOCUMENTS[documentType].name}\n placeholder={placeholder}\n defaultValue={saved.document}\n rule={rules.document}\n disabled={readOnly}\n >\n <DocumentField\n documentType={documentType}\n errorMessage={errorMessage}\n className={className}\n data-fs-client-profile-fieldset-field=\"document\"\n >\n {children}\n </DocumentField>\n </FieldBinding>\n );\n}\n\nexport function ClientProfileDocumentControl({\n label,\n placeholder,\n errorMessage,\n className,\n children,\n}: ClientProfileDocumentControlProps) {\n const { documentType } = useClientProfileFieldsetContext();\n if (!isSupportedDocumentType(documentType)) return null;\n\n return (\n <ClientProfileDocumentControlInner\n documentType={documentType}\n label={label}\n placeholder={placeholder}\n errorMessage={errorMessage}\n className={className}\n >\n {children}\n </ClientProfileDocumentControlInner>\n );\n}\n"],"mappings":"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"}
@@ -9,7 +9,10 @@ type MoneyValue = {
9
9
  type FreeShippingProgressData = {
10
10
  cart?: {
11
11
  id: string;
12
- total?: MoneyValue | null;
12
+ totalizers?: {
13
+ id: string;
14
+ value: MoneyValue;
15
+ }[] | null;
13
16
  freeShippingProgress?: {
14
17
  threshold: MoneyValue | null;
15
18
  remaining: MoneyValue;
@@ -21,7 +24,7 @@ export type FreeShippingProgressRootProps = ComponentPropsWithRef<'div'> & {
21
24
  threshold?: number;
22
25
  remaining?: number;
23
26
  qualified?: boolean;
24
- cartTotal?: number;
27
+ itemsTotal?: number;
25
28
  $data?: FreeShippingProgressData['cart'] | null;
26
29
  };
27
30
  export type FreeShippingProgressBarProps = ComponentPropsWithRef<'div'>;
@@ -32,7 +35,7 @@ export type FreeShippingProgressMessageProps = ComponentPropsWithRef<'div'> & {
32
35
  export declare const FreeShippingProgressRootDefinition: ComponentDefinition<FreeShippingProgressRootProps>;
33
36
  export declare const FreeShippingProgressBarDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
34
37
  export declare const FreeShippingProgressMessageDefinition: ComponentDefinition<FreeShippingProgressMessageProps>;
35
- export declare function FreeShippingProgressRoot({ threshold, remaining, qualified, cartTotal, $data: $dataProp, className, children, ...props }: FreeShippingProgressRootProps): import("react/jsx-runtime").JSX.Element | null;
38
+ export declare function FreeShippingProgressRoot({ threshold, remaining, qualified, itemsTotal, $data: $dataProp, className, children, ...props }: FreeShippingProgressRootProps): import("react/jsx-runtime").JSX.Element | null;
36
39
  export declare function FreeShippingProgressBar({ className, ...props }: FreeShippingProgressBarProps): import("react/jsx-runtime").JSX.Element;
37
40
  export declare function FreeShippingProgressMessage({ label, qualifiedLabel, className, children, ...props }: FreeShippingProgressMessageProps): import("react/jsx-runtime").JSX.Element;
38
41
  export declare const FreeShippingProgress: {
@@ -3,6 +3,7 @@ import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
4
  import { useStorefrontContext } from "../../sdk/context/storefront-context.js";
5
5
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
6
+ import { isFiniteAmount } from "../cart/cart.js";
6
7
  import { Meter } from "../../particles/meter/meter.js";
7
8
  import { createContext, useContext } from "react";
8
9
  import { jsx } from "react/jsx-runtime";
@@ -11,9 +12,12 @@ const fragment = `
11
12
  fragment FreeShippingProgressData on Query {
12
13
  cart {
13
14
  id
14
- total {
15
- amount
16
- currencyCode
15
+ totalizers {
16
+ id
17
+ value {
18
+ amount
19
+ currencyCode
20
+ }
17
21
  }
18
22
  freeShippingProgress {
19
23
  threshold {
@@ -45,6 +49,62 @@ function formatCurrency(value, locale, currency) {
45
49
  function clamp01(value) {
46
50
  return Math.min(Math.max(value, 0), 1);
47
51
  }
52
+ function previewCart({ threshold, remaining, qualified, itemsTotal }, currency) {
53
+ if (qualified === void 0 && threshold === void 0 && remaining === void 0 && itemsTotal === void 0) return null;
54
+ return {
55
+ id: "preview",
56
+ totalizers: itemsTotal === void 0 ? [] : [{
57
+ id: "Items",
58
+ value: {
59
+ amount: itemsTotal,
60
+ currencyCode: currency
61
+ }
62
+ }],
63
+ freeShippingProgress: {
64
+ threshold: threshold == null ? null : {
65
+ amount: threshold,
66
+ currencyCode: currency
67
+ },
68
+ remaining: {
69
+ amount: remaining ?? threshold ?? 0,
70
+ currencyCode: currency
71
+ },
72
+ qualified: qualified ?? false
73
+ }
74
+ };
75
+ }
76
+ function itemsSubtotal(cart) {
77
+ const itemsAmount = cart.totalizers?.find((totalizer) => totalizer.id === "Items")?.value.amount;
78
+ return isFiniteAmount(itemsAmount) ? Number(itemsAmount) : null;
79
+ }
80
+ function usableThreshold(threshold) {
81
+ if (threshold == null) return null;
82
+ const parsed = Number(threshold.amount);
83
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
84
+ }
85
+ function fillRatio(isQualified, thresholdAmount, covered) {
86
+ if (isQualified) return 1;
87
+ if (thresholdAmount === null) return 0;
88
+ return clamp01(covered / thresholdAmount);
89
+ }
90
+ function computeProgress(fsp, items, thresholdAmount) {
91
+ if (items === null) {
92
+ const isQualified = fsp.qualified;
93
+ const liveRemaining = !isQualified && isFiniteAmount(fsp.remaining.amount) ? Math.max(Number(fsp.remaining.amount), 0) : 0;
94
+ return {
95
+ isQualified,
96
+ fill: fillRatio(isQualified, thresholdAmount, (thresholdAmount ?? 0) - liveRemaining),
97
+ liveRemaining
98
+ };
99
+ }
100
+ const isQualified = fsp.qualified || thresholdAmount !== null && items >= thresholdAmount;
101
+ const liveRemaining = isQualified || thresholdAmount === null ? 0 : Math.max(thresholdAmount - items, 0);
102
+ return {
103
+ isQualified,
104
+ fill: fillRatio(isQualified, thresholdAmount, items),
105
+ liveRemaining
106
+ };
107
+ }
48
108
  const FreeShippingProgressRootDefinition = defineComponent({
49
109
  name: "FreeShippingProgressRoot",
50
110
  importPath: "@vtex/faststore/components",
@@ -91,40 +151,23 @@ const FreeShippingProgressMessageDefinition = defineComponent({
91
151
  },
92
152
  guideline: "Below-threshold vs earned copy, front-formatted from the active storefront context. Must be inside FreeShippingProgressRoot. See free-shipping-progress.md."
93
153
  });
94
- function FreeShippingProgressRoot({ threshold, remaining, qualified, cartTotal, $data: $dataProp, className, children, ...props }) {
154
+ function FreeShippingProgressRoot({ threshold, remaining, qualified, itemsTotal, $data: $dataProp, className, children, ...props }) {
95
155
  const { currency } = useStorefrontContext();
96
- const cart = useDataOverride($dataProp) ?? (qualified !== void 0 || threshold !== void 0 || remaining !== void 0 || cartTotal !== void 0 ? {
97
- id: "preview",
98
- total: {
99
- amount: cartTotal ?? 0,
100
- currencyCode: currency
101
- },
102
- freeShippingProgress: {
103
- threshold: threshold != null ? {
104
- amount: threshold,
105
- currencyCode: currency
106
- } : null,
107
- remaining: {
108
- amount: remaining ?? 0,
109
- currencyCode: currency
110
- },
111
- qualified: qualified ?? false
112
- }
113
- } : null);
156
+ const cart = useDataOverride($dataProp) ?? previewCart({
157
+ threshold,
158
+ remaining,
159
+ qualified,
160
+ itemsTotal
161
+ }, currency);
114
162
  const fsp = cart?.freeShippingProgress;
115
163
  if (!cart || fsp == null) return null;
116
- const parsedThreshold = fsp.threshold != null ? Number(fsp.threshold.amount) : null;
117
- const cartTotalAmount = Number(cart.total?.amount ?? 0);
118
- const thresholdAmount = parsedThreshold != null && Number.isFinite(parsedThreshold) && parsedThreshold > 0 ? parsedThreshold : null;
119
- const isQualified = fsp.qualified || thresholdAmount != null && cartTotalAmount >= thresholdAmount;
120
- const liveRemaining = isQualified || thresholdAmount == null ? 0 : Math.max(thresholdAmount - cartTotalAmount, 0);
121
- const value = {
122
- isQualified,
123
- fill: isQualified ? 1 : thresholdAmount != null ? clamp01(cartTotalAmount / thresholdAmount) : 0,
124
- liveRemaining
125
- };
164
+ const { isQualified, fill, liveRemaining } = computeProgress(fsp, itemsSubtotal(cart), usableThreshold(fsp.threshold));
126
165
  return /* @__PURE__ */ jsx(FreeShippingProgressContext, {
127
- value,
166
+ value: {
167
+ isQualified,
168
+ fill,
169
+ liveRemaining
170
+ },
128
171
  children: /* @__PURE__ */ jsx("div", {
129
172
  ...props,
130
173
  className,
@@ -1 +1 @@
1
- {"version":3,"file":"free-shipping-progress.js","names":[],"sources":["../../../src/matter/free-shipping-progress/free-shipping-progress.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext } from 'react';\nimport { Meter } from '../../particles/meter/meter.js';\nimport { useStorefrontContext } from '../../sdk/context/storefront-context.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype MoneyValue = { amount: number | string; currencyCode: string };\n\ntype FreeShippingProgressData = {\n cart?: {\n id: string;\n total?: MoneyValue | null;\n freeShippingProgress?: {\n threshold: MoneyValue | null;\n remaining: MoneyValue;\n qualified: boolean;\n } | null;\n } | null;\n};\n\ntype FreeShippingProgressContextValue = {\n isQualified: boolean;\n fill: number;\n liveRemaining: number;\n};\n\nexport type FreeShippingProgressRootProps = ComponentPropsWithRef<'div'> & {\n threshold?: number;\n remaining?: number;\n qualified?: boolean;\n cartTotal?: number;\n $data?: FreeShippingProgressData['cart'] | null;\n};\n\nexport type FreeShippingProgressBarProps = ComponentPropsWithRef<'div'>;\nexport type FreeShippingProgressMessageProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n qualifiedLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — narrow cart slice (total + freeShippingProgress)\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment FreeShippingProgressData on Query {\n cart {\n id\n total {\n amount\n currencyCode\n }\n freeShippingProgress {\n threshold {\n amount\n currencyCode\n }\n remaining {\n amount\n currencyCode\n }\n qualified\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst FSP_CONTEXT_UNSET = Symbol('FreeShippingProgressContextUnset');\n\nconst FreeShippingProgressContext = createContext<FreeShippingProgressContextValue | typeof FSP_CONTEXT_UNSET>(\n FSP_CONTEXT_UNSET,\n);\n\nfunction useFreeShippingProgress(): FreeShippingProgressContextValue {\n const ctx = useContext(FreeShippingProgressContext);\n if (ctx === FSP_CONTEXT_UNSET) {\n throw new Error('useFreeShippingProgress() must be used inside a <FreeShippingProgressRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatCurrency(value: number, locale: string, currency: string): string {\n return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value);\n}\n\nfunction clamp01(value: number): number {\n return Math.min(Math.max(value, 0), 1);\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const FreeShippingProgressRootDefinition = defineComponent<FreeShippingProgressRootProps>({\n name: 'FreeShippingProgressRoot',\n importPath: '@vtex/faststore/components',\n component: FreeShippingProgressRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Free Shipping Progress', category: 'commerce' },\n schema: {},\n guideline:\n 'Free-shipping progress bar for the top of a cart/mini-cart drawer: shows how much more to spend for free shipping and flips to an earned state when met. Renders nothing until a destination and an eligible promotion exist (self-hides on null). See free-shipping-progress.md.',\n});\n\nexport const FreeShippingProgressBarDefinition = defineComponent<FreeShippingProgressBarProps>({\n name: 'FreeShippingProgressBar',\n importPath: '@vtex/faststore/components',\n component: FreeShippingProgressBar,\n editor: { label: 'Free Shipping Progress Bar', category: 'commerce' },\n schema: {},\n guideline:\n 'Meter bar that fills toward the free-shipping goal, full when earned. Must be inside FreeShippingProgressRoot. See free-shipping-progress.md.',\n});\n\nexport const FreeShippingProgressMessageDefinition = defineComponent<FreeShippingProgressMessageProps>({\n name: 'FreeShippingProgressMessage',\n importPath: '@vtex/faststore/components',\n component: FreeShippingProgressMessage,\n editor: { label: 'Free Shipping Progress Message', category: 'commerce' },\n schema: {\n label: { type: 'string', default: 'faltam {value} para frete grátis', label: 'Below-threshold Label' },\n qualifiedLabel: { type: 'string', default: 'você ganhou frete grátis!', label: 'Qualified Label' },\n },\n guideline:\n 'Below-threshold vs earned copy, front-formatted from the active storefront context. Must be inside FreeShippingProgressRoot. See free-shipping-progress.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function FreeShippingProgressRoot({\n threshold,\n remaining,\n qualified,\n cartTotal,\n $data: $dataProp,\n className,\n children,\n ...props\n}: FreeShippingProgressRootProps) {\n // Only the synthesized preview cart below needs it; the leaves read the context where they format.\n const { currency } = useStorefrontContext();\n const $data = useDataOverride($dataProp);\n\n // Story / presentational overrides synthesize the field when no live cart is present.\n const cart =\n $data ??\n (qualified !== undefined || threshold !== undefined || remaining !== undefined || cartTotal !== undefined\n ? {\n id: 'preview',\n total: { amount: cartTotal ?? 0, currencyCode: currency },\n freeShippingProgress: {\n threshold: threshold != null ? { amount: threshold, currencyCode: currency } : null,\n remaining: { amount: remaining ?? 0, currencyCode: currency },\n qualified: qualified ?? false,\n },\n }\n : null);\n\n const fsp = cart?.freeShippingProgress;\n if (!cart || fsp == null) return null;\n\n const parsedThreshold = fsp.threshold != null ? Number(fsp.threshold.amount) : null;\n const cartTotalAmount = Number(cart.total?.amount ?? 0);\n // Usable threshold: present, finite, positive. Null/NaN/zero → no fill, no remaining.\n const thresholdAmount =\n parsedThreshold != null && Number.isFinite(parsedThreshold) && parsedThreshold > 0 ? parsedThreshold : null;\n\n const isQualified = fsp.qualified || (thresholdAmount != null && cartTotalAmount >= thresholdAmount);\n const liveRemaining = isQualified || thresholdAmount == null ? 0 : Math.max(thresholdAmount - cartTotalAmount, 0);\n const fill = isQualified ? 1 : thresholdAmount != null ? clamp01(cartTotalAmount / thresholdAmount) : 0;\n\n const value: FreeShippingProgressContextValue = { isQualified, fill, liveRemaining };\n\n return (\n <FreeShippingProgressContext value={value}>\n <div {...props} className={className} data-fs-free-shipping-progress-root=\"\">\n {children}\n </div>\n </FreeShippingProgressContext>\n );\n}\n\nexport function FreeShippingProgressBar({ className, ...props }: FreeShippingProgressBarProps) {\n const { fill, isQualified, liveRemaining } = useFreeShippingProgress();\n const { locale, currency } = useStorefrontContext();\n const ariaValueText = isQualified\n ? 'frete grátis conquistado'\n : `${formatCurrency(liveRemaining, locale, currency)} restantes para frete grátis`;\n\n return (\n <Meter.Root\n {...props}\n value={Math.round(fill * 100)}\n min={0}\n max={100}\n getAriaValueText={() => ariaValueText}\n className={className}\n data-fs-free-shipping-progress-bar=\"\"\n >\n <Meter.Track>\n <Meter.Indicator />\n </Meter.Track>\n </Meter.Root>\n );\n}\n\nexport function FreeShippingProgressMessage({\n label = 'faltam {value} para frete grátis',\n qualifiedLabel = 'você ganhou frete grátis!',\n className,\n children,\n ...props\n}: FreeShippingProgressMessageProps) {\n const { isQualified, liveRemaining } = useFreeShippingProgress();\n const { locale, currency } = useStorefrontContext();\n const text = isQualified ? qualifiedLabel : label.replace('{value}', formatCurrency(liveRemaining, locale, currency));\n\n return (\n <div {...props} className={className} data-fs-free-shipping-progress-message=\"\">\n {children ?? text}\n </div>\n );\n}\n\nexport const FreeShippingProgress = {\n Root: FreeShippingProgressRoot,\n Bar: FreeShippingProgressBar,\n Message: FreeShippingProgressMessage,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"free-shipping-progress.js","names":[],"sources":["../../../src/matter/free-shipping-progress/free-shipping-progress.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext } from 'react';\nimport { Meter } from '../../particles/meter/meter.js';\nimport { useStorefrontContext } from '../../sdk/context/storefront-context.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { isFiniteAmount } from '../cart/cart.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype MoneyValue = { amount: number | string; currencyCode: string };\n\ntype FreeShippingProgressData = {\n cart?: {\n id: string;\n totalizers?: { id: string; value: MoneyValue }[] | null;\n freeShippingProgress?: {\n threshold: MoneyValue | null;\n remaining: MoneyValue;\n qualified: boolean;\n } | null;\n } | null;\n};\n\ntype FreeShippingProgressContextValue = {\n isQualified: boolean;\n fill: number;\n liveRemaining: number;\n};\n\nexport type FreeShippingProgressRootProps = ComponentPropsWithRef<'div'> & {\n threshold?: number;\n remaining?: number;\n qualified?: boolean;\n itemsTotal?: number;\n $data?: FreeShippingProgressData['cart'] | null;\n};\n\nexport type FreeShippingProgressBarProps = ComponentPropsWithRef<'div'>;\nexport type FreeShippingProgressMessageProps = ComponentPropsWithRef<'div'> & {\n label?: string;\n qualifiedLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — narrow cart slice (Items totalizer + freeShippingProgress)\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment FreeShippingProgressData on Query {\n cart {\n id\n totalizers {\n id\n value {\n amount\n currencyCode\n }\n }\n freeShippingProgress {\n threshold {\n amount\n currencyCode\n }\n remaining {\n amount\n currencyCode\n }\n qualified\n }\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst FSP_CONTEXT_UNSET = Symbol('FreeShippingProgressContextUnset');\n\nconst FreeShippingProgressContext = createContext<FreeShippingProgressContextValue | typeof FSP_CONTEXT_UNSET>(\n FSP_CONTEXT_UNSET,\n);\n\nfunction useFreeShippingProgress(): FreeShippingProgressContextValue {\n const ctx = useContext(FreeShippingProgressContext);\n if (ctx === FSP_CONTEXT_UNSET) {\n throw new Error('useFreeShippingProgress() must be used inside a <FreeShippingProgressRoot> subtree.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatCurrency(value: number, locale: string, currency: string): string {\n return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(value);\n}\n\nfunction clamp01(value: number): number {\n return Math.min(Math.max(value, 0), 1);\n}\n\ntype FreeShippingProgressCart = NonNullable<FreeShippingProgressData['cart']>;\ntype FreeShippingProgressState = NonNullable<FreeShippingProgressCart['freeShippingProgress']>;\n\nfunction previewCart(\n {\n threshold,\n remaining,\n qualified,\n itemsTotal,\n }: Pick<FreeShippingProgressRootProps, 'threshold' | 'remaining' | 'qualified' | 'itemsTotal'>,\n currency: string,\n): FreeShippingProgressCart | null {\n if (qualified === undefined && threshold === undefined && remaining === undefined && itemsTotal === undefined) {\n return null;\n }\n return {\n id: 'preview',\n totalizers:\n itemsTotal === undefined ? [] : [{ id: 'Items', value: { amount: itemsTotal, currencyCode: currency } }],\n freeShippingProgress: {\n threshold: threshold == null ? null : { amount: threshold, currencyCode: currency },\n remaining: { amount: remaining ?? threshold ?? 0, currencyCode: currency },\n qualified: qualified ?? false,\n },\n };\n}\n\n// The server measures the Items totalizer, never the grand total (shipping and discounts do not count).\nfunction itemsSubtotal(cart: FreeShippingProgressCart): number | null {\n const itemsAmount = cart.totalizers?.find((totalizer) => totalizer.id === 'Items')?.value.amount;\n return isFiniteAmount(itemsAmount) ? Number(itemsAmount) : null;\n}\n\n// Usable threshold: present, finite, positive. Null/NaN/zero → no fill, no remaining.\nfunction usableThreshold(threshold: MoneyValue | null): number | null {\n if (threshold == null) return null;\n const parsed = Number(threshold.amount);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n\nfunction fillRatio(isQualified: boolean, thresholdAmount: number | null, covered: number): number {\n if (isQualified) return 1;\n if (thresholdAmount === null) return 0;\n return clamp01(covered / thresholdAmount);\n}\n\nfunction computeProgress(\n fsp: FreeShippingProgressState,\n items: number | null,\n thresholdAmount: number | null,\n): FreeShippingProgressContextValue {\n if (items === null) {\n // No usable Items amount: show the server's own answer rather than guess.\n const isQualified = fsp.qualified;\n const liveRemaining =\n !isQualified && isFiniteAmount(fsp.remaining.amount) ? Math.max(Number(fsp.remaining.amount), 0) : 0;\n const fill = fillRatio(isQualified, thresholdAmount, (thresholdAmount ?? 0) - liveRemaining);\n return { isQualified, fill, liveRemaining };\n }\n\n const isQualified = fsp.qualified || (thresholdAmount !== null && items >= thresholdAmount);\n const liveRemaining = isQualified || thresholdAmount === null ? 0 : Math.max(thresholdAmount - items, 0);\n return { isQualified, fill: fillRatio(isQualified, thresholdAmount, items), liveRemaining };\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const FreeShippingProgressRootDefinition = defineComponent<FreeShippingProgressRootProps>({\n name: 'FreeShippingProgressRoot',\n importPath: '@vtex/faststore/components',\n component: FreeShippingProgressRoot,\n fragment,\n fetchStrategy: 'reactive',\n editor: { label: 'Free Shipping Progress', category: 'commerce' },\n schema: {},\n guideline:\n 'Free-shipping progress bar for the top of a cart/mini-cart drawer: shows how much more to spend for free shipping and flips to an earned state when met. Renders nothing until a destination and an eligible promotion exist (self-hides on null). See free-shipping-progress.md.',\n});\n\nexport const FreeShippingProgressBarDefinition = defineComponent<FreeShippingProgressBarProps>({\n name: 'FreeShippingProgressBar',\n importPath: '@vtex/faststore/components',\n component: FreeShippingProgressBar,\n editor: { label: 'Free Shipping Progress Bar', category: 'commerce' },\n schema: {},\n guideline:\n 'Meter bar that fills toward the free-shipping goal, full when earned. Must be inside FreeShippingProgressRoot. See free-shipping-progress.md.',\n});\n\nexport const FreeShippingProgressMessageDefinition = defineComponent<FreeShippingProgressMessageProps>({\n name: 'FreeShippingProgressMessage',\n importPath: '@vtex/faststore/components',\n component: FreeShippingProgressMessage,\n editor: { label: 'Free Shipping Progress Message', category: 'commerce' },\n schema: {\n label: { type: 'string', default: 'faltam {value} para frete grátis', label: 'Below-threshold Label' },\n qualifiedLabel: { type: 'string', default: 'você ganhou frete grátis!', label: 'Qualified Label' },\n },\n guideline:\n 'Below-threshold vs earned copy, front-formatted from the active storefront context. Must be inside FreeShippingProgressRoot. See free-shipping-progress.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function FreeShippingProgressRoot({\n threshold,\n remaining,\n qualified,\n itemsTotal,\n $data: $dataProp,\n className,\n children,\n ...props\n}: FreeShippingProgressRootProps) {\n // Only the synthesized preview cart below needs it; the leaves read the context where they format.\n const { currency } = useStorefrontContext();\n const $data = useDataOverride($dataProp);\n\n // Story / presentational overrides synthesize the field when no live cart is present.\n const cart = $data ?? previewCart({ threshold, remaining, qualified, itemsTotal }, currency);\n\n const fsp = cart?.freeShippingProgress;\n if (!cart || fsp == null) return null;\n\n const { isQualified, fill, liveRemaining } = computeProgress(\n fsp,\n itemsSubtotal(cart),\n usableThreshold(fsp.threshold),\n );\n\n const value: FreeShippingProgressContextValue = { isQualified, fill, liveRemaining };\n\n return (\n <FreeShippingProgressContext value={value}>\n <div {...props} className={className} data-fs-free-shipping-progress-root=\"\">\n {children}\n </div>\n </FreeShippingProgressContext>\n );\n}\n\nexport function FreeShippingProgressBar({ className, ...props }: FreeShippingProgressBarProps) {\n const { fill, isQualified, liveRemaining } = useFreeShippingProgress();\n const { locale, currency } = useStorefrontContext();\n const ariaValueText = isQualified\n ? 'frete grátis conquistado'\n : `${formatCurrency(liveRemaining, locale, currency)} restantes para frete grátis`;\n\n return (\n <Meter.Root\n {...props}\n value={Math.round(fill * 100)}\n min={0}\n max={100}\n getAriaValueText={() => ariaValueText}\n className={className}\n data-fs-free-shipping-progress-bar=\"\"\n >\n <Meter.Track>\n <Meter.Indicator />\n </Meter.Track>\n </Meter.Root>\n );\n}\n\nexport function FreeShippingProgressMessage({\n label = 'faltam {value} para frete grátis',\n qualifiedLabel = 'você ganhou frete grátis!',\n className,\n children,\n ...props\n}: FreeShippingProgressMessageProps) {\n const { isQualified, liveRemaining } = useFreeShippingProgress();\n const { locale, currency } = useStorefrontContext();\n const text = isQualified ? qualifiedLabel : label.replace('{value}', formatCurrency(liveRemaining, locale, currency));\n\n return (\n <div {...props} className={className} data-fs-free-shipping-progress-message=\"\">\n {children ?? text}\n </div>\n );\n}\n\nexport const FreeShippingProgress = {\n Root: FreeShippingProgressRoot,\n Bar: FreeShippingProgressBar,\n Message: FreeShippingProgressMessage,\n};\n"],"mappings":"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"}