@vtex/faststore 0.0.4 → 0.0.6

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 (306) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/dist/actions.json +181 -0
  3. package/dist/catalog.json +1595 -481
  4. package/dist/client.d.ts +2 -1
  5. package/dist/client.js +2 -1
  6. package/dist/components.d.ts +31 -7
  7. package/dist/components.js +35 -13
  8. package/dist/content-types.json +73 -0
  9. package/dist/docs/access-code-sign-in.md +1 -1
  10. package/dist/docs/account-access-gate.md +153 -0
  11. package/dist/docs/account-entry-point.md +1 -2
  12. package/dist/docs/account-menu.md +164 -0
  13. package/dist/docs/address-summary.md +24 -3
  14. package/dist/docs/alert-dialog.md +204 -0
  15. package/dist/docs/billing-address.md +284 -0
  16. package/dist/docs/buy-button.md +1 -1
  17. package/dist/docs/card-entry.md +250 -0
  18. package/dist/docs/cardholder-document.md +142 -0
  19. package/dist/docs/cart.md +1 -1
  20. package/dist/docs/client-preferences-fieldset.md +148 -0
  21. package/dist/docs/client-preferences.md +2 -1
  22. package/dist/docs/client-profile-fieldset.md +184 -0
  23. package/dist/docs/client-profile.md +1 -1
  24. package/dist/docs/document-field.md +58 -4
  25. package/dist/docs/field.md +317 -0
  26. package/dist/docs/form.md +307 -0
  27. package/dist/docs/identity-edit.md +6 -6
  28. package/dist/docs/identity-summary.md +5 -5
  29. package/dist/docs/installments.md +248 -0
  30. package/dist/docs/my-account-profile.md +1 -0
  31. package/dist/docs/order-placed-payment-summary.md +138 -0
  32. package/dist/docs/payment-method-badges.md +232 -0
  33. package/dist/docs/payment-methods.md +1 -0
  34. package/dist/docs/payment.md +218 -0
  35. package/dist/docs/phone-field.md +58 -4
  36. package/dist/docs/place-order.md +157 -0
  37. package/dist/docs/save-card.md +186 -0
  38. package/dist/docs/saved-cards.md +218 -0
  39. package/dist/docs/select.md +465 -0
  40. package/dist/docs/session.md +1 -1
  41. package/dist/docs/shipping-address.md +110 -258
  42. package/dist/docs/sign-in-redirect.md +1 -1
  43. package/dist/index.d.ts +4 -1
  44. package/dist/internal/client.d.ts +3 -1
  45. package/dist/internal/client.js +3 -1
  46. package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
  47. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  48. package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
  49. package/dist/matter/account-access-gate/account-access-gate.js +283 -0
  50. package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
  51. package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
  52. package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
  53. package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
  54. package/dist/matter/account-entry-point/account-entry-point.js +2 -2
  55. package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
  56. package/dist/matter/account-menu/account-menu.d.ts +88 -0
  57. package/dist/matter/account-menu/account-menu.js +246 -0
  58. package/dist/matter/account-menu/account-menu.js.map +1 -0
  59. package/dist/matter/billing-address/billing-address.d.ts +165 -0
  60. package/dist/matter/billing-address/billing-address.js +408 -0
  61. package/dist/matter/billing-address/billing-address.js.map +1 -0
  62. package/dist/matter/card-entry/card-capture.d.ts +25 -0
  63. package/dist/matter/card-entry/card-capture.js +25 -0
  64. package/dist/matter/card-entry/card-capture.js.map +1 -0
  65. package/dist/matter/card-entry/card-entry.d.ts +120 -0
  66. package/dist/matter/card-entry/card-entry.js +832 -0
  67. package/dist/matter/card-entry/card-entry.js.map +1 -0
  68. package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
  69. package/dist/matter/cardholder-document/cardholder-document.js +70 -0
  70. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
  71. package/dist/matter/cart/cart.d.ts +1 -1
  72. package/dist/matter/cart/cart.js +2 -2
  73. package/dist/matter/cart/cart.js.map +1 -1
  74. package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
  75. package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
  76. package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
  77. package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
  78. package/dist/matter/client-preferences/client-preferences.js +6 -6
  79. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  80. package/dist/matter/client-profile/client-profile.d.ts +4 -6
  81. package/dist/matter/client-profile/client-profile.js +9 -6
  82. package/dist/matter/client-profile/client-profile.js.map +1 -1
  83. package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
  84. package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
  85. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
  86. package/dist/matter/delivery-options/delivery-options.js.map +1 -1
  87. package/dist/matter/identity-edit/identity-edit.js +2 -2
  88. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  89. package/dist/matter/identity-summary/identity-summary.js +2 -2
  90. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  91. package/dist/matter/installments/installments-select.d.ts +42 -0
  92. package/dist/matter/installments/installments-select.js +167 -0
  93. package/dist/matter/installments/installments-select.js.map +1 -0
  94. package/dist/matter/installments/installments.d.ts +58 -0
  95. package/dist/matter/installments/installments.js +176 -0
  96. package/dist/matter/installments/installments.js.map +1 -0
  97. package/dist/matter/installments/use-installments-selection.d.ts +29 -0
  98. package/dist/matter/installments/use-installments-selection.js +95 -0
  99. package/dist/matter/installments/use-installments-selection.js.map +1 -0
  100. package/dist/matter/my-account-profile/my-account-profile.js +5 -5
  101. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  102. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
  103. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
  104. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
  105. package/dist/matter/payment/payment-card-capture.d.ts +19 -0
  106. package/dist/matter/payment/payment-card-capture.js +129 -0
  107. package/dist/matter/payment/payment-card-capture.js.map +1 -0
  108. package/dist/matter/payment/payment-controller.d.ts +24 -0
  109. package/dist/matter/payment/payment-controller.js +107 -0
  110. package/dist/matter/payment/payment-controller.js.map +1 -0
  111. package/dist/matter/payment/payment-method-badges.d.ts +57 -0
  112. package/dist/matter/payment/payment-method-badges.js +192 -0
  113. package/dist/matter/payment/payment-method-badges.js.map +1 -0
  114. package/dist/matter/payment/payment.d.ts +89 -0
  115. package/dist/matter/payment/payment.js +263 -0
  116. package/dist/matter/payment/payment.js.map +1 -0
  117. package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
  118. package/dist/matter/payment/use-payment-method-selection.js +458 -0
  119. package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
  120. package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
  121. package/dist/matter/payment/use-payment-submit-gate.js +144 -0
  122. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
  123. package/dist/matter/place-order/place-order.d.ts +74 -0
  124. package/dist/matter/place-order/place-order.js +379 -0
  125. package/dist/matter/place-order/place-order.js.map +1 -0
  126. package/dist/matter/product-list/product-list.d.ts +1 -0
  127. package/dist/matter/product-list/product-list.js.map +1 -1
  128. package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
  129. package/dist/matter/product-shelf/product-shelf.js +2 -0
  130. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  131. package/dist/matter/region/region.js +1 -1
  132. package/dist/matter/region/region.js.map +1 -1
  133. package/dist/matter/save-card/save-card.d.ts +49 -0
  134. package/dist/matter/save-card/save-card.js +188 -0
  135. package/dist/matter/save-card/save-card.js.map +1 -0
  136. package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
  137. package/dist/matter/saved-cards/saved-cards.js +387 -0
  138. package/dist/matter/saved-cards/saved-cards.js.map +1 -0
  139. package/dist/matter/shipping-address/country-descriptors.js +20 -10
  140. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  141. package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
  142. package/dist/matter/shipping-address/shipping-address.js +481 -792
  143. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  144. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  145. package/dist/matter/variant-selector/variant-selector.js +1 -1
  146. package/dist/particles/address-summary/address-summary.d.ts +24 -0
  147. package/dist/particles/address-summary/address-summary.js +83 -2
  148. package/dist/particles/address-summary/address-summary.js.map +1 -1
  149. package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
  150. package/dist/particles/alert-dialog/alert-dialog.js +187 -0
  151. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
  152. package/dist/particles/checkbox/checkbox.d.ts +7 -3
  153. package/dist/particles/checkbox/checkbox.js +4 -1
  154. package/dist/particles/checkbox/checkbox.js.map +1 -1
  155. package/dist/particles/document-field/document-field.d.ts +48 -5
  156. package/dist/particles/document-field/document-field.js +189 -32
  157. package/dist/particles/document-field/document-field.js.map +1 -1
  158. package/dist/particles/field/bound-field-registry.js +52 -0
  159. package/dist/particles/field/bound-field-registry.js.map +1 -0
  160. package/dist/particles/field/field.d.ts +116 -0
  161. package/dist/particles/field/field.js +497 -6
  162. package/dist/particles/field/field.js.map +1 -1
  163. package/dist/particles/fieldset/fieldset.js +2 -2
  164. package/dist/particles/fieldset/fieldset.js.map +1 -1
  165. package/dist/particles/footer-social/footer-social.js +1 -1
  166. package/dist/particles/form/form.d.ts +104 -0
  167. package/dist/particles/form/form.js +485 -0
  168. package/dist/particles/form/form.js.map +1 -0
  169. package/dist/particles/incentives/incentives.js +1 -1
  170. package/dist/particles/phone-field/phone-field.d.ts +38 -10
  171. package/dist/particles/phone-field/phone-field.js +183 -39
  172. package/dist/particles/phone-field/phone-field.js.map +1 -1
  173. package/dist/particles/postal-code-field/postal-code-field.js +2 -2
  174. package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
  175. package/dist/particles/select/field-select.d.ts +43 -0
  176. package/dist/particles/select/field-select.js +150 -0
  177. package/dist/particles/select/field-select.js.map +1 -0
  178. package/dist/particles/select/select.d.ts +2 -0
  179. package/dist/particles/select/select.js.map +1 -1
  180. package/dist/particles/shared/compose-refs.js +21 -0
  181. package/dist/particles/shared/compose-refs.js.map +1 -0
  182. package/dist/schema.graphql +701 -2
  183. package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
  184. package/dist/sdk/actions/analytics-action-provider.js +19 -0
  185. package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
  186. package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
  187. package/dist/sdk/actions/cart-action-provider.js +1 -1
  188. package/dist/sdk/actions/cart-action-provider.js.map +1 -1
  189. package/dist/sdk/actions/index.d.ts +3 -1
  190. package/dist/sdk/actions/index.js +3 -1
  191. package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
  192. package/dist/sdk/actions/payment-action-provider.js +26 -0
  193. package/dist/sdk/actions/payment-action-provider.js.map +1 -0
  194. package/dist/sdk/actions/session-action-provider.d.ts +1 -1
  195. package/dist/sdk/actions/session-action-provider.js +1 -1
  196. package/dist/sdk/actions/session-action-provider.js.map +1 -1
  197. package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
  198. package/dist/sdk/actions/shipping-action-provider.js +1 -1
  199. package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
  200. package/dist/sdk/actions/types.d.ts +6 -2
  201. package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
  202. package/dist/sdk/analytics/analytics-destination.js +90 -0
  203. package/dist/sdk/analytics/analytics-destination.js.map +1 -0
  204. package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
  205. package/dist/sdk/analytics/use-analytics-actions.js +108 -0
  206. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
  207. package/dist/sdk/cache/storefront-cache.js +1 -1
  208. package/dist/sdk/cache/storefront-cache.js.map +1 -1
  209. package/dist/sdk/cart/operations.d.ts +4 -4
  210. package/dist/sdk/cart/operations.js +1 -1
  211. package/dist/sdk/cart/operations.js.map +1 -1
  212. package/dist/sdk/cart/optimistic.js +1 -1
  213. package/dist/sdk/cart/optimistic.js.map +1 -1
  214. package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
  215. package/dist/sdk/cart/use-cart-actions.js +4 -4
  216. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  217. package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
  218. package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
  219. package/dist/sdk/cart/write-gate.js +1 -1
  220. package/dist/sdk/cart/write-gate.js.map +1 -1
  221. package/dist/sdk/data/entity-resolution-context.js +31 -0
  222. package/dist/sdk/data/entity-resolution-context.js.map +1 -0
  223. package/dist/sdk/data/entity-subscriber.js +18 -8
  224. package/dist/sdk/data/entity-subscriber.js.map +1 -1
  225. package/dist/sdk/data/index.js +1 -0
  226. package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
  227. package/dist/sdk/data/page-reactive-data-root.js +4 -2
  228. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  229. package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
  230. package/dist/sdk/data/reactive-data-provider.js +8 -4
  231. package/dist/sdk/data/reactive-data-provider.js.map +1 -1
  232. package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
  233. package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
  234. package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
  235. package/dist/sdk/data/use-reactive-data-settled.js +39 -0
  236. package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
  237. package/dist/sdk/data/use-reactive-entity.js +6 -2
  238. package/dist/sdk/data/use-reactive-entity.js.map +1 -1
  239. package/dist/sdk/generated/fragment-types.js +2 -0
  240. package/dist/sdk/generated/fragment-types.js.map +1 -1
  241. package/dist/sdk/generated/gql.js +2 -1
  242. package/dist/sdk/generated/gql.js.map +1 -1
  243. package/dist/sdk/generated/graphql.js +85 -1
  244. package/dist/sdk/generated/graphql.js.map +1 -1
  245. package/dist/sdk/i18n/messages/en-US.js +3 -0
  246. package/dist/sdk/i18n/messages/en-US.json +3 -0
  247. package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
  248. package/dist/sdk/i18n/messages/es-ES.js +3 -0
  249. package/dist/sdk/i18n/messages/es-ES.json +3 -0
  250. package/dist/sdk/i18n/messages/pt-BR.js +3 -0
  251. package/dist/sdk/i18n/messages/pt-BR.json +3 -0
  252. package/dist/sdk/identity/operations.js.map +1 -1
  253. package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
  254. package/dist/sdk/identity/use-identity-actions.js +3 -2
  255. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  256. package/dist/sdk/payment/gateway-origin.js +35 -0
  257. package/dist/sdk/payment/gateway-origin.js.map +1 -0
  258. package/dist/sdk/payment/gateway.d.ts +85 -0
  259. package/dist/sdk/payment/gateway.js +183 -0
  260. package/dist/sdk/payment/gateway.js.map +1 -0
  261. package/dist/sdk/payment/operations.d.ts +52 -0
  262. package/dist/sdk/payment/operations.js +223 -0
  263. package/dist/sdk/payment/operations.js.map +1 -0
  264. package/dist/sdk/payment/security-code-mask.js +71 -0
  265. package/dist/sdk/payment/security-code-mask.js.map +1 -0
  266. package/dist/sdk/payment/submission-registry.d.ts +33 -0
  267. package/dist/sdk/payment/submission-registry.js +58 -0
  268. package/dist/sdk/payment/submission-registry.js.map +1 -0
  269. package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
  270. package/dist/sdk/payment/use-payment-actions.js +276 -0
  271. package/dist/sdk/payment/use-payment-actions.js.map +1 -0
  272. package/dist/sdk/payment/use-place-order.d.ts +65 -0
  273. package/dist/sdk/payment/use-place-order.js +640 -0
  274. package/dist/sdk/payment/use-place-order.js.map +1 -0
  275. package/dist/sdk/session/auth-operations.d.ts +1 -1
  276. package/dist/sdk/session/auth-operations.js.map +1 -1
  277. package/dist/sdk/session/use-session-actions.d.ts +1 -1
  278. package/dist/sdk/session/use-session-actions.js.map +1 -1
  279. package/dist/sdk/session/use-session-revalidation.js +65 -0
  280. package/dist/sdk/session/use-session-revalidation.js.map +1 -0
  281. package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
  282. package/dist/sdk/shipping/option-select-operations.js.map +1 -1
  283. package/dist/sdk/shipping/request-timeout.js +1 -1
  284. package/dist/sdk/shipping/request-timeout.js.map +1 -1
  285. package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
  286. package/dist/sdk/shipping/shipping-operations.js.map +1 -1
  287. package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
  288. package/dist/sdk/shipping/use-shipping-actions.js +1 -1
  289. package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
  290. package/dist/sdk/state/cart.d.ts +3 -4
  291. package/dist/sdk/state/cart.js +6 -7
  292. package/dist/sdk/state/cart.js.map +1 -1
  293. package/dist/utils/loggable-error-name.js +49 -0
  294. package/dist/utils/loggable-error-name.js.map +1 -0
  295. package/dist/utils/resolve-authored-copy.js +18 -0
  296. package/dist/utils/resolve-authored-copy.js.map +1 -0
  297. package/dist/utils/safe-href.js +3 -3
  298. package/dist/utils/safe-href.js.map +1 -1
  299. package/dist/utils/substitute-template.js +33 -0
  300. package/dist/utils/substitute-template.js.map +1 -0
  301. package/package.json +2 -1
  302. package/src/styles/tailwind.css +34 -0
  303. package/dist/docs/client-profile-form.md +0 -192
  304. package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
  305. package/dist/matter/client-profile-form/client-profile-form.js +0 -662
  306. package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
@@ -0,0 +1,299 @@
1
+ "use client";
2
+ import { defineComponent } from "../../core/dist/define-component.js";
3
+ import "../../core/dist/index.js";
4
+ import { Checkbox } from "../../particles/checkbox/checkbox.js";
5
+ import { Field, FieldBinding, FieldsetFieldContext, useFieldsetField, useFieldsetFieldNames, useNoAncestorFieldset } from "../../particles/field/field.js";
6
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
7
+ import { useDataOverride } from "../../sdk/data/data-override-context.js";
8
+ import { useSession } from "../session/session.js";
9
+ import { Icon } from "../../particles/icon/icon.js";
10
+ import { Fieldset } from "../../particles/fieldset/fieldset.js";
11
+ import { useFormRegistry } from "../../particles/form/form.js";
12
+ import { useEntityDataResolved } from "../../sdk/data/entity-resolution-context.js";
13
+ import { useReactiveDataSettled } from "../../sdk/data/use-reactive-data-settled.js";
14
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
15
+ import { jsx, jsxs } from "react/jsx-runtime";
16
+ import { useFormContext } from "react-hook-form";
17
+ //#region src/matter/client-preferences/client-preferences-fieldset.tsx
18
+ /**
19
+ * `ClientPreferencesFieldset` — the composable, one-Save write of the `ClientPreferences` sibling
20
+ * entity (RFC: Composable Entity Forms, `Proposal > Design > The fieldset`). It registers itself with
21
+ * the enclosing `FormRoot` under the `clientPreferences` namespace and dispatches
22
+ * `$actions.identity.clientPreferencesUpdate` for its own submit slot; it renders no `<form>` of its
23
+ * own.
24
+ *
25
+ * `ClientPreferencesRoot` (`client-preferences.tsx`) already reads both flags and writes on toggle; this
26
+ * fieldset is a second, batched write timing for the same two flags. To keep the two from compiling into
27
+ * each other's container, the shipped self-persisting matter's context is renamed to
28
+ * `client-preferences-toggle`, freeing `client-preferences` for this fieldset. Placing
29
+ * `NewsletterOptInControl` inside this fieldset, or `ClientPreferencesNewsletterControl` inside
30
+ * `ClientPreferencesRoot`, is therefore a build failure rather than a surprise write.
31
+ */
32
+ const fragment = `
33
+ fragment ClientPreferencesFieldsetData on Query {
34
+ clientPreferences {
35
+ __typename
36
+ id
37
+ optinNewsLetter
38
+ savePersonalData
39
+ }
40
+ }
41
+ `;
42
+ /** No control publishes a rule today (Decision 6) — kept as a stable empty object so the context
43
+ * value's identity does not churn on every render. */
44
+ const NO_RULES = {};
45
+ /**
46
+ * Rows `ClientPreferencesFieldsetSkeleton` draws when nobody says otherwise: one per consent flag in
47
+ * the full anatomy. It is a default and not a derivation, because the composed children are opaque
48
+ * here. A page that composes only one control sets `skeletonCount` to 1, or the form jumps by a row
49
+ * when the controls take the skeleton's place.
50
+ */
51
+ const CLIENT_PREFERENCES_SKELETON_ROWS = 2;
52
+ const CLIENT_PREFERENCES_FIELDSET_UNSET = Symbol("ClientPreferencesFieldsetContextUnset");
53
+ const ClientPreferencesFieldsetContext = createContext(CLIENT_PREFERENCES_FIELDSET_UNSET);
54
+ function useClientPreferencesFieldsetContext() {
55
+ const ctx = useContext(ClientPreferencesFieldsetContext);
56
+ if (ctx === CLIENT_PREFERENCES_FIELDSET_UNSET) throw new Error("ClientPreferencesFieldset controls must be used inside ClientPreferencesFieldset.");
57
+ return ctx;
58
+ }
59
+ const ClientPreferencesFieldsetDefinition = defineComponent({
60
+ name: "ClientPreferencesFieldset",
61
+ importPath: "@vtex/faststore/components",
62
+ component: ClientPreferencesFieldset,
63
+ fragment,
64
+ fetchStrategy: "reactive",
65
+ skeleton: ClientPreferencesFieldsetSkeleton,
66
+ requiresContext: ["form", "session"],
67
+ providesContext: "client-preferences",
68
+ actions: { identity: ["clientPreferencesUpdate"] },
69
+ editor: {
70
+ label: "Client Preferences Fieldset",
71
+ category: "identity"
72
+ },
73
+ schema: {
74
+ skeletonCount: {
75
+ type: "number",
76
+ default: 2,
77
+ label: "Skeleton rows"
78
+ },
79
+ loadingLabel: {
80
+ type: "string",
81
+ default: "Loading your preferences",
82
+ label: "Loading announcement"
83
+ },
84
+ loadedLabel: {
85
+ type: "string",
86
+ default: "Your preferences are ready",
87
+ label: "Loaded announcement"
88
+ },
89
+ groupLabel: {
90
+ type: "string",
91
+ default: "Your preferences",
92
+ label: "Accessible group name"
93
+ }
94
+ },
95
+ guideline: "Registers a batched write of the ClientPreferences entity with the enclosing FormRoot; renders one Fieldset.Root and no <form>. Distinct from ClientPreferencesRoot (matter/client-preferences), which writes on toggle and requires client-preferences-toggle — the two cannot compile into each other's container. Must be inside a FormRoot with a SessionRoot ancestor. Compose ClientPreferencesNewsletterControl and/or ClientPreferencesSavePersonalDataControl as children. The controls mount only once the preferences have 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 flags the shopper changed. groupLabel names the group as its accessible name; blank emits no aria-label, and a composed FieldsetLegend wins over it."
96
+ });
97
+ const ClientPreferencesNewsletterControlDefinition = defineComponent({
98
+ name: "ClientPreferencesNewsletterControl",
99
+ importPath: "@vtex/faststore/components",
100
+ component: ClientPreferencesNewsletterControl,
101
+ requiresContext: "client-preferences",
102
+ providesContext: "field",
103
+ editor: {
104
+ label: "Client Preferences Newsletter Control",
105
+ category: "identity"
106
+ },
107
+ schema: { label: {
108
+ type: "string",
109
+ label: "Label"
110
+ } },
111
+ guideline: "The bound newsletter opt-in control; must be a descendant of ClientPreferencesFieldset. Renders Field.Root > Field.Label > Field.Checkbox by default, or the composed children in the author's own order. Defaults to unchecked."
112
+ });
113
+ const ClientPreferencesSavePersonalDataControlDefinition = defineComponent({
114
+ name: "ClientPreferencesSavePersonalDataControl",
115
+ importPath: "@vtex/faststore/components",
116
+ component: ClientPreferencesSavePersonalDataControl,
117
+ requiresContext: "client-preferences",
118
+ providesContext: "field",
119
+ editor: {
120
+ label: "Client Preferences Save Personal Data Control",
121
+ category: "identity"
122
+ },
123
+ schema: { label: {
124
+ type: "string",
125
+ label: "Label"
126
+ } },
127
+ guideline: "The bound save-personal-data opt-in control; must be a descendant of ClientPreferencesFieldset. Renders Field.Root > Field.Label > Field.Checkbox by default, or the composed children in the author's own order. Defaults to unchecked."
128
+ });
129
+ /**
130
+ * Registers a batched write of the `ClientPreferences` entity with the enclosing `FormRoot`
131
+ * (`registerFieldset`, namespace `clientPreferences`) on mount, unconditionally — so this fieldset keeps
132
+ * its original dispatch slot even while its own query is still resolving. Its children — the seed values
133
+ * and the field-name registry its controls need — render only once the entity has resolved; until then
134
+ * the `Fieldset.Root` carries `-pending` and renders `ClientPreferencesFieldsetSkeleton` (Decision 5).
135
+ * `dispatch` and `toPayload` are `useCallback([])`-stable and read `$actions` through a ref, so the
136
+ * registration effect runs exactly once per mount regardless of how often the parent re-renders.
137
+ *
138
+ * "Resolved" is two conditions, because `$data` alone cannot answer it. Under the reactive runtime the
139
+ * slice is defined from the first render — `useReactiveEntity` hands down `fallbackData` while the cache
140
+ * read is incomplete — so an empty server answer looks exactly like a resolved one, and the checkboxes
141
+ * would mount seeded unchecked before the shopper's saved consent arrives. `useSeedAdoption` re-seeds a
142
+ * clean checkbox with `defaultValue` on every render, but once it turns dirty no later seed ever
143
+ * reaches it again: a shopper who toggles it before the real consent arrives locks that unchecked
144
+ * seed in for good, and even a shopper who does not toggle it sees the checkbox flash to the saved
145
+ * value. `useReactiveDataSettled` supplies
146
+ * the missing half: the page query's own in-flight state, latched so it only ever goes pending →
147
+ * settled. The `$data === undefined` half still carries trees with no reactive root above them, where
148
+ * the slice arrives as a plain prop.
149
+ *
150
+ * Both ends of that wait are spoken: the fieldset carries `aria-busy` while it lasts and holds a polite
151
+ * live region that announces `loadingLabel` and then `loadedLabel`. Style the region `sr-only` — it is
152
+ * text meant for a screen reader, not for the page.
153
+ */
154
+ function ClientPreferencesFieldset({ skeletonCount = CLIENT_PREFERENCES_SKELETON_ROWS, loadingLabel = "Loading your preferences", loadedLabel = "Your preferences are ready", groupLabel = "Your preferences", className, children, $data: $dataProp, $actions: $actionsProp }) {
155
+ useNoAncestorFieldset("ClientPreferencesFieldset");
156
+ const $data = useDataOverride($dataProp);
157
+ const $actions = useActionsOverride($actionsProp);
158
+ const settled = useReactiveDataSettled();
159
+ const sliceResolved = useEntityDataResolved();
160
+ const resolved = settled && sliceResolved && $data !== void 0;
161
+ const { canEditData } = useSession();
162
+ const readOnly = !canEditData;
163
+ const { registerFieldset } = useFormRegistry();
164
+ const { getFieldState } = useFormContext();
165
+ const latest = useRef({
166
+ $actions,
167
+ readOnly,
168
+ getFieldState
169
+ });
170
+ useEffect(() => {
171
+ latest.current = {
172
+ $actions,
173
+ readOnly,
174
+ getFieldState
175
+ };
176
+ }, [
177
+ $actions,
178
+ readOnly,
179
+ getFieldState
180
+ ]);
181
+ const { registry: fieldsetRegistry, getFieldNames } = useFieldsetFieldNames("clientPreferences");
182
+ const dispatch = useCallback(async (payload) => {
183
+ if (latest.current.readOnly) throw new Error("ClientPreferencesFieldset cannot dispatch while the session is read-only.");
184
+ const update = latest.current.$actions?.identity?.clientPreferencesUpdate;
185
+ if (!update) throw new Error("ClientPreferencesFieldset requires $actions.identity.clientPreferencesUpdate.");
186
+ return update(payload);
187
+ }, []);
188
+ const toPayload = useCallback((values) => {
189
+ const { getFieldState } = latest.current;
190
+ const isDirty = (key) => getFieldState(`clientPreferences.${key}`).isDirty;
191
+ const payload = {};
192
+ if ("optinNewsLetter" in values && isDirty("optinNewsLetter")) payload.optinNewsLetter = Boolean(values.optinNewsLetter);
193
+ if ("savePersonalData" in values && isDirty("savePersonalData")) payload.savePersonalData = Boolean(values.savePersonalData);
194
+ return payload;
195
+ }, []);
196
+ useEffect(() => registerFieldset({
197
+ namespace: "clientPreferences",
198
+ getFieldNames,
199
+ dispatch,
200
+ toPayload,
201
+ label: "client-preferences"
202
+ }), [
203
+ registerFieldset,
204
+ getFieldNames,
205
+ dispatch,
206
+ toPayload
207
+ ]);
208
+ const saved = useMemo(() => ({
209
+ optinNewsLetter: Boolean($data?.optinNewsLetter ?? false),
210
+ savePersonalData: Boolean($data?.savePersonalData ?? false)
211
+ }), [$data]);
212
+ const contextValue = useMemo(() => ({
213
+ namespace: "clientPreferences",
214
+ saved,
215
+ readOnly,
216
+ rules: NO_RULES
217
+ }), [saved, readOnly]);
218
+ const [announcement, setAnnouncement] = useState("");
219
+ const announced = resolved ? loadedLabel : loadingLabel;
220
+ useEffect(() => {
221
+ setAnnouncement(announced);
222
+ }, [announced]);
223
+ return /* @__PURE__ */ jsxs(Fieldset.Root, {
224
+ className,
225
+ disabled: readOnly,
226
+ "aria-busy": resolved ? void 0 : true,
227
+ ...groupLabel?.trim() ? { "aria-label": groupLabel } : {},
228
+ "data-fs-client-preferences-fieldset": "",
229
+ ...resolved ? {} : { "data-fs-client-preferences-fieldset-pending": "" },
230
+ ...readOnly ? { "data-fs-client-preferences-fieldset-readonly": "" } : {},
231
+ children: [resolved ? /* @__PURE__ */ jsx(FieldsetFieldContext.Provider, {
232
+ value: fieldsetRegistry,
233
+ children: /* @__PURE__ */ jsx(ClientPreferencesFieldsetContext.Provider, {
234
+ value: contextValue,
235
+ children
236
+ })
237
+ }) : /* @__PURE__ */ jsx(ClientPreferencesFieldsetSkeleton, { count: skeletonCount }), /* @__PURE__ */ jsx("output", {
238
+ "aria-live": "polite",
239
+ "data-fs-client-preferences-fieldset-announcement": "",
240
+ children: announcement
241
+ })]
242
+ });
243
+ }
244
+ /**
245
+ * The loading state of the fieldset, rendered in place of the controls until the entity resolves and
246
+ * declared as the definition's `skeleton`. Purely visual: the wait is spoken by the fieldset's own live
247
+ * region, so this is hidden from assistive technology rather than announced twice.
248
+ */
249
+ function ClientPreferencesFieldsetSkeleton({ count = CLIENT_PREFERENCES_SKELETON_ROWS, className, ...props }) {
250
+ return /* @__PURE__ */ jsx("div", {
251
+ ...props,
252
+ className,
253
+ "data-fs-client-preferences-fieldset-skeleton": "",
254
+ "aria-hidden": "true",
255
+ children: Array.from({ length: count }, (_, i) => /* @__PURE__ */ jsx("span", { "data-fs-client-preferences-fieldset-skeleton-field": "" }, i))
256
+ });
257
+ }
258
+ ClientPreferencesFieldset.Skeleton = ClientPreferencesFieldsetSkeleton;
259
+ function PreferenceControl({ fieldKey, attr, defaultLabel, label, className, children }) {
260
+ const { namespace, saved, readOnly, rules } = useClientPreferencesFieldsetContext();
261
+ const name = useFieldsetField(namespace, fieldKey);
262
+ const resolvedLabel = label ?? defaultLabel;
263
+ return /* @__PURE__ */ jsx(FieldBinding, {
264
+ name,
265
+ label: resolvedLabel,
266
+ defaultValue: saved[fieldKey],
267
+ rule: rules[fieldKey],
268
+ disabled: readOnly,
269
+ children: /* @__PURE__ */ jsx(Field.Root, {
270
+ className,
271
+ "data-fs-client-preferences-fieldset-field": attr,
272
+ children: children ?? /* @__PURE__ */ jsxs(Field.Label, { children: [/* @__PURE__ */ jsx(Field.Checkbox, { children: /* @__PURE__ */ jsx(Checkbox.Indicator, { children: /* @__PURE__ */ jsx(Icon, { name: "checked" }) }) }), resolvedLabel] })
273
+ })
274
+ });
275
+ }
276
+ function ClientPreferencesNewsletterControl({ label, className, children }) {
277
+ return /* @__PURE__ */ jsx(PreferenceControl, {
278
+ fieldKey: "optinNewsLetter",
279
+ attr: "newsletter",
280
+ defaultLabel: "Email me with news and offers",
281
+ label,
282
+ className,
283
+ children
284
+ });
285
+ }
286
+ function ClientPreferencesSavePersonalDataControl({ label, className, children }) {
287
+ return /* @__PURE__ */ jsx(PreferenceControl, {
288
+ fieldKey: "savePersonalData",
289
+ attr: "save-personal-data",
290
+ defaultLabel: "Save my information for next time",
291
+ label,
292
+ className,
293
+ children
294
+ });
295
+ }
296
+ //#endregion
297
+ export { ClientPreferencesFieldset, ClientPreferencesFieldsetDefinition, ClientPreferencesFieldsetSkeleton, ClientPreferencesNewsletterControl, ClientPreferencesNewsletterControlDefinition, ClientPreferencesSavePersonalDataControl, ClientPreferencesSavePersonalDataControlDefinition };
298
+
299
+ //# sourceMappingURL=client-preferences-fieldset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-preferences-fieldset.js","names":[],"sources":["../../../src/matter/client-preferences/client-preferences-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 { Checkbox } from '../../particles/checkbox/checkbox.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 { Icon } from '../../particles/icon/icon.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 { ClientPreferenceKey, ClientPreferencesUpdateInput } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * `ClientPreferencesFieldset` — the composable, one-Save write of the `ClientPreferences` sibling\n * entity (RFC: Composable Entity Forms, `Proposal > Design > The fieldset`). It registers itself with\n * the enclosing `FormRoot` under the `clientPreferences` namespace and dispatches\n * `$actions.identity.clientPreferencesUpdate` for its own submit slot; it renders no `<form>` of its\n * own.\n *\n * `ClientPreferencesRoot` (`client-preferences.tsx`) already reads both flags and writes on toggle; this\n * fieldset is a second, batched write timing for the same two flags. To keep the two from compiling into\n * each other's container, the shipped self-persisting matter's context is renamed to\n * `client-preferences-toggle`, freeing `client-preferences` for this fieldset. Placing\n * `NewsletterOptInControl` inside this fieldset, or `ClientPreferencesNewsletterControl` inside\n * `ClientPreferencesRoot`, is therefore a build failure rather than a surprise write.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on the fieldset, its own data root\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientPreferencesFieldsetData on Query {\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the ClientPreferences sibling entity, unwrapped. */\nexport type ClientPreferencesFieldsetData = {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n} | null;\n\n/** What the fieldset publishes to its controls. Carries no live value — react-hook-form owns those. */\ntype ClientPreferencesFieldsetContextValue = Readonly<{\n namespace: 'clientPreferences';\n /** Seeds, resolved from `$data`. `null` (not chosen yet) reads as unchecked, per the opt-in default. */\n saved: Readonly<Record<ClientPreferenceKey, boolean>>;\n /** True when the session reports `canEditData: false`. */\n readOnly: boolean;\n /** The fieldset's own validation rules, per key. Present and empty today (Decision 6). */\n rules: Readonly<Partial<Record<ClientPreferenceKey, FieldRule>>>;\n}>;\n\n/** No control publishes a rule today (Decision 6) — kept as a stable empty object so the context\n * value's identity does not churn on every render. */\nconst NO_RULES: Readonly<Partial<Record<ClientPreferenceKey, FieldRule>>> = {};\n\n/**\n * Rows `ClientPreferencesFieldsetSkeleton` draws when nobody says otherwise: one per consent flag in\n * the full anatomy. It is a default and not a derivation, because the composed children are opaque\n * here. A page that composes only one control sets `skeletonCount` to 1, or the form jumps by a row\n * when the controls take the skeleton's place.\n */\nconst CLIENT_PREFERENCES_SKELETON_ROWS = 2;\n\nconst CLIENT_PREFERENCES_FIELDSET_UNSET = Symbol('ClientPreferencesFieldsetContextUnset');\n\nconst ClientPreferencesFieldsetContext = createContext<\n ClientPreferencesFieldsetContextValue | typeof CLIENT_PREFERENCES_FIELDSET_UNSET\n>(CLIENT_PREFERENCES_FIELDSET_UNSET);\n\nfunction useClientPreferencesFieldsetContext(): ClientPreferencesFieldsetContextValue {\n const ctx = useContext(ClientPreferencesFieldsetContext);\n if (ctx === CLIENT_PREFERENCES_FIELDSET_UNSET) {\n throw new Error('ClientPreferencesFieldset controls must be used inside ClientPreferencesFieldset.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type ClientPreferencesFieldsetProps = {\n /** Placeholder rows the loading skeleton draws. Set it to the number of controls composed here. */\n skeletonCount?: number;\n /** Announced politely while the preferences resolve. 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 /** `ClientPreferencesFieldsetData` slice forwarded by the reactive runtime. */\n $data?: ClientPreferencesFieldsetData;\n /** Compiler-injected; carries `identity.clientPreferencesUpdate`. */\n $actions?: ComponentActions;\n};\n\ntype PreferenceControlProps = { label?: string; className?: string; children?: ReactNode };\n\nexport type ClientPreferencesNewsletterControlProps = PreferenceControlProps;\nexport type ClientPreferencesSavePersonalDataControlProps = PreferenceControlProps;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferencesFieldsetDefinition = defineComponent<ClientPreferencesFieldsetProps>({\n name: 'ClientPreferencesFieldset',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesFieldset,\n fragment,\n fetchStrategy: 'reactive',\n skeleton: ClientPreferencesFieldsetSkeleton,\n requiresContext: ['form', 'session'],\n providesContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Client Preferences Fieldset', category: 'identity' },\n schema: {\n skeletonCount: { type: 'number', default: 2, label: 'Skeleton rows' },\n loadingLabel: { type: 'string', default: 'Loading your preferences', label: 'Loading announcement' },\n loadedLabel: { type: 'string', default: 'Your preferences are ready', label: 'Loaded announcement' },\n groupLabel: { type: 'string', default: 'Your preferences', label: 'Accessible group name' },\n },\n guideline:\n \"Registers a batched write of the ClientPreferences entity with the enclosing FormRoot; renders one Fieldset.Root and no <form>. Distinct from ClientPreferencesRoot (matter/client-preferences), which writes on toggle and requires client-preferences-toggle — the two cannot compile into each other's container. Must be inside a FormRoot with a SessionRoot ancestor. Compose ClientPreferencesNewsletterControl and/or ClientPreferencesSavePersonalDataControl as children. The controls mount only once the preferences have 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 flags 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 ClientPreferencesNewsletterControlDefinition = defineComponent<ClientPreferencesNewsletterControlProps>({\n name: 'ClientPreferencesNewsletterControl',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesNewsletterControl,\n requiresContext: 'client-preferences',\n providesContext: 'field',\n editor: { label: 'Client Preferences Newsletter 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 },\n guideline:\n \"The bound newsletter opt-in control; must be a descendant of ClientPreferencesFieldset. Renders Field.Root > Field.Label > Field.Checkbox by default, or the composed children in the author's own order. Defaults to unchecked.\",\n});\n\nexport const ClientPreferencesSavePersonalDataControlDefinition =\n defineComponent<ClientPreferencesSavePersonalDataControlProps>({\n name: 'ClientPreferencesSavePersonalDataControl',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesSavePersonalDataControl,\n requiresContext: 'client-preferences',\n providesContext: 'field',\n editor: { label: 'Client Preferences Save Personal Data 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 },\n guideline:\n \"The bound save-personal-data opt-in control; must be a descendant of ClientPreferencesFieldset. Renders Field.Root > Field.Label > Field.Checkbox by default, or the composed children in the author's own order. Defaults to unchecked.\",\n });\n\n// ---------------------------------------------------------------------------\n// Fieldset root\n// ---------------------------------------------------------------------------\n\n/**\n * Registers a batched write of the `ClientPreferences` entity with the enclosing `FormRoot`\n * (`registerFieldset`, namespace `clientPreferences`) on mount, unconditionally — so this fieldset keeps\n * its original dispatch slot even while its own query is still resolving. Its children — the seed values\n * and the field-name registry its controls need — render only once the entity has resolved; until then\n * the `Fieldset.Root` carries `-pending` and renders `ClientPreferencesFieldsetSkeleton` (Decision 5).\n * `dispatch` and `toPayload` are `useCallback([])`-stable and read `$actions` through a ref, so the\n * registration effect runs exactly once per mount regardless of how often the 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 checkboxes\n * would mount seeded unchecked before the shopper's saved consent arrives. `useSeedAdoption` re-seeds a\n * clean checkbox with `defaultValue` on every render, but once it turns dirty no later seed ever\n * reaches it again: a shopper who toggles it before the real consent arrives locks that unchecked\n * seed in for good, and even a shopper who does not toggle it sees the checkbox flash to the saved\n * value. `useReactiveDataSettled` supplies\n * the missing half: the page query's own in-flight state, latched so it only ever goes pending →\n * settled. The `$data === undefined` half still carries trees with no reactive root above them, where\n * 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 ClientPreferencesFieldset({\n skeletonCount = CLIENT_PREFERENCES_SKELETON_ROWS,\n loadingLabel = 'Loading your preferences',\n loadedLabel = 'Your preferences are ready',\n groupLabel = 'Your preferences',\n className,\n children,\n $data: $dataProp,\n $actions: $actionsProp,\n}: ClientPreferencesFieldsetProps) {\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('ClientPreferencesFieldset');\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, readOnly, getFieldState });\n useEffect(() => {\n latest.current = { $actions, readOnly, getFieldState };\n }, [$actions, readOnly, getFieldState]);\n\n const { registry: fieldsetRegistry, getFieldNames } = useFieldsetFieldNames('clientPreferences');\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('ClientPreferencesFieldset cannot dispatch while the session is read-only.');\n }\n const update = latest.current.$actions?.identity?.clientPreferencesUpdate;\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('ClientPreferencesFieldset requires $actions.identity.clientPreferencesUpdate.');\n return update(payload as ClientPreferencesUpdateInput);\n }, []);\n\n const toPayload = useCallback((values: Record<string, unknown>): Record<string, unknown> => {\n const { getFieldState } = latest.current;\n // FR-6: a flag is emitted only when the shopper actually toggled it. `getFieldState` is called\n // with no second argument, which reads react-hook-form's current internal state without\n // subscribing to it (Decision 3).\n const isDirty = (key: ClientPreferenceKey) => getFieldState(`clientPreferences.${key}`).isDirty;\n // Boolean() coercion is defence in depth: the bound checkbox part itself now writes its own\n // field's typed empty value on Clear (false, not ''), but this fieldset should not depend on\n // every caller writing a well-typed value.\n const payload: Record<string, boolean> = {};\n if ('optinNewsLetter' in values && isDirty('optinNewsLetter')) {\n payload.optinNewsLetter = Boolean(values.optinNewsLetter);\n }\n if ('savePersonalData' in values && isDirty('savePersonalData')) {\n payload.savePersonalData = Boolean(values.savePersonalData);\n }\n return payload;\n }, []);\n\n useEffect(\n () =>\n registerFieldset({\n namespace: 'clientPreferences',\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` (`clientPreferences` → `client-preferences`) — not authored\n // per page (Decision 2).\n label: 'client-preferences',\n }),\n [registerFieldset, getFieldNames, dispatch, toPayload],\n );\n\n const saved = useMemo<Readonly<Record<ClientPreferenceKey, boolean>>>(\n () => ({\n optinNewsLetter: Boolean($data?.optinNewsLetter ?? false),\n savePersonalData: Boolean($data?.savePersonalData ?? false),\n }),\n [$data],\n );\n\n const contextValue = useMemo<ClientPreferencesFieldsetContextValue>(\n () => ({ namespace: 'clientPreferences', saved, readOnly, rules: NO_RULES }),\n [saved, readOnly],\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-preferences-fieldset=\"\"\n {...(resolved ? {} : { 'data-fs-client-preferences-fieldset-pending': '' })}\n {...(readOnly ? { 'data-fs-client-preferences-fieldset-readonly': '' } : {})}\n >\n {resolved ? (\n <FieldsetFieldContext.Provider value={fieldsetRegistry}>\n <ClientPreferencesFieldsetContext.Provider value={contextValue}>\n {children}\n </ClientPreferencesFieldsetContext.Provider>\n </FieldsetFieldContext.Provider>\n ) : (\n <ClientPreferencesFieldsetSkeleton count={skeletonCount} />\n )}\n <output aria-live=\"polite\" data-fs-client-preferences-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 ClientPreferencesFieldsetSkeleton({\n count = CLIENT_PREFERENCES_SKELETON_ROWS,\n className,\n ...props\n}: ComponentPropsWithRef<'div'> & { count?: number }) {\n return (\n <div {...props} className={className} data-fs-client-preferences-fieldset-skeleton=\"\" aria-hidden=\"true\">\n {Array.from({ length: count }, (_, i) => (\n <span key={i} data-fs-client-preferences-fieldset-skeleton-field=\"\" />\n ))}\n </div>\n );\n}\n\nClientPreferencesFieldset.Skeleton = ClientPreferencesFieldsetSkeleton;\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\nfunction PreferenceControl({\n fieldKey,\n attr,\n defaultLabel,\n label,\n className,\n children,\n}: {\n fieldKey: ClientPreferenceKey;\n attr: string;\n defaultLabel: string;\n label?: string;\n className?: string;\n children?: ReactNode;\n}) {\n const { namespace, saved, readOnly, rules } = useClientPreferencesFieldsetContext();\n const name = useFieldsetField(namespace, fieldKey);\n const resolvedLabel = label ?? defaultLabel;\n\n return (\n <FieldBinding\n name={name}\n label={resolvedLabel}\n defaultValue={saved[fieldKey]}\n rule={rules[fieldKey]}\n disabled={readOnly}\n >\n <Field.Root className={className} data-fs-client-preferences-fieldset-field={attr}>\n {children ?? (\n <Field.Label>\n <Field.Checkbox>\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Field.Checkbox>\n {resolvedLabel}\n </Field.Label>\n )}\n </Field.Root>\n </FieldBinding>\n );\n}\n\nexport function ClientPreferencesNewsletterControl({\n label,\n className,\n children,\n}: ClientPreferencesNewsletterControlProps) {\n return (\n <PreferenceControl\n fieldKey=\"optinNewsLetter\"\n attr=\"newsletter\"\n defaultLabel=\"Email me with news and offers\"\n label={label}\n className={className}\n >\n {children}\n </PreferenceControl>\n );\n}\n\nexport function ClientPreferencesSavePersonalDataControl({\n label,\n className,\n children,\n}: ClientPreferencesSavePersonalDataControlProps) {\n return (\n <PreferenceControl\n fieldKey=\"savePersonalData\"\n attr=\"save-personal-data\"\n defaultLabel=\"Save my information for next time\"\n label={label}\n className={className}\n >\n {children}\n </PreferenceControl>\n );\n}\n"],"mappings":"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"}
@@ -3,12 +3,10 @@ import "../../core/dist/index.js";
3
3
  import { ComponentActions } from "../../sdk/actions/types.js";
4
4
  import { ReactNode } from "react";
5
5
  //#region src/matter/client-preferences/client-preferences.d.ts
6
- /** The `$data` slice forwarded by the reactive runtime — the `ClientPreferences` sibling entity. */
6
+ /** The `$data` slice forwarded by the reactive runtime — the `ClientPreferences` sibling entity, unwrapped. */
7
7
  export type ClientPreferencesData = {
8
- clientPreferences?: {
9
- optinNewsLetter?: boolean | null;
10
- savePersonalData?: boolean | null;
11
- } | null;
8
+ optinNewsLetter?: boolean | null;
9
+ savePersonalData?: boolean | null;
12
10
  } | null;
13
11
  export type ClientPreferencesRootProps = {
14
12
  /** `Query` slice forwarded by the reactive runtime (value of the section fragment). */
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
+ import { Checkbox } from "../../particles/checkbox/checkbox.js";
4
5
  import { Field } from "../../particles/field/field.js";
5
6
  import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
6
7
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
7
8
  import { useSession } from "../session/session.js";
8
9
  import { Icon } from "../../particles/icon/icon.js";
9
- import { Checkbox } from "../../particles/checkbox/checkbox.js";
10
10
  import { createContext, useCallback, useContext, useMemo, useState } from "react";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
12
  const ClientPreferencesRootDefinition = defineComponent({
@@ -24,7 +24,7 @@ const ClientPreferencesRootDefinition = defineComponent({
24
24
  }
25
25
  `,
26
26
  fetchStrategy: "reactive",
27
- providesContext: "client-preferences",
27
+ providesContext: "client-preferences-toggle",
28
28
  requiresContext: "session",
29
29
  editor: {
30
30
  label: "Client Preferences Root",
@@ -37,7 +37,7 @@ const NewsletterOptInControlDefinition = defineComponent({
37
37
  name: "NewsletterOptInControl",
38
38
  importPath: "@vtex/faststore/components",
39
39
  component: NewsletterOptInControl,
40
- requiresContext: "client-preferences",
40
+ requiresContext: "client-preferences-toggle",
41
41
  actions: { identity: ["clientPreferencesUpdate"] },
42
42
  editor: {
43
43
  label: "Newsletter Opt-In Control",
@@ -53,7 +53,7 @@ const SavePersonalDataOptInControlDefinition = defineComponent({
53
53
  name: "SavePersonalDataOptInControl",
54
54
  importPath: "@vtex/faststore/components",
55
55
  component: SavePersonalDataOptInControl,
56
- requiresContext: "client-preferences",
56
+ requiresContext: "client-preferences-toggle",
57
57
  actions: { identity: ["clientPreferencesUpdate"] },
58
58
  editor: {
59
59
  label: "Save Personal Data Opt-In Control",
@@ -87,8 +87,8 @@ function ClientPreferencesRoot({ $data: $dataProp, className, children }) {
87
87
  const $data = useDataOverride($dataProp);
88
88
  const { canEditData } = useSession();
89
89
  const readOnly = !canEditData;
90
- const optinNewsLetter = Boolean($data?.clientPreferences?.optinNewsLetter ?? false);
91
- const savePersonalData = Boolean($data?.clientPreferences?.savePersonalData ?? false);
90
+ const optinNewsLetter = Boolean($data?.optinNewsLetter ?? false);
91
+ const savePersonalData = Boolean($data?.savePersonalData ?? false);
92
92
  const [pendingKeys, setPendingKeys] = useState(/* @__PURE__ */ new Set());
93
93
  const [errorByKey, setErrorByKey] = useState({});
94
94
  const trackWrite = useCallback((key, write) => {
@@ -1 +1 @@
1
- {"version":3,"file":"client-preferences.js","names":[],"sources":["../../../src/matter/client-preferences/client-preferences.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Icon } from '../../particles/icon/icon.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 type { ClientPreferenceKey, ClientProfileUserError } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-preferences` section matter (per ADR-0011: a matter is decomposed by DATA OWNERSHIP,\n * not per field). Both opt-ins — newsletter (`optinNewsLetter`) and save-personal-data\n * (`savePersonalData`) — are light booleans owned by the User Identity `ClientPreferences` entity\n * (a SIBLING of `Cart`, not nested under it, per the identity RFC), so they live under ONE\n * `ClientPreferencesRoot` that declares a single section fragment on `Query` selecting both fields.\n * The two merchant-facing Controls consume that one section context (ADR-0011 governs data\n * ownership, not the editor palette) and share one internal `OptInControl` implementation: the\n * section context, the `$data` → values seeding, the per-field pending/error tracking, the inlined\n * `Checkbox` + `Field` Control render, and dispatch via `$actions.identity.clientPreferencesUpdate`\n * (mirrors the `email-identification` matter; reads/writes follow ADR-0006 / ADR-0010 / ADR-0011).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated (ADR-0011), reads the ClientPreferences sibling\n// entity via Query. Authored as a raw string (not the codegen graphql() factory),\n// mirroring the EmailIdentification matter: the compiler derives the entity fragment\n// on `clientPreferences` and subscribes reactively. One section fragment owns both\n// opt-in fields (ADR-0011).\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientPreferencesData on Query {\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the `ClientPreferences` sibling entity. */\nexport type ClientPreferencesData = {\n clientPreferences?: {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n } | null;\n} | null;\n\nexport type ClientPreferencesRootProps = {\n /** `Query` slice forwarded by the reactive runtime (value of the section fragment). */\n $data?: ClientPreferencesData;\n className?: string;\n children?: ReactNode;\n};\n\nexport type ClientPreferencesControlProps = {\n label: string;\n className?: string;\n /** Injected by the compiler from `actions: { identity: ['clientPreferencesUpdate'] }`; the write surface. */\n $actions?: ComponentActions;\n};\n\nexport type NewsletterOptInControlProps = ClientPreferencesControlProps;\nexport type SavePersonalDataOptInControlProps = ClientPreferencesControlProps;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferencesRootDefinition = defineComponent<ClientPreferencesRootProps>({\n name: 'ClientPreferencesRoot',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesRoot,\n fragment,\n fetchStrategy: 'reactive',\n providesContext: 'client-preferences',\n requiresContext: 'session',\n editor: { label: 'Client Preferences Root', category: 'identity' },\n schema: {},\n guideline:\n 'Binding provider for the consent opt-in section; reads both opt-in values (optinNewsLetter, savePersonalData) from the ClientPreferences identity entity (a sibling of Cart, via Query.clientPreferences) and exposes them to its Controls. Reads session state from useSession(); must be inside SessionRoot. Writes flow through $actions.identity.clientPreferencesUpdate. Wrap a NewsletterOptInControl and/or a SavePersonalDataOptInControl.',\n});\n\nexport const NewsletterOptInControlDefinition = defineComponent<NewsletterOptInControlProps>({\n name: 'NewsletterOptInControl',\n importPath: '@vtex/faststore/components',\n component: NewsletterOptInControl,\n requiresContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Newsletter Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound newsletter opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the optinNewsLetter preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\nexport const SavePersonalDataOptInControlDefinition = defineComponent<SavePersonalDataOptInControlProps>({\n name: 'SavePersonalDataOptInControl',\n importPath: '@vtex/faststore/components',\n component: SavePersonalDataOptInControl,\n requiresContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Save Personal Data Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound save-personal-data opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the savePersonalData preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ntype ClientPreferencesContextValue = Readonly<{\n /** Current value of each opt-in, seeded from the query response (null → unchecked). */\n values: Readonly<Record<ClientPreferenceKey, boolean>>;\n /**\n * Records that a write for `key` is in flight and stores its returned userErrors under that key so\n * each Control surfaces ITS OWN pending/error. The Control owns the actual dispatch (it holds\n * `$actions`); it hands the resulting promise here for the section to track.\n */\n trackWrite: (key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => void;\n /**\n * The fields whose writes are currently in flight. A per-key set, not a single slot: concurrent\n * writes on different fields must each keep their own Control disabled until THEIR write settles.\n */\n pendingKeys: ReadonlySet<ClientPreferenceKey>;\n /** The userErrors returned by the most recent write, keyed by the field that produced them. */\n errorByKey: Readonly<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>;\n /**\n * True when the session reports `canEditData: false` — VTEX refuses profile-data edits for an\n * identified-but-unauthenticated claimed profile, so the Controls render disabled instead of\n * accepting writes that are doomed server-side (mirrors the email control).\n */\n readOnly: boolean;\n}>;\n\nconst CLIENT_PREFERENCES_UNSET = Symbol('ClientPreferencesContextUnset');\n\nconst ClientPreferencesContext = createContext<ClientPreferencesContextValue | typeof CLIENT_PREFERENCES_UNSET>(\n CLIENT_PREFERENCES_UNSET,\n);\n\nfunction useClientPreferencesContext(): ClientPreferencesContextValue {\n const ctx = useContext(ClientPreferencesContext);\n if (ctx === CLIENT_PREFERENCES_UNSET) {\n throw new Error('A client-preferences Control must be used inside a ClientPreferencesRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\n/**\n * Renders the client-preferences section Root: reads `$data` (the `ClientPreferences` sibling entity),\n * reads `useSession()` (which also enforces the `SessionRoot` ancestor, mirroring\n * `EmailIdentificationControl`), seeds both opt-in values from the section fragment, and publishes a\n * section context exposing both values plus per-field pending/error tracking. The write itself flows\n * through the Control's `$actions.identity.clientPreferencesUpdate` (ADR-0010); this Root tracks which\n * keys are in flight and stores the returned userErrors under each key so each Control surfaces ITS OWN\n * pending/error. When the session reports `canEditData: false` (identified but unauthenticated claimed\n * profile — VTEX refuses the write server-side) every Control renders disabled with its readonly\n * `data-fs-*` attribute, mirroring `EmailIdentificationControl`.\n */\nexport function ClientPreferencesRoot({ $data: $dataProp, className, children }: ClientPreferencesRootProps) {\n const $data = useDataOverride($dataProp);\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n const optinNewsLetter = Boolean($data?.clientPreferences?.optinNewsLetter ?? false);\n const savePersonalData = Boolean($data?.clientPreferences?.savePersonalData ?? false);\n\n const [pendingKeys, setPendingKeys] = useState<ReadonlySet<ClientPreferenceKey>>(new Set());\n const [errorByKey, setErrorByKey] = useState<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>(\n {},\n );\n\n const trackWrite = useCallback((key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => {\n // Clear the key's stale error at the start of a retry (parity with the email control clearing\n // its serverError on a new write).\n setErrorByKey((prev) => ({ ...prev, [key]: [] }));\n setPendingKeys((prev) => new Set(prev).add(key));\n void write\n .then((userErrors) => {\n setErrorByKey((prev) => ({ ...prev, [key]: userErrors }));\n })\n .finally(() => {\n setPendingKeys((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n });\n }, []);\n\n const values = useMemo(() => ({ optinNewsLetter, savePersonalData }) as const, [optinNewsLetter, savePersonalData]);\n\n const contextValue = useMemo<ClientPreferencesContextValue>(\n () => ({ values, trackWrite, pendingKeys, errorByKey, readOnly }),\n [values, trackWrite, pendingKeys, errorByKey, readOnly],\n );\n\n return (\n <ClientPreferencesContext value={contextValue}>\n <div className={className} data-fs-client-preferences-root=\"\">\n {children}\n </div>\n </ClientPreferencesContext>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\n/**\n * Renders one bound opt-in checkbox by inlining `Field` + `Checkbox` + `Icon` (no `CheckboxField`\n * particle). Reads ITS field's value, pending, and error from the section context and dispatches the\n * write through `$actions.identity.clientPreferencesUpdate({ [key]: value })`, handing the promise to\n * the section so the in-flight key and returned userErrors are tracked per field. The `fieldKey`,\n * `dataAttr`, `pendingAttr`, and `readonlyAttr` are fixed by each registered Control; only\n * `label`/`className` are merchant-configurable.\n */\nfunction OptInControl({\n fieldKey,\n dataAttr,\n pendingAttr,\n readonlyAttr,\n label,\n className,\n $actions: $actionsProp,\n}: ClientPreferencesControlProps & {\n fieldKey: ClientPreferenceKey;\n dataAttr: string;\n pendingAttr: string;\n readonlyAttr: string;\n}) {\n const $actions = useActionsOverride($actionsProp);\n const { values, trackWrite, pendingKeys, errorByKey, readOnly } = useClientPreferencesContext();\n const fieldErrors = errorByKey[fieldKey] ?? [];\n const value = values[fieldKey];\n const pending = pendingKeys.has(fieldKey);\n const disabled = pending || readOnly;\n const [firstError] = fieldErrors;\n const invalid = Boolean(firstError);\n\n function handleCheckedChange(checked: boolean) {\n const update = $actions?.identity?.clientPreferencesUpdate;\n if (!update) return;\n trackWrite(fieldKey, update({ [fieldKey]: checked }));\n }\n\n return (\n <div\n className={className}\n {...{ [dataAttr]: '' }}\n {...(pending ? { [pendingAttr]: '' } : {})}\n {...(readOnly ? { [readonlyAttr]: '' } : {})}\n >\n <Field.Root invalid={invalid} disabled={disabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root checked={value} onCheckedChange={handleCheckedChange} disabled={disabled}>\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {label}\n </Field.Label>\n {invalid && firstError ? <Field.Error match>{firstError.message}</Field.Error> : null}\n </Field.Root>\n </div>\n );\n}\n\nexport function NewsletterOptInControl(props: NewsletterOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"optinNewsLetter\"\n dataAttr=\"data-fs-newsletter-opt-in\"\n pendingAttr=\"data-fs-newsletter-opt-in-pending\"\n readonlyAttr=\"data-fs-newsletter-opt-in-readonly\"\n />\n );\n}\n\nexport function SavePersonalDataOptInControl(props: SavePersonalDataOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"savePersonalData\"\n dataAttr=\"data-fs-save-personal-data-opt-in\"\n pendingAttr=\"data-fs-save-personal-data-opt-in-pending\"\n readonlyAttr=\"data-fs-save-personal-data-opt-in-readonly\"\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferences = {\n Root: ClientPreferencesRoot,\n NewsletterOptInControl,\n SavePersonalDataOptInControl,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-preferences.js","names":[],"sources":["../../../src/matter/client-preferences/client-preferences.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Icon } from '../../particles/icon/icon.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 type { ClientPreferenceKey, ClientProfileUserError } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-preferences` section matter (per ADR-0011: a matter is decomposed by DATA OWNERSHIP,\n * not per field). Both opt-ins — newsletter (`optinNewsLetter`) and save-personal-data\n * (`savePersonalData`) — are light booleans owned by the User Identity `ClientPreferences` entity\n * (a SIBLING of `Cart`, not nested under it, per the identity RFC), so they live under ONE\n * `ClientPreferencesRoot` that declares a single section fragment on `Query` selecting both fields.\n * The two merchant-facing Controls consume that one section context (ADR-0011 governs data\n * ownership, not the editor palette) and share one internal `OptInControl` implementation: the\n * section context, the `$data` → values seeding, the per-field pending/error tracking, the inlined\n * `Checkbox` + `Field` Control render, and dispatch via `$actions.identity.clientPreferencesUpdate`\n * (mirrors the `email-identification` matter; reads/writes follow ADR-0006 / ADR-0010 / ADR-0011).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated (ADR-0011), reads the ClientPreferences sibling\n// entity via Query. Authored as a raw string (not the codegen graphql() factory),\n// mirroring the EmailIdentification matter: the compiler derives the entity fragment\n// on `clientPreferences` and subscribes reactively. One section fragment owns both\n// opt-in fields (ADR-0011).\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientPreferencesData on Query {\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the `ClientPreferences` sibling entity, unwrapped. */\nexport type ClientPreferencesData = {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n} | null;\n\nexport type ClientPreferencesRootProps = {\n /** `Query` slice forwarded by the reactive runtime (value of the section fragment). */\n $data?: ClientPreferencesData;\n className?: string;\n children?: ReactNode;\n};\n\nexport type ClientPreferencesControlProps = {\n label: string;\n className?: string;\n /** Injected by the compiler from `actions: { identity: ['clientPreferencesUpdate'] }`; the write surface. */\n $actions?: ComponentActions;\n};\n\nexport type NewsletterOptInControlProps = ClientPreferencesControlProps;\nexport type SavePersonalDataOptInControlProps = ClientPreferencesControlProps;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferencesRootDefinition = defineComponent<ClientPreferencesRootProps>({\n name: 'ClientPreferencesRoot',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesRoot,\n fragment,\n fetchStrategy: 'reactive',\n providesContext: 'client-preferences-toggle',\n requiresContext: 'session',\n editor: { label: 'Client Preferences Root', category: 'identity' },\n schema: {},\n guideline:\n 'Binding provider for the consent opt-in section; reads both opt-in values (optinNewsLetter, savePersonalData) from the ClientPreferences identity entity (a sibling of Cart, via Query.clientPreferences) and exposes them to its Controls. Reads session state from useSession(); must be inside SessionRoot. Writes flow through $actions.identity.clientPreferencesUpdate. Wrap a NewsletterOptInControl and/or a SavePersonalDataOptInControl.',\n});\n\nexport const NewsletterOptInControlDefinition = defineComponent<NewsletterOptInControlProps>({\n name: 'NewsletterOptInControl',\n importPath: '@vtex/faststore/components',\n component: NewsletterOptInControl,\n requiresContext: 'client-preferences-toggle',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Newsletter Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound newsletter opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the optinNewsLetter preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\nexport const SavePersonalDataOptInControlDefinition = defineComponent<SavePersonalDataOptInControlProps>({\n name: 'SavePersonalDataOptInControl',\n importPath: '@vtex/faststore/components',\n component: SavePersonalDataOptInControl,\n requiresContext: 'client-preferences-toggle',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Save Personal Data Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound save-personal-data opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the savePersonalData preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ntype ClientPreferencesContextValue = Readonly<{\n /** Current value of each opt-in, seeded from the query response (null → unchecked). */\n values: Readonly<Record<ClientPreferenceKey, boolean>>;\n /**\n * Records that a write for `key` is in flight and stores its returned userErrors under that key so\n * each Control surfaces ITS OWN pending/error. The Control owns the actual dispatch (it holds\n * `$actions`); it hands the resulting promise here for the section to track.\n */\n trackWrite: (key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => void;\n /**\n * The fields whose writes are currently in flight. A per-key set, not a single slot: concurrent\n * writes on different fields must each keep their own Control disabled until THEIR write settles.\n */\n pendingKeys: ReadonlySet<ClientPreferenceKey>;\n /** The userErrors returned by the most recent write, keyed by the field that produced them. */\n errorByKey: Readonly<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>;\n /**\n * True when the session reports `canEditData: false` — VTEX refuses profile-data edits for an\n * identified-but-unauthenticated claimed profile, so the Controls render disabled instead of\n * accepting writes that are doomed server-side (mirrors the email control).\n */\n readOnly: boolean;\n}>;\n\nconst CLIENT_PREFERENCES_UNSET = Symbol('ClientPreferencesContextUnset');\n\nconst ClientPreferencesContext = createContext<ClientPreferencesContextValue | typeof CLIENT_PREFERENCES_UNSET>(\n CLIENT_PREFERENCES_UNSET,\n);\n\nfunction useClientPreferencesContext(): ClientPreferencesContextValue {\n const ctx = useContext(ClientPreferencesContext);\n if (ctx === CLIENT_PREFERENCES_UNSET) {\n throw new Error('A client-preferences Control must be used inside a ClientPreferencesRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\n/**\n * Renders the client-preferences section Root: reads `$data` (the `ClientPreferences` sibling entity),\n * reads `useSession()` (which also enforces the `SessionRoot` ancestor, mirroring\n * `EmailIdentificationControl`), seeds both opt-in values from the section fragment, and publishes a\n * section context exposing both values plus per-field pending/error tracking. The write itself flows\n * through the Control's `$actions.identity.clientPreferencesUpdate` (ADR-0010); this Root tracks which\n * keys are in flight and stores the returned userErrors under each key so each Control surfaces ITS OWN\n * pending/error. When the session reports `canEditData: false` (identified but unauthenticated claimed\n * profile — VTEX refuses the write server-side) every Control renders disabled with its readonly\n * `data-fs-*` attribute, mirroring `EmailIdentificationControl`.\n */\nexport function ClientPreferencesRoot({ $data: $dataProp, className, children }: ClientPreferencesRootProps) {\n const $data = useDataOverride($dataProp);\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n const optinNewsLetter = Boolean($data?.optinNewsLetter ?? false);\n const savePersonalData = Boolean($data?.savePersonalData ?? false);\n\n const [pendingKeys, setPendingKeys] = useState<ReadonlySet<ClientPreferenceKey>>(new Set());\n const [errorByKey, setErrorByKey] = useState<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>(\n {},\n );\n\n const trackWrite = useCallback((key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => {\n // Clear the key's stale error at the start of a retry (parity with the email control clearing\n // its serverError on a new write).\n setErrorByKey((prev) => ({ ...prev, [key]: [] }));\n setPendingKeys((prev) => new Set(prev).add(key));\n void write\n .then((userErrors) => {\n setErrorByKey((prev) => ({ ...prev, [key]: userErrors }));\n })\n .finally(() => {\n setPendingKeys((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n });\n }, []);\n\n const values = useMemo(() => ({ optinNewsLetter, savePersonalData }) as const, [optinNewsLetter, savePersonalData]);\n\n const contextValue = useMemo<ClientPreferencesContextValue>(\n () => ({ values, trackWrite, pendingKeys, errorByKey, readOnly }),\n [values, trackWrite, pendingKeys, errorByKey, readOnly],\n );\n\n return (\n <ClientPreferencesContext value={contextValue}>\n <div className={className} data-fs-client-preferences-root=\"\">\n {children}\n </div>\n </ClientPreferencesContext>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\n/**\n * Renders one bound opt-in checkbox by inlining `Field` + `Checkbox` + `Icon` (no `CheckboxField`\n * particle). Reads ITS field's value, pending, and error from the section context and dispatches the\n * write through `$actions.identity.clientPreferencesUpdate({ [key]: value })`, handing the promise to\n * the section so the in-flight key and returned userErrors are tracked per field. The `fieldKey`,\n * `dataAttr`, `pendingAttr`, and `readonlyAttr` are fixed by each registered Control; only\n * `label`/`className` are merchant-configurable.\n */\nfunction OptInControl({\n fieldKey,\n dataAttr,\n pendingAttr,\n readonlyAttr,\n label,\n className,\n $actions: $actionsProp,\n}: ClientPreferencesControlProps & {\n fieldKey: ClientPreferenceKey;\n dataAttr: string;\n pendingAttr: string;\n readonlyAttr: string;\n}) {\n const $actions = useActionsOverride($actionsProp);\n const { values, trackWrite, pendingKeys, errorByKey, readOnly } = useClientPreferencesContext();\n const fieldErrors = errorByKey[fieldKey] ?? [];\n const value = values[fieldKey];\n const pending = pendingKeys.has(fieldKey);\n const disabled = pending || readOnly;\n const [firstError] = fieldErrors;\n const invalid = Boolean(firstError);\n\n function handleCheckedChange(checked: boolean) {\n const update = $actions?.identity?.clientPreferencesUpdate;\n if (!update) return;\n trackWrite(fieldKey, update({ [fieldKey]: checked }));\n }\n\n return (\n <div\n className={className}\n {...{ [dataAttr]: '' }}\n {...(pending ? { [pendingAttr]: '' } : {})}\n {...(readOnly ? { [readonlyAttr]: '' } : {})}\n >\n <Field.Root invalid={invalid} disabled={disabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root checked={value} onCheckedChange={handleCheckedChange} disabled={disabled}>\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {label}\n </Field.Label>\n {invalid && firstError ? <Field.Error match>{firstError.message}</Field.Error> : null}\n </Field.Root>\n </div>\n );\n}\n\nexport function NewsletterOptInControl(props: NewsletterOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"optinNewsLetter\"\n dataAttr=\"data-fs-newsletter-opt-in\"\n pendingAttr=\"data-fs-newsletter-opt-in-pending\"\n readonlyAttr=\"data-fs-newsletter-opt-in-readonly\"\n />\n );\n}\n\nexport function SavePersonalDataOptInControl(props: SavePersonalDataOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"savePersonalData\"\n dataAttr=\"data-fs-save-personal-data-opt-in\"\n pendingAttr=\"data-fs-save-personal-data-opt-in-pending\"\n readonlyAttr=\"data-fs-save-personal-data-opt-in-readonly\"\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferences = {\n Root: ClientPreferencesRoot,\n NewsletterOptInControl,\n SavePersonalDataOptInControl,\n};\n"],"mappings":"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"}
@@ -4,11 +4,9 @@ import { ComponentActions } from "../../sdk/actions/types.js";
4
4
  import { PhoneCountry } from "../../particles/phone-field/phone-field.js";
5
5
  import { ReactNode } from "react";
6
6
  //#region src/matter/client-profile/client-profile.d.ts
7
- /** The `$data` slice forwarded by the reactive runtime — the `ClientProfile` sibling entity. */
7
+ /** The `$data` slice forwarded by the reactive runtime — the `ClientProfile` sibling entity, unwrapped. */
8
8
  export type ClientProfileData = {
9
- clientProfile?: {
10
- phone?: string | null;
11
- } | null;
9
+ phone?: string | null;
12
10
  } | null;
13
11
  export type ClientProfileRootProps = {
14
12
  /** ClientProfile entity slice forwarded by the reactive runtime (value of the section fragment). */
@@ -24,13 +22,13 @@ export declare const ClientProfileRootDefinition: ComponentDefinition<ClientProf
24
22
  * `$actions.identity` (ADR-0010); the Root only supplies the pre-fill.
25
23
  */
26
24
  export declare function ClientProfileRoot({ $data: $dataProp, className, children }: ClientProfileRootProps): import("react/jsx-runtime").JSX.Element;
27
- type PhoneFieldRequirement = 'mandatory' | 'optional' | 'hidden';
25
+ type PhoneFieldRequirement = 'mandatory' | 'optional';
28
26
  export type ClientProfilePhoneControlProps = {
29
27
  /** Visible field label. */
30
28
  label?: string;
31
29
  /** Selects the phone mask and validation rule. */
32
30
  country?: PhoneCountry;
33
- /** Controls field visibility and required validation. */
31
+ /** Controls required validation. */
34
32
  requirement?: PhoneFieldRequirement;
35
33
  /** Error string shown when the input format is invalid. */
36
34
  errorInvalid?: string;
@@ -63,7 +63,7 @@ const ClientProfileRootDefinition = defineComponent({
63
63
  function ClientProfileRoot({ $data: $dataProp, className, children }) {
64
64
  const $data = useDataOverride($dataProp);
65
65
  useSession();
66
- const phone = $data?.clientProfile?.phone ?? "";
66
+ const phone = $data?.phone ?? "";
67
67
  const contextValue = useMemo(() => ({ phone }), [phone]);
68
68
  return /* @__PURE__ */ jsx(ClientProfileContext, {
69
69
  value: contextValue,
@@ -96,7 +96,7 @@ const ClientProfilePhoneControlDefinition = defineComponent({
96
96
  label: "Country (US or BR)"
97
97
  },
98
98
  requirement: {
99
- type: "string",
99
+ type: ["mandatory", "optional"],
100
100
  default: "mandatory",
101
101
  label: "Requirement"
102
102
  },
@@ -128,15 +128,17 @@ function ClientProfilePhoneControl({ label = "Phone", country = "US", requiremen
128
128
  const { canEditData } = useSession();
129
129
  const readOnly = !canEditData;
130
130
  const [draft, setDraft] = useState(savedPhone);
131
+ const dirtyRef = useRef(false);
131
132
  const [pending, setPending] = useState(false);
132
133
  const [serverError, setServerError] = useState(null);
133
- const prevSavedRef = useRef(savedPhone);
134
- if (prevSavedRef.current !== savedPhone) {
135
- prevSavedRef.current = savedPhone;
136
- setDraft(savedPhone);
134
+ const [prevSaved, setPrevSaved] = useState(savedPhone);
135
+ if (prevSaved !== savedPhone) {
136
+ setPrevSaved(savedPhone);
137
+ if (!dirtyRef.current) setDraft(savedPhone);
137
138
  }
138
139
  function handleValueChange(next) {
139
140
  setDraft(next);
141
+ dirtyRef.current = true;
140
142
  if (serverError) setServerError(null);
141
143
  }
142
144
  async function handleBlur() {
@@ -153,6 +155,7 @@ function ClientProfilePhoneControl({ label = "Phone", country = "US", requiremen
153
155
  try {
154
156
  const errors = await clientProfileUpdate({ phone }) ?? [];
155
157
  if (errors.length > 0) setServerError(errors[0]?.message ?? null);
158
+ else dirtyRef.current = false;
156
159
  } finally {
157
160
  setPending(false);
158
161
  }