@vtex/faststore 0.0.6 → 0.0.7

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 (132) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/dist/catalog.json +161 -20
  3. package/dist/cli/UnreachableCaseError-Crd168Vu.js +10 -0
  4. package/dist/cli/UnreachableCaseError-Crd168Vu.js.map +1 -0
  5. package/dist/cli/cacheControl-CEojH_TM.js +158 -0
  6. package/dist/cli/cacheControl-CEojH_TM.js.map +1 -0
  7. package/dist/cli/cachePolicy-m2vASMRW.js +26 -0
  8. package/dist/cli/cachePolicy-m2vASMRW.js.map +1 -0
  9. package/dist/cli/computeCoreSchemaHash-DPJZj-qJ.js +10 -0
  10. package/dist/cli/computeCoreSchemaHash-DPJZj-qJ.js.map +1 -0
  11. package/dist/cli/default-BCQHcwbL.js +253 -0
  12. package/dist/cli/default-BCQHcwbL.js.map +1 -0
  13. package/dist/cli/disableSuggestions-yO9UqiWn.js +21 -0
  14. package/dist/cli/disableSuggestions-yO9UqiWn.js.map +1 -0
  15. package/dist/cli/dist-C1JhdHfo.js +2001 -0
  16. package/dist/cli/dist-C1JhdHfo.js.map +1 -0
  17. package/dist/cli/graphql-DG9thCDG.js +14589 -0
  18. package/dist/cli/graphql-DG9thCDG.js.map +1 -0
  19. package/dist/cli/index.d.ts +350 -0
  20. package/dist/cli/index.js +35047 -0
  21. package/dist/cli/index.js.map +1 -0
  22. package/dist/cli/index.min-BsIHDfFK.js +613 -0
  23. package/dist/cli/index.min-BsIHDfFK.js.map +1 -0
  24. package/dist/cli/inlineTrace-uLgOJ3TH.js +52 -0
  25. package/dist/cli/inlineTrace-uLgOJ3TH.js.map +1 -0
  26. package/dist/cli/internalPlugin-DnuwN4_4.js +11 -0
  27. package/dist/cli/internalPlugin-DnuwN4_4.js.map +1 -0
  28. package/dist/cli/packageVersion-DQAnIEil.js +6 -0
  29. package/dist/cli/packageVersion-DQAnIEil.js.map +1 -0
  30. package/dist/cli/rolldown-runtime-BMI-E3GI.js +44 -0
  31. package/dist/cli/schemaIsSubgraph-DoGh3OYj.js +17 -0
  32. package/dist/cli/schemaIsSubgraph-DoGh3OYj.js.map +1 -0
  33. package/dist/cli/schemaReporting-K-me-3-v.js +199 -0
  34. package/dist/cli/schemaReporting-K-me-3-v.js.map +1 -0
  35. package/dist/cli/server-runtime.js +49859 -0
  36. package/dist/cli/server-runtime.js.map +1 -0
  37. package/dist/cli/traceTreeBuilder-BIcV2U7b.js +9118 -0
  38. package/dist/cli/traceTreeBuilder-BIcV2U7b.js.map +1 -0
  39. package/dist/cli/usageReporting-Bpl8vSZX.js +10201 -0
  40. package/dist/cli/usageReporting-Bpl8vSZX.js.map +1 -0
  41. package/dist/cli/v4-CBhFvC49.js +42 -0
  42. package/dist/cli/v4-CBhFvC49.js.map +1 -0
  43. package/dist/components.d.ts +5 -3
  44. package/dist/components.js +5 -3
  45. package/dist/content-types.json +93 -0
  46. package/dist/core/dist/define-component.d.ts +37 -0
  47. package/dist/core/dist/index.d.ts +2 -2
  48. package/dist/docs/account-entry-point.md +1 -1
  49. package/dist/docs/account-menu-identity.md +160 -0
  50. package/dist/docs/account-menu.md +106 -23
  51. package/dist/docs/avatar.md +103 -0
  52. package/dist/docs/my-account-profile.md +1 -1
  53. package/dist/docs/product-shelf.md +20 -13
  54. package/dist/index.d.ts +2 -2
  55. package/dist/internal/client.js +1 -1
  56. package/dist/internal/content.d.ts +2 -2
  57. package/dist/internal/server.d.ts +4 -1
  58. package/dist/internal/server.js +4 -1
  59. package/dist/matter/account-access-gate/account-access-gate.js.map +1 -1
  60. package/dist/matter/account-entry-point/account-entry-point.js +1 -1
  61. package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
  62. package/dist/matter/account-menu/account-menu.d.ts +70 -0
  63. package/dist/matter/account-menu/account-menu.js +201 -9
  64. package/dist/matter/account-menu/account-menu.js.map +1 -1
  65. package/dist/matter/account-menu/sign-out-failure.js +50 -0
  66. package/dist/matter/account-menu/sign-out-failure.js.map +1 -0
  67. package/dist/matter/account-menu-identity/account-menu-identity.d.ts +21 -0
  68. package/dist/matter/account-menu-identity/account-menu-identity.js +88 -0
  69. package/dist/matter/account-menu-identity/account-menu-identity.js.map +1 -0
  70. package/dist/matter/card-entry/card-entry.js +1 -0
  71. package/dist/matter/card-entry/card-entry.js.map +1 -1
  72. package/dist/matter/cart/cart.js +1 -1
  73. package/dist/matter/client-preferences/client-preferences-fieldset.js +2 -2
  74. package/dist/matter/client-profile-form/client-profile-fieldset.js +2 -2
  75. package/dist/matter/identity-summary/identity-summary.js +2 -1
  76. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  77. package/dist/matter/my-account-profile/my-account-profile.d.ts +3 -0
  78. package/dist/matter/my-account-profile/my-account-profile.js +2 -1
  79. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  80. package/dist/matter/payment/payment-controller.js +1 -0
  81. package/dist/matter/payment/payment-controller.js.map +1 -1
  82. package/dist/matter/payment/payment.js +1 -0
  83. package/dist/matter/payment/payment.js.map +1 -1
  84. package/dist/matter/product-shelf/product-shelf.d.ts +61 -31
  85. package/dist/matter/product-shelf/product-shelf.js +157 -57
  86. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  87. package/dist/matter/saved-cards/saved-cards.js +1 -0
  88. package/dist/matter/saved-cards/saved-cards.js.map +1 -1
  89. package/dist/matter/shipping-address/shipping-address.js +2 -2
  90. package/dist/particles/avatar/avatar.d.ts +17 -0
  91. package/dist/particles/avatar/avatar.js +65 -0
  92. package/dist/particles/avatar/avatar.js.map +1 -0
  93. package/dist/schema.graphql +65 -26
  94. package/dist/sdk/analytics/use-analytics-actions.js +2 -1
  95. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -1
  96. package/dist/sdk/cart/use-cart-actions.js +9 -8
  97. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  98. package/dist/sdk/cart/use-cart-validate-on-open.js +2 -1
  99. package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
  100. package/dist/sdk/content/create-content-runtime.d.ts +22 -1
  101. package/dist/sdk/content/create-content-runtime.js +89 -1
  102. package/dist/sdk/content/create-content-runtime.js.map +1 -1
  103. package/dist/sdk/data/empty-content-value.js +16 -0
  104. package/dist/sdk/data/empty-content-value.js.map +1 -0
  105. package/dist/sdk/data/hot-data-provider.js +2 -1
  106. package/dist/sdk/data/hot-data-provider.js.map +1 -1
  107. package/dist/sdk/data/index.js +2 -2
  108. package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
  109. package/dist/sdk/data/variables-from-content.d.ts +43 -0
  110. package/dist/sdk/data/variables-from-content.js +57 -0
  111. package/dist/sdk/data/variables-from-content.js.map +1 -0
  112. package/dist/sdk/generated/gql.js +2 -1
  113. package/dist/sdk/generated/gql.js.map +1 -1
  114. package/dist/sdk/generated/graphql.js +85 -1
  115. package/dist/sdk/generated/graphql.js.map +1 -1
  116. package/dist/sdk/identity/use-identity-actions.js +5 -4
  117. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  118. package/dist/sdk/payment/use-payment-actions.js +1 -1
  119. package/dist/sdk/payment/use-place-order.js +1 -1
  120. package/dist/sdk/region/use-region-actions.js +2 -1
  121. package/dist/sdk/region/use-region-actions.js.map +1 -1
  122. package/dist/sdk/session/read-server-session.d.ts +21 -0
  123. package/dist/sdk/session/read-server-session.js +61 -0
  124. package/dist/sdk/session/read-server-session.js.map +1 -0
  125. package/dist/sdk/session/use-session-actions.js +5 -4
  126. package/dist/sdk/session/use-session-actions.js.map +1 -1
  127. package/dist/sdk/session/use-session-revalidation.js +2 -1
  128. package/dist/sdk/session/use-session-revalidation.js.map +1 -1
  129. package/dist/sdk/shipping/use-shipping-actions.js +3 -2
  130. package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
  131. package/dist/utils/safe-href.d.ts +11 -0
  132. package/package.json +13 -2
@@ -1 +1 @@
1
- {"version":3,"file":"payment.js","names":[],"sources":["../../../src/matter/payment/payment.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext, useId, useLayoutEffect, useMemo } from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.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 { PaymentSubmissionProvider } from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { PaymentCardCaptureProvider, type PaymentCartData } from './payment-card-capture.js';\nimport {\n type PaymentMethodSelection,\n type PaymentSectionData,\n resolveCanRevealCardPayment,\n resolvePreselectionCandidateId,\n splitCardMethods,\n usePaymentMethodSelection,\n} from './use-payment-method-selection.js';\n\n/**\n * The `PaymentSection` matter root (specs/payment-section-root.md, T5 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D1). `PaymentSectionRoot` is the always-visible presentation\n * variant over the `Payment` entity T1 projects: it owns a thin `fragment` (the method list with its\n * server-driven validator config, plus the current selection), runs the shared\n * `usePaymentMethodSelection` engine, and provides the `payment` context its leaves read — the card\n * entry (T7), cardholder document (T8), installments (T9), save-card (T10), billing address (T11),\n * saved cards (T12), and place-order CTA (T14) — through the guarded `usePayment()` hook rather than\n * drilled props (ADR-0009). `PaymentSectionController` (payment-controller.tsx) is the second provider\n * of the same context for a collapsed overlay surface; ADR-0009 matches by contract identifier, so the\n * two substitute for one another with zero edits to any consumer.\n *\n * \"Thin\" means no `installmentOptions` (T9's own fragment), no `savedCards` (T12's), and no\n * `selectedInstallment` (T6's) — the heavy, optional, and iterated subtrees ADR-0011/ADR-0012 split out.\n * `methods[].validator` *is* selected here: it is a non-normalized value object off `Payment.methods`\n * that cannot be subscribed separately, and T7 tests every method's `numberRegex` to detect a network.\n *\n * `children` is the section composition, not a per-method template — the method picker is\n * `PaymentMethodList`, an independently placeable part with its own hooks.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — raw template literal, matching the merged `payment-summary` sibling. The codegen\n// `documents` glob does not scan `src/matter/payment*`, so the typed `graphql()` factory is\n// unavailable here; `data-component-contract.test.ts` validates this fragment against the real schema.\n//\n// Selects `payment { id }` so Apollo's InMemoryCache normalizes `Payment` by id — required for the\n// `Payment` entity that `paymentMethodSelect` echoes back to update the same cache entity this\n// fragment reads. `fetchStrategy: 'reactive'`'s no-refetch guarantee holds only if both sides\n// normalize to one entity.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment PaymentSectionData on Query {\n payment {\n __typename\n id\n methods {\n __typename\n id\n name\n groupName\n validator {\n __typename\n numberMask\n numberRegex\n cvvMask\n cvvRegex\n cvvRequired\n expirationRequired\n holderNameRequired\n billingAddressRequired\n documentDisplayable\n documentRequired\n }\n }\n selectedMethodId\n selectedAccountId\n selectedBin\n }\n cart {\n __typename\n id\n total {\n __typename\n amount\n currencyCode\n }\n }\n }\n`;\n\n/**\n * The default payment-group scope: credit and debit (T140, FR-2, Decision 2). Both strings are\n * store-configured, so a store whose groups differ must set `group` explicitly; `group: ''` scopes\n * nothing (specs/payment-section-root.md § Documentation Gaps item 1). Widened from a single string to\n * a set (Decision 1) — a public API break for any consumer importing it directly (Interfaces).\n */\n/**\n * The roots' keyed `$data` (ADR-0015, arity 2): the `Payment` slice the selection engine reads, and the\n * cart total `add_payment_info` needs (moved here from `CardEntry`, which reads no cart).\n */\nexport type PaymentSectionRootData = {\n payment?: PaymentSectionData;\n cart?: PaymentCartData;\n} | null;\n\nexport const DEFAULT_METHOD_GROUP: readonly string[] = ['creditCardPaymentGroup', 'debitCardPaymentGroup'];\n\n// ---------------------------------------------------------------------------\n// Payment context — provided by both section roots, read by every leaf\n// ---------------------------------------------------------------------------\n\n/**\n * The `payment` context contract, normative for T7–T12 and T14. Everything the selection engine\n * resolves, plus the overlay disclosure. What is deliberately absent is part of the contract too: no\n * card number, CVV, expiration, or holder name (RFC D13 — they live only in the card entry's local\n * state); no submission channel for the place-order sequence, which is the separate, narrow\n * `PaymentSubmissionProvider` below (T13) precisely so no card-shaped value ever joins this contract;\n * no `installmentOptions` / `savedCards` / `selectedInstallment` (their owners' fragments); and no\n * optimistic mirror of the persisted selection.\n */\nexport type PaymentContextValue = Readonly<\n PaymentMethodSelection & {\n /** The collapsed surface's disclosure state. Always `true` under `PaymentSectionRoot`. */\n open: boolean;\n /**\n * Drives the collapsed surface. A no-op under `PaymentSectionRoot`, so a leaf written for the\n * overlay composes unchanged inline. Selecting a method never closes the surface on its own — the\n * shopper still has a card to type — so closing is the composition's call (Decision 5).\n */\n setOpen: (open: boolean) => void;\n }\n>;\n\nconst PAYMENT_CONTEXT_UNSET = Symbol('PaymentContextUnset');\n\nconst PaymentContextInternal = createContext<PaymentContextValue | typeof PAYMENT_CONTEXT_UNSET>(PAYMENT_CONTEXT_UNSET);\n\n/** The provider both section roots render. Exported for `PaymentSectionController`. */\nexport const PaymentContext = PaymentContextInternal as React.Context<PaymentContextValue>;\n\n/**\n * Read the payment section's selection state, draft, and overlay disclosure from the nearest\n * `PaymentSectionRoot` or `PaymentSectionController`. Throws outside either subtree — a misplaced\n * consumer is a composition bug the author should see immediately, not a silent degrade to an empty\n * selection — mirroring `useSession()` / `useDeliveryOptions()`. ADR-0009's compiler check catches the\n * same mistake before emission for schema-authored pages; this is the defense-in-depth layer for\n * Storybook, tests, and hand-written JSX.\n */\nexport function usePayment(): PaymentContextValue {\n const ctx = useContext(PaymentContextInternal);\n if (ctx === PAYMENT_CONTEXT_UNSET) {\n throw new Error('usePayment() must be used inside a <PaymentSectionRoot> or <PaymentSectionController> subtree.');\n }\n return ctx;\n}\n\nconst NO_DISCLOSURE = () => {};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type PaymentSectionRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Scopes the method list to a **set** of `PaymentMethod.groupName` values — a method is in scope\n * when its `groupName` is a member (T140, FR-1, Decision 1). A single string is still accepted and\n * scopes to exactly that one group (FR-4, US-3). T1's read model is method-agnostic and assigns the\n * narrowing to the consumer; this prop supplies no method, label, or ordering — the list itself is\n * fragment data. An empty string scopes nothing.\n */\n group?: string | readonly string[];\n /** Keyed `{ payment, cart }` forwarded by the reactive runtime (arity-2 fragment, ADR-0015). */\n $data?: PaymentSectionRootData;\n /** Compiler-injected action surface; carries `payment.setMethod`, `payment.sendPayments`, and `analytics.track`. */\n $actions?: ComponentActions;\n};\n\n/** Renders its own controls from the context's method list — `children` would have nothing to template. */\nexport type PaymentMethodListProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n /**\n * The single card control's name (T171, Decision 8). The control stands for every in-scope\n * credit/debit method at once, so it cannot take its name from one of their `method.name` values the\n * way a per-network row did — it is authored copy instead, defaulting to the PRD's own string.\n * Resolved through `resolveAuthoredCopy` exactly as `legend` is, so an authored empty or\n * whitespace-only string falls back rather than rendering a nameless control.\n */\n cardLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const PaymentSectionRootDefinition = defineComponent<PaymentSectionRootProps>({\n name: 'PaymentSectionRoot',\n importPath: '@vtex/faststore/components',\n component: PaymentSectionRoot,\n fragment,\n fetchStrategy: 'reactive',\n actions: { payment: ['setMethod', 'sendPayments'], analytics: ['track'] },\n providesContext: ['payment', 'card-capture'],\n editor: { label: 'Payment Section', category: 'commerce' },\n schema: {\n // A literal, not `DEFAULT_METHOD_GROUP`: the native catalog builder extracts `schema` statically\n // and silently drops an identifier-valued default, so the built entry would carry no default and\n // the compiler would emit the section unscoped. `payment.test.tsx` asserts the literal matches the\n // constant, and that the built catalog kept the default.\n // T140, Decision 2/OQ-6: `type: 'array'` — no code path in packages/sukima/crates/sukima-core/\n // validates an authored prop's JSON value against this declared type, so a store's existing bare\n // string keeps compiling. No `items`: an enum would validate against a closed set of group names,\n // which is wrong for a store that renamed its group (OQ-3) — the exact fragility Decision 1 closes.\n group: {\n type: 'array',\n default: ['creditCardPaymentGroup', 'debitCardPaymentGroup'],\n label: 'Payment method group',\n },\n },\n guideline:\n \"Root of the checkout payment section: reads the payment methods and the current selection from the order form and provides the 'payment' context every payment leaf needs and the 'card-capture' context CardEntry needs, so place CardEntry, CardholderDocument, Installments, SaveCard, BillingAddress, SavedCards, PlaceOrder, and PaymentMethodList as its children — none of them render without a payment root ancestor. Renders one container div and no chrome or copy of its own; headings and layout belong to the composition. Selecting a payment option dispatches $actions.payment.setMethod and every subscriber re-renders from the cache with no refetch — and for a card it is the number the shopper types that decides the network, not an option they pick, so PaymentMethodList shows every in-scope credit and debit method as ONE card option. Which methods exist is always real data — never author them as props; group only scopes the list by the store's payment group(s) — defaults to credit and debit together, accepts one group name or a set of them, and set it to \\\"\\\" to show every method, one option each, with no card option. Use PaymentSectionController instead when the section should live in a drawer or modal. See payment.md.\",\n});\n\nexport const PaymentMethodListDefinition = defineComponent<PaymentMethodListProps>({\n name: 'PaymentMethodList',\n importPath: '@vtex/faststore/components',\n component: PaymentMethodList,\n requiresContext: 'payment',\n editor: { label: 'Payment Method List', category: 'commerce' },\n schema: {\n // T38/T171: inline literals, never identifiers — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Payment method', label: 'Legend' },\n cardLabel: { type: 'string', default: 'Pay with Credit / Debit Card', label: 'Card option label' },\n },\n guideline:\n 'The payment-method picker: a radio group of the payment options the order form offers, plus the inline selection error (role=\"alert\"). Every in-scope credit and debit method is ONE option — the network comes from the card number the shopper types, never from a row they pick — and any other in-scope method (e.g. Pix) keeps a row of its own; set group to \"\" on the root to switch that off and get one row per method. Selecting an option dispatches the method selection. Selecting a non-card option hides the card form, and the card option brings it back. Place it inside a PaymentSectionRoot, or inside the DialogPopup/DrawerPopup of a PaymentSectionController. It renders its own options from real data, so it takes no children and no method props; author the card option\\'s wording with cardLabel. Keep it composed: it is the only element that surfaces a rejected selection, and the only control that can recover a selection this section does not serve — omit it and a shopper whose persisted method sits outside group has no reachable way back. Must be inside a payment root.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function PaymentSectionRoot({\n group = DEFAULT_METHOD_GROUP,\n $data: $dataProp,\n $actions: $actionsProp,\n className,\n children,\n ...props\n}: PaymentSectionRootProps) {\n const $data = useDataOverride($dataProp);\n const $actions = useActionsOverride($actionsProp);\n const selection = usePaymentMethodSelection({ group, $data: $data?.payment ?? null, $actions });\n const payment = useMemo<PaymentContextValue>(\n () => ({ ...selection, open: true, setOpen: NO_DISCLOSURE }),\n [selection],\n );\n\n return (\n <PaymentContextInternal value={payment}>\n <PaymentSubmissionProvider path={selection.draft.path}>\n <PaymentCardCaptureProvider selection={selection} cart={$data?.cart} $actions={$actions}>\n <div\n {...props}\n className={className}\n data-fs-payment-section=\"\"\n {...(selection.draft.path ? { 'data-fs-payment-section-path': selection.draft.path } : {})}\n // T28, Decision 5: presence attribute only, deliberately never `inert` — `PlaceOrder` composes\n // as a child of this same container (FR-6), so making it inert here would swallow the CTA's\n // own role=\"alert\"/role=\"status\" regions, the shopper's only channel for the outcome.\n {...(selection.placing ? { 'data-fs-payment-section-placing': '' } : {})}\n >\n {children}\n </div>\n </PaymentCardCaptureProvider>\n </PaymentSubmissionProvider>\n </PaymentContextInternal>\n );\n}\n\n/**\n * The picker's presence state, always emitted so a swatch can surface a row-less container — otherwise\n * a zero-height element under any stacking swatch, and the \"visible empty region\" the section's\n * group-default risk is accepted on. `loading` (still resolving) and `empty` (resolved, nothing in\n * scope) stay distinct: one is a wait, the other a misconfiguration.\n */\nfunction methodListState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The DOM value the collapsed card control carries inside the `RadioGroup`. Base UI requires every\n * radio root to have one, and this control stands for N methods, so there is no `method.id` it could\n * use.\n *\n * It is a radio-group value and nothing else — **never a method id** (INV-1, ADR-0023). It is never\n * dispatched: `handleValueChange` below maps it back to `resolvePreselectionCandidateId`'s answer\n * before calling `selectMethod`, and records card mode through `selectNewCard()`, which dispatches\n * nothing, when there is no candidate (T191, FR-6). The prefix keeps it from ever colliding with a\n * `PaymentMethod.id` a standalone row is bound to.\n */\nconst CARD_CONTROL_VALUE = '__fs-card-control';\n\nexport function PaymentMethodList({ legend, cardLabel, className, ...props }: PaymentMethodListProps) {\n const {\n methods,\n allMethods,\n persistedMethodId,\n selectedMethodId,\n selectionError,\n selectMethod,\n selectNewCard,\n cardModeActive,\n loading,\n placing,\n setMethodListMounted,\n } = usePayment();\n\n // T171, FR-1/FR-2: one control for the in-scope card methods, one row for every other in-scope\n // method. Memoized off the two lists only (NFR-5) — nothing here may widen what re-creates the\n // `payment` context value, which a keystroke already churns enough.\n const { collapsedCardMethods, standaloneMethods } = useMemo(\n () => splitCardMethods({ methods, allMethods }),\n [methods, allMethods],\n );\n // Decision 3: the collapsed control dispatches the *shared* preselection candidate, so the escape\n // hatch and the preselection can never disagree about whether an id is available.\n const cardCandidateId = useMemo(() => resolvePreselectionCandidateId(methods), [methods]);\n const canRevealCardPayment = resolveCanRevealCardPayment({ collapsedCardMethods, standaloneMethods });\n\n // Decision 2: derived, never bound to one `method.id` — a control standing for N methods has none.\n // Checked when the persisted selection resolves inside the collapsed set, and when nothing is\n // persisted at all (PRD REQ-02's \"the card payment option is selected by default\"). The second arm\n // is a deliberate divergence from the pre-T171 bound-to-`$data` behaviour: the control can read\n // checked while `$data.selectedMethodId` is still null, having dispatched nothing. `persistedMethodId`\n // is the raw, unscoped fact on purpose — a persisted method this section does not serve must leave the\n // control unchecked, which is what keeps it activatable as US-5's way back. T191 (FR-6): card mode is\n // the third arm, the card option chosen over such a selection or over a non-card one.\n const cardControlChecked =\n collapsedCardMethods.length > 0 &&\n (persistedMethodId === null ||\n collapsedCardMethods.some((method) => method.id === selectedMethodId) ||\n cardModeActive);\n\n // Fix pass, finding R2: registers this instance with the shared engine so the card leaves know an\n // escape hatch exists before they ever collapse — a section composed with no `PaymentMethodList` at\n // all must never hide the only remaining way to change the method (typing a new card number).\n // Retracted unconditionally on unmount, unlike `setPlacing`'s own \"leave it if still placing\"\n // carve-out: there is no in-flight sequence here for an abandoned instance to still be part of.\n //\n // T191, Decision 5 (after T171's Decision 4): what is registered is `canRevealCardPayment`, not the\n // bare fact of being mounted — whether this list can bring the card form back. A rendered card\n // option always can, since card mode needs no method id; without one, only a row bound to a card\n // method can. See `resolveCanRevealCardPayment`'s own doc comment. The dependency list carries it,\n // so the registration re-runs when the method list resolves.\n //\n // Fix pass round 2 (flash regression): `useLayoutEffect`, not `useEffect` — mirroring\n // `place-order.tsx`'s own `setPlacing` registration, the structurally identical precedent for\n // publishing a ref-counted fact to this shared engine from a leaf's mount. A passive effect runs\n // after the browser has already painted, so a root whose persisted selection is genuinely out of\n // scope committed one real frame with `methodListMounted` still `false` — the collapse (then\n // `selectionOutOfScope && methodListMounted`, now `cardPaymentCollapsed`) computed `false` for that\n // frame, painting the card form visible, focusable, and with its security code not yet cleared,\n // before the registration ever landed and a second commit hid it. A layout effect's own state update\n // is guaranteed to flush synchronously before the browser paints, so the very first frame a shopper\n // can ever see already reflects `methodListMounted`. The card leaves read it purely at render time,\n // not from an effect of their own, so this reorders nothing on that side: whichever commit this\n // layout effect's own update produces is the one their next render (still pre-paint) sees.\n const instanceKey = useId();\n useLayoutEffect(() => {\n setMethodListMounted(instanceKey, canRevealCardPayment);\n return () => setMethodListMounted(instanceKey, false);\n }, [instanceKey, setMethodListMounted, canRevealCardPayment]);\n\n // INV-1: only an id the read model supplied is ever dispatched. A standalone row's value *is* its own\n // `method.id`; the collapsed control's value is a DOM value with no method behind it, so it routes\n // through the shared candidate instead (FR-4). With no candidate to name it records card mode, which\n // brings the card form back and sends nothing (T191, FR-6, Decision 6).\n function handleValueChange(value: string) {\n if (value === CARD_CONTROL_VALUE) {\n if (cardCandidateId !== null) {\n selectMethod({ methodId: cardCandidateId });\n } else {\n selectNewCard();\n }\n return;\n }\n selectMethod({ methodId: value });\n }\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-payment-method-list=\"\"\n data-fs-payment-method-list-state={methodListState({ loading, count: methods.length })}\n {...(placing ? { 'data-fs-payment-method-list-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-payment-method-list-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {methods.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-payment-method-list-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Payment method')}</Fieldset.Legend>\n <RadioGroup\n value={cardControlChecked ? CARD_CONTROL_VALUE : (selectedMethodId ?? '')}\n onValueChange={handleValueChange}\n >\n {collapsedCardMethods.length > 0 ? (\n // One control for every in-scope credit and debit method (FR-1, Decision 1). Both\n // styling hooks stay exactly where a per-network row carried them — `payment.md`'s\n // styling table documents them, so a store's swatch keeps its selectors.\n <RadioRoot value={CARD_CONTROL_VALUE}>\n <RadioIndicator data-fs-payment-method-indicator=\"\" />\n <span data-fs-payment-method-name=\"\">\n {resolveAuthoredCopy(cardLabel, 'Pay with Credit / Debit Card')}\n </span>\n </RadioRoot>\n ) : null}\n {standaloneMethods.map((method) => (\n <RadioRoot key={method.id} value={method.id}>\n <RadioIndicator data-fs-payment-method-indicator=\"\" />\n <span data-fs-payment-method-name=\"\">{method.name}</span>\n </RadioRoot>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,MAAM,WAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuD/B,MAAa,uBAA0C,CAAC,0BAA0B,uBAAuB;AA4BzG,MAAM,wBAAwB,OAAO,qBAAqB;AAE1D,MAAM,yBAAyB,cAAkE,qBAAqB;;AAGtH,MAAa,iBAAiB;;;;;;;;;AAU9B,SAAgB,aAAkC;CAChD,MAAM,MAAM,WAAW,sBAAsB;CAC7C,IAAI,QAAQ,uBACV,MAAM,IAAI,MAAM,gGAAgG;CAElH,OAAO;AACT;AAEA,MAAM,sBAAsB,CAAC;AA2C7B,MAAa,+BAA+B,gBAAyC;CACnF,MAAM;CACN,YAAY;CACZ,WAAW;CACX;CACA,eAAe;CACf,SAAS;EAAE,SAAS,CAAC,aAAa,cAAc;EAAG,WAAW,CAAC,OAAO;CAAE;CACxE,iBAAiB,CAAC,WAAW,cAAc;CAC3C,QAAQ;EAAE,OAAO;EAAmB,UAAU;CAAW;CACzD,QAAQ,EASN,OAAO;EACL,MAAM;EACN,SAAS,CAAC,0BAA0B,uBAAuB;EAC3D,OAAO;CACT,EACF;CACA,WACE;AACJ,CAAC;AAED,MAAa,8BAA8B,gBAAwC;CACjF,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAuB,UAAU;CAAW;CAC7D,QAAQ;EAGN,QAAQ;GAAE,MAAM;GAAU,SAAS;GAAkB,OAAO;EAAS;EACrE,WAAW;GAAE,MAAM;GAAU,SAAS;GAAgC,OAAO;EAAoB;CACnG;CACA,WACE;AACJ,CAAC;AAMD,SAAgB,mBAAmB,EACjC,QAAQ,sBACR,OAAO,WACP,UAAU,cACV,WACA,UACA,GAAG,SACuB;CAC1B,MAAM,QAAQ,gBAAgB,SAAS;CACvC,MAAM,WAAW,mBAAmB,YAAY;CAChD,MAAM,YAAY,0BAA0B;EAAE;EAAO,OAAO,OAAO,WAAW;EAAM;CAAS,CAAC;CAC9F,MAAM,UAAU,eACP;EAAE,GAAG;EAAW,MAAM;EAAM,SAAS;CAAc,IAC1D,CAAC,SAAS,CACZ;CAEA,OACE,oBAAC,wBAAD;EAAwB,OAAO;EAC7B,UAAA,oBAAC,2BAAD;GAA2B,MAAM,UAAU,MAAM;GAC/C,UAAA,oBAAC,4BAAD;IAAuC;IAAW,MAAM,OAAO;IAAgB;IAC7E,UAAA,oBAAC,OAAD;KACE,GAAI;KACO;KACX,2BAAwB;KACxB,GAAK,UAAU,MAAM,OAAO,EAAE,gCAAgC,UAAU,MAAM,KAAK,IAAI,CAAC;KAIxF,GAAK,UAAU,UAAU,EAAE,mCAAmC,GAAG,IAAI,CAAC;KAErE;IACE,CAAA;GACqB,CAAA;EACH,CAAA;CACL,CAAA;AAE5B;;;;;;;AAQA,SAAS,gBAAgB,EAAE,SAAS,SAA6E;CAC/G,IAAI,SAAS,OAAO;CACpB,IAAI,UAAU,GAAG,OAAO;CACxB,OAAO;AACT;;;;;;;;;;;;AAaA,MAAM,qBAAqB;AAE3B,SAAgB,kBAAkB,EAAE,QAAQ,WAAW,WAAW,GAAG,SAAiC;CACpG,MAAM,EACJ,SACA,YACA,mBACA,kBACA,gBACA,cACA,eACA,gBACA,SACA,SACA,yBACE,WAAW;CAKf,MAAM,EAAE,sBAAsB,sBAAsB,cAC5C,iBAAiB;EAAE;EAAS;CAAW,CAAC,GAC9C,CAAC,SAAS,UAAU,CACtB;CAGA,MAAM,kBAAkB,cAAc,+BAA+B,OAAO,GAAG,CAAC,OAAO,CAAC;CACxF,MAAM,uBAAuB,4BAA4B;EAAE;EAAsB;CAAkB,CAAC;CAUpG,MAAM,qBACJ,qBAAqB,SAAS,MAC7B,sBAAsB,QACrB,qBAAqB,MAAM,WAAW,OAAO,OAAO,gBAAgB,KACpE;CA0BJ,MAAM,cAAc,MAAM;CAC1B,sBAAsB;EACpB,qBAAqB,aAAa,oBAAoB;EACtD,aAAa,qBAAqB,aAAa,KAAK;CACtD,GAAG;EAAC;EAAa;EAAsB;CAAoB,CAAC;CAM5D,SAAS,kBAAkB,OAAe;EACxC,IAAI,UAAU,oBAAoB;GAChC,IAAI,oBAAoB,MACtB,aAAa,EAAE,UAAU,gBAAgB,CAAC;QAE1C,cAAc;GAEhB;EACF;EACA,aAAa,EAAE,UAAU,MAAM,CAAC;CAClC;CAEA,OACE,qBAAC,OAAD;EACE,GAAI;EACO;EACX,OAAO;EACP,iBAAe,WAAW,KAAA;EAC1B,+BAA4B;EAC5B,qCAAmC,gBAAgB;GAAE;GAAS,OAAO,QAAQ;EAAO,CAAC;EACrF,GAAK,UAAU,EAAE,uCAAuC,GAAG,IAAI,CAAC;EAPlE,UAAA,CASG,iBACC,oBAAC,OAAD;GAAK,MAAK;GAAQ,qCAAkC;GACjD,UAAA;EACE,CAAA,IACH,MACH,QAAQ,SAAS,IAIhB,qBAAC,SAAS,MAAV;GAAe,wCAAqC;GAApD,UAAA,CACE,oBAAC,SAAS,QAAV,EAAA,UAAkB,oBAAoB,QAAQ,gBAAgB,EAAmB,CAAA,GACjF,qBAAC,YAAD;IACE,OAAO,qBAAqB,qBAAsB,oBAAoB;IACtE,eAAe;IAFjB,UAAA,CAIG,qBAAqB,SAAS,IAI7B,qBAAC,WAAD;KAAW,OAAO;KAAlB,UAAA,CACE,oBAAC,gBAAD,EAAgB,oCAAiC,GAAI,CAAA,GACrD,oBAAC,QAAD;MAAM,+BAA4B;MAC/B,UAAA,oBAAoB,WAAW,8BAA8B;KAC1D,CAAA,CACG;IACT,CAAA,IAAA,MACH,kBAAkB,KAAK,WACtB,qBAAC,WAAD;KAA2B,OAAO,OAAO;KAAzC,UAAA,CACE,oBAAC,gBAAD,EAAgB,oCAAiC,GAAI,CAAA,GACrD,oBAAC,QAAD;MAAM,+BAA4B;MAAI,UAAA,OAAO;KAAW,CAAA,CAC/C;IAHK,GAAA,OAAO,EAGZ,CACZ,CACS;GACC,CAAA,CAAA;EACb,CAAA,IAAA,IACD;;AAET"}
1
+ {"version":3,"file":"payment.js","names":[],"sources":["../../../src/matter/payment/payment.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { type ComponentPropsWithRef, createContext, useContext, useId, useLayoutEffect, useMemo } from 'react';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { RadioGroup, RadioIndicator, RadioRoot } from '../../particles/radio/radio.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 { PaymentSubmissionProvider } from '../../sdk/payment/submission-registry.js';\nimport { resolveAuthoredCopy } from '../../utils/resolve-authored-copy.js';\nimport { PaymentCardCaptureProvider, type PaymentCartData } from './payment-card-capture.js';\nimport {\n type PaymentMethodSelection,\n type PaymentSectionData,\n resolveCanRevealCardPayment,\n resolvePreselectionCandidateId,\n splitCardMethods,\n usePaymentMethodSelection,\n} from './use-payment-method-selection.js';\n\n/**\n * The `PaymentSection` matter root (specs/payment-section-root.md, T5 of the Credit Card epic;\n * docs/features/credit-card/rfc.md § D1). `PaymentSectionRoot` is the always-visible presentation\n * variant over the `Payment` entity T1 projects: it owns a thin `fragment` (the method list with its\n * server-driven validator config, plus the current selection), runs the shared\n * `usePaymentMethodSelection` engine, and provides the `payment` context its leaves read — the card\n * entry (T7), cardholder document (T8), installments (T9), save-card (T10), billing address (T11),\n * saved cards (T12), and place-order CTA (T14) — through the guarded `usePayment()` hook rather than\n * drilled props (ADR-0009). `PaymentSectionController` (payment-controller.tsx) is the second provider\n * of the same context for a collapsed overlay surface; ADR-0009 matches by contract identifier, so the\n * two substitute for one another with zero edits to any consumer.\n *\n * \"Thin\" means no `installmentOptions` (T9's own fragment), no `savedCards` (T12's), and no\n * `selectedInstallment` (T6's) — the heavy, optional, and iterated subtrees ADR-0011/ADR-0012 split out.\n * `methods[].validator` *is* selected here: it is a non-normalized value object off `Payment.methods`\n * that cannot be subscribed separately, and T7 tests every method's `numberRegex` to detect a network.\n *\n * `children` is the section composition, not a per-method template — the method picker is\n * `PaymentMethodList`, an independently placeable part with its own hooks.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — raw template literal, matching the merged `payment-summary` sibling. The codegen\n// `documents` glob does not scan `src/matter/payment*`, so the typed `graphql()` factory is\n// unavailable here; `data-component-contract.test.ts` validates this fragment against the real schema.\n//\n// Selects `payment { id }` so Apollo's InMemoryCache normalizes `Payment` by id — required for the\n// `Payment` entity that `paymentMethodSelect` echoes back to update the same cache entity this\n// fragment reads. `fetchStrategy: 'reactive'`'s no-refetch guarantee holds only if both sides\n// normalize to one entity.\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment PaymentSectionData on Query {\n payment {\n __typename\n id\n methods {\n __typename\n id\n name\n groupName\n validator {\n __typename\n numberMask\n numberRegex\n cvvMask\n cvvRegex\n cvvRequired\n expirationRequired\n holderNameRequired\n billingAddressRequired\n documentDisplayable\n documentRequired\n }\n }\n selectedMethodId\n selectedAccountId\n selectedBin\n }\n cart {\n __typename\n id\n total {\n __typename\n amount\n currencyCode\n }\n }\n }\n`;\n\n/**\n * The default payment-group scope: credit and debit (T140, FR-2, Decision 2). Both strings are\n * store-configured, so a store whose groups differ must set `group` explicitly; `group: ''` scopes\n * nothing (specs/payment-section-root.md § Documentation Gaps item 1). Widened from a single string to\n * a set (Decision 1) — a public API break for any consumer importing it directly (Interfaces).\n */\n/**\n * The roots' keyed `$data` (ADR-0015, arity 2): the `Payment` slice the selection engine reads, and the\n * cart total `add_payment_info` needs (moved here from `CardEntry`, which reads no cart).\n */\nexport type PaymentSectionRootData = {\n payment?: PaymentSectionData;\n cart?: PaymentCartData;\n} | null;\n\nexport const DEFAULT_METHOD_GROUP: readonly string[] = ['creditCardPaymentGroup', 'debitCardPaymentGroup'];\n\n// ---------------------------------------------------------------------------\n// Payment context — provided by both section roots, read by every leaf\n// ---------------------------------------------------------------------------\n\n/**\n * The `payment` context contract, normative for T7–T12 and T14. Everything the selection engine\n * resolves, plus the overlay disclosure. What is deliberately absent is part of the contract too: no\n * card number, CVV, expiration, or holder name (RFC D13 — they live only in the card entry's local\n * state); no submission channel for the place-order sequence, which is the separate, narrow\n * `PaymentSubmissionProvider` below (T13) precisely so no card-shaped value ever joins this contract;\n * no `installmentOptions` / `savedCards` / `selectedInstallment` (their owners' fragments); and no\n * optimistic mirror of the persisted selection.\n */\nexport type PaymentContextValue = Readonly<\n PaymentMethodSelection & {\n /** The collapsed surface's disclosure state. Always `true` under `PaymentSectionRoot`. */\n open: boolean;\n /**\n * Drives the collapsed surface. A no-op under `PaymentSectionRoot`, so a leaf written for the\n * overlay composes unchanged inline. Selecting a method never closes the surface on its own — the\n * shopper still has a card to type — so closing is the composition's call (Decision 5).\n */\n setOpen: (open: boolean) => void;\n }\n>;\n\nconst PAYMENT_CONTEXT_UNSET = Symbol('PaymentContextUnset');\n\nconst PaymentContextInternal = createContext<PaymentContextValue | typeof PAYMENT_CONTEXT_UNSET>(PAYMENT_CONTEXT_UNSET);\n\n/** The provider both section roots render. Exported for `PaymentSectionController`. */\nexport const PaymentContext = PaymentContextInternal as React.Context<PaymentContextValue>;\n\n/**\n * Read the payment section's selection state, draft, and overlay disclosure from the nearest\n * `PaymentSectionRoot` or `PaymentSectionController`. Throws outside either subtree — a misplaced\n * consumer is a composition bug the author should see immediately, not a silent degrade to an empty\n * selection — mirroring `useSession()` / `useDeliveryOptions()`. ADR-0009's compiler check catches the\n * same mistake before emission for schema-authored pages; this is the defense-in-depth layer for\n * Storybook, tests, and hand-written JSX.\n */\nexport function usePayment(): PaymentContextValue {\n const ctx = useContext(PaymentContextInternal);\n if (ctx === PAYMENT_CONTEXT_UNSET) {\n throw new Error('usePayment() must be used inside a <PaymentSectionRoot> or <PaymentSectionController> subtree.');\n }\n return ctx;\n}\n\nconst NO_DISCLOSURE = () => {};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type PaymentSectionRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Scopes the method list to a **set** of `PaymentMethod.groupName` values — a method is in scope\n * when its `groupName` is a member (T140, FR-1, Decision 1). A single string is still accepted and\n * scopes to exactly that one group (FR-4, US-3). T1's read model is method-agnostic and assigns the\n * narrowing to the consumer; this prop supplies no method, label, or ordering — the list itself is\n * fragment data. An empty string scopes nothing.\n */\n group?: string | readonly string[];\n /** Keyed `{ payment, cart }` forwarded by the reactive runtime (arity-2 fragment, ADR-0015). */\n $data?: PaymentSectionRootData;\n /** Compiler-injected action surface; carries `payment.setMethod`, `payment.sendPayments`, and `analytics.track`. */\n $actions?: ComponentActions;\n};\n\n/** Renders its own controls from the context's method list — `children` would have nothing to template. */\nexport type PaymentMethodListProps = Omit<ComponentPropsWithRef<'div'>, 'children'> & {\n /**\n * The radio group's accessible name, rendered as a visible `Fieldset.Legend` (T38, Decision 1).\n * Resolved through `resolveAuthoredCopy`: an authored empty or whitespace-only string falls back to\n * the default rather than rendering a nameless group.\n */\n legend?: string;\n /**\n * The single card control's name (T171, Decision 8). The control stands for every in-scope\n * credit/debit method at once, so it cannot take its name from one of their `method.name` values the\n * way a per-network row did — it is authored copy instead, defaulting to the PRD's own string.\n * Resolved through `resolveAuthoredCopy` exactly as `legend` is, so an authored empty or\n * whitespace-only string falls back rather than rendering a nameless control.\n */\n cardLabel?: string;\n};\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const PaymentSectionRootDefinition = defineComponent<PaymentSectionRootProps>({\n name: 'PaymentSectionRoot',\n importPath: '@vtex/faststore/components',\n component: PaymentSectionRoot,\n fragment,\n fetchStrategy: 'reactive',\n actions: { payment: ['setMethod', 'sendPayments'], analytics: ['track'] },\n providesContext: ['payment', 'card-capture'],\n hardened: true,\n editor: { label: 'Payment Section', category: 'commerce' },\n schema: {\n // A literal, not `DEFAULT_METHOD_GROUP`: the native catalog builder extracts `schema` statically\n // and silently drops an identifier-valued default, so the built entry would carry no default and\n // the compiler would emit the section unscoped. `payment.test.tsx` asserts the literal matches the\n // constant, and that the built catalog kept the default.\n // T140, Decision 2/OQ-6: `type: 'array'` — no code path in packages/sukima/crates/sukima-core/\n // validates an authored prop's JSON value against this declared type, so a store's existing bare\n // string keeps compiling. No `items`: an enum would validate against a closed set of group names,\n // which is wrong for a store that renamed its group (OQ-3) — the exact fragility Decision 1 closes.\n group: {\n type: 'array',\n default: ['creditCardPaymentGroup', 'debitCardPaymentGroup'],\n label: 'Payment method group',\n },\n },\n guideline:\n \"Root of the checkout payment section: reads the payment methods and the current selection from the order form and provides the 'payment' context every payment leaf needs and the 'card-capture' context CardEntry needs, so place CardEntry, CardholderDocument, Installments, SaveCard, BillingAddress, SavedCards, PlaceOrder, and PaymentMethodList as its children — none of them render without a payment root ancestor. Renders one container div and no chrome or copy of its own; headings and layout belong to the composition. Selecting a payment option dispatches $actions.payment.setMethod and every subscriber re-renders from the cache with no refetch — and for a card it is the number the shopper types that decides the network, not an option they pick, so PaymentMethodList shows every in-scope credit and debit method as ONE card option. Which methods exist is always real data — never author them as props; group only scopes the list by the store's payment group(s) — defaults to credit and debit together, accepts one group name or a set of them, and set it to \\\"\\\" to show every method, one option each, with no card option. Use PaymentSectionController instead when the section should live in a drawer or modal. See payment.md.\",\n});\n\nexport const PaymentMethodListDefinition = defineComponent<PaymentMethodListProps>({\n name: 'PaymentMethodList',\n importPath: '@vtex/faststore/components',\n component: PaymentMethodList,\n requiresContext: 'payment',\n editor: { label: 'Payment Method List', category: 'commerce' },\n schema: {\n // T38/T171: inline literals, never identifiers — the native catalog builder extracts `schema`\n // statically and silently drops an identifier-valued default (INV-8).\n legend: { type: 'string', default: 'Payment method', label: 'Legend' },\n cardLabel: { type: 'string', default: 'Pay with Credit / Debit Card', label: 'Card option label' },\n },\n guideline:\n 'The payment-method picker: a radio group of the payment options the order form offers, plus the inline selection error (role=\"alert\"). Every in-scope credit and debit method is ONE option — the network comes from the card number the shopper types, never from a row they pick — and any other in-scope method (e.g. Pix) keeps a row of its own; set group to \"\" on the root to switch that off and get one row per method. Selecting an option dispatches the method selection. Selecting a non-card option hides the card form, and the card option brings it back. Place it inside a PaymentSectionRoot, or inside the DialogPopup/DrawerPopup of a PaymentSectionController. It renders its own options from real data, so it takes no children and no method props; author the card option\\'s wording with cardLabel. Keep it composed: it is the only element that surfaces a rejected selection, and the only control that can recover a selection this section does not serve — omit it and a shopper whose persisted method sits outside group has no reachable way back. Must be inside a payment root.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function PaymentSectionRoot({\n group = DEFAULT_METHOD_GROUP,\n $data: $dataProp,\n $actions: $actionsProp,\n className,\n children,\n ...props\n}: PaymentSectionRootProps) {\n const $data = useDataOverride($dataProp);\n const $actions = useActionsOverride($actionsProp);\n const selection = usePaymentMethodSelection({ group, $data: $data?.payment ?? null, $actions });\n const payment = useMemo<PaymentContextValue>(\n () => ({ ...selection, open: true, setOpen: NO_DISCLOSURE }),\n [selection],\n );\n\n return (\n <PaymentContextInternal value={payment}>\n <PaymentSubmissionProvider path={selection.draft.path}>\n <PaymentCardCaptureProvider selection={selection} cart={$data?.cart} $actions={$actions}>\n <div\n {...props}\n className={className}\n data-fs-payment-section=\"\"\n {...(selection.draft.path ? { 'data-fs-payment-section-path': selection.draft.path } : {})}\n // T28, Decision 5: presence attribute only, deliberately never `inert` — `PlaceOrder` composes\n // as a child of this same container (FR-6), so making it inert here would swallow the CTA's\n // own role=\"alert\"/role=\"status\" regions, the shopper's only channel for the outcome.\n {...(selection.placing ? { 'data-fs-payment-section-placing': '' } : {})}\n >\n {children}\n </div>\n </PaymentCardCaptureProvider>\n </PaymentSubmissionProvider>\n </PaymentContextInternal>\n );\n}\n\n/**\n * The picker's presence state, always emitted so a swatch can surface a row-less container — otherwise\n * a zero-height element under any stacking swatch, and the \"visible empty region\" the section's\n * group-default risk is accepted on. `loading` (still resolving) and `empty` (resolved, nothing in\n * scope) stay distinct: one is a wait, the other a misconfiguration.\n */\nfunction methodListState({ loading, count }: { loading: boolean; count: number }): 'loading' | 'empty' | 'ready' {\n if (loading) return 'loading';\n if (count === 0) return 'empty';\n return 'ready';\n}\n\n/**\n * The DOM value the collapsed card control carries inside the `RadioGroup`. Base UI requires every\n * radio root to have one, and this control stands for N methods, so there is no `method.id` it could\n * use.\n *\n * It is a radio-group value and nothing else — **never a method id** (INV-1, ADR-0023). It is never\n * dispatched: `handleValueChange` below maps it back to `resolvePreselectionCandidateId`'s answer\n * before calling `selectMethod`, and records card mode through `selectNewCard()`, which dispatches\n * nothing, when there is no candidate (T191, FR-6). The prefix keeps it from ever colliding with a\n * `PaymentMethod.id` a standalone row is bound to.\n */\nconst CARD_CONTROL_VALUE = '__fs-card-control';\n\nexport function PaymentMethodList({ legend, cardLabel, className, ...props }: PaymentMethodListProps) {\n const {\n methods,\n allMethods,\n persistedMethodId,\n selectedMethodId,\n selectionError,\n selectMethod,\n selectNewCard,\n cardModeActive,\n loading,\n placing,\n setMethodListMounted,\n } = usePayment();\n\n // T171, FR-1/FR-2: one control for the in-scope card methods, one row for every other in-scope\n // method. Memoized off the two lists only (NFR-5) — nothing here may widen what re-creates the\n // `payment` context value, which a keystroke already churns enough.\n const { collapsedCardMethods, standaloneMethods } = useMemo(\n () => splitCardMethods({ methods, allMethods }),\n [methods, allMethods],\n );\n // Decision 3: the collapsed control dispatches the *shared* preselection candidate, so the escape\n // hatch and the preselection can never disagree about whether an id is available.\n const cardCandidateId = useMemo(() => resolvePreselectionCandidateId(methods), [methods]);\n const canRevealCardPayment = resolveCanRevealCardPayment({ collapsedCardMethods, standaloneMethods });\n\n // Decision 2: derived, never bound to one `method.id` — a control standing for N methods has none.\n // Checked when the persisted selection resolves inside the collapsed set, and when nothing is\n // persisted at all (PRD REQ-02's \"the card payment option is selected by default\"). The second arm\n // is a deliberate divergence from the pre-T171 bound-to-`$data` behaviour: the control can read\n // checked while `$data.selectedMethodId` is still null, having dispatched nothing. `persistedMethodId`\n // is the raw, unscoped fact on purpose — a persisted method this section does not serve must leave the\n // control unchecked, which is what keeps it activatable as US-5's way back. T191 (FR-6): card mode is\n // the third arm, the card option chosen over such a selection or over a non-card one.\n const cardControlChecked =\n collapsedCardMethods.length > 0 &&\n (persistedMethodId === null ||\n collapsedCardMethods.some((method) => method.id === selectedMethodId) ||\n cardModeActive);\n\n // Fix pass, finding R2: registers this instance with the shared engine so the card leaves know an\n // escape hatch exists before they ever collapse — a section composed with no `PaymentMethodList` at\n // all must never hide the only remaining way to change the method (typing a new card number).\n // Retracted unconditionally on unmount, unlike `setPlacing`'s own \"leave it if still placing\"\n // carve-out: there is no in-flight sequence here for an abandoned instance to still be part of.\n //\n // T191, Decision 5 (after T171's Decision 4): what is registered is `canRevealCardPayment`, not the\n // bare fact of being mounted — whether this list can bring the card form back. A rendered card\n // option always can, since card mode needs no method id; without one, only a row bound to a card\n // method can. See `resolveCanRevealCardPayment`'s own doc comment. The dependency list carries it,\n // so the registration re-runs when the method list resolves.\n //\n // Fix pass round 2 (flash regression): `useLayoutEffect`, not `useEffect` — mirroring\n // `place-order.tsx`'s own `setPlacing` registration, the structurally identical precedent for\n // publishing a ref-counted fact to this shared engine from a leaf's mount. A passive effect runs\n // after the browser has already painted, so a root whose persisted selection is genuinely out of\n // scope committed one real frame with `methodListMounted` still `false` — the collapse (then\n // `selectionOutOfScope && methodListMounted`, now `cardPaymentCollapsed`) computed `false` for that\n // frame, painting the card form visible, focusable, and with its security code not yet cleared,\n // before the registration ever landed and a second commit hid it. A layout effect's own state update\n // is guaranteed to flush synchronously before the browser paints, so the very first frame a shopper\n // can ever see already reflects `methodListMounted`. The card leaves read it purely at render time,\n // not from an effect of their own, so this reorders nothing on that side: whichever commit this\n // layout effect's own update produces is the one their next render (still pre-paint) sees.\n const instanceKey = useId();\n useLayoutEffect(() => {\n setMethodListMounted(instanceKey, canRevealCardPayment);\n return () => setMethodListMounted(instanceKey, false);\n }, [instanceKey, setMethodListMounted, canRevealCardPayment]);\n\n // INV-1: only an id the read model supplied is ever dispatched. A standalone row's value *is* its own\n // `method.id`; the collapsed control's value is a DOM value with no method behind it, so it routes\n // through the shared candidate instead (FR-4). With no candidate to name it records card mode, which\n // brings the card form back and sends nothing (T191, FR-6, Decision 6).\n function handleValueChange(value: string) {\n if (value === CARD_CONTROL_VALUE) {\n if (cardCandidateId !== null) {\n selectMethod({ methodId: cardCandidateId });\n } else {\n selectNewCard();\n }\n return;\n }\n selectMethod({ methodId: value });\n }\n\n return (\n <div\n {...props}\n className={className}\n inert={placing}\n aria-disabled={placing || undefined}\n data-fs-payment-method-list=\"\"\n data-fs-payment-method-list-state={methodListState({ loading, count: methods.length })}\n {...(placing ? { 'data-fs-payment-method-list-placing': '' } : {})}\n >\n {selectionError ? (\n <div role=\"alert\" data-fs-payment-method-list-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {methods.length > 0 ? (\n // T38, Decision 1: Fieldset.Root + Fieldset.Legend, exactly what card-entry.tsx:1101-1112\n // already ships — Base UI propagates the legend's id as aria-labelledby onto the descendant\n // RadioGroup, so this one wrapper names both the fieldset and the radiogroup (measured).\n <Fieldset.Root data-fs-payment-method-list-fieldset=\"\">\n <Fieldset.Legend>{resolveAuthoredCopy(legend, 'Payment method')}</Fieldset.Legend>\n <RadioGroup\n value={cardControlChecked ? CARD_CONTROL_VALUE : (selectedMethodId ?? '')}\n onValueChange={handleValueChange}\n >\n {collapsedCardMethods.length > 0 ? (\n // One control for every in-scope credit and debit method (FR-1, Decision 1). Both\n // styling hooks stay exactly where a per-network row carried them — `payment.md`'s\n // styling table documents them, so a store's swatch keeps its selectors.\n <RadioRoot value={CARD_CONTROL_VALUE}>\n <RadioIndicator data-fs-payment-method-indicator=\"\" />\n <span data-fs-payment-method-name=\"\">\n {resolveAuthoredCopy(cardLabel, 'Pay with Credit / Debit Card')}\n </span>\n </RadioRoot>\n ) : null}\n {standaloneMethods.map((method) => (\n <RadioRoot key={method.id} value={method.id}>\n <RadioIndicator data-fs-payment-method-indicator=\"\" />\n <span data-fs-payment-method-name=\"\">{method.name}</span>\n </RadioRoot>\n ))}\n </RadioGroup>\n </Fieldset.Root>\n ) : null}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,8 +1,51 @@
1
+ import { ContentValue } from "../../core/dist/define-content-type.js";
1
2
  import { ComponentDefinition } from "../../core/dist/define-component.js";
2
3
  import "../../core/dist/index.js";
3
4
  import { ProductListItem } from "../product-list/product-list.js";
4
5
  import { ComponentPropsWithRef } from "react";
5
6
  //#region src/matter/product-shelf/product-shelf.d.ts
7
+ export declare const ProductShelfContent: {
8
+ readonly id: "product-shelf";
9
+ readonly title: "Product Shelf";
10
+ readonly description: "Which products a shelf shows: how many, in which order, and from which facet.";
11
+ readonly schema: {
12
+ readonly type: "object";
13
+ readonly additionalProperties: false;
14
+ readonly properties: {
15
+ readonly first: {
16
+ readonly type: "integer";
17
+ readonly title: "Number of products";
18
+ readonly minimum: 1;
19
+ readonly maximum: 100;
20
+ };
21
+ readonly sort: {
22
+ readonly type: "string";
23
+ readonly enum: readonly ["relevance", "price_asc", "price_desc", "name_asc", "name_desc", "release", "orders", "discount"];
24
+ readonly title: "Sort";
25
+ };
26
+ readonly categoryId: {
27
+ readonly type: "string";
28
+ readonly title: "Category ID";
29
+ };
30
+ readonly brandId: {
31
+ readonly type: "string";
32
+ readonly title: "Brand ID";
33
+ };
34
+ readonly collectionId: {
35
+ readonly type: "string";
36
+ readonly title: "Collection ID";
37
+ };
38
+ readonly searchTerm: {
39
+ readonly type: "string";
40
+ readonly title: "Search term";
41
+ };
42
+ readonly availableOnly: {
43
+ readonly type: "boolean";
44
+ readonly title: "Available only";
45
+ };
46
+ };
47
+ };
48
+ };
6
49
  type RawMoney = {
7
50
  amount: number;
8
51
  currencyCode: string;
@@ -54,62 +97,49 @@ export type ProductShelfItem = ProductListItem;
54
97
  export type ProductShelfSort = 'relevance' | 'price_asc' | 'price_desc' | 'name_asc' | 'name_desc' | 'release' | 'orders' | 'discount';
55
98
  export type ProductSortField = 'TITLE' | 'PRICE' | 'RELEASE_DATE' | 'RELEVANCE' | 'BEST_SELLING';
56
99
  export type SortDirection = 'ASC' | 'DESC';
57
- export type ProductsFilterInput = {
58
- searchTerm?: string;
59
- categoryIds?: string[];
60
- brandIds?: string[];
61
- collectionId?: string;
62
- specifications?: Array<{
63
- name: string;
64
- values: string[];
65
- }>;
66
- availableOnly?: boolean;
67
- };
68
- export type ProductShelfVariables = {
69
- first: number;
70
- orderBy?: {
71
- field: ProductSortField;
72
- direction: SortDirection;
73
- };
74
- filter: ProductsFilterInput;
75
- };
100
+ /**
101
+ * The query configuration the shelf resolved for this element: the content slice when the
102
+ * merchant filled a field, else the prop. The query has already applied these values; the
103
+ * context republishes them for parts that describe the shelf (a "sorted by" label, a facet link).
104
+ */
76
105
  export type ProductShelfConfig = {
77
- first?: number;
78
- itemsPerPage?: number;
79
- sort?: ProductShelfSort;
106
+ first: number;
107
+ sort: ProductShelfSort;
80
108
  searchTerm?: string;
81
109
  categoryId?: string;
82
110
  brandId?: string;
83
111
  collectionId?: string;
84
- specifications?: Array<{
85
- name: string;
86
- values: string[];
87
- }>;
88
- availableOnly?: boolean;
112
+ availableOnly: boolean;
89
113
  };
90
114
  export type ProductShelfContextValue = {
91
115
  itemsPerPage: number;
92
- variables: ProductShelfVariables;
116
+ config: ProductShelfConfig;
93
117
  count: number;
94
118
  };
95
- export type ProductShelfRootProps = ComponentPropsWithRef<'section'> & ProductShelfConfig & {
119
+ export type ProductShelfRootProps = ComponentPropsWithRef<'section'> & Partial<ProductShelfConfig> & {
120
+ itemsPerPage?: number;
96
121
  /** When true, card prices render tax-inclusive (from each item's tax data). Default false. */
97
122
  usePriceWithTaxes?: boolean;
98
123
  /** Text for the tax label part when tax display is on. Default `'Tax included'`. */
99
124
  taxesLabel?: string;
100
125
  products?: ProductShelfItem[];
126
+ /** Injected by the compiler, sliced from the page's or layout's composite content entry. */
127
+ $content?: ContentValue<typeof ProductShelfContent>;
101
128
  $data?: ProductShelfData | null;
102
129
  };
103
130
  export type ProductShelfItemsProps = ComponentPropsWithRef<'div'>;
104
131
  export type ProductShelfHeadingProps = ComponentPropsWithRef<'h2'>;
105
132
  export declare function useProductShelf(): ProductShelfContextValue;
106
133
  export declare function useProductShelfItem(): ProductShelfItem;
107
- export declare function buildProductShelfVariables(config: ProductShelfConfig): ProductShelfVariables;
134
+ export declare const PRODUCT_SHELF_SORT_MAP: Record<ProductShelfSort, {
135
+ field: ProductSortField;
136
+ direction: SortDirection;
137
+ }>;
108
138
  export declare function transformProducts(data: ProductShelfData): ProductShelfItem[];
109
139
  export declare const ProductShelfRootDefinition: ComponentDefinition<ProductShelfRootProps>;
110
140
  export declare const ProductShelfItemsDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
111
141
  export declare const ProductShelfHeadingDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>>;
112
- export declare function ProductShelfRoot({ first, itemsPerPage, sort, searchTerm, categoryId, brandId, collectionId, specifications, availableOnly, usePriceWithTaxes, taxesLabel, products, $data: $dataProp, className, children, ...props }: ProductShelfRootProps): import("react/jsx-runtime").JSX.Element | null;
142
+ export declare function ProductShelfRoot({ first, itemsPerPage, sort, searchTerm, categoryId, brandId, collectionId, availableOnly, usePriceWithTaxes, taxesLabel, products, $content, $data: $dataProp, className, children, ...props }: ProductShelfRootProps): import("react/jsx-runtime").JSX.Element | null;
113
143
  export declare namespace ProductShelfRoot {
114
144
  var Skeleton: typeof ProductShelfSkeleton;
115
145
  }
@@ -1,16 +1,79 @@
1
1
  "use client";
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
+ import { defineContentType } from "../../core/dist/define-content-type.js";
3
4
  import "../../core/dist/index.js";
4
5
  import { useDataOverride } from "../../sdk/data/data-override-context.js";
5
6
  import { Text } from "../../particles/text/text.js";
6
7
  import { ProductListConfigContext, ProductListItemContext } from "../product-list/product-list.js";
7
8
  import { CarouselTrack } from "../../particles/carousel/carousel.js";
9
+ import { isEmptyContentValue } from "../../sdk/data/empty-content-value.js";
8
10
  import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState } from "react";
9
11
  import { jsx, jsxs } from "react/jsx-runtime";
10
12
  //#region src/matter/product-shelf/product-shelf.tsx
13
+ const ProductShelfContent = defineContentType({
14
+ id: "product-shelf",
15
+ title: "Product Shelf",
16
+ description: "Which products a shelf shows: how many, in which order, and from which facet.",
17
+ schema: {
18
+ type: "object",
19
+ additionalProperties: false,
20
+ properties: {
21
+ first: {
22
+ type: "integer",
23
+ title: "Number of products",
24
+ minimum: 1,
25
+ maximum: 100
26
+ },
27
+ sort: {
28
+ type: "string",
29
+ enum: [
30
+ "relevance",
31
+ "price_asc",
32
+ "price_desc",
33
+ "name_asc",
34
+ "name_desc",
35
+ "release",
36
+ "orders",
37
+ "discount"
38
+ ],
39
+ title: "Sort"
40
+ },
41
+ categoryId: {
42
+ type: "string",
43
+ title: "Category ID"
44
+ },
45
+ brandId: {
46
+ type: "string",
47
+ title: "Brand ID"
48
+ },
49
+ collectionId: {
50
+ type: "string",
51
+ title: "Collection ID"
52
+ },
53
+ searchTerm: {
54
+ type: "string",
55
+ title: "Search term"
56
+ },
57
+ availableOnly: {
58
+ type: "boolean",
59
+ title: "Available only"
60
+ }
61
+ }
62
+ }
63
+ });
11
64
  const fragment = `
12
65
  fragment ProductShelfData on Query {
13
- products(first: 12, orderBy: { field: RELEVANCE, direction: DESC }) {
66
+ products(
67
+ first: $first
68
+ orderBy: $orderBy
69
+ filter: {
70
+ categoryIds: $categoryId
71
+ brandIds: $brandId
72
+ collectionId: $collectionId
73
+ searchTerm: $searchTerm
74
+ availableOnly: $availableOnly
75
+ }
76
+ ) {
14
77
  edges {
15
78
  node {
16
79
  id
@@ -88,7 +151,7 @@ function useProductShelfItem() {
88
151
  if (!item) throw new Error("Product card components must be used inside ProductShelfItems.");
89
152
  return item;
90
153
  }
91
- const SORT_MAP = {
154
+ const PRODUCT_SHELF_SORT_MAP = {
92
155
  relevance: {
93
156
  field: "RELEVANCE",
94
157
  direction: "DESC"
@@ -122,21 +185,6 @@ const SORT_MAP = {
122
185
  direction: "DESC"
123
186
  }
124
187
  };
125
- function buildProductShelfVariables(config) {
126
- const { first = 5, sort = "relevance", searchTerm, categoryId, brandId, collectionId, specifications, availableOnly } = config;
127
- const filter = {};
128
- if (searchTerm) filter.searchTerm = searchTerm;
129
- if (categoryId) filter.categoryIds = [categoryId];
130
- if (brandId) filter.brandIds = [brandId];
131
- if (collectionId) filter.collectionId = collectionId;
132
- if (specifications && specifications.length > 0) filter.specifications = specifications;
133
- if (availableOnly) filter.availableOnly = true;
134
- return {
135
- first,
136
- orderBy: SORT_MAP[sort],
137
- filter
138
- };
139
- }
140
188
  function transformProducts(data) {
141
189
  return data.edges.map(({ node }) => {
142
190
  const image = node.images.edges[0]?.node;
@@ -175,21 +223,69 @@ const ProductShelfRootDefinition = defineComponent({
175
223
  fragment,
176
224
  fetchStrategy: "lazy",
177
225
  skeleton: ProductShelfSkeleton,
226
+ content: ProductShelfContent,
227
+ variables: { orderBy: {
228
+ from: "sort",
229
+ values: {
230
+ relevance: {
231
+ field: "RELEVANCE",
232
+ direction: "DESC"
233
+ },
234
+ price_asc: {
235
+ field: "PRICE",
236
+ direction: "ASC"
237
+ },
238
+ price_desc: {
239
+ field: "PRICE",
240
+ direction: "DESC"
241
+ },
242
+ name_asc: {
243
+ field: "TITLE",
244
+ direction: "ASC"
245
+ },
246
+ name_desc: {
247
+ field: "TITLE",
248
+ direction: "DESC"
249
+ },
250
+ release: {
251
+ field: "RELEASE_DATE",
252
+ direction: "DESC"
253
+ },
254
+ orders: {
255
+ field: "BEST_SELLING",
256
+ direction: "DESC"
257
+ },
258
+ discount: {
259
+ field: "RELEVANCE",
260
+ direction: "DESC"
261
+ }
262
+ }
263
+ } },
178
264
  editor: {
179
265
  label: "Product Shelf Root",
180
266
  category: "commerce"
181
267
  },
182
268
  providesContext: "product-shelf",
183
269
  schema: {
184
- first: {
270
+ itemsPerPage: {
185
271
  type: "number",
186
272
  default: 5,
187
- label: "Number of Products"
273
+ label: "Items Per Page"
188
274
  },
189
- itemsPerPage: {
275
+ usePriceWithTaxes: {
276
+ type: "boolean",
277
+ default: false,
278
+ label: "Use Price With Taxes"
279
+ },
280
+ taxesLabel: {
281
+ type: "string",
282
+ default: "Tax included",
283
+ label: "Taxes Label"
284
+ },
285
+ first: {
190
286
  type: "number",
191
287
  default: 5,
192
- label: "Items Per Page"
288
+ label: "Number of Products"
193
289
  },
194
290
  sort: {
195
291
  type: [
@@ -221,27 +317,13 @@ const ProductShelfRootDefinition = defineComponent({
221
317
  type: "string",
222
318
  label: "Collection ID"
223
319
  },
224
- specifications: {
225
- type: "array",
226
- label: "Specification Filters"
227
- },
228
320
  availableOnly: {
229
321
  type: "boolean",
230
322
  default: false,
231
323
  label: "Available Only"
232
- },
233
- usePriceWithTaxes: {
234
- type: "boolean",
235
- default: false,
236
- label: "Use Price With Taxes"
237
- },
238
- taxesLabel: {
239
- type: "string",
240
- default: "Tax included",
241
- label: "Taxes Label"
242
324
  }
243
325
  },
244
- guideline: "Data-owning root of a facet-sourced product shelf. Owns a products(filter:) fragment and receives $data lazily. Compose as: ProductShelfRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext + CarouselBullets)). Compose ProductShelfHeading with the title as its children to render a heading; set the source with categoryId / brandId / collectionId / searchTerm / specifications and tune first, sort, availableOnly. Renders a skeleton while loading and nothing when the result is empty. Bind itemsPerPage to the CarouselRoot so paging matches the shelf config. See product-shelf.md."
326
+ guideline: "Data-owning root of a facet-sourced product shelf. Owns a products(first:, orderBy:, filter:) fragment whose variables bind to this element: the merchant fills them in the CMS (content model product-shelf) and the props of the same name are the defaults. Receives $data lazily. Compose as: ProductShelfRoot > (ProductShelfHeading + CarouselRoot > (ProductShelfItems + CarouselPrevious + CarouselNext + CarouselBullets)). Compose ProductShelfHeading with the title as its children to render a heading; set the source with categoryId / brandId / collectionId / searchTerm and tune first, sort, availableOnly. Renders a skeleton while loading and nothing when the result is empty. Bind itemsPerPage to the CarouselRoot so paging matches the shelf config. See product-shelf.md."
245
327
  });
246
328
  const ProductShelfItemsDefinition = defineComponent({
247
329
  name: "ProductShelfItems",
@@ -267,8 +349,31 @@ const ProductShelfHeadingDefinition = defineComponent({
267
349
  schema: {},
268
350
  guideline: "Renders its children as the shelf title (an <h2>) and provides the section's accessible label. Author the title text as its children (props.children). Place it inside ProductShelfRoot where the title should sit — usually above CarouselRoot. Renders nothing when it has no children."
269
351
  });
270
- function ProductShelfRoot({ first = 5, itemsPerPage = 5, sort = "relevance", searchTerm, categoryId, brandId, collectionId, specifications, availableOnly = false, usePriceWithTaxes = false, taxesLabel = "Tax included", products, $data: $dataProp, className, children, ...props }) {
352
+ /** The content value when the merchant filled the field, else the prop (the shared emptiness rule). */
353
+ function contentOrProp(content, prop) {
354
+ if (content === void 0 || isEmptyContentValue(content)) return prop;
355
+ return content;
356
+ }
357
+ function ProductShelfRoot({ first = 5, itemsPerPage = 5, sort = "relevance", searchTerm, categoryId, brandId, collectionId, availableOnly = false, usePriceWithTaxes = false, taxesLabel = "Tax included", products, $content, $data: $dataProp, className, children, ...props }) {
271
358
  const $data = useDataOverride($dataProp);
359
+ const shelfConfig = useMemo(() => ({
360
+ first: contentOrProp($content?.first, first),
361
+ sort: contentOrProp($content?.sort, sort),
362
+ searchTerm: contentOrProp($content?.searchTerm, searchTerm),
363
+ categoryId: contentOrProp($content?.categoryId, categoryId),
364
+ brandId: contentOrProp($content?.brandId, brandId),
365
+ collectionId: contentOrProp($content?.collectionId, collectionId),
366
+ availableOnly: contentOrProp($content?.availableOnly, availableOnly)
367
+ }), [
368
+ $content,
369
+ first,
370
+ sort,
371
+ searchTerm,
372
+ categoryId,
373
+ brandId,
374
+ collectionId,
375
+ availableOnly
376
+ ]);
272
377
  const headingId = useId();
273
378
  const [headingCount, setHeadingCount] = useState(0);
274
379
  const registerHeading = useCallback(() => {
@@ -283,27 +388,22 @@ function ProductShelfRoot({ first = 5, itemsPerPage = 5, sort = "relevance", sea
283
388
  usePriceWithTaxes,
284
389
  taxesLabel
285
390
  }), [usePriceWithTaxes, taxesLabel]);
286
- const hasHeading = headingCount > 0;
287
- let items;
288
- if (products !== void 0) items = products;
289
- else if ($data === void 0) return /* @__PURE__ */ jsx(ProductShelfSkeleton, { className });
290
- else items = $data ? transformProducts($data) : [];
291
- items = items.slice(0, first);
292
- if (items.length === 0) return null;
293
- const shelf = {
391
+ const items = useMemo(() => {
392
+ if (products !== void 0) return products;
393
+ return $data ? transformProducts($data) : [];
394
+ }, [products, $data]);
395
+ const shelf = useMemo(() => ({
294
396
  itemsPerPage,
295
- variables: buildProductShelfVariables({
296
- first,
297
- sort,
298
- searchTerm,
299
- categoryId,
300
- brandId,
301
- collectionId,
302
- specifications,
303
- availableOnly
304
- }),
397
+ config: shelfConfig,
305
398
  count: items.length
306
- };
399
+ }), [
400
+ itemsPerPage,
401
+ shelfConfig,
402
+ items.length
403
+ ]);
404
+ const hasHeading = headingCount > 0;
405
+ if (products === void 0 && $data === void 0) return /* @__PURE__ */ jsx(ProductShelfSkeleton, { className });
406
+ if (items.length === 0) return null;
307
407
  return /* @__PURE__ */ jsx(ProductListConfigContext, {
308
408
  value: config,
309
409
  children: /* @__PURE__ */ jsx(ProductShelfProductsContext, {
@@ -373,6 +473,6 @@ const ProductShelf = {
373
473
  Skeleton: ProductShelfSkeleton
374
474
  };
375
475
  //#endregion
376
- export { ProductShelf, ProductShelfHeading, ProductShelfHeadingDefinition, ProductShelfItems, ProductShelfItemsDefinition, ProductShelfRoot, ProductShelfRootDefinition, ProductShelfSkeleton, buildProductShelfVariables, transformProducts, useProductShelf, useProductShelfItem };
476
+ export { PRODUCT_SHELF_SORT_MAP, ProductShelf, ProductShelfContent, ProductShelfHeading, ProductShelfHeadingDefinition, ProductShelfItems, ProductShelfItemsDefinition, ProductShelfRoot, ProductShelfRootDefinition, ProductShelfSkeleton, transformProducts, useProductShelf, useProductShelfItem };
377
477
 
378
478
  //# sourceMappingURL=product-shelf.js.map