@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.
- package/CHANGELOG.md +67 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1595 -481
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +31 -7
- package/dist/components.js +35 -13
- package/dist/content-types.json +73 -0
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-access-gate.md +153 -0
- package/dist/docs/account-entry-point.md +1 -2
- package/dist/docs/account-menu.md +164 -0
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/buy-button.md +1 -1
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/cart.md +1 -1
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +1 -0
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +110 -258
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +3 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
- package/dist/matter/account-access-gate/account-access-gate.js +283 -0
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
- package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
- package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
- package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +2 -2
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +88 -0
- package/dist/matter/account-menu/account-menu.js +246 -0
- package/dist/matter/account-menu/account-menu.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +832 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +1 -1
- package/dist/matter/cart/cart.js +2 -2
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.js +5 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +107 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +263 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
- package/dist/matter/product-shelf/product-shelf.js +2 -0
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +387 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +481 -792
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +701 -2
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
- package/dist/sdk/actions/cart-action-provider.js +1 -1
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/session-action-provider.d.ts +1 -1
- package/dist/sdk/actions/session-action-provider.js +1 -1
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
- package/dist/sdk/actions/shipping-action-provider.js +1 -1
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +108 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +1 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +4 -4
- package/dist/sdk/cart/operations.js +1 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.js +1 -1
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
- package/dist/sdk/cart/use-cart-actions.js +4 -4
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/cart/write-gate.js +1 -1
- package/dist/sdk/cart/write-gate.js.map +1 -1
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/index.js +1 -0
- package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-data-provider.js +8 -4
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +6 -2
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/operations.js.map +1 -1
- package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
- package/dist/sdk/identity/use-identity-actions.js +3 -2
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/session/auth-operations.d.ts +1 -1
- package/dist/sdk/session/auth-operations.js.map +1 -1
- package/dist/sdk/session/use-session-actions.d.ts +1 -1
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +65 -0
- package/dist/sdk/session/use-session-revalidation.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
- package/dist/sdk/shipping/option-select-operations.js.map +1 -1
- package/dist/sdk/shipping/request-timeout.js +1 -1
- package/dist/sdk/shipping/request-timeout.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
- package/dist/sdk/shipping/use-shipping-actions.js +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +3 -4
- package/dist/sdk/state/cart.js +6 -7
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.js +3 -3
- package/dist/utils/safe-href.js.map +1 -1
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +2 -1
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# CardholderDocument
|
|
2
|
+
|
|
3
|
+
**Category:** commerce
|
|
4
|
+
**Purpose:** A single tax-document input (CPF) for the checkout payment step, shown or hidden and marked required by the currently selected payment method's server-driven configuration.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Visual cues (how to identify in an image)
|
|
9
|
+
|
|
10
|
+
Use these signals to detect that a CardholderDocument block is present. Position on the page is irrelevant for detection — it typically sits near the card-entry fields in a payment/checkout step. The image is the source of truth for layout.
|
|
11
|
+
|
|
12
|
+
A CardholderDocument block is:
|
|
13
|
+
|
|
14
|
+
- A **single labeled text input**, formatted with dots and a dash like `123.456.789-01` (the CPF mask).
|
|
15
|
+
- Labeled **"CPF"** or a similar tax-document label — never "Card number", "Expiration", or "CVV".
|
|
16
|
+
- Not accompanied by a network icon, a "•••• 1234" masked number, or any card-brand logo — those belong to `CardEntry` or `SavedCards`, not this block.
|
|
17
|
+
- Sometimes marked with a required indicator (an asterisk or similar) next to its label; sometimes not — both are valid, since requiredness is server-driven per payment method.
|
|
18
|
+
|
|
19
|
+
If the image shows a **card-entry form with one extra plain text field formatted like a Brazilian tax ID**, use `CardholderDocument`. Do not use it for the shopper's account/profile document field — that is a different, identity-step component this block never composes with or reads from.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## When to use
|
|
24
|
+
|
|
25
|
+
- The checkout/payment step, as a sibling of the card-entry fields, for a store whose selected payment method requires (or offers) a cardholder tax document.
|
|
26
|
+
|
|
27
|
+
## When NOT to use
|
|
28
|
+
|
|
29
|
+
- The shopper's saved profile/identity document (a client-identity or user-profile field) → use the identity-step document component instead. `CardholderDocument` is a payment-time value, deliberately separate — it never reads or writes the stored profile document, because the epic's RFC records that the two may differ.
|
|
30
|
+
- A generic, data-agnostic tax-document input with no server-driven presence/requiredness → use the `DocumentField` particle directly instead; `CardholderDocument` exists specifically to wire that particle to the selected payment method's validator.
|
|
31
|
+
- More than one document type or country → out of scope today. Only CPF renders; see this component's own spec for the recorded deferral.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Composition
|
|
36
|
+
|
|
37
|
+
`CardholderDocument` is a leaf and must be a descendant of `PaymentSectionRoot` or `PaymentSectionController`. It takes no children and is placed as a **sibling** of `CardEntry`, `SavedCards`, `Installments`, and the other payment leaves — never nested inside any of them.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
PaymentSectionRoot (or PaymentSectionController)
|
|
41
|
+
├── PaymentMethodList
|
|
42
|
+
├── CardEntry (sibling — new-card fields)
|
|
43
|
+
├── CardholderDocument (sibling — this block)
|
|
44
|
+
├── Installments
|
|
45
|
+
└── SavedCards
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Child order reflects visual order. There is no parent/child relationship for this component to define — it is a single, independently placeable field.
|
|
49
|
+
|
|
50
|
+
**Composition rules:**
|
|
51
|
+
|
|
52
|
+
- Must be inside `PaymentSectionRoot` or `PaymentSectionController`. Outside either, it throws.
|
|
53
|
+
- Renders `null` (no DOM at all) when the selected payment method's validator does not mark a document displayable, or when no method is selected yet. This is normal, not an error — most methods most of the time render nothing here.
|
|
54
|
+
- Also renders `null` while the card payment is collapsed — the persisted selection is a method that is not a card, or one the section does not serve, and a composed `PaymentMethodList` can bring the card form back (see [[payment]]) — whatever that method's own validator says. When the card comes back, the field re-seeds from the value already typed.
|
|
55
|
+
- Takes no children; passing any is a no-op.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Components
|
|
60
|
+
|
|
61
|
+
### CardholderDocument
|
|
62
|
+
|
|
63
|
+
The field itself. Renders the `DocumentField` particle for CPF, with presence and requiredness resolved from `usePayment().selectedMethod.validator` (`documentDisplayable` / `documentRequired`) rather than from any prop.
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Default | Notes |
|
|
66
|
+
|------|------|---------|-------|
|
|
67
|
+
| `label` | `string` | `'CPF'` | The field's visible label. |
|
|
68
|
+
| `placeholder` | `string` | `'000.000.000-00'` | Shown in the empty input as a format hint. |
|
|
69
|
+
| `errorMessage` | `string` | `'Invalid CPF'` | Shown inline when the typed value fails CPF check-digit validation on blur. |
|
|
70
|
+
| `disabled` | `boolean` | `false` | Disables the input without hiding it — independent of the data-driven presence gate. |
|
|
71
|
+
|
|
72
|
+
No `documentType` prop — this build supports CPF only. No `required` prop — requiredness is always read from the selected method's validator, never authored.
|
|
73
|
+
|
|
74
|
+
**Data:** declares no `fragment` of its own. It reads `selectedMethod`, `draft`, and `setDraftDocument` off the ambient `payment` context that `PaymentSectionRoot` / `PaymentSectionController` already fetches for.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Canonical schema example
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"payment-section": {
|
|
83
|
+
"type": "PaymentSectionRoot",
|
|
84
|
+
"children": ["payment-method-list", "card-entry", "cardholder-document", "installments"]
|
|
85
|
+
},
|
|
86
|
+
"payment-method-list": { "type": "PaymentMethodList" },
|
|
87
|
+
"card-entry": { "type": "CardEntry" },
|
|
88
|
+
"cardholder-document": {
|
|
89
|
+
"type": "CardholderDocument",
|
|
90
|
+
"props": { "label": "CPF", "placeholder": "000.000.000-00" }
|
|
91
|
+
},
|
|
92
|
+
"installments": { "type": "Installments" }
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Common mistakes
|
|
99
|
+
|
|
100
|
+
- **Nesting `CardholderDocument` inside `CardEntry`.** It is a sibling under the same `PaymentSectionRoot`, never a child — `CardEntry` does not accept children.
|
|
101
|
+
- **Passing a `documentType`, `required`, `countryCode`, or `document` prop.** None exist. Document type is fixed to CPF; requiredness and presence are always read from the selected method's validator, never authored; the typed value is not a prop at all — it flows into the shared payment draft internally.
|
|
102
|
+
- **Using this component for the shopper's saved profile document.** That is a different, identity-step field this component never reads from or writes to — the two are intentionally separate, even for the same shopper.
|
|
103
|
+
- **Expecting it to render outside a `PaymentSectionRoot` / `PaymentSectionController`.** It throws — the same requirement every other payment leaf (`CardEntry`, `Installments`, `SavedCards`, …) has.
|
|
104
|
+
- **Treating a blank render as broken.** Most of the time, for most payment methods, this component correctly renders nothing — that is the "not displayable" state, not a bug.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Known limitations
|
|
109
|
+
|
|
110
|
+
- **CPF only.** The underlying read model carries no document-type or country signal (see this component's own spec for the full, measured deferral) — a store outside Brazil cannot get a different document type from this component today.
|
|
111
|
+
- **Server-driven requiredness renders the field correctly and is also enforced at submit time — but not by this component or by `CardEntry`.** This component marks the field required when the validator says so; a required-but-blank or malformed value (whether or not required) blocks Place Order client-side, before any transaction opens, **while the selected method's validator also says the document is displayable**. That gate lives in `matter/payment/use-payment-submit-gate.ts` (T24) and is consumed by `PlaceOrder`, not by this component or by `CardEntry`'s own `validateDraft` — this component's only job stays publishing `draft.document` via `setDraftDocument`. When the *currently* selected method's validator says `documentDisplayable: false`, the gate treats `draft.document` as inert either way (fix pass, findings R1/S4/S5): there is no field on screen for the shopper to read a leftover value from or correct it in, whether that value is left over from a previously selected, displayable method or simply blank. See `specs/cardholder-document-submit-gate.md` for the gate's full contract.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Styling
|
|
116
|
+
|
|
117
|
+
`CardholderDocument`'s single root is the composed `DocumentField` particle — style it exactly like any other text field in the payment section.
|
|
118
|
+
|
|
119
|
+
| Component | What you style | Plausible classes |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `CardholderDocument` | The field's own label, input box, and inline error — same shape as a `Field.Root` | `flex flex-col gap-[4px] w-full` on the root; `text-[14px] font-medium text-[#1A1A1A]` for the label; `border border-[#CCCCCC] rounded-[6px] px-[12px] py-[8px] text-[14px]` for the input |
|
|
122
|
+
| Placing (`data-fs-cardholder-document-placing`) | Present while an order-placement sequence is in flight (T28); the field is also `inert` and `aria-disabled` at that point | `opacity-60 cursor-not-allowed` |
|
|
123
|
+
|
|
124
|
+
Styled example:
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"cardholder-document": {
|
|
129
|
+
"type": "CardholderDocument",
|
|
130
|
+
"props": { "label": "CPF" },
|
|
131
|
+
"style": ["flex", "flex-col", "gap-[4px]", "w-full"]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Related
|
|
139
|
+
|
|
140
|
+
- [[payment]] — `PaymentSectionRoot` / `PaymentMethodList`: the required ancestor and the method picker. `CardholderDocument` cannot render outside a `PaymentSectionRoot` (or `PaymentSectionController`).
|
|
141
|
+
- `CardEntry` — the sibling new-card entry fields (number, holder name, expiration, CVV); a different task's deliverable, always a sibling, never a parent of this block.
|
|
142
|
+
- `DocumentField` — the underlying data-agnostic particle this component wraps. Use it directly only outside the payment context, where server-driven presence/requiredness do not apply.
|
package/dist/docs/cart.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**Category:** commerce
|
|
4
4
|
**Purpose:** Cart **read** layer for the store. `CartRoot` loads the active cart and exposes it (and the cart query error) to descendants, keeping them in sync as the cart changes. It is **hosted by the layout** (shared shell), wrapping the page slot — so every page, including PDPs, consumes its context without declaring it.
|
|
5
5
|
|
|
6
|
-
Cart **writes** (add / remove / quantity) no longer flow through `CartRoot`. A component declares the cart methods it needs as an `actions: { cart: [...] }` selector on its definition and dispatches through the compiler-injected `$actions.cart.*` prop — the write-side mirror of `fragment` → `$data`. Cart writes are **context-free**: an add-to-cart button works on a PDP, a product card, a search result, or the header with **no `CartRoot` ancestor** (
|
|
6
|
+
Cart **writes** (add / remove / quantity) no longer flow through `CartRoot`. A component declares the cart methods it needs as an `actions: { cart: [...] }` selector on its definition and dispatches through the compiler-injected `$actions.cart.*` prop — the write-side mirror of `fragment` → `$data`. Cart writes are **context-free**: an add-to-cart button works on a PDP, a product card, a search result, or the header with **no `CartRoot` ancestor** (docs/storefront-core/reference-free-particle-model.md § Write side — actions, ADR-0010).
|
|
7
7
|
|
|
8
8
|
This doc covers `CartRoot` — hosted in the layout, never emitted in page schemas — plus the **mini-cart footer checkout CTA** ([below](#mini-cart-footer-checkout-cta)), the composition of `CartCheckoutGuard` → (`CartTotal` + `CartGoCheckout`). The remaining cart line-item and summary particles (`CartList`, `CartItem*`, `CartCount`, `CartError`) exist in the catalog and will be documented when the full cart page enters scope.
|
|
9
9
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# ClientPreferencesFieldset
|
|
2
|
+
|
|
3
|
+
**Category:** identity
|
|
4
|
+
**Purpose:** The composable, one-Save write of the `ClientPreferences` entity — the same two consent flags `ClientPreferencesRoot` already writes on toggle, given a second, batched write timing. Registers itself with the enclosing `FormRoot` under the `clientPreferences` namespace and dispatches at most one `identity.clientPreferencesUpdate` when the shopper presses that form's submit. Two bound controls — newsletter, save-personal-data — compose freely inside it.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Two write timings for one entity — read this before composing a control
|
|
9
|
+
|
|
10
|
+
`ClientPreferencesRoot` and `ClientPreferencesFieldset` both read and write the same `ClientPreferences` entity (`optinNewsLetter`, `savePersonalData`). They differ only in *when* a toggle writes:
|
|
11
|
+
|
|
12
|
+
- **`ClientPreferencesRoot`** (`matter/client-preferences/client-preferences.tsx`) — self-persisting. Its Controls (`NewsletterOptInControl`, `SavePersonalDataOptInControl`) write the moment the shopper toggles them. Provides context `client-preferences-toggle`.
|
|
13
|
+
- **`ClientPreferencesFieldset`** (this matter) — batched. Its Controls (`ClientPreferencesNewsletterControl`, `ClientPreferencesSavePersonalDataControl`) write nothing themselves; they register with the enclosing `FormRoot` and wait for that form's submit. Provides context `client-preferences`.
|
|
14
|
+
|
|
15
|
+
The two context names are deliberately different, on purpose: reusing one name for both would let an on-toggle control compile inside the batched fieldset (or vice versa) and write at the wrong moment with no warning. Composing a Root's control under the other's provider fails the build instead — the compiler's `context-constraint-violation` diagnostic names the missing context.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Visual cues (how to identify in an image)
|
|
20
|
+
|
|
21
|
+
- One or two consent checkboxes ("email me with news and offers", "save my information for next time") inside a page that also shows a **single submit button somewhere else on the page** — not one owned by this fieldset.
|
|
22
|
+
- If the checkbox instead writes the instant it is toggled (no surrounding Save button gates it), that is `ClientPreferencesRoot` + `NewsletterOptInControl` / `SavePersonalDataOptInControl` — a different matter with the same visible checkboxes. The image alone cannot always distinguish the two; the surrounding form structure is the signal.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## When to use
|
|
27
|
+
|
|
28
|
+
- A page that wants the two consent flags to submit together with other fieldsets (e.g. `ClientProfileFieldset`, `ShippingAddressFieldset`) behind one shared `FormRoot` submit.
|
|
29
|
+
- Anywhere the store wants the write timing (batched, on submit) legible from the schema, rather than immediate.
|
|
30
|
+
|
|
31
|
+
## When NOT to use
|
|
32
|
+
|
|
33
|
+
- A standalone opt-in checkbox that should persist itself the instant it's toggled — use `ClientPreferencesRoot` + `NewsletterOptInControl` / `SavePersonalDataOptInControl` instead.
|
|
34
|
+
- Placing `NewsletterOptInControl` or `SavePersonalDataOptInControl` inside this fieldset, or `ClientPreferencesNewsletterControl` / `ClientPreferencesSavePersonalDataControl` inside `ClientPreferencesRoot` — neither compiles; see *Two write timings* above.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Composition
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
SessionRoot (required ancestor; session tri-state + canEditData)
|
|
42
|
+
└── FormRoot (the only <form> it dispatches from; owns useForm() and the registry)
|
|
43
|
+
├── ClientPreferencesFieldset (data-fs-client-preferences-fieldset; registers namespace "clientPreferences")
|
|
44
|
+
│ ├── ClientPreferencesNewsletterControl
|
|
45
|
+
│ └── ClientPreferencesSavePersonalDataControl
|
|
46
|
+
├── (any other fieldset — e.g. ClientProfileFieldset — as a sibling, same FormRoot)
|
|
47
|
+
├── FormError
|
|
48
|
+
└── FormSubmit
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`ClientPreferencesFieldset` renders one `Fieldset.Root` and no `<form>` of its own. Every control `requiresContext: 'client-preferences'`; a control placed outside it (or under `ClientPreferencesRoot` instead) is rejected by the context-constraint walk at build time.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Components
|
|
56
|
+
|
|
57
|
+
### ClientPreferencesFieldset
|
|
58
|
+
|
|
59
|
+
| Prop | Type | Default | Notes |
|
|
60
|
+
|------|------|---------|-------|
|
|
61
|
+
| `skeletonCount` | number | `2` | Placeholder rows drawn while the preferences resolve. Set it to the number of controls composed. |
|
|
62
|
+
| `loadingLabel` | string | `'Loading your preferences'` | Announced politely while the preferences resolve. Blank means no announcement. |
|
|
63
|
+
| `loadedLabel` | string | `'Your preferences are ready'` | Announced politely once the controls mount. Blank means no announcement. |
|
|
64
|
+
|
|
65
|
+
Declares `requiresContext: ['form', 'session']` (needs both a `FormRoot` and a `SessionRoot` ancestor), `providesContext: 'client-preferences'`, `actions: { identity: ['clientPreferencesUpdate'] }`, and its own fragment on `Query.clientPreferences`. Renders `Fieldset.Root` with `disabled={readOnly}`; carries `-pending` until its entity resolves, and `-readonly` when the session reports `canEditData: false`.
|
|
66
|
+
|
|
67
|
+
**Loading:** the controls mount once, after the entity has resolved; until then the fieldset renders `ClientPreferencesFieldsetSkeleton` itself, in their place. Each checkbox hands its saved value to react-hook-form as a field-level `defaultValue`, re-applied on every render for as long as the checkbox stays clean — mounting before the answer arrives would seed unchecked, and a shopper who toggles it before the answer lands makes the checkbox dirty on that seed, permanently locking their saved preference out of it; even a shopper who does not toggle it sees the checkbox flash to the saved value. Resolution is the page query settling, latched: once the controls are up a later refetch never takes them down, so nothing the shopper toggled is lost.
|
|
68
|
+
|
|
69
|
+
The definition also declares that same component as its `skeleton`, which is the shape the repo's skeleton-with-fragment rule asks for, but nothing reads it today: the compiler wires `skeleton` into a Suspense fallback only on the `lazy` path, and this definition is `reactive`. The generator still emits the import, so a generated page carries an unused `ClientPreferencesFieldsetSkeleton` symbol.
|
|
70
|
+
|
|
71
|
+
`skeletonCount` sizes the wait. The skeleton draws two rows by default, one per consent flag in the full anatomy; a page that composes one sets `skeletonCount: 1`, or the form jumps by a row when the controls take the skeleton's place. The skeleton is `aria-hidden` — the wait is spoken instead by a polite live region the fieldset keeps mounted in both states, carrying `loadingLabel` and then `loadedLabel`, with `aria-busy` on the `<fieldset>` for as long as it lasts.
|
|
72
|
+
|
|
73
|
+
### ClientPreferencesNewsletterControl / ClientPreferencesSavePersonalDataControl
|
|
74
|
+
|
|
75
|
+
| Prop | Type | Default | Notes |
|
|
76
|
+
|------|------|---------|-------|
|
|
77
|
+
| `label` | string | `'Email me with news and offers'` / `'Save my information for next time'` | |
|
|
78
|
+
|
|
79
|
+
Childless, renders `Field.Root > Field.Label > Field.Checkbox` (with the checkmark icon), the label text following. Composed children replace that default anatomy and still bind to the same field. Defaults to unchecked (opt-in, not opt-out) — a `null` saved value reads as `false`.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Canonical schema example
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"session": { "type": "SessionRoot", "children": ["form"] },
|
|
88
|
+
"form": {
|
|
89
|
+
"type": "FormRoot",
|
|
90
|
+
"children": ["preferences-fieldset", "form-error", "form-submit"]
|
|
91
|
+
},
|
|
92
|
+
"preferences-fieldset": {
|
|
93
|
+
"type": "ClientPreferencesFieldset",
|
|
94
|
+
"children": ["newsletter", "save-personal-data"]
|
|
95
|
+
},
|
|
96
|
+
"newsletter": {
|
|
97
|
+
"type": "ClientPreferencesNewsletterControl",
|
|
98
|
+
"props": { "label": "Sign me up for the newsletter" },
|
|
99
|
+
"children": []
|
|
100
|
+
},
|
|
101
|
+
"save-personal-data": {
|
|
102
|
+
"type": "ClientPreferencesSavePersonalDataControl",
|
|
103
|
+
"children": []
|
|
104
|
+
},
|
|
105
|
+
"form-error": { "type": "FormError", "children": [] },
|
|
106
|
+
"form-submit": { "type": "FormSubmit", "children": [] }
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
(Omitting `save-personal-data` above drops that flag entirely — its default stays untouched server-side, with no CSS hack.)
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## FormClear coerces booleans
|
|
115
|
+
|
|
116
|
+
`FormClear` empties every registered field to `''`. `ClientPreferencesFieldset`'s own `toPayload` runs `Boolean()` over each flag present in the namespace snapshot, so a cleared field dispatches `false` on the following submit — never the raw string `''`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Common mistakes
|
|
121
|
+
|
|
122
|
+
- **Confusing this fieldset's controls with `NewsletterOptInControl` / `SavePersonalDataOptInControl`.** See *Two write timings* above — they require different contexts and neither compiles under the other's provider.
|
|
123
|
+
- **Composing this fieldset without a `FormRoot` ancestor.** It requires both `form` and `session`; a `SessionRoot` alone is not enough.
|
|
124
|
+
- **Expecting a submit button from this fieldset.** It renders none — compose `FormSubmit` once, anywhere inside the enclosing `FormRoot`.
|
|
125
|
+
- **Composing `ClientPreferencesNewsletterControl` (or the other control) outside `ClientPreferencesFieldset`.** Every control `requiresContext: 'client-preferences'` and throws otherwise.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Styling
|
|
130
|
+
|
|
131
|
+
| Component | What you style | Plausible classes |
|
|
132
|
+
|---|---|---|
|
|
133
|
+
| `ClientPreferencesFieldset` | The `data-fs-client-preferences-fieldset` root; carries `-pending`/`-readonly` presence attributes | `flex flex-col gap-2` |
|
|
134
|
+
| `ClientPreferencesFieldsetSkeleton` | `data-fs-client-preferences-fieldset-skeleton` and its `-skeleton-field` rows | `flex flex-col gap-2`, rows `h-5 rounded bg-gray-100 animate-pulse` |
|
|
135
|
+
| Announcement | `data-fs-client-preferences-fieldset-announcement` reaches the live region carrying `loadingLabel`/`loadedLabel`. Hide it with a clipping technique, never `display:none` — that drops it from the accessibility tree and defeats the announcement | `sr-only` |
|
|
136
|
+
| Control wrapper | `data-fs-client-preferences-fieldset-field="newsletter\|save-personal-data"` | `flex items-center gap-2` |
|
|
137
|
+
|
|
138
|
+
Checkbox-level visuals (the indicator, the checked state) are owned by the composed `checkbox`/`field` particles' own swatches and are not redefined here.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Related
|
|
143
|
+
|
|
144
|
+
- [[client-preferences]] — the self-persisting Root and Controls this fieldset shares an entity with; see *Two write timings* above.
|
|
145
|
+
- [[client-profile-fieldset]] — the sibling fieldset for the other entity a checkout identity step commonly needs; composes into the same `FormRoot`.
|
|
146
|
+
- [[session]] — provides the identification tri-state and the required `SessionRoot` ancestor.
|
|
147
|
+
- [[form]] — `FormRoot`/`FormSubmit`/`FormError`/`FormClear`/`FormReset`, the enclosing form this fieldset registers with.
|
|
148
|
+
- [[field]], [[fieldset]], [[checkbox]] — the particles this matter composes directly.
|
|
@@ -50,7 +50,7 @@ SessionRoot (provides the session tri-state; required a
|
|
|
50
50
|
|
|
51
51
|
### ClientPreferencesRoot
|
|
52
52
|
|
|
53
|
-
Binding provider for the section. Reads both opt-in values from `Query.clientPreferences`, seeds each Control, and tracks per-field pending/error. Renders a `<div data-fs-client-preferences-root>` wrapper. No merchant-facing props beyond `className`.
|
|
53
|
+
Binding provider for the section. Reads both opt-in values from `Query.clientPreferences`, seeds each Control, and tracks per-field pending/error. Renders a `<div data-fs-client-preferences-root>` wrapper. No merchant-facing props beyond `className`. Provides context `client-preferences-toggle` — a different name from the batched `ClientPreferencesFieldset` (context `client-preferences`), so the two write timings for the same entity cannot compile into each other's container. See [[client-preferences-fieldset]] for the one-Save alternative.
|
|
54
54
|
|
|
55
55
|
**Data:** declares a colocated `fragment ClientPreferencesData on Query { clientPreferences { optinNewsLetter savePersonalData } }` with `fetchStrategy: 'reactive'`. The compiler merges it into the page query — no explicit `dataSources` entry needed.
|
|
56
56
|
|
|
@@ -179,6 +179,7 @@ Styled example:
|
|
|
179
179
|
## Related
|
|
180
180
|
|
|
181
181
|
- [[email-identification]] — sibling identity control; reads/writes `clientProfile.email` and moves the session `anonymous → identified`. Same `SessionRoot` requirement.
|
|
182
|
+
- [[client-preferences-fieldset]] — the batched, one-Save alternative for the same two flags; requires a `FormRoot` and a different context (`client-preferences`), so it cannot compile alongside `NewsletterOptInControl` / `SavePersonalDataOptInControl` in the same container.
|
|
182
183
|
- [[session]] — provides the identification tri-state and the required `SessionRoot` ancestor.
|
|
183
184
|
- [[checkbox]] — the presentational particle each Control wraps.
|
|
184
185
|
- [[field]] — provides the label/error wrapper each Control inlines.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# ClientProfileFieldset
|
|
2
|
+
|
|
3
|
+
**Category:** identity
|
|
4
|
+
**Purpose:** The composable, one-Save write of the `ClientProfile` entity. Registers itself with the enclosing `FormRoot` under the `clientProfile` namespace and dispatches at most one `identity.clientProfileUpdate` when the shopper presses that form's submit. Four bound controls — first name, last name, phone, document — compose freely inside it, in any order or subset.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Naming collision — read this before composing the phone control
|
|
9
|
+
|
|
10
|
+
Two phone controls exist for the same entity, with different write timings, and their names are easy to swap by accident:
|
|
11
|
+
|
|
12
|
+
- **`ClientProfileFieldsetPhoneControl`** (this matter) — the batched control. It writes nothing on its own; it registers `clientProfile.phone` with the enclosing `FormRoot` and waits for that form's submit.
|
|
13
|
+
- **`ClientProfilePhoneControl`** (`matter/client-profile/client-profile.tsx`) — the on-blur control. It writes `clientProfileUpdate({ phone })` itself, the moment the field loses focus.
|
|
14
|
+
|
|
15
|
+
Neither compiles inside the other's container: `ClientProfileFieldsetPhoneControl` requires `client-profile-fieldset` context (provided only by `ClientProfileFieldset`), and `ClientProfilePhoneControl` requires `client-profile` context (provided only by `ClientProfileRoot`). Composing one under the other's Root fails the build — the compiler's `context-constraint-violation` diagnostic names the missing context.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Visual cues (how to identify in an image)
|
|
20
|
+
|
|
21
|
+
- A group of shopper-profile inputs (first name, last name, a country-masked phone field, an optional tax-document field) inside a page that also shows a **single submit button somewhere else on the page**, not one owned by this fieldset.
|
|
22
|
+
- No visible Save/Continue button belongs to this block by itself — the nearest `FormSubmit` (composed by `FormRoot`) is what dispatches it.
|
|
23
|
+
- The image is the source of truth for layout.
|
|
24
|
+
|
|
25
|
+
Use `ClientProfileFieldset` (not the standalone `ClientProfilePhoneControl`) whenever the profile fields are meant to submit together with other fieldsets on the same page, behind one shared button.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## When to use
|
|
30
|
+
|
|
31
|
+
- A page that wants the shopper's profile fields (name, phone, document) to write behind a shared `FormRoot` submit, alongside other fieldsets (e.g. `ShippingAddressFieldset`, `ClientPreferencesFieldset`) — the RFC's "compose a profile write and an address write behind one submit" scenario.
|
|
32
|
+
- Anywhere the store wants one entity, one action, one namespace, with the write timing (batched, on submit) legible from the schema.
|
|
33
|
+
|
|
34
|
+
## When NOT to use
|
|
35
|
+
|
|
36
|
+
- A single phone field that should persist itself on blur — use `ClientProfilePhoneControl` (`matter/client-profile`).
|
|
37
|
+
- Editing the email — that's `EmailIdentificationControl`'s job; no control in this fieldset touches it.
|
|
38
|
+
- Consent flags (newsletter, save-personal-data) — those belong to `ClientPreferencesFieldset`, a sibling namespace in the same `FormRoot`.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Composition
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
SessionRoot (required ancestor; session tri-state + canEditData)
|
|
46
|
+
└── FormRoot (the only <form> it dispatches from; owns useForm() and the registry)
|
|
47
|
+
├── ClientProfileFieldset (data-fs-client-profile-fieldset; registers namespace "clientProfile")
|
|
48
|
+
│ ├── ClientProfileFirstNameControl
|
|
49
|
+
│ ├── ClientProfileLastNameControl
|
|
50
|
+
│ ├── ClientProfileFieldsetPhoneControl (country/requirement/errors as props)
|
|
51
|
+
│ └── ClientProfileDocumentControl (renders nothing when documentType is unsupported)
|
|
52
|
+
├── (any other fieldset — e.g. ClientPreferencesFieldset — as a sibling, same FormRoot)
|
|
53
|
+
├── FormError
|
|
54
|
+
└── FormSubmit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`ClientProfileFieldset` renders one `Fieldset.Root` and no `<form>` of its own — `FormRoot` owns the only one. Every control `requiresContext: 'client-profile-fieldset'`; a control placed outside `ClientProfileFieldset` (or under `ClientProfileRoot` instead) is rejected by the context-constraint walk at build time.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Components
|
|
62
|
+
|
|
63
|
+
### ClientProfileFieldset
|
|
64
|
+
|
|
65
|
+
| Prop | Type | Default | Notes |
|
|
66
|
+
|------|------|---------|-------|
|
|
67
|
+
| `country` | `'US' \| 'BR'` | `'US'` | Phone mask/validation country, provided to the phone control via context. |
|
|
68
|
+
| `documentType` | string | — | e.g. `'CPF'`. The document control renders only when supported. |
|
|
69
|
+
| `skeletonCount` | number | `4` | Placeholder rows drawn while the profile resolves. Set it to the number of controls composed. |
|
|
70
|
+
| `loadingLabel` | string | `'Loading your details'` | Announced politely while the profile resolves. Blank means no announcement. |
|
|
71
|
+
| `loadedLabel` | string | `'Your details are ready'` | Announced politely once the controls mount. Blank means no announcement. |
|
|
72
|
+
|
|
73
|
+
Declares `requiresContext: ['form', 'session']` (needs both a `FormRoot` and a `SessionRoot` ancestor), `providesContext: 'client-profile-fieldset'`, `actions: { identity: ['clientProfileUpdate'] }`, and its own fragment on `Query.clientProfile`. Renders `Fieldset.Root` with `disabled={readOnly}`; carries `-pending` until its entity resolves, and `-readonly` when the session reports `canEditData: false`.
|
|
74
|
+
|
|
75
|
+
**Loading:** the controls mount once, after the entity has resolved; until then the fieldset renders `ClientProfileFieldsetSkeleton` itself, in their place. Each control hands its saved value to react-hook-form as a field-level `defaultValue`, re-applied on every render for as long as the field stays clean — mounting before the answer arrives would seed `''`, and a shopper who starts typing before it lands makes the field dirty on that empty seed, permanently locking their saved value out of it; even a shopper who does not type sees the field flash from `''` to the real data. Resolution is the page query settling, latched: once the controls are up a later refetch never takes them down, so nothing the shopper typed is lost.
|
|
76
|
+
|
|
77
|
+
The definition also declares that same component as its `skeleton`, which is the shape the repo's skeleton-with-fragment rule asks for, but nothing reads it today: the compiler wires `skeleton` into a Suspense fallback only on the `lazy` path, and this definition is `reactive`. The generator still emits the import, so a generated page carries an unused `ClientProfileFieldsetSkeleton` symbol.
|
|
78
|
+
|
|
79
|
+
`skeletonCount` sizes the wait. The skeleton draws four rows by default, one per control in the full anatomy; a page that composes three sets `skeletonCount: 3`, or the form jumps by a row when the controls take the skeleton's place. The skeleton is `aria-hidden` — the wait is spoken instead by a polite live region the fieldset keeps mounted in both states, carrying `loadingLabel` and then `loadedLabel`, with `aria-busy` on the `<fieldset>` for as long as it lasts.
|
|
80
|
+
|
|
81
|
+
### ClientProfileFirstNameControl / ClientProfileLastNameControl
|
|
82
|
+
|
|
83
|
+
| Prop | Type | Default | Notes |
|
|
84
|
+
|------|------|---------|-------|
|
|
85
|
+
| `label` | string | `'First name'` / `'Last name'` | |
|
|
86
|
+
| `placeholder` | string | — | |
|
|
87
|
+
|
|
88
|
+
Childless, renders `Field.Root > Field.Label + Field.Control + Field.Error`. Composed children replace that default anatomy and still bind to the same field.
|
|
89
|
+
|
|
90
|
+
### ClientProfileFieldsetPhoneControl
|
|
91
|
+
|
|
92
|
+
| Prop | Type | Default | Notes |
|
|
93
|
+
|------|------|---------|-------|
|
|
94
|
+
| `label` | string | `'Phone'` | |
|
|
95
|
+
| `requirement` | `'mandatory' \| 'optional'` | — | Requiredness only — the only source of it for this control, since the fieldset publishes no baseline. Unset raises nothing, so the phone is optional. A field the store does not want is omitted from the page, not switched off by a prop value. |
|
|
96
|
+
| `errorInvalid` | string | `'Invalid phone number'` | |
|
|
97
|
+
| `errorRequired` | string | `'Phone number is required'` | |
|
|
98
|
+
|
|
99
|
+
Composes the `PhoneField` particle. Recomposable (RFC:503): given children, they replace `PhoneField`'s
|
|
100
|
+
default `Field.Label` + `PhoneFieldInput` + `Field.Error` anatomy, in any order; childless keeps today's
|
|
101
|
+
order and text. To switch the phone field off, omit this element from the page — `requirement` never
|
|
102
|
+
governs visibility (see *Switching the phone field off* below).
|
|
103
|
+
|
|
104
|
+
### ClientProfileDocumentControl
|
|
105
|
+
|
|
106
|
+
| Prop | Type | Default | Notes |
|
|
107
|
+
|------|------|---------|-------|
|
|
108
|
+
| `label` | string | `'CPF'` | |
|
|
109
|
+
| `placeholder` | string | — | |
|
|
110
|
+
| `errorMessage` | string | — | |
|
|
111
|
+
|
|
112
|
+
Renders nothing, and registers no field name, when the fieldset's `documentType` is unsupported. Composes
|
|
113
|
+
the `DocumentField` particle. Recomposable (RFC:503): given children, they replace `DocumentField`'s
|
|
114
|
+
default `Field.Label` + `DocumentFieldInput` + `Field.Error` anatomy, in any order; childless keeps
|
|
115
|
+
today's order and text.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Canonical schema example
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"session": { "type": "SessionRoot", "children": ["form"] },
|
|
124
|
+
"form": {
|
|
125
|
+
"type": "FormRoot",
|
|
126
|
+
"children": ["profile-fieldset", "form-error", "form-submit"]
|
|
127
|
+
},
|
|
128
|
+
"profile-fieldset": {
|
|
129
|
+
"type": "ClientProfileFieldset",
|
|
130
|
+
"props": { "country": "BR", "documentType": "CPF" },
|
|
131
|
+
"children": ["first-name", "last-name", "phone", "document"]
|
|
132
|
+
},
|
|
133
|
+
"first-name": { "type": "ClientProfileFirstNameControl", "children": [] },
|
|
134
|
+
"last-name": { "type": "ClientProfileLastNameControl", "children": [] },
|
|
135
|
+
"phone": { "type": "ClientProfileFieldsetPhoneControl", "children": [] },
|
|
136
|
+
"document": { "type": "ClientProfileDocumentControl", "children": [] },
|
|
137
|
+
"form-error": { "type": "FormError", "children": [] },
|
|
138
|
+
"form-submit": { "type": "FormSubmit", "children": [] }
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Adding a second fieldset (e.g. `ClientPreferencesFieldset`) as another child of `form`, alongside `profile-fieldset`, is what puts both entities behind the one `form-submit` button — the RFC's core composition.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## Switching the phone field off
|
|
147
|
+
|
|
148
|
+
`requirement` accepts only `'mandatory'` and `'optional'`, and left out it raises nothing — it governs required validation, never visibility. There is no prop value that hides the phone field: a store that does not want it omits `ClientProfileFieldsetPhoneControl` from the page entirely, which also means `clientProfile.phone` is never registered and can never block a submit. A stale `page.json` still carrying `"requirement": "hidden"` fails the generated store's own `next build` with a `TS2322` type error naming the attribute — it is not a schema-validation failure, and no diagnostic fires at `sukima build`.
|
|
149
|
+
|
|
150
|
+
No control in this fieldset ever puts `required` (or any other `RegisterOptions` key react-hook-form evaluates before `validate`) into its field contract, so the fieldset declares no baseline at all. Requiredness on the phone control rides only on its own `requirement` prop, which `PhoneField` passes to `useBoundField` as a raise: mandatory-and-empty means blocked, optional means clean. Because the baseline is empty here, the raise is the whole of it — where a fieldset does declare a baseline, `'optional'` could not lower it. A first-name or last-name control can never be required today — only the phone control has a `requirement` prop, because only `PhoneField` implements one.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Common mistakes
|
|
155
|
+
|
|
156
|
+
- **Confusing `ClientProfileFieldsetPhoneControl` with `ClientProfilePhoneControl`.** See *Naming collision* above — they require different contexts and neither compiles under the other's Root.
|
|
157
|
+
- **Composing this fieldset without a `FormRoot` ancestor.** It requires both `form` and `session`; a `SessionRoot` alone is not enough.
|
|
158
|
+
- **Expecting a submit button from this fieldset.** It renders none — compose `FormSubmit` once, anywhere inside the enclosing `FormRoot`.
|
|
159
|
+
- **Adding a `required` prop to a text control.** There is none — the fieldset never blocks a submit on requiredness for a field it did not choose to gate. See *Switching the phone field off*.
|
|
160
|
+
- **Setting `requirement="hidden"` to hide the phone field.** The value does not exist; TypeScript rejects it and a generated store fails `next build`. Omit `ClientProfileFieldsetPhoneControl` from the page instead.
|
|
161
|
+
- **Composing `ClientProfileFirstNameControl` (or any control here) outside `ClientProfileFieldset`.** Every control `requiresContext: 'client-profile-fieldset'` and throws otherwise.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Styling
|
|
166
|
+
|
|
167
|
+
| Component | What you style | Plausible classes |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `ClientProfileFieldset` | The `data-fs-client-profile-fieldset` root; carries `-pending`/`-readonly` presence attributes | `flex flex-col gap-4` |
|
|
170
|
+
| Control wrapper | `data-fs-client-profile-fieldset-field="first-name\|last-name\|phone\|document"` | `flex flex-col gap-1` |
|
|
171
|
+
| `ClientProfileFieldsetSkeleton` | `data-fs-client-profile-fieldset-skeleton` and its `-skeleton-field` rows | `flex flex-col gap-4`, rows `h-11 rounded-lg bg-gray-100 animate-pulse` |
|
|
172
|
+
| Announcement | `data-fs-client-profile-fieldset-announcement` reaches the live region carrying `loadingLabel`/`loadedLabel`. Hide it with a clipping technique, never `display:none` — that drops it from the accessibility tree and defeats the announcement | `sr-only` |
|
|
173
|
+
|
|
174
|
+
Field-level visuals (borders, focus rings, error color) are owned by the composed particles' own swatches (`field`, `phone-field`, `document-field`) and are not redefined here.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related
|
|
179
|
+
|
|
180
|
+
- [[client-profile]] — the standalone, on-blur phone binding; see *Naming collision* above.
|
|
181
|
+
- [[client-preferences-fieldset]] — the sibling fieldset for the other entity a checkout identity step commonly needs; composes into the same `FormRoot`.
|
|
182
|
+
- [[session]] — provides the identification tri-state and the required `SessionRoot` ancestor.
|
|
183
|
+
- [[form]] — `FormRoot`/`FormSubmit`/`FormError`/`FormClear`/`FormReset`, the enclosing form this fieldset registers with.
|
|
184
|
+
- [[phone-field]], [[document-field]], [[field]], [[fieldset]] — the particles this matter composes directly.
|
|
@@ -58,7 +58,7 @@ Binds the reusable `PhoneField` particle to the profile. Reads the saved phone f
|
|
|
58
58
|
|------|------|---------|-------|
|
|
59
59
|
| `label` | string | `'Phone'` | Visible field label. |
|
|
60
60
|
| `country` | `'US' \| 'BR'` | `'US'` | Selects the phone mask and validation rule. |
|
|
61
|
-
| `requirement` | `'mandatory' \| 'optional'
|
|
61
|
+
| `requirement` | `'mandatory' \| 'optional'` | `'mandatory'` | Controls required validation. A field the store does not want is omitted from the page rather than hidden by a prop. |
|
|
62
62
|
| `errorInvalid` | string | `'Invalid phone number'` | Inline error for an invalid format. |
|
|
63
63
|
| `errorRequired` | string | `'Phone number is required'` | Inline error when a mandatory field is empty. |
|
|
64
64
|
| `$actions` | injected | — | Carries `identity.clientProfileUpdate` (compiler-injected). |
|
|
@@ -28,15 +28,24 @@ If the image shows a checkout identification step with a tax-document field, use
|
|
|
28
28
|
|
|
29
29
|
## Composition
|
|
30
30
|
|
|
31
|
-
`DocumentField`
|
|
31
|
+
`DocumentField` provides `document-field` context and, childless, renders:
|
|
32
32
|
|
|
33
33
|
```
|
|
34
|
-
DocumentField (data-fs-document-field +
|
|
34
|
+
DocumentField (data-fs-document-field + data-fs-field-root)
|
|
35
35
|
├── Field.Label (data-fs-field-label; the `label` prop)
|
|
36
|
-
├──
|
|
36
|
+
├── DocumentFieldInput (data-fs-field-control; native <input inputmode="numeric">)
|
|
37
37
|
└── Field.Error (data-fs-field-error; the `errorMessage`, shown when invalid)
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
**Recomposable (RFC:503):** given children, `DocumentField` renders them in place of the three parts
|
|
41
|
+
above, in any order. `DocumentFieldInput` is the one catalogued part that requires `document-field`
|
|
42
|
+
context — it reads the masked value, the composed ref and the particle's own `onChange`/`onBlur` from
|
|
43
|
+
`DocumentField` itself, so it must sit inside it; it carries the same `data-fs-field-control` hook the
|
|
44
|
+
default anatomy always used, so a recomposed page keeps its existing CSS. A composed childless
|
|
45
|
+
`Field.Label` / `Field.Error` (the generic parts from the `field` particle) works exactly as it does under
|
|
46
|
+
any other bound Control; a composed generic `Field.Control` instead re-registers the field with
|
|
47
|
+
react-hook-form and loses the mask — unsupported, the RFC's named double-binder hole.
|
|
48
|
+
|
|
40
49
|
---
|
|
41
50
|
|
|
42
51
|
## Props
|
|
@@ -52,9 +61,11 @@ DocumentField (data-fs-document-field + inherited data-fs-field-roo
|
|
|
52
61
|
| `label` | string | — | Accessible label. |
|
|
53
62
|
| `placeholder` | string | — | Input placeholder. |
|
|
54
63
|
| `disabled` | boolean | `false` | Disables the input (e.g. while a write is in flight). |
|
|
55
|
-
| `
|
|
64
|
+
| `requirement` | `'mandatory' \| 'optional'` | — | Raises the field to required, announced as `aria-required` (never the native attribute). Unset is no opinion, which is what keeps CPF Optional by Recommendation, unlike email. Bound to a fieldset whose rule already says required, `'optional'` does not lower it. |
|
|
56
65
|
| `name` | string | `'document'` | Field/input name. |
|
|
57
66
|
| `documentType` | `'CPF'` | `'CPF'` | Selects the mask + validator + digit-only input mode. |
|
|
67
|
+
| `ref` | `Ref<HTMLInputElement>` | — | Lands on the `<input>` `Field.Control` renders — **not on the wrapper `<div>`, as it did before** (a deliberate, source-breaking change). Composed with react-hook-form's own ref on the bound path — a caller ref and react-hook-form's ref are both honoured. |
|
|
68
|
+
| `children` | `ReactNode` | — | Given, replaces the default `Field.Label` + `DocumentFieldInput` + `Field.Error` anatomy, in any order (RFC:503). |
|
|
58
69
|
|
|
59
70
|
**Masking:** applied progressively on every change; input is never blocked. Digit-only masks (CPF) strip non-digits and hint `inputMode="numeric"`.
|
|
60
71
|
|
|
@@ -62,6 +73,49 @@ DocumentField (data-fs-document-field + inherited data-fs-field-roo
|
|
|
62
73
|
|
|
63
74
|
---
|
|
64
75
|
|
|
76
|
+
## Bound behaviour
|
|
77
|
+
|
|
78
|
+
`DocumentField` reads the field contract through `useField()` and binds through `useController` when both
|
|
79
|
+
a field contract (`FieldBinding`) and a react-hook-form `FormProvider` are present above it; otherwise it
|
|
80
|
+
runs the plain, unbound behaviour documented above, unaltered.
|
|
81
|
+
|
|
82
|
+
- **What the contract supplies**: `label` and `placeholder`, when the particle's own prop is omitted (an
|
|
83
|
+
explicit prop always wins); `defaultValue`, seeded into `useController`; and `rule`, merged with the
|
|
84
|
+
particle's own check-digit validator (below).
|
|
85
|
+
- **What stays the particle's**: the CPF mask, `documentType`, and the `errorMessage` copy.
|
|
86
|
+
- **Single binder, real contract for the read-only parts (RFC:503)**: the composite remains the sole
|
|
87
|
+
binder for its field — `useController` is called once, here. Its default (and a recomposed) anatomy's
|
|
88
|
+
input is `DocumentFieldInput`, which renders the raw Base UI `Field.Control` directly rather than this
|
|
89
|
+
package's own bound-aware wrapper, so it never independently re-registers the name and overwrites the
|
|
90
|
+
composite's masked value. `Field.Label` / `Field.Error` composed by an author, by contrast, do read the
|
|
91
|
+
real (non-empty) re-provided contract — carrying the *resolved* `name`/`label`/`placeholder` — so a
|
|
92
|
+
composed childless `Field.Label` renders real text; a composed childless `Field.Error` renders
|
|
93
|
+
react-hook-form's real message only when one exists — when `errorMessage` is unset the check-digit
|
|
94
|
+
validator reports failure with a bare `false`, react-hook-form's message is empty, and `Field.Error`
|
|
95
|
+
renders nothing, exactly like the default anatomy below. An earlier design (`FieldContractBoundary`)
|
|
96
|
+
pushed an empty contract instead, which is why a composed childless label used to render nothing; that
|
|
97
|
+
mechanism is gone.
|
|
98
|
+
- **Value shape stored in react-hook-form**: the same **masked** string the unbound particle emits
|
|
99
|
+
through `onValueChange` (e.g. `111.444.777-35`), matching the asymmetry with `PhoneField`'s unmasked
|
|
100
|
+
storage — pre-existing and preserved on purpose.
|
|
101
|
+
- **Validation**: when the contract's `rule` carries no `validate` of its own, the composite installs its
|
|
102
|
+
own check-digit validator as the rule's `validate`, returning `errorMessage` on failure (or a bare
|
|
103
|
+
`false` when `errorMessage` is unset) — reproducing today's behaviour exactly: the field goes invalid,
|
|
104
|
+
and inline text renders only when `errorMessage` is supplied. A contract-supplied `validate` always wins
|
|
105
|
+
over the particle's own. An empty value still passes (today's optional-field rule).
|
|
106
|
+
- **Focus**: the `ref` lands on the `<input>` `DocumentFieldInput` renders, so a failed submit moves focus
|
|
107
|
+
to it — in the default anatomy and in a recomposed one, since the composed ref threads through
|
|
108
|
+
regardless of where the author places the part.
|
|
109
|
+
- **Explicit `invalid`/`errorMessage` props still surface** on the default (childless) anatomy
|
|
110
|
+
unconditionally — it renders the raw Base UI `Field.Label`/`Field.Error` primitives directly, so it
|
|
111
|
+
never gates on react-hook-form's own error state the way a composed childless `Field.Error` intentionally
|
|
112
|
+
does (above).
|
|
113
|
+
- `DocumentFieldDefinition` gains no `requiresContext` — the particle stays placeable outside any form. It
|
|
114
|
+
does declare `providesContext: 'document-field'` — that is what makes `DocumentFieldInput` a legal
|
|
115
|
+
child.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
65
119
|
## Accessibility
|
|
66
120
|
|
|
67
121
|
The label is associated with the input and the error is linked via `aria-describedby` automatically by the Base UI `Field` primitive (WCAG 2.1 AA). Standard text-input keyboard editing; masking/validation never blocks typing.
|