@vtex/faststore 0.0.5 → 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 +45 -0
- package/dist/actions.json +181 -0
- package/dist/catalog.json +1543 -495
- package/dist/client.d.ts +2 -1
- package/dist/client.js +2 -1
- package/dist/components.d.ts +28 -7
- package/dist/components.js +31 -12
- package/dist/docs/access-code-sign-in.md +1 -1
- 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/card-entry.md +250 -0
- package/dist/docs/cardholder-document.md +142 -0
- 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/shipping-address.md +110 -258
- 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/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/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/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 +4 -4
- 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 +480 -791
- package/dist/matter/shipping-address/shipping-address.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/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/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/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/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/cart/use-cart-actions.js +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 +36 -0
- 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.js +7 -3
- package/dist/sdk/data/reactive-data-provider.js.map +1 -1
- package/dist/sdk/data/reactive-list-item-data-provider.js +12 -4
- 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 +5 -1
- 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/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/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/shipping/use-shipping-actions.js +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/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,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":"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"}
|
|
@@ -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"}
|
|
@@ -19,24 +19,139 @@ export type FieldErrorProps = ComponentPropsWithRef<'div'> & {
|
|
|
19
19
|
* fall back to Base UI's default: shown whenever the field's tracked validity is invalid.
|
|
20
20
|
* Note `match={false}` does not suppress the error — it falls back to that default
|
|
21
21
|
* invalid-driven behavior; callers needing a boolean should use `invalid ? true : 'customError'`.
|
|
22
|
+
* Only consulted on the unbound path — a bound `Field.Error` renders from the field contract's
|
|
23
|
+
* name and react-hook-form's error state instead.
|
|
22
24
|
*/
|
|
23
25
|
match?: boolean | keyof ValidityState;
|
|
24
26
|
};
|
|
25
27
|
export type FieldValidityProps = {
|
|
26
28
|
children: (state: FieldValidityState) => ReactNode;
|
|
27
29
|
};
|
|
30
|
+
/** One choice of a field whose value is picked from a list. Platform-owned; an author reorders the part, not the list. */
|
|
31
|
+
export type FieldOption = Readonly<{
|
|
32
|
+
value: string;
|
|
33
|
+
label: string;
|
|
34
|
+
}>;
|
|
35
|
+
/** A rule member constrained to `value`, carrying its own message — react-hook-form's own shape for
|
|
36
|
+
* a constrained validation rule, declared here as our own type (Decision 5). */
|
|
37
|
+
export type FieldRuleConstraint<T> = Readonly<{
|
|
38
|
+
value: T;
|
|
39
|
+
message: string;
|
|
40
|
+
}>;
|
|
41
|
+
/** A field-level validator: reads the live value, returns `true` (valid), a message, or `false`. */
|
|
42
|
+
export type FieldValidator = (value: unknown) => boolean | string;
|
|
43
|
+
/**
|
|
44
|
+
* The fieldset's validation rule for this field, handed to `useBoundField` and mapped internally to
|
|
45
|
+
* react-hook-form's `RegisterOptions` (`toRegisterOptions`) — nothing that imports this type from the
|
|
46
|
+
* barrel ever names a react-hook-form type (RFC `Dependencies`).
|
|
47
|
+
*
|
|
48
|
+
* These seven members are a starting point measured against every rule this repository authors
|
|
49
|
+
* today, not a closed contract: `RegisterOptions` carries more (`setValueAs`, `deps`, `valueAsNumber`,
|
|
50
|
+
* …), and a member is added here the day a field actually needs it — this is not a deliberately closed
|
|
51
|
+
* set to work around.
|
|
52
|
+
*/
|
|
53
|
+
export type FieldRule = Readonly<{
|
|
54
|
+
required?: boolean | string | FieldRuleConstraint<boolean>;
|
|
55
|
+
min?: number | string | FieldRuleConstraint<number | string>;
|
|
56
|
+
max?: number | string | FieldRuleConstraint<number | string>;
|
|
57
|
+
minLength?: number | FieldRuleConstraint<number>;
|
|
58
|
+
maxLength?: number | FieldRuleConstraint<number>;
|
|
59
|
+
pattern?: RegExp | FieldRuleConstraint<RegExp>;
|
|
60
|
+
validate?: FieldValidator | Readonly<Record<string, FieldValidator>>;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* One field's identity and its static facts: the argument list `useController` needs, hoisted out of
|
|
64
|
+
* a prop-drill that composition made impossible. Not a mirror of state react-hook-form also holds —
|
|
65
|
+
* it carries NO live value, so it never changes while the shopper types, and the parts subscribe to
|
|
66
|
+
* react-hook-form for value, error, dirty and touched.
|
|
67
|
+
*/
|
|
68
|
+
export type FieldContract = Readonly<{
|
|
69
|
+
/** Full, namespaced react-hook-form field name, e.g. `shippingAddress.street`. */
|
|
70
|
+
name: string;
|
|
71
|
+
/** Resolved label text: the Control's `label` prop, else the platform's default. */
|
|
72
|
+
label?: string;
|
|
73
|
+
placeholder?: string;
|
|
74
|
+
/** Seed value, registered as react-hook-form's field-level `defaultValue`. */
|
|
75
|
+
defaultValue?: unknown;
|
|
76
|
+
rule?: FieldRule;
|
|
77
|
+
/** Only for a field whose value is picked from a list. Unread until `Field.Select` is added. */
|
|
78
|
+
options?: readonly FieldOption[];
|
|
79
|
+
disabled?: boolean;
|
|
80
|
+
readOnly?: boolean;
|
|
81
|
+
autoComplete?: string;
|
|
82
|
+
inputMode?: 'none' | 'text' | 'decimal' | 'numeric' | 'tel' | 'search' | 'email' | 'url';
|
|
83
|
+
}>;
|
|
84
|
+
/** Supplies the field contract to everything below it. Renders no DOM. Never catalogued. */
|
|
85
|
+
export type FieldBindingProps = FieldContract & {
|
|
86
|
+
children?: ReactNode;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Provided by a fieldset. Declares the one namespace every name registered through it belongs to, and
|
|
90
|
+
* registers a full field name, returning its unregister.
|
|
91
|
+
*/
|
|
92
|
+
export type FieldsetFieldRegistry = {
|
|
93
|
+
/**
|
|
94
|
+
* The namespace this fieldset owns — the prefix of every full name its own controls register. Declared
|
|
95
|
+
* by the provider, never inferred from a caller.
|
|
96
|
+
*/
|
|
97
|
+
namespace: string;
|
|
98
|
+
/**
|
|
99
|
+
* Registers a full, namespaced field name and returns a release function. Refcounted by the provider,
|
|
100
|
+
* so two callers holding one name produce one entry, released when the last of them releases. The
|
|
101
|
+
* returned function releases at most once.
|
|
102
|
+
*/
|
|
103
|
+
registerFieldName: (name: string) => () => void;
|
|
104
|
+
};
|
|
105
|
+
/** What `useFieldsetFieldNames` hands a fieldset: the value to provide, and the list to register. */
|
|
106
|
+
export type FieldsetFieldNames = Readonly<{
|
|
107
|
+
/** Referentially stable while `namespace` is unchanged. Goes straight into the context provider. */
|
|
108
|
+
registry: FieldsetFieldRegistry;
|
|
109
|
+
/** Every full field name currently held, in first-registration order. Stable identity. */
|
|
110
|
+
getFieldNames: () => readonly string[];
|
|
111
|
+
}>;
|
|
112
|
+
/** New bound part. Reads the contract, binds through useBoundField, drives Checkbox.Root. */
|
|
113
|
+
export type FieldCheckboxProps = {
|
|
114
|
+
readonly className?: string;
|
|
115
|
+
readonly children?: ReactNode;
|
|
116
|
+
};
|
|
117
|
+
/** Reads the field contract. Returns null when no binding is above the caller. */
|
|
118
|
+
export declare function useField(): FieldContract | null;
|
|
119
|
+
export declare function FieldBinding({ name, label, placeholder, defaultValue, rule, options, disabled, readOnly, autoComplete, inputMode, children }: FieldBindingProps): ReactNode;
|
|
120
|
+
/** Exported so those tasks import it rather than redefine it. */
|
|
121
|
+
export declare const FieldsetFieldContext: import("react").Context<FieldsetFieldRegistry | null>;
|
|
122
|
+
/** Unchanged: the registry above the caller, or null. Now carrying a namespace. */
|
|
123
|
+
export declare function useFieldsetContext(): FieldsetFieldRegistry | null;
|
|
124
|
+
/**
|
|
125
|
+
* Registers `${namespace}.${key}` with the enclosing fieldset while the caller is mounted, and returns
|
|
126
|
+
* that full name. Throws during render when a registry is above the caller and declares a different
|
|
127
|
+
* namespace. Registers nothing and throws nothing when no registry is above. Holds no state: the
|
|
128
|
+
* refcount belongs to the provider.
|
|
129
|
+
*/
|
|
130
|
+
export declare function useFieldsetField(namespace: string, key: string): string;
|
|
131
|
+
/**
|
|
132
|
+
* The field-name bookkeeping a fieldset provides. Refcounted, so two callers of one name collapse to one
|
|
133
|
+
* entry however they arrived — including a caller that reaches `registerFieldName` directly.
|
|
134
|
+
*/
|
|
135
|
+
export declare function useFieldsetFieldNames(namespace: string): FieldsetFieldNames;
|
|
136
|
+
/**
|
|
137
|
+
* Throws during render when an ancestor fieldset is already providing field registration for this
|
|
138
|
+
* subtree. Every fieldset calls it in one line.
|
|
139
|
+
*/
|
|
140
|
+
export declare function useNoAncestorFieldset(componentName: string): void;
|
|
28
141
|
export declare const FieldRootDefinition: ComponentDefinition<FieldRootProps>;
|
|
29
142
|
export declare const FieldLabelDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").LabelHTMLAttributes<HTMLLabelElement>, HTMLLabelElement>>;
|
|
30
143
|
export declare const FieldControlDefinition: ComponentDefinition<FieldControlProps>;
|
|
31
144
|
export declare const FieldDescriptionDefinition: ComponentDefinition<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>>;
|
|
32
145
|
export declare const FieldErrorDefinition: ComponentDefinition<FieldErrorProps>;
|
|
33
146
|
export declare const FieldValidityDefinition: ComponentDefinition<FieldValidityProps>;
|
|
147
|
+
export declare const FieldCheckboxDefinition: ComponentDefinition<FieldCheckboxProps>;
|
|
34
148
|
export declare function FieldRoot({ disabled, invalid, name, className, children, ...props }: FieldRootProps): import("react/jsx-runtime").JSX.Element;
|
|
35
149
|
export declare function FieldLabel({ className, children, ...props }: FieldLabelProps): import("react/jsx-runtime").JSX.Element;
|
|
36
150
|
export declare function FieldControl({ className, ...props }: FieldControlProps): import("react/jsx-runtime").JSX.Element;
|
|
37
151
|
export declare function FieldDescription({ className, children, ...props }: FieldDescriptionProps): import("react/jsx-runtime").JSX.Element;
|
|
38
152
|
export declare function FieldError({ className, children, ...props }: FieldErrorProps): import("react/jsx-runtime").JSX.Element;
|
|
39
153
|
export declare function FieldValidity({ children }: FieldValidityProps): import("react/jsx-runtime").JSX.Element;
|
|
154
|
+
export declare function FieldCheckbox({ className, children }: FieldCheckboxProps): ReactNode;
|
|
40
155
|
declare const Field$1: {
|
|
41
156
|
Root: typeof FieldRoot;
|
|
42
157
|
Label: typeof FieldLabel;
|
|
@@ -44,6 +159,7 @@ declare const Field$1: {
|
|
|
44
159
|
Description: typeof FieldDescription;
|
|
45
160
|
Error: typeof FieldError;
|
|
46
161
|
Validity: typeof FieldValidity;
|
|
162
|
+
Checkbox: typeof FieldCheckbox;
|
|
47
163
|
};
|
|
48
164
|
//#endregion
|
|
49
165
|
export { Field$1 as Field };
|