@vtex/faststore 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +11 -5
  3. package/dist/catalog.json +556 -265
  4. package/dist/components.d.ts +3 -1
  5. package/dist/components.js +5 -3
  6. package/dist/content-types.json +16 -0
  7. package/dist/core/dist/actions/registry.d.ts +1 -1
  8. package/dist/core/dist/actions/registry.js +1 -1
  9. package/dist/core/dist/actions/registry.js.map +1 -1
  10. package/dist/docs/cart.md +2 -2
  11. package/dist/docs/my-account-profile.md +208 -0
  12. package/dist/docs/session.md +12 -6
  13. package/dist/docs/sign-in-redirect.md +137 -0
  14. package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +2 -2
  15. package/dist/matter/access-code-sign-in/access-code-sign-in.js +5 -2
  16. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  17. package/dist/matter/buy-button/buy-button.d.ts +1 -1
  18. package/dist/matter/buy-button/buy-button.js +3 -1
  19. package/dist/matter/buy-button/buy-button.js.map +1 -1
  20. package/dist/matter/cart/cart.d.ts +6 -6
  21. package/dist/matter/cart/cart.js +13 -6
  22. package/dist/matter/cart/cart.js.map +1 -1
  23. package/dist/matter/cart-item/cart-item.d.ts +2 -2
  24. package/dist/matter/cart-item/cart-item.js +5 -2
  25. package/dist/matter/cart-item/cart-item.js.map +1 -1
  26. package/dist/matter/client-preferences/client-preferences.js +3 -1
  27. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  28. package/dist/matter/client-profile/client-profile.d.ts +1 -1
  29. package/dist/matter/client-profile/client-profile.js +3 -1
  30. package/dist/matter/client-profile/client-profile.js.map +1 -1
  31. package/dist/matter/client-profile-form/client-profile-form.d.ts +1 -1
  32. package/dist/matter/client-profile-form/client-profile-form.js +3 -1
  33. package/dist/matter/client-profile-form/client-profile-form.js.map +1 -1
  34. package/dist/matter/delivery-options/delivery-options-accordion.d.ts +1 -1
  35. package/dist/matter/delivery-options/delivery-options-accordion.js +3 -1
  36. package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
  37. package/dist/matter/delivery-options/delivery-options-controller.d.ts +1 -1
  38. package/dist/matter/delivery-options/delivery-options-controller.js +3 -1
  39. package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
  40. package/dist/matter/delivery-options/delivery-options.d.ts +1 -1
  41. package/dist/matter/delivery-options/delivery-options.js +3 -1
  42. package/dist/matter/delivery-options/delivery-options.js.map +1 -1
  43. package/dist/matter/email-identification/email-identification.d.ts +1 -1
  44. package/dist/matter/email-identification/email-identification.js +3 -1
  45. package/dist/matter/email-identification/email-identification.js.map +1 -1
  46. package/dist/matter/identity-summary/identity-summary.d.ts +1 -1
  47. package/dist/matter/identity-summary/identity-summary.js +3 -1
  48. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  49. package/dist/matter/my-account-profile/my-account-profile.d.ts +291 -0
  50. package/dist/matter/my-account-profile/my-account-profile.js +856 -0
  51. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -0
  52. package/dist/matter/product-list/product-list.d.ts +1 -1
  53. package/dist/matter/product-list/product-list.js +3 -1
  54. package/dist/matter/product-list/product-list.js.map +1 -1
  55. package/dist/matter/region/region.d.ts +1 -1
  56. package/dist/matter/region/region.js +5 -2
  57. package/dist/matter/region/region.js.map +1 -1
  58. package/dist/matter/session/session.js +1 -1
  59. package/dist/matter/session/session.js.map +1 -1
  60. package/dist/matter/shipping-address/shipping-address.d.ts +1 -1
  61. package/dist/matter/shipping-address/shipping-address.js +4 -2
  62. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  63. package/dist/matter/sign-in-form/sign-in-form.d.ts +1 -1
  64. package/dist/matter/sign-in-form/sign-in-form.js +3 -1
  65. package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
  66. package/dist/matter/sign-in-redirect/sign-in-redirect.d.ts +45 -0
  67. package/dist/matter/sign-in-redirect/sign-in-redirect.js +127 -0
  68. package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -0
  69. package/dist/sdk/actions/actions-override-context.js +37 -0
  70. package/dist/sdk/actions/actions-override-context.js.map +1 -0
  71. package/dist/sdk/actions/cart-action-provider.d.ts +8 -6
  72. package/dist/sdk/actions/cart-action-provider.js +10 -8
  73. package/dist/sdk/actions/cart-action-provider.js.map +1 -1
  74. package/dist/sdk/actions/identity-action-provider.d.ts +2 -2
  75. package/dist/sdk/actions/identity-action-provider.js +4 -4
  76. package/dist/sdk/actions/identity-action-provider.js.map +1 -1
  77. package/dist/sdk/actions/inject-actions.d.ts +1 -5
  78. package/dist/sdk/actions/inject-actions.js +23 -21
  79. package/dist/sdk/actions/inject-actions.js.map +1 -1
  80. package/dist/sdk/actions/region-action-provider.d.ts +4 -3
  81. package/dist/sdk/actions/region-action-provider.js +6 -5
  82. package/dist/sdk/actions/region-action-provider.js.map +1 -1
  83. package/dist/sdk/actions/session-action-provider.d.ts +4 -3
  84. package/dist/sdk/actions/session-action-provider.js +6 -5
  85. package/dist/sdk/actions/session-action-provider.js.map +1 -1
  86. package/dist/sdk/actions/shipping-action-provider.d.ts +4 -3
  87. package/dist/sdk/actions/shipping-action-provider.js +6 -5
  88. package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
  89. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  90. package/dist/sdk/client/create-client.js +24 -2
  91. package/dist/sdk/client/create-client.js.map +1 -1
  92. package/dist/sdk/content/create-content-runtime.js +1 -1
  93. package/dist/utils/safe-href.js +74 -4
  94. package/dist/utils/safe-href.js.map +1 -1
  95. package/package.json +1 -18
@@ -1 +1 @@
1
- {"version":3,"file":"cart-item.js","names":["BaseNumberField"],"sources":["../../../src/matter/cart-item/cart-item.tsx"],"sourcesContent":["'use client';\n\nimport { NumberField as BaseNumberField } from '@base-ui/react/number-field';\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, type ReactNode, useContext, useState } from 'react';\nimport { NavigationLink, type NavigationLinkProps } from '../../particles/navigation-link/navigation-link.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport type { CartItemEntity } from '../../sdk/cart/operations.js';\nimport { useStorefrontContext } from '../../sdk/context/storefront-context.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport { resolveSafeHref } from '../../utils/safe-href.js';\nimport { formatPrice } from '../cart/cart.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type CartItemRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Per-line slice delivered by the `reactive-list-item` runtime (`ReactiveListItemDataProvider`) —\n * the resolved `CartItem` entity read from the normalized cache via `useFragment(CartItem:<id>)`.\n * Injected through the iteration context in a compiled page; the `$data` prop is retained for test\n * injection.\n */\n $data?: CartItemEntity | null;\n};\n\nexport type CartItemImageProps = ComponentPropsWithRef<'img'> & {\n objectFit?: 'contain' | 'cover' | 'fill' | 'none';\n};\n\n// The destination is the line's own product page, so `href` is not authorable. `$content` goes with it\n// because the leaf declares no content model: a product URL is commerce data, not copy. `target` goes\n// too, so the same-tab rule is enforced by the type rather than merely absent from the schema — an\n// image-only link cannot carry the \"(opens in new tab)\" cue a `_blank` destination needs.\nexport type CartItemProductLinkProps = Omit<NavigationLinkProps, 'href' | '$content' | 'target'>;\n\nexport type CartItemNameProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemVariantProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemSkuProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemPriceProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemListPriceProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemDiscountPercentageProps = ComponentPropsWithRef<'span'> & {\n template?: string;\n};\n\nexport type CartItemLineTotalProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemOwnTotalProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemAggregateTotalProps = ComponentPropsWithRef<'span'>;\n\n/**\n * Per-line slice delivered by the `reactive-list-item` runtime: the value the wrapper publishes\n * through `DataOverrideContext`, read via `useDataOverride`. Only the field the stepper converts with\n * is typed; the resolved cache slice carries the full fragment selection.\n */\nexport type CartItemQuantitySlice = {\n variant: { unitMultiplier: number } | null;\n};\n\nexport type CartItemQuantityProps = Omit<ComponentPropsWithRef<'div'>, 'defaultValue' | 'value'> & {\n /** Minimum, in purchasable units — not in the unit shown to the shopper. */\n min?: number;\n /** Maximum, in purchasable units. Unbounded when unset. */\n max?: number;\n /** Step, in purchasable units: one step moves the display by one unit multiplier. */\n step?: number;\n removeOnDecrement?: boolean;\n /** Snap-notice template; `{value}` is replaced with the corrected displayed amount. */\n validationMessage?: string;\n children?: ReactNode;\n /** Per-line slice from the reactive-list-item runtime; injected/overridden by the wrapper. */\n $data?: CartItemQuantitySlice | null;\n /** Injected by the compiler from `actions: { cart: [...] }`; the cart write surface. */\n $actions?: ComponentActions;\n};\n\nexport type CartItemQuantityValueProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemRemoveProps = ComponentPropsWithRef<'button'> & {\n /** Injected by the compiler from `actions: { cart: ['removeItem'] }`; the cart write surface. */\n $actions?: ComponentActions;\n};\n\nexport type CartItemAvailableProps = ComponentPropsWithRef<'div'>;\n\nexport type CartItemUnavailableProps = ComponentPropsWithRef<'div'>;\n\nexport type CartItemGiftProps = ComponentPropsWithRef<'span'>;\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — typed via @graphql-codegen client-preset (T7)\n//\n// CartItemRoot owns the core-scalar slice of each cart line (ADR-0012 Option B). Fields are inlined\n// (no `...` spread) so the compiler's list-field derivation finds the iterated `items` list by its\n// direct child field and re-roots this fragment to `CartItemRootData_CartItem on CartItem` for the\n// per-line `useFragment` subscription. `variant.availability` stays here — it is a cross-cutting core\n// scalar (CartCheckoutGuard, CartItemAvailable/Unavailable read it), not an optional display leaf.\n// `gift` stays here for the same reason: CartItemGift, CartItemRemove's suppression, and the row's\n// own state hook all read it, so one selection serves three consumers.\n//\n// Every field selected here MUST also be selected by the mutation fragment (`CartItemFragment` in\n// sdk/cart/operations.ts). ReactiveListItemDataProvider resolves `complete ? data : fallbackData`,\n// so a field present here but missing there makes every line incomplete after a cart write and\n// degrades the whole list to the cart-level slice, which carries no product or prices.\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment CartItemRootData on Query {\n cart {\n items {\n __typename\n id\n quantity\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n sellingPrice {\n amount\n currencyCode\n }\n gift\n ownTotal {\n ...MoneyFragment\n }\n total {\n ...MoneyFragment\n }\n variant {\n __typename\n variantId\n sku\n name\n image {\n url\n altText\n }\n availability\n }\n product {\n __typename\n productId\n title\n detailUrl\n categories {\n categoryId\n name\n }\n brand {\n brandId\n name\n }\n }\n }\n }\n }\n`);\n\n// ADR-0011: the unit multiplier is optional per-line data owned by the stepper that converts with\n// it, not by the core root — a cart line showing a read-only quantity never fetches it. Fields are\n// inlined (no `...` spread) so the derived per-item fragment is self-contained for `useFragment`, and\n// it drills the identical cart { items { ... } } path as the other per-item matters so it merges\n// un-aliased (ADR-0012).\nconst cartItemQuantityFragment = graphql(`\n fragment CartItemQuantityData on Query {\n cart {\n items {\n __typename\n id\n variant {\n __typename\n unitMultiplier\n }\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Cart item context — provided by CartItemRoot with its resolved per-line slice\n// ---------------------------------------------------------------------------\n\nconst CartItemContext = createContext<CartItemEntity | null>(null);\n\nfunction useCartItem(): CartItemEntity {\n const item = useContext(CartItemContext);\n if (!item) {\n throw new Error('Cart item components must be used inside a <CartItemRoot> subtree.');\n }\n return item;\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const CartItemRootDefinition = defineComponent<CartItemRootProps>({\n name: 'CartItemRoot',\n importPath: '@vtex/faststore/components',\n component: CartItemRoot,\n fragment,\n fetchStrategy: 'reactive-list-item',\n // Placement: requires CartList's `cart-item` iteration context and provides `cart-item-data` to the\n // core particles below. The two tokens differ, so it does not self-satisfy — the context-constraints\n // walk checks `requiresContext` before pushing this node's own `providesContext`.\n requiresContext: 'cart-item',\n providesContext: 'cart-item-data',\n editor: { label: 'Cart Item Root', category: 'commerce' },\n schema: {},\n guideline:\n 'Per-line data provider for the cart line-item template: place it directly inside CartList, wrapping the CartItem* particles (CartItemImage, CartItemName, CartItemPrice, the CartItemQuantity stepper, CartItemRemove, …). It owns the core per-item fields and delivers each line its own slice, so the core particles must sit inside it. Must be inside CartList. See cart-item.md.',\n});\n\nexport const CartItemImageDefinition = defineComponent<CartItemImageProps>({\n name: 'CartItemImage',\n importPath: '@vtex/faststore/components',\n component: CartItemImage,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Image', category: 'commerce' },\n schema: {\n objectFit: { type: ['contain', 'cover', 'fill', 'none'] as const, default: 'contain', label: 'Object Fit' },\n },\n guideline: 'Renders the product image from the cart item. Must be inside CartItemRoot.',\n});\n\nexport const CartItemProductLinkDefinition = defineComponent<CartItemProductLinkProps>({\n name: 'CartItemProductLink',\n importPath: '@vtex/faststore/components',\n component: CartItemProductLink,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Product Link', category: 'commerce' },\n schema: {},\n guideline:\n 'Links the cart line image to its product page, using the product URL the line already carries: place it inside CartItemRoot wrapping CartItemImage. Whether a line links is decided by placing this element, so there is no prop to toggle it — a checkout line simply omits it. Renders its children unwrapped when the line has no image or no usable product URL, so it never leaves an empty or unsafe link. Navigation is same-tab, and the leaf adds no fields to the page query. Must be inside CartItemRoot. See cart-item.md.',\n});\n\nexport const CartItemNameDefinition = defineComponent<CartItemNameProps>({\n name: 'CartItemName',\n importPath: '@vtex/faststore/components',\n component: CartItemName,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Name', category: 'commerce' },\n schema: {},\n guideline: 'Renders the product name from the cart item. Must be inside CartItemRoot.',\n});\n\nexport const CartItemVariantDefinition = defineComponent<CartItemVariantProps>({\n name: 'CartItemVariant',\n importPath: '@vtex/faststore/components',\n component: CartItemVariant,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Variant', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the selected variant name of the cart item (e.g. \"Midnight Black\"); renders null when the variant has no name. Use as a subtitle under CartItemName, typically alongside CartItemSku. Must be inside CartItemRoot.',\n});\n\nexport const CartItemSkuDefinition = defineComponent<CartItemSkuProps>({\n name: 'CartItemSku',\n importPath: '@vtex/faststore/components',\n component: CartItemSku,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item SKU', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the SKU code of the cart item variant (e.g. \"WNC-400X\"); renders null when the variant has no SKU. Use as a subtitle under CartItemName, typically alongside CartItemVariant. Must be inside CartItemRoot.',\n});\n\nexport const CartItemPriceDefinition = defineComponent<CartItemPriceProps>({\n name: 'CartItemPrice',\n importPath: '@vtex/faststore/components',\n component: CartItemPrice,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Price', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the selling price of the cart item, formatted from the active storefront context. Must be inside CartItemRoot.',\n});\n\nexport const CartItemListPriceDefinition = defineComponent<CartItemListPriceProps>({\n name: 'CartItemListPrice',\n importPath: '@vtex/faststore/components',\n component: CartItemListPrice,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item List Price', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the original list price of the cart item (strikethrough); renders null when no discount is active. Use alongside CartItemPrice to show promotional pricing. Must be inside CartItemRoot.',\n});\n\nexport const CartItemDiscountPercentageDefinition = defineComponent<CartItemDiscountPercentageProps>({\n name: 'CartItemDiscountPercentage',\n importPath: '@vtex/faststore/components',\n component: CartItemDiscountPercentage,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Discount %', category: 'commerce' },\n schema: {\n template: { type: 'string', default: '-{percentage}%', label: 'Template' },\n },\n guideline:\n 'Per-line discount badge showing the integer listPrice-vs-sellingPrice percentage (e.g. \"-20%\"); use the template prop to customize the format ({percentage} is the integer percent). Renders null when the line has no discount (sellingPrice >= listPrice), so it only appears on promotional lines. Place alongside CartItemPrice/CartItemListPrice. Must be inside CartItemRoot.',\n});\n\nexport const CartItemLineTotalDefinition = defineComponent<CartItemLineTotalProps>({\n name: 'CartItemLineTotal',\n importPath: '@vtex/faststore/components',\n component: CartItemLineTotal,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Line Total', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the per-line total (selling price × quantity) formatted from the active storefront context. Must be inside CartItemRoot.',\n});\n\nexport const CartItemOwnTotalDefinition = defineComponent<CartItemOwnTotalProps>({\n name: 'CartItemOwnTotal',\n importPath: '@vtex/faststore/components',\n component: CartItemOwnTotal,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Own Total', category: 'commerce' },\n schema: {},\n guideline:\n \"Renders the line's own total (excludes bundle/composition children), formatted from the active storefront context. Equals CartItemAggregateTotal on lines with no compositions. Must be inside CartItemRoot. See cart-item.md.\",\n});\n\nexport const CartItemAggregateTotalDefinition = defineComponent<CartItemAggregateTotalProps>({\n name: 'CartItemAggregateTotal',\n importPath: '@vtex/faststore/components',\n component: CartItemAggregateTotal,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Aggregate Total', category: 'commerce' },\n schema: {},\n guideline:\n \"Renders the line's aggregate total including all nested composition children, formatted from the active storefront context — the price the shopper actually pays for a bundled line. Equals CartItemOwnTotal on lines with no compositions. Must be inside CartItemRoot. See cart-item.md.\",\n});\n\nexport const CartItemQuantityDefinition = defineComponent<CartItemQuantityProps>({\n name: 'CartItemQuantity',\n importPath: '@vtex/faststore/components',\n component: CartItemQuantity,\n fragment: cartItemQuantityFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Quantity', category: 'commerce' },\n schema: {\n min: { type: 'number', default: 1, label: 'Min' },\n max: { type: 'number', label: 'Max' },\n step: { type: 'number', default: 1, label: 'Step' },\n removeOnDecrement: { type: 'boolean', default: false, label: 'Remove On Decrement' },\n validationMessage: { type: 'string', default: 'Quantity adjusted to {value}.', label: 'Validation Message' },\n },\n actions: { cart: ['increaseQuantity', 'decreaseQuantity', 'removeItem'] },\n guideline:\n \"Quantity stepper for a cart item, shown in the unit the merchant sells in: the displayed amount is the line's quantity times its unit multiplier, so a SKU sold in 500 g portions reads 1.5 rather than 3. min, max, and step are configured in purchasable units and converted for display, so one template serves SKUs with different multipliers. A typed amount that is out of bounds or off the multiplier grid snaps to the nearest valid value on commit and renders validationMessage. Set removeOnDecrement=true to remove the item when quantity reaches the minimum. Renders nothing on a gift line — a gift's quantity is promotion-controlled and cannot be stepped or removed, so use CartItemQuantityValue to show a gift's fixed quantity. Must be inside CartItemRoot. See cart-item.md.\",\n});\n\nexport const CartItemQuantityValueDefinition = defineComponent<CartItemQuantityValueProps>({\n name: 'CartItemQuantityValue',\n importPath: '@vtex/faststore/components',\n component: CartItemQuantityValue,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Quantity Value', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the read-only quantity count of the cart item as text (e.g. a \"Qty\" badge). Distinct from the editable CartItemQuantity stepper. Must be inside CartItemRoot.',\n});\n\nexport const CartItemRemoveDefinition = defineComponent<CartItemRemoveProps>({\n name: 'CartItemRemove',\n importPath: '@vtex/faststore/components',\n component: CartItemRemove,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Remove', category: 'commerce' },\n schema: {},\n actions: { cart: ['removeItem'] },\n guideline:\n 'Button that removes the current line item from the cart. Must be inside CartItemRoot. Also composes inside CartItemUnavailable as a remove-on-alert control. See cart-item.md.',\n});\n\nexport const CartItemAvailableDefinition = defineComponent<CartItemAvailableProps>({\n name: 'CartItemAvailable',\n importPath: '@vtex/faststore/components',\n component: CartItemAvailable,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Available', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders its children only when the current cart item is in stock (availability AVAILABLE). Wrap price, quantity stepper, and line total so they hide for unavailable items. Must be inside CartItemRoot.',\n});\n\nexport const CartItemUnavailableDefinition = defineComponent<CartItemUnavailableProps>({\n name: 'CartItemUnavailable',\n importPath: '@vtex/faststore/components',\n component: CartItemUnavailable,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Unavailable', category: 'commerce' },\n schema: {},\n guideline:\n 'Status badge shown only when the current cart item is NOT available. Renders a built-in English label per state (OUT_OF_STOCK → \"Out of stock\", UNAVAILABLE → \"Cannot be delivered\", otherwise \"Unavailable\"); pass children to override the text — children REPLACE the label, so a remove-on-alert composition nests an explicit label plus a CartItemRemove. Emits data-fs-cart-item-unavailable-state for per-state styling. Must be inside CartItemRoot. See cart-item.md.',\n});\n\nexport const CartItemGiftDefinition = defineComponent<CartItemGiftProps>({\n name: 'CartItemGift',\n importPath: '@vtex/faststore/components',\n component: CartItemGift,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Gift', category: 'commerce' },\n schema: {},\n guideline:\n 'Free marker for a promotional gift line: renders only when the current cart item is a gift, showing a built-in \"Free\" label; pass children to override the text — children REPLACE the label. Place it beside CartItemPrice/CartItemListPrice, whose struck-through list price it complements. It is an inline marker, not a wrapper for a gift-only subtree. On a gift line CartItemRemove suppresses itself and CartItemRoot marks the row with data-fs-cart-item-gift-line, so neither needs composing differently. Must be inside CartItemRoot. See cart-item.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function CartItemRoot({ $data: $dataProp, className, children, ...props }: CartItemRootProps) {\n // Read the resolved per-line slice through the DataOverrideContext seam the reactive-list-item\n // wrapper publishes (or via $data in tests), then re-provide it to the core particles — the same\n // \"read my slice, then provide React context to descendants\" shape CartRoot uses for the cart.\n const slice = useDataOverride<CartItemEntity | null | undefined>($dataProp) ?? null;\n\n return (\n <CartItemContext value={slice}>\n <div\n {...props}\n className={className}\n data-fs-cart-item-root=\"\"\n {...(slice?.gift ? { 'data-fs-cart-item-gift-line': '' } : {})}\n >\n {children}\n </div>\n </CartItemContext>\n );\n}\n\nexport function CartItemImage({ className, objectFit = 'contain', ...props }: CartItemImageProps) {\n const item = useCartItem();\n const image = item.variant.image;\n\n if (!image) return null;\n\n return (\n // biome-ignore lint/performance/noImgElement: Cart item image URL is dynamic; matter stays Next-agnostic.\n <img\n {...props}\n src={image.url}\n alt={image.altText ?? item.variant.name}\n className={className}\n style={{ objectFit, ...props.style }}\n data-fs-cart-item-image=\"\"\n data-fs-image=\"\"\n />\n );\n}\n\nexport function CartItemProductLink({ className, children, ...props }: Readonly<CartItemProductLinkProps>) {\n const item = useCartItem();\n // The particle gates only CMS-authored destinations, so a platform URL arriving on the `href` prop\n // is this leaf's own to validate. `detailUrl` is `String!`, which excludes null but not an empty or\n // hostile value. Either way the leaf degrades to a passthrough rather than self-hiding the way the\n // data-bearing leaves do: returning null would take the image with it.\n const destination = resolveSafeHref(item.product.detailUrl);\n\n if (!destination.safe) {\n // A blank URL is ordinary, so it stays quiet. Anything else was actively refused, and the reason\n // is logged the way the particle logs a refused authored destination — never the value itself.\n if (destination.reason !== 'blank') {\n console.warn('[CartItemProductLink] product URL rejected', { reason: destination.reason });\n }\n return children;\n }\n\n // The anchor is named by the wrapped image's alt text (`altText ?? variant.name`). With no image\n // there is nothing to render or name; with a blank alt the anchor would be a focus stop with no\n // accessible name. Either way, degrade to the unwrapped children rather than emit an unnamed link.\n const image = item.variant.image;\n if (!image || !(image.altText ?? item.variant.name ?? '').trim()) return children;\n\n return (\n <NavigationLink {...props} href={destination.href} className={className} data-fs-cart-item-product-link=\"\">\n {children}\n </NavigationLink>\n );\n}\n\nexport function CartItemName({ className, children, ...props }: CartItemNameProps) {\n const item = useCartItem();\n\n return (\n <span {...props} className={className} data-fs-cart-item-name=\"\" data-fs-text=\"\">\n {children ?? item.product.title}\n </span>\n );\n}\n\nexport function CartItemVariant({ className, children, ...props }: CartItemVariantProps) {\n const item = useCartItem();\n const { name } = item.variant;\n if (!name) return null;\n\n return (\n <span {...props} className={className} data-fs-cart-item-variant=\"\">\n {children ?? name}\n </span>\n );\n}\n\nexport function CartItemSku({ className, children, ...props }: CartItemSkuProps) {\n const item = useCartItem();\n const { sku } = item.variant;\n if (!sku) return null;\n\n return (\n <span {...props} className={className} data-fs-cart-item-sku=\"\">\n {children ?? sku}\n </span>\n );\n}\n\nexport function CartItemPrice({ className, children, ...props }: CartItemPriceProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-price=\"\" data-fs-text=\"\">\n {children ?? formatPrice(item.sellingPrice.amount, locale, currency)}\n </span>\n );\n}\n\nexport function CartItemListPrice({ className, children, ...props }: CartItemListPriceProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n if (item.listPrice.amount <= item.sellingPrice.amount) {\n return null;\n }\n\n return (\n <span {...props} className={className} data-fs-cart-item-list-price=\"\" data-fs-text=\"\">\n {children ?? formatPrice(item.listPrice.amount, locale, currency)}\n </span>\n );\n}\n\nexport function CartItemDiscountPercentage({\n template = '-{percentage}%',\n className,\n children,\n ...props\n}: CartItemDiscountPercentageProps) {\n const item = useCartItem();\n\n const listPrice = Number(item.listPrice.amount);\n const sellingPrice = Number(item.sellingPrice.amount);\n\n if (listPrice <= sellingPrice) {\n return null;\n }\n\n const percent = Math.round(((listPrice - sellingPrice) / listPrice) * 100);\n const text = template.replaceAll('{percentage}', String(percent));\n\n return (\n <span {...props} className={className} data-fs-cart-item-discount-percentage=\"\" data-fs-text=\"\">\n {children ?? text}\n </span>\n );\n}\n\nexport function CartItemLineTotal({ className, children, ...props }: CartItemLineTotalProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-line-total=\"\">\n {children ?? formatPrice(item.sellingPrice.amount * item.quantity, locale, currency)}\n </span>\n );\n}\n\nexport function CartItemOwnTotal({ className, children, ...props }: CartItemOwnTotalProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-own-total=\"\" data-fs-text=\"\">\n {children ?? formatPrice(Number(item.ownTotal.amount), locale, currency)}\n </span>\n );\n}\n\nexport function CartItemAggregateTotal({ className, children, ...props }: CartItemAggregateTotalProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-aggregate-total=\"\" data-fs-text=\"\">\n {children ?? formatPrice(Number(item.total.amount), locale, currency)}\n </span>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Quantity stepper — the unit multiplier converts between the purchasable count the order form and\n// the cart mutations use, and the measure the merchant sells in. `1` is the identity, so a SKU sold\n// by the unit behaves exactly as it did before the multiplier was read.\n// ---------------------------------------------------------------------------\n\n/** Why a committed amount had to be corrected; drives both the notice and its styling hook. */\ntype QuantityAdjustment = { reason: 'min' | 'max' | 'multiple'; value: number };\n\nconst MAX_DISPLAY_DECIMALS = 6;\n\n/**\n * A multiplier the platform cannot sell in — zero, negative, or non-finite — is treated as `1`, which\n * is also the fallback when the optional slice is absent (the stepper composed with no own provider).\n */\nfunction effectiveMultiplier(slice: CartItemQuantitySlice | null | undefined): number {\n const multiplier = slice?.variant?.unitMultiplier ?? 1;\n return Number.isFinite(multiplier) && multiplier > 0 ? multiplier : 1;\n}\n\n/**\n * Precision of the multiplier, so a displayed amount never carries binary-representation noise.\n * Derived through `toFixed` rather than `String`: `String` switches to exponential notation below\n * 1e-6, where it carries no decimal point and would report zero places, yielding a step of 0 that no\n * control can move. The normal-path precisions (0.5, 0.1, 0.333) are covered by tests; the\n * exponential branch is not, since it needs a multiplier no SKU can have.\n */\nfunction decimalPlacesOf(multiplier: number): number {\n const [, decimals = ''] = multiplier.toFixed(MAX_DISPLAY_DECIMALS).split('.');\n // Trailing zeros carry no precision; strip them with a linear scan rather than a `/0+$/` regex,\n // which SonarQube flags as super-linear (harmless here — the input is a bounded toFixed string).\n let end = decimals.length;\n while (end > 0 && decimals[end - 1] === '0') end -= 1;\n return end;\n}\n\nfunction roundTo(value: number, decimals: number): number {\n return Number(value.toFixed(decimals));\n}\n\nfunction resolveAdjustment(\n entered: number,\n snappedDisplay: number,\n multiplier: number,\n minDisplay: number,\n maxDisplay: number | undefined,\n): QuantityAdjustment | null {\n if (maxDisplay != null && entered > maxDisplay) return { reason: 'max', value: snappedDisplay };\n if (entered < minDisplay) return { reason: 'min', value: snappedDisplay };\n // Tolerance is relative to the multiplier, so recovering the count through a division never reads\n // as an off-grid entry.\n if (Math.abs(entered - snappedDisplay) > multiplier * 1e-6) return { reason: 'multiple', value: snappedDisplay };\n return null;\n}\n\nexport function CartItemQuantity({\n className,\n children,\n min = 1,\n max,\n step,\n removeOnDecrement,\n validationMessage = 'Quantity adjusted to {value}.',\n $data: $dataProp,\n $actions,\n ...props\n}: CartItemQuantityProps) {\n const item = useCartItem();\n const slice = useDataOverride<CartItemQuantitySlice | null | undefined>($dataProp);\n const [adjustment, setAdjustment] = useState<QuantityAdjustment | null>(null);\n\n // A gift line has no interactive quantity control, the same way CartItemRemove has no remove\n // control: a promotion-controlled quantity the shopper cannot validly change, and — with\n // removeOnDecrement — a path that would otherwise dispatch removeItem on a gift, discarding a\n // benefit T6 already sealed off from the remove button. Suppressing the stepper closes that path\n // under any composition. A store showing a gift's fixed quantity composes CartItemQuantityValue.\n if (item.gift) return null;\n\n const cart = $actions?.cart;\n\n const multiplier = effectiveMultiplier(slice);\n const decimals = decimalPlacesOf(multiplier);\n // min, max, and step are counts of purchasable units, and a cart line holds a whole number of\n // them, so a fractional bound has no valid value to land on. Left unrounded, a fractional `min` or\n // `max` becomes the clamp result and leaks into the delta — `decreaseQuantity({ by: 2.5 })` — which\n // the cart mutations reject. Rounding all three keeps every reachable value, and every dispatched\n // delta, an integer.\n const stepUnits = Math.max(1, Math.round(step ?? 1));\n const minUnits = Math.round(min);\n const maxUnits = max == null ? undefined : Math.round(max);\n const displayStep = roundTo(stepUnits * multiplier, decimals);\n const minDisplay = roundTo(minUnits * multiplier, decimals);\n const maxDisplay = maxUnits == null ? undefined : roundTo(maxUnits * multiplier, decimals);\n\n // Commit rather than change: `onValueChange` fires per parseable keystroke, so enforcing there\n // writes to the cart for every digit typed and snaps a value the shopper has not finished entering.\n function handleValueCommitted(value: number | null) {\n if (value == null) return;\n\n if (removeOnDecrement && value < minDisplay) {\n setAdjustment(null);\n void cart?.removeItem?.({ id: item.id });\n return;\n }\n\n const snapped = Math.min(Math.max(Math.round(value / multiplier), minUnits), maxUnits ?? Number.POSITIVE_INFINITY);\n const snappedDisplay = roundTo(snapped * multiplier, decimals);\n setAdjustment(resolveAdjustment(value, snappedDisplay, multiplier, minDisplay, maxDisplay));\n\n const delta = snapped - item.quantity;\n if (delta > 0) {\n void cart?.increaseQuantity?.({ id: item.id, by: delta });\n } else if (delta < 0) {\n void cart?.decreaseQuantity?.({ id: item.id, by: -delta });\n }\n }\n\n const effectiveMin = removeOnDecrement ? 0 : minDisplay;\n const computedClassName = removeOnDecrement ? (className ? `group ${className}` : 'group') : className;\n\n return (\n <BaseNumberField.Root\n {...props}\n value={roundTo(item.quantity * multiplier, decimals)}\n onValueCommitted={handleValueCommitted}\n // A shopper editing the field again makes the previous correction stale, so it is dropped as\n // soon as they type. Step and scrub reasons are excluded: those land on the grid by\n // construction and never raise a notice, so clearing on them would be dead code.\n onValueChange={(_value, details) => {\n if (details.reason === 'input-change' || details.reason === 'input-paste' || details.reason === 'input-clear') {\n setAdjustment(null);\n }\n }}\n min={effectiveMin}\n max={maxDisplay}\n step={displayStep}\n smallStep={displayStep}\n largeStep={roundTo(displayStep * 10, decimals)}\n snapOnStep\n // Base UI clamps text entry to min/max on its own, which would correct an out-of-bounds amount\n // before this component ever saw it — and a correction nobody can observe cannot be explained.\n allowOutOfRange\n className={computedClassName}\n data-fs-cart-item-quantity=\"\"\n {...(multiplier === 1 ? {} : { 'data-fs-cart-item-quantity-multiplier': String(multiplier) })}\n {...(adjustment\n ? {\n 'data-fs-cart-item-quantity-adjusted': '',\n 'data-fs-cart-item-quantity-adjusted-reason': adjustment.reason,\n }\n : {})}\n {...(removeOnDecrement && item.quantity <= minUnits ? { 'data-fs-cart-item-at-min': '' } : {})}\n >\n {children}\n {adjustment ? (\n <div role=\"alert\" data-fs-cart-item-quantity-validation=\"\">\n {validationMessage.replaceAll('{value}', String(adjustment.value))}\n </div>\n ) : null}\n </BaseNumberField.Root>\n );\n}\n\nexport function CartItemQuantityValue({ className, children, ...props }: CartItemQuantityValueProps) {\n const item = useCartItem();\n\n return (\n <span {...props} className={className} data-fs-cart-item-quantity-value=\"\">\n {children ?? String(item.quantity)}\n </span>\n );\n}\n\nexport function CartItemRemove({ className, children, onClick, $actions, ...props }: CartItemRemoveProps) {\n const item = useCartItem();\n\n // A gift line has no remove control: the promotion granted the item, and this phase does not let a\n // shopper discard it. Suppressed here rather than by composition, because CartList renders one\n // template for every line — an author cannot leave the leaf out of gift rows alone. Covers the\n // remove-on-alert composition too; an unavailable gift is recovered by the cart-level\n // CartRemoveUnavailable batch and by server-side cart validation.\n if (item.gift) return null;\n\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n void $actions?.cart?.removeItem?.({ id: item.id });\n onClick?.(e);\n }\n\n return (\n <button\n {...props}\n type=\"button\"\n className={className}\n onClick={handleClick}\n data-fs-cart-item-remove=\"\"\n data-fs-button=\"\"\n >\n {children}\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Availability gates — read `variant.availability` from the core slice (F4).\n// UNKNOWN and any future non-available state fall back to the generic \"Unavailable\".\n// ---------------------------------------------------------------------------\n\nconst UNAVAILABLE_LABELS: Record<string, string> = {\n OUT_OF_STOCK: 'Out of stock',\n UNAVAILABLE: 'Cannot be delivered',\n};\n\nfunction unavailableLabel(availability: string): string {\n return UNAVAILABLE_LABELS[availability] ?? 'Unavailable';\n}\n\nexport function CartItemAvailable({ className, children, ...props }: CartItemAvailableProps) {\n const item = useCartItem();\n if (item.variant.availability !== 'AVAILABLE') return null;\n\n return (\n <div {...props} className={className} data-fs-cart-item-available=\"\">\n {children}\n </div>\n );\n}\n\nexport function CartItemUnavailable({ className, children, ...props }: CartItemUnavailableProps) {\n const item = useCartItem();\n const { availability } = item.variant;\n if (availability === 'AVAILABLE') return null;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-cart-item-unavailable=\"\"\n data-fs-cart-item-unavailable-state={availability.toLowerCase()}\n >\n {children ?? unavailableLabel(availability)}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// F9 — gift marker. Inline, so `children` override the label rather than nesting a subtree; a gift\n// row's other needs are met by the row's own `data-fs-cart-item-gift-line` hook. \"Free\" is\n// operational copy, so it ships as a built-in default like the unavailability labels above, and moves\n// to the localization catalog rather than to a content model.\n// ---------------------------------------------------------------------------\n\nexport function CartItemGift({ className, children, ...props }: CartItemGiftProps) {\n const item = useCartItem();\n if (!item.gift) return null;\n\n return (\n <span {...props} className={className} data-fs-cart-item-gift=\"\" data-fs-text=\"\">\n {children ?? 'Free'}\n </span>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cart-item.js","names":["BaseNumberField"],"sources":["../../../src/matter/cart-item/cart-item.tsx"],"sourcesContent":["'use client';\n\nimport { NumberField as BaseNumberField } from '@base-ui/react/number-field';\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, type ReactNode, useContext, useState } from 'react';\nimport { NavigationLink, type NavigationLinkProps } from '../../particles/navigation-link/navigation-link.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport type { CartItemEntity } from '../../sdk/cart/operations.js';\nimport { useStorefrontContext } from '../../sdk/context/storefront-context.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { graphql } from '../../sdk/generated/gql.js';\nimport { resolveSafeHref } from '../../utils/safe-href.js';\nimport { formatPrice } from '../cart/cart.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type CartItemRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Per-line slice delivered by the `reactive-list-item` runtime (`ReactiveListItemDataProvider`) —\n * the resolved `CartItem` entity read from the normalized cache via `useFragment(CartItem:<id>)`.\n * Injected through the iteration context in a compiled page; the `$data` prop is retained for test\n * injection.\n */\n $data?: CartItemEntity | null;\n};\n\nexport type CartItemImageProps = ComponentPropsWithRef<'img'> & {\n objectFit?: 'contain' | 'cover' | 'fill' | 'none';\n};\n\n// The destination is the line's own product page, so `href` is not authorable. `$content` goes with it\n// because the leaf declares no content model: a product URL is commerce data, not copy. `target` goes\n// too, so the same-tab rule is enforced by the type rather than merely absent from the schema — an\n// image-only link cannot carry the \"(opens in new tab)\" cue a `_blank` destination needs.\nexport type CartItemProductLinkProps = Omit<NavigationLinkProps, 'href' | '$content' | 'target'>;\n\nexport type CartItemNameProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemVariantProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemSkuProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemPriceProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemListPriceProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemDiscountPercentageProps = ComponentPropsWithRef<'span'> & {\n template?: string;\n};\n\nexport type CartItemLineTotalProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemOwnTotalProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemAggregateTotalProps = ComponentPropsWithRef<'span'>;\n\n/**\n * Per-line slice delivered by the `reactive-list-item` runtime: the value the wrapper publishes\n * through `DataOverrideContext`, read via `useDataOverride`. Only the field the stepper converts with\n * is typed; the resolved cache slice carries the full fragment selection.\n */\nexport type CartItemQuantitySlice = {\n variant: { unitMultiplier: number } | null;\n};\n\nexport type CartItemQuantityProps = Omit<ComponentPropsWithRef<'div'>, 'defaultValue' | 'value'> & {\n /** Minimum, in purchasable units — not in the unit shown to the shopper. */\n min?: number;\n /** Maximum, in purchasable units. Unbounded when unset. */\n max?: number;\n /** Step, in purchasable units: one step moves the display by one unit multiplier. */\n step?: number;\n removeOnDecrement?: boolean;\n /** Snap-notice template; `{value}` is replaced with the corrected displayed amount. */\n validationMessage?: string;\n children?: ReactNode;\n /** Per-line slice from the reactive-list-item runtime; injected/overridden by the wrapper. */\n $data?: CartItemQuantitySlice | null;\n /** Injected by the compiler from `actions: { cart: [...] }`; the cart write surface. */\n $actions?: ComponentActions;\n};\n\nexport type CartItemQuantityValueProps = ComponentPropsWithRef<'span'>;\n\nexport type CartItemRemoveProps = ComponentPropsWithRef<'button'> & {\n /** Injected by the compiler from `actions: { cart: ['removeItem'] }`; the cart write surface. */\n $actions?: ComponentActions;\n};\n\nexport type CartItemAvailableProps = ComponentPropsWithRef<'div'>;\n\nexport type CartItemUnavailableProps = ComponentPropsWithRef<'div'>;\n\nexport type CartItemGiftProps = ComponentPropsWithRef<'span'>;\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — typed via @graphql-codegen client-preset (T7)\n//\n// CartItemRoot owns the core-scalar slice of each cart line (ADR-0012 Option B). Fields are inlined\n// (no `...` spread) so the compiler's list-field derivation finds the iterated `items` list by its\n// direct child field and re-roots this fragment to `CartItemRootData_CartItem on CartItem` for the\n// per-line `useFragment` subscription. `variant.availability` stays here — it is a cross-cutting core\n// scalar (CartCheckoutGuard, CartItemAvailable/Unavailable read it), not an optional display leaf.\n// `gift` stays here for the same reason: CartItemGift, CartItemRemove's suppression, and the row's\n// own state hook all read it, so one selection serves three consumers.\n//\n// Every field selected here MUST also be selected by the mutation fragment (`CartItemFragment` in\n// sdk/cart/operations.ts). ReactiveListItemDataProvider resolves `complete ? data : fallbackData`,\n// so a field present here but missing there makes every line incomplete after a cart write and\n// degrades the whole list to the cart-level slice, which carries no product or prices.\n// ---------------------------------------------------------------------------\n\nconst fragment = graphql(`\n fragment CartItemRootData on Query {\n cart {\n items {\n __typename\n id\n quantity\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n sellingPrice {\n amount\n currencyCode\n }\n gift\n ownTotal {\n ...MoneyFragment\n }\n total {\n ...MoneyFragment\n }\n variant {\n __typename\n variantId\n sku\n name\n image {\n url\n altText\n }\n availability\n }\n product {\n __typename\n productId\n title\n detailUrl\n categories {\n categoryId\n name\n }\n brand {\n brandId\n name\n }\n }\n }\n }\n }\n`);\n\n// ADR-0011: the unit multiplier is optional per-line data owned by the stepper that converts with\n// it, not by the core root — a cart line showing a read-only quantity never fetches it. Fields are\n// inlined (no `...` spread) so the derived per-item fragment is self-contained for `useFragment`, and\n// it drills the identical cart { items { ... } } path as the other per-item matters so it merges\n// un-aliased (ADR-0012).\nconst cartItemQuantityFragment = graphql(`\n fragment CartItemQuantityData on Query {\n cart {\n items {\n __typename\n id\n variant {\n __typename\n unitMultiplier\n }\n }\n }\n }\n`);\n\n// ---------------------------------------------------------------------------\n// Cart item context — provided by CartItemRoot with its resolved per-line slice\n// ---------------------------------------------------------------------------\n\nconst CartItemContext = createContext<CartItemEntity | null>(null);\n\nfunction useCartItem(): CartItemEntity {\n const item = useContext(CartItemContext);\n if (!item) {\n throw new Error('Cart item components must be used inside a <CartItemRoot> subtree.');\n }\n return item;\n}\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const CartItemRootDefinition = defineComponent<CartItemRootProps>({\n name: 'CartItemRoot',\n importPath: '@vtex/faststore/components',\n component: CartItemRoot,\n fragment,\n fetchStrategy: 'reactive-list-item',\n // Placement: requires CartList's `cart-item` iteration context and provides `cart-item-data` to the\n // core particles below. The two tokens differ, so it does not self-satisfy — the context-constraints\n // walk checks `requiresContext` before pushing this node's own `providesContext`.\n requiresContext: 'cart-item',\n providesContext: 'cart-item-data',\n editor: { label: 'Cart Item Root', category: 'commerce' },\n schema: {},\n guideline:\n 'Per-line data provider for the cart line-item template: place it directly inside CartList, wrapping the CartItem* particles (CartItemImage, CartItemName, CartItemPrice, the CartItemQuantity stepper, CartItemRemove, …). It owns the core per-item fields and delivers each line its own slice, so the core particles must sit inside it. Must be inside CartList. See cart-item.md.',\n});\n\nexport const CartItemImageDefinition = defineComponent<CartItemImageProps>({\n name: 'CartItemImage',\n importPath: '@vtex/faststore/components',\n component: CartItemImage,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Image', category: 'commerce' },\n schema: {\n objectFit: { type: ['contain', 'cover', 'fill', 'none'] as const, default: 'contain', label: 'Object Fit' },\n },\n guideline: 'Renders the product image from the cart item. Must be inside CartItemRoot.',\n});\n\nexport const CartItemProductLinkDefinition = defineComponent<CartItemProductLinkProps>({\n name: 'CartItemProductLink',\n importPath: '@vtex/faststore/components',\n component: CartItemProductLink,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Product Link', category: 'commerce' },\n schema: {},\n guideline:\n 'Links the cart line image to its product page, using the product URL the line already carries: place it inside CartItemRoot wrapping CartItemImage. Whether a line links is decided by placing this element, so there is no prop to toggle it — a checkout line simply omits it. Renders its children unwrapped when the line has no image or no usable product URL, so it never leaves an empty or unsafe link. Navigation is same-tab, and the leaf adds no fields to the page query. Must be inside CartItemRoot. See cart-item.md.',\n});\n\nexport const CartItemNameDefinition = defineComponent<CartItemNameProps>({\n name: 'CartItemName',\n importPath: '@vtex/faststore/components',\n component: CartItemName,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Name', category: 'commerce' },\n schema: {},\n guideline: 'Renders the product name from the cart item. Must be inside CartItemRoot.',\n});\n\nexport const CartItemVariantDefinition = defineComponent<CartItemVariantProps>({\n name: 'CartItemVariant',\n importPath: '@vtex/faststore/components',\n component: CartItemVariant,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Variant', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the selected variant name of the cart item (e.g. \"Midnight Black\"); renders null when the variant has no name. Use as a subtitle under CartItemName, typically alongside CartItemSku. Must be inside CartItemRoot.',\n});\n\nexport const CartItemSkuDefinition = defineComponent<CartItemSkuProps>({\n name: 'CartItemSku',\n importPath: '@vtex/faststore/components',\n component: CartItemSku,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item SKU', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the SKU code of the cart item variant (e.g. \"WNC-400X\"); renders null when the variant has no SKU. Use as a subtitle under CartItemName, typically alongside CartItemVariant. Must be inside CartItemRoot.',\n});\n\nexport const CartItemPriceDefinition = defineComponent<CartItemPriceProps>({\n name: 'CartItemPrice',\n importPath: '@vtex/faststore/components',\n component: CartItemPrice,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Price', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the selling price of the cart item, formatted from the active storefront context. Must be inside CartItemRoot.',\n});\n\nexport const CartItemListPriceDefinition = defineComponent<CartItemListPriceProps>({\n name: 'CartItemListPrice',\n importPath: '@vtex/faststore/components',\n component: CartItemListPrice,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item List Price', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the original list price of the cart item (strikethrough); renders null when no discount is active. Use alongside CartItemPrice to show promotional pricing. Must be inside CartItemRoot.',\n});\n\nexport const CartItemDiscountPercentageDefinition = defineComponent<CartItemDiscountPercentageProps>({\n name: 'CartItemDiscountPercentage',\n importPath: '@vtex/faststore/components',\n component: CartItemDiscountPercentage,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Discount %', category: 'commerce' },\n schema: {\n template: { type: 'string', default: '-{percentage}%', label: 'Template' },\n },\n guideline:\n 'Per-line discount badge showing the integer listPrice-vs-sellingPrice percentage (e.g. \"-20%\"); use the template prop to customize the format ({percentage} is the integer percent). Renders null when the line has no discount (sellingPrice >= listPrice), so it only appears on promotional lines. Place alongside CartItemPrice/CartItemListPrice. Must be inside CartItemRoot.',\n});\n\nexport const CartItemLineTotalDefinition = defineComponent<CartItemLineTotalProps>({\n name: 'CartItemLineTotal',\n importPath: '@vtex/faststore/components',\n component: CartItemLineTotal,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Line Total', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the per-line total (selling price × quantity) formatted from the active storefront context. Must be inside CartItemRoot.',\n});\n\nexport const CartItemOwnTotalDefinition = defineComponent<CartItemOwnTotalProps>({\n name: 'CartItemOwnTotal',\n importPath: '@vtex/faststore/components',\n component: CartItemOwnTotal,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Own Total', category: 'commerce' },\n schema: {},\n guideline:\n \"Renders the line's own total (excludes bundle/composition children), formatted from the active storefront context. Equals CartItemAggregateTotal on lines with no compositions. Must be inside CartItemRoot. See cart-item.md.\",\n});\n\nexport const CartItemAggregateTotalDefinition = defineComponent<CartItemAggregateTotalProps>({\n name: 'CartItemAggregateTotal',\n importPath: '@vtex/faststore/components',\n component: CartItemAggregateTotal,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Aggregate Total', category: 'commerce' },\n schema: {},\n guideline:\n \"Renders the line's aggregate total including all nested composition children, formatted from the active storefront context — the price the shopper actually pays for a bundled line. Equals CartItemOwnTotal on lines with no compositions. Must be inside CartItemRoot. See cart-item.md.\",\n});\n\nexport const CartItemQuantityDefinition = defineComponent<CartItemQuantityProps>({\n name: 'CartItemQuantity',\n importPath: '@vtex/faststore/components',\n component: CartItemQuantity,\n fragment: cartItemQuantityFragment,\n fetchStrategy: 'reactive-list-item',\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Quantity', category: 'commerce' },\n schema: {\n min: { type: 'number', default: 1, label: 'Min' },\n max: { type: 'number', label: 'Max' },\n step: { type: 'number', default: 1, label: 'Step' },\n removeOnDecrement: { type: 'boolean', default: false, label: 'Remove On Decrement' },\n validationMessage: { type: 'string', default: 'Quantity adjusted to {value}.', label: 'Validation Message' },\n },\n actions: { cart: ['increaseQuantity', 'decreaseQuantity', 'removeItem'] },\n guideline:\n \"Quantity stepper for a cart item, shown in the unit the merchant sells in: the displayed amount is the line's quantity times its unit multiplier, so a SKU sold in 500 g portions reads 1.5 rather than 3. min, max, and step are configured in purchasable units and converted for display, so one template serves SKUs with different multipliers. A typed amount that is out of bounds or off the multiplier grid snaps to the nearest valid value on commit and renders validationMessage. Set removeOnDecrement=true to remove the item when quantity reaches the minimum. Renders nothing on a gift line — a gift's quantity is promotion-controlled and cannot be stepped or removed, so use CartItemQuantityValue to show a gift's fixed quantity. Must be inside CartItemRoot. See cart-item.md.\",\n});\n\nexport const CartItemQuantityValueDefinition = defineComponent<CartItemQuantityValueProps>({\n name: 'CartItemQuantityValue',\n importPath: '@vtex/faststore/components',\n component: CartItemQuantityValue,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Quantity Value', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders the read-only quantity count of the cart item as text (e.g. a \"Qty\" badge). Distinct from the editable CartItemQuantity stepper. Must be inside CartItemRoot.',\n});\n\nexport const CartItemRemoveDefinition = defineComponent<CartItemRemoveProps>({\n name: 'CartItemRemove',\n importPath: '@vtex/faststore/components',\n component: CartItemRemove,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Remove', category: 'commerce' },\n schema: {},\n actions: { cart: ['removeItem'] },\n guideline:\n 'Button that removes the current line item from the cart. Must be inside CartItemRoot. Also composes inside CartItemUnavailable as a remove-on-alert control. See cart-item.md.',\n});\n\nexport const CartItemAvailableDefinition = defineComponent<CartItemAvailableProps>({\n name: 'CartItemAvailable',\n importPath: '@vtex/faststore/components',\n component: CartItemAvailable,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Available', category: 'commerce' },\n schema: {},\n guideline:\n 'Renders its children only when the current cart item is in stock (availability AVAILABLE). Wrap price, quantity stepper, and line total so they hide for unavailable items. Must be inside CartItemRoot.',\n});\n\nexport const CartItemUnavailableDefinition = defineComponent<CartItemUnavailableProps>({\n name: 'CartItemUnavailable',\n importPath: '@vtex/faststore/components',\n component: CartItemUnavailable,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Unavailable', category: 'commerce' },\n schema: {},\n guideline:\n 'Status badge shown only when the current cart item is NOT available. Renders a built-in English label per state (OUT_OF_STOCK → \"Out of stock\", UNAVAILABLE → \"Cannot be delivered\", otherwise \"Unavailable\"); pass children to override the text — children REPLACE the label, so a remove-on-alert composition nests an explicit label plus a CartItemRemove. Emits data-fs-cart-item-unavailable-state for per-state styling. Must be inside CartItemRoot. See cart-item.md.',\n});\n\nexport const CartItemGiftDefinition = defineComponent<CartItemGiftProps>({\n name: 'CartItemGift',\n importPath: '@vtex/faststore/components',\n component: CartItemGift,\n requiresContext: 'cart-item-data',\n editor: { label: 'Cart Item Gift', category: 'commerce' },\n schema: {},\n guideline:\n 'Free marker for a promotional gift line: renders only when the current cart item is a gift, showing a built-in \"Free\" label; pass children to override the text — children REPLACE the label. Place it beside CartItemPrice/CartItemListPrice, whose struck-through list price it complements. It is an inline marker, not a wrapper for a gift-only subtree. On a gift line CartItemRemove suppresses itself and CartItemRoot marks the row with data-fs-cart-item-gift-line, so neither needs composing differently. Must be inside CartItemRoot. See cart-item.md.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function CartItemRoot({ $data: $dataProp, className, children, ...props }: CartItemRootProps) {\n // Read the resolved per-line slice through the DataOverrideContext seam the reactive-list-item\n // wrapper publishes (or via $data in tests), then re-provide it to the core particles — the same\n // \"read my slice, then provide React context to descendants\" shape CartRoot uses for the cart.\n const slice = useDataOverride<CartItemEntity | null | undefined>($dataProp) ?? null;\n\n return (\n <CartItemContext value={slice}>\n <div\n {...props}\n className={className}\n data-fs-cart-item-root=\"\"\n {...(slice?.gift ? { 'data-fs-cart-item-gift-line': '' } : {})}\n >\n {children}\n </div>\n </CartItemContext>\n );\n}\n\nexport function CartItemImage({ className, objectFit = 'contain', ...props }: CartItemImageProps) {\n const item = useCartItem();\n const image = item.variant.image;\n\n if (!image) return null;\n\n return (\n // biome-ignore lint/performance/noImgElement: Cart item image URL is dynamic; matter stays Next-agnostic.\n <img\n {...props}\n src={image.url}\n alt={image.altText ?? item.variant.name}\n className={className}\n style={{ objectFit, ...props.style }}\n data-fs-cart-item-image=\"\"\n data-fs-image=\"\"\n />\n );\n}\n\nexport function CartItemProductLink({ className, children, ...props }: Readonly<CartItemProductLinkProps>) {\n const item = useCartItem();\n // The particle gates only CMS-authored destinations, so a platform URL arriving on the `href` prop\n // is this leaf's own to validate. `detailUrl` is `String!`, which excludes null but not an empty or\n // hostile value. Either way the leaf degrades to a passthrough rather than self-hiding the way the\n // data-bearing leaves do: returning null would take the image with it.\n const destination = resolveSafeHref(item.product.detailUrl);\n\n if (!destination.safe) {\n // A blank URL is ordinary, so it stays quiet. Anything else was actively refused, and the reason\n // is logged the way the particle logs a refused authored destination — never the value itself.\n if (destination.reason !== 'blank') {\n console.warn('[CartItemProductLink] product URL rejected', { reason: destination.reason });\n }\n return children;\n }\n\n // The anchor is named by the wrapped image's alt text (`altText ?? variant.name`). With no image\n // there is nothing to render or name; with a blank alt the anchor would be a focus stop with no\n // accessible name. Either way, degrade to the unwrapped children rather than emit an unnamed link.\n const image = item.variant.image;\n if (!image || !(image.altText ?? item.variant.name ?? '').trim()) return children;\n\n return (\n <NavigationLink {...props} href={destination.href} className={className} data-fs-cart-item-product-link=\"\">\n {children}\n </NavigationLink>\n );\n}\n\nexport function CartItemName({ className, children, ...props }: CartItemNameProps) {\n const item = useCartItem();\n\n return (\n <span {...props} className={className} data-fs-cart-item-name=\"\" data-fs-text=\"\">\n {children ?? item.product.title}\n </span>\n );\n}\n\nexport function CartItemVariant({ className, children, ...props }: CartItemVariantProps) {\n const item = useCartItem();\n const { name } = item.variant;\n if (!name) return null;\n\n return (\n <span {...props} className={className} data-fs-cart-item-variant=\"\">\n {children ?? name}\n </span>\n );\n}\n\nexport function CartItemSku({ className, children, ...props }: CartItemSkuProps) {\n const item = useCartItem();\n const { sku } = item.variant;\n if (!sku) return null;\n\n return (\n <span {...props} className={className} data-fs-cart-item-sku=\"\">\n {children ?? sku}\n </span>\n );\n}\n\nexport function CartItemPrice({ className, children, ...props }: CartItemPriceProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-price=\"\" data-fs-text=\"\">\n {children ?? formatPrice(item.sellingPrice.amount, locale, currency)}\n </span>\n );\n}\n\nexport function CartItemListPrice({ className, children, ...props }: CartItemListPriceProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n if (item.listPrice.amount <= item.sellingPrice.amount) {\n return null;\n }\n\n return (\n <span {...props} className={className} data-fs-cart-item-list-price=\"\" data-fs-text=\"\">\n {children ?? formatPrice(item.listPrice.amount, locale, currency)}\n </span>\n );\n}\n\nexport function CartItemDiscountPercentage({\n template = '-{percentage}%',\n className,\n children,\n ...props\n}: CartItemDiscountPercentageProps) {\n const item = useCartItem();\n\n const listPrice = Number(item.listPrice.amount);\n const sellingPrice = Number(item.sellingPrice.amount);\n\n if (listPrice <= sellingPrice) {\n return null;\n }\n\n const percent = Math.round(((listPrice - sellingPrice) / listPrice) * 100);\n const text = template.replaceAll('{percentage}', String(percent));\n\n return (\n <span {...props} className={className} data-fs-cart-item-discount-percentage=\"\" data-fs-text=\"\">\n {children ?? text}\n </span>\n );\n}\n\nexport function CartItemLineTotal({ className, children, ...props }: CartItemLineTotalProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-line-total=\"\">\n {children ?? formatPrice(item.sellingPrice.amount * item.quantity, locale, currency)}\n </span>\n );\n}\n\nexport function CartItemOwnTotal({ className, children, ...props }: CartItemOwnTotalProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-own-total=\"\" data-fs-text=\"\">\n {children ?? formatPrice(Number(item.ownTotal.amount), locale, currency)}\n </span>\n );\n}\n\nexport function CartItemAggregateTotal({ className, children, ...props }: CartItemAggregateTotalProps) {\n const item = useCartItem();\n const { locale, currency } = useStorefrontContext();\n\n return (\n <span {...props} className={className} data-fs-cart-item-aggregate-total=\"\" data-fs-text=\"\">\n {children ?? formatPrice(Number(item.total.amount), locale, currency)}\n </span>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Quantity stepper — the unit multiplier converts between the purchasable count the order form and\n// the cart mutations use, and the measure the merchant sells in. `1` is the identity, so a SKU sold\n// by the unit behaves exactly as it did before the multiplier was read.\n// ---------------------------------------------------------------------------\n\n/** Why a committed amount had to be corrected; drives both the notice and its styling hook. */\ntype QuantityAdjustment = { reason: 'min' | 'max' | 'multiple'; value: number };\n\nconst MAX_DISPLAY_DECIMALS = 6;\n\n/**\n * A multiplier the platform cannot sell in — zero, negative, or non-finite — is treated as `1`, which\n * is also the fallback when the optional slice is absent (the stepper composed with no own provider).\n */\nfunction effectiveMultiplier(slice: CartItemQuantitySlice | null | undefined): number {\n const multiplier = slice?.variant?.unitMultiplier ?? 1;\n return Number.isFinite(multiplier) && multiplier > 0 ? multiplier : 1;\n}\n\n/**\n * Precision of the multiplier, so a displayed amount never carries binary-representation noise.\n * Derived through `toFixed` rather than `String`: `String` switches to exponential notation below\n * 1e-6, where it carries no decimal point and would report zero places, yielding a step of 0 that no\n * control can move. The normal-path precisions (0.5, 0.1, 0.333) are covered by tests; the\n * exponential branch is not, since it needs a multiplier no SKU can have.\n */\nfunction decimalPlacesOf(multiplier: number): number {\n const [, decimals = ''] = multiplier.toFixed(MAX_DISPLAY_DECIMALS).split('.');\n // Trailing zeros carry no precision; strip them with a linear scan rather than a `/0+$/` regex,\n // which SonarQube flags as super-linear (harmless here — the input is a bounded toFixed string).\n let end = decimals.length;\n while (end > 0 && decimals[end - 1] === '0') end -= 1;\n return end;\n}\n\nfunction roundTo(value: number, decimals: number): number {\n return Number(value.toFixed(decimals));\n}\n\nfunction resolveAdjustment(\n entered: number,\n snappedDisplay: number,\n multiplier: number,\n minDisplay: number,\n maxDisplay: number | undefined,\n): QuantityAdjustment | null {\n if (maxDisplay != null && entered > maxDisplay) return { reason: 'max', value: snappedDisplay };\n if (entered < minDisplay) return { reason: 'min', value: snappedDisplay };\n // Tolerance is relative to the multiplier, so recovering the count through a division never reads\n // as an off-grid entry.\n if (Math.abs(entered - snappedDisplay) > multiplier * 1e-6) return { reason: 'multiple', value: snappedDisplay };\n return null;\n}\n\nexport function CartItemQuantity({\n className,\n children,\n min = 1,\n max,\n step,\n removeOnDecrement,\n validationMessage = 'Quantity adjusted to {value}.',\n $data: $dataProp,\n $actions: $actionsProp,\n ...props\n}: CartItemQuantityProps) {\n const item = useCartItem();\n const slice = useDataOverride<CartItemQuantitySlice | null | undefined>($dataProp);\n const $actions = useActionsOverride($actionsProp);\n const [adjustment, setAdjustment] = useState<QuantityAdjustment | null>(null);\n\n // A gift line has no interactive quantity control, the same way CartItemRemove has no remove\n // control: a promotion-controlled quantity the shopper cannot validly change, and — with\n // removeOnDecrement — a path that would otherwise dispatch removeItem on a gift, discarding a\n // benefit T6 already sealed off from the remove button. Suppressing the stepper closes that path\n // under any composition. A store showing a gift's fixed quantity composes CartItemQuantityValue.\n if (item.gift) return null;\n\n const cart = $actions?.cart;\n\n const multiplier = effectiveMultiplier(slice);\n const decimals = decimalPlacesOf(multiplier);\n // min, max, and step are counts of purchasable units, and a cart line holds a whole number of\n // them, so a fractional bound has no valid value to land on. Left unrounded, a fractional `min` or\n // `max` becomes the clamp result and leaks into the delta — `decreaseQuantity({ by: 2.5 })` — which\n // the cart mutations reject. Rounding all three keeps every reachable value, and every dispatched\n // delta, an integer.\n const stepUnits = Math.max(1, Math.round(step ?? 1));\n const minUnits = Math.round(min);\n const maxUnits = max == null ? undefined : Math.round(max);\n const displayStep = roundTo(stepUnits * multiplier, decimals);\n const minDisplay = roundTo(minUnits * multiplier, decimals);\n const maxDisplay = maxUnits == null ? undefined : roundTo(maxUnits * multiplier, decimals);\n\n // Commit rather than change: `onValueChange` fires per parseable keystroke, so enforcing there\n // writes to the cart for every digit typed and snaps a value the shopper has not finished entering.\n function handleValueCommitted(value: number | null) {\n if (value == null) return;\n\n if (removeOnDecrement && value < minDisplay) {\n setAdjustment(null);\n void cart?.removeItem?.({ id: item.id });\n return;\n }\n\n const snapped = Math.min(Math.max(Math.round(value / multiplier), minUnits), maxUnits ?? Number.POSITIVE_INFINITY);\n const snappedDisplay = roundTo(snapped * multiplier, decimals);\n setAdjustment(resolveAdjustment(value, snappedDisplay, multiplier, minDisplay, maxDisplay));\n\n const delta = snapped - item.quantity;\n if (delta > 0) {\n void cart?.increaseQuantity?.({ id: item.id, by: delta });\n } else if (delta < 0) {\n void cart?.decreaseQuantity?.({ id: item.id, by: -delta });\n }\n }\n\n const effectiveMin = removeOnDecrement ? 0 : minDisplay;\n const computedClassName = removeOnDecrement ? (className ? `group ${className}` : 'group') : className;\n\n return (\n <BaseNumberField.Root\n {...props}\n value={roundTo(item.quantity * multiplier, decimals)}\n onValueCommitted={handleValueCommitted}\n // A shopper editing the field again makes the previous correction stale, so it is dropped as\n // soon as they type. Step and scrub reasons are excluded: those land on the grid by\n // construction and never raise a notice, so clearing on them would be dead code.\n onValueChange={(_value, details) => {\n if (details.reason === 'input-change' || details.reason === 'input-paste' || details.reason === 'input-clear') {\n setAdjustment(null);\n }\n }}\n min={effectiveMin}\n max={maxDisplay}\n step={displayStep}\n smallStep={displayStep}\n largeStep={roundTo(displayStep * 10, decimals)}\n snapOnStep\n // Base UI clamps text entry to min/max on its own, which would correct an out-of-bounds amount\n // before this component ever saw it — and a correction nobody can observe cannot be explained.\n allowOutOfRange\n className={computedClassName}\n data-fs-cart-item-quantity=\"\"\n {...(multiplier === 1 ? {} : { 'data-fs-cart-item-quantity-multiplier': String(multiplier) })}\n {...(adjustment\n ? {\n 'data-fs-cart-item-quantity-adjusted': '',\n 'data-fs-cart-item-quantity-adjusted-reason': adjustment.reason,\n }\n : {})}\n {...(removeOnDecrement && item.quantity <= minUnits ? { 'data-fs-cart-item-at-min': '' } : {})}\n >\n {children}\n {adjustment ? (\n <div role=\"alert\" data-fs-cart-item-quantity-validation=\"\">\n {validationMessage.replaceAll('{value}', String(adjustment.value))}\n </div>\n ) : null}\n </BaseNumberField.Root>\n );\n}\n\nexport function CartItemQuantityValue({ className, children, ...props }: CartItemQuantityValueProps) {\n const item = useCartItem();\n\n return (\n <span {...props} className={className} data-fs-cart-item-quantity-value=\"\">\n {children ?? String(item.quantity)}\n </span>\n );\n}\n\nexport function CartItemRemove({\n className,\n children,\n onClick,\n $actions: $actionsProp,\n ...props\n}: CartItemRemoveProps) {\n const $actions = useActionsOverride($actionsProp);\n const item = useCartItem();\n\n // A gift line has no remove control: the promotion granted the item, and this phase does not let a\n // shopper discard it. Suppressed here rather than by composition, because CartList renders one\n // template for every line — an author cannot leave the leaf out of gift rows alone. Covers the\n // remove-on-alert composition too; an unavailable gift is recovered by the cart-level\n // CartRemoveUnavailable batch and by server-side cart validation.\n if (item.gift) return null;\n\n function handleClick(e: React.MouseEvent<HTMLButtonElement>) {\n void $actions?.cart?.removeItem?.({ id: item.id });\n onClick?.(e);\n }\n\n return (\n <button\n {...props}\n type=\"button\"\n className={className}\n onClick={handleClick}\n data-fs-cart-item-remove=\"\"\n data-fs-button=\"\"\n >\n {children}\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Availability gates — read `variant.availability` from the core slice (F4).\n// UNKNOWN and any future non-available state fall back to the generic \"Unavailable\".\n// ---------------------------------------------------------------------------\n\nconst UNAVAILABLE_LABELS: Record<string, string> = {\n OUT_OF_STOCK: 'Out of stock',\n UNAVAILABLE: 'Cannot be delivered',\n};\n\nfunction unavailableLabel(availability: string): string {\n return UNAVAILABLE_LABELS[availability] ?? 'Unavailable';\n}\n\nexport function CartItemAvailable({ className, children, ...props }: CartItemAvailableProps) {\n const item = useCartItem();\n if (item.variant.availability !== 'AVAILABLE') return null;\n\n return (\n <div {...props} className={className} data-fs-cart-item-available=\"\">\n {children}\n </div>\n );\n}\n\nexport function CartItemUnavailable({ className, children, ...props }: CartItemUnavailableProps) {\n const item = useCartItem();\n const { availability } = item.variant;\n if (availability === 'AVAILABLE') return null;\n\n return (\n <div\n {...props}\n className={className}\n data-fs-cart-item-unavailable=\"\"\n data-fs-cart-item-unavailable-state={availability.toLowerCase()}\n >\n {children ?? unavailableLabel(availability)}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// F9 — gift marker. Inline, so `children` override the label rather than nesting a subtree; a gift\n// row's other needs are met by the row's own `data-fs-cart-item-gift-line` hook. \"Free\" is\n// operational copy, so it ships as a built-in default like the unavailability labels above, and moves\n// to the localization catalog rather than to a content model.\n// ---------------------------------------------------------------------------\n\nexport function CartItemGift({ className, children, ...props }: CartItemGiftProps) {\n const item = useCartItem();\n if (!item.gift) return null;\n\n return (\n <span {...props} className={className} data-fs-cart-item-gift=\"\" data-fs-text=\"\">\n {children ?? 'Free'}\n </span>\n );\n}\n"],"mappings":"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"}
@@ -2,6 +2,7 @@
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
4
  import { Field } from "../../particles/field/field.js";
5
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
5
6
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
6
7
  import { useSession } from "../session/session.js";
7
8
  import { Icon } from "../../particles/icon/icon.js";
@@ -143,7 +144,8 @@ function ClientPreferencesRoot({ $data: $dataProp, className, children }) {
143
144
  * `dataAttr`, `pendingAttr`, and `readonlyAttr` are fixed by each registered Control; only
144
145
  * `label`/`className` are merchant-configurable.
145
146
  */
146
- function OptInControl({ fieldKey, dataAttr, pendingAttr, readonlyAttr, label, className, $actions }) {
147
+ function OptInControl({ fieldKey, dataAttr, pendingAttr, readonlyAttr, label, className, $actions: $actionsProp }) {
148
+ const $actions = useActionsOverride($actionsProp);
147
149
  const { values, trackWrite, pendingKeys, errorByKey, readOnly } = useClientPreferencesContext();
148
150
  const fieldErrors = errorByKey[fieldKey] ?? [];
149
151
  const value = values[fieldKey];
@@ -1 +1 @@
1
- {"version":3,"file":"client-preferences.js","names":[],"sources":["../../../src/matter/client-preferences/client-preferences.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport type { ClientPreferenceKey, ClientProfileUserError } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-preferences` section matter (per ADR-0011: a matter is decomposed by DATA OWNERSHIP,\n * not per field). Both opt-ins — newsletter (`optinNewsLetter`) and save-personal-data\n * (`savePersonalData`) — are light booleans owned by the User Identity `ClientPreferences` entity\n * (a SIBLING of `Cart`, not nested under it, per the identity RFC), so they live under ONE\n * `ClientPreferencesRoot` that declares a single section fragment on `Query` selecting both fields.\n * The two merchant-facing Controls consume that one section context (ADR-0011 governs data\n * ownership, not the editor palette) and share one internal `OptInControl` implementation: the\n * section context, the `$data` → values seeding, the per-field pending/error tracking, the inlined\n * `Checkbox` + `Field` Control render, and dispatch via `$actions.identity.clientPreferencesUpdate`\n * (mirrors the `email-identification` matter; reads/writes follow ADR-0006 / ADR-0010 / ADR-0011).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated (ADR-0011), reads the ClientPreferences sibling\n// entity via Query. Authored as a raw string (not the codegen graphql() factory),\n// mirroring the EmailIdentification matter: the compiler derives the entity fragment\n// on `clientPreferences` and subscribes reactively. One section fragment owns both\n// opt-in fields (ADR-0011).\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientPreferencesData on Query {\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the `ClientPreferences` sibling entity. */\nexport type ClientPreferencesData = {\n clientPreferences?: {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n } | null;\n} | null;\n\nexport type ClientPreferencesRootProps = {\n /** `Query` slice forwarded by the reactive runtime (value of the section fragment). */\n $data?: ClientPreferencesData;\n className?: string;\n children?: ReactNode;\n};\n\nexport type ClientPreferencesControlProps = {\n label: string;\n className?: string;\n /** Injected by the compiler from `actions: { identity: ['clientPreferencesUpdate'] }`; the write surface. */\n $actions?: ComponentActions;\n};\n\nexport type NewsletterOptInControlProps = ClientPreferencesControlProps;\nexport type SavePersonalDataOptInControlProps = ClientPreferencesControlProps;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferencesRootDefinition = defineComponent<ClientPreferencesRootProps>({\n name: 'ClientPreferencesRoot',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesRoot,\n fragment,\n fetchStrategy: 'reactive',\n providesContext: 'client-preferences',\n requiresContext: 'session',\n editor: { label: 'Client Preferences Root', category: 'identity' },\n schema: {},\n guideline:\n 'Binding provider for the consent opt-in section; reads both opt-in values (optinNewsLetter, savePersonalData) from the ClientPreferences identity entity (a sibling of Cart, via Query.clientPreferences) and exposes them to its Controls. Reads session state from useSession(); must be inside SessionRoot. Writes flow through $actions.identity.clientPreferencesUpdate. Wrap a NewsletterOptInControl and/or a SavePersonalDataOptInControl.',\n});\n\nexport const NewsletterOptInControlDefinition = defineComponent<NewsletterOptInControlProps>({\n name: 'NewsletterOptInControl',\n importPath: '@vtex/faststore/components',\n component: NewsletterOptInControl,\n requiresContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Newsletter Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound newsletter opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the optinNewsLetter preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\nexport const SavePersonalDataOptInControlDefinition = defineComponent<SavePersonalDataOptInControlProps>({\n name: 'SavePersonalDataOptInControl',\n importPath: '@vtex/faststore/components',\n component: SavePersonalDataOptInControl,\n requiresContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Save Personal Data Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound save-personal-data opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the savePersonalData preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ntype ClientPreferencesContextValue = Readonly<{\n /** Current value of each opt-in, seeded from the query response (null → unchecked). */\n values: Readonly<Record<ClientPreferenceKey, boolean>>;\n /**\n * Records that a write for `key` is in flight and stores its returned userErrors under that key so\n * each Control surfaces ITS OWN pending/error. The Control owns the actual dispatch (it holds\n * `$actions`); it hands the resulting promise here for the section to track.\n */\n trackWrite: (key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => void;\n /**\n * The fields whose writes are currently in flight. A per-key set, not a single slot: concurrent\n * writes on different fields must each keep their own Control disabled until THEIR write settles.\n */\n pendingKeys: ReadonlySet<ClientPreferenceKey>;\n /** The userErrors returned by the most recent write, keyed by the field that produced them. */\n errorByKey: Readonly<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>;\n /**\n * True when the session reports `canEditData: false` — VTEX refuses profile-data edits for an\n * identified-but-unauthenticated claimed profile, so the Controls render disabled instead of\n * accepting writes that are doomed server-side (mirrors the email control).\n */\n readOnly: boolean;\n}>;\n\nconst CLIENT_PREFERENCES_UNSET = Symbol('ClientPreferencesContextUnset');\n\nconst ClientPreferencesContext = createContext<ClientPreferencesContextValue | typeof CLIENT_PREFERENCES_UNSET>(\n CLIENT_PREFERENCES_UNSET,\n);\n\nfunction useClientPreferencesContext(): ClientPreferencesContextValue {\n const ctx = useContext(ClientPreferencesContext);\n if (ctx === CLIENT_PREFERENCES_UNSET) {\n throw new Error('A client-preferences Control must be used inside a ClientPreferencesRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\n/**\n * Renders the client-preferences section Root: reads `$data` (the `ClientPreferences` sibling entity),\n * reads `useSession()` (which also enforces the `SessionRoot` ancestor, mirroring\n * `EmailIdentificationControl`), seeds both opt-in values from the section fragment, and publishes a\n * section context exposing both values plus per-field pending/error tracking. The write itself flows\n * through the Control's `$actions.identity.clientPreferencesUpdate` (ADR-0010); this Root tracks which\n * keys are in flight and stores the returned userErrors under each key so each Control surfaces ITS OWN\n * pending/error. When the session reports `canEditData: false` (identified but unauthenticated claimed\n * profile — VTEX refuses the write server-side) every Control renders disabled with its readonly\n * `data-fs-*` attribute, mirroring `EmailIdentificationControl`.\n */\nexport function ClientPreferencesRoot({ $data: $dataProp, className, children }: ClientPreferencesRootProps) {\n const $data = useDataOverride($dataProp);\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n const optinNewsLetter = Boolean($data?.clientPreferences?.optinNewsLetter ?? false);\n const savePersonalData = Boolean($data?.clientPreferences?.savePersonalData ?? false);\n\n const [pendingKeys, setPendingKeys] = useState<ReadonlySet<ClientPreferenceKey>>(new Set());\n const [errorByKey, setErrorByKey] = useState<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>(\n {},\n );\n\n const trackWrite = useCallback((key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => {\n // Clear the key's stale error at the start of a retry (parity with the email control clearing\n // its serverError on a new write).\n setErrorByKey((prev) => ({ ...prev, [key]: [] }));\n setPendingKeys((prev) => new Set(prev).add(key));\n void write\n .then((userErrors) => {\n setErrorByKey((prev) => ({ ...prev, [key]: userErrors }));\n })\n .finally(() => {\n setPendingKeys((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n });\n }, []);\n\n const values = useMemo(() => ({ optinNewsLetter, savePersonalData }) as const, [optinNewsLetter, savePersonalData]);\n\n const contextValue = useMemo<ClientPreferencesContextValue>(\n () => ({ values, trackWrite, pendingKeys, errorByKey, readOnly }),\n [values, trackWrite, pendingKeys, errorByKey, readOnly],\n );\n\n return (\n <ClientPreferencesContext value={contextValue}>\n <div className={className} data-fs-client-preferences-root=\"\">\n {children}\n </div>\n </ClientPreferencesContext>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\n/**\n * Renders one bound opt-in checkbox by inlining `Field` + `Checkbox` + `Icon` (no `CheckboxField`\n * particle). Reads ITS field's value, pending, and error from the section context and dispatches the\n * write through `$actions.identity.clientPreferencesUpdate({ [key]: value })`, handing the promise to\n * the section so the in-flight key and returned userErrors are tracked per field. The `fieldKey`,\n * `dataAttr`, `pendingAttr`, and `readonlyAttr` are fixed by each registered Control; only\n * `label`/`className` are merchant-configurable.\n */\nfunction OptInControl({\n fieldKey,\n dataAttr,\n pendingAttr,\n readonlyAttr,\n label,\n className,\n $actions,\n}: ClientPreferencesControlProps & {\n fieldKey: ClientPreferenceKey;\n dataAttr: string;\n pendingAttr: string;\n readonlyAttr: string;\n}) {\n const { values, trackWrite, pendingKeys, errorByKey, readOnly } = useClientPreferencesContext();\n const fieldErrors = errorByKey[fieldKey] ?? [];\n const value = values[fieldKey];\n const pending = pendingKeys.has(fieldKey);\n const disabled = pending || readOnly;\n const [firstError] = fieldErrors;\n const invalid = Boolean(firstError);\n\n function handleCheckedChange(checked: boolean) {\n const update = $actions?.identity?.clientPreferencesUpdate;\n if (!update) return;\n trackWrite(fieldKey, update({ [fieldKey]: checked }));\n }\n\n return (\n <div\n className={className}\n {...{ [dataAttr]: '' }}\n {...(pending ? { [pendingAttr]: '' } : {})}\n {...(readOnly ? { [readonlyAttr]: '' } : {})}\n >\n <Field.Root invalid={invalid} disabled={disabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root checked={value} onCheckedChange={handleCheckedChange} disabled={disabled}>\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {label}\n </Field.Label>\n {invalid && firstError ? <Field.Error match>{firstError.message}</Field.Error> : null}\n </Field.Root>\n </div>\n );\n}\n\nexport function NewsletterOptInControl(props: NewsletterOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"optinNewsLetter\"\n dataAttr=\"data-fs-newsletter-opt-in\"\n pendingAttr=\"data-fs-newsletter-opt-in-pending\"\n readonlyAttr=\"data-fs-newsletter-opt-in-readonly\"\n />\n );\n}\n\nexport function SavePersonalDataOptInControl(props: SavePersonalDataOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"savePersonalData\"\n dataAttr=\"data-fs-save-personal-data-opt-in\"\n pendingAttr=\"data-fs-save-personal-data-opt-in-pending\"\n readonlyAttr=\"data-fs-save-personal-data-opt-in-readonly\"\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferences = {\n Root: ClientPreferencesRoot,\n NewsletterOptInControl,\n SavePersonalDataOptInControl,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-preferences.js","names":[],"sources":["../../../src/matter/client-preferences/client-preferences.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useCallback, useContext, useMemo, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport type { ClientPreferenceKey, ClientProfileUserError } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-preferences` section matter (per ADR-0011: a matter is decomposed by DATA OWNERSHIP,\n * not per field). Both opt-ins — newsletter (`optinNewsLetter`) and save-personal-data\n * (`savePersonalData`) — are light booleans owned by the User Identity `ClientPreferences` entity\n * (a SIBLING of `Cart`, not nested under it, per the identity RFC), so they live under ONE\n * `ClientPreferencesRoot` that declares a single section fragment on `Query` selecting both fields.\n * The two merchant-facing Controls consume that one section context (ADR-0011 governs data\n * ownership, not the editor palette) and share one internal `OptInControl` implementation: the\n * section context, the `$data` → values seeding, the per-field pending/error tracking, the inlined\n * `Checkbox` + `Field` Control render, and dispatch via `$actions.identity.clientPreferencesUpdate`\n * (mirrors the `email-identification` matter; reads/writes follow ADR-0006 / ADR-0010 / ADR-0011).\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated (ADR-0011), reads the ClientPreferences sibling\n// entity via Query. Authored as a raw string (not the codegen graphql() factory),\n// mirroring the EmailIdentification matter: the compiler derives the entity fragment\n// on `clientPreferences` and subscribes reactively. One section fragment owns both\n// opt-in fields (ADR-0011).\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientPreferencesData on Query {\n clientPreferences {\n __typename\n id\n optinNewsLetter\n savePersonalData\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the `ClientPreferences` sibling entity. */\nexport type ClientPreferencesData = {\n clientPreferences?: {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n } | null;\n} | null;\n\nexport type ClientPreferencesRootProps = {\n /** `Query` slice forwarded by the reactive runtime (value of the section fragment). */\n $data?: ClientPreferencesData;\n className?: string;\n children?: ReactNode;\n};\n\nexport type ClientPreferencesControlProps = {\n label: string;\n className?: string;\n /** Injected by the compiler from `actions: { identity: ['clientPreferencesUpdate'] }`; the write surface. */\n $actions?: ComponentActions;\n};\n\nexport type NewsletterOptInControlProps = ClientPreferencesControlProps;\nexport type SavePersonalDataOptInControlProps = ClientPreferencesControlProps;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferencesRootDefinition = defineComponent<ClientPreferencesRootProps>({\n name: 'ClientPreferencesRoot',\n importPath: '@vtex/faststore/components',\n component: ClientPreferencesRoot,\n fragment,\n fetchStrategy: 'reactive',\n providesContext: 'client-preferences',\n requiresContext: 'session',\n editor: { label: 'Client Preferences Root', category: 'identity' },\n schema: {},\n guideline:\n 'Binding provider for the consent opt-in section; reads both opt-in values (optinNewsLetter, savePersonalData) from the ClientPreferences identity entity (a sibling of Cart, via Query.clientPreferences) and exposes them to its Controls. Reads session state from useSession(); must be inside SessionRoot. Writes flow through $actions.identity.clientPreferencesUpdate. Wrap a NewsletterOptInControl and/or a SavePersonalDataOptInControl.',\n});\n\nexport const NewsletterOptInControlDefinition = defineComponent<NewsletterOptInControlProps>({\n name: 'NewsletterOptInControl',\n importPath: '@vtex/faststore/components',\n component: NewsletterOptInControl,\n requiresContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Newsletter Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound newsletter opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the optinNewsLetter preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\nexport const SavePersonalDataOptInControlDefinition = defineComponent<SavePersonalDataOptInControlProps>({\n name: 'SavePersonalDataOptInControl',\n importPath: '@vtex/faststore/components',\n component: SavePersonalDataOptInControl,\n requiresContext: 'client-preferences',\n actions: { identity: ['clientPreferencesUpdate'] },\n editor: { label: 'Save Personal Data Opt-In Control', category: 'identity' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'The bound save-personal-data opt-in checkbox; must be a descendant of ClientPreferencesRoot. Toggling writes the savePersonalData preference via $actions.identity.clientPreferencesUpdate. Defaults to unchecked.',\n});\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\ntype ClientPreferencesContextValue = Readonly<{\n /** Current value of each opt-in, seeded from the query response (null → unchecked). */\n values: Readonly<Record<ClientPreferenceKey, boolean>>;\n /**\n * Records that a write for `key` is in flight and stores its returned userErrors under that key so\n * each Control surfaces ITS OWN pending/error. The Control owns the actual dispatch (it holds\n * `$actions`); it hands the resulting promise here for the section to track.\n */\n trackWrite: (key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => void;\n /**\n * The fields whose writes are currently in flight. A per-key set, not a single slot: concurrent\n * writes on different fields must each keep their own Control disabled until THEIR write settles.\n */\n pendingKeys: ReadonlySet<ClientPreferenceKey>;\n /** The userErrors returned by the most recent write, keyed by the field that produced them. */\n errorByKey: Readonly<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>;\n /**\n * True when the session reports `canEditData: false` — VTEX refuses profile-data edits for an\n * identified-but-unauthenticated claimed profile, so the Controls render disabled instead of\n * accepting writes that are doomed server-side (mirrors the email control).\n */\n readOnly: boolean;\n}>;\n\nconst CLIENT_PREFERENCES_UNSET = Symbol('ClientPreferencesContextUnset');\n\nconst ClientPreferencesContext = createContext<ClientPreferencesContextValue | typeof CLIENT_PREFERENCES_UNSET>(\n CLIENT_PREFERENCES_UNSET,\n);\n\nfunction useClientPreferencesContext(): ClientPreferencesContextValue {\n const ctx = useContext(ClientPreferencesContext);\n if (ctx === CLIENT_PREFERENCES_UNSET) {\n throw new Error('A client-preferences Control must be used inside a ClientPreferencesRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\n/**\n * Renders the client-preferences section Root: reads `$data` (the `ClientPreferences` sibling entity),\n * reads `useSession()` (which also enforces the `SessionRoot` ancestor, mirroring\n * `EmailIdentificationControl`), seeds both opt-in values from the section fragment, and publishes a\n * section context exposing both values plus per-field pending/error tracking. The write itself flows\n * through the Control's `$actions.identity.clientPreferencesUpdate` (ADR-0010); this Root tracks which\n * keys are in flight and stores the returned userErrors under each key so each Control surfaces ITS OWN\n * pending/error. When the session reports `canEditData: false` (identified but unauthenticated claimed\n * profile — VTEX refuses the write server-side) every Control renders disabled with its readonly\n * `data-fs-*` attribute, mirroring `EmailIdentificationControl`.\n */\nexport function ClientPreferencesRoot({ $data: $dataProp, className, children }: ClientPreferencesRootProps) {\n const $data = useDataOverride($dataProp);\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n const optinNewsLetter = Boolean($data?.clientPreferences?.optinNewsLetter ?? false);\n const savePersonalData = Boolean($data?.clientPreferences?.savePersonalData ?? false);\n\n const [pendingKeys, setPendingKeys] = useState<ReadonlySet<ClientPreferenceKey>>(new Set());\n const [errorByKey, setErrorByKey] = useState<Partial<Record<ClientPreferenceKey, readonly ClientProfileUserError[]>>>(\n {},\n );\n\n const trackWrite = useCallback((key: ClientPreferenceKey, write: Promise<readonly ClientProfileUserError[]>) => {\n // Clear the key's stale error at the start of a retry (parity with the email control clearing\n // its serverError on a new write).\n setErrorByKey((prev) => ({ ...prev, [key]: [] }));\n setPendingKeys((prev) => new Set(prev).add(key));\n void write\n .then((userErrors) => {\n setErrorByKey((prev) => ({ ...prev, [key]: userErrors }));\n })\n .finally(() => {\n setPendingKeys((prev) => {\n const next = new Set(prev);\n next.delete(key);\n return next;\n });\n });\n }, []);\n\n const values = useMemo(() => ({ optinNewsLetter, savePersonalData }) as const, [optinNewsLetter, savePersonalData]);\n\n const contextValue = useMemo<ClientPreferencesContextValue>(\n () => ({ values, trackWrite, pendingKeys, errorByKey, readOnly }),\n [values, trackWrite, pendingKeys, errorByKey, readOnly],\n );\n\n return (\n <ClientPreferencesContext value={contextValue}>\n <div className={className} data-fs-client-preferences-root=\"\">\n {children}\n </div>\n </ClientPreferencesContext>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Controls\n// ---------------------------------------------------------------------------\n\n/**\n * Renders one bound opt-in checkbox by inlining `Field` + `Checkbox` + `Icon` (no `CheckboxField`\n * particle). Reads ITS field's value, pending, and error from the section context and dispatches the\n * write through `$actions.identity.clientPreferencesUpdate({ [key]: value })`, handing the promise to\n * the section so the in-flight key and returned userErrors are tracked per field. The `fieldKey`,\n * `dataAttr`, `pendingAttr`, and `readonlyAttr` are fixed by each registered Control; only\n * `label`/`className` are merchant-configurable.\n */\nfunction OptInControl({\n fieldKey,\n dataAttr,\n pendingAttr,\n readonlyAttr,\n label,\n className,\n $actions: $actionsProp,\n}: ClientPreferencesControlProps & {\n fieldKey: ClientPreferenceKey;\n dataAttr: string;\n pendingAttr: string;\n readonlyAttr: string;\n}) {\n const $actions = useActionsOverride($actionsProp);\n const { values, trackWrite, pendingKeys, errorByKey, readOnly } = useClientPreferencesContext();\n const fieldErrors = errorByKey[fieldKey] ?? [];\n const value = values[fieldKey];\n const pending = pendingKeys.has(fieldKey);\n const disabled = pending || readOnly;\n const [firstError] = fieldErrors;\n const invalid = Boolean(firstError);\n\n function handleCheckedChange(checked: boolean) {\n const update = $actions?.identity?.clientPreferencesUpdate;\n if (!update) return;\n trackWrite(fieldKey, update({ [fieldKey]: checked }));\n }\n\n return (\n <div\n className={className}\n {...{ [dataAttr]: '' }}\n {...(pending ? { [pendingAttr]: '' } : {})}\n {...(readOnly ? { [readonlyAttr]: '' } : {})}\n >\n <Field.Root invalid={invalid} disabled={disabled} data-fs-field-root=\"\">\n <Field.Label>\n <Checkbox.Root checked={value} onCheckedChange={handleCheckedChange} disabled={disabled}>\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {label}\n </Field.Label>\n {invalid && firstError ? <Field.Error match>{firstError.message}</Field.Error> : null}\n </Field.Root>\n </div>\n );\n}\n\nexport function NewsletterOptInControl(props: NewsletterOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"optinNewsLetter\"\n dataAttr=\"data-fs-newsletter-opt-in\"\n pendingAttr=\"data-fs-newsletter-opt-in-pending\"\n readonlyAttr=\"data-fs-newsletter-opt-in-readonly\"\n />\n );\n}\n\nexport function SavePersonalDataOptInControl(props: SavePersonalDataOptInControlProps) {\n return (\n <OptInControl\n {...props}\n fieldKey=\"savePersonalData\"\n dataAttr=\"data-fs-save-personal-data-opt-in\"\n pendingAttr=\"data-fs-save-personal-data-opt-in-pending\"\n readonlyAttr=\"data-fs-save-personal-data-opt-in-readonly\"\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ClientPreferences = {\n Root: ClientPreferencesRoot,\n NewsletterOptInControl,\n SavePersonalDataOptInControl,\n};\n"],"mappings":"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"}
@@ -50,7 +50,7 @@ export declare const ClientProfilePhoneControlDefinition: ComponentDefinition<Cl
50
50
  * session reports `canEditData: false` the field renders disabled with
51
51
  * `data-fs-client-profile-phone-readonly`. Server `userErrors` are surfaced inline.
52
52
  */
53
- export declare function ClientProfilePhoneControl({ label, country, requirement, errorInvalid, errorRequired, className, $actions }: ClientProfilePhoneControlProps): import("react/jsx-runtime").JSX.Element;
53
+ export declare function ClientProfilePhoneControl({ label, country, requirement, errorInvalid, errorRequired, className, $actions: $actionsProp }: Readonly<ClientProfilePhoneControlProps>): import("react/jsx-runtime").JSX.Element;
54
54
  export declare const ClientProfile: {
55
55
  Root: typeof ClientProfileRoot;
56
56
  PhoneControl: typeof ClientProfilePhoneControl;
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
4
5
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
5
6
  import { useSession } from "../session/session.js";
6
7
  import { PhoneField, toE164, validatePhoneNumber } from "../../particles/phone-field/phone-field.js";
@@ -121,7 +122,8 @@ const ClientProfilePhoneControlDefinition = defineComponent({
121
122
  * session reports `canEditData: false` the field renders disabled with
122
123
  * `data-fs-client-profile-phone-readonly`. Server `userErrors` are surfaced inline.
123
124
  */
124
- function ClientProfilePhoneControl({ label = "Phone", country = "US", requirement = "mandatory", errorInvalid = "Invalid phone number", errorRequired = "Phone number is required", className, $actions }) {
125
+ function ClientProfilePhoneControl({ label = "Phone", country = "US", requirement = "mandatory", errorInvalid = "Invalid phone number", errorRequired = "Phone number is required", className, $actions: $actionsProp }) {
126
+ const $actions = useActionsOverride($actionsProp);
125
127
  const { phone: savedPhone } = useClientProfileContext();
126
128
  const { canEditData } = useSession();
127
129
  const readOnly = !canEditData;
@@ -1 +1 @@
1
- {"version":3,"file":"client-profile.js","names":[],"sources":["../../../src/matter/client-profile/client-profile.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useContext, useMemo, useRef, useState } from 'react';\nimport type { PhoneCountry } from '../../particles/phone-field/phone-field.js';\nimport { PhoneField, toE164, validatePhoneNumber } from '../../particles/phone-field/phone-field.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-profile` section matter (per ADR-0011: a matter is decomposed by DATA OWNERSHIP, not\n * per field). The shopper-profile fields recorded on the cart's `clientProfileData` live on the User\n * Identity `ClientProfile` entity — a SIBLING of `Cart`, not nested under it. They are read through\n * `Query.clientProfile` and written through the shared `$actions.identity.clientProfileUpdate`\n * action, which performs a partial write that leaves the fields it is not given untouched.\n *\n * This matter wires only `phone`: a `ClientProfilePhoneControl` that composes the reusable,\n * business-agnostic `PhoneField` particle (reused later in MyAccount / extensions; the binding here\n * owns the data source and persistence). Room is kept for future profile Controls on the same Root.\n * Reads follow the colocated-fragment Root pattern (ADR-0006, fetchStrategy 'reactive'), mirroring\n * `EmailIdentificationControl`; writes flow through `$actions.identity` (ADR-0010). The write is\n * pessimistic (awaits the mutation) and is dispatched on blur — form-bound saving via the profile-form\n * CTA is a follow-up, not implemented here.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on the ClientProfile sibling entity (ADR-0011)\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientProfilePhoneData on Query {\n clientProfile {\n __typename\n id\n phone\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Raw data shape of the section fragment\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the `ClientProfile` sibling entity. */\nexport type ClientProfileData = {\n clientProfile?: {\n phone?: string | null;\n } | null;\n} | null;\n\n// ---------------------------------------------------------------------------\n// Context — the Root publishes the seeded profile values to its Controls\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileContextValue = Readonly<{\n /** Current value of each profile field, seeded from the ClientProfile entity (null → empty). */\n phone: string;\n}>;\n\nconst CLIENT_PROFILE_UNSET = Symbol('ClientProfileContextUnset');\n\nconst ClientProfileContext = createContext<ClientProfileContextValue | typeof CLIENT_PROFILE_UNSET>(\n CLIENT_PROFILE_UNSET,\n);\n\nfunction useClientProfileContext(): ClientProfileContextValue {\n const ctx = useContext(ClientProfileContext);\n if (ctx === CLIENT_PROFILE_UNSET) {\n throw new Error('A client-profile Control must be used inside a ClientProfileRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileRootProps = {\n /** ClientProfile entity slice forwarded by the reactive runtime (value of the section fragment). */\n $data?: ClientProfileData;\n className?: string;\n children?: ReactNode;\n};\n\nexport const ClientProfileRootDefinition = defineComponent<ClientProfileRootProps>({\n name: 'ClientProfileRoot',\n importPath: '@vtex/faststore/components',\n component: ClientProfileRoot,\n fragment,\n fetchStrategy: 'reactive',\n providesContext: 'client-profile',\n requiresContext: 'session',\n editor: { label: 'Client Profile Root', category: 'identity' },\n schema: {},\n guideline:\n 'Binding provider for the shopper-profile section; reads the profile fields (today: phone) from the ClientProfile entity (Query.clientProfile) and exposes them to its Controls. Writes flow through $actions.identity.clientProfileUpdate. Must be inside SessionRoot. Wrap a ClientProfilePhoneControl.',\n});\n\n/**\n * Renders the client-profile section Root: reads `$data` (the `ClientProfile` sibling entity), seeds\n * the profile values from the colocated fragment, and publishes a section context. It also reads\n * `useSession()` — which enforces the `SessionRoot` ancestor. Writes are owned by the Control's\n * `$actions.identity` (ADR-0010); the Root only supplies the pre-fill.\n */\nexport function ClientProfileRoot({ $data: $dataProp, className, children }: ClientProfileRootProps) {\n const $data = useDataOverride($dataProp);\n // Reads identification state and, as a side effect, throws when rendered outside <SessionRoot>.\n useSession();\n\n const phone = $data?.clientProfile?.phone ?? '';\n\n const contextValue = useMemo<ClientProfileContextValue>(() => ({ phone }), [phone]);\n\n return (\n <ClientProfileContext value={contextValue}>\n <div className={className} data-fs-client-profile-root=\"\">\n {children}\n </div>\n </ClientProfileContext>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Phone Control\n// ---------------------------------------------------------------------------\n\ntype PhoneFieldRequirement = 'mandatory' | 'optional' | 'hidden';\n\nexport type ClientProfilePhoneControlProps = {\n /** Visible field label. */\n label?: string;\n /** Selects the phone mask and validation rule. */\n country?: PhoneCountry;\n /** Controls field visibility and required validation. */\n requirement?: PhoneFieldRequirement;\n /** Error string shown when the input format is invalid. */\n errorInvalid?: string;\n /** Error string shown when the mandatory field is left empty. */\n errorRequired?: string;\n className?: string;\n /** Injected by the compiler from `actions: { identity: ['clientProfileUpdate'] }`; the write surface. */\n $actions?: ComponentActions;\n};\n\nexport const ClientProfilePhoneControlDefinition = defineComponent<ClientProfilePhoneControlProps>({\n name: 'ClientProfilePhoneControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfilePhoneControl,\n requiresContext: 'client-profile',\n actions: { identity: ['clientProfileUpdate'] },\n editor: { label: 'Client Profile Phone Control', category: 'identity' },\n schema: {\n label: { type: 'string', default: 'Phone', label: 'Label' },\n country: { type: 'string', default: 'US', label: 'Country (US or BR)' },\n requirement: { type: 'string', default: 'mandatory', label: 'Requirement' },\n errorInvalid: { type: 'string', default: 'Invalid phone number', label: 'Invalid error message' },\n errorRequired: { type: 'string', default: 'Phone number is required', label: 'Required error message' },\n },\n guideline:\n 'The bound phone field; must be a descendant of ClientProfileRoot. Reads the saved phone from the ClientProfile entity and persists it on blur via $actions.identity.clientProfileUpdate({ phone }). Composes the reusable PhoneField particle.',\n});\n\n/**\n * Renders the bound phone field by composing the reusable `PhoneField` particle. Reads the saved\n * phone from the section context, and on a **changed** value persists only `phone` — normalized to\n * E.164 — through `$actions.identity.clientProfileUpdate({ phone })` on blur (pessimistic,\n * mirroring `EmailIdentificationControl`). While the write is in flight the field is disabled and\n * `data-fs-client-profile-phone-pending` is present so a checkout-CTA guard can block. When the\n * session reports `canEditData: false` the field renders disabled with\n * `data-fs-client-profile-phone-readonly`. Server `userErrors` are surfaced inline.\n */\nexport function ClientProfilePhoneControl({\n label = 'Phone',\n country = 'US',\n requirement = 'mandatory',\n errorInvalid = 'Invalid phone number',\n errorRequired = 'Phone number is required',\n className,\n $actions,\n}: ClientProfilePhoneControlProps) {\n const { phone: savedPhone } = useClientProfileContext();\n // VTEX refuses profile edits for an identified-but-unauthenticated claimed profile; render the\n // field read-only instead of accepting a write that is doomed server-side (mirrors\n // EmailIdentificationControl).\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n const [draft, setDraft] = useState(savedPhone);\n const [pending, setPending] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n\n // Re-seed the local draft when the saved value changes (e.g. cache update after a write, or a\n // logged-in shopper's profile resolving). Like the email control's pre-fill sync, this overwrites\n // any in-progress draft — the profile is the source of truth when it changes.\n const prevSavedRef = useRef(savedPhone);\n if (prevSavedRef.current !== savedPhone) {\n // react-doctor-disable-next-line no-ref-current-in-render -- paired with the setDraft call right below, both replay together\n prevSavedRef.current = savedPhone;\n setDraft(savedPhone);\n }\n\n function handleValueChange(next: string) {\n setDraft(next);\n if (serverError) setServerError(null);\n }\n\n async function handleBlur() {\n const clientProfileUpdate = $actions?.identity?.clientProfileUpdate;\n if (!clientProfileUpdate) return;\n // Untouched → nothing to persist.\n if (draft === savedPhone) return;\n // Client-side invalid → skip the write and let the PhoneField's inline error stand; don't\n // rely on server userErrors for a value the mask/validator already rejects. Mirrors the\n // validity the PhoneField particle computes on blur (mandatory-empty or bad format).\n const isEmpty = !draft.trim();\n if (requirement === 'mandatory' && isEmpty) return;\n if (!isEmpty && !validatePhoneNumber(draft, country)) return;\n const phone = isEmpty ? '' : toE164(draft, country);\n // Unchanged after normalization (e.g. the saved \"+12015551234\" re-typed as \"2015551234\") →\n // nothing to persist either.\n if (phone === savedPhone) return;\n\n setPending(true);\n setServerError(null);\n try {\n const errors = (await clientProfileUpdate({ phone })) ?? [];\n if (errors.length > 0) setServerError(errors[0]?.message ?? null);\n } finally {\n setPending(false);\n }\n }\n\n return (\n <div\n className={className}\n data-fs-client-profile-phone-control=\"\"\n {...(pending ? { 'data-fs-client-profile-phone-pending': '' } : {})}\n {...(readOnly ? { 'data-fs-client-profile-phone-readonly': '' } : {})}\n >\n <PhoneField\n label={label}\n country={country}\n requirement={requirement}\n errorInvalid={errorInvalid}\n errorRequired={errorRequired}\n value={draft}\n onValueChange={handleValueChange}\n onBlur={handleBlur}\n isDisabled={pending || readOnly}\n invalid={serverError != null || undefined}\n error={serverError ?? undefined}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ClientProfile = {\n Root: ClientProfileRoot,\n PhoneControl: ClientProfilePhoneControl,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"client-profile.js","names":[],"sources":["../../../src/matter/client-profile/client-profile.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type ReactNode, useContext, useMemo, useRef, useState } from 'react';\nimport type { PhoneCountry } from '../../particles/phone-field/phone-field.js';\nimport { PhoneField, toE164, validatePhoneNumber } from '../../particles/phone-field/phone-field.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport { useDataOverride } from '../../sdk/data/data-override-context.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-profile` section matter (per ADR-0011: a matter is decomposed by DATA OWNERSHIP, not\n * per field). The shopper-profile fields recorded on the cart's `clientProfileData` live on the User\n * Identity `ClientProfile` entity — a SIBLING of `Cart`, not nested under it. They are read through\n * `Query.clientProfile` and written through the shared `$actions.identity.clientProfileUpdate`\n * action, which performs a partial write that leaves the fields it is not given untouched.\n *\n * This matter wires only `phone`: a `ClientProfilePhoneControl` that composes the reusable,\n * business-agnostic `PhoneField` particle (reused later in MyAccount / extensions; the binding here\n * owns the data source and persistence). Room is kept for future profile Controls on the same Root.\n * Reads follow the colocated-fragment Root pattern (ADR-0006, fetchStrategy 'reactive'), mirroring\n * `EmailIdentificationControl`; writes flow through `$actions.identity` (ADR-0010). The write is\n * pessimistic (awaits the mutation) and is dispatched on blur — form-bound saving via the profile-form\n * CTA is a follow-up, not implemented here.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on the ClientProfile sibling entity (ADR-0011)\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientProfilePhoneData on Query {\n clientProfile {\n __typename\n id\n phone\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Raw data shape of the section fragment\n// ---------------------------------------------------------------------------\n\n/** The `$data` slice forwarded by the reactive runtime — the `ClientProfile` sibling entity. */\nexport type ClientProfileData = {\n clientProfile?: {\n phone?: string | null;\n } | null;\n} | null;\n\n// ---------------------------------------------------------------------------\n// Context — the Root publishes the seeded profile values to its Controls\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileContextValue = Readonly<{\n /** Current value of each profile field, seeded from the ClientProfile entity (null → empty). */\n phone: string;\n}>;\n\nconst CLIENT_PROFILE_UNSET = Symbol('ClientProfileContextUnset');\n\nconst ClientProfileContext = createContext<ClientProfileContextValue | typeof CLIENT_PROFILE_UNSET>(\n CLIENT_PROFILE_UNSET,\n);\n\nfunction useClientProfileContext(): ClientProfileContextValue {\n const ctx = useContext(ClientProfileContext);\n if (ctx === CLIENT_PROFILE_UNSET) {\n throw new Error('A client-profile Control must be used inside a ClientProfileRoot.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileRootProps = {\n /** ClientProfile entity slice forwarded by the reactive runtime (value of the section fragment). */\n $data?: ClientProfileData;\n className?: string;\n children?: ReactNode;\n};\n\nexport const ClientProfileRootDefinition = defineComponent<ClientProfileRootProps>({\n name: 'ClientProfileRoot',\n importPath: '@vtex/faststore/components',\n component: ClientProfileRoot,\n fragment,\n fetchStrategy: 'reactive',\n providesContext: 'client-profile',\n requiresContext: 'session',\n editor: { label: 'Client Profile Root', category: 'identity' },\n schema: {},\n guideline:\n 'Binding provider for the shopper-profile section; reads the profile fields (today: phone) from the ClientProfile entity (Query.clientProfile) and exposes them to its Controls. Writes flow through $actions.identity.clientProfileUpdate. Must be inside SessionRoot. Wrap a ClientProfilePhoneControl.',\n});\n\n/**\n * Renders the client-profile section Root: reads `$data` (the `ClientProfile` sibling entity), seeds\n * the profile values from the colocated fragment, and publishes a section context. It also reads\n * `useSession()` — which enforces the `SessionRoot` ancestor. Writes are owned by the Control's\n * `$actions.identity` (ADR-0010); the Root only supplies the pre-fill.\n */\nexport function ClientProfileRoot({ $data: $dataProp, className, children }: ClientProfileRootProps) {\n const $data = useDataOverride($dataProp);\n // Reads identification state and, as a side effect, throws when rendered outside <SessionRoot>.\n useSession();\n\n const phone = $data?.clientProfile?.phone ?? '';\n\n const contextValue = useMemo<ClientProfileContextValue>(() => ({ phone }), [phone]);\n\n return (\n <ClientProfileContext value={contextValue}>\n <div className={className} data-fs-client-profile-root=\"\">\n {children}\n </div>\n </ClientProfileContext>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Phone Control\n// ---------------------------------------------------------------------------\n\ntype PhoneFieldRequirement = 'mandatory' | 'optional' | 'hidden';\n\nexport type ClientProfilePhoneControlProps = {\n /** Visible field label. */\n label?: string;\n /** Selects the phone mask and validation rule. */\n country?: PhoneCountry;\n /** Controls field visibility and required validation. */\n requirement?: PhoneFieldRequirement;\n /** Error string shown when the input format is invalid. */\n errorInvalid?: string;\n /** Error string shown when the mandatory field is left empty. */\n errorRequired?: string;\n className?: string;\n /** Injected by the compiler from `actions: { identity: ['clientProfileUpdate'] }`; the write surface. */\n $actions?: ComponentActions;\n};\n\nexport const ClientProfilePhoneControlDefinition = defineComponent<ClientProfilePhoneControlProps>({\n name: 'ClientProfilePhoneControl',\n importPath: '@vtex/faststore/components',\n component: ClientProfilePhoneControl,\n requiresContext: 'client-profile',\n actions: { identity: ['clientProfileUpdate'] },\n editor: { label: 'Client Profile Phone Control', category: 'identity' },\n schema: {\n label: { type: 'string', default: 'Phone', label: 'Label' },\n country: { type: 'string', default: 'US', label: 'Country (US or BR)' },\n requirement: { type: 'string', default: 'mandatory', label: 'Requirement' },\n errorInvalid: { type: 'string', default: 'Invalid phone number', label: 'Invalid error message' },\n errorRequired: { type: 'string', default: 'Phone number is required', label: 'Required error message' },\n },\n guideline:\n 'The bound phone field; must be a descendant of ClientProfileRoot. Reads the saved phone from the ClientProfile entity and persists it on blur via $actions.identity.clientProfileUpdate({ phone }). Composes the reusable PhoneField particle.',\n});\n\n/**\n * Renders the bound phone field by composing the reusable `PhoneField` particle. Reads the saved\n * phone from the section context, and on a **changed** value persists only `phone` — normalized to\n * E.164 — through `$actions.identity.clientProfileUpdate({ phone })` on blur (pessimistic,\n * mirroring `EmailIdentificationControl`). While the write is in flight the field is disabled and\n * `data-fs-client-profile-phone-pending` is present so a checkout-CTA guard can block. When the\n * session reports `canEditData: false` the field renders disabled with\n * `data-fs-client-profile-phone-readonly`. Server `userErrors` are surfaced inline.\n */\nexport function ClientProfilePhoneControl({\n label = 'Phone',\n country = 'US',\n requirement = 'mandatory',\n errorInvalid = 'Invalid phone number',\n errorRequired = 'Phone number is required',\n className,\n $actions: $actionsProp,\n}: Readonly<ClientProfilePhoneControlProps>) {\n const $actions = useActionsOverride($actionsProp);\n const { phone: savedPhone } = useClientProfileContext();\n // VTEX refuses profile edits for an identified-but-unauthenticated claimed profile; render the\n // field read-only instead of accepting a write that is doomed server-side (mirrors\n // EmailIdentificationControl).\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n const [draft, setDraft] = useState(savedPhone);\n const [pending, setPending] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n\n // Re-seed the local draft when the saved value changes (e.g. cache update after a write, or a\n // logged-in shopper's profile resolving). Like the email control's pre-fill sync, this overwrites\n // any in-progress draft — the profile is the source of truth when it changes.\n const prevSavedRef = useRef(savedPhone);\n if (prevSavedRef.current !== savedPhone) {\n // react-doctor-disable-next-line no-ref-current-in-render -- paired with the setDraft call right below, both replay together\n prevSavedRef.current = savedPhone;\n setDraft(savedPhone);\n }\n\n function handleValueChange(next: string) {\n setDraft(next);\n if (serverError) setServerError(null);\n }\n\n async function handleBlur() {\n const clientProfileUpdate = $actions?.identity?.clientProfileUpdate;\n if (!clientProfileUpdate) return;\n // Untouched → nothing to persist.\n if (draft === savedPhone) return;\n // Client-side invalid → skip the write and let the PhoneField's inline error stand; don't\n // rely on server userErrors for a value the mask/validator already rejects. Mirrors the\n // validity the PhoneField particle computes on blur (mandatory-empty or bad format).\n const isEmpty = !draft.trim();\n if (requirement === 'mandatory' && isEmpty) return;\n if (!isEmpty && !validatePhoneNumber(draft, country)) return;\n const phone = isEmpty ? '' : toE164(draft, country);\n // Unchanged after normalization (e.g. the saved \"+12015551234\" re-typed as \"2015551234\") →\n // nothing to persist either.\n if (phone === savedPhone) return;\n\n setPending(true);\n setServerError(null);\n try {\n const errors = (await clientProfileUpdate({ phone })) ?? [];\n if (errors.length > 0) setServerError(errors[0]?.message ?? null);\n } finally {\n setPending(false);\n }\n }\n\n return (\n <div\n className={className}\n data-fs-client-profile-phone-control=\"\"\n {...(pending ? { 'data-fs-client-profile-phone-pending': '' } : {})}\n {...(readOnly ? { 'data-fs-client-profile-phone-readonly': '' } : {})}\n >\n <PhoneField\n label={label}\n country={country}\n requirement={requirement}\n errorInvalid={errorInvalid}\n errorRequired={errorRequired}\n value={draft}\n onValueChange={handleValueChange}\n onBlur={handleBlur}\n isDisabled={pending || readOnly}\n invalid={serverError != null || undefined}\n error={serverError ?? undefined}\n />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace export\n// ---------------------------------------------------------------------------\n\nexport const ClientProfile = {\n Root: ClientProfileRoot,\n PhoneControl: ClientProfilePhoneControl,\n};\n"],"mappings":"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"}
@@ -84,7 +84,7 @@ export declare const ClientProfileFormSubmitButtonDefinition: ComponentDefinitio
84
84
  * nothing outside this form can react to it. Whether a checkout sequences on a completed form is left to
85
85
  * the flow-of-steps RFC (#611).
86
86
  */
87
- export declare function ClientProfileFormRoot({ country, documentType, className, children, $data: $dataProp, $actions }: ClientProfileFormRootProps): import("react/jsx-runtime").JSX.Element;
87
+ export declare function ClientProfileFormRoot({ country, documentType, className, children, $data: $dataProp, $actions: $actionsProp }: Readonly<ClientProfileFormRootProps>): import("react/jsx-runtime").JSX.Element;
88
88
  export declare function ClientProfileFormFieldset({ legend, section, className, children }: ClientProfileFormFieldsetProps): import("react/jsx-runtime").JSX.Element;
89
89
  export declare function ClientProfileFormEmailField({ label, className }: ClientProfileFormEmailFieldProps): import("react/jsx-runtime").JSX.Element;
90
90
  export declare function ClientProfileFormFirstNameField({ label, className }: ClientProfileFormFirstNameFieldProps): import("react/jsx-runtime").JSX.Element;
@@ -2,6 +2,7 @@
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
4
  import { Field } from "../../particles/field/field.js";
5
+ import { useActionsOverride } from "../../sdk/actions/actions-override-context.js";
5
6
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
6
7
  import { useSession } from "../session/session.js";
7
8
  import { Icon } from "../../particles/icon/icon.js";
@@ -285,8 +286,9 @@ function validateDraft(draft, country, documentType) {
285
286
  * nothing outside this form can react to it. Whether a checkout sequences on a completed form is left to
286
287
  * the flow-of-steps RFC (#611).
287
288
  */
288
- function ClientProfileFormRoot({ country = "US", documentType, className, children, $data: $dataProp, $actions }) {
289
+ function ClientProfileFormRoot({ country = "US", documentType, className, children, $data: $dataProp, $actions: $actionsProp }) {
289
290
  const $data = useDataOverride($dataProp);
291
+ const $actions = useActionsOverride($actionsProp);
290
292
  const { canEditData } = useSession();
291
293
  const readOnly = !canEditData;
292
294
  const savedEmail = $data?.clientProfile?.email ?? "";