@vtex/faststore 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1595 -481
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +31 -7
- package/dist/components.js +35 -13
- package/dist/content-types.json +73 -0
- package/dist/docs/access-code-sign-in.md +1 -1
- package/dist/docs/account-access-gate.md +153 -0
- package/dist/docs/account-entry-point.md +1 -2
- package/dist/docs/account-menu.md +164 -0
- package/dist/docs/address-summary.md +24 -3
- package/dist/docs/alert-dialog.md +204 -0
- package/dist/docs/billing-address.md +284 -0
- package/dist/docs/buy-button.md +1 -1
- package/dist/docs/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- package/dist/docs/cart.md +1 -1
- package/dist/docs/client-preferences-fieldset.md +148 -0
- package/dist/docs/client-preferences.md +2 -1
- package/dist/docs/client-profile-fieldset.md +184 -0
- package/dist/docs/client-profile.md +1 -1
- package/dist/docs/document-field.md +58 -4
- package/dist/docs/field.md +317 -0
- package/dist/docs/form.md +307 -0
- package/dist/docs/identity-edit.md +6 -6
- package/dist/docs/identity-summary.md +5 -5
- package/dist/docs/installments.md +248 -0
- package/dist/docs/my-account-profile.md +1 -0
- package/dist/docs/order-placed-payment-summary.md +138 -0
- package/dist/docs/payment-method-badges.md +232 -0
- package/dist/docs/payment-methods.md +1 -0
- package/dist/docs/payment.md +218 -0
- package/dist/docs/phone-field.md +58 -4
- package/dist/docs/place-order.md +157 -0
- package/dist/docs/save-card.md +186 -0
- package/dist/docs/saved-cards.md +218 -0
- package/dist/docs/select.md +465 -0
- package/dist/docs/session.md +1 -1
- package/dist/docs/shipping-address.md +110 -258
- package/dist/docs/sign-in-redirect.md +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/internal/client.d.ts +3 -1
- package/dist/internal/client.js +3 -1
- package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
- package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
- package/dist/matter/account-access-gate/account-access-gate.d.ts +86 -0
- package/dist/matter/account-access-gate/account-access-gate.js +283 -0
- package/dist/matter/account-access-gate/account-access-gate.js.map +1 -0
- package/dist/matter/account-access-gate/sign-out-intent.d.ts +17 -0
- package/dist/matter/account-access-gate/sign-out-intent.js +62 -0
- package/dist/matter/account-access-gate/sign-out-intent.js.map +1 -0
- package/dist/matter/account-entry-point/account-entry-point.js +2 -2
- package/dist/matter/account-entry-point/account-entry-point.js.map +1 -1
- package/dist/matter/account-menu/account-menu.d.ts +88 -0
- package/dist/matter/account-menu/account-menu.js +246 -0
- package/dist/matter/account-menu/account-menu.js.map +1 -0
- package/dist/matter/billing-address/billing-address.d.ts +165 -0
- package/dist/matter/billing-address/billing-address.js +408 -0
- package/dist/matter/billing-address/billing-address.js.map +1 -0
- package/dist/matter/card-entry/card-capture.d.ts +25 -0
- package/dist/matter/card-entry/card-capture.js +25 -0
- package/dist/matter/card-entry/card-capture.js.map +1 -0
- package/dist/matter/card-entry/card-entry.d.ts +120 -0
- package/dist/matter/card-entry/card-entry.js +832 -0
- package/dist/matter/card-entry/card-entry.js.map +1 -0
- package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
- package/dist/matter/cardholder-document/cardholder-document.js +70 -0
- package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
- package/dist/matter/cart/cart.d.ts +1 -1
- package/dist/matter/cart/cart.js +2 -2
- package/dist/matter/cart/cart.js.map +1 -1
- package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
- package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
- package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
- package/dist/matter/client-preferences/client-preferences.js +6 -6
- package/dist/matter/client-preferences/client-preferences.js.map +1 -1
- package/dist/matter/client-profile/client-profile.d.ts +4 -6
- package/dist/matter/client-profile/client-profile.js +9 -6
- package/dist/matter/client-profile/client-profile.js.map +1 -1
- package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
- package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
- package/dist/matter/delivery-options/delivery-options.js.map +1 -1
- package/dist/matter/identity-edit/identity-edit.js +2 -2
- package/dist/matter/identity-edit/identity-edit.js.map +1 -1
- package/dist/matter/identity-summary/identity-summary.js +2 -2
- package/dist/matter/identity-summary/identity-summary.js.map +1 -1
- package/dist/matter/installments/installments-select.d.ts +42 -0
- package/dist/matter/installments/installments-select.js +167 -0
- package/dist/matter/installments/installments-select.js.map +1 -0
- package/dist/matter/installments/installments.d.ts +58 -0
- package/dist/matter/installments/installments.js +176 -0
- package/dist/matter/installments/installments.js.map +1 -0
- package/dist/matter/installments/use-installments-selection.d.ts +29 -0
- package/dist/matter/installments/use-installments-selection.js +95 -0
- package/dist/matter/installments/use-installments-selection.js.map +1 -0
- package/dist/matter/my-account-profile/my-account-profile.js +5 -5
- package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
- package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
- package/dist/matter/payment/payment-card-capture.d.ts +19 -0
- package/dist/matter/payment/payment-card-capture.js +129 -0
- package/dist/matter/payment/payment-card-capture.js.map +1 -0
- package/dist/matter/payment/payment-controller.d.ts +24 -0
- package/dist/matter/payment/payment-controller.js +107 -0
- package/dist/matter/payment/payment-controller.js.map +1 -0
- package/dist/matter/payment/payment-method-badges.d.ts +57 -0
- package/dist/matter/payment/payment-method-badges.js +192 -0
- package/dist/matter/payment/payment-method-badges.js.map +1 -0
- package/dist/matter/payment/payment.d.ts +89 -0
- package/dist/matter/payment/payment.js +263 -0
- package/dist/matter/payment/payment.js.map +1 -0
- package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
- package/dist/matter/payment/use-payment-method-selection.js +458 -0
- package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
- package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
- package/dist/matter/payment/use-payment-submit-gate.js +144 -0
- package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
- package/dist/matter/place-order/place-order.d.ts +74 -0
- package/dist/matter/place-order/place-order.js +379 -0
- package/dist/matter/place-order/place-order.js.map +1 -0
- package/dist/matter/product-list/product-list.d.ts +1 -0
- package/dist/matter/product-list/product-list.js.map +1 -1
- package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
- package/dist/matter/product-shelf/product-shelf.js +2 -0
- package/dist/matter/product-shelf/product-shelf.js.map +1 -1
- package/dist/matter/region/region.js +1 -1
- package/dist/matter/region/region.js.map +1 -1
- package/dist/matter/save-card/save-card.d.ts +49 -0
- package/dist/matter/save-card/save-card.js +188 -0
- package/dist/matter/save-card/save-card.js.map +1 -0
- package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
- package/dist/matter/saved-cards/saved-cards.js +387 -0
- package/dist/matter/saved-cards/saved-cards.js.map +1 -0
- package/dist/matter/shipping-address/country-descriptors.js +20 -10
- package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
- package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
- package/dist/matter/shipping-address/shipping-address.js +481 -792
- package/dist/matter/shipping-address/shipping-address.js.map +1 -1
- package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
- package/dist/matter/variant-selector/variant-selector.js +1 -1
- package/dist/particles/address-summary/address-summary.d.ts +24 -0
- package/dist/particles/address-summary/address-summary.js +83 -2
- package/dist/particles/address-summary/address-summary.js.map +1 -1
- package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
- package/dist/particles/alert-dialog/alert-dialog.js +187 -0
- package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
- package/dist/particles/checkbox/checkbox.d.ts +7 -3
- package/dist/particles/checkbox/checkbox.js +4 -1
- package/dist/particles/checkbox/checkbox.js.map +1 -1
- package/dist/particles/document-field/document-field.d.ts +48 -5
- package/dist/particles/document-field/document-field.js +189 -32
- package/dist/particles/document-field/document-field.js.map +1 -1
- package/dist/particles/field/bound-field-registry.js +52 -0
- package/dist/particles/field/bound-field-registry.js.map +1 -0
- package/dist/particles/field/field.d.ts +116 -0
- package/dist/particles/field/field.js +497 -6
- package/dist/particles/field/field.js.map +1 -1
- package/dist/particles/fieldset/fieldset.js +2 -2
- package/dist/particles/fieldset/fieldset.js.map +1 -1
- package/dist/particles/footer-social/footer-social.js +1 -1
- package/dist/particles/form/form.d.ts +104 -0
- package/dist/particles/form/form.js +485 -0
- package/dist/particles/form/form.js.map +1 -0
- package/dist/particles/incentives/incentives.js +1 -1
- package/dist/particles/phone-field/phone-field.d.ts +38 -10
- package/dist/particles/phone-field/phone-field.js +183 -39
- package/dist/particles/phone-field/phone-field.js.map +1 -1
- package/dist/particles/postal-code-field/postal-code-field.js +2 -2
- package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
- package/dist/particles/select/field-select.d.ts +43 -0
- package/dist/particles/select/field-select.js +150 -0
- package/dist/particles/select/field-select.js.map +1 -0
- package/dist/particles/select/select.d.ts +2 -0
- package/dist/particles/select/select.js.map +1 -1
- package/dist/particles/shared/compose-refs.js +21 -0
- package/dist/particles/shared/compose-refs.js.map +1 -0
- package/dist/schema.graphql +701 -2
- package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
- package/dist/sdk/actions/analytics-action-provider.js +19 -0
- package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
- package/dist/sdk/actions/cart-action-provider.d.ts +1 -1
- package/dist/sdk/actions/cart-action-provider.js +1 -1
- package/dist/sdk/actions/cart-action-provider.js.map +1 -1
- package/dist/sdk/actions/index.d.ts +3 -1
- package/dist/sdk/actions/index.js +3 -1
- package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
- package/dist/sdk/actions/payment-action-provider.js +26 -0
- package/dist/sdk/actions/payment-action-provider.js.map +1 -0
- package/dist/sdk/actions/session-action-provider.d.ts +1 -1
- package/dist/sdk/actions/session-action-provider.js +1 -1
- package/dist/sdk/actions/session-action-provider.js.map +1 -1
- package/dist/sdk/actions/shipping-action-provider.d.ts +1 -1
- package/dist/sdk/actions/shipping-action-provider.js +1 -1
- package/dist/sdk/actions/shipping-action-provider.js.map +1 -1
- package/dist/sdk/actions/types.d.ts +6 -2
- package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
- package/dist/sdk/analytics/analytics-destination.js +90 -0
- package/dist/sdk/analytics/analytics-destination.js.map +1 -0
- package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
- package/dist/sdk/analytics/use-analytics-actions.js +108 -0
- package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
- package/dist/sdk/cache/storefront-cache.js +1 -1
- package/dist/sdk/cache/storefront-cache.js.map +1 -1
- package/dist/sdk/cart/operations.d.ts +4 -4
- package/dist/sdk/cart/operations.js +1 -1
- package/dist/sdk/cart/operations.js.map +1 -1
- package/dist/sdk/cart/optimistic.js +1 -1
- package/dist/sdk/cart/optimistic.js.map +1 -1
- package/dist/sdk/cart/use-cart-actions.d.ts +1 -1
- package/dist/sdk/cart/use-cart-actions.js +4 -4
- package/dist/sdk/cart/use-cart-actions.js.map +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js +1 -1
- package/dist/sdk/cart/use-cart-validate-on-open.js.map +1 -1
- package/dist/sdk/cart/write-gate.js +1 -1
- package/dist/sdk/cart/write-gate.js.map +1 -1
- package/dist/sdk/data/entity-resolution-context.js +31 -0
- package/dist/sdk/data/entity-resolution-context.js.map +1 -0
- package/dist/sdk/data/entity-subscriber.js +18 -8
- package/dist/sdk/data/entity-subscriber.js.map +1 -1
- package/dist/sdk/data/index.js +1 -0
- package/dist/sdk/data/page-reactive-data-root.d.ts +38 -2
- package/dist/sdk/data/page-reactive-data-root.js +4 -2
- package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
- package/dist/sdk/data/reactive-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-data-provider.js +8 -4
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.d.ts +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +13 -5
- package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
- package/dist/sdk/data/use-reactive-data-settled.js +39 -0
- package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
- package/dist/sdk/data/use-reactive-entity.js +6 -2
- package/dist/sdk/data/use-reactive-entity.js.map +1 -1
- package/dist/sdk/generated/fragment-types.js +2 -0
- package/dist/sdk/generated/fragment-types.js.map +1 -1
- package/dist/sdk/generated/gql.js +2 -1
- package/dist/sdk/generated/gql.js.map +1 -1
- package/dist/sdk/generated/graphql.js +85 -1
- package/dist/sdk/generated/graphql.js.map +1 -1
- package/dist/sdk/i18n/messages/en-US.js +3 -0
- package/dist/sdk/i18n/messages/en-US.json +3 -0
- package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
- package/dist/sdk/i18n/messages/es-ES.js +3 -0
- package/dist/sdk/i18n/messages/es-ES.json +3 -0
- package/dist/sdk/i18n/messages/pt-BR.js +3 -0
- package/dist/sdk/i18n/messages/pt-BR.json +3 -0
- package/dist/sdk/identity/operations.js.map +1 -1
- package/dist/sdk/identity/use-identity-actions.d.ts +1 -1
- package/dist/sdk/identity/use-identity-actions.js +3 -2
- package/dist/sdk/identity/use-identity-actions.js.map +1 -1
- package/dist/sdk/payment/gateway-origin.js +35 -0
- package/dist/sdk/payment/gateway-origin.js.map +1 -0
- package/dist/sdk/payment/gateway.d.ts +85 -0
- package/dist/sdk/payment/gateway.js +183 -0
- package/dist/sdk/payment/gateway.js.map +1 -0
- package/dist/sdk/payment/operations.d.ts +52 -0
- package/dist/sdk/payment/operations.js +223 -0
- package/dist/sdk/payment/operations.js.map +1 -0
- package/dist/sdk/payment/security-code-mask.js +71 -0
- package/dist/sdk/payment/security-code-mask.js.map +1 -0
- package/dist/sdk/payment/submission-registry.d.ts +33 -0
- package/dist/sdk/payment/submission-registry.js +58 -0
- package/dist/sdk/payment/submission-registry.js.map +1 -0
- package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
- package/dist/sdk/payment/use-payment-actions.js +276 -0
- package/dist/sdk/payment/use-payment-actions.js.map +1 -0
- package/dist/sdk/payment/use-place-order.d.ts +65 -0
- package/dist/sdk/payment/use-place-order.js +640 -0
- package/dist/sdk/payment/use-place-order.js.map +1 -0
- package/dist/sdk/session/auth-operations.d.ts +1 -1
- package/dist/sdk/session/auth-operations.js.map +1 -1
- package/dist/sdk/session/use-session-actions.d.ts +1 -1
- package/dist/sdk/session/use-session-actions.js.map +1 -1
- package/dist/sdk/session/use-session-revalidation.js +65 -0
- package/dist/sdk/session/use-session-revalidation.js.map +1 -0
- package/dist/sdk/shipping/option-select-operations.d.ts +1 -1
- package/dist/sdk/shipping/option-select-operations.js.map +1 -1
- package/dist/sdk/shipping/request-timeout.js +1 -1
- package/dist/sdk/shipping/request-timeout.js.map +1 -1
- package/dist/sdk/shipping/shipping-operations.d.ts +1 -1
- package/dist/sdk/shipping/shipping-operations.js.map +1 -1
- package/dist/sdk/shipping/use-shipping-actions.d.ts +2 -2
- package/dist/sdk/shipping/use-shipping-actions.js +1 -1
- package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
- package/dist/sdk/state/cart.d.ts +3 -4
- package/dist/sdk/state/cart.js +6 -7
- package/dist/sdk/state/cart.js.map +1 -1
- package/dist/utils/loggable-error-name.js +49 -0
- package/dist/utils/loggable-error-name.js.map +1 -0
- package/dist/utils/resolve-authored-copy.js +18 -0
- package/dist/utils/resolve-authored-copy.js.map +1 -0
- package/dist/utils/safe-href.js +3 -3
- package/dist/utils/safe-href.js.map +1 -1
- package/dist/utils/substitute-template.js +33 -0
- package/dist/utils/substitute-template.js.map +1 -0
- package/package.json +2 -1
- package/src/styles/tailwind.css +34 -0
- package/dist/docs/client-profile-form.md +0 -192
- package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
- package/dist/matter/client-profile-form/client-profile-form.js +0 -662
- package/dist/matter/client-profile-form/client-profile-form.js.map +0 -1
|
@@ -1,28 +1,52 @@
|
|
|
1
1
|
import { ComponentDefinition } from "../../core/dist/define-component.js";
|
|
2
2
|
import "../../core/dist/index.js";
|
|
3
3
|
import { applyMask } from "../shared/input-mask.js";
|
|
4
|
-
import { ComponentPropsWithRef, FocusEvent } from "react";
|
|
4
|
+
import { ComponentPropsWithRef, FocusEvent, ReactNode, Ref } from "react";
|
|
5
5
|
//#region src/particles/document-field/document-field.d.ts
|
|
6
6
|
export type DocumentType = 'CPF';
|
|
7
|
+
type DocumentFieldRequirement = 'mandatory' | 'optional';
|
|
7
8
|
export type DocumentSpec = {
|
|
8
9
|
id: DocumentType;
|
|
9
10
|
name: string;
|
|
10
11
|
mask: string;
|
|
11
12
|
validate: (value: string) => boolean;
|
|
12
13
|
};
|
|
13
|
-
export type DocumentFieldProps = Omit<ComponentPropsWithRef<'div'>, 'defaultValue' | 'value' | 'onChange' | 'onBlur'> & {
|
|
14
|
+
export type DocumentFieldProps = Omit<ComponentPropsWithRef<'div'>, 'defaultValue' | 'value' | 'onChange' | 'onBlur' | 'ref' | 'dangerouslySetInnerHTML'> & {
|
|
15
|
+
/** Unused on the bound path — react-hook-form owns the value. */
|
|
14
16
|
value?: string;
|
|
17
|
+
/** Unused on the bound path — the field contract's `defaultValue` seeds it instead. */
|
|
15
18
|
defaultValue?: string;
|
|
16
19
|
onValueChange?: (value: string) => void;
|
|
17
20
|
onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
|
|
18
21
|
invalid?: boolean;
|
|
19
22
|
errorMessage?: string;
|
|
23
|
+
/** Falls back to the field contract's `label` when bound. */
|
|
20
24
|
label?: string;
|
|
25
|
+
/** Falls back to the field contract's `placeholder` when bound. */
|
|
21
26
|
placeholder?: string;
|
|
27
|
+
/** Falls back to the field contract's `disabled` when bound. */
|
|
22
28
|
disabled?: boolean;
|
|
23
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Raises the field to required, the same word `PhoneField` uses. It only ever raises: bound to a
|
|
31
|
+
* fieldset whose rule already says required, `'optional'` does not lower it. Left unset it is no
|
|
32
|
+
* opinion, which keeps CPF optional by recommendation.
|
|
33
|
+
*/
|
|
34
|
+
requirement?: DocumentFieldRequirement;
|
|
24
35
|
name?: string;
|
|
25
36
|
documentType?: DocumentType;
|
|
37
|
+
/**
|
|
38
|
+
* Lands on the `<input>` `Field.Control` renders — not on the wrapper `<div>`, as it did before
|
|
39
|
+
* (a deliberate, source-breaking re-route: the previous ref pointed at an untabbable div). Composed
|
|
40
|
+
* with react-hook-form's own ref on the bound path.
|
|
41
|
+
*/
|
|
42
|
+
ref?: Ref<HTMLInputElement>;
|
|
43
|
+
/**
|
|
44
|
+
* Given, replaces the default `Field.Label` + `DocumentFieldInput` + `Field.Error` anatomy — any
|
|
45
|
+
* order, any subset. `DocumentFieldInput` is the one part that carries the mask, the composed ref
|
|
46
|
+
* and the particle's own value/handlers; a composed generic `Field.Control` would call
|
|
47
|
+
* `useController` a second time for the same name (RFC's named, accepted double-binder hole).
|
|
48
|
+
*/
|
|
49
|
+
children?: ReactNode;
|
|
26
50
|
};
|
|
27
51
|
/**
|
|
28
52
|
* CPF check-digit validation, ported from the legacy `validateCPF`. Strips non-digits, requires
|
|
@@ -31,7 +55,15 @@ export type DocumentFieldProps = Omit<ComponentPropsWithRef<'div'>, 'defaultValu
|
|
|
31
55
|
*/
|
|
32
56
|
export declare function validateCPF(value: string): boolean;
|
|
33
57
|
export declare const DOCUMENTS: Record<DocumentType, DocumentSpec>;
|
|
34
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* True when `type` is a document type this build supports (lets a host gate the field by locale).
|
|
60
|
+
*
|
|
61
|
+
* `Object.hasOwn`, not the `in` operator: `in` walks the prototype chain, so `constructor`,
|
|
62
|
+
* `__proto__` and `toString` all satisfied it and were then trusted as a `DocumentType` by callers
|
|
63
|
+
* (`client-profile-fieldset.tsx` renders the field on the strength of this narrowing). The value is
|
|
64
|
+
* author-only — a literal written into `page.json`, never a runtime expression — so this is
|
|
65
|
+
* robustness rather than a reachable defect, but the narrowing should not be able to lie.
|
|
66
|
+
*/
|
|
35
67
|
export declare function isSupportedDocumentType(type: unknown): type is DocumentType;
|
|
36
68
|
/**
|
|
37
69
|
* Apply a document type's mask to raw input: strip non-digits for digit-only masks, then truncate to
|
|
@@ -54,8 +86,19 @@ export declare const DocumentFieldDefinition: ComponentDefinition<DocumentFieldP
|
|
|
54
86
|
* when the check digit fails or when the host forces `invalid` (e.g. a server error surfaced by the
|
|
55
87
|
* form that owns the write). It consumes no GraphQL and no domain context — value flows through
|
|
56
88
|
* `value`/`defaultValue`/`onValueChange`, and `documentType` selects the mask + validator.
|
|
89
|
+
*
|
|
90
|
+
* Reads the field contract through `useField()` and `useFormContext()` and dispatches to a bound or an
|
|
91
|
+
* unbound sub-component — "bound" means both are present. Every react-hook-form hook lives in
|
|
92
|
+
* `BoundDocumentField`, so hook order never changes under a mounted component. Both sub-components
|
|
93
|
+
* render the same `DocumentFieldView`, so the two paths cannot drift into different DOM.
|
|
57
94
|
*/
|
|
58
|
-
export declare function DocumentField(
|
|
95
|
+
export declare function DocumentField(props: DocumentFieldProps): ReactNode;
|
|
96
|
+
export type DocumentFieldInputProps = {
|
|
97
|
+
className?: string;
|
|
98
|
+
};
|
|
99
|
+
export declare const DocumentFieldInputDefinition: ComponentDefinition<DocumentFieldInputProps>;
|
|
100
|
+
/** Renders the masked document `<input>`, reading its value/handlers/ref from the enclosing `DocumentField`. */
|
|
101
|
+
export declare function DocumentFieldInput({ className }: DocumentFieldInputProps): ReactNode;
|
|
59
102
|
//#endregion
|
|
60
103
|
export { applyMask };
|
|
61
104
|
//# sourceMappingURL=document-field.d.ts.map
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { defineComponent } from "../../core/dist/define-component.js";
|
|
3
3
|
import "../../core/dist/index.js";
|
|
4
|
-
import {
|
|
4
|
+
import { FieldBinding, TEXT_FIELD_VALUE, mergeFieldValidator, useBoundField, useField } from "../field/field.js";
|
|
5
|
+
import { useComposedRef } from "../shared/compose-refs.js";
|
|
5
6
|
import { applyMask, countSlots, isDigitOnly } from "../shared/input-mask.js";
|
|
6
|
-
import { useState } from "react";
|
|
7
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { createContext, useContext, useMemo, useState } from "react";
|
|
8
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
|
+
import { Field } from "@base-ui/react/field";
|
|
10
|
+
import { useFormContext, useFormState } from "react-hook-form";
|
|
8
11
|
//#region src/particles/document-field/document-field.tsx
|
|
9
12
|
/**
|
|
10
13
|
* CPF check-digit validation, ported from the legacy `validateCPF`. Strips non-digits, requires
|
|
@@ -24,9 +27,17 @@ const DOCUMENTS = { CPF: {
|
|
|
24
27
|
mask: "999.999.999-99",
|
|
25
28
|
validate: validateCPF
|
|
26
29
|
} };
|
|
27
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* True when `type` is a document type this build supports (lets a host gate the field by locale).
|
|
32
|
+
*
|
|
33
|
+
* `Object.hasOwn`, not the `in` operator: `in` walks the prototype chain, so `constructor`,
|
|
34
|
+
* `__proto__` and `toString` all satisfied it and were then trusted as a `DocumentType` by callers
|
|
35
|
+
* (`client-profile-fieldset.tsx` renders the field on the strength of this narrowing). The value is
|
|
36
|
+
* author-only — a literal written into `page.json`, never a runtime expression — so this is
|
|
37
|
+
* robustness rather than a reachable defect, but the narrowing should not be able to lie.
|
|
38
|
+
*/
|
|
28
39
|
function isSupportedDocumentType(type) {
|
|
29
|
-
return typeof type === "string" && type
|
|
40
|
+
return typeof type === "string" && Object.hasOwn(DOCUMENTS, type);
|
|
30
41
|
}
|
|
31
42
|
/**
|
|
32
43
|
* Apply a document type's mask to raw input: strip non-digits for digit-only masks, then truncate to
|
|
@@ -52,6 +63,7 @@ const DocumentFieldDefinition = defineComponent({
|
|
|
52
63
|
name: "DocumentField",
|
|
53
64
|
importPath: "@vtex/faststore/components",
|
|
54
65
|
component: DocumentField,
|
|
66
|
+
providesContext: "document-field",
|
|
55
67
|
editor: {
|
|
56
68
|
label: "Document Field",
|
|
57
69
|
category: "content"
|
|
@@ -74,10 +86,9 @@ const DocumentFieldDefinition = defineComponent({
|
|
|
74
86
|
default: "CPF",
|
|
75
87
|
label: "Document type"
|
|
76
88
|
},
|
|
77
|
-
|
|
78
|
-
type: "
|
|
79
|
-
|
|
80
|
-
label: "Required"
|
|
89
|
+
requirement: {
|
|
90
|
+
type: ["mandatory", "optional"],
|
|
91
|
+
label: "Requirement"
|
|
81
92
|
},
|
|
82
93
|
disabled: {
|
|
83
94
|
type: "boolean",
|
|
@@ -85,7 +96,7 @@ const DocumentFieldDefinition = defineComponent({
|
|
|
85
96
|
label: "Disabled"
|
|
86
97
|
}
|
|
87
98
|
},
|
|
88
|
-
guideline: "Presentational tax-document input composing the Field particle; applies the locale mask on change and runs check-digit validation on blur/submit. Data-agnostic (value/defaultValue/onValueChange); a host wires it to the shopper profile. CPF is optional by default."
|
|
99
|
+
guideline: "Presentational tax-document input composing the Field particle; applies the locale mask on change and runs check-digit validation on blur/submit. Data-agnostic (value/defaultValue/onValueChange); a host wires it to the shopper profile. CPF is optional by default: requirement only ever raises the field to required, announced as aria-required, and bound to a fieldset whose rule already says required it cannot lower it back. Recomposable (RFC:503): given children, they replace the default Field.Label + DocumentFieldInput + Field.Error anatomy, in any order."
|
|
89
100
|
});
|
|
90
101
|
/**
|
|
91
102
|
* Presentational tax-document input (particle). Composes the `Field` particle (`Field.Root` +
|
|
@@ -95,8 +106,104 @@ const DocumentFieldDefinition = defineComponent({
|
|
|
95
106
|
* when the check digit fails or when the host forces `invalid` (e.g. a server error surfaced by the
|
|
96
107
|
* form that owns the write). It consumes no GraphQL and no domain context — value flows through
|
|
97
108
|
* `value`/`defaultValue`/`onValueChange`, and `documentType` selects the mask + validator.
|
|
109
|
+
*
|
|
110
|
+
* Reads the field contract through `useField()` and `useFormContext()` and dispatches to a bound or an
|
|
111
|
+
* unbound sub-component — "bound" means both are present. Every react-hook-form hook lives in
|
|
112
|
+
* `BoundDocumentField`, so hook order never changes under a mounted component. Both sub-components
|
|
113
|
+
* render the same `DocumentFieldView`, so the two paths cannot drift into different DOM.
|
|
98
114
|
*/
|
|
99
|
-
function DocumentField(
|
|
115
|
+
function DocumentField(props) {
|
|
116
|
+
const contract = useField();
|
|
117
|
+
const formContext = useFormContext();
|
|
118
|
+
if (contract && formContext) return /* @__PURE__ */ jsx(BoundDocumentField, {
|
|
119
|
+
...props,
|
|
120
|
+
contract
|
|
121
|
+
});
|
|
122
|
+
return /* @__PURE__ */ jsx(UnboundDocumentField, { ...props });
|
|
123
|
+
}
|
|
124
|
+
const DocumentFieldInputContext = createContext(null);
|
|
125
|
+
function useDocumentFieldInputContext() {
|
|
126
|
+
const context = useContext(DocumentFieldInputContext);
|
|
127
|
+
if (!context) throw new Error("DocumentFieldInput must be a descendant of DocumentField.");
|
|
128
|
+
return context;
|
|
129
|
+
}
|
|
130
|
+
const DocumentFieldInputDefinition = defineComponent({
|
|
131
|
+
name: "DocumentFieldInput",
|
|
132
|
+
importPath: "@vtex/faststore/components",
|
|
133
|
+
component: DocumentFieldInput,
|
|
134
|
+
requiresContext: "document-field",
|
|
135
|
+
editor: {
|
|
136
|
+
label: "Document Field Input",
|
|
137
|
+
category: "action"
|
|
138
|
+
},
|
|
139
|
+
schema: {},
|
|
140
|
+
guideline: "The masked document <input> DocumentField owns. Must sit inside DocumentField, in either the default anatomy or an author's composed children. Carries the locale mask, the composed ref, and DocumentField's own onChange/onBlur — a composed Field.Control here instead re-registers the field with react-hook-form and loses the mask (unsupported, RFC's named double-binder hole)."
|
|
141
|
+
});
|
|
142
|
+
/** Renders the masked document `<input>`, reading its value/handlers/ref from the enclosing `DocumentField`. */
|
|
143
|
+
function DocumentFieldInput({ className }) {
|
|
144
|
+
const { ref, name, value, placeholder, disabled, required, numericOnly, onChange, onBlur } = useDocumentFieldInputContext();
|
|
145
|
+
return /* @__PURE__ */ jsx(Field.Control, {
|
|
146
|
+
ref,
|
|
147
|
+
name,
|
|
148
|
+
"aria-required": required || void 0,
|
|
149
|
+
placeholder,
|
|
150
|
+
disabled,
|
|
151
|
+
value,
|
|
152
|
+
...numericOnly ? { inputMode: "numeric" } : {},
|
|
153
|
+
onChange,
|
|
154
|
+
onBlur,
|
|
155
|
+
className,
|
|
156
|
+
"data-fs-field-control": ""
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
function DocumentFieldView({ className, name, label, placeholder, disabled, required, numericOnly, displayValue, showInvalid, errorMessage, onChange, onBlur, ref, children, ...props }) {
|
|
160
|
+
const inputContext = useMemo(() => ({
|
|
161
|
+
ref,
|
|
162
|
+
name,
|
|
163
|
+
value: displayValue,
|
|
164
|
+
placeholder,
|
|
165
|
+
disabled,
|
|
166
|
+
required,
|
|
167
|
+
numericOnly,
|
|
168
|
+
onChange,
|
|
169
|
+
onBlur
|
|
170
|
+
}), [
|
|
171
|
+
ref,
|
|
172
|
+
name,
|
|
173
|
+
displayValue,
|
|
174
|
+
placeholder,
|
|
175
|
+
disabled,
|
|
176
|
+
required,
|
|
177
|
+
numericOnly,
|
|
178
|
+
onChange,
|
|
179
|
+
onBlur
|
|
180
|
+
]);
|
|
181
|
+
return /* @__PURE__ */ jsx(Field.Root, {
|
|
182
|
+
...props,
|
|
183
|
+
className,
|
|
184
|
+
name,
|
|
185
|
+
invalid: showInvalid,
|
|
186
|
+
disabled,
|
|
187
|
+
"data-fs-document-field": "",
|
|
188
|
+
"data-fs-field-root": "",
|
|
189
|
+
children: /* @__PURE__ */ jsx(DocumentFieldInputContext.Provider, {
|
|
190
|
+
value: inputContext,
|
|
191
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
192
|
+
label ? /* @__PURE__ */ jsx(Field.Label, {
|
|
193
|
+
"data-fs-field-label": "",
|
|
194
|
+
children: label
|
|
195
|
+
}) : null,
|
|
196
|
+
/* @__PURE__ */ jsx(DocumentFieldInput, {}),
|
|
197
|
+
showInvalid && errorMessage ? /* @__PURE__ */ jsx(Field.Error, {
|
|
198
|
+
match: true,
|
|
199
|
+
"data-fs-field-error": "",
|
|
200
|
+
children: errorMessage
|
|
201
|
+
}) : null
|
|
202
|
+
] })
|
|
203
|
+
})
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function UnboundDocumentField({ value, defaultValue = "", onValueChange, onBlur, invalid = false, errorMessage, label, placeholder, disabled = false, requirement, name = "document", documentType = "CPF", className, ref, children, ...props }) {
|
|
100
207
|
const isControlled = value !== void 0;
|
|
101
208
|
const [internalValue, setInternalValue] = useState(() => maskDocumentValue(defaultValue, documentType));
|
|
102
209
|
const [checkInvalid, setCheckInvalid] = useState(false);
|
|
@@ -114,33 +221,83 @@ function DocumentField({ value, defaultValue = "", onValueChange, onBlur, invali
|
|
|
114
221
|
setCheckInvalid(!isValidDocument(currentValue, documentType));
|
|
115
222
|
onBlur?.(event);
|
|
116
223
|
}
|
|
117
|
-
return /* @__PURE__ */
|
|
224
|
+
return /* @__PURE__ */ jsx(DocumentFieldView, {
|
|
118
225
|
...props,
|
|
226
|
+
ref,
|
|
119
227
|
className,
|
|
120
228
|
name,
|
|
121
|
-
|
|
229
|
+
label,
|
|
230
|
+
placeholder,
|
|
122
231
|
disabled,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
232
|
+
required: requirement === "mandatory",
|
|
233
|
+
numericOnly,
|
|
234
|
+
displayValue,
|
|
235
|
+
showInvalid,
|
|
236
|
+
errorMessage,
|
|
237
|
+
onChange: handleChange,
|
|
238
|
+
onBlur: handleBlur,
|
|
239
|
+
children
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function BoundDocumentField({ value: _value, defaultValue: _defaultValue, onValueChange, onBlur, invalid = false, errorMessage, label, placeholder, disabled, requirement, name, documentType = "CPF", className, ref: callerRef, contract, children, ...props }) {
|
|
243
|
+
const rule = useMemo(() => mergeFieldValidator(contract.rule, "checkDigit", (rawValue) => {
|
|
244
|
+
return isValidDocument(typeof rawValue === "string" ? rawValue : String(rawValue ?? ""), documentType) || (errorMessage ?? false);
|
|
245
|
+
}), [
|
|
246
|
+
contract.rule,
|
|
247
|
+
documentType,
|
|
248
|
+
errorMessage
|
|
249
|
+
]);
|
|
250
|
+
const field = useBoundField(contract, TEXT_FIELD_VALUE, rule, { required: requirement === "mandatory" });
|
|
251
|
+
const { getFieldState } = useFormContext();
|
|
252
|
+
const { invalid: rhfInvalid } = getFieldState(contract.name, useFormState({ name: contract.name }));
|
|
253
|
+
const composedRef = useComposedRef(callerRef, field.ref);
|
|
254
|
+
const displayValue = maskDocumentValue(typeof field.value === "string" ? field.value : String(field.value ?? ""), documentType);
|
|
255
|
+
const showInvalid = invalid || rhfInvalid;
|
|
256
|
+
const numericOnly = isDigitOnly(DOCUMENTS[documentType].mask);
|
|
257
|
+
const resolvedLabel = label ?? contract.label;
|
|
258
|
+
const resolvedPlaceholder = placeholder ?? contract.placeholder;
|
|
259
|
+
const resolvedDisabled = disabled ?? contract.disabled ?? false;
|
|
260
|
+
const resolvedName = name ?? contract.name;
|
|
261
|
+
function handleChange(event) {
|
|
262
|
+
const masked = maskDocumentValue(event.target.value, documentType);
|
|
263
|
+
field.onChange(masked);
|
|
264
|
+
onValueChange?.(masked);
|
|
265
|
+
}
|
|
266
|
+
function handleBlur(event) {
|
|
267
|
+
field.onBlur();
|
|
268
|
+
onBlur?.(event);
|
|
269
|
+
}
|
|
270
|
+
return /* @__PURE__ */ jsx(FieldBinding, {
|
|
271
|
+
name: contract.name,
|
|
272
|
+
label: resolvedLabel,
|
|
273
|
+
placeholder: resolvedPlaceholder,
|
|
274
|
+
defaultValue: contract.defaultValue,
|
|
275
|
+
rule: contract.rule,
|
|
276
|
+
options: contract.options,
|
|
277
|
+
disabled: resolvedDisabled,
|
|
278
|
+
readOnly: contract.readOnly,
|
|
279
|
+
autoComplete: contract.autoComplete,
|
|
280
|
+
inputMode: contract.inputMode,
|
|
281
|
+
children: /* @__PURE__ */ jsx(DocumentFieldView, {
|
|
282
|
+
...props,
|
|
283
|
+
ref: composedRef,
|
|
284
|
+
className,
|
|
285
|
+
name: resolvedName,
|
|
286
|
+
label: resolvedLabel,
|
|
287
|
+
placeholder: resolvedPlaceholder,
|
|
288
|
+
disabled: resolvedDisabled,
|
|
289
|
+
required: field.required,
|
|
290
|
+
numericOnly,
|
|
291
|
+
displayValue,
|
|
292
|
+
showInvalid,
|
|
293
|
+
errorMessage,
|
|
294
|
+
onChange: handleChange,
|
|
295
|
+
onBlur: handleBlur,
|
|
296
|
+
children
|
|
297
|
+
})
|
|
141
298
|
});
|
|
142
299
|
}
|
|
143
300
|
//#endregion
|
|
144
|
-
export { DOCUMENTS, DocumentField, DocumentFieldDefinition, applyMask, isSupportedDocumentType, isValidDocument, maskDocumentValue, validateCPF };
|
|
301
|
+
export { DOCUMENTS, DocumentField, DocumentFieldDefinition, DocumentFieldInput, DocumentFieldInputDefinition, applyMask, isSupportedDocumentType, isValidDocument, maskDocumentValue, validateCPF };
|
|
145
302
|
|
|
146
303
|
//# sourceMappingURL=document-field.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-field.js","names":[],"sources":["../../../src/particles/document-field/document-field.tsx"],"sourcesContent":["'use client';\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ChangeEvent, ComponentPropsWithRef, FocusEvent } from 'react';\nimport { useState } from 'react';\nimport { Field } from '../field/field.js';\nimport { applyMask, countSlots, isDigitOnly } from '../shared/input-mask.js';\n\n// `applyMask` was public from this module before the engine moved out; keep the export path stable.\nexport { applyMask } from '../shared/input-mask.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type DocumentType = 'CPF';\n\nexport type DocumentSpec = {\n id: DocumentType;\n name: string;\n mask: string;\n validate: (value: string) => boolean;\n};\n\nexport type DocumentFieldProps = Omit<\n ComponentPropsWithRef<'div'>,\n 'defaultValue' | 'value' | 'onChange' | 'onBlur'\n> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n onBlur?: (event: FocusEvent<HTMLInputElement>) => void;\n invalid?: boolean;\n errorMessage?: string;\n label?: string;\n placeholder?: string;\n disabled?: boolean;\n required?: boolean;\n name?: string;\n documentType?: DocumentType;\n};\n\n// ---------------------------------------------------------------------------\n// Ported check-digit validation (fastcheckout `packages/libs/validators/src/document`).\n// ---------------------------------------------------------------------------\n\n/**\n * CPF check-digit validation, ported from the legacy `validateCPF`. Strips non-digits, requires\n * exactly 11 digits, rejects all-equal sequences (e.g. `111.111.111-11`), and verifies both check\n * digits. Runs on blur/submit only, never per keystroke.\n */\nexport function validateCPF(value: string): boolean {\n const digits = value.replace(/[^\\d]+/g, '');\n\n if (digits.length !== 11 || /(\\d)\\1{10}/.test(digits)) {\n return false;\n }\n\n const values = digits.split('').map((el) => +el);\n const rest = (count: number) =>\n ((values.slice(0, count - 12).reduce((sum, el, index) => sum + el * (count - index), 0) * 10) % 11) % 10;\n\n return rest(10) === values[9] && rest(11) === values[10];\n}\n\n// ---------------------------------------------------------------------------\n// Document table (subset needed for the CPF launch). `documentType` selects the mask +\n// validator + digit-only input mode. Extendable later (SSN | SIN | CURP) without touching callers.\n// ---------------------------------------------------------------------------\n\nexport const DOCUMENTS: Record<DocumentType, DocumentSpec> = {\n CPF: {\n id: 'CPF',\n name: 'CPF',\n mask: '999.999.999-99',\n validate: validateCPF,\n },\n};\n\n/** True when `type` is a document type this build supports (lets a host gate the field by locale). */\nexport function isSupportedDocumentType(type: unknown): type is DocumentType {\n return typeof type === 'string' && type in DOCUMENTS;\n}\n\n/**\n * Apply a document type's mask to raw input: strip non-digits for digit-only masks, then truncate to\n * the mask's slot count so excess characters never trail past the mask (e.g. a 15-digit paste into\n * the 11-slot CPF mask yields `111.444.777-35`, not `111.444.777-358888`).\n */\nexport function maskDocumentValue(rawValue: string, documentType: DocumentType): string {\n const spec = DOCUMENTS[documentType];\n const sanitized = isDigitOnly(spec.mask) ? rawValue.replace(/\\D/g, '') : rawValue;\n const maxSlots = Math.max(...spec.mask.split('|').map(countSlots));\n return applyMask(sanitized.slice(0, maxSlots), spec.mask);\n}\n\n/**\n * Check-digit validity for a document value. An **empty** value is valid (the field is optional),\n * mirroring the legacy `validateCardholderDocument`; a non-empty value must pass the document\n * type's check-digit validation.\n */\nexport function isValidDocument(value: string, documentType: DocumentType): boolean {\n if (!value.trim()) return true;\n return DOCUMENTS[documentType].validate(value);\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const DocumentFieldDefinition = defineComponent<DocumentFieldProps>({\n name: 'DocumentField',\n importPath: '@vtex/faststore/components',\n component: DocumentField,\n editor: { label: 'Document Field', category: 'content' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n errorMessage: { type: 'string', label: 'Error message' },\n documentType: { type: 'string', default: 'CPF', label: 'Document type' },\n required: { type: 'boolean', default: false, label: 'Required' },\n disabled: { type: 'boolean', default: false, label: 'Disabled' },\n },\n guideline:\n 'Presentational tax-document input composing the Field particle; applies the locale mask on change and runs check-digit validation on blur/submit. Data-agnostic (value/defaultValue/onValueChange); a host wires it to the shopper profile. CPF is optional by default.',\n});\n\n/**\n * Presentational tax-document input (particle). Composes the `Field` particle (`Field.Root` +\n * `Field.Label` + `Field.Control` + `Field.Error`) and owns the locale mask (applied progressively on\n * every change, never blocking typing) plus check-digit validation (run on blur/submit only — never\n * per keystroke). An empty value is valid (the field is optional). It surfaces `errorMessage` inline\n * when the check digit fails or when the host forces `invalid` (e.g. a server error surfaced by the\n * form that owns the write). It consumes no GraphQL and no domain context — value flows through\n * `value`/`defaultValue`/`onValueChange`, and `documentType` selects the mask + validator.\n */\nexport function DocumentField({\n value,\n defaultValue = '',\n onValueChange,\n onBlur,\n invalid = false,\n errorMessage,\n label,\n placeholder,\n disabled = false,\n required = false,\n name = 'document',\n documentType = 'CPF',\n className,\n ...props\n}: DocumentFieldProps) {\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState(() => maskDocumentValue(defaultValue, documentType));\n const [checkInvalid, setCheckInvalid] = useState(false);\n\n const currentValue = isControlled ? value : internalValue;\n // Masking is idempotent, so masking here formats both a raw pre-fill (a controlled `value` seeded\n // from the saved profile) and an already-masked typed value identically.\n const displayValue = maskDocumentValue(currentValue, documentType);\n const showInvalid = invalid || checkInvalid;\n const numericOnly = isDigitOnly(DOCUMENTS[documentType].mask);\n\n function handleChange(event: ChangeEvent<HTMLInputElement>) {\n const masked = maskDocumentValue(event.target.value, documentType);\n if (!isControlled) setInternalValue(masked);\n // Clear a prior check-digit error while typing; it is re-evaluated on the next blur.\n if (checkInvalid) setCheckInvalid(false);\n onValueChange?.(masked);\n }\n\n function handleBlur(event: FocusEvent<HTMLInputElement>) {\n // Validation runs only after the complete number is entered; empty is valid (optional field).\n setCheckInvalid(!isValidDocument(currentValue, documentType));\n onBlur?.(event);\n }\n\n return (\n <Field.Root\n {...props}\n className={className}\n name={name}\n invalid={showInvalid}\n disabled={disabled}\n data-fs-document-field=\"\"\n >\n {label ? <Field.Label>{label}</Field.Label> : null}\n <Field.Control\n name={name}\n required={required}\n placeholder={placeholder}\n disabled={disabled}\n value={displayValue}\n {...(numericOnly ? { inputMode: 'numeric' as const } : {})}\n onChange={handleChange}\n onBlur={handleBlur}\n />\n {showInvalid && errorMessage ? <Field.Error match>{errorMessage}</Field.Error> : null}\n </Field.Root>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"document-field.js","names":["BaseField"],"sources":["../../../src/particles/document-field/document-field.tsx"],"sourcesContent":["'use client';\nimport { Field as BaseField } from '@base-ui/react/field';\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ChangeEvent, ComponentPropsWithRef, FocusEvent, ReactNode, Ref } from 'react';\nimport { createContext, useContext, useMemo, useState } from 'react';\nimport { useFormContext, useFormState } from 'react-hook-form';\nimport {\n FieldBinding,\n type FieldContract,\n type FieldRule,\n mergeFieldValidator,\n TEXT_FIELD_VALUE,\n useBoundField,\n useField,\n} from '../field/field.js';\nimport { useComposedRef } from '../shared/compose-refs.js';\nimport { applyMask, countSlots, isDigitOnly } from '../shared/input-mask.js';\n\n// `applyMask` was public from this module before the engine moved out; keep the export path stable.\nexport { applyMask } from '../shared/input-mask.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type DocumentType = 'CPF';\n\ntype DocumentFieldRequirement = 'mandatory' | 'optional';\n\nexport type DocumentSpec = {\n id: DocumentType;\n name: string;\n mask: string;\n validate: (value: string) => boolean;\n};\n\n// `dangerouslySetInnerHTML` is excluded — the rest spread this props type feeds forwards straight to\n// `Field.Root`, and a schema-authored `page.json` can supply it with no allowlist in between.\nexport type DocumentFieldProps = Omit<\n ComponentPropsWithRef<'div'>,\n 'defaultValue' | 'value' | 'onChange' | 'onBlur' | 'ref' | 'dangerouslySetInnerHTML'\n> & {\n /** Unused on the bound path — react-hook-form owns the value. */\n value?: string;\n /** Unused on the bound path — the field contract's `defaultValue` seeds it instead. */\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n onBlur?: (event: FocusEvent<HTMLInputElement>) => void;\n invalid?: boolean;\n errorMessage?: string;\n /** Falls back to the field contract's `label` when bound. */\n label?: string;\n /** Falls back to the field contract's `placeholder` when bound. */\n placeholder?: string;\n /** Falls back to the field contract's `disabled` when bound. */\n disabled?: boolean;\n /**\n * Raises the field to required, the same word `PhoneField` uses. It only ever raises: bound to a\n * fieldset whose rule already says required, `'optional'` does not lower it. Left unset it is no\n * opinion, which keeps CPF optional by recommendation.\n */\n requirement?: DocumentFieldRequirement;\n name?: string;\n documentType?: DocumentType;\n /**\n * Lands on the `<input>` `Field.Control` renders — not on the wrapper `<div>`, as it did before\n * (a deliberate, source-breaking re-route: the previous ref pointed at an untabbable div). Composed\n * with react-hook-form's own ref on the bound path.\n */\n ref?: Ref<HTMLInputElement>;\n /**\n * Given, replaces the default `Field.Label` + `DocumentFieldInput` + `Field.Error` anatomy — any\n * order, any subset. `DocumentFieldInput` is the one part that carries the mask, the composed ref\n * and the particle's own value/handlers; a composed generic `Field.Control` would call\n * `useController` a second time for the same name (RFC's named, accepted double-binder hole).\n */\n children?: ReactNode;\n};\n\n// ---------------------------------------------------------------------------\n// Ported check-digit validation (fastcheckout `packages/libs/validators/src/document`).\n// ---------------------------------------------------------------------------\n\n/**\n * CPF check-digit validation, ported from the legacy `validateCPF`. Strips non-digits, requires\n * exactly 11 digits, rejects all-equal sequences (e.g. `111.111.111-11`), and verifies both check\n * digits. Runs on blur/submit only, never per keystroke.\n */\nexport function validateCPF(value: string): boolean {\n const digits = value.replace(/[^\\d]+/g, '');\n\n if (digits.length !== 11 || /(\\d)\\1{10}/.test(digits)) {\n return false;\n }\n\n const values = digits.split('').map((el) => +el);\n const rest = (count: number) =>\n ((values.slice(0, count - 12).reduce((sum, el, index) => sum + el * (count - index), 0) * 10) % 11) % 10;\n\n return rest(10) === values[9] && rest(11) === values[10];\n}\n\n// ---------------------------------------------------------------------------\n// Document table (subset needed for the CPF launch). `documentType` selects the mask +\n// validator + digit-only input mode. Extendable later (SSN | SIN | CURP) without touching callers.\n// ---------------------------------------------------------------------------\n\nexport const DOCUMENTS: Record<DocumentType, DocumentSpec> = {\n CPF: {\n id: 'CPF',\n name: 'CPF',\n mask: '999.999.999-99',\n validate: validateCPF,\n },\n};\n\n/**\n * True when `type` is a document type this build supports (lets a host gate the field by locale).\n *\n * `Object.hasOwn`, not the `in` operator: `in` walks the prototype chain, so `constructor`,\n * `__proto__` and `toString` all satisfied it and were then trusted as a `DocumentType` by callers\n * (`client-profile-fieldset.tsx` renders the field on the strength of this narrowing). The value is\n * author-only — a literal written into `page.json`, never a runtime expression — so this is\n * robustness rather than a reachable defect, but the narrowing should not be able to lie.\n */\nexport function isSupportedDocumentType(type: unknown): type is DocumentType {\n return typeof type === 'string' && Object.hasOwn(DOCUMENTS, type);\n}\n\n/**\n * Apply a document type's mask to raw input: strip non-digits for digit-only masks, then truncate to\n * the mask's slot count so excess characters never trail past the mask (e.g. a 15-digit paste into\n * the 11-slot CPF mask yields `111.444.777-35`, not `111.444.777-358888`).\n */\nexport function maskDocumentValue(rawValue: string, documentType: DocumentType): string {\n const spec = DOCUMENTS[documentType];\n const sanitized = isDigitOnly(spec.mask) ? rawValue.replace(/\\D/g, '') : rawValue;\n const maxSlots = Math.max(...spec.mask.split('|').map(countSlots));\n return applyMask(sanitized.slice(0, maxSlots), spec.mask);\n}\n\n/**\n * Check-digit validity for a document value. An **empty** value is valid (the field is optional),\n * mirroring the legacy `validateCardholderDocument`; a non-empty value must pass the document\n * type's check-digit validation.\n */\nexport function isValidDocument(value: string, documentType: DocumentType): boolean {\n if (!value.trim()) return true;\n return DOCUMENTS[documentType].validate(value);\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport const DocumentFieldDefinition = defineComponent<DocumentFieldProps>({\n name: 'DocumentField',\n importPath: '@vtex/faststore/components',\n component: DocumentField,\n providesContext: 'document-field',\n editor: { label: 'Document Field', category: 'content' },\n schema: {\n label: { type: 'string', label: 'Label' },\n placeholder: { type: 'string', label: 'Placeholder' },\n errorMessage: { type: 'string', label: 'Error message' },\n documentType: { type: 'string', default: 'CPF', label: 'Document type' },\n requirement: { type: ['mandatory', 'optional'] as const, label: 'Requirement' },\n disabled: { type: 'boolean', default: false, label: 'Disabled' },\n },\n guideline:\n 'Presentational tax-document input composing the Field particle; applies the locale mask on change and runs check-digit validation on blur/submit. Data-agnostic (value/defaultValue/onValueChange); a host wires it to the shopper profile. CPF is optional by default: requirement only ever raises the field to required, announced as aria-required, and bound to a fieldset whose rule already says required it cannot lower it back. Recomposable (RFC:503): given children, they replace the default Field.Label + DocumentFieldInput + Field.Error anatomy, in any order.',\n});\n\n/**\n * Presentational tax-document input (particle). Composes the `Field` particle (`Field.Root` +\n * `Field.Label` + `Field.Control` + `Field.Error`) and owns the locale mask (applied progressively on\n * every change, never blocking typing) plus check-digit validation (run on blur/submit only — never\n * per keystroke). An empty value is valid (the field is optional). It surfaces `errorMessage` inline\n * when the check digit fails or when the host forces `invalid` (e.g. a server error surfaced by the\n * form that owns the write). It consumes no GraphQL and no domain context — value flows through\n * `value`/`defaultValue`/`onValueChange`, and `documentType` selects the mask + validator.\n *\n * Reads the field contract through `useField()` and `useFormContext()` and dispatches to a bound or an\n * unbound sub-component — \"bound\" means both are present. Every react-hook-form hook lives in\n * `BoundDocumentField`, so hook order never changes under a mounted component. Both sub-components\n * render the same `DocumentFieldView`, so the two paths cannot drift into different DOM.\n */\nexport function DocumentField(props: DocumentFieldProps): ReactNode {\n const contract = useField();\n const formContext = useFormContext();\n\n if (contract && formContext) {\n return <BoundDocumentField {...props} contract={contract} />;\n }\n\n return <UnboundDocumentField {...props} />;\n}\n\n// ---------------------------------------------------------------------------\n// Input part — catalogued, placeable (Decision 4). Carries the mask, the composed ref, and the\n// particle's own onChange/onBlur. Renders the RAW Base UI Field.Control, not this package's own\n// Field.Control wrapper — the latter would see the real ambient contract Decision 3 now provides\n// and call useController a second time for the same name, overwriting the composite's masked value\n// (the exact double-registration bug the removed FieldContractBoundary used to prevent globally).\n// ---------------------------------------------------------------------------\n\ntype DocumentFieldInputContextValue = {\n ref?: Ref<HTMLInputElement>;\n name?: string;\n value: string;\n placeholder?: string;\n disabled: boolean;\n /** Resolved by the binder, threaded here because this part is not the one that binds. */\n required: boolean;\n numericOnly: boolean;\n onChange: (event: ChangeEvent<HTMLInputElement>) => void;\n onBlur: (event: FocusEvent<HTMLInputElement>) => void;\n};\n\nconst DocumentFieldInputContext = createContext<DocumentFieldInputContextValue | null>(null);\n\nfunction useDocumentFieldInputContext(): DocumentFieldInputContextValue {\n const context = useContext(DocumentFieldInputContext);\n if (!context) {\n throw new Error('DocumentFieldInput must be a descendant of DocumentField.');\n }\n return context;\n}\n\nexport type DocumentFieldInputProps = { className?: string };\n\nexport const DocumentFieldInputDefinition = defineComponent<DocumentFieldInputProps>({\n name: 'DocumentFieldInput',\n importPath: '@vtex/faststore/components',\n component: DocumentFieldInput,\n requiresContext: 'document-field',\n editor: { label: 'Document Field Input', category: 'action' },\n schema: {},\n guideline:\n \"The masked document <input> DocumentField owns. Must sit inside DocumentField, in either the default anatomy or an author's composed children. Carries the locale mask, the composed ref, and DocumentField's own onChange/onBlur — a composed Field.Control here instead re-registers the field with react-hook-form and loses the mask (unsupported, RFC's named double-binder hole).\",\n});\n\n/** Renders the masked document `<input>`, reading its value/handlers/ref from the enclosing `DocumentField`. */\nexport function DocumentFieldInput({ className }: DocumentFieldInputProps): ReactNode {\n const { ref, name, value, placeholder, disabled, required, numericOnly, onChange, onBlur } =\n useDocumentFieldInputContext();\n\n return (\n <BaseField.Control\n ref={ref}\n name={name}\n // `aria-required`, not native `required`, for the reason password-field.tsx:89-91 records:\n // this is a Base UI `Field.Control`, and native constraint tracking would flip `aria-invalid`\n // on its own, ahead of react-hook-form's own validation.\n aria-required={required || undefined}\n placeholder={placeholder}\n disabled={disabled}\n value={value}\n {...(numericOnly ? { inputMode: 'numeric' as const } : {})}\n onChange={onChange}\n onBlur={onBlur}\n className={className}\n data-fs-field-control=\"\"\n />\n );\n}\n\n// ---------------------------------------------------------------------------\n// Shared view — identical DOM whether bound or unbound\n// ---------------------------------------------------------------------------\n\ntype DocumentFieldViewProps = Omit<\n ComponentPropsWithRef<'div'>,\n 'defaultValue' | 'value' | 'onChange' | 'onBlur' | 'ref'\n> & {\n ref?: Ref<HTMLInputElement>;\n name?: string;\n label?: string;\n placeholder?: string;\n disabled: boolean;\n required: boolean;\n numericOnly: boolean;\n displayValue: string;\n showInvalid: boolean;\n errorMessage?: string;\n onChange: (event: ChangeEvent<HTMLInputElement>) => void;\n onBlur: (event: FocusEvent<HTMLInputElement>) => void;\n children?: ReactNode;\n};\n\nfunction DocumentFieldView({\n className,\n name,\n label,\n placeholder,\n disabled,\n required,\n numericOnly,\n displayValue,\n showInvalid,\n errorMessage,\n onChange,\n onBlur,\n ref,\n children,\n ...props\n}: DocumentFieldViewProps) {\n const inputContext = useMemo<DocumentFieldInputContextValue>(\n () => ({ ref, name, value: displayValue, placeholder, disabled, required, numericOnly, onChange, onBlur }),\n [ref, name, displayValue, placeholder, disabled, required, numericOnly, onChange, onBlur],\n );\n\n return (\n // Deliberately the RAW Base UI Field.Root/Label/Error, not this package's own Field.* wrapper,\n // for the default anatomy — reproduces exactly what rendered before FieldContractBoundary was\n // removed (Decision 3), including the host `invalid`/`errorMessage` override working\n // unconditionally. Composed children, below, use the package's own Field.* parts on purpose —\n // that is what makes a childless Field.Label / Field.Error resolve real text (FR-5).\n <BaseField.Root\n {...props}\n className={className}\n name={name}\n invalid={showInvalid}\n disabled={disabled}\n data-fs-document-field=\"\"\n data-fs-field-root=\"\"\n >\n <DocumentFieldInputContext.Provider value={inputContext}>\n {children ?? (\n <>\n {label ? <BaseField.Label data-fs-field-label=\"\">{label}</BaseField.Label> : null}\n <DocumentFieldInput />\n {showInvalid && errorMessage ? (\n <BaseField.Error match data-fs-field-error=\"\">\n {errorMessage}\n </BaseField.Error>\n ) : null}\n </>\n )}\n </DocumentFieldInputContext.Provider>\n </BaseField.Root>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Unbound — today's body, moved verbatim\n// ---------------------------------------------------------------------------\n\nfunction UnboundDocumentField({\n value,\n defaultValue = '',\n onValueChange,\n onBlur,\n invalid = false,\n errorMessage,\n label,\n placeholder,\n disabled = false,\n requirement,\n name = 'document',\n documentType = 'CPF',\n className,\n ref,\n children,\n ...props\n}: DocumentFieldProps) {\n const isControlled = value !== undefined;\n const [internalValue, setInternalValue] = useState(() => maskDocumentValue(defaultValue, documentType));\n const [checkInvalid, setCheckInvalid] = useState(false);\n\n const currentValue = isControlled ? value : internalValue;\n // Masking is idempotent, so masking here formats both a raw pre-fill (a controlled `value` seeded\n // from the saved profile) and an already-masked typed value identically.\n const displayValue = maskDocumentValue(currentValue, documentType);\n const showInvalid = invalid || checkInvalid;\n const numericOnly = isDigitOnly(DOCUMENTS[documentType].mask);\n\n function handleChange(event: ChangeEvent<HTMLInputElement>) {\n const masked = maskDocumentValue(event.target.value, documentType);\n if (!isControlled) setInternalValue(masked);\n // Clear a prior check-digit error while typing; it is re-evaluated on the next blur.\n if (checkInvalid) setCheckInvalid(false);\n onValueChange?.(masked);\n }\n\n function handleBlur(event: FocusEvent<HTMLInputElement>) {\n // Validation runs only after the complete number is entered; empty is valid (optional field).\n setCheckInvalid(!isValidDocument(currentValue, documentType));\n onBlur?.(event);\n }\n\n return (\n <DocumentFieldView\n {...props}\n ref={ref}\n className={className}\n name={name}\n label={label}\n placeholder={placeholder}\n disabled={disabled}\n required={requirement === 'mandatory'}\n numericOnly={numericOnly}\n displayValue={displayValue}\n showInvalid={showInvalid}\n errorMessage={errorMessage}\n onChange={handleChange}\n onBlur={handleBlur}\n >\n {children}\n </DocumentFieldView>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Bound — reads the field contract, binds through useController\n// ---------------------------------------------------------------------------\n\ntype BoundDocumentFieldProps = DocumentFieldProps & { contract: FieldContract };\n\nfunction BoundDocumentField({\n value: _value,\n defaultValue: _defaultValue,\n onValueChange,\n onBlur,\n invalid = false,\n errorMessage,\n label,\n placeholder,\n disabled,\n requirement,\n name,\n documentType = 'CPF',\n className,\n ref: callerRef,\n contract,\n children,\n ...props\n}: BoundDocumentFieldProps) {\n // Check digit only. Requiredness is not decided here: `useBoundField` resolves it from the\n // fieldset's baseline and the raise below. Returning the `errorMessage` prop (or a bare `false`\n // when it is unset) keeps today's behaviour: the field goes invalid, and — since the composite's\n // own inline text is driven by the `errorMessage` prop, not by react-hook-form's error message —\n // text renders only when supplied. The check composes alongside whatever the fieldset validates;\n // before this merge, any contract validator discarded the particle's own validation whole.\n const rule = useMemo<FieldRule>(\n () =>\n mergeFieldValidator(contract.rule, 'checkDigit', (rawValue) => {\n const raw = typeof rawValue === 'string' ? rawValue : String(rawValue ?? '');\n return isValidDocument(raw, documentType) || (errorMessage ?? false);\n }),\n [contract.rule, documentType, errorMessage],\n );\n\n const field = useBoundField(contract, TEXT_FIELD_VALUE, rule, { required: requirement === 'mandatory' });\n const { getFieldState } = useFormContext();\n const { invalid: rhfInvalid } = getFieldState(contract.name, useFormState({ name: contract.name }));\n\n const composedRef = useComposedRef<HTMLInputElement>(callerRef, field.ref);\n\n const currentValue = typeof field.value === 'string' ? field.value : String(field.value ?? '');\n const displayValue = maskDocumentValue(currentValue, documentType);\n const showInvalid = invalid || rhfInvalid;\n const numericOnly = isDigitOnly(DOCUMENTS[documentType].mask);\n const resolvedLabel = label ?? contract.label;\n const resolvedPlaceholder = placeholder ?? contract.placeholder;\n const resolvedDisabled = disabled ?? contract.disabled ?? false;\n // Unlike `UnboundDocumentField`, `name` carries no signature default here — a signature default\n // would always win over the contract's name (Decision 5), colliding two bound fieldsets (e.g.\n // `profile.document` and `billing.document`) on the same rendered `name=\"document\"`.\n const resolvedName = name ?? contract.name;\n\n function handleChange(event: ChangeEvent<HTMLInputElement>) {\n const masked = maskDocumentValue(event.target.value, documentType);\n field.onChange(masked);\n onValueChange?.(masked);\n }\n\n function handleBlur(event: FocusEvent<HTMLInputElement>) {\n field.onBlur();\n onBlur?.(event);\n }\n\n return (\n // Decision 3: replaces the empty-contract FieldContractBoundary with a re-provided contract\n // carrying the RESOLVED name/label/placeholder — the values a composed childless Field.Label /\n // Field.Error actually need, as opposed to the raw (possibly-undefined) ones the ambient\n // contract from the enclosing FieldBinding carries. useBoundField above remains the sole binder\n // for contract.name; this FieldBinding never registers anything itself.\n <FieldBinding\n name={contract.name}\n label={resolvedLabel}\n placeholder={resolvedPlaceholder}\n defaultValue={contract.defaultValue}\n rule={contract.rule}\n options={contract.options}\n disabled={resolvedDisabled}\n readOnly={contract.readOnly}\n autoComplete={contract.autoComplete}\n inputMode={contract.inputMode}\n >\n <DocumentFieldView\n {...props}\n ref={composedRef}\n className={className}\n name={resolvedName}\n label={resolvedLabel}\n placeholder={resolvedPlaceholder}\n disabled={resolvedDisabled}\n required={field.required}\n numericOnly={numericOnly}\n displayValue={displayValue}\n showInvalid={showInvalid}\n errorMessage={errorMessage}\n onChange={handleChange}\n onBlur={handleBlur}\n >\n {children}\n </DocumentFieldView>\n </FieldBinding>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAwFA,SAAgB,YAAY,OAAwB;CAClD,MAAM,SAAS,MAAM,QAAQ,WAAW,EAAE;CAE1C,IAAI,OAAO,WAAW,MAAM,aAAa,KAAK,MAAM,GAClD,OAAO;CAGT,MAAM,SAAS,OAAO,MAAM,EAAE,CAAC,CAAC,KAAK,OAAO,CAAC,EAAE;CAC/C,MAAM,QAAQ,UACV,OAAO,MAAM,GAAG,QAAQ,EAAE,CAAC,CAAC,QAAQ,KAAK,IAAI,UAAU,MAAM,MAAM,QAAQ,QAAQ,CAAC,IAAI,KAAM,KAAM;CAExG,OAAO,KAAK,EAAE,MAAM,OAAO,MAAM,KAAK,EAAE,MAAM,OAAO;AACvD;AAOA,MAAa,YAAgD,EAC3D,KAAK;CACH,IAAI;CACJ,MAAM;CACN,MAAM;CACN,UAAU;AACZ,EACF;;;;;;;;;;AAWA,SAAgB,wBAAwB,MAAqC;CAC3E,OAAO,OAAO,SAAS,YAAY,OAAO,OAAO,WAAW,IAAI;AAClE;;;;;;AAOA,SAAgB,kBAAkB,UAAkB,cAAoC;CACtF,MAAM,OAAO,UAAU;CACvB,MAAM,YAAY,YAAY,KAAK,IAAI,IAAI,SAAS,QAAQ,OAAO,EAAE,IAAI;CACzE,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC,IAAI,UAAU,CAAC;CACjE,OAAO,UAAU,UAAU,MAAM,GAAG,QAAQ,GAAG,KAAK,IAAI;AAC1D;;;;;;AAOA,SAAgB,gBAAgB,OAAe,cAAqC;CAClF,IAAI,CAAC,MAAM,KAAK,GAAG,OAAO;CAC1B,OAAO,UAAU,aAAa,CAAC,SAAS,KAAK;AAC/C;AAMA,MAAa,0BAA0B,gBAAoC;CACzE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAkB,UAAU;CAAU;CACvD,QAAQ;EACN,OAAO;GAAE,MAAM;GAAU,OAAO;EAAQ;EACxC,aAAa;GAAE,MAAM;GAAU,OAAO;EAAc;EACpD,cAAc;GAAE,MAAM;GAAU,OAAO;EAAgB;EACvD,cAAc;GAAE,MAAM;GAAU,SAAS;GAAO,OAAO;EAAgB;EACvE,aAAa;GAAE,MAAM,CAAC,aAAa,UAAU;GAAY,OAAO;EAAc;EAC9E,UAAU;GAAE,MAAM;GAAW,SAAS;GAAO,OAAO;EAAW;CACjE;CACA,WACE;AACJ,CAAC;;;;;;;;;;;;;;;AAgBD,SAAgB,cAAc,OAAsC;CAClE,MAAM,WAAW,SAAS;CAC1B,MAAM,cAAc,eAAe;CAEnC,IAAI,YAAY,aACd,OAAO,oBAAC,oBAAD;EAAoB,GAAI;EAAiB;CAAW,CAAA;CAG7D,OAAO,oBAAC,sBAAD,EAAsB,GAAI,MAAQ,CAAA;AAC3C;AAuBA,MAAM,4BAA4B,cAAqD,IAAI;AAE3F,SAAS,+BAA+D;CACtE,MAAM,UAAU,WAAW,yBAAyB;CACpD,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,2DAA2D;CAE7E,OAAO;AACT;AAIA,MAAa,+BAA+B,gBAAyC;CACnF,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAwB,UAAU;CAAS;CAC5D,QAAQ,CAAC;CACT,WACE;AACJ,CAAC;;AAGD,SAAgB,mBAAmB,EAAE,aAAiD;CACpF,MAAM,EAAE,KAAK,MAAM,OAAO,aAAa,UAAU,UAAU,aAAa,UAAU,WAChF,6BAA6B;CAE/B,OACE,oBAACA,MAAU,SAAX;EACO;EACC;EAIN,iBAAe,YAAY,KAAA;EACd;EACH;EACH;EACP,GAAK,cAAc,EAAE,WAAW,UAAmB,IAAI,CAAC;EAC9C;EACF;EACG;EACX,yBAAsB;CACvB,CAAA;AAEL;AAyBA,SAAS,kBAAkB,EACzB,WACA,MACA,OACA,aACA,UACA,UACA,aACA,cACA,aACA,cACA,UACA,QACA,KACA,UACA,GAAG,SACsB;CACzB,MAAM,eAAe,eACZ;EAAE;EAAK;EAAM,OAAO;EAAc;EAAa;EAAU;EAAU;EAAa;EAAU;CAAO,IACxG;EAAC;EAAK;EAAM;EAAc;EAAa;EAAU;EAAU;EAAa;EAAU;CAAM,CAC1F;CAEA,OAME,oBAACA,MAAU,MAAX;EACE,GAAI;EACO;EACL;EACN,SAAS;EACC;EACV,0BAAuB;EACvB,sBAAmB;EAEnB,UAAA,oBAAC,0BAA0B,UAA3B;GAAoC,OAAO;GACxC,UAAA,YACC,qBAAA,UAAA,EAAA,UAAA;IACG,QAAQ,oBAACA,MAAU,OAAX;KAAiB,uBAAoB;KAAI,UAAA;IAAuB,CAAA,IAAI;IAC7E,oBAAC,oBAAD,CAAqB,CAAA;IACpB,eAAe,eACd,oBAACA,MAAU,OAAX;KAAiB,OAAA;KAAM,uBAAoB;KACxC,UAAA;IACc,CAAA,IACf;GACJ,EAAA,CAAA;EAE8B,CAAA;CACtB,CAAA;AAEpB;AAMA,SAAS,qBAAqB,EAC5B,OACA,eAAe,IACf,eACA,QACA,UAAU,OACV,cACA,OACA,aACA,WAAW,OACX,aACA,OAAO,YACP,eAAe,OACf,WACA,KACA,UACA,GAAG,SACkB;CACrB,MAAM,eAAe,UAAU,KAAA;CAC/B,MAAM,CAAC,eAAe,oBAAoB,eAAe,kBAAkB,cAAc,YAAY,CAAC;CACtG,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CAEtD,MAAM,eAAe,eAAe,QAAQ;CAG5C,MAAM,eAAe,kBAAkB,cAAc,YAAY;CACjE,MAAM,cAAc,WAAW;CAC/B,MAAM,cAAc,YAAY,UAAU,aAAa,CAAC,IAAI;CAE5D,SAAS,aAAa,OAAsC;EAC1D,MAAM,SAAS,kBAAkB,MAAM,OAAO,OAAO,YAAY;EACjE,IAAI,CAAC,cAAc,iBAAiB,MAAM;EAE1C,IAAI,cAAc,gBAAgB,KAAK;EACvC,gBAAgB,MAAM;CACxB;CAEA,SAAS,WAAW,OAAqC;EAEvD,gBAAgB,CAAC,gBAAgB,cAAc,YAAY,CAAC;EAC5D,SAAS,KAAK;CAChB;CAEA,OACE,oBAAC,mBAAD;EACE,GAAI;EACC;EACM;EACL;EACC;EACM;EACH;EACV,UAAU,gBAAgB;EACb;EACC;EACD;EACC;EACd,UAAU;EACV,QAAQ;EAEP;CACgB,CAAA;AAEvB;AAQA,SAAS,mBAAmB,EAC1B,OAAO,QACP,cAAc,eACd,eACA,QACA,UAAU,OACV,cACA,OACA,aACA,UACA,aACA,MACA,eAAe,OACf,WACA,KAAK,WACL,UACA,UACA,GAAG,SACuB;CAO1B,MAAM,OAAO,cAET,oBAAoB,SAAS,MAAM,eAAe,aAAa;EAE7D,OAAO,gBADK,OAAO,aAAa,WAAW,WAAW,OAAO,YAAY,EAAE,GAC/C,YAAY,MAAM,gBAAgB;CAChE,CAAC,GACH;EAAC,SAAS;EAAM;EAAc;CAAY,CAC5C;CAEA,MAAM,QAAQ,cAAc,UAAU,kBAAkB,MAAM,EAAE,UAAU,gBAAgB,YAAY,CAAC;CACvG,MAAM,EAAE,kBAAkB,eAAe;CACzC,MAAM,EAAE,SAAS,eAAe,cAAc,SAAS,MAAM,aAAa,EAAE,MAAM,SAAS,KAAK,CAAC,CAAC;CAElG,MAAM,cAAc,eAAiC,WAAW,MAAM,GAAG;CAGzE,MAAM,eAAe,kBADA,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,OAAO,MAAM,SAAS,EAAE,GACxC,YAAY;CACjE,MAAM,cAAc,WAAW;CAC/B,MAAM,cAAc,YAAY,UAAU,aAAa,CAAC,IAAI;CAC5D,MAAM,gBAAgB,SAAS,SAAS;CACxC,MAAM,sBAAsB,eAAe,SAAS;CACpD,MAAM,mBAAmB,YAAY,SAAS,YAAY;CAI1D,MAAM,eAAe,QAAQ,SAAS;CAEtC,SAAS,aAAa,OAAsC;EAC1D,MAAM,SAAS,kBAAkB,MAAM,OAAO,OAAO,YAAY;EACjE,MAAM,SAAS,MAAM;EACrB,gBAAgB,MAAM;CACxB;CAEA,SAAS,WAAW,OAAqC;EACvD,MAAM,OAAO;EACb,SAAS,KAAK;CAChB;CAEA,OAME,oBAAC,cAAD;EACE,MAAM,SAAS;EACf,OAAO;EACP,aAAa;EACb,cAAc,SAAS;EACvB,MAAM,SAAS;EACf,SAAS,SAAS;EAClB,UAAU;EACV,UAAU,SAAS;EACnB,cAAc,SAAS;EACvB,WAAW,SAAS;EAEpB,UAAA,oBAAC,mBAAD;GACE,GAAI;GACJ,KAAK;GACM;GACX,MAAM;GACN,OAAO;GACP,aAAa;GACb,UAAU;GACV,UAAU,MAAM;GACH;GACC;GACD;GACC;GACd,UAAU;GACV,QAAQ;GAEP;EACgB,CAAA;CACP,CAAA;AAElB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
//#region src/particles/field/bound-field-registry.ts
|
|
2
|
+
/**
|
|
3
|
+
* Every clearer currently registered for a (control, name) pair. A `Set` per name, not a single
|
|
4
|
+
* function: two bound parts registering the same name — the RFC's named, unsupported double-binder
|
|
5
|
+
* composition, reachable via `PhoneField`'s and `DocumentField`'s own re-provided `FieldBinding` — both
|
|
6
|
+
* run on Clear instead of one silently winning, and releasing one leaves the other intact.
|
|
7
|
+
*
|
|
8
|
+
* Keyed by `control` — the same object identity `field.tsx`'s adoption ledger (`adoptedSeeds`) uses,
|
|
9
|
+
* and for the same reason: a `WeakMap` so a form that itself unmounts (a fresh `useForm()` call, a
|
|
10
|
+
* fresh `control`) starts over empty and is collected once nothing else references it.
|
|
11
|
+
*/
|
|
12
|
+
const clearers = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
function clearersFor(control) {
|
|
14
|
+
let byName = clearers.get(control);
|
|
15
|
+
if (!byName) {
|
|
16
|
+
byName = /* @__PURE__ */ new Map();
|
|
17
|
+
clearers.set(control, byName);
|
|
18
|
+
}
|
|
19
|
+
return byName;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Registers `clear` for `name` on `control` and returns its release. The release is one-shot —
|
|
23
|
+
* calling it a second time removes nothing further, mirroring `useFieldsetFieldNames`'s own `released`
|
|
24
|
+
* latch (`field.tsx`) — and it deletes `name`'s entry entirely once its set empties, so a name no bound
|
|
25
|
+
* part still claims leaves no residue in the map.
|
|
26
|
+
*/
|
|
27
|
+
function registerFieldClearer(control, name, clear) {
|
|
28
|
+
const byName = clearersFor(control);
|
|
29
|
+
let forName = byName.get(name);
|
|
30
|
+
if (!forName) {
|
|
31
|
+
forName = /* @__PURE__ */ new Set();
|
|
32
|
+
byName.set(name, forName);
|
|
33
|
+
}
|
|
34
|
+
forName.add(clear);
|
|
35
|
+
let released = false;
|
|
36
|
+
return () => {
|
|
37
|
+
if (released) return;
|
|
38
|
+
released = true;
|
|
39
|
+
forName.delete(clear);
|
|
40
|
+
if (forName.size === 0) byName.delete(name);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Runs every clearer registered for `name` on `control`. A name with none registered is a no-op. */
|
|
44
|
+
function clearBoundField(control, name) {
|
|
45
|
+
const forName = clearers.get(control)?.get(name);
|
|
46
|
+
if (!forName) return;
|
|
47
|
+
for (const clear of forName) clear();
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { clearBoundField, registerFieldClearer };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=bound-field-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bound-field-registry.js","names":[],"sources":["../../../src/particles/field/bound-field-registry.ts"],"sourcesContent":["import type { Control } from 'react-hook-form';\n\n/** Clears one bound part's own field back to its empty value. Owned and invoked by `clearBoundField`. */\ntype FieldClearer = () => void;\n\n/**\n * Every clearer currently registered for a (control, name) pair. A `Set` per name, not a single\n * function: two bound parts registering the same name — the RFC's named, unsupported double-binder\n * composition, reachable via `PhoneField`'s and `DocumentField`'s own re-provided `FieldBinding` — both\n * run on Clear instead of one silently winning, and releasing one leaves the other intact.\n *\n * Keyed by `control` — the same object identity `field.tsx`'s adoption ledger (`adoptedSeeds`) uses,\n * and for the same reason: a `WeakMap` so a form that itself unmounts (a fresh `useForm()` call, a\n * fresh `control`) starts over empty and is collected once nothing else references it.\n */\nconst clearers = new WeakMap<Control, Map<string, Set<FieldClearer>>>();\n\nfunction clearersFor(control: Control): Map<string, Set<FieldClearer>> {\n let byName = clearers.get(control);\n if (!byName) {\n byName = new Map();\n clearers.set(control, byName);\n }\n return byName;\n}\n\n/**\n * Registers `clear` for `name` on `control` and returns its release. The release is one-shot —\n * calling it a second time removes nothing further, mirroring `useFieldsetFieldNames`'s own `released`\n * latch (`field.tsx`) — and it deletes `name`'s entry entirely once its set empties, so a name no bound\n * part still claims leaves no residue in the map.\n */\nexport function registerFieldClearer(control: Control, name: string, clear: FieldClearer): () => void {\n const byName = clearersFor(control);\n let forName = byName.get(name);\n if (!forName) {\n forName = new Set();\n byName.set(name, forName);\n }\n forName.add(clear);\n\n let released = false;\n return () => {\n if (released) return;\n released = true;\n\n forName.delete(clear);\n if (forName.size === 0) {\n byName.delete(name);\n }\n };\n}\n\n/** Runs every clearer registered for `name` on `control`. A name with none registered is a no-op. */\nexport function clearBoundField(control: Control, name: string): void {\n const forName = clearers.get(control)?.get(name);\n if (!forName) return;\n for (const clear of forName) {\n clear();\n }\n}\n"],"mappings":";;;;;;;;;;;AAeA,MAAM,2BAAW,IAAI,QAAiD;AAEtE,SAAS,YAAY,SAAkD;CACrE,IAAI,SAAS,SAAS,IAAI,OAAO;CACjC,IAAI,CAAC,QAAQ;EACX,yBAAS,IAAI,IAAI;EACjB,SAAS,IAAI,SAAS,MAAM;CAC9B;CACA,OAAO;AACT;;;;;;;AAQA,SAAgB,qBAAqB,SAAkB,MAAc,OAAiC;CACpG,MAAM,SAAS,YAAY,OAAO;CAClC,IAAI,UAAU,OAAO,IAAI,IAAI;CAC7B,IAAI,CAAC,SAAS;EACZ,0BAAU,IAAI,IAAI;EAClB,OAAO,IAAI,MAAM,OAAO;CAC1B;CACA,QAAQ,IAAI,KAAK;CAEjB,IAAI,WAAW;CACf,aAAa;EACX,IAAI,UAAU;EACd,WAAW;EAEX,QAAQ,OAAO,KAAK;EACpB,IAAI,QAAQ,SAAS,GACnB,OAAO,OAAO,IAAI;CAEtB;AACF;;AAGA,SAAgB,gBAAgB,SAAkB,MAAoB;CACpE,MAAM,UAAU,SAAS,IAAI,OAAO,CAAC,EAAE,IAAI,IAAI;CAC/C,IAAI,CAAC,SAAS;CACd,KAAK,MAAM,SAAS,SAClB,MAAM;AAEV"}
|