@vtex/faststore 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +4 -2
- package/dist/catalog.json +286 -265
- package/dist/components.d.ts +2 -1
- package/dist/components.js +2 -1
- package/dist/content-types.json +16 -0
- package/dist/core/dist/actions/registry.d.ts +1 -1
- package/dist/core/dist/actions/registry.js +1 -1
- package/dist/core/dist/actions/registry.js.map +1 -1
- package/dist/docs/account-entry-point.md +1 -1
- package/dist/docs/cart.md +2 -2
- package/dist/docs/session.md +12 -6
- package/dist/docs/sign-in-redirect.md +137 -0
- package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +2 -2
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +5 -2
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-entry-point/account-entry-point.js +4 -16
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/buy-button/buy-button.d.ts +1 -1
- package/dist/matter/buy-button/buy-button.js +3 -1
- package/dist/matter/buy-button/buy-button.js.map +1 -1
- package/dist/matter/cart/cart.d.ts +6 -6
- package/dist/matter/cart/cart.js +13 -6
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/cart-item/cart-item.d.ts +2 -2
- package/dist/matter/cart-item/cart-item.js +5 -2
- package/dist/matter/cart-item/cart-item.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences.js +3 -1
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +1 -1
- package/dist/matter/client-profile/client-profile.js +3 -1
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-form.d.ts +1 -1
- package/dist/matter/client-profile-form/client-profile-form.js +3 -1
- package/dist/matter/client-profile-form/client-profile-form.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options-accordion.d.ts +1 -1
- package/dist/matter/delivery-options/delivery-options-accordion.js +3 -1
- package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options-controller.d.ts +1 -1
- package/dist/matter/delivery-options/delivery-options-controller.js +3 -1
- package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
- package/dist/matter/delivery-options/delivery-options.d.ts +1 -1
- package/dist/matter/delivery-options/delivery-options.js +3 -1
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/email-identification/email-identification.d.ts +1 -1
- package/dist/matter/email-identification/email-identification.js +3 -1
- package/dist/matter/email-identification/email-identification.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.d.ts +1 -1
- package/dist/matter/identity-summary/identity-summary.js +3 -1
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/product-list/product-list.d.ts +1 -1
- package/dist/matter/product-list/product-list.js +3 -1
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/region/region.d.ts +1 -1
- package/dist/matter/region/region.js +5 -2
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/session/session.js +1 -1
- package/dist/matter/session/session.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +1 -1
- package/dist/matter/shipping-address/shipping-address.js +3 -1
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/sign-in-form/sign-in-form.d.ts +1 -1
- package/dist/matter/sign-in-form/sign-in-form.js +3 -1
- package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
- package/dist/matter/sign-in-redirect/sign-in-redirect.d.ts +45 -0
- package/dist/matter/sign-in-redirect/sign-in-redirect.js +127 -0
- package/dist/matter/sign-in-redirect/sign-in-redirect.js.map +1 -0
- package/dist/sdk/actions/actions-override-context.js +37 -0
- package/dist/sdk/actions/actions-override-context.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +8 -6
- package/dist/sdk/actions/cart-action-provider.js +10 -8
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/identity-action-provider.d.ts +2 -2
- package/dist/sdk/actions/identity-action-provider.js +4 -4
- package/dist/sdk/actions/identity-action-provider.js.map +1 -1
- package/dist/sdk/actions/inject-actions.d.ts +1 -5
- package/dist/sdk/actions/inject-actions.js +23 -21
- package/dist/sdk/actions/inject-actions.js.map +1 -1
- package/dist/sdk/actions/region-action-provider.d.ts +4 -3
- package/dist/sdk/actions/region-action-provider.js +6 -5
- package/dist/sdk/actions/region-action-provider.js.map +1 -1
- package/dist/sdk/actions/session-action-provider.d.ts +4 -3
- package/dist/sdk/actions/session-action-provider.js +6 -5
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +4 -3
- package/dist/sdk/actions/shipping-action-provider.js +6 -5
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/client/create-client.js +24 -2
- package/dist/sdk/client/create-client.js.map +1 -1
- package/dist/sdk/content/create-content-runtime.js +1 -1
- package/dist/utils/safe-href.js +107 -1
- package/dist/utils/safe-href.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-profile-form.js","names":["PhoneFieldParticle","DocumentFieldParticle"],"sources":["../../../src/matter/client-profile-form/client-profile-form.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type FormEvent, type ReactNode, useContext, useRef, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport {\n DocumentField as DocumentFieldParticle,\n isSupportedDocumentType,\n isValidDocument,\n} from '../../particles/document-field/document-field.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport type { PhoneCountry } from '../../particles/phone-field/phone-field.js';\nimport {\n PhoneField as PhoneFieldParticle,\n toE164,\n validatePhoneNumber,\n} 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 type { ClientProfileUserError } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-profile-form` compound (specs/fspg-863-client-profile-form.md) — the batched,\n * explicit-save customer-information form, decomposed into freely-composable parts. `Root` owns the\n * mechanics (a local draft across BOTH sibling identity entities, dirty tracking, format validation,\n * a single batched Save that dispatches at most one `clientProfileUpdate` and one\n * `clientPreferencesUpdate` per press carrying only touched fields, and the confirmation on a clean\n * save); it publishes no signal, so nothing outside it reacts to a completed press. The\n * field/fieldset/feedback leaves are thin, context-bound, and placed by the\n * schema author in any order/subset.\n *\n * Composability is the point: a merchant whose platform config drops a field, reorders them, or adds\n * a derived one just composes accordingly — nothing is hard-coded into a closed template. A part that\n * needs extra data declares its own `requiresContext`.\n *\n * Email is read-only (identification, not profile data — `EmailIdentificationControl`'s job). The\n * document field self-gates on `isSupportedDocumentType(documentType)`.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on Root (ADR-0011), self-contained data root\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientProfileFormData on Query {\n clientProfile {\n __typename\n id\n email\n firstName\n lastName\n phone\n document\n }\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 — both identity sibling entities. */\nexport type ClientProfileFormData = {\n clientProfile?: {\n email?: string | null;\n firstName?: string | null;\n lastName?: string | null;\n phone?: string | null;\n document?: string | null;\n } | null;\n clientPreferences?: {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n } | null;\n} | null;\n\ntype Draft = {\n firstName: string;\n lastName: string;\n phone: string;\n document: string;\n optinNewsLetter: boolean;\n savePersonalData: boolean;\n};\n\ntype FieldKey = keyof Draft;\ntype TextFieldKey = 'firstName' | 'lastName';\ntype CheckboxKey = 'optinNewsLetter' | 'savePersonalData';\ntype ValidatableFieldKey = 'phone' | 'document';\ntype FieldErrors = Partial<Record<ValidatableFieldKey, string>>;\n\nconst VALIDATION_ORDER: readonly ValidatableFieldKey[] = ['phone', 'document'];\n\n/** Shown when a write throws instead of resolving `userErrors`; the resolved case carries its own message. */\nconst SAVE_FAILED_MESSAGE = 'Unable to save your information. Please try again.';\n\nfunction stripNonDigits(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n// ---------------------------------------------------------------------------\n// Context — Root publishes the draft + mechanics to its field leaves\n// ---------------------------------------------------------------------------\n\ntype ClientProfileFormContextValue = {\n savedEmail: string;\n draft: Draft;\n fieldErrors: FieldErrors;\n disabled: boolean;\n readOnly: boolean;\n pending: boolean;\n serverError: string | null;\n saved: boolean;\n country: PhoneCountry;\n documentType: string | undefined;\n setText: (key: TextFieldKey, value: string) => void;\n setPhone: (value: string) => void;\n setDocument: (value: string) => void;\n setCheckbox: (key: CheckboxKey, checked: boolean) => void;\n};\n\nconst CLIENT_PROFILE_FORM_UNSET = Symbol('ClientProfileFormContextUnset');\n\nconst ClientProfileFormContext = createContext<ClientProfileFormContextValue | typeof CLIENT_PROFILE_FORM_UNSET>(\n CLIENT_PROFILE_FORM_UNSET,\n);\n\nfunction useClientProfileFormContext(): ClientProfileFormContextValue {\n const ctx = useContext(ClientProfileFormContext);\n if (ctx === CLIENT_PROFILE_FORM_UNSET) {\n throw new Error('ClientProfileForm parts must be used inside ClientProfileForm.Root.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileFormRootProps = {\n /** Phone mask/validation country, provided to the phone leaf via context. */\n country?: PhoneCountry;\n /** Locale document type; the document leaf renders only when supported. Provided via context. */\n documentType?: string;\n className?: string;\n children?: ReactNode;\n /** `ClientProfileFormData` slice forwarded by the reactive runtime. */\n $data?: ClientProfileFormData;\n /** Compiler-injected action surface; carries `identity.clientProfileUpdate` / `clientPreferencesUpdate`. */\n $actions?: ComponentActions;\n};\n\nexport type ClientProfileFormFieldsetProps = {\n /** Visible legend; omitted (no title) when unset. */\n legend?: string;\n /** Value emitted on `data-fs-client-profile-form-section` for scoped styling (e.g. `profile`). */\n section?: string;\n className?: string;\n children?: ReactNode;\n};\n\ntype LabelledFieldProps = { label?: string; className?: string };\n\nexport type ClientProfileFormEmailFieldProps = LabelledFieldProps;\nexport type ClientProfileFormFirstNameFieldProps = LabelledFieldProps;\nexport type ClientProfileFormLastNameFieldProps = LabelledFieldProps;\nexport type ClientProfileFormPhoneFieldProps = LabelledFieldProps;\nexport type ClientProfileFormDocumentFieldProps = LabelledFieldProps;\nexport type ClientProfileFormNewsletterCheckboxProps = LabelledFieldProps;\nexport type ClientProfileFormSavePersonalDataCheckboxProps = LabelledFieldProps;\nexport type ClientProfileFormErrorProps = { className?: string };\nexport type ClientProfileFormConfirmationProps = { message?: string; className?: string };\nexport type ClientProfileFormSubmitButtonProps = { label?: string; className?: string; children?: ReactNode };\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientProfileFormRootDefinition = defineComponent<ClientProfileFormRootProps>({\n name: 'ClientProfileFormRoot',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n providesContext: 'client-profile-form',\n actions: { identity: ['clientProfileUpdate', 'clientPreferencesUpdate'] },\n editor: { label: 'Client Profile Form Root', category: 'action' },\n schema: {\n country: { type: 'string', default: 'US', label: 'Phone country (US or BR)' },\n documentType: { type: 'string', label: 'Document type (e.g. CPF)' },\n },\n guideline:\n 'Data root + form mechanics for the batched customer-information form: owns the draft across clientProfile + clientPreferences, dirty tracking, format-only validation (never requiredness), one batched Save (dispatches only touched fields), and the confirmation on a clean save. It reports nothing to anything outside itself. Requires a SessionRoot ancestor. Compose the field/fieldset/feedback parts as children in any order/subset. See client-profile-form.md.',\n});\n\nexport const ClientProfileFormFieldsetDefinition = defineComponent<ClientProfileFormFieldsetProps>({\n name: 'ClientProfileFormFieldset',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormFieldset,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Fieldset', category: 'layout' },\n schema: {\n legend: { type: 'string', label: 'Legend' },\n section: { type: 'string', label: 'Section name (data-fs hook)' },\n },\n guideline:\n 'Optional grouping wrapper: a Fieldset whose disabled state follows the form, with an optional legend and a `section` name emitted on data-fs-client-profile-form-section for scoped styling. Compose any subset of the field parts as children. Must be inside ClientProfileForm.Root.',\n});\n\n// Each field leaf is registered with its own inline `defineComponent({...})` — the native catalog\n// builder statically extracts only that literal form, so a factory helper would leave these parts out\n// of catalog.json (and thus uncomposable in a store schema). See client-profile-form.test.tsx's\n// catalog-completeness guard.\n\nexport const ClientProfileFormEmailFieldDefinition = defineComponent<ClientProfileFormEmailFieldProps>({\n name: 'ClientProfileFormEmailField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormEmailField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Email Field', category: 'action' },\n schema: { label: { type: 'string', default: 'Email', label: 'Label' } },\n guideline: 'Read-only email display, seeded from the profile. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormFirstNameFieldDefinition = defineComponent<ClientProfileFormFirstNameFieldProps>({\n name: 'ClientProfileFormFirstNameField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormFirstNameField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile First Name Field', category: 'action' },\n schema: { label: { type: 'string', default: 'First name', label: 'Label' } },\n guideline: 'First-name text field bound to the form draft. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormLastNameFieldDefinition = defineComponent<ClientProfileFormLastNameFieldProps>({\n name: 'ClientProfileFormLastNameField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormLastNameField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Last Name Field', category: 'action' },\n schema: { label: { type: 'string', default: 'Last name', label: 'Label' } },\n guideline: 'Last-name text field bound to the form draft. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormPhoneFieldDefinition = defineComponent<ClientProfileFormPhoneFieldProps>({\n name: 'ClientProfileFormPhoneField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormPhoneField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Phone Field', category: 'action' },\n schema: { label: { type: 'string', default: 'Phone number', label: 'Label' } },\n guideline:\n 'Country-masked phone field (country from Root); format-validated on Save. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormDocumentFieldDefinition = defineComponent<ClientProfileFormDocumentFieldProps>({\n name: 'ClientProfileFormDocumentField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormDocumentField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Document Field', category: 'action' },\n schema: { label: { type: 'string', default: 'CPF', label: 'Label' } },\n guideline:\n 'Tax-document field; self-gates on Root.documentType and check-digit-validates on Save. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormNewsletterCheckboxDefinition = defineComponent<ClientProfileFormNewsletterCheckboxProps>({\n name: 'ClientProfileFormNewsletterCheckbox',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormNewsletterCheckbox,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Newsletter Checkbox', category: 'action' },\n schema: { label: { type: 'string', default: 'Email me with news and offers', label: 'Label' } },\n guideline: 'Newsletter opt-in checkbox bound to the form draft. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormSavePersonalDataCheckboxDefinition =\n defineComponent<ClientProfileFormSavePersonalDataCheckboxProps>({\n name: 'ClientProfileFormSavePersonalDataCheckbox',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormSavePersonalDataCheckbox,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Save-Personal-Data Checkbox', category: 'action' },\n schema: { label: { type: 'string', default: 'Save my information for next time', label: 'Label' } },\n guideline: 'Save-personal-data opt-in checkbox bound to the form draft. Must be inside ClientProfileForm.Root.',\n });\n\nexport const ClientProfileFormErrorDefinition = defineComponent<ClientProfileFormErrorProps>({\n name: 'ClientProfileFormError',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormError,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Error', category: 'content' },\n schema: {},\n guideline:\n 'Renders the inline server error (role=\"alert\") from the last Save, or nothing. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormConfirmationDefinition = defineComponent<ClientProfileFormConfirmationProps>({\n name: 'ClientProfileFormConfirmation',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormConfirmation,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Confirmation', category: 'content' },\n schema: { message: { type: 'string', default: 'Your information has been saved.', label: 'Confirmation message' } },\n guideline:\n 'Renders the success confirmation (an <output>) after a Save with no errors, cleared on the next edit. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormSubmitButtonDefinition = defineComponent<ClientProfileFormSubmitButtonProps>({\n name: 'ClientProfileFormSubmitButton',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormSubmitButton,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Submit Button', category: 'action' },\n schema: { label: { type: 'string', default: 'Save and continue', label: 'Button label' } },\n guideline:\n 'The batched-Save submit button (type=submit). Disabled while a write is in flight; renders nothing when the session is read-only. Compose an Icon + text as children, or rely on `label`. Must be inside ClientProfileForm.Root.',\n});\n\n// ---------------------------------------------------------------------------\n// Validation — field-intrinsic format only\n//\n// Which fields a shopper must fill is a business rule, and the parts of this form are composed freely\n// by the schema, so a required check here can fire for a field the page never mounted: nothing renders\n// the message, focus has nowhere to land, and Save goes dead. Requiredness waits for the RFC that\n// decides where it belongs.\n// ---------------------------------------------------------------------------\n\nfunction validateDraft(draft: Draft, country: PhoneCountry, documentType: string | undefined): FieldErrors {\n const errors: FieldErrors = {};\n\n const phoneRaw = draft.phone.trim();\n if (phoneRaw && !validatePhoneNumber(phoneRaw, country)) {\n errors.phone = 'Invalid phone number';\n }\n\n if (isSupportedDocumentType(documentType)) {\n const digits = stripNonDigits(draft.document);\n if (digits && !isValidDocument(digits, documentType)) {\n errors.document = 'Invalid document number';\n }\n }\n\n return errors;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\n/**\n * Data root + form shell. Seeds a local draft from `$data`, tracks dirty fields (background `$data`\n * refreshes only re-seed untouched fields), runs format-only validation, and on Save dispatches the\n * batched `clientProfileUpdate` / `clientPreferencesUpdate` (touched fields only), showing the\n * confirmation on success. Provides everything the field leaves need via context; renders a `<form>`\n * around its children.\n *\n * It publishes nothing. A finished press is not announced, so no section collapses as a result and\n * nothing outside this form can react to it. Whether a checkout sequences on a completed form is left to\n * the flow-of-steps RFC (#611).\n */\nexport function ClientProfileFormRoot({\n country = 'US',\n documentType,\n className,\n children,\n $data: $dataProp,\n $actions,\n}: ClientProfileFormRootProps) {\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 savedEmail = $data?.clientProfile?.email ?? '';\n const savedFirstName = $data?.clientProfile?.firstName ?? '';\n const savedLastName = $data?.clientProfile?.lastName ?? '';\n const savedPhone = $data?.clientProfile?.phone ?? '';\n const savedDocument = $data?.clientProfile?.document ?? '';\n const savedOptinNewsLetter = Boolean($data?.clientPreferences?.optinNewsLetter ?? false);\n const savedSavePersonalData = Boolean($data?.clientPreferences?.savePersonalData ?? false);\n\n const [draft, setDraft] = useState<Draft>(() => ({\n firstName: savedFirstName,\n lastName: savedLastName,\n phone: savedPhone,\n document: savedDocument,\n optinNewsLetter: savedOptinNewsLetter,\n savePersonalData: savedSavePersonalData,\n }));\n const [dirtyFields, setDirtyFields] = useState<ReadonlySet<FieldKey>>(new Set());\n const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});\n const [pending, setPending] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const formRef = useRef<HTMLFormElement>(null);\n\n // Re-seed pristine fields when the underlying saved entities change; dirty fields are never\n // clobbered. Runs during render (ref-comparison), so the re-seed lands in the same commit.\n const prevSavedRef = useRef({\n firstName: savedFirstName,\n lastName: savedLastName,\n phone: savedPhone,\n document: savedDocument,\n optinNewsLetter: savedOptinNewsLetter,\n savePersonalData: savedSavePersonalData,\n });\n const prevSaved = prevSavedRef.current;\n const changedKeys: FieldKey[] = [];\n if (prevSaved.firstName !== savedFirstName) changedKeys.push('firstName');\n if (prevSaved.lastName !== savedLastName) changedKeys.push('lastName');\n if (prevSaved.phone !== savedPhone) changedKeys.push('phone');\n if (prevSaved.document !== savedDocument) changedKeys.push('document');\n if (prevSaved.optinNewsLetter !== savedOptinNewsLetter) changedKeys.push('optinNewsLetter');\n if (prevSaved.savePersonalData !== savedSavePersonalData) changedKeys.push('savePersonalData');\n if (changedKeys.length > 0) {\n const nextSaved = {\n firstName: savedFirstName,\n lastName: savedLastName,\n phone: savedPhone,\n document: savedDocument,\n optinNewsLetter: savedOptinNewsLetter,\n savePersonalData: savedSavePersonalData,\n };\n // react-doctor-disable-next-line no-ref-current-in-render -- paired with the setDraft call right below, both replay together\n prevSavedRef.current = nextSaved;\n setDraft((prevDraft) => {\n let next = prevDraft;\n for (const key of changedKeys) {\n if (dirtyFields.has(key)) continue;\n if (next === prevDraft) next = { ...prevDraft };\n (next as Record<FieldKey, string | boolean>)[key] = nextSaved[key];\n }\n return next;\n });\n }\n\n const disabled = pending || readOnly;\n\n function markDirty(key: FieldKey) {\n setDirtyFields((prev) => {\n if (prev.has(key)) return prev;\n const next = new Set(prev);\n next.add(key);\n return next;\n });\n }\n\n function clearFeedback() {\n if (saved) setSaved(false);\n if (serverError) setServerError(null);\n }\n\n function clearFieldError(key: ValidatableFieldKey) {\n if (fieldErrors[key]) {\n setFieldErrors((prev) => {\n const next = { ...prev };\n delete next[key];\n return next;\n });\n }\n }\n\n function setText(key: TextFieldKey, value: string) {\n setDraft((prev) => ({ ...prev, [key]: value }));\n markDirty(key);\n clearFeedback();\n }\n\n function setPhone(value: string) {\n setDraft((prev) => ({ ...prev, phone: value }));\n markDirty('phone');\n clearFeedback();\n clearFieldError('phone');\n }\n\n function setDocument(value: string) {\n setDraft((prev) => ({ ...prev, document: value }));\n markDirty('document');\n clearFeedback();\n clearFieldError('document');\n }\n\n function setCheckbox(key: CheckboxKey, checked: boolean) {\n setDraft((prev) => ({ ...prev, [key]: checked }));\n markDirty(key);\n clearFeedback();\n }\n\n function focusField(key: ValidatableFieldKey) {\n const el = formRef.current?.querySelector<HTMLInputElement>(`[data-fs-client-profile-form-field=\"${key}\"] input`);\n el?.focus();\n }\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (readOnly || pending) return;\n\n // Validation runs before the pristine shortcut below, not after: a stored value can be malformed\n // without anything having been typed this session, and a press that reached the shortcut first would\n // confirm it as acceptable.\n const errors = validateDraft(draft, country, documentType);\n if (Object.keys(errors).length > 0) {\n setFieldErrors(errors);\n const firstInvalid = VALIDATION_ORDER.find((key) => errors[key]);\n if (firstInvalid) focusField(firstInvalid);\n return;\n }\n setFieldErrors({});\n\n // Nothing changed: dispatch nothing, which also avoids the NO_*_PROVIDED user errors. The press\n // still confirms, because a shopper who opens an already-filled form and presses Save is asking\n // whether their data is accepted, and a silent return answers nothing.\n if (dirtyFields.size === 0) {\n setServerError(null);\n setSaved(true);\n return;\n }\n\n const identity = $actions?.identity;\n if (!identity) return;\n\n const profilePayload: Record<string, string> = {};\n if (dirtyFields.has('firstName')) profilePayload.firstName = draft.firstName.trim();\n if (dirtyFields.has('lastName')) profilePayload.lastName = draft.lastName.trim();\n // toE164 is documented for values validatePhoneNumber has accepted; on a blank one it would return\n // the bare country code, writing \"+55\" over the shopper's number and then failing every later Save.\n if (dirtyFields.has('phone')) {\n profilePayload.phone = draft.phone.trim() ? toE164(draft.phone, country) : '';\n }\n if (dirtyFields.has('document')) profilePayload.document = stripNonDigits(draft.document);\n\n const preferencesPayload: Record<string, boolean> = {};\n if (dirtyFields.has('optinNewsLetter')) preferencesPayload.optinNewsLetter = draft.optinNewsLetter;\n if (dirtyFields.has('savePersonalData')) preferencesPayload.savePersonalData = draft.savePersonalData;\n\n const writes: Promise<readonly ClientProfileUserError[]>[] = [];\n if (Object.keys(profilePayload).length > 0 && identity.clientProfileUpdate) {\n writes.push(identity.clientProfileUpdate(profilePayload));\n }\n if (Object.keys(preferencesPayload).length > 0 && identity.clientPreferencesUpdate) {\n writes.push(identity.clientPreferencesUpdate(preferencesPayload));\n }\n if (writes.length === 0) return;\n\n setPending(true);\n setServerError(null);\n setSaved(false);\n try {\n const results = await Promise.all(writes);\n const allErrors = results.flat();\n if (allErrors.length > 0) {\n setServerError(allErrors[0]?.message ?? null);\n return;\n }\n setDirtyFields(new Set());\n setSaved(true);\n } catch (error) {\n // A write that throws rather than resolving userErrors would otherwise snap the form back to idle\n // with no message, losing the edit silently and escaping as an unhandled rejection. The dirty marks\n // stay, so the shopper can press Save again without retyping.\n console.error('[ClientProfileForm] profile write failed:', error);\n setServerError(SAVE_FAILED_MESSAGE);\n } finally {\n setPending(false);\n }\n }\n\n const contextValue: ClientProfileFormContextValue = {\n savedEmail,\n draft,\n fieldErrors,\n disabled,\n readOnly,\n pending,\n serverError,\n saved,\n country,\n documentType,\n setText,\n setPhone,\n setDocument,\n setCheckbox,\n };\n\n return (\n <ClientProfileFormContext.Provider value={contextValue}>\n <form\n ref={formRef}\n onSubmit={handleSubmit}\n className={className}\n data-fs-client-profile-form=\"\"\n {...(pending ? { 'data-fs-client-profile-form-pending': '' } : {})}\n {...(readOnly ? { 'data-fs-client-profile-form-readonly': '' } : {})}\n {...(saved ? { 'data-fs-client-profile-form-saved': '' } : {})}\n >\n {children}\n </form>\n </ClientProfileFormContext.Provider>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Fieldset (optional grouping)\n// ---------------------------------------------------------------------------\n\nexport function ClientProfileFormFieldset({ legend, section, className, children }: ClientProfileFormFieldsetProps) {\n const { disabled } = useClientProfileFormContext();\n return (\n <Fieldset.Root\n disabled={disabled}\n className={className}\n {...(section ? { 'data-fs-client-profile-form-section': section } : {})}\n >\n {legend ? <Fieldset.Legend>{legend}</Fieldset.Legend> : null}\n {children}\n </Fieldset.Root>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Field leaves\n// ---------------------------------------------------------------------------\n\nexport function ClientProfileFormEmailField({ label = 'Email', className }: ClientProfileFormEmailFieldProps) {\n const { savedEmail, disabled } = useClientProfileFormContext();\n return (\n <Field.Root className={className} data-fs-client-profile-form-field=\"email\" disabled={disabled}>\n <Field.Label>{label}</Field.Label>\n <Field.Control readOnly value={savedEmail} disabled={disabled} />\n </Field.Root>\n );\n}\n\nfunction TextField({ fieldKey, label, className }: { fieldKey: TextFieldKey; label: string; className?: string }) {\n const { draft, disabled, setText } = useClientProfileFormContext();\n const attr = fieldKey === 'firstName' ? 'first-name' : 'last-name';\n const autoComplete = fieldKey === 'firstName' ? 'given-name' : 'family-name';\n return (\n <Field.Root className={className} data-fs-client-profile-form-field={attr} disabled={disabled}>\n <Field.Label>{label}</Field.Label>\n <Field.Control\n name={fieldKey}\n autoComplete={autoComplete}\n value={draft[fieldKey]}\n onChange={(event) => setText(fieldKey, event.target.value)}\n disabled={disabled}\n />\n </Field.Root>\n );\n}\n\nexport function ClientProfileFormFirstNameField({\n label = 'First name',\n className,\n}: ClientProfileFormFirstNameFieldProps) {\n return <TextField fieldKey=\"firstName\" label={label} className={className} />;\n}\n\nexport function ClientProfileFormLastNameField({\n label = 'Last name',\n className,\n}: ClientProfileFormLastNameFieldProps) {\n return <TextField fieldKey=\"lastName\" label={label} className={className} />;\n}\n\nexport function ClientProfileFormPhoneField({ label = 'Phone number', className }: ClientProfileFormPhoneFieldProps) {\n const { draft, fieldErrors, disabled, country, setPhone } = useClientProfileFormContext();\n return (\n <div className={className} data-fs-client-profile-form-field=\"phone\">\n <PhoneFieldParticle\n label={label}\n country={country}\n value={draft.phone}\n onValueChange={setPhone}\n isDisabled={disabled}\n invalid={fieldErrors.phone ? true : undefined}\n error={fieldErrors.phone}\n />\n </div>\n );\n}\n\nexport function ClientProfileFormDocumentField({ label = 'CPF', className }: ClientProfileFormDocumentFieldProps) {\n const { draft, fieldErrors, disabled, documentType, setDocument } = useClientProfileFormContext();\n if (!isSupportedDocumentType(documentType)) return null;\n return (\n <div className={className} data-fs-client-profile-form-field=\"document\">\n <DocumentFieldParticle\n label={label}\n documentType={documentType}\n value={draft.document}\n onValueChange={setDocument}\n disabled={disabled}\n invalid={Boolean(fieldErrors.document)}\n errorMessage={fieldErrors.document ?? 'Invalid document number'}\n />\n </div>\n );\n}\n\nfunction CheckboxField({\n fieldKey,\n attr,\n label,\n className,\n}: {\n fieldKey: CheckboxKey;\n attr: string;\n label: string;\n className?: string;\n}) {\n const { draft, disabled, setCheckbox } = useClientProfileFormContext();\n return (\n <Field.Root className={className} data-fs-client-profile-form-field={attr} disabled={disabled}>\n <Field.Label>\n <Checkbox.Root\n checked={draft[fieldKey]}\n onCheckedChange={(checked) => setCheckbox(fieldKey, checked)}\n disabled={disabled}\n >\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {label}\n </Field.Label>\n </Field.Root>\n );\n}\n\nexport function ClientProfileFormNewsletterCheckbox({\n label = 'Email me with news and offers',\n className,\n}: ClientProfileFormNewsletterCheckboxProps) {\n return <CheckboxField fieldKey=\"optinNewsLetter\" attr=\"newsletter\" label={label} className={className} />;\n}\n\nexport function ClientProfileFormSavePersonalDataCheckbox({\n label = 'Save my information for next time',\n className,\n}: ClientProfileFormSavePersonalDataCheckboxProps) {\n return <CheckboxField fieldKey=\"savePersonalData\" attr=\"save-personal-data\" label={label} className={className} />;\n}\n\n// ---------------------------------------------------------------------------\n// Feedback + submit\n// ---------------------------------------------------------------------------\n\nexport function ClientProfileFormError({ className }: ClientProfileFormErrorProps) {\n const { serverError } = useClientProfileFormContext();\n if (!serverError) return null;\n return (\n <div role=\"alert\" className={className} data-fs-client-profile-form-error=\"\">\n {serverError}\n </div>\n );\n}\n\nexport function ClientProfileFormConfirmation({\n message = 'Your information has been saved.',\n className,\n}: ClientProfileFormConfirmationProps) {\n const { saved } = useClientProfileFormContext();\n if (!saved) return null;\n return (\n <output className={className} data-fs-client-profile-form-confirmation=\"\">\n {message}\n </output>\n );\n}\n\nexport function ClientProfileFormSubmitButton({\n label = 'Save and continue',\n className,\n children,\n}: ClientProfileFormSubmitButtonProps) {\n const { pending, readOnly } = useClientProfileFormContext();\n if (readOnly) return null;\n return (\n <button\n type=\"submit\"\n disabled={pending}\n className={className}\n data-fs-button=\"\"\n data-fs-client-profile-form-save=\"\"\n >\n {children ?? label}\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace Export\n// ---------------------------------------------------------------------------\n\nexport const ClientProfileForm = {\n Root: ClientProfileFormRoot,\n RootDefinition: ClientProfileFormRootDefinition,\n Fieldset: ClientProfileFormFieldset,\n FieldsetDefinition: ClientProfileFormFieldsetDefinition,\n EmailField: ClientProfileFormEmailField,\n EmailFieldDefinition: ClientProfileFormEmailFieldDefinition,\n FirstNameField: ClientProfileFormFirstNameField,\n FirstNameFieldDefinition: ClientProfileFormFirstNameFieldDefinition,\n LastNameField: ClientProfileFormLastNameField,\n LastNameFieldDefinition: ClientProfileFormLastNameFieldDefinition,\n PhoneField: ClientProfileFormPhoneField,\n PhoneFieldDefinition: ClientProfileFormPhoneFieldDefinition,\n DocumentField: ClientProfileFormDocumentField,\n DocumentFieldDefinition: ClientProfileFormDocumentFieldDefinition,\n NewsletterCheckbox: ClientProfileFormNewsletterCheckbox,\n NewsletterCheckboxDefinition: ClientProfileFormNewsletterCheckboxDefinition,\n SavePersonalDataCheckbox: ClientProfileFormSavePersonalDataCheckbox,\n SavePersonalDataCheckboxDefinition: ClientProfileFormSavePersonalDataCheckboxDefinition,\n Error: ClientProfileFormError,\n ErrorDefinition: ClientProfileFormErrorDefinition,\n Confirmation: ClientProfileFormConfirmation,\n ConfirmationDefinition: ClientProfileFormConfirmationDefinition,\n SubmitButton: ClientProfileFormSubmitButton,\n SubmitButtonDefinition: ClientProfileFormSubmitButtonDefinition,\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"client-profile-form.js","names":["PhoneFieldParticle","DocumentFieldParticle"],"sources":["../../../src/matter/client-profile-form/client-profile-form.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport { createContext, type FormEvent, type ReactNode, useContext, useRef, useState } from 'react';\nimport { Checkbox } from '../../particles/checkbox/checkbox.js';\nimport {\n DocumentField as DocumentFieldParticle,\n isSupportedDocumentType,\n isValidDocument,\n} from '../../particles/document-field/document-field.js';\nimport { Field } from '../../particles/field/field.js';\nimport { Fieldset } from '../../particles/fieldset/fieldset.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport type { PhoneCountry } from '../../particles/phone-field/phone-field.js';\nimport {\n PhoneField as PhoneFieldParticle,\n toE164,\n validatePhoneNumber,\n} 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 type { ClientProfileUserError } from '../../sdk/identity/operations.js';\nimport { useSession } from '../session/session.js';\n\n/**\n * The `client-profile-form` compound (specs/fspg-863-client-profile-form.md) — the batched,\n * explicit-save customer-information form, decomposed into freely-composable parts. `Root` owns the\n * mechanics (a local draft across BOTH sibling identity entities, dirty tracking, format validation,\n * a single batched Save that dispatches at most one `clientProfileUpdate` and one\n * `clientPreferencesUpdate` per press carrying only touched fields, and the confirmation on a clean\n * save); it publishes no signal, so nothing outside it reacts to a completed press. The\n * field/fieldset/feedback leaves are thin, context-bound, and placed by the\n * schema author in any order/subset.\n *\n * Composability is the point: a merchant whose platform config drops a field, reorders them, or adds\n * a derived one just composes accordingly — nothing is hard-coded into a closed template. A part that\n * needs extra data declares its own `requiresContext`.\n *\n * Email is read-only (identification, not profile data — `EmailIdentificationControl`'s job). The\n * document field self-gates on `isSupportedDocumentType(documentType)`.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — colocated on Root (ADR-0011), self-contained data root\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment ClientProfileFormData on Query {\n clientProfile {\n __typename\n id\n email\n firstName\n lastName\n phone\n document\n }\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 — both identity sibling entities. */\nexport type ClientProfileFormData = {\n clientProfile?: {\n email?: string | null;\n firstName?: string | null;\n lastName?: string | null;\n phone?: string | null;\n document?: string | null;\n } | null;\n clientPreferences?: {\n optinNewsLetter?: boolean | null;\n savePersonalData?: boolean | null;\n } | null;\n} | null;\n\ntype Draft = {\n firstName: string;\n lastName: string;\n phone: string;\n document: string;\n optinNewsLetter: boolean;\n savePersonalData: boolean;\n};\n\ntype FieldKey = keyof Draft;\ntype TextFieldKey = 'firstName' | 'lastName';\ntype CheckboxKey = 'optinNewsLetter' | 'savePersonalData';\ntype ValidatableFieldKey = 'phone' | 'document';\ntype FieldErrors = Partial<Record<ValidatableFieldKey, string>>;\n\nconst VALIDATION_ORDER: readonly ValidatableFieldKey[] = ['phone', 'document'];\n\n/** Shown when a write throws instead of resolving `userErrors`; the resolved case carries its own message. */\nconst SAVE_FAILED_MESSAGE = 'Unable to save your information. Please try again.';\n\nfunction stripNonDigits(value: string): string {\n return value.replace(/\\D/g, '');\n}\n\n// ---------------------------------------------------------------------------\n// Context — Root publishes the draft + mechanics to its field leaves\n// ---------------------------------------------------------------------------\n\ntype ClientProfileFormContextValue = {\n savedEmail: string;\n draft: Draft;\n fieldErrors: FieldErrors;\n disabled: boolean;\n readOnly: boolean;\n pending: boolean;\n serverError: string | null;\n saved: boolean;\n country: PhoneCountry;\n documentType: string | undefined;\n setText: (key: TextFieldKey, value: string) => void;\n setPhone: (value: string) => void;\n setDocument: (value: string) => void;\n setCheckbox: (key: CheckboxKey, checked: boolean) => void;\n};\n\nconst CLIENT_PROFILE_FORM_UNSET = Symbol('ClientProfileFormContextUnset');\n\nconst ClientProfileFormContext = createContext<ClientProfileFormContextValue | typeof CLIENT_PROFILE_FORM_UNSET>(\n CLIENT_PROFILE_FORM_UNSET,\n);\n\nfunction useClientProfileFormContext(): ClientProfileFormContextValue {\n const ctx = useContext(ClientProfileFormContext);\n if (ctx === CLIENT_PROFILE_FORM_UNSET) {\n throw new Error('ClientProfileForm parts must be used inside ClientProfileForm.Root.');\n }\n return ctx;\n}\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type ClientProfileFormRootProps = {\n /** Phone mask/validation country, provided to the phone leaf via context. */\n country?: PhoneCountry;\n /** Locale document type; the document leaf renders only when supported. Provided via context. */\n documentType?: string;\n className?: string;\n children?: ReactNode;\n /** `ClientProfileFormData` slice forwarded by the reactive runtime. */\n $data?: ClientProfileFormData;\n /** Compiler-injected action surface; carries `identity.clientProfileUpdate` / `clientPreferencesUpdate`. */\n $actions?: ComponentActions;\n};\n\nexport type ClientProfileFormFieldsetProps = {\n /** Visible legend; omitted (no title) when unset. */\n legend?: string;\n /** Value emitted on `data-fs-client-profile-form-section` for scoped styling (e.g. `profile`). */\n section?: string;\n className?: string;\n children?: ReactNode;\n};\n\ntype LabelledFieldProps = { label?: string; className?: string };\n\nexport type ClientProfileFormEmailFieldProps = LabelledFieldProps;\nexport type ClientProfileFormFirstNameFieldProps = LabelledFieldProps;\nexport type ClientProfileFormLastNameFieldProps = LabelledFieldProps;\nexport type ClientProfileFormPhoneFieldProps = LabelledFieldProps;\nexport type ClientProfileFormDocumentFieldProps = LabelledFieldProps;\nexport type ClientProfileFormNewsletterCheckboxProps = LabelledFieldProps;\nexport type ClientProfileFormSavePersonalDataCheckboxProps = LabelledFieldProps;\nexport type ClientProfileFormErrorProps = { className?: string };\nexport type ClientProfileFormConfirmationProps = { message?: string; className?: string };\nexport type ClientProfileFormSubmitButtonProps = { label?: string; className?: string; children?: ReactNode };\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const ClientProfileFormRootDefinition = defineComponent<ClientProfileFormRootProps>({\n name: 'ClientProfileFormRoot',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormRoot,\n fragment,\n fetchStrategy: 'reactive',\n requiresContext: 'session',\n providesContext: 'client-profile-form',\n actions: { identity: ['clientProfileUpdate', 'clientPreferencesUpdate'] },\n editor: { label: 'Client Profile Form Root', category: 'action' },\n schema: {\n country: { type: 'string', default: 'US', label: 'Phone country (US or BR)' },\n documentType: { type: 'string', label: 'Document type (e.g. CPF)' },\n },\n guideline:\n 'Data root + form mechanics for the batched customer-information form: owns the draft across clientProfile + clientPreferences, dirty tracking, format-only validation (never requiredness), one batched Save (dispatches only touched fields), and the confirmation on a clean save. It reports nothing to anything outside itself. Requires a SessionRoot ancestor. Compose the field/fieldset/feedback parts as children in any order/subset. See client-profile-form.md.',\n});\n\nexport const ClientProfileFormFieldsetDefinition = defineComponent<ClientProfileFormFieldsetProps>({\n name: 'ClientProfileFormFieldset',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormFieldset,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Fieldset', category: 'layout' },\n schema: {\n legend: { type: 'string', label: 'Legend' },\n section: { type: 'string', label: 'Section name (data-fs hook)' },\n },\n guideline:\n 'Optional grouping wrapper: a Fieldset whose disabled state follows the form, with an optional legend and a `section` name emitted on data-fs-client-profile-form-section for scoped styling. Compose any subset of the field parts as children. Must be inside ClientProfileForm.Root.',\n});\n\n// Each field leaf is registered with its own inline `defineComponent({...})` — the native catalog\n// builder statically extracts only that literal form, so a factory helper would leave these parts out\n// of catalog.json (and thus uncomposable in a store schema). See client-profile-form.test.tsx's\n// catalog-completeness guard.\n\nexport const ClientProfileFormEmailFieldDefinition = defineComponent<ClientProfileFormEmailFieldProps>({\n name: 'ClientProfileFormEmailField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormEmailField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Email Field', category: 'action' },\n schema: { label: { type: 'string', default: 'Email', label: 'Label' } },\n guideline: 'Read-only email display, seeded from the profile. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormFirstNameFieldDefinition = defineComponent<ClientProfileFormFirstNameFieldProps>({\n name: 'ClientProfileFormFirstNameField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormFirstNameField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile First Name Field', category: 'action' },\n schema: { label: { type: 'string', default: 'First name', label: 'Label' } },\n guideline: 'First-name text field bound to the form draft. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormLastNameFieldDefinition = defineComponent<ClientProfileFormLastNameFieldProps>({\n name: 'ClientProfileFormLastNameField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormLastNameField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Last Name Field', category: 'action' },\n schema: { label: { type: 'string', default: 'Last name', label: 'Label' } },\n guideline: 'Last-name text field bound to the form draft. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormPhoneFieldDefinition = defineComponent<ClientProfileFormPhoneFieldProps>({\n name: 'ClientProfileFormPhoneField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormPhoneField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Phone Field', category: 'action' },\n schema: { label: { type: 'string', default: 'Phone number', label: 'Label' } },\n guideline:\n 'Country-masked phone field (country from Root); format-validated on Save. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormDocumentFieldDefinition = defineComponent<ClientProfileFormDocumentFieldProps>({\n name: 'ClientProfileFormDocumentField',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormDocumentField,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Document Field', category: 'action' },\n schema: { label: { type: 'string', default: 'CPF', label: 'Label' } },\n guideline:\n 'Tax-document field; self-gates on Root.documentType and check-digit-validates on Save. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormNewsletterCheckboxDefinition = defineComponent<ClientProfileFormNewsletterCheckboxProps>({\n name: 'ClientProfileFormNewsletterCheckbox',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormNewsletterCheckbox,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Newsletter Checkbox', category: 'action' },\n schema: { label: { type: 'string', default: 'Email me with news and offers', label: 'Label' } },\n guideline: 'Newsletter opt-in checkbox bound to the form draft. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormSavePersonalDataCheckboxDefinition =\n defineComponent<ClientProfileFormSavePersonalDataCheckboxProps>({\n name: 'ClientProfileFormSavePersonalDataCheckbox',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormSavePersonalDataCheckbox,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Save-Personal-Data Checkbox', category: 'action' },\n schema: { label: { type: 'string', default: 'Save my information for next time', label: 'Label' } },\n guideline: 'Save-personal-data opt-in checkbox bound to the form draft. Must be inside ClientProfileForm.Root.',\n });\n\nexport const ClientProfileFormErrorDefinition = defineComponent<ClientProfileFormErrorProps>({\n name: 'ClientProfileFormError',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormError,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Error', category: 'content' },\n schema: {},\n guideline:\n 'Renders the inline server error (role=\"alert\") from the last Save, or nothing. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormConfirmationDefinition = defineComponent<ClientProfileFormConfirmationProps>({\n name: 'ClientProfileFormConfirmation',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormConfirmation,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Confirmation', category: 'content' },\n schema: { message: { type: 'string', default: 'Your information has been saved.', label: 'Confirmation message' } },\n guideline:\n 'Renders the success confirmation (an <output>) after a Save with no errors, cleared on the next edit. Must be inside ClientProfileForm.Root.',\n});\n\nexport const ClientProfileFormSubmitButtonDefinition = defineComponent<ClientProfileFormSubmitButtonProps>({\n name: 'ClientProfileFormSubmitButton',\n importPath: '@vtex/faststore/components',\n component: ClientProfileFormSubmitButton,\n requiresContext: 'client-profile-form',\n editor: { label: 'Client Profile Form Submit Button', category: 'action' },\n schema: { label: { type: 'string', default: 'Save and continue', label: 'Button label' } },\n guideline:\n 'The batched-Save submit button (type=submit). Disabled while a write is in flight; renders nothing when the session is read-only. Compose an Icon + text as children, or rely on `label`. Must be inside ClientProfileForm.Root.',\n});\n\n// ---------------------------------------------------------------------------\n// Validation — field-intrinsic format only\n//\n// Which fields a shopper must fill is a business rule, and the parts of this form are composed freely\n// by the schema, so a required check here can fire for a field the page never mounted: nothing renders\n// the message, focus has nowhere to land, and Save goes dead. Requiredness waits for the RFC that\n// decides where it belongs.\n// ---------------------------------------------------------------------------\n\nfunction validateDraft(draft: Draft, country: PhoneCountry, documentType: string | undefined): FieldErrors {\n const errors: FieldErrors = {};\n\n const phoneRaw = draft.phone.trim();\n if (phoneRaw && !validatePhoneNumber(phoneRaw, country)) {\n errors.phone = 'Invalid phone number';\n }\n\n if (isSupportedDocumentType(documentType)) {\n const digits = stripNonDigits(draft.document);\n if (digits && !isValidDocument(digits, documentType)) {\n errors.document = 'Invalid document number';\n }\n }\n\n return errors;\n}\n\n// ---------------------------------------------------------------------------\n// Root\n// ---------------------------------------------------------------------------\n\n/**\n * Data root + form shell. Seeds a local draft from `$data`, tracks dirty fields (background `$data`\n * refreshes only re-seed untouched fields), runs format-only validation, and on Save dispatches the\n * batched `clientProfileUpdate` / `clientPreferencesUpdate` (touched fields only), showing the\n * confirmation on success. Provides everything the field leaves need via context; renders a `<form>`\n * around its children.\n *\n * It publishes nothing. A finished press is not announced, so no section collapses as a result and\n * nothing outside this form can react to it. Whether a checkout sequences on a completed form is left to\n * the flow-of-steps RFC (#611).\n */\nexport function ClientProfileFormRoot({\n country = 'US',\n documentType,\n className,\n children,\n $data: $dataProp,\n $actions: $actionsProp,\n}: Readonly<ClientProfileFormRootProps>) {\n const $data = useDataOverride($dataProp);\n const $actions = useActionsOverride($actionsProp);\n // Also enforces the SessionRoot ancestor: useSession() throws when rendered outside one.\n const { canEditData } = useSession();\n const readOnly = !canEditData;\n\n const savedEmail = $data?.clientProfile?.email ?? '';\n const savedFirstName = $data?.clientProfile?.firstName ?? '';\n const savedLastName = $data?.clientProfile?.lastName ?? '';\n const savedPhone = $data?.clientProfile?.phone ?? '';\n const savedDocument = $data?.clientProfile?.document ?? '';\n const savedOptinNewsLetter = Boolean($data?.clientPreferences?.optinNewsLetter ?? false);\n const savedSavePersonalData = Boolean($data?.clientPreferences?.savePersonalData ?? false);\n\n const [draft, setDraft] = useState<Draft>(() => ({\n firstName: savedFirstName,\n lastName: savedLastName,\n phone: savedPhone,\n document: savedDocument,\n optinNewsLetter: savedOptinNewsLetter,\n savePersonalData: savedSavePersonalData,\n }));\n const [dirtyFields, setDirtyFields] = useState<ReadonlySet<FieldKey>>(new Set());\n const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});\n const [pending, setPending] = useState(false);\n const [serverError, setServerError] = useState<string | null>(null);\n const [saved, setSaved] = useState(false);\n\n const formRef = useRef<HTMLFormElement>(null);\n\n // Re-seed pristine fields when the underlying saved entities change; dirty fields are never\n // clobbered. Runs during render (ref-comparison), so the re-seed lands in the same commit.\n const prevSavedRef = useRef({\n firstName: savedFirstName,\n lastName: savedLastName,\n phone: savedPhone,\n document: savedDocument,\n optinNewsLetter: savedOptinNewsLetter,\n savePersonalData: savedSavePersonalData,\n });\n const prevSaved = prevSavedRef.current;\n const changedKeys: FieldKey[] = [];\n if (prevSaved.firstName !== savedFirstName) changedKeys.push('firstName');\n if (prevSaved.lastName !== savedLastName) changedKeys.push('lastName');\n if (prevSaved.phone !== savedPhone) changedKeys.push('phone');\n if (prevSaved.document !== savedDocument) changedKeys.push('document');\n if (prevSaved.optinNewsLetter !== savedOptinNewsLetter) changedKeys.push('optinNewsLetter');\n if (prevSaved.savePersonalData !== savedSavePersonalData) changedKeys.push('savePersonalData');\n if (changedKeys.length > 0) {\n const nextSaved = {\n firstName: savedFirstName,\n lastName: savedLastName,\n phone: savedPhone,\n document: savedDocument,\n optinNewsLetter: savedOptinNewsLetter,\n savePersonalData: savedSavePersonalData,\n };\n // react-doctor-disable-next-line no-ref-current-in-render -- paired with the setDraft call right below, both replay together\n prevSavedRef.current = nextSaved;\n setDraft((prevDraft) => {\n let next = prevDraft;\n for (const key of changedKeys) {\n if (dirtyFields.has(key)) continue;\n if (next === prevDraft) next = { ...prevDraft };\n (next as Record<FieldKey, string | boolean>)[key] = nextSaved[key];\n }\n return next;\n });\n }\n\n const disabled = pending || readOnly;\n\n function markDirty(key: FieldKey) {\n setDirtyFields((prev) => {\n if (prev.has(key)) return prev;\n const next = new Set(prev);\n next.add(key);\n return next;\n });\n }\n\n function clearFeedback() {\n if (saved) setSaved(false);\n if (serverError) setServerError(null);\n }\n\n function clearFieldError(key: ValidatableFieldKey) {\n if (fieldErrors[key]) {\n setFieldErrors((prev) => {\n const next = { ...prev };\n delete next[key];\n return next;\n });\n }\n }\n\n function setText(key: TextFieldKey, value: string) {\n setDraft((prev) => ({ ...prev, [key]: value }));\n markDirty(key);\n clearFeedback();\n }\n\n function setPhone(value: string) {\n setDraft((prev) => ({ ...prev, phone: value }));\n markDirty('phone');\n clearFeedback();\n clearFieldError('phone');\n }\n\n function setDocument(value: string) {\n setDraft((prev) => ({ ...prev, document: value }));\n markDirty('document');\n clearFeedback();\n clearFieldError('document');\n }\n\n function setCheckbox(key: CheckboxKey, checked: boolean) {\n setDraft((prev) => ({ ...prev, [key]: checked }));\n markDirty(key);\n clearFeedback();\n }\n\n function focusField(key: ValidatableFieldKey) {\n const el = formRef.current?.querySelector<HTMLInputElement>(`[data-fs-client-profile-form-field=\"${key}\"] input`);\n el?.focus();\n }\n\n async function handleSubmit(event: FormEvent<HTMLFormElement>) {\n event.preventDefault();\n if (readOnly || pending) return;\n\n // Validation runs before the pristine shortcut below, not after: a stored value can be malformed\n // without anything having been typed this session, and a press that reached the shortcut first would\n // confirm it as acceptable.\n const errors = validateDraft(draft, country, documentType);\n if (Object.keys(errors).length > 0) {\n setFieldErrors(errors);\n const firstInvalid = VALIDATION_ORDER.find((key) => errors[key]);\n if (firstInvalid) focusField(firstInvalid);\n return;\n }\n setFieldErrors({});\n\n // Nothing changed: dispatch nothing, which also avoids the NO_*_PROVIDED user errors. The press\n // still confirms, because a shopper who opens an already-filled form and presses Save is asking\n // whether their data is accepted, and a silent return answers nothing.\n if (dirtyFields.size === 0) {\n setServerError(null);\n setSaved(true);\n return;\n }\n\n const identity = $actions?.identity;\n if (!identity) return;\n\n const profilePayload: Record<string, string> = {};\n if (dirtyFields.has('firstName')) profilePayload.firstName = draft.firstName.trim();\n if (dirtyFields.has('lastName')) profilePayload.lastName = draft.lastName.trim();\n // toE164 is documented for values validatePhoneNumber has accepted; on a blank one it would return\n // the bare country code, writing \"+55\" over the shopper's number and then failing every later Save.\n if (dirtyFields.has('phone')) {\n profilePayload.phone = draft.phone.trim() ? toE164(draft.phone, country) : '';\n }\n if (dirtyFields.has('document')) profilePayload.document = stripNonDigits(draft.document);\n\n const preferencesPayload: Record<string, boolean> = {};\n if (dirtyFields.has('optinNewsLetter')) preferencesPayload.optinNewsLetter = draft.optinNewsLetter;\n if (dirtyFields.has('savePersonalData')) preferencesPayload.savePersonalData = draft.savePersonalData;\n\n const writes: Promise<readonly ClientProfileUserError[]>[] = [];\n if (Object.keys(profilePayload).length > 0 && identity.clientProfileUpdate) {\n writes.push(identity.clientProfileUpdate(profilePayload));\n }\n if (Object.keys(preferencesPayload).length > 0 && identity.clientPreferencesUpdate) {\n writes.push(identity.clientPreferencesUpdate(preferencesPayload));\n }\n if (writes.length === 0) return;\n\n setPending(true);\n setServerError(null);\n setSaved(false);\n try {\n const results = await Promise.all(writes);\n const allErrors = results.flat();\n if (allErrors.length > 0) {\n setServerError(allErrors[0]?.message ?? null);\n return;\n }\n setDirtyFields(new Set());\n setSaved(true);\n } catch (error) {\n // A write that throws rather than resolving userErrors would otherwise snap the form back to idle\n // with no message, losing the edit silently and escaping as an unhandled rejection. The dirty marks\n // stay, so the shopper can press Save again without retyping.\n console.error('[ClientProfileForm] profile write failed:', error);\n setServerError(SAVE_FAILED_MESSAGE);\n } finally {\n setPending(false);\n }\n }\n\n const contextValue: ClientProfileFormContextValue = {\n savedEmail,\n draft,\n fieldErrors,\n disabled,\n readOnly,\n pending,\n serverError,\n saved,\n country,\n documentType,\n setText,\n setPhone,\n setDocument,\n setCheckbox,\n };\n\n return (\n <ClientProfileFormContext.Provider value={contextValue}>\n <form\n ref={formRef}\n onSubmit={handleSubmit}\n className={className}\n data-fs-client-profile-form=\"\"\n {...(pending ? { 'data-fs-client-profile-form-pending': '' } : {})}\n {...(readOnly ? { 'data-fs-client-profile-form-readonly': '' } : {})}\n {...(saved ? { 'data-fs-client-profile-form-saved': '' } : {})}\n >\n {children}\n </form>\n </ClientProfileFormContext.Provider>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Fieldset (optional grouping)\n// ---------------------------------------------------------------------------\n\nexport function ClientProfileFormFieldset({ legend, section, className, children }: ClientProfileFormFieldsetProps) {\n const { disabled } = useClientProfileFormContext();\n return (\n <Fieldset.Root\n disabled={disabled}\n className={className}\n {...(section ? { 'data-fs-client-profile-form-section': section } : {})}\n >\n {legend ? <Fieldset.Legend>{legend}</Fieldset.Legend> : null}\n {children}\n </Fieldset.Root>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Field leaves\n// ---------------------------------------------------------------------------\n\nexport function ClientProfileFormEmailField({ label = 'Email', className }: ClientProfileFormEmailFieldProps) {\n const { savedEmail, disabled } = useClientProfileFormContext();\n return (\n <Field.Root className={className} data-fs-client-profile-form-field=\"email\" disabled={disabled}>\n <Field.Label>{label}</Field.Label>\n <Field.Control readOnly value={savedEmail} disabled={disabled} />\n </Field.Root>\n );\n}\n\nfunction TextField({ fieldKey, label, className }: { fieldKey: TextFieldKey; label: string; className?: string }) {\n const { draft, disabled, setText } = useClientProfileFormContext();\n const attr = fieldKey === 'firstName' ? 'first-name' : 'last-name';\n const autoComplete = fieldKey === 'firstName' ? 'given-name' : 'family-name';\n return (\n <Field.Root className={className} data-fs-client-profile-form-field={attr} disabled={disabled}>\n <Field.Label>{label}</Field.Label>\n <Field.Control\n name={fieldKey}\n autoComplete={autoComplete}\n value={draft[fieldKey]}\n onChange={(event) => setText(fieldKey, event.target.value)}\n disabled={disabled}\n />\n </Field.Root>\n );\n}\n\nexport function ClientProfileFormFirstNameField({\n label = 'First name',\n className,\n}: ClientProfileFormFirstNameFieldProps) {\n return <TextField fieldKey=\"firstName\" label={label} className={className} />;\n}\n\nexport function ClientProfileFormLastNameField({\n label = 'Last name',\n className,\n}: ClientProfileFormLastNameFieldProps) {\n return <TextField fieldKey=\"lastName\" label={label} className={className} />;\n}\n\nexport function ClientProfileFormPhoneField({ label = 'Phone number', className }: ClientProfileFormPhoneFieldProps) {\n const { draft, fieldErrors, disabled, country, setPhone } = useClientProfileFormContext();\n return (\n <div className={className} data-fs-client-profile-form-field=\"phone\">\n <PhoneFieldParticle\n label={label}\n country={country}\n value={draft.phone}\n onValueChange={setPhone}\n isDisabled={disabled}\n invalid={fieldErrors.phone ? true : undefined}\n error={fieldErrors.phone}\n />\n </div>\n );\n}\n\nexport function ClientProfileFormDocumentField({ label = 'CPF', className }: ClientProfileFormDocumentFieldProps) {\n const { draft, fieldErrors, disabled, documentType, setDocument } = useClientProfileFormContext();\n if (!isSupportedDocumentType(documentType)) return null;\n return (\n <div className={className} data-fs-client-profile-form-field=\"document\">\n <DocumentFieldParticle\n label={label}\n documentType={documentType}\n value={draft.document}\n onValueChange={setDocument}\n disabled={disabled}\n invalid={Boolean(fieldErrors.document)}\n errorMessage={fieldErrors.document ?? 'Invalid document number'}\n />\n </div>\n );\n}\n\nfunction CheckboxField({\n fieldKey,\n attr,\n label,\n className,\n}: {\n fieldKey: CheckboxKey;\n attr: string;\n label: string;\n className?: string;\n}) {\n const { draft, disabled, setCheckbox } = useClientProfileFormContext();\n return (\n <Field.Root className={className} data-fs-client-profile-form-field={attr} disabled={disabled}>\n <Field.Label>\n <Checkbox.Root\n checked={draft[fieldKey]}\n onCheckedChange={(checked) => setCheckbox(fieldKey, checked)}\n disabled={disabled}\n >\n <Checkbox.Indicator>\n <Icon name=\"checked\" />\n </Checkbox.Indicator>\n </Checkbox.Root>\n {label}\n </Field.Label>\n </Field.Root>\n );\n}\n\nexport function ClientProfileFormNewsletterCheckbox({\n label = 'Email me with news and offers',\n className,\n}: ClientProfileFormNewsletterCheckboxProps) {\n return <CheckboxField fieldKey=\"optinNewsLetter\" attr=\"newsletter\" label={label} className={className} />;\n}\n\nexport function ClientProfileFormSavePersonalDataCheckbox({\n label = 'Save my information for next time',\n className,\n}: ClientProfileFormSavePersonalDataCheckboxProps) {\n return <CheckboxField fieldKey=\"savePersonalData\" attr=\"save-personal-data\" label={label} className={className} />;\n}\n\n// ---------------------------------------------------------------------------\n// Feedback + submit\n// ---------------------------------------------------------------------------\n\nexport function ClientProfileFormError({ className }: ClientProfileFormErrorProps) {\n const { serverError } = useClientProfileFormContext();\n if (!serverError) return null;\n return (\n <div role=\"alert\" className={className} data-fs-client-profile-form-error=\"\">\n {serverError}\n </div>\n );\n}\n\nexport function ClientProfileFormConfirmation({\n message = 'Your information has been saved.',\n className,\n}: ClientProfileFormConfirmationProps) {\n const { saved } = useClientProfileFormContext();\n if (!saved) return null;\n return (\n <output className={className} data-fs-client-profile-form-confirmation=\"\">\n {message}\n </output>\n );\n}\n\nexport function ClientProfileFormSubmitButton({\n label = 'Save and continue',\n className,\n children,\n}: ClientProfileFormSubmitButtonProps) {\n const { pending, readOnly } = useClientProfileFormContext();\n if (readOnly) return null;\n return (\n <button\n type=\"submit\"\n disabled={pending}\n className={className}\n data-fs-button=\"\"\n data-fs-client-profile-form-save=\"\"\n >\n {children ?? label}\n </button>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Namespace Export\n// ---------------------------------------------------------------------------\n\nexport const ClientProfileForm = {\n Root: ClientProfileFormRoot,\n RootDefinition: ClientProfileFormRootDefinition,\n Fieldset: ClientProfileFormFieldset,\n FieldsetDefinition: ClientProfileFormFieldsetDefinition,\n EmailField: ClientProfileFormEmailField,\n EmailFieldDefinition: ClientProfileFormEmailFieldDefinition,\n FirstNameField: ClientProfileFormFirstNameField,\n FirstNameFieldDefinition: ClientProfileFormFirstNameFieldDefinition,\n LastNameField: ClientProfileFormLastNameField,\n LastNameFieldDefinition: ClientProfileFormLastNameFieldDefinition,\n PhoneField: ClientProfileFormPhoneField,\n PhoneFieldDefinition: ClientProfileFormPhoneFieldDefinition,\n DocumentField: ClientProfileFormDocumentField,\n DocumentFieldDefinition: ClientProfileFormDocumentFieldDefinition,\n NewsletterCheckbox: ClientProfileFormNewsletterCheckbox,\n NewsletterCheckboxDefinition: ClientProfileFormNewsletterCheckboxDefinition,\n SavePersonalDataCheckbox: ClientProfileFormSavePersonalDataCheckbox,\n SavePersonalDataCheckboxDefinition: ClientProfileFormSavePersonalDataCheckboxDefinition,\n Error: ClientProfileFormError,\n ErrorDefinition: ClientProfileFormErrorDefinition,\n Confirmation: ClientProfileFormConfirmation,\n ConfirmationDefinition: ClientProfileFormConfirmationDefinition,\n SubmitButton: ClientProfileFormSubmitButton,\n SubmitButtonDefinition: ClientProfileFormSubmitButtonDefinition,\n};\n"],"mappings":"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"}
|
|
@@ -25,6 +25,6 @@ export type DeliveryOptionsAccordionProps = ComponentPropsWithRef<'button'> & {
|
|
|
25
25
|
mockOptions?: ShippingOption[];
|
|
26
26
|
};
|
|
27
27
|
export declare const DeliveryOptionsAccordionDefinition: ComponentDefinition<DeliveryOptionsAccordionProps>;
|
|
28
|
-
export declare function DeliveryOptionsAccordion({ label, placeholder, separator, icon, panelClassName, $data, $actions, mockOptions, className, children, ...props }: DeliveryOptionsAccordionProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function DeliveryOptionsAccordion({ label, placeholder, separator, icon, panelClassName, $data, $actions: $actionsProp, mockOptions, className, children, ...props }: DeliveryOptionsAccordionProps): import("react/jsx-runtime").JSX.Element;
|
|
29
29
|
//#endregion
|
|
30
30
|
//# sourceMappingURL=delivery-options-accordion.d.ts.map
|
|
@@ -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 { Icon } from "../../particles/icon/icon.js";
|
|
5
6
|
import { RadioGroup, RadioRoot } from "../../particles/radio/radio.js";
|
|
6
7
|
import { OptionItemContext, ShippingSimulatorContext, ShippingSimulatorOptionName, ShippingSimulatorOptionPrice } from "../shipping-simulator/shipping-simulator.js";
|
|
@@ -99,7 +100,8 @@ const DeliveryOptionsAccordionDefinition = defineComponent({
|
|
|
99
100
|
providesContext: ["shipping-simulator", "shipping-simulator-option"],
|
|
100
101
|
guideline: "Collapsed delivery-option picker: a single summary row that expands the whole-cart option list inline (an accordion, not an overlay) and collapses it the moment a row is picked, echoing the choice (name + price) back on the trigger or a placeholder when nothing is selected. Reuses the FSPG-832 option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as the per-option template placed as children, exactly like DeliveryOptionsRoot. Selecting a row dispatches $actions.shipping.selectOption; mockOptions is a local/dev-only fallback whose selection stays local. Use instead of a DeliveryOptionsController overlay (Dialog/Drawer) when the picker should expand in the page flow rather than surface in an overlay."
|
|
101
102
|
});
|
|
102
|
-
function DeliveryOptionsAccordion({ label, placeholder = "Selecionar forma de entrega", separator = " — ", icon = "caret-down", panelClassName = DEFAULT_PANEL_CLASS, $data, $actions, mockOptions, className, children, ...props }) {
|
|
103
|
+
function DeliveryOptionsAccordion({ label, placeholder = "Selecionar forma de entrega", separator = " — ", icon = "caret-down", panelClassName = DEFAULT_PANEL_CLASS, $data, $actions: $actionsProp, mockOptions, className, children, ...props }) {
|
|
104
|
+
const $actions = useActionsOverride($actionsProp);
|
|
103
105
|
const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({
|
|
104
106
|
$data,
|
|
105
107
|
$actions,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delivery-options-accordion.js","names":[],"sources":["../../../src/matter/delivery-options/delivery-options-accordion.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { useState } from 'react';\nimport {\n AccordionHeader,\n AccordionItem,\n AccordionPanel,\n AccordionRoot,\n AccordionTrigger,\n} from '../../particles/accordion/accordion.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport { RadioGroup, RadioRoot } from '../../particles/radio/radio.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport {\n OptionItemContext,\n type ShippingOption,\n ShippingSimulatorContext,\n ShippingSimulatorOptionName,\n ShippingSimulatorOptionPrice,\n} from '../shipping-simulator/shipping-simulator.js';\nimport { type RawDeliveryOptionsData, useDeliveryOptionSelection } from './use-delivery-option-selection.js';\n\n/**\n * `DeliveryOptionsAccordion` presents the whole-cart delivery-option list as a single summary/trigger\n * row that expands the full list inline, then collapses it once an option is picked and the selection\n * succeeds — with the trigger reflecting the chosen option's name + price (or a placeholder when\n * nothing is selected yet).\n * It is the inline (non-overlay) sibling of the `DeliveryOptionsController` Dialog/Drawer surfaces:\n * same fragment, `$data` shape, selection rule, and reuse of the FSPG-832 option leaves as the per-option\n * template placed as `children`, but the option list expands/collapses in the normal page flow\n * directly below the trigger instead of surfacing in an overlay — no Portal, no Backdrop, no\n * fixed/z-index positioning.\n *\n * The Accordion particle differs from Drawer/Dialog in the control surface this component adapts to:\n * it has no `open`/`onOpenChange` boolean; Base UI models disclosure as a controlled *array* of open\n * item values (`value` + `onValueChange`), so a single-open accordion is `[]` (collapsed) or\n * `[ACCORDION_ITEM_VALUE]` (expanded). There is no overlay chrome (title bar, close button, swipe\n * edge), so those props are dropped; the panel collapse is a height animation rather than a slide.\n *\n * It owns the expand/collapse state and the selection→collapse coordination internally (neither is\n * expressible by composing `Accordion` + `DeliveryOptionsRoot` in a schema), and shares the selection\n * engine with `DeliveryOptionsRoot` via `useDeliveryOptionSelection`. Real options dispatch\n * `$actions.shipping.selectOption` and the panel collapses only once the mutation resolves without a\n * `userError` — a rejected option keeps the panel open with an inline `role=\"alert\"` (US-4);\n * `mockOptions` update local state only and collapse immediately.\n */\n\nconst fragment = /* GraphQL */ `\n fragment DeliveryOptionsAccordionData on Query {\n shipping {\n id\n options {\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n }\n`;\n\n// Stable value identifying the single accordion item; the controlled open state is `[]` (collapsed)\n// or `[ACCORDION_ITEM_VALUE]` (expanded).\nconst ACCORDION_ITEM_VALUE = 'delivery-options';\n\n// Base UI drives the collapse with the `--accordion-panel-height` CSS var it sets on the panel, so\n// the list expands/collapses in flow with a height transition (overflow-hidden clips it mid-animation).\nconst DEFAULT_PANEL_CLASS =\n 'overflow-hidden transition-[height] duration-200 ease-out h-[var(--accordion-panel-height)] data-starting-style:h-0 data-ending-style:h-0';\n\nexport type DeliveryOptionsAccordionProps = ComponentPropsWithRef<'button'> & {\n /** Optional prefix shown before the summary value on the trigger row (e.g. \"Forma de entrega\"). */\n label?: string;\n /** Text shown on the trigger row while nothing is selected. */\n placeholder?: string;\n /** Separator between the selected option name and price on the trigger row. */\n separator?: string;\n /** Disclosure icon shown on the trigger. The compiler's per-route sprite is built from explicit\n * `icon`-typed prop values in the schema, so a page must set this rather than rely on the default. */\n icon?: string;\n /** Overrides the default collapsible panel styling (height-animation + overflow clip). */\n panelClassName?: string;\n /** Shipping slice forwarded by the reactive runtime; `null` until shipping resolves. */\n $data?: RawDeliveryOptionsData | null;\n /** Compiler-injected action surface; carries `shipping.selectOption`. */\n $actions?: ComponentActions;\n /** Local/dev fallback rows, used only when the reactive fetch resolves no options. */\n mockOptions?: ShippingOption[];\n};\n\nexport const DeliveryOptionsAccordionDefinition = defineComponent<DeliveryOptionsAccordionProps>({\n name: 'DeliveryOptionsAccordion',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsAccordion,\n fragment,\n fetchStrategy: 'reactive',\n actions: { shipping: ['selectOption'] },\n editor: { label: 'Delivery Options Accordion', category: 'commerce' },\n schema: {\n label: { type: 'string', label: 'Trigger label prefix' },\n placeholder: { type: 'string', default: 'Selecionar forma de entrega', label: 'Placeholder' },\n separator: { type: 'string', default: ' — ', label: 'Summary separator' },\n icon: { type: 'icon', default: 'caret-down', label: 'Disclosure icon' },\n mockOptions: { type: 'array', label: 'Mock options (local/dev fallback, ignored once real data has options)' },\n },\n // Re-provides the reused option leaves' runtime contexts (Decision 2): `shipping-simulator-option`\n // for the leaves composed as direct children here, plus the outer `shipping-simulator` this\n // accordion also provides at runtime.\n providesContext: ['shipping-simulator', 'shipping-simulator-option'],\n guideline:\n 'Collapsed delivery-option picker: a single summary row that expands the whole-cart option list inline (an accordion, not an overlay) and collapses it the moment a row is picked, echoing the choice (name + price) back on the trigger or a placeholder when nothing is selected. Reuses the FSPG-832 option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as the per-option template placed as children, exactly like DeliveryOptionsRoot. Selecting a row dispatches $actions.shipping.selectOption; mockOptions is a local/dev-only fallback whose selection stays local. Use instead of a DeliveryOptionsController overlay (Dialog/Drawer) when the picker should expand in the page flow rather than surface in an overlay.',\n});\n\nexport function DeliveryOptionsAccordion({\n label,\n placeholder = 'Selecionar forma de entrega',\n separator = ' — ',\n icon = 'caret-down',\n panelClassName = DEFAULT_PANEL_CLASS,\n $data,\n $actions,\n mockOptions,\n className,\n children,\n ...props\n}: DeliveryOptionsAccordionProps) {\n const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({\n $data,\n $actions,\n mockOptions,\n });\n const [open, setOpen] = useState(false);\n const selectedOption = options.find((option) => option.id === selectedOptionId) ?? null;\n\n function handleValueChange(optionId: string) {\n selectOption(optionId, {\n onSettled: (ok) => {\n if (ok) setOpen(false);\n },\n });\n }\n\n return (\n <ShippingSimulatorContext value={contextValue}>\n <AccordionRoot\n openMultiple={false}\n value={open ? [ACCORDION_ITEM_VALUE] : []}\n onValueChange={(value) => setOpen(value.length > 0)}\n data-fs-delivery-options-accordion=\"\"\n >\n <AccordionItem value={ACCORDION_ITEM_VALUE}>\n <AccordionHeader className=\"m-0\">\n <AccordionTrigger {...props} className={className} data-fs-delivery-options-accordion-trigger=\"\">\n {label ? <span data-fs-delivery-options-accordion-label=\"\">{label}</span> : null}\n {selectedOption ? (\n <OptionItemContext value={selectedOption}>\n <span data-fs-delivery-options-accordion-value=\"\">\n <ShippingSimulatorOptionName />\n <span data-fs-delivery-options-accordion-separator=\"\">{separator}</span>\n <ShippingSimulatorOptionPrice />\n </span>\n </OptionItemContext>\n ) : (\n <span data-fs-delivery-options-accordion-placeholder=\"\">{placeholder}</span>\n )}\n <span data-fs-delivery-options-accordion-icon=\"\">\n <Icon name={icon} />\n </span>\n </AccordionTrigger>\n </AccordionHeader>\n <AccordionPanel className={panelClassName} data-fs-delivery-options-accordion-panel=\"\">\n <div data-fs-delivery-options=\"\" className=\"pt-3\">\n {selectionError ? (\n <div role=\"alert\" data-fs-delivery-options-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {options.length > 0 ? (\n <RadioGroup\n value={selectedOptionId ?? ''}\n onValueChange={handleValueChange}\n className=\"flex flex-col gap-3\"\n >\n {options.map((option) => (\n <OptionItemContext key={option.id} value={option}>\n <RadioRoot value={option.id}>{children}</RadioRoot>\n </OptionItemContext>\n ))}\n </RadioGroup>\n ) : null}\n </div>\n </AccordionPanel>\n </AccordionItem>\n </AccordionRoot>\n </ShippingSimulatorContext>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"delivery-options-accordion.js","names":[],"sources":["../../../src/matter/delivery-options/delivery-options-accordion.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { useState } from 'react';\nimport {\n AccordionHeader,\n AccordionItem,\n AccordionPanel,\n AccordionRoot,\n AccordionTrigger,\n} from '../../particles/accordion/accordion.js';\nimport { Icon } from '../../particles/icon/icon.js';\nimport { RadioGroup, RadioRoot } from '../../particles/radio/radio.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport {\n OptionItemContext,\n type ShippingOption,\n ShippingSimulatorContext,\n ShippingSimulatorOptionName,\n ShippingSimulatorOptionPrice,\n} from '../shipping-simulator/shipping-simulator.js';\nimport { type RawDeliveryOptionsData, useDeliveryOptionSelection } from './use-delivery-option-selection.js';\n\n/**\n * `DeliveryOptionsAccordion` presents the whole-cart delivery-option list as a single summary/trigger\n * row that expands the full list inline, then collapses it once an option is picked and the selection\n * succeeds — with the trigger reflecting the chosen option's name + price (or a placeholder when\n * nothing is selected yet).\n * It is the inline (non-overlay) sibling of the `DeliveryOptionsController` Dialog/Drawer surfaces:\n * same fragment, `$data` shape, selection rule, and reuse of the FSPG-832 option leaves as the per-option\n * template placed as `children`, but the option list expands/collapses in the normal page flow\n * directly below the trigger instead of surfacing in an overlay — no Portal, no Backdrop, no\n * fixed/z-index positioning.\n *\n * The Accordion particle differs from Drawer/Dialog in the control surface this component adapts to:\n * it has no `open`/`onOpenChange` boolean; Base UI models disclosure as a controlled *array* of open\n * item values (`value` + `onValueChange`), so a single-open accordion is `[]` (collapsed) or\n * `[ACCORDION_ITEM_VALUE]` (expanded). There is no overlay chrome (title bar, close button, swipe\n * edge), so those props are dropped; the panel collapse is a height animation rather than a slide.\n *\n * It owns the expand/collapse state and the selection→collapse coordination internally (neither is\n * expressible by composing `Accordion` + `DeliveryOptionsRoot` in a schema), and shares the selection\n * engine with `DeliveryOptionsRoot` via `useDeliveryOptionSelection`. Real options dispatch\n * `$actions.shipping.selectOption` and the panel collapses only once the mutation resolves without a\n * `userError` — a rejected option keeps the panel open with an inline `role=\"alert\"` (US-4);\n * `mockOptions` update local state only and collapse immediately.\n */\n\nconst fragment = /* GraphQL */ `\n fragment DeliveryOptionsAccordionData on Query {\n shipping {\n id\n options {\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n }\n`;\n\n// Stable value identifying the single accordion item; the controlled open state is `[]` (collapsed)\n// or `[ACCORDION_ITEM_VALUE]` (expanded).\nconst ACCORDION_ITEM_VALUE = 'delivery-options';\n\n// Base UI drives the collapse with the `--accordion-panel-height` CSS var it sets on the panel, so\n// the list expands/collapses in flow with a height transition (overflow-hidden clips it mid-animation).\nconst DEFAULT_PANEL_CLASS =\n 'overflow-hidden transition-[height] duration-200 ease-out h-[var(--accordion-panel-height)] data-starting-style:h-0 data-ending-style:h-0';\n\nexport type DeliveryOptionsAccordionProps = ComponentPropsWithRef<'button'> & {\n /** Optional prefix shown before the summary value on the trigger row (e.g. \"Forma de entrega\"). */\n label?: string;\n /** Text shown on the trigger row while nothing is selected. */\n placeholder?: string;\n /** Separator between the selected option name and price on the trigger row. */\n separator?: string;\n /** Disclosure icon shown on the trigger. The compiler's per-route sprite is built from explicit\n * `icon`-typed prop values in the schema, so a page must set this rather than rely on the default. */\n icon?: string;\n /** Overrides the default collapsible panel styling (height-animation + overflow clip). */\n panelClassName?: string;\n /** Shipping slice forwarded by the reactive runtime; `null` until shipping resolves. */\n $data?: RawDeliveryOptionsData | null;\n /** Compiler-injected action surface; carries `shipping.selectOption`. */\n $actions?: ComponentActions;\n /** Local/dev fallback rows, used only when the reactive fetch resolves no options. */\n mockOptions?: ShippingOption[];\n};\n\nexport const DeliveryOptionsAccordionDefinition = defineComponent<DeliveryOptionsAccordionProps>({\n name: 'DeliveryOptionsAccordion',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsAccordion,\n fragment,\n fetchStrategy: 'reactive',\n actions: { shipping: ['selectOption'] },\n editor: { label: 'Delivery Options Accordion', category: 'commerce' },\n schema: {\n label: { type: 'string', label: 'Trigger label prefix' },\n placeholder: { type: 'string', default: 'Selecionar forma de entrega', label: 'Placeholder' },\n separator: { type: 'string', default: ' — ', label: 'Summary separator' },\n icon: { type: 'icon', default: 'caret-down', label: 'Disclosure icon' },\n mockOptions: { type: 'array', label: 'Mock options (local/dev fallback, ignored once real data has options)' },\n },\n // Re-provides the reused option leaves' runtime contexts (Decision 2): `shipping-simulator-option`\n // for the leaves composed as direct children here, plus the outer `shipping-simulator` this\n // accordion also provides at runtime.\n providesContext: ['shipping-simulator', 'shipping-simulator-option'],\n guideline:\n 'Collapsed delivery-option picker: a single summary row that expands the whole-cart option list inline (an accordion, not an overlay) and collapses it the moment a row is picked, echoing the choice (name + price) back on the trigger or a placeholder when nothing is selected. Reuses the FSPG-832 option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as the per-option template placed as children, exactly like DeliveryOptionsRoot. Selecting a row dispatches $actions.shipping.selectOption; mockOptions is a local/dev-only fallback whose selection stays local. Use instead of a DeliveryOptionsController overlay (Dialog/Drawer) when the picker should expand in the page flow rather than surface in an overlay.',\n});\n\nexport function DeliveryOptionsAccordion({\n label,\n placeholder = 'Selecionar forma de entrega',\n separator = ' — ',\n icon = 'caret-down',\n panelClassName = DEFAULT_PANEL_CLASS,\n $data,\n $actions: $actionsProp,\n mockOptions,\n className,\n children,\n ...props\n}: DeliveryOptionsAccordionProps) {\n const $actions = useActionsOverride($actionsProp);\n const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({\n $data,\n $actions,\n mockOptions,\n });\n const [open, setOpen] = useState(false);\n const selectedOption = options.find((option) => option.id === selectedOptionId) ?? null;\n\n function handleValueChange(optionId: string) {\n selectOption(optionId, {\n onSettled: (ok) => {\n if (ok) setOpen(false);\n },\n });\n }\n\n return (\n <ShippingSimulatorContext value={contextValue}>\n <AccordionRoot\n openMultiple={false}\n value={open ? [ACCORDION_ITEM_VALUE] : []}\n onValueChange={(value) => setOpen(value.length > 0)}\n data-fs-delivery-options-accordion=\"\"\n >\n <AccordionItem value={ACCORDION_ITEM_VALUE}>\n <AccordionHeader className=\"m-0\">\n <AccordionTrigger {...props} className={className} data-fs-delivery-options-accordion-trigger=\"\">\n {label ? <span data-fs-delivery-options-accordion-label=\"\">{label}</span> : null}\n {selectedOption ? (\n <OptionItemContext value={selectedOption}>\n <span data-fs-delivery-options-accordion-value=\"\">\n <ShippingSimulatorOptionName />\n <span data-fs-delivery-options-accordion-separator=\"\">{separator}</span>\n <ShippingSimulatorOptionPrice />\n </span>\n </OptionItemContext>\n ) : (\n <span data-fs-delivery-options-accordion-placeholder=\"\">{placeholder}</span>\n )}\n <span data-fs-delivery-options-accordion-icon=\"\">\n <Icon name={icon} />\n </span>\n </AccordionTrigger>\n </AccordionHeader>\n <AccordionPanel className={panelClassName} data-fs-delivery-options-accordion-panel=\"\">\n <div data-fs-delivery-options=\"\" className=\"pt-3\">\n {selectionError ? (\n <div role=\"alert\" data-fs-delivery-options-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {options.length > 0 ? (\n <RadioGroup\n value={selectedOptionId ?? ''}\n onValueChange={handleValueChange}\n className=\"flex flex-col gap-3\"\n >\n {options.map((option) => (\n <OptionItemContext key={option.id} value={option}>\n <RadioRoot value={option.id}>{children}</RadioRoot>\n </OptionItemContext>\n ))}\n </RadioGroup>\n ) : null}\n </div>\n </AccordionPanel>\n </AccordionItem>\n </AccordionRoot>\n </ShippingSimulatorContext>\n );\n}\n"],"mappings":"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"}
|
|
@@ -42,7 +42,7 @@ export type DeliveryOptionsListProps = ComponentPropsWithRef<'div'>;
|
|
|
42
42
|
export declare const DeliveryOptionsControllerDefinition: ComponentDefinition<DeliveryOptionsControllerProps>;
|
|
43
43
|
export declare const DeliveryOptionsSummaryDefinition: ComponentDefinition<DeliveryOptionsSummaryProps>;
|
|
44
44
|
export declare const DeliveryOptionsListDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>>;
|
|
45
|
-
export declare function DeliveryOptionsController({ $data, $actions, mockOptions, children }: DeliveryOptionsControllerProps): string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
|
|
45
|
+
export declare function DeliveryOptionsController({ $data, $actions: $actionsProp, mockOptions, children }: Readonly<DeliveryOptionsControllerProps>): string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | import("react").ReactPortal | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | Iterable<ReactNode> | null | undefined> | null | undefined;
|
|
46
46
|
export declare function DeliveryOptionsSummary({ label, placeholder, separator, className, ...props }: DeliveryOptionsSummaryProps): import("react/jsx-runtime").JSX.Element;
|
|
47
47
|
export declare function DeliveryOptionsList({ className, children, ...props }: DeliveryOptionsListProps): import("react/jsx-runtime").JSX.Element;
|
|
48
48
|
//#endregion
|
|
@@ -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 { OptionItemContext, ShippingSimulatorContext, ShippingSimulatorOptionName, ShippingSimulatorOptionPrice } from "../shipping-simulator/shipping-simulator.js";
|
|
5
6
|
import { useDeliveryOptionSelection } from "./use-delivery-option-selection.js";
|
|
6
7
|
import { DeliveryOptionsListBody } from "./delivery-options.js";
|
|
@@ -124,7 +125,8 @@ const DeliveryOptionsListDefinition = defineComponent({
|
|
|
124
125
|
schema: {},
|
|
125
126
|
guideline: "The collapsed picker body: the whole-cart option list as a radio group bound to the selected option, plus the inline selection error (role=\"alert\"). Place it inside the DialogPopup / DrawerPopup of a DeliveryOptionsController subtree, with the reused option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as its children template. Picking a row dispatches the selection and closes the surface on success. Must be inside DeliveryOptionsController."
|
|
126
127
|
});
|
|
127
|
-
function DeliveryOptionsController({ $data, $actions, mockOptions, children }) {
|
|
128
|
+
function DeliveryOptionsController({ $data, $actions: $actionsProp, mockOptions, children }) {
|
|
129
|
+
const $actions = useActionsOverride($actionsProp);
|
|
128
130
|
const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({
|
|
129
131
|
$data,
|
|
130
132
|
$actions,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delivery-options-controller.js","names":[],"sources":["../../../src/matter/delivery-options/delivery-options-controller.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n cloneElement,\n createContext,\n isValidElement,\n type ReactNode,\n useContext,\n useMemo,\n useState,\n} from 'react';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport {\n OptionItemContext,\n type ShippingOption,\n ShippingSimulatorContext,\n ShippingSimulatorOptionName,\n ShippingSimulatorOptionPrice,\n} from '../shipping-simulator/shipping-simulator.js';\nimport { DeliveryOptionsListBody } from './delivery-options.js';\nimport { type RawDeliveryOptionsData, useDeliveryOptionSelection } from './use-delivery-option-selection.js';\n\n/**\n * The collapsed delivery-option surfaces (centered modal / slide-in panel) as a controller +\n * composed particles, replacing the former monolithic `DeliveryOptionsDialog` / `DeliveryOptionsDrawer`\n * (Decision 8). The page composes a raw `DialogRoot` or `DrawerRoot` and each of its particles\n * (`DialogTrigger` / `DialogBackdrop` / `DialogPopup` …) directly as `page.json` nodes, so every part\n * is a swatch-addressable node with zero Tailwind baked into the package — the portaled backdrop/popup\n * a descendant selector could not reach in the monolith are now first-class nodes (Decision 9).\n *\n * `DeliveryOptionsController` owns state only and renders no markup: it runs the shared\n * `useDeliveryOptionSelection` engine, holds the open state, and provides both the FSPG-832\n * `ShippingSimulatorContext` and a `DeliveryOptions` selection context to its subtree, then injects\n * `open` / `onOpenChange` into its single overlay-`Root` child via `cloneElement` — the same mechanism\n * as `CartOpenOnAdd` and `ShippingAddressSavedAddressDrawerController`. A single engine behind the\n * context keeps the trigger echo (`DeliveryOptionsSummary`) and the option list (`DeliveryOptionsList`)\n * on one selection state, so even a local `mockOptions` pick reflects on the trigger.\n *\n * Selection follows the shared rule (INV-10/11): a real pick dispatches `shipping.selectOption` and\n * the surface closes only once it resolves without a `userError`; a rejected pick keeps it open with\n * the inline `role=\"alert\"`; a `mockOptions` pick updates local state and closes immediately.\n */\n\nconst fragment = /* GraphQL */ `\n fragment DeliveryOptionsControllerData on Query {\n shipping {\n id\n options {\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Selection context — provided by DeliveryOptionsController, read by the leaves\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsContextValue = Readonly<{\n /** Resolved option rows (real when the fetch has any, else the `mockOptions` fallback). */\n options: ShippingOption[];\n /** Group value: the persisted `selectedOptionId` for real data, local state under mock. */\n selectedOptionId: string | null;\n /** First `userError` message from the latest real selection, or `null`. */\n selectionError: string | null;\n /** Selects an option and closes the surface once the selection resolves without a `userError`. */\n selectOption: (optionId: string) => void;\n}>;\n\nconst DELIVERY_OPTIONS_UNSET = Symbol('DeliveryOptionsContextUnset');\n\nconst DeliveryOptionsContextInternal = createContext<DeliveryOptionsContextValue | typeof DELIVERY_OPTIONS_UNSET>(\n DELIVERY_OPTIONS_UNSET,\n);\n\n/**\n * Read the delivery-option selection state from the nearest `DeliveryOptionsController`. Throws when\n * called outside a controller subtree.\n */\nexport function useDeliveryOptions(): DeliveryOptionsContextValue {\n const ctx = useContext(DeliveryOptionsContextInternal);\n if (ctx === DELIVERY_OPTIONS_UNSET) {\n throw new Error('useDeliveryOptions() must be used inside a <DeliveryOptionsController> subtree.');\n }\n return ctx;\n}\n\n// The minimal open-state shape the controller injects into its overlay-Root child; both\n// `DialogRoot` and `DrawerRoot` accept it.\ntype OverlayOpenProps = {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsControllerProps = {\n /** Shipping slice forwarded by the reactive runtime; `null` until shipping resolves. */\n $data?: RawDeliveryOptionsData | null;\n /** Compiler-injected action surface; carries `shipping.selectOption`. */\n $actions?: ComponentActions;\n /** Local/dev fallback rows, used only when the reactive fetch resolves no options. */\n mockOptions?: ShippingOption[];\n /** The single overlay `Root` to control — a `DialogRoot` (modal) or `DrawerRoot` (slide-in). */\n children?: ReactNode;\n};\n\nexport type DeliveryOptionsSummaryProps = ComponentPropsWithRef<'span'> & {\n /** Optional prefix shown before the summary value (e.g. \"Forma de entrega\"). */\n label?: string;\n /** Text shown while nothing is selected. */\n placeholder?: string;\n /** Separator between the selected option name and price. */\n separator?: string;\n};\n\nexport type DeliveryOptionsListProps = ComponentPropsWithRef<'div'>;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const DeliveryOptionsControllerDefinition = defineComponent<DeliveryOptionsControllerProps>({\n name: 'DeliveryOptionsController',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsController,\n fragment,\n fetchStrategy: 'reactive',\n actions: { shipping: ['selectOption'] },\n // Its own `delivery-options` selection context, plus the reused option leaves' runtime contexts\n // (Decision 2): `shipping-simulator-option` for the leaves composed under DeliveryOptionsList, and\n // the outer `shipping-simulator` this controller also provides at runtime.\n providesContext: ['delivery-options', 'shipping-simulator', 'shipping-simulator-option'],\n editor: { label: 'Delivery Options Controller', category: 'commerce' },\n schema: {\n mockOptions: { type: 'array', label: 'Mock options (local/dev fallback, ignored once real data has options)' },\n },\n guideline:\n 'State-only controller for a collapsed delivery-option picker: wrap a single DialogRoot (centered modal) or DrawerRoot (slide-in) child and it becomes controlled — the surface opens from its trigger and closes once a real selection succeeds; a userError keeps it open. Renders no markup and bakes no styling: compose the overlay particles (DialogTrigger/DialogPortal/DialogBackdrop/DialogPopup or the Drawer equivalents) directly as page nodes, place a DeliveryOptionsSummary inside the trigger (echoes the selected name + price, or a placeholder) and a DeliveryOptionsList inside the popup (the option radio group; put the reused ShippingSimulatorOption leaves as its children). Selection dispatches $actions.shipping.selectOption; mockOptions is a local/dev-only fallback. Mirrors the CartOpenOnAdd / ShippingAddressSavedAddressDrawerController pattern.',\n});\n\nexport const DeliveryOptionsSummaryDefinition = defineComponent<DeliveryOptionsSummaryProps>({\n name: 'DeliveryOptionsSummary',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsSummary,\n requiresContext: 'delivery-options',\n editor: { label: 'Delivery Options Summary', category: 'commerce' },\n schema: {\n label: { type: 'string', label: 'Trigger label prefix' },\n placeholder: { type: 'string', default: 'Selecionar forma de entrega', label: 'Placeholder' },\n separator: { type: 'string', default: ' — ', label: 'Summary separator' },\n },\n guideline:\n 'The collapsed picker trigger echo: renders the selected option name + price (reusing the FSPG-832 option leaves), or a placeholder while nothing is selected. Place it inside the DialogTrigger / DrawerTrigger of a DeliveryOptionsController subtree. Reads the shared selection context, so it reflects the current pick — real or mockOptions. Must be inside DeliveryOptionsController.',\n});\n\nexport const DeliveryOptionsListDefinition = defineComponent<DeliveryOptionsListProps>({\n name: 'DeliveryOptionsList',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsList,\n requiresContext: 'delivery-options',\n editor: { label: 'Delivery Options List', category: 'commerce' },\n schema: {},\n guideline:\n 'The collapsed picker body: the whole-cart option list as a radio group bound to the selected option, plus the inline selection error (role=\"alert\"). Place it inside the DialogPopup / DrawerPopup of a DeliveryOptionsController subtree, with the reused option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as its children template. Picking a row dispatches the selection and closes the surface on success. Must be inside DeliveryOptionsController.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function DeliveryOptionsController({ $data, $actions, mockOptions, children }: DeliveryOptionsControllerProps) {\n const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({\n $data,\n $actions,\n mockOptions,\n });\n const [open, setOpen] = useState(false);\n\n const delivery = useMemo<DeliveryOptionsContextValue>(\n () => ({\n options,\n selectedOptionId,\n selectionError,\n selectOption: (optionId: string) =>\n selectOption(optionId, {\n onSettled: (ok) => {\n if (ok) setOpen(false);\n },\n }),\n }),\n [options, selectedOptionId, selectionError, selectOption],\n );\n\n if (!isValidElement<OverlayOpenProps>(children)) {\n return children;\n }\n\n return (\n <ShippingSimulatorContext value={contextValue}>\n <DeliveryOptionsContextInternal value={delivery}>\n {cloneElement(children, { open, onOpenChange: (next: boolean) => setOpen(next) })}\n </DeliveryOptionsContextInternal>\n </ShippingSimulatorContext>\n );\n}\n\nexport function DeliveryOptionsSummary({\n label,\n placeholder = 'Selecionar forma de entrega',\n separator = ' — ',\n className,\n ...props\n}: DeliveryOptionsSummaryProps) {\n const { options, selectedOptionId } = useDeliveryOptions();\n const selectedOption = options.find((option) => option.id === selectedOptionId) ?? null;\n\n return (\n <span {...props} className={className} data-fs-delivery-options-summary=\"\">\n {label ? <span data-fs-delivery-options-summary-label=\"\">{label}</span> : null}\n {selectedOption ? (\n <OptionItemContext value={selectedOption}>\n <span data-fs-delivery-options-summary-value=\"\">\n <ShippingSimulatorOptionName />\n <span data-fs-delivery-options-summary-separator=\"\">{separator}</span>\n <ShippingSimulatorOptionPrice />\n </span>\n </OptionItemContext>\n ) : (\n <span data-fs-delivery-options-summary-placeholder=\"\">{placeholder}</span>\n )}\n </span>\n );\n}\n\nexport function DeliveryOptionsList({ className, children, ...props }: DeliveryOptionsListProps) {\n const { options, selectedOptionId, selectionError, selectOption } = useDeliveryOptions();\n\n return (\n <DeliveryOptionsListBody\n {...props}\n className={className}\n options={options}\n selectedOptionId={selectedOptionId}\n selectionError={selectionError}\n onSelectOption={selectOption}\n >\n {children}\n </DeliveryOptionsListBody>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"delivery-options-controller.js","names":[],"sources":["../../../src/matter/delivery-options/delivery-options-controller.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport {\n type ComponentPropsWithRef,\n cloneElement,\n createContext,\n isValidElement,\n type ReactNode,\n useContext,\n useMemo,\n useState,\n} from 'react';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport {\n OptionItemContext,\n type ShippingOption,\n ShippingSimulatorContext,\n ShippingSimulatorOptionName,\n ShippingSimulatorOptionPrice,\n} from '../shipping-simulator/shipping-simulator.js';\nimport { DeliveryOptionsListBody } from './delivery-options.js';\nimport { type RawDeliveryOptionsData, useDeliveryOptionSelection } from './use-delivery-option-selection.js';\n\n/**\n * The collapsed delivery-option surfaces (centered modal / slide-in panel) as a controller +\n * composed particles, replacing the former monolithic `DeliveryOptionsDialog` / `DeliveryOptionsDrawer`\n * (Decision 8). The page composes a raw `DialogRoot` or `DrawerRoot` and each of its particles\n * (`DialogTrigger` / `DialogBackdrop` / `DialogPopup` …) directly as `page.json` nodes, so every part\n * is a swatch-addressable node with zero Tailwind baked into the package — the portaled backdrop/popup\n * a descendant selector could not reach in the monolith are now first-class nodes (Decision 9).\n *\n * `DeliveryOptionsController` owns state only and renders no markup: it runs the shared\n * `useDeliveryOptionSelection` engine, holds the open state, and provides both the FSPG-832\n * `ShippingSimulatorContext` and a `DeliveryOptions` selection context to its subtree, then injects\n * `open` / `onOpenChange` into its single overlay-`Root` child via `cloneElement` — the same mechanism\n * as `CartOpenOnAdd` and `ShippingAddressSavedAddressDrawerController`. A single engine behind the\n * context keeps the trigger echo (`DeliveryOptionsSummary`) and the option list (`DeliveryOptionsList`)\n * on one selection state, so even a local `mockOptions` pick reflects on the trigger.\n *\n * Selection follows the shared rule (INV-10/11): a real pick dispatches `shipping.selectOption` and\n * the surface closes only once it resolves without a `userError`; a rejected pick keeps it open with\n * the inline `role=\"alert\"`; a `mockOptions` pick updates local state and closes immediately.\n */\n\nconst fragment = /* GraphQL */ `\n fragment DeliveryOptionsControllerData on Query {\n shipping {\n id\n options {\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Selection context — provided by DeliveryOptionsController, read by the leaves\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsContextValue = Readonly<{\n /** Resolved option rows (real when the fetch has any, else the `mockOptions` fallback). */\n options: ShippingOption[];\n /** Group value: the persisted `selectedOptionId` for real data, local state under mock. */\n selectedOptionId: string | null;\n /** First `userError` message from the latest real selection, or `null`. */\n selectionError: string | null;\n /** Selects an option and closes the surface once the selection resolves without a `userError`. */\n selectOption: (optionId: string) => void;\n}>;\n\nconst DELIVERY_OPTIONS_UNSET = Symbol('DeliveryOptionsContextUnset');\n\nconst DeliveryOptionsContextInternal = createContext<DeliveryOptionsContextValue | typeof DELIVERY_OPTIONS_UNSET>(\n DELIVERY_OPTIONS_UNSET,\n);\n\n/**\n * Read the delivery-option selection state from the nearest `DeliveryOptionsController`. Throws when\n * called outside a controller subtree.\n */\nexport function useDeliveryOptions(): DeliveryOptionsContextValue {\n const ctx = useContext(DeliveryOptionsContextInternal);\n if (ctx === DELIVERY_OPTIONS_UNSET) {\n throw new Error('useDeliveryOptions() must be used inside a <DeliveryOptionsController> subtree.');\n }\n return ctx;\n}\n\n// The minimal open-state shape the controller injects into its overlay-Root child; both\n// `DialogRoot` and `DrawerRoot` accept it.\ntype OverlayOpenProps = {\n open?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsControllerProps = {\n /** Shipping slice forwarded by the reactive runtime; `null` until shipping resolves. */\n $data?: RawDeliveryOptionsData | null;\n /** Compiler-injected action surface; carries `shipping.selectOption`. */\n $actions?: ComponentActions;\n /** Local/dev fallback rows, used only when the reactive fetch resolves no options. */\n mockOptions?: ShippingOption[];\n /** The single overlay `Root` to control — a `DialogRoot` (modal) or `DrawerRoot` (slide-in). */\n children?: ReactNode;\n};\n\nexport type DeliveryOptionsSummaryProps = ComponentPropsWithRef<'span'> & {\n /** Optional prefix shown before the summary value (e.g. \"Forma de entrega\"). */\n label?: string;\n /** Text shown while nothing is selected. */\n placeholder?: string;\n /** Separator between the selected option name and price. */\n separator?: string;\n};\n\nexport type DeliveryOptionsListProps = ComponentPropsWithRef<'div'>;\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const DeliveryOptionsControllerDefinition = defineComponent<DeliveryOptionsControllerProps>({\n name: 'DeliveryOptionsController',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsController,\n fragment,\n fetchStrategy: 'reactive',\n actions: { shipping: ['selectOption'] },\n // Its own `delivery-options` selection context, plus the reused option leaves' runtime contexts\n // (Decision 2): `shipping-simulator-option` for the leaves composed under DeliveryOptionsList, and\n // the outer `shipping-simulator` this controller also provides at runtime.\n providesContext: ['delivery-options', 'shipping-simulator', 'shipping-simulator-option'],\n editor: { label: 'Delivery Options Controller', category: 'commerce' },\n schema: {\n mockOptions: { type: 'array', label: 'Mock options (local/dev fallback, ignored once real data has options)' },\n },\n guideline:\n 'State-only controller for a collapsed delivery-option picker: wrap a single DialogRoot (centered modal) or DrawerRoot (slide-in) child and it becomes controlled — the surface opens from its trigger and closes once a real selection succeeds; a userError keeps it open. Renders no markup and bakes no styling: compose the overlay particles (DialogTrigger/DialogPortal/DialogBackdrop/DialogPopup or the Drawer equivalents) directly as page nodes, place a DeliveryOptionsSummary inside the trigger (echoes the selected name + price, or a placeholder) and a DeliveryOptionsList inside the popup (the option radio group; put the reused ShippingSimulatorOption leaves as its children). Selection dispatches $actions.shipping.selectOption; mockOptions is a local/dev-only fallback. Mirrors the CartOpenOnAdd / ShippingAddressSavedAddressDrawerController pattern.',\n});\n\nexport const DeliveryOptionsSummaryDefinition = defineComponent<DeliveryOptionsSummaryProps>({\n name: 'DeliveryOptionsSummary',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsSummary,\n requiresContext: 'delivery-options',\n editor: { label: 'Delivery Options Summary', category: 'commerce' },\n schema: {\n label: { type: 'string', label: 'Trigger label prefix' },\n placeholder: { type: 'string', default: 'Selecionar forma de entrega', label: 'Placeholder' },\n separator: { type: 'string', default: ' — ', label: 'Summary separator' },\n },\n guideline:\n 'The collapsed picker trigger echo: renders the selected option name + price (reusing the FSPG-832 option leaves), or a placeholder while nothing is selected. Place it inside the DialogTrigger / DrawerTrigger of a DeliveryOptionsController subtree. Reads the shared selection context, so it reflects the current pick — real or mockOptions. Must be inside DeliveryOptionsController.',\n});\n\nexport const DeliveryOptionsListDefinition = defineComponent<DeliveryOptionsListProps>({\n name: 'DeliveryOptionsList',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsList,\n requiresContext: 'delivery-options',\n editor: { label: 'Delivery Options List', category: 'commerce' },\n schema: {},\n guideline:\n 'The collapsed picker body: the whole-cart option list as a radio group bound to the selected option, plus the inline selection error (role=\"alert\"). Place it inside the DialogPopup / DrawerPopup of a DeliveryOptionsController subtree, with the reused option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as its children template. Picking a row dispatches the selection and closes the surface on success. Must be inside DeliveryOptionsController.',\n});\n\n// ---------------------------------------------------------------------------\n// Implementations\n// ---------------------------------------------------------------------------\n\nexport function DeliveryOptionsController({\n $data,\n $actions: $actionsProp,\n mockOptions,\n children,\n}: Readonly<DeliveryOptionsControllerProps>) {\n const $actions = useActionsOverride($actionsProp);\n const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({\n $data,\n $actions,\n mockOptions,\n });\n const [open, setOpen] = useState(false);\n\n const delivery = useMemo<DeliveryOptionsContextValue>(\n () => ({\n options,\n selectedOptionId,\n selectionError,\n selectOption: (optionId: string) =>\n selectOption(optionId, {\n onSettled: (ok) => {\n if (ok) setOpen(false);\n },\n }),\n }),\n [options, selectedOptionId, selectionError, selectOption],\n );\n\n if (!isValidElement<OverlayOpenProps>(children)) {\n return children;\n }\n\n return (\n <ShippingSimulatorContext value={contextValue}>\n <DeliveryOptionsContextInternal value={delivery}>\n {cloneElement(children, { open, onOpenChange: (next: boolean) => setOpen(next) })}\n </DeliveryOptionsContextInternal>\n </ShippingSimulatorContext>\n );\n}\n\nexport function DeliveryOptionsSummary({\n label,\n placeholder = 'Selecionar forma de entrega',\n separator = ' — ',\n className,\n ...props\n}: DeliveryOptionsSummaryProps) {\n const { options, selectedOptionId } = useDeliveryOptions();\n const selectedOption = options.find((option) => option.id === selectedOptionId) ?? null;\n\n return (\n <span {...props} className={className} data-fs-delivery-options-summary=\"\">\n {label ? <span data-fs-delivery-options-summary-label=\"\">{label}</span> : null}\n {selectedOption ? (\n <OptionItemContext value={selectedOption}>\n <span data-fs-delivery-options-summary-value=\"\">\n <ShippingSimulatorOptionName />\n <span data-fs-delivery-options-summary-separator=\"\">{separator}</span>\n <ShippingSimulatorOptionPrice />\n </span>\n </OptionItemContext>\n ) : (\n <span data-fs-delivery-options-summary-placeholder=\"\">{placeholder}</span>\n )}\n </span>\n );\n}\n\nexport function DeliveryOptionsList({ className, children, ...props }: DeliveryOptionsListProps) {\n const { options, selectedOptionId, selectionError, selectOption } = useDeliveryOptions();\n\n return (\n <DeliveryOptionsListBody\n {...props}\n className={className}\n options={options}\n selectedOptionId={selectedOptionId}\n selectionError={selectionError}\n onSelectOption={selectOption}\n >\n {children}\n </DeliveryOptionsListBody>\n );\n}\n"],"mappings":"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"}
|
|
@@ -26,6 +26,6 @@ export type DeliveryOptionsRootProps = ComponentPropsWithRef<'div'> & {
|
|
|
26
26
|
mockOptions?: ShippingOption[];
|
|
27
27
|
};
|
|
28
28
|
export declare const DeliveryOptionsRootDefinition: ComponentDefinition<DeliveryOptionsRootProps>;
|
|
29
|
-
export declare function DeliveryOptionsRoot({ $data, $actions, mockOptions, className, children, ...props }: DeliveryOptionsRootProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function DeliveryOptionsRoot({ $data, $actions: $actionsProp, mockOptions, className, children, ...props }: DeliveryOptionsRootProps): import("react/jsx-runtime").JSX.Element;
|
|
30
30
|
//#endregion
|
|
31
31
|
//# sourceMappingURL=delivery-options.d.ts.map
|
|
@@ -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 { RadioGroup, RadioRoot } from "../../particles/radio/radio.js";
|
|
5
6
|
import { OptionItemContext, ShippingSimulatorContext } from "../shipping-simulator/shipping-simulator.js";
|
|
6
7
|
import { useDeliveryOptionSelection } from "./use-delivery-option-selection.js";
|
|
@@ -78,7 +79,8 @@ function DeliveryOptionsListBody({ options, selectedOptionId, selectionError, on
|
|
|
78
79
|
}) : null]
|
|
79
80
|
});
|
|
80
81
|
}
|
|
81
|
-
function DeliveryOptionsRoot({ $data, $actions, mockOptions, className, children, ...props }) {
|
|
82
|
+
function DeliveryOptionsRoot({ $data, $actions: $actionsProp, mockOptions, className, children, ...props }) {
|
|
83
|
+
const $actions = useActionsOverride($actionsProp);
|
|
82
84
|
const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({
|
|
83
85
|
$data,
|
|
84
86
|
$actions,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delivery-options.js","names":[],"sources":["../../../src/matter/delivery-options/delivery-options.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { RadioGroup, RadioRoot } from '../../particles/radio/radio.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport {\n OptionItemContext,\n type ShippingOption,\n ShippingSimulatorContext,\n} from '../shipping-simulator/shipping-simulator.js';\nimport { type RawDeliveryOptionsData, useDeliveryOptionSelection } from './use-delivery-option-selection.js';\n\n/**\n * The `DeliveryOptions` matter section (specs/delivery-options-step.md, T3 of the Single Address\n * Delivery epic). `DeliveryOptionsRoot` is a reactive Root over the persisted `Shipping` entity\n * (Decision 1) that becomes the THIRD provider of the FSPG-832 option-leaf runtime contexts —\n * `ShippingSimulatorContext` and the per-option `OptionItemContext` — alongside the two existing\n * simulator Roots (Decision 2), so `ShippingSimulatorOption` / `OptionName` / `OptionEstimate` /\n * `OptionPrice` / `OptionFreeBadge` render identically here with zero edits to those leaves.\n * `children` is the per-option template, rendered once per option inside a `RadioRoot` (radio\n * particle) whose group value is bound directly to `selectedOptionId` (Decision 5 — no local\n * selection state); changing it fires the `shipping.selectOption` action. Fields the reused leaves\n * do not read for this section (`postalCode`, `setPostalCode`, `submit`, `status`) are inert\n * defaults, satisfying the shared context type without a simulation flow. Exception: while\n * rendering `mockOptions` (no real data), selection is local-only and never dispatches the action —\n * see `mockOptions` on `DeliveryOptionsRootProps`.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — raw template literal, matching the ShippingSimulator matter's own convention\n// (not yet migrated to the codegen `graphql()` pipeline; see codegen.ts's documents glob comment).\n//\n// Selects `shipping { id ... }` (not shown in the spec's fragment snippet) so Apollo's\n// InMemoryCache normalizes `Shipping` by id — required for `shippingOptionSelect`'s returned\n// `shipping` to update the same cache entity this fragment reads (Decision 1/Decision 5's\n// \"reactive cache, no local state\" only holds if both sides normalize to the same entity).\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment DeliveryOptionsData on Query {\n shipping {\n id\n options {\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Shipping slice forwarded by the reactive runtime (`ReactiveDataProvider`) — it is the value of\n * the `shipping` top-level field on `DeliveryOptionsData on Query`, not the wrapping\n * `{ shipping: … }` shape. Receives `null` when shipping has not yet resolved.\n */\n $data?: RawDeliveryOptionsData | null;\n /** Compiler-injected action surface; carries `shipping.selectOption`. */\n $actions?: ComponentActions;\n /**\n * Local/dev fallback rows, used only when the reactive fetch resolves no options — real data\n * always wins the moment it has any. `Shipping.options` is non-nullable, so a genuinely empty\n * cart is indistinguishable from \"no data yet\" at this prop's shape; setting `mockOptions` is an\n * explicit per-page opt-in that accepts showing these rows in both cases. Omit on production\n * pages. Selecting a mock row is handled entirely with local state and never calls\n * `$actions.shipping.selectOption` — there is no real cart/address for the real mutation to act\n * on, so dispatching it would only ever surface a domain error (e.g. `MISSING_SHIPPING_ADDRESS`).\n */\n mockOptions?: ShippingOption[];\n};\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\nexport const DeliveryOptionsRootDefinition = defineComponent<DeliveryOptionsRootProps>({\n name: 'DeliveryOptionsRoot',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsRoot,\n fragment,\n fetchStrategy: 'reactive',\n actions: { shipping: ['selectOption'] },\n editor: { label: 'Delivery Options', category: 'commerce' },\n schema: {\n mockOptions: { type: 'array', label: 'Mock options (local/dev fallback, ignored once real data has options)' },\n },\n // Re-provides the reused option leaves' runtime contexts (Decision 2): the inner\n // `shipping-simulator-option` token is required — the option leaves compose as direct children\n // here with no `ShippingSimulatorOptions` node between them — and the outer `shipping-simulator`\n // token reflects the `ShippingSimulatorContext` this Root also provides at runtime.\n providesContext: ['shipping-simulator', 'shipping-simulator-option'],\n guideline:\n 'Root for the whole-cart delivery-option list at the delivery step: renders Shipping.options as a radio group bound to selectedOptionId, reusing the FSPG-832 option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as the per-option template placed as children. Selecting a row dispatches $actions.shipping.selectOption. No local selection state — the group value is always the server selectedOptionId. mockOptions is a local/dev-only fallback rendered when the real fetch has no options; omit it in production.',\n});\n\n// ---------------------------------------------------------------------------\n// Shared list body\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsListBodyProps = ComponentPropsWithRef<'div'> & {\n /** Resolved option rows (real or mock), supplied by the caller's selection engine. */\n options: ShippingOption[];\n /** Group value bound to the radio group; `null` renders nothing selected. */\n selectedOptionId: string | null;\n /** First `userError` message from the latest selection, or `null`; rendered as `role=\"alert\"`. */\n selectionError: string | null;\n /** Dispatched on a row change with the picked option id. */\n onSelectOption: (optionId: string) => void;\n};\n\n/**\n * The list body shared by `DeliveryOptionsRoot` (always-open) and `DeliveryOptionsList` (the\n * collapsed-surface body under `DeliveryOptionsController`): the inline selection error plus the\n * radio group of options, reusing the FSPG-832 option leaves as the per-option `children` template.\n * It owns no data or selection engine — the caller resolves `options` / `selectedOptionId` /\n * `selectionError` and supplies the `onSelect` dispatch, so both surfaces render through one\n * component and cannot drift. Not registered: it renders no independently placeable part of its own\n * (the `data-fs-delivery-options` container hook and the reused leaves are the addressable parts).\n */\nexport function DeliveryOptionsListBody({\n options,\n selectedOptionId,\n selectionError,\n onSelectOption,\n className,\n children,\n ...props\n}: DeliveryOptionsListBodyProps) {\n return (\n <div {...props} className={className} data-fs-delivery-options=\"\">\n {selectionError ? (\n <div role=\"alert\" data-fs-delivery-options-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {options.length > 0 ? (\n <RadioGroup value={selectedOptionId ?? ''} onValueChange={onSelectOption}>\n {options.map((option) => (\n <OptionItemContext key={option.id} value={option}>\n <RadioRoot value={option.id}>{children}</RadioRoot>\n </OptionItemContext>\n ))}\n </RadioGroup>\n ) : null}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function DeliveryOptionsRoot({\n $data,\n $actions,\n mockOptions,\n className,\n children,\n ...props\n}: DeliveryOptionsRootProps) {\n const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({\n $data,\n $actions,\n mockOptions,\n });\n\n return (\n <ShippingSimulatorContext value={contextValue}>\n <DeliveryOptionsListBody\n {...props}\n className={className}\n options={options}\n selectedOptionId={selectedOptionId}\n selectionError={selectionError}\n onSelectOption={(optionId) => selectOption(optionId)}\n >\n {children}\n </DeliveryOptionsListBody>\n </ShippingSimulatorContext>\n );\n}\n"],"mappings":";;;;;;;AA8FA,MAAa,gCAAgC,gBAA0C;CACrF,MAAM;CACN,YAAY;CACZ,WAAW;CACX;;;;;;;;;;;;;;;;;;;;;;;;;CACA,eAAe;CACf,SAAS,EAAE,UAAU,CAAC,cAAc,EAAE;CACtC,QAAQ;EAAE,OAAO;EAAoB,UAAU;CAAW;CAC1D,QAAQ,EACN,aAAa;EAAE,MAAM;EAAS,OAAO;CAAwE,EAC/G;CAKA,iBAAiB,CAAC,sBAAsB,2BAA2B;CACnE,WACE;AACJ,CAAC;;;;;;;;;;AA0BD,SAAgB,wBAAwB,EACtC,SACA,kBACA,gBACA,gBACA,WACA,UACA,GAAG,SAC4B;CAC/B,OACE,qBAAC,OAAD;EAAK,GAAI;EAAkB;EAAW,4BAAyB;EAA/D,UAAA,CACG,iBACC,oBAAC,OAAD;GAAK,MAAK;GAAQ,kCAA+B;GAC9C,UAAA;EACE,CAAA,IACH,MACH,QAAQ,SAAS,IAChB,oBAAC,YAAD;GAAY,OAAO,oBAAoB;GAAI,eAAe;GACvD,UAAA,QAAQ,KAAK,WACZ,oBAAC,mBAAD;IAAmC,OAAO;IACxC,UAAA,oBAAC,WAAD;KAAW,OAAO,OAAO;KAAK;IAAoB,CAAA;GACjC,GAFK,OAAO,EAEZ,CACpB;EACS,CAAA,IACV,IACD;;AAET;AAMA,SAAgB,oBAAoB,EAClC,OACA,UACA,aACA,WACA,UACA,GAAG,SACwB;CAC3B,MAAM,EAAE,SAAS,kBAAkB,gBAAgB,cAAc,iBAAiB,2BAA2B;EAC3G;EACA;EACA;CACF,CAAC;CAED,OACE,oBAAC,0BAAD;EAA0B,OAAO;EAC/B,UAAA,oBAAC,yBAAD;GACE,GAAI;GACO;GACF;GACS;GACF;GAChB,iBAAiB,aAAa,aAAa,QAAQ;GAElD;EACsB,CAAA;CACD,CAAA;AAE9B"}
|
|
1
|
+
{"version":3,"file":"delivery-options.js","names":[],"sources":["../../../src/matter/delivery-options/delivery-options.tsx"],"sourcesContent":["'use client';\n\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef } from 'react';\nimport { RadioGroup, RadioRoot } from '../../particles/radio/radio.js';\nimport { useActionsOverride } from '../../sdk/actions/actions-override-context.js';\nimport type { ComponentActions } from '../../sdk/actions/types.js';\nimport {\n OptionItemContext,\n type ShippingOption,\n ShippingSimulatorContext,\n} from '../shipping-simulator/shipping-simulator.js';\nimport { type RawDeliveryOptionsData, useDeliveryOptionSelection } from './use-delivery-option-selection.js';\n\n/**\n * The `DeliveryOptions` matter section (specs/delivery-options-step.md, T3 of the Single Address\n * Delivery epic). `DeliveryOptionsRoot` is a reactive Root over the persisted `Shipping` entity\n * (Decision 1) that becomes the THIRD provider of the FSPG-832 option-leaf runtime contexts —\n * `ShippingSimulatorContext` and the per-option `OptionItemContext` — alongside the two existing\n * simulator Roots (Decision 2), so `ShippingSimulatorOption` / `OptionName` / `OptionEstimate` /\n * `OptionPrice` / `OptionFreeBadge` render identically here with zero edits to those leaves.\n * `children` is the per-option template, rendered once per option inside a `RadioRoot` (radio\n * particle) whose group value is bound directly to `selectedOptionId` (Decision 5 — no local\n * selection state); changing it fires the `shipping.selectOption` action. Fields the reused leaves\n * do not read for this section (`postalCode`, `setPostalCode`, `submit`, `status`) are inert\n * defaults, satisfying the shared context type without a simulation flow. Exception: while\n * rendering `mockOptions` (no real data), selection is local-only and never dispatches the action —\n * see `mockOptions` on `DeliveryOptionsRootProps`.\n */\n\n// ---------------------------------------------------------------------------\n// GraphQL Fragment — raw template literal, matching the ShippingSimulator matter's own convention\n// (not yet migrated to the codegen `graphql()` pipeline; see codegen.ts's documents glob comment).\n//\n// Selects `shipping { id ... }` (not shown in the spec's fragment snippet) so Apollo's\n// InMemoryCache normalizes `Shipping` by id — required for `shippingOptionSelect`'s returned\n// `shipping` to update the same cache entity this fragment reads (Decision 1/Decision 5's\n// \"reactive cache, no local state\" only holds if both sides normalize to the same entity).\n// ---------------------------------------------------------------------------\n\nconst fragment = /* GraphQL */ `\n fragment DeliveryOptionsData on Query {\n shipping {\n id\n options {\n id\n name\n channel\n shippingEstimate {\n value\n unit\n }\n price {\n amount\n currencyCode\n }\n listPrice {\n amount\n currencyCode\n }\n }\n selectedOptionId\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsRootProps = ComponentPropsWithRef<'div'> & {\n /**\n * Shipping slice forwarded by the reactive runtime (`ReactiveDataProvider`) — it is the value of\n * the `shipping` top-level field on `DeliveryOptionsData on Query`, not the wrapping\n * `{ shipping: … }` shape. Receives `null` when shipping has not yet resolved.\n */\n $data?: RawDeliveryOptionsData | null;\n /** Compiler-injected action surface; carries `shipping.selectOption`. */\n $actions?: ComponentActions;\n /**\n * Local/dev fallback rows, used only when the reactive fetch resolves no options — real data\n * always wins the moment it has any. `Shipping.options` is non-nullable, so a genuinely empty\n * cart is indistinguishable from \"no data yet\" at this prop's shape; setting `mockOptions` is an\n * explicit per-page opt-in that accepts showing these rows in both cases. Omit on production\n * pages. Selecting a mock row is handled entirely with local state and never calls\n * `$actions.shipping.selectOption` — there is no real cart/address for the real mutation to act\n * on, so dispatching it would only ever surface a domain error (e.g. `MISSING_SHIPPING_ADDRESS`).\n */\n mockOptions?: ShippingOption[];\n};\n\n// ---------------------------------------------------------------------------\n// Definition\n// ---------------------------------------------------------------------------\n\nexport const DeliveryOptionsRootDefinition = defineComponent<DeliveryOptionsRootProps>({\n name: 'DeliveryOptionsRoot',\n importPath: '@vtex/faststore/components',\n component: DeliveryOptionsRoot,\n fragment,\n fetchStrategy: 'reactive',\n actions: { shipping: ['selectOption'] },\n editor: { label: 'Delivery Options', category: 'commerce' },\n schema: {\n mockOptions: { type: 'array', label: 'Mock options (local/dev fallback, ignored once real data has options)' },\n },\n // Re-provides the reused option leaves' runtime contexts (Decision 2): the inner\n // `shipping-simulator-option` token is required — the option leaves compose as direct children\n // here with no `ShippingSimulatorOptions` node between them — and the outer `shipping-simulator`\n // token reflects the `ShippingSimulatorContext` this Root also provides at runtime.\n providesContext: ['shipping-simulator', 'shipping-simulator-option'],\n guideline:\n 'Root for the whole-cart delivery-option list at the delivery step: renders Shipping.options as a radio group bound to selectedOptionId, reusing the FSPG-832 option leaves (ShippingSimulatorOption, OptionName, OptionEstimate, OptionPrice, OptionFreeBadge) as the per-option template placed as children. Selecting a row dispatches $actions.shipping.selectOption. No local selection state — the group value is always the server selectedOptionId. mockOptions is a local/dev-only fallback rendered when the real fetch has no options; omit it in production.',\n});\n\n// ---------------------------------------------------------------------------\n// Shared list body\n// ---------------------------------------------------------------------------\n\nexport type DeliveryOptionsListBodyProps = ComponentPropsWithRef<'div'> & {\n /** Resolved option rows (real or mock), supplied by the caller's selection engine. */\n options: ShippingOption[];\n /** Group value bound to the radio group; `null` renders nothing selected. */\n selectedOptionId: string | null;\n /** First `userError` message from the latest selection, or `null`; rendered as `role=\"alert\"`. */\n selectionError: string | null;\n /** Dispatched on a row change with the picked option id. */\n onSelectOption: (optionId: string) => void;\n};\n\n/**\n * The list body shared by `DeliveryOptionsRoot` (always-open) and `DeliveryOptionsList` (the\n * collapsed-surface body under `DeliveryOptionsController`): the inline selection error plus the\n * radio group of options, reusing the FSPG-832 option leaves as the per-option `children` template.\n * It owns no data or selection engine — the caller resolves `options` / `selectedOptionId` /\n * `selectionError` and supplies the `onSelect` dispatch, so both surfaces render through one\n * component and cannot drift. Not registered: it renders no independently placeable part of its own\n * (the `data-fs-delivery-options` container hook and the reused leaves are the addressable parts).\n */\nexport function DeliveryOptionsListBody({\n options,\n selectedOptionId,\n selectionError,\n onSelectOption,\n className,\n children,\n ...props\n}: DeliveryOptionsListBodyProps) {\n return (\n <div {...props} className={className} data-fs-delivery-options=\"\">\n {selectionError ? (\n <div role=\"alert\" data-fs-delivery-options-error=\"\">\n {selectionError}\n </div>\n ) : null}\n {options.length > 0 ? (\n <RadioGroup value={selectedOptionId ?? ''} onValueChange={onSelectOption}>\n {options.map((option) => (\n <OptionItemContext key={option.id} value={option}>\n <RadioRoot value={option.id}>{children}</RadioRoot>\n </OptionItemContext>\n ))}\n </RadioGroup>\n ) : null}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Implementation\n// ---------------------------------------------------------------------------\n\nexport function DeliveryOptionsRoot({\n $data,\n $actions: $actionsProp,\n mockOptions,\n className,\n children,\n ...props\n}: DeliveryOptionsRootProps) {\n const $actions = useActionsOverride($actionsProp);\n const { options, selectedOptionId, selectionError, selectOption, contextValue } = useDeliveryOptionSelection({\n $data,\n $actions,\n mockOptions,\n });\n\n return (\n <ShippingSimulatorContext value={contextValue}>\n <DeliveryOptionsListBody\n {...props}\n className={className}\n options={options}\n selectedOptionId={selectedOptionId}\n selectionError={selectionError}\n onSelectOption={(optionId) => selectOption(optionId)}\n >\n {children}\n </DeliveryOptionsListBody>\n </ShippingSimulatorContext>\n );\n}\n"],"mappings":";;;;;;;;AA+FA,MAAa,gCAAgC,gBAA0C;CACrF,MAAM;CACN,YAAY;CACZ,WAAW;CACX;;;;;;;;;;;;;;;;;;;;;;;;;CACA,eAAe;CACf,SAAS,EAAE,UAAU,CAAC,cAAc,EAAE;CACtC,QAAQ;EAAE,OAAO;EAAoB,UAAU;CAAW;CAC1D,QAAQ,EACN,aAAa;EAAE,MAAM;EAAS,OAAO;CAAwE,EAC/G;CAKA,iBAAiB,CAAC,sBAAsB,2BAA2B;CACnE,WACE;AACJ,CAAC;;;;;;;;;;AA0BD,SAAgB,wBAAwB,EACtC,SACA,kBACA,gBACA,gBACA,WACA,UACA,GAAG,SAC4B;CAC/B,OACE,qBAAC,OAAD;EAAK,GAAI;EAAkB;EAAW,4BAAyB;EAA/D,UAAA,CACG,iBACC,oBAAC,OAAD;GAAK,MAAK;GAAQ,kCAA+B;GAC9C,UAAA;EACE,CAAA,IACH,MACH,QAAQ,SAAS,IAChB,oBAAC,YAAD;GAAY,OAAO,oBAAoB;GAAI,eAAe;GACvD,UAAA,QAAQ,KAAK,WACZ,oBAAC,mBAAD;IAAmC,OAAO;IACxC,UAAA,oBAAC,WAAD;KAAW,OAAO,OAAO;KAAK;IAAoB,CAAA;GACjC,GAFK,OAAO,EAEZ,CACpB;EACS,CAAA,IACV,IACD;;AAET;AAMA,SAAgB,oBAAoB,EAClC,OACA,UAAU,cACV,aACA,WACA,UACA,GAAG,SACwB;CAC3B,MAAM,WAAW,mBAAmB,YAAY;CAChD,MAAM,EAAE,SAAS,kBAAkB,gBAAgB,cAAc,iBAAiB,2BAA2B;EAC3G;EACA;EACA;CACF,CAAC;CAED,OACE,oBAAC,0BAAD;EAA0B,OAAO;EAC/B,UAAA,oBAAC,yBAAD;GACE,GAAI;GACO;GACF;GACS;GACF;GAChB,iBAAiB,aAAa,aAAa,QAAQ;GAElD;EACsB,CAAA;CACD,CAAA;AAE9B"}
|
|
@@ -32,6 +32,6 @@ export declare const EmailIdentificationControlDefinition: ComponentDefinition<E
|
|
|
32
32
|
* with `data-fs-email-identification-readonly`. Server `userErrors` are surfaced inline; the
|
|
33
33
|
* session state is never computed locally.
|
|
34
34
|
*/
|
|
35
|
-
export declare function EmailIdentificationControl({ label, placeholder, invalidEmailMessage, $data: $dataProp, $actions, className, ...props }: EmailIdentificationControlProps): import("react/jsx-runtime").JSX.Element;
|
|
35
|
+
export declare function EmailIdentificationControl({ label, placeholder, invalidEmailMessage, $data: $dataProp, $actions: $actionsProp, className, ...props }: EmailIdentificationControlProps): import("react/jsx-runtime").JSX.Element;
|
|
36
36
|
//#endregion
|
|
37
37
|
//# sourceMappingURL=email-identification.d.ts.map
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { defineComponent } from "../../core/dist/define-component.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
4
|
import { EmailField, isValidEmail } from "../../particles/email-field/email-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 { useEffect, useState } from "react";
|
|
@@ -54,8 +55,9 @@ const EmailIdentificationControlDefinition = defineComponent({
|
|
|
54
55
|
* with `data-fs-email-identification-readonly`. Server `userErrors` are surfaced inline; the
|
|
55
56
|
* session state is never computed locally.
|
|
56
57
|
*/
|
|
57
|
-
function EmailIdentificationControl({ label, placeholder, invalidEmailMessage, $data: $dataProp, $actions, className, ...props }) {
|
|
58
|
+
function EmailIdentificationControl({ label, placeholder, invalidEmailMessage, $data: $dataProp, $actions: $actionsProp, className, ...props }) {
|
|
58
59
|
const $data = useDataOverride($dataProp);
|
|
60
|
+
const $actions = useActionsOverride($actionsProp);
|
|
59
61
|
const { canEditData } = useSession();
|
|
60
62
|
const readOnly = !canEditData;
|
|
61
63
|
const savedEmail = $data?.email ?? "";
|