@vtex/faststore 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -0
- package/dist/actions.json +42 -0
- package/dist/catalog.json +731 -16
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/components.d.ts +9 -4
- package/dist/components.js +11 -6
- package/dist/content-types.json +300 -0
- package/dist/core/dist/define-component.d.ts +6 -1
- package/dist/docs/account-entry-point.md +4 -4
- package/dist/docs/account-menu.md +2 -2
- package/dist/docs/account-saved-cards.md +301 -0
- package/dist/docs/binding-switcher.md +154 -0
- package/dist/docs/card-entry.md +3 -3
- package/dist/docs/card-summary.md +175 -0
- package/dist/docs/cart-destination.md +238 -0
- package/dist/docs/cart-item.md +21 -3
- package/dist/docs/cart-totalizer.md +1 -1
- package/dist/docs/cart.md +118 -11
- package/dist/docs/footer-social.md +1 -1
- package/dist/docs/free-shipping-progress.md +13 -9
- package/dist/docs/incentives.md +2 -2
- package/dist/docs/logo.md +1 -1
- package/dist/docs/payment-method-badges.md +2 -2
- package/dist/docs/payment-methods.md +1 -1
- package/dist/docs/product-information.md +2 -2
- package/dist/docs/product-shelf.md +7 -0
- package/dist/docs/select.md +3 -4
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/docs/toast.md +30 -12
- package/dist/index.d.ts +2 -1
- package/dist/internal/client.d.ts +4 -1
- package/dist/internal/client.js +4 -1
- package/dist/internal/server.d.ts +4 -2
- package/dist/internal/server.js +4 -2
- package/dist/matter/account-saved-cards/account-saved-cards.d.ts +383 -0
- package/dist/matter/account-saved-cards/account-saved-cards.js +956 -0
- package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -1
- package/dist/matter/cart/cart-namespace.d.ts +5 -1
- package/dist/matter/cart/cart-namespace.js +5 -1
- package/dist/matter/cart/cart-namespace.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +34 -4
- package/dist/matter/cart/cart.js +238 -28
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-destination/cart-destination.d.ts +222 -0
- package/dist/matter/cart-destination/cart-destination.js +613 -0
- package/dist/matter/cart-destination/cart-destination.js.map +1 -0
- package/dist/matter/cart-item/cart-item.d.ts +4 -2
- package/dist/matter/cart-item/cart-item.js +75 -16
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/cart-totalizer/cart-totalizer.js +3 -2
- package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
- package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +6 -3
- package/dist/matter/free-shipping-progress/free-shipping-progress.js +76 -33
- package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
- package/dist/matter/pricing/pricing.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +7 -0
- package/dist/matter/product-shelf/product-shelf.js +115 -1
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.d.ts +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js +6 -2
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/particles/binding-switcher/binding-switcher.d.ts +38 -0
- package/dist/particles/binding-switcher/binding-switcher.js +183 -0
- package/dist/particles/binding-switcher/binding-switcher.js.map +1 -0
- package/dist/particles/card-summary/card-network.d.ts +17 -0
- package/dist/particles/card-summary/card-network.js +25 -0
- package/dist/particles/card-summary/card-network.js.map +1 -0
- package/dist/particles/card-summary/card-summary.d.ts +83 -0
- package/dist/particles/card-summary/card-summary.js +188 -0
- package/dist/particles/card-summary/card-summary.js.map +1 -0
- package/dist/particles/footer-social/footer-social.d.ts +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/footer-social/footer-social.js.map +1 -1
- package/dist/particles/incentives/incentives.d.ts +1 -1
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/particles/incentives/incentives.js.map +1 -1
- package/dist/particles/payment-methods/payment-methods.d.ts +1 -1
- package/dist/particles/payment-methods/payment-methods.js +1 -1
- package/dist/particles/payment-methods/payment-methods.js.map +1 -1
- package/dist/particles/toast/toast.d.ts +20 -6
- package/dist/particles/toast/toast.js +49 -7
- package/dist/particles/toast/toast.js.map +1 -1
- package/dist/schema.graphql +298 -34
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/locale-action-provider.d.ts +16 -0
- package/dist/sdk/actions/locale-action-provider.js +20 -0
- package/dist/sdk/actions/locale-action-provider.js.map +1 -0
- package/dist/sdk/actions/saved-card-action-provider.d.ts +12 -0
- package/dist/sdk/actions/saved-card-action-provider.js +16 -0
- package/dist/sdk/actions/saved-card-action-provider.js.map +1 -0
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/cache/storefront-cache.js +2 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +2 -1
- package/dist/sdk/cart/operations.js +55 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.d.ts +30 -7
- package/dist/sdk/cart/optimistic.js +78 -41
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +7 -0
- package/dist/sdk/cart/use-cart-actions.js +61 -15
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-sync-sales-channel.js +70 -0
- package/dist/sdk/cart/use-cart-sync-sales-channel.js.map +1 -0
- package/dist/sdk/generated/gql.js +15 -11
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.d.ts +177 -46
- package/dist/sdk/generated/graphql.js +2314 -93
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +19 -0
- package/dist/sdk/i18n/messages/en-US.json +19 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +19 -0
- package/dist/sdk/i18n/messages/es-ES.js +19 -0
- package/dist/sdk/i18n/messages/es-ES.json +19 -0
- package/dist/sdk/i18n/messages/pt-BR.js +19 -0
- package/dist/sdk/i18n/messages/pt-BR.json +19 -0
- package/dist/sdk/i18n/provider.js +1 -1
- package/dist/sdk/i18n/provider.js.map +1 -1
- package/dist/sdk/locale/binding-switch-provider.d.ts +13 -0
- package/dist/sdk/locale/binding-switch-provider.js +20 -0
- package/dist/sdk/locale/binding-switch-provider.js.map +1 -0
- package/dist/sdk/locale/use-locale-actions.d.ts +16 -0
- package/dist/sdk/locale/use-locale-actions.js +70 -0
- package/dist/sdk/locale/use-locale-actions.js.map +1 -0
- package/dist/sdk/navigation/binding-switch.d.ts +39 -0
- package/dist/sdk/navigation/binding-switch.js +129 -0
- package/dist/sdk/navigation/binding-switch.js.map +1 -0
- package/dist/sdk/navigation/decode-route-params.d.ts +11 -0
- package/dist/sdk/navigation/decode-route-params.js +32 -0
- package/dist/sdk/navigation/decode-route-params.js.map +1 -0
- package/dist/sdk/saved-cards/operations.d.ts +16 -0
- package/dist/sdk/saved-cards/operations.js +41 -0
- package/dist/sdk/saved-cards/operations.js.map +1 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.d.ts +26 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.js +86 -0
- package/dist/sdk/saved-cards/use-saved-card-actions.js.map +1 -0
- package/dist/sdk/seo/page-alternates.d.ts +5 -3
- package/dist/sdk/seo/page-alternates.js +7 -12
- package/dist/sdk/seo/page-alternates.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js +13 -0
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +9 -2
- package/dist/sdk/state/cart.js +9 -2
- package/dist/sdk/state/cart.js.map +1 -1
- package/package.json +2 -2
- package/src/styles/tailwind.css +1 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"account-saved-cards.js","names":[],"sources":["../../../src/matter/account-saved-cards/account-saved-cards.tsx"],"sourcesContent":["'use client';\n\nimport type { ErrorLike } from '@apollo/client';\nimport { CombinedGraphQLErrors } from '@apollo/client/errors';\nimport { type ContentValue, defineComponent, defineContentType } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n cloneElement,\n createContext,\n isValidElement,\n type MouseEvent,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport {\n AlertDialogBackdrop,\n AlertDialogClose,\n AlertDialogDescription,\n AlertDialogPopup,\n AlertDialogPortal,\n AlertDialogRoot,\n AlertDialogTitle,\n AlertDialogTrigger,\n AlertDialogViewport,\n} from '../../particles/alert-dialog/alert-dialog.js';\nimport { CardSummary, CardSummaryRoot, useCardSummary } from '../../particles/card-summary/card-summary.js';\nimport { NavigationLink } from '../../particles/navigation-link/navigation-link.js';\nimport { ActionsOverrideContext, useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { ListItemRefContext } from '../../sdk/data/list-item-ref-context.js';\nimport { ReactiveRootContext } from '../../sdk/data/page-reactive-data-root.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport type { CustomerSavedCardStatus } from '../../sdk/generated/graphql.js';\nimport { useIntl } from '../../sdk/i18n/client.js';\nimport { useStorefrontRoute } from '../../sdk/navigation/use-storefront-route.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { signInTarget } from '../../utils/safe-href.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The signed-in shopper's saved cards in the account.\n * The Root reads card ids only; each AccountSavedCardItem subscribes to its own CustomerSavedCard\n * (ADR-0012) and provides `card-summary` and the row itself, so the schema arranges the CardSummary\n * parts and AccountSavedCardExpiry in the row.\n * Every text resolves children, then the CMS entry, then the `fs/saved-cards.*` catalog; the expiry\n * line, repeated per row, takes no children.\n * Removal state lives in the Root, because a successful removal unmounts the row it started from. The\n * Root also declares the `savedCard.remove` write, so a remove control rendered by a default, which the\n * compiler cannot wrap, still reaches the provider.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL fragments\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment AccountSavedCardsData on Query {\n customerSavedCards {\n __typename\n id\n cards {\n __typename\n id\n }\n }\n }\n`);\n\nconst itemFragment = graphql(`\n fragment AccountSavedCardItemData on Query {\n customerSavedCards {\n cards {\n __typename\n id\n network {\n name\n }\n lastDigits\n expiry {\n month\n year\n }\n status\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Content models — optional and default-free, so an unauthored field falls through to the catalog\n// ---------------------------------------------------------------------------\n\nexport const AccountSavedCardsHeadingContent = defineContentType({\n id: 'account-saved-cards-heading',\n title: 'Account Saved Cards Heading',\n description: \"Heading of the account's saved-cards section.\",\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Heading' },\n },\n },\n});\n\nexport const AccountSavedCardsEmptyContent = defineContentType({\n id: 'account-saved-cards-empty',\n title: 'Account Saved Cards Empty',\n description: 'Statement shown when the shopper has no saved cards.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Empty statement' },\n },\n },\n});\n\nexport const AccountSavedCardsLoadingContent = defineContentType({\n id: 'account-saved-cards-loading',\n title: 'Account Saved Cards Loading',\n description: 'Announcement read to assistive technology while the saved cards load.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Loading announcement' },\n },\n },\n});\n\nexport const AccountSavedCardsErrorContent = defineContentType({\n id: 'account-saved-cards-error',\n title: 'Account Saved Cards Error',\n description: 'Messages shown when the saved cards cannot be read.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Load failure message' },\n sessionExpiredText: { type: 'string', minLength: 1, title: 'Session expired message' },\n signInLabel: { type: 'string', minLength: 1, title: 'Sign-in link label' },\n },\n },\n});\n\nexport const AccountSavedCardExpiryContent = defineContentType({\n id: 'account-saved-card-expiry',\n title: 'Account Saved Card Expiry',\n description: \"A saved card's expiry line. The date follows the label and is never authored.\",\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n expiresLabel: { type: 'string', minLength: 1, title: \"Label before a working card's date\" },\n expiredLabel: { type: 'string', minLength: 1, title: \"Label before an expired card's date\" },\n expiredText: { type: 'string', minLength: 1, title: 'Expired card with no date' },\n unusableText: { type: 'string', minLength: 1, title: 'Unusable card' },\n },\n },\n});\n\nexport const AccountSavedCardRemoveTriggerContent = defineContentType({\n id: 'account-saved-card-remove-trigger',\n title: 'Account Saved Card Remove Trigger',\n description: \"Label of the control that opens a saved card's removal prompt.\",\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Trigger label' },\n },\n },\n});\n\nexport const AccountSavedCardRemoveTitleContent = defineContentType({\n id: 'account-saved-card-remove-title',\n title: 'Account Saved Card Remove Title',\n description: 'Question that titles the removal prompt.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Prompt title' },\n },\n },\n});\n\nexport const AccountSavedCardRemoveDescriptionContent = defineContentType({\n id: 'account-saved-card-remove-description',\n title: 'Account Saved Card Remove Description',\n description: 'What removing the card means, shown after the card in the removal prompt.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Consequence' },\n },\n },\n});\n\nexport const AccountSavedCardRemoveCancelContent = defineContentType({\n id: 'account-saved-card-remove-cancel',\n title: 'Account Saved Card Remove Cancel',\n description: 'Label of the control that keeps the card.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Cancel label' },\n },\n },\n});\n\nexport const AccountSavedCardRemoveConfirmContent = defineContentType({\n id: 'account-saved-card-remove-confirm',\n title: 'Account Saved Card Remove Confirm',\n description: 'Label of the control that removes the card, and its label while the removal runs.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n text: { type: 'string', minLength: 1, title: 'Confirm label' },\n pendingText: { type: 'string', minLength: 1, title: 'Label while removing' },\n },\n },\n});\n\nexport const AccountSavedCardsRemoveFeedbackContent = defineContentType({\n id: 'account-saved-cards-remove-feedback',\n title: 'Account Saved Cards Remove Feedback',\n description: 'Messages that tell the shopper how a card removal ended.',\n schema: {\n type: 'object',\n additionalProperties: false,\n properties: {\n successText: { type: 'string', minLength: 1, title: 'Card removed' },\n failureText: { type: 'string', minLength: 1, title: 'Card not removed' },\n sessionExpiredText: { type: 'string', minLength: 1, title: 'Session expired message' },\n signInLabel: { type: 'string', minLength: 1, title: 'Sign-in link label' },\n },\n },\n});\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The unwrapped `customerSavedCards` entity the reactive runtime delivers to the Root. */\nexport type AccountSavedCardsData = {\n id?: string | null;\n cards?: ReadonlyArray<{ id: string }> | null;\n} | null;\n\n/** One card's slice, delivered per row by the reactive-list-item runtime. */\nexport type AccountSavedCardItemData = {\n id: string;\n network: { name: string };\n lastDigits: string;\n expiry?: { month: number; year: number } | null;\n status: CustomerSavedCardStatus;\n} | null;\n\n/** The row a resolved AccountSavedCardItem provides to the parts inside it. */\nexport type AccountSavedCardContextValue = NonNullable<AccountSavedCardItemData>;\n\n/** `idle` is a null slice with no query in flight, such as a render with no reactive root. */\nexport type AccountSavedCardsState = 'idle' | 'loading' | 'error' | 'empty' | 'ready';\n\nexport type AccountSavedCardRemovalOutcome = Readonly<{ kind: 'removed' }> | Readonly<{ kind: 'failed'; code: string }>;\n\nexport type AccountSavedCardsRemoval = Readonly<{\n /** Card id of the removal in flight, or null. At most one per section. */\n pendingId: string | null;\n /** The last removal's outcome, or null before any and after begin(). */\n outcome: AccountSavedCardRemovalOutcome | null;\n /** Starts a removal. False, with no state change, while another is pending. */\n begin: (id: string) => boolean;\n /** Ends the pending removal with the action's userErrors (empty on success). */\n settle: (id: string, userErrors: readonly { code: string }[]) => void;\n}>;\n\nexport type AccountSavedCardsContextValue = Readonly<{\n state: AccountSavedCardsState;\n cards: ReadonlyArray<{ id: string }>;\n /** `extensions.code` of the read's error, or `null`. Set only in the `error` state. */\n errorCode: string | null;\n removal: AccountSavedCardsRemoval;\n}>;\n\nexport type AccountSavedCardsRootProps = ComponentPropsWithRef<'div'> & {\n $data?: AccountSavedCardsData;\n /** Injected by the compiler from `actions: { savedCard: ['remove'] }`, for every remove control inside. */\n $actions?: ComponentActions;\n};\n\nexport type AccountSavedCardsHeadingLevel = 'h2' | 'h3' | 'h4' | 'h5' | 'h6';\n\nexport type AccountSavedCardsHeadingProps = ComponentPropsWithRef<'h2'> & {\n /** Heading level. `h1` is excluded: the page title belongs to the page shell. Defaults to `h2`. */\n as?: AccountSavedCardsHeadingLevel;\n $content?: ContentValue<typeof AccountSavedCardsHeadingContent>;\n};\n\nexport type AccountSavedCardsListProps = ComponentPropsWithRef<'ul'> & {\n /** The per-row template, typically one AccountSavedCardItem. */\n children?: ReactNode;\n};\n\nexport type AccountSavedCardItemProps = ComponentPropsWithRef<'li'> & {\n /** Per-row slice from the reactive-list-item runtime; passed directly in tests. */\n $data?: AccountSavedCardItemData;\n};\n\nexport type AccountSavedCardExpiryProps = Omit<ComponentPropsWithRef<'p'>, 'children'> & {\n $content?: ContentValue<typeof AccountSavedCardExpiryContent>;\n};\n\nexport type AccountSavedCardsEmptyProps = ComponentPropsWithRef<'p'> & {\n $content?: ContentValue<typeof AccountSavedCardsEmptyContent>;\n};\n\nexport type AccountSavedCardsLoadingProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /** Placeholder rows shown while the cards load. Defaults to 2. */\n skeletonCount?: number;\n $content?: ContentValue<typeof AccountSavedCardsLoadingContent>;\n};\n\nexport type AccountSavedCardsErrorProps = ComponentPropsWithRef<'div'> & {\n /** Replaces the message for every error code and drops the sign-in link. */\n children?: ReactNode;\n $content?: ContentValue<typeof AccountSavedCardsErrorContent>;\n};\n\n/** The remove controller a row's prompt parts read. */\nexport type AccountSavedCardRemoveValue = Readonly<{\n id: string;\n /** This card's removal is pending. */\n pending: boolean;\n /** Any removal in the section is pending. */\n blocked: boolean;\n close: () => void;\n}>;\n\nexport type AccountSavedCardRemoveProps = {\n /** One AlertDialogRoot to control, or none for the default prompt. */\n children?: ReactNode;\n};\n\nexport type AccountSavedCardRemoveTriggerProps = ComponentPropsWithRef<'button'> & {\n $content?: ContentValue<typeof AccountSavedCardRemoveTriggerContent>;\n};\n\nexport type AccountSavedCardRemoveTitleProps = ComponentPropsWithRef<'h2'> & {\n $content?: ContentValue<typeof AccountSavedCardRemoveTitleContent>;\n};\n\nexport type AccountSavedCardRemoveDescriptionProps = ComponentPropsWithRef<'p'> & {\n $content?: ContentValue<typeof AccountSavedCardRemoveDescriptionContent>;\n};\n\nexport type AccountSavedCardRemoveCancelProps = ComponentPropsWithRef<'button'> & {\n $content?: ContentValue<typeof AccountSavedCardRemoveCancelContent>;\n};\n\nexport type AccountSavedCardRemoveConfirmProps = ComponentPropsWithRef<'button'> & {\n $content?: ContentValue<typeof AccountSavedCardRemoveConfirmContent>;\n};\n\nexport type AccountSavedCardsRemoveFeedbackProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n $content?: ContentValue<typeof AccountSavedCardsRemoveFeedbackContent>;\n};\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nconst AccountSavedCardsContext = createContext<AccountSavedCardsContextValue | null>(null);\n\n/** The section state the nearest AccountSavedCardsRoot publishes. Throws outside one. */\nexport function useAccountSavedCards(): AccountSavedCardsContextValue {\n const ctx = useContext(AccountSavedCardsContext);\n if (!ctx) throw new Error('AccountSavedCards parts must be used inside AccountSavedCardsRoot.');\n return ctx;\n}\n\nconst AccountSavedCardContext = createContext<AccountSavedCardContextValue | null>(null);\n\n/** The card the nearest AccountSavedCardItem provides. Throws outside one. */\nexport function useAccountSavedCard(): AccountSavedCardContextValue {\n const ctx = useContext(AccountSavedCardContext);\n if (!ctx) throw new Error('AccountSavedCard parts must be used inside AccountSavedCardItem.');\n return ctx;\n}\n\nconst AccountSavedCardRemoveContext = createContext<AccountSavedCardRemoveValue | null>(null);\n\n/** The remove controller the nearest AccountSavedCardRemove provides. Throws outside one. */\nexport function useAccountSavedCardRemove(): AccountSavedCardRemoveValue {\n const ctx = useContext(AccountSavedCardRemoveContext);\n if (!ctx) throw new Error('AccountSavedCardRemove parts must be used inside AccountSavedCardRemove.');\n return ctx;\n}\n\ntype SavedCardState = 'usable' | 'expired' | 'unusable';\n\n/** Only an explicit USABLE reads as usable, so a status this client does not know never looks usable. */\nfunction savedCardState(status: string): SavedCardState {\n if (status === 'USABLE') return 'usable';\n if (status === 'EXPIRED') return 'expired';\n return 'unusable';\n}\n\nfunction validExpiry(expiry: AccountSavedCardContextValue['expiry']): { month: number; year: number } | null {\n if (!expiry) return null;\n const { month, year } = expiry;\n if (!Number.isInteger(month) || month < 1 || month > 12) return null;\n if (!Number.isInteger(year) || year < 1000 || year > 9999) return null;\n return { month, year };\n}\n\nconst EMPTY_CARDS: ReadonlyArray<{ id: string }> = Object.freeze([]);\n\n/**\n * The read's own error code. The root error is page-wide, so an error whose path names another field\n * does not count; with no path at all, the first code stands.\n */\nfunction readErrorCode(error: ErrorLike): string | null {\n if (!CombinedGraphQLErrors.is(error)) return null;\n const own =\n error.errors.find((entry) => entry.path?.[0] === 'customerSavedCards') ??\n error.errors.find((entry) => entry.path == null);\n const code = own?.extensions?.code;\n return typeof code === 'string' ? code : null;\n}\n\n/**\n * One state for the whole section. A null slice plus an error is a failed read; an error beside a\n * resolved slice belongs to another field of the page query and changes nothing here.\n */\nfunction resolveSectionState(\n data: AccountSavedCardsData,\n loading: boolean,\n error: ErrorLike | undefined,\n): Omit<AccountSavedCardsContextValue, 'removal'> {\n if (data == null) {\n if (error) return { state: 'error', cards: EMPTY_CARDS, errorCode: readErrorCode(error) };\n return { state: loading ? 'loading' : 'idle', cards: EMPTY_CARDS, errorCode: null };\n }\n const cards = data.cards ?? EMPTY_CARDS;\n return { state: cards.length === 0 ? 'empty' : 'ready', cards, errorCode: null };\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const AccountSavedCardsRootDefinition = defineComponent<AccountSavedCardsRootProps>({\n name: 'AccountSavedCardsRoot',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n providesContext: 'account-saved-cards',\n actions: { savedCard: ['remove'] },\n editor: { label: 'Account Saved Cards Root', category: 'identity' },\n schema: {},\n guideline:\n \"The signed-in shopper's saved cards, read with no cart. Renders nothing unless the session is loggedIn, and no chrome of its own: compose AccountSavedCardsHeading, AccountSavedCardsRemoveFeedback, AccountSavedCardsLoading, AccountSavedCardsError, AccountSavedCardsEmpty, and AccountSavedCardsList (with an AccountSavedCardItem inside) as children. Exactly one of Loading, Error, Empty, or List renders at a time; the state is on data-fs-account-saved-cards-state. It owns the removal state and supplies the savedCard.remove write to every AccountSavedCardRemove inside, placed or default, so the write sits here rather than on the control that dispatches it; with no remove control composed the write is never called. Must be inside a SessionRoot, normally the my-account layout's. See account-saved-cards.md.\",\n});\n\nexport const AccountSavedCardsHeadingDefinition = defineComponent<AccountSavedCardsHeadingProps>({\n name: 'AccountSavedCardsHeading',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsHeading,\n content: AccountSavedCardsHeadingContent,\n requiresContext: 'account-saved-cards',\n editor: { label: 'Account Saved Cards Heading', category: 'identity' },\n schema: {\n as: { type: ['h2', 'h3', 'h4', 'h5', 'h6'] as const, default: 'h2', label: 'Heading level' },\n },\n guideline:\n 'The section heading, rendered in every state. `as` sets the level from h2 to h6 (default h2); there is no h1, because the page title belongs to the page shell — pick the level that fits where the section is nested, or omit the heading. Text resolves children, then the CMS entry, then the catalog (fs/saved-cards.heading). Must be inside AccountSavedCardsRoot.',\n});\n\nexport const AccountSavedCardsListDefinition = defineComponent<AccountSavedCardsListProps>({\n name: 'AccountSavedCardsList',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsList,\n requiresContext: 'account-saved-cards',\n providesContext: 'account-saved-card',\n editor: { label: 'Account Saved Cards List', category: 'identity' },\n schema: {},\n guideline:\n 'The list of saved cards, one row per card in the platform order, rendered only when the shopper has at least one. Place one AccountSavedCardItem inside as the row template. Must be inside AccountSavedCardsRoot.',\n});\n\nexport const AccountSavedCardItemDefinition = defineComponent<AccountSavedCardItemProps>({\n name: 'AccountSavedCardItem',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardItem,\n fragment: itemFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'account-saved-card',\n providesContext: ['card-summary', 'account-saved-card-data'],\n editor: { label: 'Account Saved Card Item', category: 'identity' },\n schema: {},\n guideline:\n 'One saved card as a list row. It provides the card to the CardSummary parts, AccountSavedCardExpiry, and AccountSavedCardRemove. With no children it renders CardSummary (mark, network name, mask, last four digits), AccountSavedCardExpiry, and AccountSavedCardRemove, whose text is then always the catalog wording; compose them as children to rearrange them or to reword them from the CMS, keep AccountSavedCardExpiry in any custom row, and leave AccountSavedCardRemove out only for a read-only list. An expired row carries data-fs-account-saved-card-expired, an unusable one data-fs-account-saved-card-unusable, and a row whose removal is pending data-fs-account-saved-card-removing, for a swatch to dim. Never shows more than the last four digits. Must be inside AccountSavedCardsList.',\n});\n\nexport const AccountSavedCardExpiryDefinition = defineComponent<AccountSavedCardExpiryProps>({\n name: 'AccountSavedCardExpiry',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardExpiry,\n content: AccountSavedCardExpiryContent,\n requiresContext: 'account-saved-card-data',\n editor: { label: 'Account Saved Card Expiry', category: 'identity' },\n schema: {},\n guideline:\n 'When a saved card expires: \"Expires 05/28\" for a working card, \"Expired 05/24\" (or \"Expired\" with no date) for an expired one, and \"This card can\\'t be used\", with no date, for an unusable one. Renders nothing for a working card with no date. The label comes from the CMS entry, then the catalog (fs/saved-cards.expires, .expired, .expired-no-date, .unusable); the component always places the MM/YY date after it, and only a placed element reads the CMS entry. The state is on data-fs-account-saved-card-expiry-status (usable, expired, unusable). Must be inside AccountSavedCardItem.',\n});\n\nexport const AccountSavedCardsEmptyDefinition = defineComponent<AccountSavedCardsEmptyProps>({\n name: 'AccountSavedCardsEmpty',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsEmpty,\n content: AccountSavedCardsEmptyContent,\n requiresContext: 'account-saved-cards',\n editor: { label: 'Account Saved Cards Empty', category: 'identity' },\n schema: {},\n guideline:\n 'States that the shopper has no saved cards. Renders only once the read resolved with none — never while loading or after a failure. Text resolves children, then the CMS entry, then the catalog (fs/saved-cards.empty). Must be inside AccountSavedCardsRoot.',\n});\n\nexport const AccountSavedCardsLoadingDefinition = defineComponent<AccountSavedCardsLoadingProps>({\n name: 'AccountSavedCardsLoading',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsLoading,\n content: AccountSavedCardsLoadingContent,\n requiresContext: 'account-saved-cards',\n editor: { label: 'Account Saved Cards Loading', category: 'identity' },\n schema: {\n skeletonCount: { type: 'number', default: 2, label: 'Placeholder rows' },\n },\n guideline:\n 'Placeholder rows while the saved cards load, plus one polite live region that announces the wait (CMS entry, then the catalog fs/saved-cards.loading). The rows are hidden from assistive technology; style them through data-fs-account-saved-cards-loading-row. Without this part the section shows no loading look. Must be inside AccountSavedCardsRoot.',\n});\n\nexport const AccountSavedCardsErrorDefinition = defineComponent<AccountSavedCardsErrorProps>({\n name: 'AccountSavedCardsError',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsError,\n content: AccountSavedCardsErrorContent,\n requiresContext: 'account-saved-cards',\n editor: { label: 'Account Saved Cards Error', category: 'identity' },\n schema: {},\n guideline:\n 'An alert shown when the saved cards cannot be read. An expired session says so and links to sign-in, returning the shopper to this page; any other failure says the cards could not be loaded. The wording comes from the CMS entry, then the catalog (fs/saved-cards.session-expired, .sign-in, .load-failed), never from the server; children replace it for every failure and drop the link. Must be inside AccountSavedCardsRoot.',\n});\n\nexport const AccountSavedCardRemoveDefinition = defineComponent<AccountSavedCardRemoveProps>({\n name: 'AccountSavedCardRemove',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardRemove,\n requiresContext: 'account-saved-card-data',\n providesContext: 'account-saved-card-remove',\n editor: { label: 'Account Saved Card Remove', category: 'identity' },\n schema: {},\n guideline:\n \"Lets the shopper remove the row's card after confirming. State-only controller: wrap one AlertDialogRoot and it becomes controlled, so never set open or onOpenChange on it. Compose AlertDialogRoot > (AccountSavedCardRemoveTrigger + AlertDialogPortal > (AlertDialogBackdrop + AlertDialogViewport > AlertDialogPopup > (AccountSavedCardRemoveTitle + AccountSavedCardRemoveDescription + AccountSavedCardRemoveCancel + AccountSavedCardRemoveConfirm))), Cancel before Confirm so the safe action takes focus. With no children it renders that prompt with catalog text; compose the parts to reword them from the CMS. The prompt stays open while the removal runs and closes when it fails; on success the row leaves the list. AccountSavedCardsRoot supplies the savedCard.remove write, and the outcome goes unreported unless the section composes AccountSavedCardsRemoveFeedback. Must be inside AccountSavedCardItem.\",\n});\n\nexport const AccountSavedCardRemoveTriggerDefinition = defineComponent<AccountSavedCardRemoveTriggerProps>({\n name: 'AccountSavedCardRemoveTrigger',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardRemoveTrigger,\n content: AccountSavedCardRemoveTriggerContent,\n requiresContext: 'account-saved-card-remove',\n editor: { label: 'Account Saved Card Remove Trigger', category: 'identity' },\n schema: {},\n guideline:\n 'Opens the removal prompt. Its accessible name is its text followed by the card, e.g. \"Remove Visa ending in 1234\"; the card part is visually hidden. Disabled while any removal in the section is pending. Text resolves children, then the CMS entry, then the catalog (fs/saved-cards.remove.trigger). Direct child of the AlertDialogRoot inside AccountSavedCardRemove.',\n});\n\nexport const AccountSavedCardRemoveTitleDefinition = defineComponent<AccountSavedCardRemoveTitleProps>({\n name: 'AccountSavedCardRemoveTitle',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardRemoveTitle,\n content: AccountSavedCardRemoveTitleContent,\n requiresContext: 'account-saved-card-remove',\n editor: { label: 'Account Saved Card Remove Title', category: 'identity' },\n schema: {},\n guideline:\n \"The removal prompt's question and its accessible name. Required inside AlertDialogPopup. Text resolves children, then the CMS entry, then the catalog (fs/saved-cards.remove.title). Must be inside AccountSavedCardRemove.\",\n});\n\nexport const AccountSavedCardRemoveDescriptionDefinition = defineComponent<AccountSavedCardRemoveDescriptionProps>({\n name: 'AccountSavedCardRemoveDescription',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardRemoveDescription,\n content: AccountSavedCardRemoveDescriptionContent,\n requiresContext: 'account-saved-card-remove',\n editor: { label: 'Account Saved Card Remove Description', category: 'identity' },\n schema: {},\n guideline:\n 'Names the card and states the consequence; screen readers announce it with the prompt. With no children it renders CardSummary and then the text, which resolves the CMS entry, then the catalog (fs/saved-cards.remove.description); children replace both, so keep a CardSummary in them for the prompt to name the card. Inside AlertDialogPopup, after AccountSavedCardRemoveTitle. Must be inside AccountSavedCardRemove.',\n});\n\nexport const AccountSavedCardRemoveCancelDefinition = defineComponent<AccountSavedCardRemoveCancelProps>({\n name: 'AccountSavedCardRemoveCancel',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardRemoveCancel,\n content: AccountSavedCardRemoveCancelContent,\n requiresContext: 'account-saved-card-remove',\n editor: { label: 'Account Saved Card Remove Cancel', category: 'identity' },\n schema: {},\n guideline:\n 'Closes the removal prompt and keeps the card. Disabled while the removal runs. Place it before AccountSavedCardRemoveConfirm so it takes focus when the prompt opens. Text resolves children, then the CMS entry, then the catalog (fs/saved-cards.remove.cancel). Must be inside AccountSavedCardRemove.',\n});\n\nexport const AccountSavedCardRemoveConfirmDefinition = defineComponent<AccountSavedCardRemoveConfirmProps>({\n name: 'AccountSavedCardRemoveConfirm',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardRemoveConfirm,\n content: AccountSavedCardRemoveConfirmContent,\n requiresContext: 'account-saved-card-remove',\n editor: { label: 'Account Saved Card Remove Confirm', category: 'identity' },\n schema: {},\n guideline:\n 'Removes the card. It dispatches savedCard.remove through the write AccountSavedCardsRoot supplies and declares none of its own. While the removal runs it is busy and shows its pending text; one removal runs at a time in the section. Text resolves children, then the CMS entry, then the catalog (fs/saved-cards.remove.confirm, .pending). Must be inside AccountSavedCardRemove.',\n});\n\nexport const AccountSavedCardsRemoveFeedbackDefinition = defineComponent<AccountSavedCardsRemoveFeedbackProps>({\n name: 'AccountSavedCardsRemoveFeedback',\n importPath: '@vtex/faststore/components',\n component: AccountSavedCardsRemoveFeedback,\n content: AccountSavedCardsRemoveFeedbackContent,\n requiresContext: 'account-saved-cards',\n editor: { label: 'Account Saved Cards Remove Feedback', category: 'identity' },\n schema: {},\n guideline:\n \"Tells the shopper how a card removal ended: a status message that takes focus when the card was removed, or an alert when it was not, with a sign-in link back to this page when the session expired. Renders in every state of the section, so the message outlives the removed row and an emptied list; it stays until the next removal starts. Without this part a removal's outcome goes unreported. The wording comes from the CMS entry, then the catalog (fs/saved-cards.remove.success, .failed, fs/saved-cards.session-expired, .sign-in), never from the server; the outcome is on data-fs-account-saved-cards-remove-feedback-outcome. Must be inside AccountSavedCardsRoot.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\n/**\n * One removal at a time: each success replaces the whole cached list, so two in flight could land out\n * of order and restore a card the later one removed.\n */\nfunction useRemovalState(): AccountSavedCardsRemoval {\n const [pendingId, setPendingId] = useState<string | null>(null);\n const [outcome, setOutcome] = useState<AccountSavedCardRemovalOutcome | null>(null);\n // Read synchronously, so a second activation in the same tick already sees the first.\n const pendingRef = useRef<string | null>(null);\n\n const begin = useCallback((id: string) => {\n if (pendingRef.current !== null) return false;\n pendingRef.current = id;\n setPendingId(id);\n setOutcome(null);\n return true;\n }, []);\n\n const settle = useCallback((id: string, userErrors: readonly { code: string }[]) => {\n if (pendingRef.current !== id) return;\n pendingRef.current = null;\n setPendingId(null);\n const [first] = userErrors;\n setOutcome(first ? { kind: 'failed', code: first.code } : { kind: 'removed' });\n }, []);\n\n return useMemo(() => ({ pendingId, outcome, begin, settle }), [pendingId, outcome, begin, settle]);\n}\n\nexport function AccountSavedCardsRoot({\n $data: $dataProp,\n $actions: $actionsProp,\n className,\n children,\n ...props\n}: AccountSavedCardsRootProps) {\n // Enforces the SessionRoot ancestor: useSession() throws outside one.\n const { state: sessionState } = useSession();\n const data = useDataOverride<AccountSavedCardsData | undefined>($dataProp) ?? null;\n const $actions = useActionsOverride($actionsProp);\n const { loading, error } = useContext(ReactiveRootContext);\n const section = useMemo(() => resolveSectionState(data, loading, error), [data, loading, error]);\n const removal = useRemovalState();\n const value = useMemo(() => ({ ...section, removal }), [section, removal]);\n\n if (sessionState !== 'loggedIn') return null;\n\n return (\n <AccountSavedCardsContext value={value}>\n <ActionsOverrideContext value={$actions}>\n <div\n {...props}\n className={className}\n data-fs-account-saved-cards=\"\"\n data-fs-account-saved-cards-state={value.state}\n >\n {children}\n </div>\n </ActionsOverrideContext>\n </AccountSavedCardsContext>\n );\n}\n\nexport function AccountSavedCardsHeading({\n as: Tag = 'h2',\n $content,\n className,\n children,\n ...props\n}: AccountSavedCardsHeadingProps) {\n useAccountSavedCards();\n const intl = useIntl();\n return (\n <Tag {...props} className={className} data-fs-account-saved-cards-heading=\"\">\n {children ?? resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.heading' }))}\n </Tag>\n );\n}\n\nexport function AccountSavedCardsList({ className, children, ...props }: AccountSavedCardsListProps) {\n const { state, cards } = useAccountSavedCards();\n if (state !== 'ready') return null;\n\n return (\n <ul {...props} className={className} data-fs-account-saved-cards-list=\"\">\n {cards.map((card) => (\n // No fallback slice: the Root selects ids only, so an incomplete row renders nothing until\n // its cache entry resolves rather than a partial card.\n <ListItemRefContext\n key={card.id}\n value={{ __typename: 'CustomerSavedCard', id: card.id, fallbackData: undefined }}\n >\n {children}\n </ListItemRefContext>\n ))}\n </ul>\n );\n}\n\nexport function AccountSavedCardItem({ $data: $dataProp, className, children, ...props }: AccountSavedCardItemProps) {\n const card = useDataOverride<AccountSavedCardItemData | undefined>($dataProp) ?? null;\n // Not useAccountSavedCards(): an Item composed without a remove control stays usable outside a Root.\n const pendingId = useContext(AccountSavedCardsContext)?.removal.pendingId ?? null;\n if (!card) return null;\n const state = savedCardState(card.status);\n\n return (\n <AccountSavedCardContext value={card}>\n <li\n {...props}\n className={className}\n data-fs-account-saved-card=\"\"\n {...(state === 'expired' ? { 'data-fs-account-saved-card-expired': '' } : {})}\n {...(state === 'unusable' ? { 'data-fs-account-saved-card-unusable': '' } : {})}\n {...(pendingId === card.id ? { 'data-fs-account-saved-card-removing': '' } : {})}\n >\n <CardSummaryRoot network={card.network.name} lastDigits={card.lastDigits}>\n {children ?? (\n <>\n <CardSummary />\n <AccountSavedCardExpiry />\n <AccountSavedCardRemove />\n </>\n )}\n </CardSummaryRoot>\n </li>\n </AccountSavedCardContext>\n );\n}\n\nexport function AccountSavedCardExpiry({ $content, className, ...props }: AccountSavedCardExpiryProps) {\n const { status, expiry } = useAccountSavedCard();\n const intl = useIntl();\n const state = savedCardState(status);\n const date = state === 'unusable' ? null : validExpiry(expiry);\n if (state === 'usable' && !date) return null;\n\n let label: string;\n if (state === 'unusable') {\n label = resolveAuthoredCopy($content?.unusableText, intl.formatMessage({ id: 'fs/saved-cards.unusable' }));\n } else if (!date) {\n label = resolveAuthoredCopy($content?.expiredText, intl.formatMessage({ id: 'fs/saved-cards.expired-no-date' }));\n } else if (state === 'expired') {\n label = resolveAuthoredCopy($content?.expiredLabel, intl.formatMessage({ id: 'fs/saved-cards.expired' }));\n } else {\n label = resolveAuthoredCopy($content?.expiresLabel, intl.formatMessage({ id: 'fs/saved-cards.expires' }));\n }\n const month = date ? String(date.month).padStart(2, '0') : '';\n\n return (\n <p\n {...props}\n className={className}\n data-fs-account-saved-card-expiry=\"\"\n data-fs-account-saved-card-expiry-status={state}\n >\n <span data-fs-account-saved-card-expiry-label=\"\">{label}</span>\n {date ? (\n <>\n {' '}\n <time data-fs-account-saved-card-expiry-date=\"\" dateTime={`${date.year}-${month}`} aria-hidden=\"true\">\n {`${month}/${String(date.year % 100).padStart(2, '0')}`}\n </time>{' '}\n <span data-fs-sr-only=\"\" className=\"sr-only\">\n {/* UTC on the first of the month, so no time zone moves the date into another month. */}\n {intl.formatDate(Date.UTC(date.year, date.month - 1, 1), {\n month: 'long',\n year: 'numeric',\n timeZone: 'UTC',\n })}\n </span>\n </>\n ) : null}\n </p>\n );\n}\n\nexport function AccountSavedCardsEmpty({ $content, className, children, ...props }: AccountSavedCardsEmptyProps) {\n const { state } = useAccountSavedCards();\n const intl = useIntl();\n if (state !== 'empty') return null;\n\n return (\n <p {...props} className={className} data-fs-account-saved-cards-empty=\"\">\n {children ?? resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.empty' }))}\n </p>\n );\n}\n\nconst DEFAULT_SKELETON_COUNT = 2;\n\nexport function AccountSavedCardsLoading({\n skeletonCount = DEFAULT_SKELETON_COUNT,\n $content,\n className,\n ...props\n}: AccountSavedCardsLoadingProps) {\n const { state } = useAccountSavedCards();\n const intl = useIntl();\n const loading = state === 'loading';\n const label = resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.loading' }));\n const rows = Number.isFinite(skeletonCount) ? Math.max(1, Math.trunc(skeletonCount)) : DEFAULT_SKELETON_COUNT;\n\n // A live region only announces text written after it mounted, so it stays in the tree and is\n // filled on the pass after loading starts (AccountEntryPoint's pattern).\n const [announcement, setAnnouncement] = useState('');\n const pendingLabel = loading ? label : '';\n useEffect(() => {\n setAnnouncement(pendingLabel);\n }, [pendingLabel]);\n\n return (\n <>\n {loading ? (\n <div {...props} className={className} aria-hidden=\"true\" data-fs-account-saved-cards-loading=\"\">\n {Array.from({ length: rows }, (_, index) => (\n <span key={index} data-fs-account-saved-cards-loading-row=\"\" />\n ))}\n </div>\n ) : null}\n <output aria-live=\"polite\" data-fs-account-saved-cards-loading-announcement=\"\">\n {announcement}\n </output>\n </>\n );\n}\n\nexport function AccountSavedCardsError({ $content, className, children, ...props }: AccountSavedCardsErrorProps) {\n const { state, errorCode } = useAccountSavedCards();\n const intl = useIntl();\n const { path } = useStorefrontRoute();\n if (state !== 'error') return null;\n\n // Keyed on the code as a whole: confronted identity gives every refusal one UNAUTHORIZED, and the\n // server's message is never rendered.\n const sessionExpired = errorCode === 'UNAUTHORIZED';\n\n return (\n <div\n {...props}\n role=\"alert\"\n className={className}\n data-fs-account-saved-cards-error=\"\"\n data-fs-account-saved-cards-error-code={sessionExpired ? 'unauthorized' : 'failed'}\n >\n {children ??\n (sessionExpired ? (\n <>\n {resolveAuthoredCopy(\n $content?.sessionExpiredText,\n intl.formatMessage({ id: 'fs/saved-cards.session-expired' }),\n )}{' '}\n <NavigationLink href={signInTarget(path)} prefetch={false} data-fs-account-saved-cards-sign-in=\"\">\n {resolveAuthoredCopy($content?.signInLabel, intl.formatMessage({ id: 'fs/saved-cards.sign-in' }))}\n </NavigationLink>\n </>\n ) : (\n resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.load-failed' }))\n ))}\n </div>\n );\n}\n\ntype OverlayOpenProps = {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\nexport function AccountSavedCardRemove({ children }: Readonly<AccountSavedCardRemoveProps>) {\n const { id } = useAccountSavedCard();\n const { removal } = useAccountSavedCards();\n const [open, setOpen] = useState(false);\n const pending = removal.pendingId === id;\n const blocked = removal.pendingId !== null;\n const close = useCallback(() => setOpen(false), []);\n const value = useMemo(() => ({ id, pending, blocked, close }), [id, pending, blocked, close]);\n const prompt = children ?? <AccountSavedCardRemoveDefaultPrompt />;\n\n if (!isValidElement<OverlayOpenProps>(prompt)) {\n return <AccountSavedCardRemoveContext value={value}>{prompt}</AccountSavedCardRemoveContext>;\n }\n\n return (\n <AccountSavedCardRemoveContext value={value}>\n {cloneElement(prompt, {\n open,\n onOpenChange: (next: boolean) => {\n // Escape and Cancel cannot leave a removal in flight, and no prompt opens while one runs.\n if (next ? blocked : pending) return;\n setOpen(next);\n },\n })}\n </AccountSavedCardRemoveContext>\n );\n}\n\nfunction AccountSavedCardRemoveDefaultPrompt({ open, onOpenChange }: Readonly<OverlayOpenProps>) {\n return (\n <AlertDialogRoot open={open} onOpenChange={onOpenChange}>\n <AccountSavedCardRemoveTrigger />\n <AlertDialogPortal>\n <AlertDialogBackdrop />\n <AlertDialogViewport>\n <AlertDialogPopup>\n <AccountSavedCardRemoveTitle />\n <AccountSavedCardRemoveDescription />\n <AccountSavedCardRemoveCancel />\n <AccountSavedCardRemoveConfirm />\n </AlertDialogPopup>\n </AlertDialogViewport>\n </AlertDialogPortal>\n </AlertDialogRoot>\n );\n}\n\nexport function AccountSavedCardRemoveTrigger({\n $content,\n className,\n children,\n disabled,\n ...props\n}: AccountSavedCardRemoveTriggerProps) {\n const { blocked } = useAccountSavedCardRemove();\n const { name, lastFour } = useCardSummary();\n const intl = useIntl();\n const card = [name, lastFour ? intl.formatMessage({ id: 'fs/card.ending-in' }, { lastDigits: lastFour }) : null]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AlertDialogTrigger\n {...props}\n className={className}\n disabled={blocked || disabled}\n data-fs-account-saved-card-remove-trigger=\"\"\n >\n {children ?? resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.remove.trigger' }))}\n {card ? (\n <>\n {' '}\n <span data-fs-sr-only=\"\" className=\"sr-only\">\n {card}\n </span>\n </>\n ) : null}\n </AlertDialogTrigger>\n );\n}\n\nexport function AccountSavedCardRemoveTitle({\n $content,\n className,\n children,\n ...props\n}: AccountSavedCardRemoveTitleProps) {\n useAccountSavedCardRemove();\n const intl = useIntl();\n return (\n <AlertDialogTitle {...props} className={className} data-fs-account-saved-card-remove-title=\"\">\n {children ?? resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.remove.title' }))}\n </AlertDialogTitle>\n );\n}\n\nexport function AccountSavedCardRemoveDescription({\n $content,\n className,\n children,\n ...props\n}: AccountSavedCardRemoveDescriptionProps) {\n useAccountSavedCardRemove();\n const intl = useIntl();\n return (\n <AlertDialogDescription {...props} className={className} data-fs-account-saved-card-remove-description=\"\">\n {children ?? (\n <>\n <CardSummary />{' '}\n {resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.remove.description' }))}\n </>\n )}\n </AlertDialogDescription>\n );\n}\n\nexport function AccountSavedCardRemoveCancel({\n $content,\n className,\n children,\n disabled,\n ...props\n}: AccountSavedCardRemoveCancelProps) {\n const { pending } = useAccountSavedCardRemove();\n const intl = useIntl();\n return (\n <AlertDialogClose\n {...props}\n className={className}\n disabled={pending || disabled}\n data-fs-account-saved-card-remove-cancel=\"\"\n >\n {children ?? resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.remove.cancel' }))}\n </AlertDialogClose>\n );\n}\n\nexport function AccountSavedCardRemoveConfirm({\n $content,\n className,\n children,\n onClick,\n ...props\n}: AccountSavedCardRemoveConfirmProps) {\n const { id, pending, close } = useAccountSavedCardRemove();\n const { removal } = useAccountSavedCards();\n const remove = useActionsOverride(undefined)?.savedCard?.remove;\n const intl = useIntl();\n\n async function handleClick(event: MouseEvent<HTMLButtonElement>) {\n onClick?.(event);\n if (event.defaultPrevented || !remove || !removal.begin(id)) return;\n let userErrors: readonly { code: string }[];\n try {\n userErrors = await remove({ id });\n } catch {\n // An injected action may reject; a removal left pending would lock the prompt open.\n userErrors = [{ code: 'NETWORK_ERROR' }];\n }\n // On success the row, and this prompt with it, has already left the list; the Root is still there.\n removal.settle(id, userErrors);\n close();\n }\n\n return (\n <button\n {...props}\n type=\"button\"\n className={className}\n // aria-disabled rather than disabled, so focus stays on the control while it reports progress.\n aria-disabled={pending || undefined}\n aria-busy={pending || undefined}\n data-fs-account-saved-card-remove-confirm=\"\"\n {...(pending ? { 'data-fs-account-saved-card-remove-confirm-pending': '' } : {})}\n onClick={handleClick}\n >\n {pending\n ? resolveAuthoredCopy($content?.pendingText, intl.formatMessage({ id: 'fs/saved-cards.remove.pending' }))\n : (children ??\n resolveAuthoredCopy($content?.text, intl.formatMessage({ id: 'fs/saved-cards.remove.confirm' })))}\n </button>\n );\n}\n\nfunction feedbackOutcome(outcome: AccountSavedCardRemovalOutcome | null) {\n if (!outcome) return undefined;\n if (outcome.kind === 'removed') return 'removed';\n return outcome.code === 'UNAUTHORIZED' ? 'unauthorized' : 'failed';\n}\n\nexport function AccountSavedCardsRemoveFeedback({\n $content,\n className,\n ...props\n}: AccountSavedCardsRemoveFeedbackProps) {\n const {\n removal: { outcome },\n } = useAccountSavedCards();\n const intl = useIntl();\n const { path } = useStorefrontRoute();\n const statusRef = useRef<HTMLOutputElement>(null);\n const kind = feedbackOutcome(outcome);\n const successText = resolveAuthoredCopy(\n $content?.successText,\n intl.formatMessage({ id: 'fs/saved-cards.remove.success' }),\n );\n\n // A live region only announces text written after it mounted, so it stays in the tree and is filled\n // on the pass after the outcome. Focus moves to it because the trigger it would return to left with\n // the removed row.\n const [announcement, setAnnouncement] = useState('');\n useEffect(() => {\n const removed = outcome?.kind === 'removed';\n setAnnouncement(removed ? successText : '');\n if (removed) statusRef.current?.focus();\n }, [outcome, successText]);\n\n return (\n <div\n {...props}\n className={className}\n data-fs-account-saved-cards-remove-feedback=\"\"\n data-fs-account-saved-cards-remove-feedback-outcome={kind}\n >\n <output ref={statusRef} aria-live=\"polite\" tabIndex={-1}>\n {announcement}\n </output>\n {kind === 'unauthorized' ? (\n <div role=\"alert\">\n {resolveAuthoredCopy(\n $content?.sessionExpiredText,\n intl.formatMessage({ id: 'fs/saved-cards.session-expired' }),\n )}{' '}\n <NavigationLink\n href={signInTarget(path)}\n prefetch={false}\n data-fs-account-saved-cards-remove-feedback-sign-in=\"\"\n >\n {resolveAuthoredCopy($content?.signInLabel, intl.formatMessage({ id: 'fs/saved-cards.sign-in' }))}\n </NavigationLink>\n </div>\n ) : null}\n {kind === 'failed' ? (\n <div role=\"alert\">\n {resolveAuthoredCopy($content?.failureText, intl.formatMessage({ id: 'fs/saved-cards.remove.failed' }))}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"billing-address.js","names":[],"sources":["../../../src/matter/billing-address/billing-address.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { useEffect, useId, 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 { Select } from '../../particles/select/select.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\nimport {\n type CountryDescriptor,\n type FieldDescriptor,\n getAvailableCountries,\n getCountryDescriptor,\n satisfiesFieldPattern,\n} from '../shipping-address/country-descriptors.js';\n\n/**\n * The `BillingAddress` matter (specs/billing-address.md, T11 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D6). A \"same as delivery\" toggle (default on) plus, when off, a\n * country-specific address form that persists via `$actions.payment.setBillingAddress`\n * (already shipped by T4, zero consumers before this task).\n *\n * The shared `Address` composition RFC D6 names (shipping and billing as two control matters over\n * one set of reusable particles) is DEFERRED — an implementation-time call the engineer resolved\n * narrow (specs/billing-address.md Decision 1). This component builds its own controls and imports\n * `countryDescriptors` from `../shipping-address/country-descriptors.js`, the one place a country's field\n * set, labels, copy and format rules live.\n *\n * There is NO read-back path for a persisted billing address (specs/billing-address.md Decision 3):\n * `type Payment` carries no `billingAddress` field, and `setBillingAddress` itself resolves only\n * `userErrors`, discarding the mutation's own echo. This component declares no fragment and renders\n * nothing below the toggle while \"same as delivery\" is checked, not even the delivery address\n * (specs/stop-re-rendering-the-full-delivery.md, T175). The custom-address form always starts blank,\n * and its own local state is the sole source of truth for what was typed.\n *\n * Only a boolean rides the shared `draft` (`billingAddressDifferent`, widened onto `PaymentDraft` by\n * this task under sole ownership of `use-payment-method-selection.ts` — specs/billing-address.md\n * Decision 5); the address itself is persisted server-side by this component's own action call, never\n * passed through the draft. `setBillingAddress` is dispatched at exactly two points (Decision 9): the\n * toggle's transition back to \"same as delivery\", and the custom form's explicit submit control —\n * never on mount, never per keystroke.\n *\n * **`draft.billingAddressDifferent` only ever becomes `true` once `setBillingAddress` has actually\n * persisted a different address (PR #544 N1).** Unchecking \"same as delivery\" opens the form\n * (`formOpen`, local-only state below) but does not touch the draft — `gateway.ts`'s own doc comment\n * (\"`setBillingAddress` already persisted it … so the gateway reads it from there\") is the contract\n * this must not break: publishing `true` before anything is persisted would point the gateway at an\n * `invoiceData` address nothing ever wrote. The engineer's own resolution for this (session of\n * 2026-07-31): the new-address fields become required whenever \"different\" is selected, so this\n * component's own submit blocks on an incomplete address (`isBillingAddressFormValid`) — but that\n * alone cannot stop a shopper from declining \"same as delivery\" and clicking Place Order without ever\n * opening the form's own submit control, which is why the draft write itself is gated on success, not\n * merely the submit button.\n *\n * **The native `required` attribute below is cosmetic here, not an enforcement mechanism (PR #544\n * N7).** This file renders no `aria-required` at all — only `required` (native HTML). There is no\n * `<form>` in this render tree and the submit control is `type=\"button\"`, so the browser's own\n * constraint validation never runs — nothing in this file or its tests may claim this attribute itself\n * prevents a shopper from placing an order with an unsaved address; the actual submit gate is\n * `isBillingAddressFormValid`'s verdict (`areRenderableFieldsValid`), read at the submit control's own\n * `disabled` below. Since T38\n * (specs/harden-and-verify-accessibility-across-the.md FR-6) each rendered field does carry a\n * `Field.Error`, but it is driven by a local `touched` set (a field joins it on blur, or when a refused\n * submit moves focus to it) and `getFieldIssue`'s verdict (`required` for a blank field, `format` for a\n * non-blank value that fails the country's postal pattern), never by `required`'s own native\n * `ValidityState` — so this paragraph's point is unchanged: `required` itself still enforces nothing\n * here. What this component owes, and does: make the gap **visible** — an inline `role=\"status\"`\n * notice while the form is open and nothing has been persisted yet — and publish that same fact\n * (`setDraftBillingAddressUnsaved`) onto the shared draft, from the identical expression the notice\n * renders from (T24, specs/cardholder-document-submit-gate.md FR-6). **The actual hard block lives\n * elsewhere**: T24's `usePaymentSubmitGate()` reads that published fact and refuses Place Order\n * client-side — this component's own job stops at publishing the fact and showing the notice, never at\n * gating the CTA itself. See `specs/billing-address.md`'s Documentation Gaps #1 for the deferral this closes.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Local-only values for the \"different\" address form, keyed by the country descriptor's own\n * canonical field names — a plain string index signature, not a closed set of properties, so a future\n * country descriptor introducing a new field name needs no type change here (mirrors\n * `FieldDescriptor.name: string`'s own deliberately untyped choice; specs/billing-address.md\n * Decision 7). `postalCode` and `country` are named explicitly because this component's own logic\n * reads both directly. Never carries a `recipientName` key: `PaymentBillingAddressUpdateInput` has no\n * such field (Decision 4) — the render loop filters it out of whatever the descriptor lists.\n */\nexport type BillingAddressFormDraft = Readonly<{\n postalCode: string;\n country: string;\n [fieldName: string]: string;\n}>;\n\nexport type BillingAddressProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /** The \"same as delivery\" toggle's own accessible label. */\n sameAsDeliveryLabel?: string;\n /** ISO 3166-1 alpha-3 default country for the custom-address form's country selector. */\n defaultCountry?: string;\n /** Label for the country selector inside the custom-address form. */\n countryLabel?: string;\n /** Label for the control that persists a custom billing address. */\n submitLabel?: string;\n /** Label on the submit control while the mutation is in flight. */\n submitPendingLabel?: string;\n /** Shown when the selected method's validator marks a billing address required. */\n requiredNoticeLabel?: string;\n /** Label for the recipient-name descriptor field. Not rendered by the custom-address form today\n * (`OMITTED_FORM_FIELD_NAMES` — the write contract has no matching input slot, Decision 4); kept\n * for parity with the other seven field labels this same map covers. */\n recipientNameLabel?: string;\n /** Label for the street descriptor field. */\n streetLabel?: string;\n /** Label for the street-number descriptor field. */\n numberLabel?: string;\n /** Label for the complement descriptor field. Its \"(optional)\" suffix is the only signal that the\n * field is not required — `OPTIONAL_FORM_FIELD_NAMES` drives the actual `required` attribute\n * independently, so authoring this without the suffix loses the optionality cue, not the behavior\n * (R7). */\n complementLabel?: string;\n /** Label for the neighborhood descriptor field. Unused when the active country's descriptor has no\n * `neighborhood` field (e.g. USA). */\n neighborhoodLabel?: string;\n /** Label for the city descriptor field. */\n cityLabel?: string;\n /** Label for the state descriptor field. */\n stateLabel?: string;\n /** Label for the postal-code descriptor field. */\n postalCodeLabel?: string;\n /** Shown while the form is open and no address has been persisted yet (PR #544 N7) — the shopper\n * declined \"same as delivery\" but has not (or not yet again) had a submit succeed, so Place Order\n * will bill the delivery address unless they save one first. Rendered `role=\"status\"`, not\n * `role=\"alert\"`: it is a persistent, advisory condition of the form being open, not a one-off\n * announcement reacting to a rejected action (see this file's own existing `-error` alert for that\n * shape) — see `specs/billing-address.md`'s Accessibility Contract for the site count this choice is\n * measured against. */\n unsavedAddressNoticeLabel?: string;\n /** Always-on billing-match disclaimer (requirement 12.20): tells the shopper their billing address\n * must match the one their card issuer or bank statement holds, so an AVS mismatch does not get the\n * order declined with no explanation on screen. Gated on this prop and `billingMatchNoticeVisible`\n * only — reads no `usePayment()` value, no local state, no `$data` (FR-4), unlike the two notices\n * above. */\n billingMatchNoticeLabel?: string;\n /** Switches the billing-match disclaimer above on or off. Defaults to `true` (Decision 5 — the\n * requirement's own intent); subordinate to `billingMatchNoticeLabel.trim()`, which alone can still\n * suppress an empty notice (FR-3) regardless of this prop's value. */\n billingMatchNoticeVisible?: boolean;\n /**\n * Shown as a `Field.Error` under a required, blank, touched field (T38, FR-6/FR-7) — the same\n * non-blank rule `isBillingAddressFormValid` already applies, except `complement`\n * (`OPTIONAL_FORM_FIELD_NAMES`), which is never marked invalid. One prop, not one per field: every\n * rendered field shares the same rule, so seven props would be seven ways to say the same sentence.\n * Resolved through `resolveAuthoredCopy`.\n */\n fieldRequiredMessage?: string;\n /**\n * Shown as a `Field.Error` under a touched, non-blank postal code that does not match the selected\n * country's format (the descriptor's `rule.pattern`). Only the postal code reads this prop: any other\n * field that gains a pattern speaks with its own descriptor message. Blank wins over format: a blank\n * field reads `fieldRequiredMessage`, never this. Never contains the typed value. Resolved through\n * `resolveAuthoredCopy`, so a blank value falls back to the English default like every other copy on\n * this form.\n */\n postalCodeInvalidMessage?: string;\n /** Disables the toggle, every form field, the country selector, and the submit control. */\n disabled?: boolean;\n /** Compiler-injected action surface; carries `payment.setBillingAddress`. */\n $actions?: ComponentActions;\n};\n\n/** Canonical field names the custom-address form never renders, even when a country descriptor\n * lists them — because the write contract has no matching input slot (Decision 4). */\nconst OMITTED_FORM_FIELD_NAMES = new Set(['recipientName']);\n\n/** Descriptor field names the custom-address form renders but never marks required, even while\n * \"different\" is selected (PR #544 N1 item 1 — the engineer's own rule, verbatim: \"if the person says\n * the address is different, the new address fields must be required\"). `complement` is the one\n * exception: `complementLabel`'s own default already labels it optional (\"Complement (optional)\"),\n * and neither a real-world address nor the server's own write contract treats it as load-bearing.\n * Shared between the `required` attribute below and `isBillingAddressFormValid`, so the two never\n * drift apart. An authored `complementLabel` that drops the \"(optional)\" suffix loses that visual cue\n * without changing this set — the two are independent (R7, billing-address.md). */\nconst OPTIONAL_FORM_FIELD_NAMES = new Set(['complement']);\n\n/**\n * English override for the descriptor's own country display names (`'Brasil'`, `'United States'`), because\n * the Brazilian entry's name is Portuguese. This map is a stopgap: copy a country's form speaks belongs on\n * its descriptor in `country-descriptors.ts`, as `countryLabel` and `requiredSuffix` already do. Put a new\n * string there, not in another English override map here. T31\n * (specs/make-the-payment-components-authored-copy.md, Decision 5) turned the eight *field* labels into\n * schema props but left this one a plain map: a prop per ISO code does not scale the way eight named\n * field-label props do. Keyed by the descriptor's own ISO code.\n */\nconst COUNTRY_LABELS_EN: Readonly<Record<string, string>> = {\n BRA: 'Brazil',\n USA: 'United States',\n};\n\n/** Resolves a country's English display name, falling back to the descriptor's own label (then the\n * raw ISO code) for a country this map does not yet cover. */\nfunction getCountryLabel(iso: string): string {\n return COUNTRY_LABELS_EN[iso] ?? getCountryDescriptor(iso)?.label ?? iso;\n}\n\n// ---------------------------------------------------------------------------\n// Pure decision functions — exported so both are unit-testable independent of any rendering\n// (specs/billing-address.md § Resolution logic).\n// ---------------------------------------------------------------------------\n\n/**\n * Whether the toggle reads as \"same as delivery\" — `true` whenever the published flag is `null`\n * (never touched, the default) or `false` (explicitly reverted); only an explicit `true` means the\n * shopper is on the custom-address path. Takes no closure over component state, so it is safe to call\n * fresh on every render.\n */\nexport function resolveSameAsDelivery(billingAddressDifferent: boolean | null | undefined): boolean {\n return billingAddressDifferent !== true;\n}\n\n/** Why a rendered field is not acceptable: blank (`required`), or non-blank and not in the selected\n * country's format (`format`). `null` means acceptable. */\ntype FieldIssue = 'required' | 'format' | null;\n\n/**\n * The one verdict on a rendered field, read by both the submit gate (`areRenderableFieldsValid`, behind\n * `isBillingAddressFormValid`) and the per-field error (the `issue` computed per field in the render), so the\n * two can never disagree (T38 Decision 8). Blank (after\n * trim) wins over format: `complement` (`OPTIONAL_FORM_FIELD_NAMES`) may be blank, every other field may\n * not. A non-blank value is tested raw against the descriptor's `rule.pattern` — the same\n * predicate the delivery form's react-hook-form rule applies, so a surrounding space is a format error\n * in both. A whitespace-only value is the one divergence: it is blank here (`required`), where the\n * delivery form would run the pattern on it (see `satisfiesFieldPattern`). No postal format is defined\n * here: the descriptor's `rule.pattern` is the only definition.\n */\nfunction getFieldIssue(field: FieldDescriptor, value: string): FieldIssue {\n if (value.trim().length === 0) return OPTIONAL_FORM_FIELD_NAMES.has(field.name) ? null : 'required';\n return satisfiesFieldPattern(field, value) ? null : 'format';\n}\n\n/**\n * The message under a field whose verdict is `format`. `postalCode` keeps the authored\n * `postalCodeInvalidMessage` (already resolved by the caller, so it always has an English default); every\n * other field speaks with its own descriptor `rule.pattern.message`, the one the delivery form shows for the\n * same rule, so a pattern a descriptor gains later never surfaces the postal-code copy. A field with no\n * pattern never reaches a `format` verdict, so the empty string is not a message anyone sees.\n */\nexport function getFieldFormatMessage(field: FieldDescriptor, postalCodeMessage: string): string {\n if (field.name === 'postalCode') return postalCodeMessage;\n return field.rule?.pattern?.message ?? '';\n}\n\n/** The descriptor's fields the custom-address form renders: everything but `OMITTED_FORM_FIELD_NAMES`. */\nfunction getRenderableFields(descriptor: CountryDescriptor): readonly FieldDescriptor[] {\n return descriptor.fields.filter((field) => !OMITTED_FORM_FIELD_NAMES.has(field.name));\n}\n\n/**\n * Whether the custom-address form may be submitted. Stricter than the server's own\n * `MISSING_BILLING_ADDRESS_FIELDS` floor (\"sameAsDelivery is false but postalCode **or country** was\n * omitted\" [source: `core.graphql:2857-2858`]) — every field the active country's descriptor renders,\n * except `complement` (`OPTIONAL_FORM_FIELD_NAMES`), must be non-blank (PR #544 N1 item 1: the\n * engineer's own rule that the new-address fields are required once \"different\" is selected), and every\n * non-blank value must match its descriptor's `rule.pattern`. Resolves the descriptor from\n * `formDraft.country` itself rather than taking a second parameter, so this stays a single\n * self-contained fact about one `BillingAddressFormDraft` value. `country` itself is not part of\n * `descriptor.fields` (Decision 7) and is never checked here — it always carries a non-blank value from\n * `defaultCountry` or the selector's own current value (review F12), so it can never be the field this\n * gate would fail on.\n */\nexport function isBillingAddressFormValid(formDraft: BillingAddressFormDraft): boolean {\n // No descriptor means no known required-field list to satisfy — a vacuous `true` would be exactly\n // wrong here. Not reachable through the rendered component today (review F7's fallback already\n // resolves `formDraft.country` to a real descriptor before the form ever mounts), but this function\n // is exported and unit-tested directly with such a country, so it must not depend on the render path.\n const descriptor = getCountryDescriptor(formDraft.country);\n return descriptor ? areRenderableFieldsValid(getRenderableFields(descriptor), formDraft) : false;\n}\n\n/**\n * The verdict behind `isBillingAddressFormValid`, taking the renderable fields as an argument so the\n * render path can pass the list it already memoises per country instead of resolving them again on every\n * render. One body for both callers, so the gate can never disagree with itself.\n */\nfunction areRenderableFieldsValid(\n renderableFields: readonly FieldDescriptor[],\n formDraft: BillingAddressFormDraft,\n): boolean {\n return renderableFields.every((field) => getFieldIssue(field, formDraft[field.name] ?? '') === null);\n}\n\n/**\n * Whether the submit control may fire: the active country has a descriptor and every field it renders is\n * valid. Mirrors `isBillingAddressFormValid`, over the descriptor and fields the render path already holds.\n */\nfunction canSaveBillingDraft(\n descriptor: CountryDescriptor | undefined,\n renderableFields: readonly FieldDescriptor[],\n formDraft: BillingAddressFormDraft,\n): boolean {\n return descriptor !== undefined && areRenderableFieldsValid(renderableFields, formDraft);\n}\n\n/** The touched set with `name` added. Returns `prev` itself when it already holds `name`, so a state update\n * built on it never triggers a redundant re-render. */\nfunction withTouched(prev: ReadonlySet<string>, name: string): ReadonlySet<string> {\n return prev.has(name) ? prev : new Set(prev).add(name);\n}\n\n/**\n * Takes focus for a refusal that named the billing form (T212, Decision 3): the first rendered field, in\n * render order, whose value is blank and which is required, since the shopper has to fill it next; else the\n * first field whose value fails its format (the submit control is disabled while one does, so it cannot\n * take focus); else the submit control, since saving is then the next step. Reads the live DOM inside\n * `panel`, so it never sees stale state. `country` is never a target (it always holds a value). Returns\n * whether focus landed: a disabled field, or a disabled submit control while saving, takes none.\n * `onFieldFocused` receives the name of a field that took focus, so the caller can mark it touched: the\n * shopper is sent there to read its message, and blur would never come.\n */\nfunction focusNextBillingControl(\n panel: HTMLElement | null,\n renderableFields: readonly FieldDescriptor[],\n onFieldFocused: (name: string) => void,\n): boolean {\n if (!panel) return false;\n const fields = panel.querySelectorAll<HTMLElement>('[data-fs-billing-address-field]');\n let target: HTMLElement | null = null;\n let malformed: HTMLElement | null = null;\n for (const field of fields) {\n const name = field.dataset.fsBillingAddressField ?? '';\n const input = field.querySelector('input');\n const descriptor = renderableFields.find((candidate) => candidate.name === name);\n if (!input || !descriptor) continue;\n const issue = getFieldIssue(descriptor, input.value);\n if (issue === 'required') {\n target = input;\n break;\n }\n if (issue === 'format') malformed ??= input;\n }\n target ??= malformed ?? panel.querySelector<HTMLElement>('[data-fs-billing-address-submit]');\n if (!target) return false;\n target.focus();\n const landed = target.ownerDocument.activeElement === target;\n const fieldName = target.closest<HTMLElement>('[data-fs-billing-address-field]')?.dataset.fsBillingAddressField;\n if (landed && fieldName) onFieldFocused(fieldName);\n return landed;\n}\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\nexport const BillingAddressDefinition = defineComponent<BillingAddressProps>({\n name: 'BillingAddress',\n importPath: '@vtex/faststore/components',\n component: BillingAddress,\n requiresContext: 'payment',\n actions: { payment: ['setBillingAddress'] },\n editor: { label: 'Billing Address', category: 'commerce' },\n schema: {\n sameAsDeliveryLabel: {\n type: 'string',\n default: 'Billing address same as delivery address',\n label: 'Same-as-delivery toggle label',\n },\n defaultCountry: { type: 'string', default: 'BRA', label: 'Default country (ISO-3)' },\n countryLabel: { type: 'string', default: 'Country', label: 'Country field label' },\n submitLabel: { type: 'string', default: 'Save billing address', label: 'Submit button label' },\n submitPendingLabel: { type: 'string', default: 'Saving...', label: 'Submit button pending label' },\n requiredNoticeLabel: {\n type: 'string',\n default: 'This payment method requires a billing address.',\n label: 'Required-billing-address notice label',\n },\n unsavedAddressNoticeLabel: {\n type: 'string',\n default: 'No billing address has been saved yet — the delivery address will be used unless you save one below.',\n label: 'Unsaved-address notice label',\n },\n // T73 (specs/render-the-billing-match-disclaimer-beside.md) — requirement 12.20. Always on unless\n // switched off; gated on these two props alone, never on usePayment()/local state/$data (FR-4).\n // Both defaults are INLINE LITERALS (FR-7): the native catalog builder extracts `schema`\n // statically, so an identifier-valued default is dropped from `dist/catalog.json` with no error.\n billingMatchNoticeLabel: {\n type: 'string',\n default:\n 'Your billing address must match the one on your card or bank statement, or your bank may decline the order.',\n label: 'Billing-match disclaimer',\n },\n billingMatchNoticeVisible: { type: 'boolean', default: true, label: 'Show the billing-match disclaimer' },\n // T31 (specs/make-the-payment-components-authored-copy.md, FR-7, Decision 5) — the eight canonical\n // descriptor field names `FIELD_LABELS_EN` used to hardcode, now schema props defaulting to that\n // same map's own values. `recipientNameLabel` is not rendered by the custom-address form today\n // (OMITTED_FORM_FIELD_NAMES) — kept for parity with the other seven.\n recipientNameLabel: { type: 'string', default: \"Recipient's name\", label: 'Recipient name field label' },\n streetLabel: { type: 'string', default: 'Street', label: 'Street field label' },\n numberLabel: { type: 'string', default: 'Number', label: 'Number field label' },\n complementLabel: { type: 'string', default: 'Complement (optional)', label: 'Complement field label' },\n neighborhoodLabel: { type: 'string', default: 'Neighborhood', label: 'Neighborhood field label' },\n cityLabel: { type: 'string', default: 'City', label: 'City field label' },\n stateLabel: { type: 'string', default: 'State', label: 'State field label' },\n postalCodeLabel: { type: 'string', default: 'Postal code', label: 'Postal code field label' },\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n fieldRequiredMessage: {\n type: 'string',\n default: 'This field is required.',\n label: 'Field required message',\n },\n // An inline literal as well, for the same reason (INV-6).\n postalCodeInvalidMessage: {\n type: 'string',\n default: 'Enter a valid postal code.',\n label: 'Postal code format message',\n },\n disabled: { type: 'boolean', default: false, label: 'Disabled' },\n },\n guideline:\n \"Same-as-delivery toggle (default on) for the billing address; when off, mounts a country-specific address form (reusing shipping-address's country descriptors) and persists it via $actions.payment.setBillingAddress on an explicit submit control. Re-checking the toggle dispatches sameAsDelivery: true only once an address was actually persisted (otherwise it's a local no-op — there's nothing on the server to revert). Publishes only a boolean (never the address) onto the shared payment draft for usePlaceOrder to read. While the form is open and nothing has been saved yet, renders an inline status notice — the required attribute on the form's own fields is not a submit gate; Place Order can still complete billed to the delivery address if the shopper never saves. Must be inside a PaymentSectionRoot or PaymentSectionController; place as a sibling of CardEntry/SavedCards/Installments/SaveCard/PlaceOrder, never nested inside any of them. See billing-address.md.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function BillingAddress({\n sameAsDeliveryLabel = 'Billing address same as delivery address',\n defaultCountry = 'BRA',\n countryLabel = 'Country',\n submitLabel = 'Save billing address',\n submitPendingLabel = 'Saving...',\n requiredNoticeLabel = 'This payment method requires a billing address.',\n unsavedAddressNoticeLabel = 'No billing address has been saved yet — the delivery address will be used unless you save one below.',\n billingMatchNoticeLabel = 'Your billing address must match the one on your card or bank statement, or your bank may decline the order.',\n billingMatchNoticeVisible = true,\n recipientNameLabel = \"Recipient's name\",\n streetLabel = 'Street',\n numberLabel = 'Number',\n complementLabel = 'Complement (optional)',\n neighborhoodLabel = 'Neighborhood',\n cityLabel = 'City',\n stateLabel = 'State',\n postalCodeLabel = 'Postal code',\n fieldRequiredMessage,\n postalCodeInvalidMessage,\n disabled = false,\n className,\n $actions: $actionsProp,\n ...props\n}: BillingAddressProps) {\n const $actions = useActionsOverride($actionsProp);\n // T31 (FR-7) — replaces the hardcoded `FIELD_LABELS_EN` map: keyed by the descriptor's own canonical\n // field name, same resolution as before (`renderableFields.map` below falls back to the descriptor's\n // own label when a field name has no matching prop; none does today).\n const fieldLabels: Readonly<Record<string, string>> = {\n recipientName: recipientNameLabel,\n street: streetLabel,\n number: numberLabel,\n complement: complementLabel,\n neighborhood: neighborhoodLabel,\n city: cityLabel,\n state: stateLabel,\n postalCode: postalCodeLabel,\n };\n // Also enforces the PaymentSectionRoot/PaymentSectionController ancestor: usePayment() throws\n // when rendered outside one.\n const {\n draft,\n selectedMethod,\n setDraftBillingAddressDifferent,\n setDraftBillingAddressUnsaved,\n placing,\n cardPaymentCollapsed,\n registerRefusalFocus,\n } = usePayment();\n // Fix pass, findings R2/S2: a stable identity for *this* mounted instance, so its cleanup below\n // retracts only its own contribution to the shared `billingAddressUnsaved` fact — never a second\n // instance's still-open, still-unsaved one (billing-address.md documents multiple instances under one\n // root as allowed). `useId()` is stable for the component's lifetime and distinct per instance, which\n // is exactly what a per-instance set key needs; it is never rendered or sent anywhere.\n const instanceKey = useId();\n // T175 (Decision 3): the id the toggle's `aria-controls` names in every state, rendered on the\n // always-mounted panel below it. Its own `useId()`, so `instanceKey` above stays unrendered.\n const panelId = useId();\n\n const [formDraft, setFormDraft] = useState<BillingAddressFormDraft>(() => ({\n postalCode: '',\n // A `defaultCountry` with no descriptor (a store-editor typo) falls back to the first available\n // country rather than shipping a dead form with no fields and a permanently-disabled submit\n // (review F7) — `getAvailableCountries()` is never empty in practice (Decision 7), but the raw\n // prop is the last-resort fallback if it ever were, matching today's behavior for that edge case.\n country: getCountryDescriptor(defaultCountry) ? defaultCountry : (getAvailableCountries()[0] ?? defaultCountry),\n street: '',\n number: '',\n complement: '',\n neighborhood: '',\n city: '',\n state: '',\n }));\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n // T38 (FR-6, Decision 8): per-field validity, mirroring card-entry.tsx:1115/:1150. A field joins this\n // set on blur, or when a refused submit moves focus to it (`markTouched`), where blur never comes.\n // Gated on it so a field is never marked invalid before the shopper reaches it (US-3,\n // Risk R5) — wiring `invalid` off `isBillingAddressFormValid` alone would mark every field invalid\n // the instant the form opens.\n const [touched, setTouched] = useState<ReadonlySet<string>>(() => new Set());\n // Local-only: whether the custom-address form is showing. Decoupled from `draft.billingAddressDifferent`\n // on purpose (PR #544 N1) — that published flag is what `gateway.ts` reads to decide whose address to\n // bill, so it must say \"different\" only once an address has actually been persisted differently, never\n // merely because the shopper opened this form. Initialized from the draft (via the same pure\n // `resolveSameAsDelivery` the checkbox used to read directly) so the two never start out of sync.\n const [formOpen, setFormOpen] = useState(() => !resolveSameAsDelivery(draft.billingAddressDifferent));\n\n // Re-syncs `formOpen` whenever the shared draft's `billingAddressDifferent` changes for a reason\n // outside THIS instance's own clicks (PR #544 N8) — e.g. a sibling `BillingAddress` mounted under the\n // same `PaymentSectionRoot` persists or reverts an address through its own copy of this same shared\n // context. A bare local `useState` only ever reflects the instance that called its own setter; without\n // this effect, a second mounted instance keeps showing whatever `formOpen` it computed at its own\n // mount forever, even after `draft.billingAddressDifferent` — the one fact the gateway actually reads\n // — has moved on. Deliberately keyed on the primitive `draft.billingAddressDifferent`, not the `draft`\n // object itself: `draft` is a stable `useMemo` over seven facts (`use-payment-method-selection.ts`),\n // so its identity changes whenever ANY of them does — including `draftBin`, which `CardEntry`\n // publishes on every keystroke of the card number. Keying this effect on the whole object instead\n // would refire it on each of those keystrokes while this form is open and nothing is yet persisted,\n // recomputing `formOpen` from an unchanged `billingAddressDifferent` and closing the form mid-input\n // (review #544 N15 — a dedicated regression test in `billing-address.test.tsx` pins this). This\n // differs from `shipping-address.tsx:645`'s own snapshot-keyed effect, whose reason is Apollo\n // returning a new-but-equal object reference every render (its own `biome-ignore` comment says so) —\n // `draft` here is a local `useMemo`, already stable; the hazard is refiring on an unrelated fact\n // change, not on render churn. Does NOT reintroduce N1: this component's own uncheck transition (below) sets\n // `formOpen` without ever touching the draft, so that transition alone never changes this effect's\n // dependency and never fires it. Deleting this effect is exactly the mutant PR #544's re-verification\n // named V-P2/N8 — the two-instance regression test below fails without it.\n useEffect(() => {\n setFormOpen(!resolveSameAsDelivery(draft.billingAddressDifferent));\n }, [draft.billingAddressDifferent]);\n\n const sameAsDelivery = !formOpen;\n // PR #544 N7: visible only while the shopper is on the custom-address path AND nothing has actually\n // been persisted differently yet — the exact window in which Place Order would silently bill the\n // delivery address. Mirrors the same `draft.billingAddressDifferent !== true` check\n // `handleSameAsDeliveryChange`'s local-no-op branch already uses, so the notice and the no-op branch\n // can never disagree about what \"nothing persisted\" means. T191 (FR-8): never while the card payment\n // is collapsed, when this component renders nothing — the gate below must not refuse Place order over\n // a form the shopper cannot see, and the notice and the gate keep reading one expression.\n const unsavedAddressNoticeVisible = !cardPaymentCollapsed && formOpen && draft.billingAddressDifferent !== true;\n\n // T24's submit gate (specs/cardholder-document-submit-gate.md FR-6): publishes the EXACT same fact\n // the notice above renders from, so the gate and the notice can never disagree (Risk R5). Keyed by\n // this instance's own `instanceKey` (fix pass, findings R2/S2): `billingAddressUnsaved` is one fact\n // shared by every `BillingAddress` mounted under this root, so the cleanup must retract only *this*\n // instance's own contribution on unmount — never a second, still-open, still-unsaved instance's own\n // block (measured: an unconditional publish here left the CTA proceeding while that instance's own\n // notice kept rendering, billing the declined delivery address anyway). Not cosmetics either way: an\n // unmounted `BillingAddress` must not leave the CTA blocked with no form on screen left to fix it\n // (Risk R2) when it was the *only* instance.\n useEffect(() => {\n setDraftBillingAddressUnsaved(instanceKey, unsavedAddressNoticeVisible);\n return () => setDraftBillingAddressUnsaved(instanceKey, false);\n }, [instanceKey, unsavedAddressNoticeVisible, setDraftBillingAddressUnsaved]);\n\n // T212: while the form is open and on screen, a refusal naming the billing form may move focus into it.\n // Registered by the instance that renders the form, so a closed or collapsed instance never takes it.\n const canTakeFocus = !cardPaymentCollapsed && formOpen;\n const draftCountry = formDraft.country;\n const draftDescriptor = useMemo(() => getCountryDescriptor(draftCountry), [draftCountry]);\n const renderableFields = useMemo(\n () => (draftDescriptor ? getRenderableFields(draftDescriptor) : []),\n [draftDescriptor],\n );\n useEffect(() => {\n if (!canTakeFocus) return;\n const markTouched = (name: string) => setTouched((prev) => withTouched(prev, name));\n return registerRefusalFocus('billing-form', () =>\n focusNextBillingControl(document.getElementById(panelId), renderableFields, markTouched),\n );\n }, [canTakeFocus, renderableFields, panelId, registerRefusalFocus]);\n\n const billingAddressRequired = selectedMethod?.validator.billingAddressRequired ?? false;\n const fieldsDisabled = disabled || pending;\n const requiredMessage = resolveAuthoredCopy(fieldRequiredMessage, 'This field is required.');\n const invalidMessage = resolveAuthoredCopy(postalCodeInvalidMessage, 'Enter a valid postal code.');\n\n async function handleSameAsDeliveryChange(nextSameAsDelivery: boolean) {\n setError(null);\n\n if (!nextSameAsDelivery) {\n // Opens the custom-address form locally. Nothing is dispatched and the shared draft is left\n // untouched here — the shopper has typed nothing yet, and publishing `billingAddressDifferent:\n // true` before an address is actually persisted is exactly the false claim review #544's N1\n // measured reaching the gateway in two clicks. Only a successful submit (handleSubmit below) may\n // ever publish `true` — see this file's own top-level doc comment.\n setFormOpen(true);\n return;\n }\n\n if (draft.billingAddressDifferent !== true) {\n // Nothing was ever persisted differently — either the shopper never submitted, or an earlier\n // submit failed — so there is nothing on the server to revert. Closing the form is a local-only\n // action: dispatching `setBillingAddress({ sameAsDelivery: true })` here would be a wasted round\n // trip reverting an address the server was never asked to set in the first place.\n setFormOpen(false);\n return;\n }\n\n const setBillingAddress = $actions?.payment?.setBillingAddress;\n // Silent no-op when the action namespace is not injected, mirroring `SaveCard`'s / `selectMethod`'s\n // \"no-op (no error, no throw)\" convention.\n if (!setBillingAddress) return;\n\n setPending(true);\n try {\n const userErrors = await setBillingAddress({ sameAsDelivery: true });\n const [firstError] = userErrors;\n if (firstError) {\n setError(firstError.message);\n // A refused revert (review F3): the persisted address is still the custom one the shopper had\n // before, so the form stays open (`formOpen` untouched) and the draft keeps claiming\n // \"different\" — neither claims \"same as delivery\" the server never accepted. There is nothing\n // to roll back here, unlike before this pass: the draft is only ever written `false` a few\n // lines below, after `setBillingAddress` has already resolved with no error.\n } else {\n setDraftBillingAddressDifferent(false);\n // Redundant with the resync effect above once this draft write lands (the effect closes the\n // form on its own whenever `draft.billingAddressDifferent` moves to `false` — review #544 N18)\n // — kept anyway so this instance closes synchronously with its own click rather than waiting on\n // a re-render, which costs nothing since both agree on the outcome.\n setFormOpen(false);\n }\n } finally {\n setPending(false);\n }\n }\n\n function handleFieldChange(name: string, value: string) {\n setFormDraft((prev) => ({ ...prev, [name]: value }));\n }\n\n /** Marks a field touched on its first blur (T38, FR-6). Idempotent on every later blur — a `Set`\n * already containing `name` is returned unchanged, so this never triggers a redundant re-render. */\n function handleFieldBlur(name: string) {\n setTouched((prev) => withTouched(prev, name));\n }\n\n function handleCountryChange(value: string | null) {\n // Base UI's `SelectRoot` (`select/root/SelectRoot.js`) only ever calls `onValueChange` with one of\n // `Select.Item`'s own `value`s — all descriptor-backed, non-null ISO codes here (verified by\n // reading its source, not assumed) — so `value` is `null` on no path this component's own `Select`\n // below can reach. The `?? prev.country` fallback exists only to satisfy\n // `BillingAddressFormDraft.country: string`'s type, not as a runtime branch pretending to be tested\n // (review F10's residual, R5′): dropping the fallback (`country: value`) is a `pnpm typecheck`\n // failure, not merely an unproven `if` — a stronger, mechanical guarantee than a guard clause no\n // real interaction could ever exercise.\n //\n // Preserves every other field's value across the switch (Decision 7) — matches\n // `shipping-address`'s own documented behavior: a value typed under one country resurfaces if the\n // shopper switches away and back, even while the field is hidden for the currently-selected one.\n setFormDraft((prev) => ({ ...prev, country: value ?? prev.country }));\n }\n\n async function handleSubmit() {\n const setBillingAddress = $actions?.payment?.setBillingAddress;\n setError(null);\n if (!setBillingAddress) return;\n\n // Only include a field the active country's descriptor actually renders (mirrors\n // `shipping-address.tsx`'s own `isFieldActive` guard at its submit handler) — otherwise a value\n // typed under a previously-selected country (e.g. `neighborhood` retained in the draft after\n // switching BR -> USA) would leak into the submitted payload even though the field was never\n // rendered, editable, or clearable for the country actually being submitted (review F2).\n // `postalCode` and `country` are unconditional: every descriptor declares both.\n const isFieldActive = (name: string) => renderableFields.some((field) => field.name === name);\n\n setPending(true);\n try {\n const userErrors = await setBillingAddress({\n sameAsDelivery: false,\n postalCode: formDraft.postalCode,\n country: formDraft.country,\n ...(isFieldActive('street') ? { street: formDraft.street } : {}),\n ...(isFieldActive('number') ? { number: formDraft.number } : {}),\n ...(isFieldActive('complement') ? { complement: formDraft.complement } : {}),\n ...(isFieldActive('neighborhood') ? { neighborhood: formDraft.neighborhood } : {}),\n ...(isFieldActive('city') ? { city: formDraft.city } : {}),\n ...(isFieldActive('state') ? { state: formDraft.state } : {}),\n });\n const [firstError] = userErrors;\n if (firstError) {\n setError(firstError.message);\n } else {\n // The one place `billingAddressDifferent` may ever become `true` (PR #544 N1) — only once the\n // server actually holds a different address, so the flag means exactly what `gateway.ts`'s own\n // doc comment already claims: \"`setBillingAddress` already persisted it … so the gateway reads\n // it from there.\"\n setDraftBillingAddressDifferent(true);\n }\n } finally {\n setPending(false);\n }\n }\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed. Returned after the\n // last hook, so this instance stays mounted and keeps what the shopper typed for when the card is back.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-billing-address=\"\"\n {...(placing ? { 'data-fs-billing-address-placing': '' } : {})}\n {...(pending ? { 'data-fs-billing-address-pending': '' } : {})}\n {...(sameAsDelivery ? { 'data-fs-billing-address-same-as-delivery': '' } : {})}\n >\n {billingMatchNoticeVisible && billingMatchNoticeLabel.trim() ? (\n <p data-fs-billing-address-match-notice=\"\">{billingMatchNoticeLabel}</p>\n ) : null}\n <div data-fs-billing-address-toggle-field=\"\">\n <Field.Root disabled={fieldsDisabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root\n checked={sameAsDelivery}\n onCheckedChange={handleSameAsDeliveryChange}\n disabled={fieldsDisabled}\n aria-controls={panelId}\n >\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {sameAsDeliveryLabel}\n </Field.Label>\n </Field.Root>\n {billingAddressRequired ? <p data-fs-billing-address-required-note=\"\">{requiredNoticeLabel}</p> : null}\n </div>\n\n {/* T175 (Decision 3): always mounted, so the toggle's `aria-controls` resolves in every state. It\n takes no className or props, and it is `hidden` only while empty (INV-2): a store's display\n utility can beat `[hidden]`, but then it shows an empty box, never content. */}\n <div id={panelId} data-fs-billing-address-panel=\"\" hidden={sameAsDelivery}>\n {sameAsDelivery ? null : (\n <div data-fs-billing-address-form=\"\">\n {unsavedAddressNoticeVisible ? (\n <p role=\"status\" data-fs-billing-address-unsaved-notice=\"\">\n {unsavedAddressNoticeLabel}\n </p>\n ) : null}\n <Field.Root disabled={fieldsDisabled} data-fs-field-root=\"\" data-fs-billing-address-field=\"country\">\n <Field.Label>{countryLabel}</Field.Label>\n <Select.Root\n value={formDraft.country}\n onValueChange={handleCountryChange}\n disabled={fieldsDisabled}\n required\n >\n <Select.Trigger>\n {/* Resolves the label directly from the country descriptor rather than relying on\n Base UI's automatic item-label lookup, which only reflects a mounted Select.Item —\n mirrors `ShippingAddressCountryControl`'s identical reasoning. English override per\n `getCountryLabel` (review F5) — the Brazilian descriptor's own name is Portuguese. */}\n <Select.Value>{(value) => (value ? getCountryLabel(value) : value)}</Select.Value>\n <Select.Icon>▾</Select.Icon>\n </Select.Trigger>\n <Select.Portal>\n <Select.Positioner>\n <Select.Popup>\n <Select.List>\n {getAvailableCountries().map((iso) => {\n const label = getCountryLabel(iso);\n return (\n <Select.Item key={iso} value={iso} label={label}>\n {label}\n </Select.Item>\n );\n })}\n </Select.List>\n </Select.Popup>\n </Select.Positioner>\n </Select.Portal>\n </Select.Root>\n </Field.Root>\n\n {renderableFields.map((field) => {\n // T38 (FR-6): gated on `touched` (blur, or a refused submit focusing the field) so nothing is\n // marked invalid before the shopper reaches it (US-3, Risk R5). A touched field is invalid once\n // `getFieldIssue` finds an issue with its value: blank (except `complement`) or not in the\n // selected country's format (INV-3/INV-4). `areRenderableFieldsValid` reads the same\n // `getFieldIssue`, so the two are never allowed to disagree about a field.\n const issue = touched.has(field.name) ? getFieldIssue(field, formDraft[field.name] ?? '') : null;\n const invalid = issue !== null;\n return (\n <Field.Root\n key={field.name}\n disabled={fieldsDisabled}\n invalid={invalid}\n data-fs-field-root=\"\"\n data-fs-billing-address-field={field.name}\n >\n {/* Authored field-label prop per `fieldLabels` (T31, FR-7) — the Brazilian descriptor's own\n labels are Portuguese, so every country gets an authored English one. `||`, not `??`\n (fix pass round 2, finding N1 — same defect class as place-order.tsx's finding 7 from\n round 1): a store author can write `\"\"` for a field's authored label — a valid\n `type: 'string'` prop value — and `??` only falls through on `null`/`undefined`, so an\n authored empty string would render an empty accessible name on a control that is\n required for every field but `complement`. Pre-existing hazard, not fixed here: `||`\n still renders blank for a whitespace-only authored label — inside T31's ownership,\n reported not fixed (T38 § 5). */}\n <Field.Label>{fieldLabels[field.name] || field.label}</Field.Label>\n <Field.Control\n value={formDraft[field.name] ?? ''}\n onChange={(event) => handleFieldChange(field.name, event.currentTarget.value)}\n onBlur={() => handleFieldBlur(field.name)}\n disabled={fieldsDisabled}\n // Every rendered field is required once \"different\" is selected, except `complement`\n // (`OPTIONAL_FORM_FIELD_NAMES`) — PR #544 N1 item 1. Not a like-for-like precedent with\n // `CardholderDocument`'s own `required={documentRequired}`: that one forwards to a\n // particle with its own declared prop, a root state hook, and a paired `Field.Error`\n // driven by a server-sourced validator flag, where this sets `required` directly off a\n // locally hardcoded set instead (full comparison: specs/billing-address.md Accessibility\n // Contract).\n required={!OPTIONAL_FORM_FIELD_NAMES.has(field.name)}\n />\n {invalid ? (\n <Field.Error match>\n {issue === 'format' ? getFieldFormatMessage(field, invalidMessage) : requiredMessage}\n </Field.Error>\n ) : null}\n </Field.Root>\n );\n })}\n\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={fieldsDisabled || !canSaveBillingDraft(draftDescriptor, renderableFields, formDraft)}\n data-fs-billing-address-submit=\"\"\n >\n {pending ? submitPendingLabel : submitLabel}\n </button>\n </div>\n )}\n </div>\n\n {error ? (\n <div role=\"alert\" data-fs-billing-address-error=\"\">\n {error}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"billing-address.js","names":[],"sources":["../../../src/matter/billing-address/billing-address.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { useEffect, useId, 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 { Select } from '../../particles/select/select.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { usePayment } from '../payment/payment.js';\nimport {\n type CountryDescriptor,\n type FieldDescriptor,\n getAvailableCountries,\n getCountryDescriptor,\n satisfiesFieldPattern,\n} from '../shipping-address/country-descriptors.js';\n\n/**\n * The `BillingAddress` matter (specs/billing-address.md, T11 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D6). A \"same as delivery\" toggle (default on) plus, when off, a\n * country-specific address form that persists via `$actions.payment.setBillingAddress`\n * (already shipped by T4, zero consumers before this task).\n *\n * The shared `Address` composition RFC D6 names (shipping and billing as two control matters over\n * one set of reusable particles) is DEFERRED — an implementation-time call the engineer resolved\n * narrow (specs/billing-address.md Decision 1). This component builds its own controls and imports\n * `countryDescriptors` from `../shipping-address/country-descriptors.js`, the one place a country's field\n * set, labels, copy and format rules live.\n *\n * There is NO read-back path for a persisted billing address (specs/billing-address.md Decision 3):\n * `type Payment` carries no `billingAddress` field, and `setBillingAddress` itself resolves only\n * `userErrors`, discarding the mutation's own echo. This component declares no fragment and renders\n * nothing below the toggle while \"same as delivery\" is checked, not even the delivery address\n * (specs/stop-re-rendering-the-full-delivery.md, T175). The custom-address form always starts blank,\n * and its own local state is the sole source of truth for what was typed.\n *\n * Only a boolean rides the shared `draft` (`billingAddressDifferent`, widened onto `PaymentDraft` by\n * this task under sole ownership of `use-payment-method-selection.ts` — specs/billing-address.md\n * Decision 5); the address itself is persisted server-side by this component's own action call, never\n * passed through the draft. `setBillingAddress` is dispatched at exactly two points (Decision 9): the\n * toggle's transition back to \"same as delivery\", and the custom form's explicit submit control —\n * never on mount, never per keystroke.\n *\n * **`draft.billingAddressDifferent` only ever becomes `true` once `setBillingAddress` has actually\n * persisted a different address (PR #544 N1).** Unchecking \"same as delivery\" opens the form\n * (`formOpen`, local-only state below) but does not touch the draft — `gateway.ts`'s own doc comment\n * (\"`setBillingAddress` already persisted it … so the gateway reads it from there\") is the contract\n * this must not break: publishing `true` before anything is persisted would point the gateway at an\n * `invoiceData` address nothing ever wrote. The engineer's own resolution for this (session of\n * 2026-07-31): the new-address fields become required whenever \"different\" is selected, so this\n * component's own submit blocks on an incomplete address (`isBillingAddressFormValid`) — but that\n * alone cannot stop a shopper from declining \"same as delivery\" and clicking Place Order without ever\n * opening the form's own submit control, which is why the draft write itself is gated on success, not\n * merely the submit button.\n *\n * **The native `required` attribute below is cosmetic here, not an enforcement mechanism (PR #544\n * N7).** This file renders no `aria-required` at all — only `required` (native HTML). There is no\n * `<form>` in this render tree and the submit control is `type=\"button\"`, so the browser's own\n * constraint validation never runs — nothing in this file or its tests may claim this attribute itself\n * prevents a shopper from placing an order with an unsaved address; the actual submit gate is\n * `isBillingAddressFormValid`'s verdict (`areRenderableFieldsValid`), read at the submit control's own\n * `disabled` below. Since T38\n * (specs/harden-and-verify-accessibility-across-the.md FR-6) each rendered field does carry a\n * `Field.Error`, but it is driven by a local `touched` set (a field joins it on blur, or when a refused\n * submit moves focus to it) and `getFieldIssue`'s verdict (`required` for a blank field, `format` for a\n * non-blank value that fails the country's postal pattern), never by `required`'s own native\n * `ValidityState` — so this paragraph's point is unchanged: `required` itself still enforces nothing\n * here. What this component owes, and does: make the gap **visible** — an inline `role=\"status\"`\n * notice while the form is open and nothing has been persisted yet — and publish that same fact\n * (`setDraftBillingAddressUnsaved`) onto the shared draft, from the identical expression the notice\n * renders from (T24, specs/cardholder-document-submit-gate.md FR-6). **The actual hard block lives\n * elsewhere**: T24's `usePaymentSubmitGate()` reads that published fact and refuses Place Order\n * client-side — this component's own job stops at publishing the fact and showing the notice, never at\n * gating the CTA itself. See `specs/billing-address.md`'s Documentation Gaps #1 for the deferral this closes.\n */\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * Local-only values for the \"different\" address form, keyed by the country descriptor's own\n * canonical field names — a plain string index signature, not a closed set of properties, so a future\n * country descriptor introducing a new field name needs no type change here (mirrors\n * `FieldDescriptor.name: string`'s own deliberately untyped choice; specs/billing-address.md\n * Decision 7). `postalCode` and `country` are named explicitly because this component's own logic\n * reads both directly. Never carries a `recipientName` key: `PaymentBillingAddressUpdateInput` has no\n * such field (Decision 4) — the render loop filters it out of whatever the descriptor lists.\n */\nexport type BillingAddressFormDraft = Readonly<{\n postalCode: string;\n country: string;\n [fieldName: string]: string;\n}>;\n\nexport type BillingAddressProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /** The \"same as delivery\" toggle's own accessible label. */\n sameAsDeliveryLabel?: string;\n /** ISO 3166-1 alpha-3 default country for the custom-address form's country selector. */\n defaultCountry?: string;\n /** Label for the country selector inside the custom-address form. */\n countryLabel?: string;\n /** Label for the control that persists a custom billing address. */\n submitLabel?: string;\n /** Label on the submit control while the mutation is in flight. */\n submitPendingLabel?: string;\n /** Shown when the selected method's validator marks a billing address required. */\n requiredNoticeLabel?: string;\n /** Label for the recipient-name descriptor field. Not rendered by the custom-address form today\n * (`OMITTED_FORM_FIELD_NAMES` — the write contract has no matching input slot, Decision 4); kept\n * for parity with the other seven field labels this same map covers. */\n recipientNameLabel?: string;\n /** Label for the street descriptor field. */\n streetLabel?: string;\n /** Label for the street-number descriptor field. */\n numberLabel?: string;\n /** Label for the complement descriptor field. Its \"(optional)\" suffix is the only signal that the\n * field is not required — `OPTIONAL_FORM_FIELD_NAMES` drives the actual `required` attribute\n * independently, so authoring this without the suffix loses the optionality cue, not the behavior\n * (R7). */\n complementLabel?: string;\n /** Label for the neighborhood descriptor field. Unused when the active country's descriptor has no\n * `neighborhood` field (e.g. USA). */\n neighborhoodLabel?: string;\n /** Label for the city descriptor field. */\n cityLabel?: string;\n /** Label for the state descriptor field. */\n stateLabel?: string;\n /** Label for the postal-code descriptor field. */\n postalCodeLabel?: string;\n /** Shown while the form is open and no address has been persisted yet (PR #544 N7) — the shopper\n * declined \"same as delivery\" but has not (or not yet again) had a submit succeed, so Place Order\n * will bill the delivery address unless they save one first. Rendered `role=\"status\"`, not\n * `role=\"alert\"`: it is a persistent, advisory condition of the form being open, not a one-off\n * announcement reacting to a rejected action (see this file's own existing `-error` alert for that\n * shape) — see `specs/billing-address.md`'s Accessibility Contract for the site count this choice is\n * measured against. */\n unsavedAddressNoticeLabel?: string;\n /** Always-on billing-match disclaimer (requirement 12.20): tells the shopper their billing address\n * must match the one their card issuer or bank statement holds, so an AVS mismatch does not get the\n * order declined with no explanation on screen. Gated on this prop and `billingMatchNoticeVisible`\n * only — reads no `usePayment()` value, no local state, no `$data` (FR-4), unlike the two notices\n * above. */\n billingMatchNoticeLabel?: string;\n /** Switches the billing-match disclaimer above on or off. Defaults to `true` (Decision 5 — the\n * requirement's own intent); subordinate to `billingMatchNoticeLabel.trim()`, which alone can still\n * suppress an empty notice (FR-3) regardless of this prop's value. */\n billingMatchNoticeVisible?: boolean;\n /**\n * Shown as a `Field.Error` under a required, blank, touched field (T38, FR-6/FR-7) — the same\n * non-blank rule `isBillingAddressFormValid` already applies, except `complement`\n * (`OPTIONAL_FORM_FIELD_NAMES`), which is never marked invalid. One prop, not one per field: every\n * rendered field shares the same rule, so seven props would be seven ways to say the same sentence.\n * Resolved through `resolveAuthoredCopy`.\n */\n fieldRequiredMessage?: string;\n /**\n * Shown as a `Field.Error` under a touched, non-blank postal code that does not match the selected\n * country's format (the descriptor's `rule.pattern`). Only the postal code reads this prop: any other\n * field that gains a pattern speaks with its own descriptor message. Blank wins over format: a blank\n * field reads `fieldRequiredMessage`, never this. Never contains the typed value. Resolved through\n * `resolveAuthoredCopy`, so a blank value falls back to the English default like every other copy on\n * this form.\n */\n postalCodeInvalidMessage?: string;\n /** Disables the toggle, every form field, the country selector, and the submit control. */\n disabled?: boolean;\n /** Compiler-injected action surface; carries `payment.setBillingAddress`. */\n $actions?: ComponentActions;\n};\n\n/** Canonical field names the custom-address form never renders, even when a country descriptor\n * lists them — because the write contract has no matching input slot (Decision 4). */\nconst OMITTED_FORM_FIELD_NAMES = new Set(['recipientName']);\n\n/** Descriptor field names the custom-address form renders but never marks required, even while\n * \"different\" is selected (PR #544 N1 item 1 — the engineer's own rule, verbatim: \"if the person says\n * the address is different, the new address fields must be required\"). `complement` is the one\n * exception: `complementLabel`'s own default already labels it optional (\"Complement (optional)\"),\n * and neither a real-world address nor the server's own write contract treats it as load-bearing.\n * Shared between the `required` attribute below and `isBillingAddressFormValid`, so the two never\n * drift apart. An authored `complementLabel` that drops the \"(optional)\" suffix loses that visual cue\n * without changing this set — the two are independent (R7, billing-address.md). */\nconst OPTIONAL_FORM_FIELD_NAMES = new Set(['complement']);\n\n/**\n * English override for the descriptor's own country display names (`'Brasil'`, `'United States'`), because\n * the Brazilian entry's name is Portuguese. This map is a stopgap: copy a country's form speaks belongs on\n * its descriptor in `country-descriptors.ts`, as `countryLabel` and `requiredSuffix` already do. Put a new\n * string there, not in another English override map here. T31\n * (specs/make-the-payment-components-authored-copy.md, Decision 5) turned the eight *field* labels into\n * schema props but left this one a plain map: a prop per ISO code does not scale the way eight named\n * field-label props do. Keyed by the descriptor's own ISO code.\n */\nconst COUNTRY_LABELS_EN: Readonly<Record<string, string>> = {\n BRA: 'Brazil',\n USA: 'United States',\n};\n\n/** Resolves a country's English display name, falling back to the descriptor's own label (then the\n * raw ISO code) for a country this map does not yet cover. */\nfunction getCountryLabel(iso: string): string {\n return COUNTRY_LABELS_EN[iso] ?? getCountryDescriptor(iso)?.label ?? iso;\n}\n\n// ---------------------------------------------------------------------------\n// Pure decision functions — exported so both are unit-testable independent of any rendering\n// (specs/billing-address.md § Resolution logic).\n// ---------------------------------------------------------------------------\n\n/**\n * Whether the toggle reads as \"same as delivery\" — `true` whenever the published flag is `null`\n * (never touched, the default) or `false` (explicitly reverted); only an explicit `true` means the\n * shopper is on the custom-address path. Takes no closure over component state, so it is safe to call\n * fresh on every render.\n */\nexport function resolveSameAsDelivery(billingAddressDifferent: boolean | null | undefined): boolean {\n return billingAddressDifferent !== true;\n}\n\n/** Why a rendered field is not acceptable: blank (`required`), or non-blank and not in the selected\n * country's format (`format`). `null` means acceptable. */\ntype FieldIssue = 'required' | 'format' | null;\n\n/**\n * The one verdict on a rendered field, read by both the submit gate (`areRenderableFieldsValid`, behind\n * `isBillingAddressFormValid`) and the per-field error (the `issue` computed per field in the render), so the\n * two can never disagree (T38 Decision 8). Blank (after\n * trim) wins over format: `complement` (`OPTIONAL_FORM_FIELD_NAMES`) may be blank, every other field may\n * not. A non-blank value is tested raw against the descriptor's `rule.pattern` — the same\n * predicate the delivery form's react-hook-form rule applies, so a surrounding space is a format error\n * in both. A whitespace-only value is the one divergence: it is blank here (`required`), where the\n * delivery form would run the pattern on it (see `satisfiesFieldPattern`). No postal format is defined\n * here: the descriptor's `rule.pattern` is the only definition.\n */\nfunction getFieldIssue(field: FieldDescriptor, value: string): FieldIssue {\n if (value.trim().length === 0) return OPTIONAL_FORM_FIELD_NAMES.has(field.name) ? null : 'required';\n return satisfiesFieldPattern(field, value) ? null : 'format';\n}\n\n/**\n * The message under a field whose verdict is `format`. `postalCode` keeps the authored\n * `postalCodeInvalidMessage` (already resolved by the caller, so it always has an English default); every\n * other field speaks with its own descriptor `rule.pattern.message`, the one the delivery form shows for the\n * same rule, so a pattern a descriptor gains later never surfaces the postal-code copy. A field with no\n * pattern never reaches a `format` verdict, so the empty string is not a message anyone sees.\n */\nexport function getFieldFormatMessage(field: FieldDescriptor, postalCodeMessage: string): string {\n if (field.name === 'postalCode') return postalCodeMessage;\n return field.rule?.pattern?.message ?? '';\n}\n\n/** The descriptor's fields the custom-address form renders: everything but `OMITTED_FORM_FIELD_NAMES`. */\nfunction getRenderableFields(descriptor: CountryDescriptor): readonly FieldDescriptor[] {\n return descriptor.fields.filter((field) => !OMITTED_FORM_FIELD_NAMES.has(field.name));\n}\n\n/**\n * Whether the custom-address form may be submitted. Stricter than the server's own\n * `MISSING_BILLING_ADDRESS_FIELDS` floor (\"sameAsDelivery is false but postalCode **or country** was\n * omitted\" [source: `core.graphql:2857-2858`]) — every field the active country's descriptor renders,\n * except `complement` (`OPTIONAL_FORM_FIELD_NAMES`), must be non-blank (PR #544 N1 item 1: the\n * engineer's own rule that the new-address fields are required once \"different\" is selected), and every\n * non-blank value must match its descriptor's `rule.pattern`. Resolves the descriptor from\n * `formDraft.country` itself rather than taking a second parameter, so this stays a single\n * self-contained fact about one `BillingAddressFormDraft` value. `country` itself is not part of\n * `descriptor.fields` (Decision 7) and is never checked here — it always carries a non-blank value from\n * `defaultCountry` or the selector's own current value (review F12), so it can never be the field this\n * gate would fail on.\n */\nexport function isBillingAddressFormValid(formDraft: BillingAddressFormDraft): boolean {\n // No descriptor means no known required-field list to satisfy — a vacuous `true` would be exactly\n // wrong here. Not reachable through the rendered component today (review F7's fallback already\n // resolves `formDraft.country` to a real descriptor before the form ever mounts), but this function\n // is exported and unit-tested directly with such a country, so it must not depend on the render path.\n const descriptor = getCountryDescriptor(formDraft.country);\n return descriptor ? areRenderableFieldsValid(getRenderableFields(descriptor), formDraft) : false;\n}\n\n/**\n * The verdict behind `isBillingAddressFormValid`, taking the renderable fields as an argument so the\n * render path can pass the list it already memoises per country instead of resolving them again on every\n * render. One body for both callers, so the gate can never disagree with itself.\n */\nfunction areRenderableFieldsValid(\n renderableFields: readonly FieldDescriptor[],\n formDraft: BillingAddressFormDraft,\n): boolean {\n return renderableFields.every((field) => getFieldIssue(field, formDraft[field.name] ?? '') === null);\n}\n\n/**\n * Whether the submit control may fire: the active country has a descriptor and every field it renders is\n * valid. Mirrors `isBillingAddressFormValid`, over the descriptor and fields the render path already holds.\n */\nfunction canSaveBillingDraft(\n descriptor: CountryDescriptor | undefined,\n renderableFields: readonly FieldDescriptor[],\n formDraft: BillingAddressFormDraft,\n): boolean {\n return descriptor !== undefined && areRenderableFieldsValid(renderableFields, formDraft);\n}\n\n/** The touched set with `name` added. Returns `prev` itself when it already holds `name`, so a state update\n * built on it never triggers a redundant re-render. */\nfunction withTouched(prev: ReadonlySet<string>, name: string): ReadonlySet<string> {\n return prev.has(name) ? prev : new Set(prev).add(name);\n}\n\n/**\n * Takes focus for a refusal that named the billing form (T212, Decision 3): the first rendered field, in\n * render order, whose value is blank and which is required, since the shopper has to fill it next; else the\n * first field whose value fails its format (the submit control is disabled while one does, so it cannot\n * take focus); else the submit control, since saving is then the next step. Reads the live DOM inside\n * `panel`, so it never sees stale state. `country` is never a target (it always holds a value). Returns\n * whether focus landed: a disabled field, or a disabled submit control while saving, takes none.\n * `onFieldFocused` receives the name of a field that took focus, so the caller can mark it touched: the\n * shopper is sent there to read its message, and blur would never come.\n */\nfunction focusNextBillingControl(\n panel: HTMLElement | null,\n renderableFields: readonly FieldDescriptor[],\n onFieldFocused: (name: string) => void,\n): boolean {\n if (!panel) return false;\n const fields = panel.querySelectorAll<HTMLElement>('[data-fs-billing-address-field]');\n let target: HTMLElement | null = null;\n let malformed: HTMLElement | null = null;\n for (const field of fields) {\n const name = field.dataset.fsBillingAddressField ?? '';\n const input = field.querySelector('input');\n const descriptor = renderableFields.find((candidate) => candidate.name === name);\n if (!input || !descriptor) continue;\n const issue = getFieldIssue(descriptor, input.value);\n if (issue === 'required') {\n target = input;\n break;\n }\n if (issue === 'format') malformed ??= input;\n }\n target ??= malformed ?? panel.querySelector<HTMLElement>('[data-fs-billing-address-submit]');\n if (!target) return false;\n target.focus();\n const landed = target.ownerDocument.activeElement === target;\n const fieldName = target.closest<HTMLElement>('[data-fs-billing-address-field]')?.dataset.fsBillingAddressField;\n if (landed && fieldName) onFieldFocused(fieldName);\n return landed;\n}\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\nexport const BillingAddressDefinition = defineComponent<BillingAddressProps>({\n name: 'BillingAddress',\n importPath: '@vtex/faststore/components',\n component: BillingAddress,\n requiresContext: 'payment',\n actions: { payment: ['setBillingAddress'] },\n editor: { label: 'Billing Address', category: 'commerce' },\n schema: {\n sameAsDeliveryLabel: {\n type: 'string',\n default: 'Billing address same as delivery address',\n label: 'Same-as-delivery toggle label',\n },\n defaultCountry: { type: 'string', default: 'BRA', label: 'Default country (ISO-3)' },\n countryLabel: { type: 'string', default: 'Country', label: 'Country field label' },\n submitLabel: { type: 'string', default: 'Save billing address', label: 'Submit button label' },\n submitPendingLabel: { type: 'string', default: 'Saving...', label: 'Submit button pending label' },\n requiredNoticeLabel: {\n type: 'string',\n default: 'This payment method requires a billing address.',\n label: 'Required-billing-address notice label',\n },\n unsavedAddressNoticeLabel: {\n type: 'string',\n default: 'No billing address has been saved yet — the delivery address will be used unless you save one below.',\n label: 'Unsaved-address notice label',\n },\n // T73 (specs/render-the-billing-match-disclaimer-beside.md) — requirement 12.20. Always on unless\n // switched off; gated on these two props alone, never on usePayment()/local state/$data (FR-4).\n // Both defaults are INLINE LITERALS (FR-7): the native catalog builder extracts `schema`\n // statically, so an identifier-valued default is dropped from `dist/catalog.json` with no error.\n billingMatchNoticeLabel: {\n type: 'string',\n default:\n 'Your billing address must match the one on your card or bank statement, or your bank may decline the order.',\n label: 'Billing-match disclaimer',\n },\n billingMatchNoticeVisible: { type: 'boolean', default: true, label: 'Show the billing-match disclaimer' },\n // T31 (specs/make-the-payment-components-authored-copy.md, FR-7, Decision 5) — the eight canonical\n // descriptor field names `FIELD_LABELS_EN` used to hardcode, now schema props defaulting to that\n // same map's own values. `recipientNameLabel` is not rendered by the custom-address form today\n // (OMITTED_FORM_FIELD_NAMES) — kept for parity with the other seven.\n recipientNameLabel: { type: 'string', default: \"Recipient's name\", label: 'Recipient name field label' },\n streetLabel: { type: 'string', default: 'Street', label: 'Street field label' },\n numberLabel: { type: 'string', default: 'Number', label: 'Number field label' },\n complementLabel: { type: 'string', default: 'Complement (optional)', label: 'Complement field label' },\n neighborhoodLabel: { type: 'string', default: 'Neighborhood', label: 'Neighborhood field label' },\n cityLabel: { type: 'string', default: 'City', label: 'City field label' },\n stateLabel: { type: 'string', default: 'State', label: 'State field label' },\n postalCodeLabel: { type: 'string', default: 'Postal code', label: 'Postal code field label' },\n // T38: an inline literal, never an identifier — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n fieldRequiredMessage: {\n type: 'string',\n default: 'This field is required.',\n label: 'Field required message',\n },\n // An inline literal as well, for the same reason (INV-6).\n postalCodeInvalidMessage: {\n type: 'string',\n default: 'Enter a valid postal code.',\n label: 'Postal code format message',\n },\n disabled: { type: 'boolean', default: false, label: 'Disabled' },\n },\n guideline:\n \"Same-as-delivery toggle (default on) for the billing address; when off, mounts a country-specific address form (reusing shipping-address's country descriptors) and persists it via $actions.payment.setBillingAddress on an explicit submit control. Re-checking the toggle dispatches sameAsDelivery: true only once an address was actually persisted (otherwise it's a local no-op — there's nothing on the server to revert). Publishes only a boolean (never the address) onto the shared payment draft for usePlaceOrder to read. While the form is open and nothing has been saved yet, renders an inline status notice — the required attribute on the form's own fields is not a submit gate; Place Order can still complete billed to the delivery address if the shopper never saves. Must be inside a PaymentSectionRoot or PaymentSectionController; place as a sibling of CardEntry/SavedCards/Installments/SaveCard/PlaceOrder, never nested inside any of them. See billing-address.md.\",\n});\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function BillingAddress({\n sameAsDeliveryLabel = 'Billing address same as delivery address',\n defaultCountry = 'BRA',\n countryLabel = 'Country',\n submitLabel = 'Save billing address',\n submitPendingLabel = 'Saving...',\n requiredNoticeLabel = 'This payment method requires a billing address.',\n unsavedAddressNoticeLabel = 'No billing address has been saved yet — the delivery address will be used unless you save one below.',\n billingMatchNoticeLabel = 'Your billing address must match the one on your card or bank statement, or your bank may decline the order.',\n billingMatchNoticeVisible = true,\n recipientNameLabel = \"Recipient's name\",\n streetLabel = 'Street',\n numberLabel = 'Number',\n complementLabel = 'Complement (optional)',\n neighborhoodLabel = 'Neighborhood',\n cityLabel = 'City',\n stateLabel = 'State',\n postalCodeLabel = 'Postal code',\n fieldRequiredMessage,\n postalCodeInvalidMessage,\n disabled = false,\n className,\n $actions: $actionsProp,\n ...props\n}: BillingAddressProps) {\n const $actions = useActionsOverride($actionsProp);\n // T31 (FR-7) — replaces the hardcoded `FIELD_LABELS_EN` map: keyed by the descriptor's own canonical\n // field name, same resolution as before (`renderableFields.map` below falls back to the descriptor's\n // own label when a field name has no matching prop; none does today).\n const fieldLabels: Readonly<Record<string, string>> = {\n recipientName: recipientNameLabel,\n street: streetLabel,\n number: numberLabel,\n complement: complementLabel,\n neighborhood: neighborhoodLabel,\n city: cityLabel,\n state: stateLabel,\n postalCode: postalCodeLabel,\n };\n // Also enforces the PaymentSectionRoot/PaymentSectionController ancestor: usePayment() throws\n // when rendered outside one.\n const {\n draft,\n selectedMethod,\n setDraftBillingAddressDifferent,\n setDraftBillingAddressUnsaved,\n placing,\n cardPaymentCollapsed,\n registerRefusalFocus,\n } = usePayment();\n // Fix pass, findings R2/S2: a stable identity for *this* mounted instance, so its cleanup below\n // retracts only its own contribution to the shared `billingAddressUnsaved` fact — never a second\n // instance's still-open, still-unsaved one (billing-address.md documents multiple instances under one\n // root as allowed). `useId()` is stable for the component's lifetime and distinct per instance, which\n // is exactly what a per-instance set key needs; it is never rendered or sent anywhere.\n const instanceKey = useId();\n // T175 (Decision 3): the id the toggle's `aria-controls` names in every state, rendered on the\n // always-mounted panel below it. Its own `useId()`, so `instanceKey` above stays unrendered.\n const panelId = useId();\n\n const [formDraft, setFormDraft] = useState<BillingAddressFormDraft>(() => ({\n postalCode: '',\n // A `defaultCountry` with no descriptor (a typo) falls back to the first available\n // country rather than shipping a dead form with no fields and a permanently-disabled submit\n // (review F7) — `getAvailableCountries()` is never empty in practice (Decision 7), but the raw\n // prop is the last-resort fallback if it ever were, matching today's behavior for that edge case.\n country: getCountryDescriptor(defaultCountry) ? defaultCountry : (getAvailableCountries()[0] ?? defaultCountry),\n street: '',\n number: '',\n complement: '',\n neighborhood: '',\n city: '',\n state: '',\n }));\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n // T38 (FR-6, Decision 8): per-field validity, mirroring card-entry.tsx:1115/:1150. A field joins this\n // set on blur, or when a refused submit moves focus to it (`markTouched`), where blur never comes.\n // Gated on it so a field is never marked invalid before the shopper reaches it (US-3,\n // Risk R5) — wiring `invalid` off `isBillingAddressFormValid` alone would mark every field invalid\n // the instant the form opens.\n const [touched, setTouched] = useState<ReadonlySet<string>>(() => new Set());\n // Local-only: whether the custom-address form is showing. Decoupled from `draft.billingAddressDifferent`\n // on purpose (PR #544 N1) — that published flag is what `gateway.ts` reads to decide whose address to\n // bill, so it must say \"different\" only once an address has actually been persisted differently, never\n // merely because the shopper opened this form. Initialized from the draft (via the same pure\n // `resolveSameAsDelivery` the checkbox used to read directly) so the two never start out of sync.\n const [formOpen, setFormOpen] = useState(() => !resolveSameAsDelivery(draft.billingAddressDifferent));\n\n // Re-syncs `formOpen` whenever the shared draft's `billingAddressDifferent` changes for a reason\n // outside THIS instance's own clicks (PR #544 N8) — e.g. a sibling `BillingAddress` mounted under the\n // same `PaymentSectionRoot` persists or reverts an address through its own copy of this same shared\n // context. A bare local `useState` only ever reflects the instance that called its own setter; without\n // this effect, a second mounted instance keeps showing whatever `formOpen` it computed at its own\n // mount forever, even after `draft.billingAddressDifferent` — the one fact the gateway actually reads\n // — has moved on. Deliberately keyed on the primitive `draft.billingAddressDifferent`, not the `draft`\n // object itself: `draft` is a stable `useMemo` over seven facts (`use-payment-method-selection.ts`),\n // so its identity changes whenever ANY of them does — including `draftBin`, which `CardEntry`\n // publishes on every keystroke of the card number. Keying this effect on the whole object instead\n // would refire it on each of those keystrokes while this form is open and nothing is yet persisted,\n // recomputing `formOpen` from an unchanged `billingAddressDifferent` and closing the form mid-input\n // (review #544 N15 — a dedicated regression test in `billing-address.test.tsx` pins this). This\n // differs from `shipping-address.tsx:645`'s own snapshot-keyed effect, whose reason is Apollo\n // returning a new-but-equal object reference every render (its own `biome-ignore` comment says so) —\n // `draft` here is a local `useMemo`, already stable; the hazard is refiring on an unrelated fact\n // change, not on render churn. Does NOT reintroduce N1: this component's own uncheck transition (below) sets\n // `formOpen` without ever touching the draft, so that transition alone never changes this effect's\n // dependency and never fires it. Deleting this effect is exactly the mutant PR #544's re-verification\n // named V-P2/N8 — the two-instance regression test below fails without it.\n useEffect(() => {\n setFormOpen(!resolveSameAsDelivery(draft.billingAddressDifferent));\n }, [draft.billingAddressDifferent]);\n\n const sameAsDelivery = !formOpen;\n // PR #544 N7: visible only while the shopper is on the custom-address path AND nothing has actually\n // been persisted differently yet — the exact window in which Place Order would silently bill the\n // delivery address. Mirrors the same `draft.billingAddressDifferent !== true` check\n // `handleSameAsDeliveryChange`'s local-no-op branch already uses, so the notice and the no-op branch\n // can never disagree about what \"nothing persisted\" means. T191 (FR-8): never while the card payment\n // is collapsed, when this component renders nothing — the gate below must not refuse Place order over\n // a form the shopper cannot see, and the notice and the gate keep reading one expression.\n const unsavedAddressNoticeVisible = !cardPaymentCollapsed && formOpen && draft.billingAddressDifferent !== true;\n\n // T24's submit gate (specs/cardholder-document-submit-gate.md FR-6): publishes the EXACT same fact\n // the notice above renders from, so the gate and the notice can never disagree (Risk R5). Keyed by\n // this instance's own `instanceKey` (fix pass, findings R2/S2): `billingAddressUnsaved` is one fact\n // shared by every `BillingAddress` mounted under this root, so the cleanup must retract only *this*\n // instance's own contribution on unmount — never a second, still-open, still-unsaved instance's own\n // block (measured: an unconditional publish here left the CTA proceeding while that instance's own\n // notice kept rendering, billing the declined delivery address anyway). Not cosmetics either way: an\n // unmounted `BillingAddress` must not leave the CTA blocked with no form on screen left to fix it\n // (Risk R2) when it was the *only* instance.\n useEffect(() => {\n setDraftBillingAddressUnsaved(instanceKey, unsavedAddressNoticeVisible);\n return () => setDraftBillingAddressUnsaved(instanceKey, false);\n }, [instanceKey, unsavedAddressNoticeVisible, setDraftBillingAddressUnsaved]);\n\n // T212: while the form is open and on screen, a refusal naming the billing form may move focus into it.\n // Registered by the instance that renders the form, so a closed or collapsed instance never takes it.\n const canTakeFocus = !cardPaymentCollapsed && formOpen;\n const draftCountry = formDraft.country;\n const draftDescriptor = useMemo(() => getCountryDescriptor(draftCountry), [draftCountry]);\n const renderableFields = useMemo(\n () => (draftDescriptor ? getRenderableFields(draftDescriptor) : []),\n [draftDescriptor],\n );\n useEffect(() => {\n if (!canTakeFocus) return;\n const markTouched = (name: string) => setTouched((prev) => withTouched(prev, name));\n return registerRefusalFocus('billing-form', () =>\n focusNextBillingControl(document.getElementById(panelId), renderableFields, markTouched),\n );\n }, [canTakeFocus, renderableFields, panelId, registerRefusalFocus]);\n\n const billingAddressRequired = selectedMethod?.validator.billingAddressRequired ?? false;\n const fieldsDisabled = disabled || pending;\n const requiredMessage = resolveAuthoredCopy(fieldRequiredMessage, 'This field is required.');\n const invalidMessage = resolveAuthoredCopy(postalCodeInvalidMessage, 'Enter a valid postal code.');\n\n async function handleSameAsDeliveryChange(nextSameAsDelivery: boolean) {\n setError(null);\n\n if (!nextSameAsDelivery) {\n // Opens the custom-address form locally. Nothing is dispatched and the shared draft is left\n // untouched here — the shopper has typed nothing yet, and publishing `billingAddressDifferent:\n // true` before an address is actually persisted is exactly the false claim review #544's N1\n // measured reaching the gateway in two clicks. Only a successful submit (handleSubmit below) may\n // ever publish `true` — see this file's own top-level doc comment.\n setFormOpen(true);\n return;\n }\n\n if (draft.billingAddressDifferent !== true) {\n // Nothing was ever persisted differently — either the shopper never submitted, or an earlier\n // submit failed — so there is nothing on the server to revert. Closing the form is a local-only\n // action: dispatching `setBillingAddress({ sameAsDelivery: true })` here would be a wasted round\n // trip reverting an address the server was never asked to set in the first place.\n setFormOpen(false);\n return;\n }\n\n const setBillingAddress = $actions?.payment?.setBillingAddress;\n // Silent no-op when the action namespace is not injected, mirroring `SaveCard`'s / `selectMethod`'s\n // \"no-op (no error, no throw)\" convention.\n if (!setBillingAddress) return;\n\n setPending(true);\n try {\n const userErrors = await setBillingAddress({ sameAsDelivery: true });\n const [firstError] = userErrors;\n if (firstError) {\n setError(firstError.message);\n // A refused revert (review F3): the persisted address is still the custom one the shopper had\n // before, so the form stays open (`formOpen` untouched) and the draft keeps claiming\n // \"different\" — neither claims \"same as delivery\" the server never accepted. There is nothing\n // to roll back here, unlike before this pass: the draft is only ever written `false` a few\n // lines below, after `setBillingAddress` has already resolved with no error.\n } else {\n setDraftBillingAddressDifferent(false);\n // Redundant with the resync effect above once this draft write lands (the effect closes the\n // form on its own whenever `draft.billingAddressDifferent` moves to `false` — review #544 N18)\n // — kept anyway so this instance closes synchronously with its own click rather than waiting on\n // a re-render, which costs nothing since both agree on the outcome.\n setFormOpen(false);\n }\n } finally {\n setPending(false);\n }\n }\n\n function handleFieldChange(name: string, value: string) {\n setFormDraft((prev) => ({ ...prev, [name]: value }));\n }\n\n /** Marks a field touched on its first blur (T38, FR-6). Idempotent on every later blur — a `Set`\n * already containing `name` is returned unchanged, so this never triggers a redundant re-render. */\n function handleFieldBlur(name: string) {\n setTouched((prev) => withTouched(prev, name));\n }\n\n function handleCountryChange(value: string | null) {\n // Base UI's `SelectRoot` (`select/root/SelectRoot.js`) only ever calls `onValueChange` with one of\n // `Select.Item`'s own `value`s — all descriptor-backed, non-null ISO codes here (verified by\n // reading its source, not assumed) — so `value` is `null` on no path this component's own `Select`\n // below can reach. The `?? prev.country` fallback exists only to satisfy\n // `BillingAddressFormDraft.country: string`'s type, not as a runtime branch pretending to be tested\n // (review F10's residual, R5′): dropping the fallback (`country: value`) is a `pnpm typecheck`\n // failure, not merely an unproven `if` — a stronger, mechanical guarantee than a guard clause no\n // real interaction could ever exercise.\n //\n // Preserves every other field's value across the switch (Decision 7) — matches\n // `shipping-address`'s own documented behavior: a value typed under one country resurfaces if the\n // shopper switches away and back, even while the field is hidden for the currently-selected one.\n setFormDraft((prev) => ({ ...prev, country: value ?? prev.country }));\n }\n\n async function handleSubmit() {\n const setBillingAddress = $actions?.payment?.setBillingAddress;\n setError(null);\n if (!setBillingAddress) return;\n\n // Only include a field the active country's descriptor actually renders (mirrors\n // `shipping-address.tsx`'s own `isFieldActive` guard at its submit handler) — otherwise a value\n // typed under a previously-selected country (e.g. `neighborhood` retained in the draft after\n // switching BR -> USA) would leak into the submitted payload even though the field was never\n // rendered, editable, or clearable for the country actually being submitted (review F2).\n // `postalCode` and `country` are unconditional: every descriptor declares both.\n const isFieldActive = (name: string) => renderableFields.some((field) => field.name === name);\n\n setPending(true);\n try {\n const userErrors = await setBillingAddress({\n sameAsDelivery: false,\n postalCode: formDraft.postalCode,\n country: formDraft.country,\n ...(isFieldActive('street') ? { street: formDraft.street } : {}),\n ...(isFieldActive('number') ? { number: formDraft.number } : {}),\n ...(isFieldActive('complement') ? { complement: formDraft.complement } : {}),\n ...(isFieldActive('neighborhood') ? { neighborhood: formDraft.neighborhood } : {}),\n ...(isFieldActive('city') ? { city: formDraft.city } : {}),\n ...(isFieldActive('state') ? { state: formDraft.state } : {}),\n });\n const [firstError] = userErrors;\n if (firstError) {\n setError(firstError.message);\n } else {\n // The one place `billingAddressDifferent` may ever become `true` (PR #544 N1) — only once the\n // server actually holds a different address, so the flag means exactly what `gateway.ts`'s own\n // doc comment already claims: \"`setBillingAddress` already persisted it … so the gateway reads\n // it from there.\"\n setDraftBillingAddressDifferent(true);\n }\n } finally {\n setPending(false);\n }\n }\n\n // T191 (FR-7, Decision 3): nothing renders while the card payment is collapsed. Returned after the\n // last hook, so this instance stays mounted and keeps what the shopper typed for when the card is back.\n if (cardPaymentCollapsed) return null;\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-billing-address=\"\"\n {...(placing ? { 'data-fs-billing-address-placing': '' } : {})}\n {...(pending ? { 'data-fs-billing-address-pending': '' } : {})}\n {...(sameAsDelivery ? { 'data-fs-billing-address-same-as-delivery': '' } : {})}\n >\n {billingMatchNoticeVisible && billingMatchNoticeLabel.trim() ? (\n <p data-fs-billing-address-match-notice=\"\">{billingMatchNoticeLabel}</p>\n ) : null}\n <div data-fs-billing-address-toggle-field=\"\">\n <Field.Root disabled={fieldsDisabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root\n checked={sameAsDelivery}\n onCheckedChange={handleSameAsDeliveryChange}\n disabled={fieldsDisabled}\n aria-controls={panelId}\n >\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {sameAsDeliveryLabel}\n </Field.Label>\n </Field.Root>\n {billingAddressRequired ? <p data-fs-billing-address-required-note=\"\">{requiredNoticeLabel}</p> : null}\n </div>\n\n {/* T175 (Decision 3): always mounted, so the toggle's `aria-controls` resolves in every state. It\n takes no className or props, and it is `hidden` only while empty (INV-2): a store's display\n utility can beat `[hidden]`, but then it shows an empty box, never content. */}\n <div id={panelId} data-fs-billing-address-panel=\"\" hidden={sameAsDelivery}>\n {sameAsDelivery ? null : (\n <div data-fs-billing-address-form=\"\">\n {unsavedAddressNoticeVisible ? (\n <p role=\"status\" data-fs-billing-address-unsaved-notice=\"\">\n {unsavedAddressNoticeLabel}\n </p>\n ) : null}\n <Field.Root disabled={fieldsDisabled} data-fs-field-root=\"\" data-fs-billing-address-field=\"country\">\n <Field.Label>{countryLabel}</Field.Label>\n <Select.Root\n value={formDraft.country}\n onValueChange={handleCountryChange}\n disabled={fieldsDisabled}\n required\n >\n <Select.Trigger>\n {/* Resolves the label directly from the country descriptor rather than relying on\n Base UI's automatic item-label lookup, which only reflects a mounted Select.Item —\n mirrors `ShippingAddressCountryControl`'s identical reasoning. English override per\n `getCountryLabel` (review F5) — the Brazilian descriptor's own name is Portuguese. */}\n <Select.Value>{(value) => (value ? getCountryLabel(value) : value)}</Select.Value>\n <Select.Icon>▾</Select.Icon>\n </Select.Trigger>\n <Select.Portal>\n <Select.Positioner>\n <Select.Popup>\n <Select.List>\n {getAvailableCountries().map((iso) => {\n const label = getCountryLabel(iso);\n return (\n <Select.Item key={iso} value={iso} label={label}>\n {label}\n </Select.Item>\n );\n })}\n </Select.List>\n </Select.Popup>\n </Select.Positioner>\n </Select.Portal>\n </Select.Root>\n </Field.Root>\n\n {renderableFields.map((field) => {\n // T38 (FR-6): gated on `touched` (blur, or a refused submit focusing the field) so nothing is\n // marked invalid before the shopper reaches it (US-3, Risk R5). A touched field is invalid once\n // `getFieldIssue` finds an issue with its value: blank (except `complement`) or not in the\n // selected country's format (INV-3/INV-4). `areRenderableFieldsValid` reads the same\n // `getFieldIssue`, so the two are never allowed to disagree about a field.\n const issue = touched.has(field.name) ? getFieldIssue(field, formDraft[field.name] ?? '') : null;\n const invalid = issue !== null;\n return (\n <Field.Root\n key={field.name}\n disabled={fieldsDisabled}\n invalid={invalid}\n data-fs-field-root=\"\"\n data-fs-billing-address-field={field.name}\n >\n {/* Authored field-label prop per `fieldLabels` (T31, FR-7) — the Brazilian descriptor's own\n labels are Portuguese, so every country gets an authored English one. `||`, not `??`\n (fix pass round 2, finding N1 — same defect class as place-order.tsx's finding 7 from\n round 1): a store author can write `\"\"` for a field's authored label — a valid\n `type: 'string'` prop value — and `??` only falls through on `null`/`undefined`, so an\n authored empty string would render an empty accessible name on a control that is\n required for every field but `complement`. Pre-existing hazard, not fixed here: `||`\n still renders blank for a whitespace-only authored label — inside T31's ownership,\n reported not fixed (T38 § 5). */}\n <Field.Label>{fieldLabels[field.name] || field.label}</Field.Label>\n <Field.Control\n value={formDraft[field.name] ?? ''}\n onChange={(event) => handleFieldChange(field.name, event.currentTarget.value)}\n onBlur={() => handleFieldBlur(field.name)}\n disabled={fieldsDisabled}\n // Every rendered field is required once \"different\" is selected, except `complement`\n // (`OPTIONAL_FORM_FIELD_NAMES`) — PR #544 N1 item 1. Not a like-for-like precedent with\n // `CardholderDocument`'s own `required={documentRequired}`: that one forwards to a\n // particle with its own declared prop, a root state hook, and a paired `Field.Error`\n // driven by a server-sourced validator flag, where this sets `required` directly off a\n // locally hardcoded set instead (full comparison: specs/billing-address.md Accessibility\n // Contract).\n required={!OPTIONAL_FORM_FIELD_NAMES.has(field.name)}\n />\n {invalid ? (\n <Field.Error match>\n {issue === 'format' ? getFieldFormatMessage(field, invalidMessage) : requiredMessage}\n </Field.Error>\n ) : null}\n </Field.Root>\n );\n })}\n\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={fieldsDisabled || !canSaveBillingDraft(draftDescriptor, renderableFields, formDraft)}\n data-fs-billing-address-submit=\"\"\n >\n {pending ? submitPendingLabel : submitLabel}\n </button>\n </div>\n )}\n </div>\n\n {error ? (\n <div role=\"alert\" data-fs-billing-address-error=\"\">\n {error}\n </div>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CartAlertIcon, CartAlertRoot, CartAlertText, CartAppliedCoupons, CartCheckoutGuard, CartContinueShopping, CartCount, CartCouponError, CartCouponForm, CartCouponRemove, CartDisclaimer, CartEmpty, CartEmptyMessage, CartError, CartFilled, CartGoCheckout, CartItemSkeleton, CartList, CartLoading, CartOpenOnAdd, CartRedirectOnAdd, CartRemoveUnavailable, CartRoot, CartTotal, CartUnavailableCount } from "./cart.js";
|
|
1
|
+
import { CartAlertIcon, CartAlertRoot, CartAlertText, CartAppliedCoupons, CartCheckoutGuard, CartContinueShopping, CartCount, CartCouponEmpty, CartCouponError, CartCouponForm, CartCouponRemove, CartCouponSavings, CartCouponUnmatched, CartDisclaimer, CartEmpty, CartEmptyMessage, CartError, CartFilled, CartGoCheckout, CartItemSkeleton, CartList, CartLoading, CartOpenOnAdd, CartRedirectOnAdd, CartRemoveUnavailable, CartRoot, CartTotal, CartUnavailableCount, ClearCartButton } from "./cart.js";
|
|
2
2
|
import { CartItemAvailable, CartItemDiscountPercentage, CartItemGift, CartItemImage, CartItemLineTotal, CartItemListPrice, CartItemName, CartItemPrice, CartItemProductLink, CartItemQuantity, CartItemQuantityValue, CartItemRemove, CartItemRoot, CartItemSku, CartItemUnavailable, CartItemVariant } from "../cart-item/cart-item.js";
|
|
3
3
|
//#region src/matter/cart/cart-namespace.d.ts
|
|
4
4
|
export declare const Cart: {
|
|
@@ -28,6 +28,7 @@ export declare const Cart: {
|
|
|
28
28
|
CheckoutGuard: typeof CartCheckoutGuard;
|
|
29
29
|
UnavailableCount: typeof CartUnavailableCount;
|
|
30
30
|
RemoveUnavailable: typeof CartRemoveUnavailable;
|
|
31
|
+
ClearCart: typeof ClearCartButton;
|
|
31
32
|
OpenOnAdd: typeof CartOpenOnAdd;
|
|
32
33
|
RedirectOnAdd: typeof CartRedirectOnAdd;
|
|
33
34
|
Loading: typeof CartLoading;
|
|
@@ -40,6 +41,9 @@ export declare const Cart: {
|
|
|
40
41
|
AppliedCoupons: typeof CartAppliedCoupons;
|
|
41
42
|
CouponRemove: typeof CartCouponRemove;
|
|
42
43
|
CouponError: typeof CartCouponError;
|
|
44
|
+
CouponEmpty: typeof CartCouponEmpty;
|
|
45
|
+
CouponSavings: typeof CartCouponSavings;
|
|
46
|
+
CouponUnmatched: typeof CartCouponUnmatched;
|
|
43
47
|
AlertRoot: typeof CartAlertRoot;
|
|
44
48
|
AlertIcon: typeof CartAlertIcon;
|
|
45
49
|
AlertText: typeof CartAlertText;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CartAlertIcon, CartAlertRoot, CartAlertText, CartAppliedCoupons, CartCheckoutGuard, CartContinueShopping, CartCount, CartCouponError, CartCouponForm, CartCouponRemove, CartDisclaimer, CartEmpty, CartEmptyMessage, CartError, CartFilled, CartGoCheckout, CartItemSkeleton, CartList, CartLoading, CartOpenOnAdd, CartRedirectOnAdd, CartRemoveUnavailable, CartRoot, CartTotal, CartUnavailableCount } from "./cart.js";
|
|
1
|
+
import { CartAlertIcon, CartAlertRoot, CartAlertText, CartAppliedCoupons, CartCheckoutGuard, CartContinueShopping, CartCount, CartCouponEmpty, CartCouponError, CartCouponForm, CartCouponRemove, CartCouponSavings, CartCouponUnmatched, CartDisclaimer, CartEmpty, CartEmptyMessage, CartError, CartFilled, CartGoCheckout, CartItemSkeleton, CartList, CartLoading, CartOpenOnAdd, CartRedirectOnAdd, CartRemoveUnavailable, CartRoot, CartTotal, CartUnavailableCount, ClearCartButton } from "./cart.js";
|
|
2
2
|
import { CartItemAvailable, CartItemDiscountPercentage, CartItemGift, CartItemImage, CartItemLineTotal, CartItemListPrice, CartItemName, CartItemPrice, CartItemProductLink, CartItemQuantity, CartItemQuantityValue, CartItemRemove, CartItemRoot, CartItemSku, CartItemUnavailable, CartItemVariant } from "../cart-item/cart-item.js";
|
|
3
3
|
//#region src/matter/cart/cart-namespace.ts
|
|
4
4
|
const Cart = {
|
|
@@ -28,6 +28,7 @@ const Cart = {
|
|
|
28
28
|
CheckoutGuard: CartCheckoutGuard,
|
|
29
29
|
UnavailableCount: CartUnavailableCount,
|
|
30
30
|
RemoveUnavailable: CartRemoveUnavailable,
|
|
31
|
+
ClearCart: ClearCartButton,
|
|
31
32
|
OpenOnAdd: CartOpenOnAdd,
|
|
32
33
|
RedirectOnAdd: CartRedirectOnAdd,
|
|
33
34
|
Loading: CartLoading,
|
|
@@ -40,6 +41,9 @@ const Cart = {
|
|
|
40
41
|
AppliedCoupons: CartAppliedCoupons,
|
|
41
42
|
CouponRemove: CartCouponRemove,
|
|
42
43
|
CouponError: CartCouponError,
|
|
44
|
+
CouponEmpty: CartCouponEmpty,
|
|
45
|
+
CouponSavings: CartCouponSavings,
|
|
46
|
+
CouponUnmatched: CartCouponUnmatched,
|
|
43
47
|
AlertRoot: CartAlertRoot,
|
|
44
48
|
AlertIcon: CartAlertIcon,
|
|
45
49
|
AlertText: CartAlertText
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cart-namespace.js","names":[],"sources":["../../../src/matter/cart/cart-namespace.ts"],"sourcesContent":["import {\n CartItemAvailable,\n CartItemDiscountPercentage,\n CartItemGift,\n CartItemImage,\n CartItemLineTotal,\n CartItemListPrice,\n CartItemName,\n CartItemPrice,\n CartItemProductLink,\n CartItemQuantity,\n CartItemQuantityValue,\n CartItemRemove,\n CartItemRoot,\n CartItemSku,\n CartItemUnavailable,\n CartItemVariant,\n} from '../cart-item/cart-item.js';\nimport {\n CartAlertIcon,\n CartAlertRoot,\n CartAlertText,\n CartAppliedCoupons,\n CartCheckoutGuard,\n CartContinueShopping,\n CartCount,\n CartCouponError,\n CartCouponForm,\n CartCouponRemove,\n CartDisclaimer,\n CartEmpty,\n CartEmptyMessage,\n CartError,\n CartFilled,\n CartGoCheckout,\n CartItemSkeleton,\n CartList,\n CartLoading,\n CartOpenOnAdd,\n CartRedirectOnAdd,\n CartRemoveUnavailable,\n CartRoot,\n CartTotal,\n CartUnavailableCount,\n} from './cart.js';\n\n// The `Cart.*` namespace combines the aggregate components (matter/cart) with the line-item\n// components (matter/cart-item). It lives in its own module so neither matter file imports the\n// other, keeping the read root and its per-item line template decoupled (ADR-0012 Option B).\nexport const Cart = {\n Root: CartRoot,\n List: CartList,\n ItemRoot: CartItemRoot,\n ItemImage: CartItemImage,\n ItemProductLink: CartItemProductLink,\n ItemName: CartItemName,\n ItemVariant: CartItemVariant,\n ItemSku: CartItemSku,\n ItemPrice: CartItemPrice,\n ItemListPrice: CartItemListPrice,\n ItemDiscountPercentage: CartItemDiscountPercentage,\n ItemLineTotal: CartItemLineTotal,\n ItemQuantity: CartItemQuantity,\n ItemQuantityValue: CartItemQuantityValue,\n ItemRemove: CartItemRemove,\n ItemAvailable: CartItemAvailable,\n ItemUnavailable: CartItemUnavailable,\n ItemGift: CartItemGift,\n ItemSkeleton: CartItemSkeleton,\n Total: CartTotal,\n Count: CartCount,\n Error: CartError,\n GoCheckout: CartGoCheckout,\n CheckoutGuard: CartCheckoutGuard,\n UnavailableCount: CartUnavailableCount,\n RemoveUnavailable: CartRemoveUnavailable,\n OpenOnAdd: CartOpenOnAdd,\n RedirectOnAdd: CartRedirectOnAdd,\n Loading: CartLoading,\n Empty: CartEmpty,\n EmptyMessage: CartEmptyMessage,\n ContinueShopping: CartContinueShopping,\n Filled: CartFilled,\n Disclaimer: CartDisclaimer,\n CouponForm: CartCouponForm,\n AppliedCoupons: CartAppliedCoupons,\n CouponRemove: CartCouponRemove,\n CouponError: CartCouponError,\n AlertRoot: CartAlertRoot,\n AlertIcon: CartAlertIcon,\n AlertText: CartAlertText,\n};\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"cart-namespace.js","names":[],"sources":["../../../src/matter/cart/cart-namespace.ts"],"sourcesContent":["import {\n CartItemAvailable,\n CartItemDiscountPercentage,\n CartItemGift,\n CartItemImage,\n CartItemLineTotal,\n CartItemListPrice,\n CartItemName,\n CartItemPrice,\n CartItemProductLink,\n CartItemQuantity,\n CartItemQuantityValue,\n CartItemRemove,\n CartItemRoot,\n CartItemSku,\n CartItemUnavailable,\n CartItemVariant,\n} from '../cart-item/cart-item.js';\nimport {\n CartAlertIcon,\n CartAlertRoot,\n CartAlertText,\n CartAppliedCoupons,\n CartCheckoutGuard,\n CartContinueShopping,\n CartCount,\n CartCouponEmpty,\n CartCouponError,\n CartCouponForm,\n CartCouponRemove,\n CartCouponSavings,\n CartCouponUnmatched,\n CartDisclaimer,\n CartEmpty,\n CartEmptyMessage,\n CartError,\n CartFilled,\n CartGoCheckout,\n CartItemSkeleton,\n CartList,\n CartLoading,\n CartOpenOnAdd,\n CartRedirectOnAdd,\n CartRemoveUnavailable,\n CartRoot,\n CartTotal,\n CartUnavailableCount,\n ClearCartButton,\n} from './cart.js';\n\n// The `Cart.*` namespace combines the aggregate components (matter/cart) with the line-item\n// components (matter/cart-item). It lives in its own module so neither matter file imports the\n// other, keeping the read root and its per-item line template decoupled (ADR-0012 Option B).\nexport const Cart = {\n Root: CartRoot,\n List: CartList,\n ItemRoot: CartItemRoot,\n ItemImage: CartItemImage,\n ItemProductLink: CartItemProductLink,\n ItemName: CartItemName,\n ItemVariant: CartItemVariant,\n ItemSku: CartItemSku,\n ItemPrice: CartItemPrice,\n ItemListPrice: CartItemListPrice,\n ItemDiscountPercentage: CartItemDiscountPercentage,\n ItemLineTotal: CartItemLineTotal,\n ItemQuantity: CartItemQuantity,\n ItemQuantityValue: CartItemQuantityValue,\n ItemRemove: CartItemRemove,\n ItemAvailable: CartItemAvailable,\n ItemUnavailable: CartItemUnavailable,\n ItemGift: CartItemGift,\n ItemSkeleton: CartItemSkeleton,\n Total: CartTotal,\n Count: CartCount,\n Error: CartError,\n GoCheckout: CartGoCheckout,\n CheckoutGuard: CartCheckoutGuard,\n UnavailableCount: CartUnavailableCount,\n RemoveUnavailable: CartRemoveUnavailable,\n ClearCart: ClearCartButton,\n OpenOnAdd: CartOpenOnAdd,\n RedirectOnAdd: CartRedirectOnAdd,\n Loading: CartLoading,\n Empty: CartEmpty,\n EmptyMessage: CartEmptyMessage,\n ContinueShopping: CartContinueShopping,\n Filled: CartFilled,\n Disclaimer: CartDisclaimer,\n CouponForm: CartCouponForm,\n AppliedCoupons: CartAppliedCoupons,\n CouponRemove: CartCouponRemove,\n CouponError: CartCouponError,\n CouponEmpty: CartCouponEmpty,\n CouponSavings: CartCouponSavings,\n CouponUnmatched: CartCouponUnmatched,\n AlertRoot: CartAlertRoot,\n AlertIcon: CartAlertIcon,\n AlertText: CartAlertText,\n};\n"],"mappings":";;;AAqDA,MAAa,OAAO;CAClB,MAAM;CACN,MAAM;CACN,UAAU;CACV,WAAW;CACX,iBAAiB;CACjB,UAAU;CACV,aAAa;CACb,SAAS;CACT,WAAW;CACX,eAAe;CACf,wBAAwB;CACxB,eAAe;CACf,cAAc;CACd,mBAAmB;CACnB,YAAY;CACZ,eAAe;CACf,iBAAiB;CACjB,UAAU;CACV,cAAc;CACd,OAAO;CACP,OAAO;CACP,OAAO;CACP,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,mBAAmB;CACnB,WAAW;CACX,WAAW;CACX,eAAe;CACf,SAAS;CACT,OAAO;CACP,cAAc;CACd,kBAAkB;CAClB,QAAQ;CACR,YAAY;CACZ,YAAY;CACZ,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,aAAa;CACb,eAAe;CACf,iBAAiB;CACjB,WAAW;CACX,WAAW;CACX,WAAW;AACb"}
|