@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.
Files changed (232) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/dist/actions.json +181 -0
  3. package/dist/catalog.json +1543 -495
  4. package/dist/client.d.ts +2 -1
  5. package/dist/client.js +2 -1
  6. package/dist/components.d.ts +28 -7
  7. package/dist/components.js +31 -12
  8. package/dist/docs/access-code-sign-in.md +1 -1
  9. package/dist/docs/address-summary.md +24 -3
  10. package/dist/docs/alert-dialog.md +204 -0
  11. package/dist/docs/billing-address.md +284 -0
  12. package/dist/docs/card-entry.md +250 -0
  13. package/dist/docs/cardholder-document.md +142 -0
  14. package/dist/docs/client-preferences-fieldset.md +148 -0
  15. package/dist/docs/client-preferences.md +2 -1
  16. package/dist/docs/client-profile-fieldset.md +184 -0
  17. package/dist/docs/client-profile.md +1 -1
  18. package/dist/docs/document-field.md +58 -4
  19. package/dist/docs/field.md +317 -0
  20. package/dist/docs/form.md +307 -0
  21. package/dist/docs/identity-edit.md +6 -6
  22. package/dist/docs/identity-summary.md +5 -5
  23. package/dist/docs/installments.md +248 -0
  24. package/dist/docs/my-account-profile.md +1 -0
  25. package/dist/docs/order-placed-payment-summary.md +138 -0
  26. package/dist/docs/payment-method-badges.md +232 -0
  27. package/dist/docs/payment-methods.md +1 -0
  28. package/dist/docs/payment.md +218 -0
  29. package/dist/docs/phone-field.md +58 -4
  30. package/dist/docs/place-order.md +157 -0
  31. package/dist/docs/save-card.md +186 -0
  32. package/dist/docs/saved-cards.md +218 -0
  33. package/dist/docs/select.md +465 -0
  34. package/dist/docs/shipping-address.md +110 -258
  35. package/dist/index.d.ts +4 -1
  36. package/dist/internal/client.d.ts +3 -1
  37. package/dist/internal/client.js +3 -1
  38. package/dist/matter/access-code-sign-in/access-code-sign-in.js +9 -8
  39. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  40. package/dist/matter/billing-address/billing-address.d.ts +165 -0
  41. package/dist/matter/billing-address/billing-address.js +408 -0
  42. package/dist/matter/billing-address/billing-address.js.map +1 -0
  43. package/dist/matter/card-entry/card-capture.d.ts +25 -0
  44. package/dist/matter/card-entry/card-capture.js +25 -0
  45. package/dist/matter/card-entry/card-capture.js.map +1 -0
  46. package/dist/matter/card-entry/card-entry.d.ts +120 -0
  47. package/dist/matter/card-entry/card-entry.js +832 -0
  48. package/dist/matter/card-entry/card-entry.js.map +1 -0
  49. package/dist/matter/cardholder-document/cardholder-document.d.ts +45 -0
  50. package/dist/matter/cardholder-document/cardholder-document.js +70 -0
  51. package/dist/matter/cardholder-document/cardholder-document.js.map +1 -0
  52. package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +77 -0
  53. package/dist/matter/client-preferences/client-preferences-fieldset.js +299 -0
  54. package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -0
  55. package/dist/matter/client-preferences/client-preferences.d.ts +3 -5
  56. package/dist/matter/client-preferences/client-preferences.js +6 -6
  57. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  58. package/dist/matter/client-profile/client-profile.d.ts +4 -6
  59. package/dist/matter/client-profile/client-profile.js +9 -6
  60. package/dist/matter/client-profile/client-profile.js.map +1 -1
  61. package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -0
  62. package/dist/matter/client-profile-form/client-profile-fieldset.js +488 -0
  63. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -0
  64. package/dist/matter/identity-edit/identity-edit.js +2 -2
  65. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  66. package/dist/matter/identity-summary/identity-summary.js +2 -2
  67. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  68. package/dist/matter/installments/installments-select.d.ts +42 -0
  69. package/dist/matter/installments/installments-select.js +167 -0
  70. package/dist/matter/installments/installments-select.js.map +1 -0
  71. package/dist/matter/installments/installments.d.ts +58 -0
  72. package/dist/matter/installments/installments.js +176 -0
  73. package/dist/matter/installments/installments.js.map +1 -0
  74. package/dist/matter/installments/use-installments-selection.d.ts +29 -0
  75. package/dist/matter/installments/use-installments-selection.js +95 -0
  76. package/dist/matter/installments/use-installments-selection.js.map +1 -0
  77. package/dist/matter/my-account-profile/my-account-profile.js +4 -4
  78. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  79. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +63 -0
  80. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +305 -0
  81. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -0
  82. package/dist/matter/payment/payment-card-capture.d.ts +19 -0
  83. package/dist/matter/payment/payment-card-capture.js +129 -0
  84. package/dist/matter/payment/payment-card-capture.js.map +1 -0
  85. package/dist/matter/payment/payment-controller.d.ts +24 -0
  86. package/dist/matter/payment/payment-controller.js +107 -0
  87. package/dist/matter/payment/payment-controller.js.map +1 -0
  88. package/dist/matter/payment/payment-method-badges.d.ts +57 -0
  89. package/dist/matter/payment/payment-method-badges.js +192 -0
  90. package/dist/matter/payment/payment-method-badges.js.map +1 -0
  91. package/dist/matter/payment/payment.d.ts +89 -0
  92. package/dist/matter/payment/payment.js +263 -0
  93. package/dist/matter/payment/payment.js.map +1 -0
  94. package/dist/matter/payment/use-payment-method-selection.d.ts +302 -0
  95. package/dist/matter/payment/use-payment-method-selection.js +458 -0
  96. package/dist/matter/payment/use-payment-method-selection.js.map +1 -0
  97. package/dist/matter/payment/use-payment-submit-gate.d.ts +111 -0
  98. package/dist/matter/payment/use-payment-submit-gate.js +144 -0
  99. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -0
  100. package/dist/matter/place-order/place-order.d.ts +74 -0
  101. package/dist/matter/place-order/place-order.js +379 -0
  102. package/dist/matter/place-order/place-order.js.map +1 -0
  103. package/dist/matter/product-list/product-list.d.ts +1 -0
  104. package/dist/matter/product-list/product-list.js.map +1 -1
  105. package/dist/matter/product-shelf/product-shelf.d.ts +1 -0
  106. package/dist/matter/product-shelf/product-shelf.js +2 -0
  107. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  108. package/dist/matter/region/region.js +1 -1
  109. package/dist/matter/region/region.js.map +1 -1
  110. package/dist/matter/save-card/save-card.d.ts +49 -0
  111. package/dist/matter/save-card/save-card.js +188 -0
  112. package/dist/matter/save-card/save-card.js.map +1 -0
  113. package/dist/matter/saved-cards/saved-cards.d.ts +79 -0
  114. package/dist/matter/saved-cards/saved-cards.js +387 -0
  115. package/dist/matter/saved-cards/saved-cards.js.map +1 -0
  116. package/dist/matter/shipping-address/country-descriptors.js +20 -10
  117. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  118. package/dist/matter/shipping-address/shipping-address.d.ts +113 -174
  119. package/dist/matter/shipping-address/shipping-address.js +480 -791
  120. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  121. package/dist/matter/variant-selector/variant-selector.js +1 -1
  122. package/dist/particles/address-summary/address-summary.d.ts +24 -0
  123. package/dist/particles/address-summary/address-summary.js +83 -2
  124. package/dist/particles/address-summary/address-summary.js.map +1 -1
  125. package/dist/particles/alert-dialog/alert-dialog.d.ts +68 -0
  126. package/dist/particles/alert-dialog/alert-dialog.js +187 -0
  127. package/dist/particles/alert-dialog/alert-dialog.js.map +1 -0
  128. package/dist/particles/checkbox/checkbox.d.ts +7 -3
  129. package/dist/particles/checkbox/checkbox.js +4 -1
  130. package/dist/particles/checkbox/checkbox.js.map +1 -1
  131. package/dist/particles/document-field/document-field.d.ts +48 -5
  132. package/dist/particles/document-field/document-field.js +189 -32
  133. package/dist/particles/document-field/document-field.js.map +1 -1
  134. package/dist/particles/field/bound-field-registry.js +52 -0
  135. package/dist/particles/field/bound-field-registry.js.map +1 -0
  136. package/dist/particles/field/field.d.ts +116 -0
  137. package/dist/particles/field/field.js +497 -6
  138. package/dist/particles/field/field.js.map +1 -1
  139. package/dist/particles/fieldset/fieldset.js +2 -2
  140. package/dist/particles/fieldset/fieldset.js.map +1 -1
  141. package/dist/particles/form/form.d.ts +104 -0
  142. package/dist/particles/form/form.js +485 -0
  143. package/dist/particles/form/form.js.map +1 -0
  144. package/dist/particles/phone-field/phone-field.d.ts +38 -10
  145. package/dist/particles/phone-field/phone-field.js +183 -39
  146. package/dist/particles/phone-field/phone-field.js.map +1 -1
  147. package/dist/particles/postal-code-field/postal-code-field.js +2 -2
  148. package/dist/particles/postal-code-field/postal-code-field.js.map +1 -1
  149. package/dist/particles/select/field-select.d.ts +43 -0
  150. package/dist/particles/select/field-select.js +150 -0
  151. package/dist/particles/select/field-select.js.map +1 -0
  152. package/dist/particles/select/select.d.ts +2 -0
  153. package/dist/particles/select/select.js.map +1 -1
  154. package/dist/particles/shared/compose-refs.js +21 -0
  155. package/dist/particles/shared/compose-refs.js.map +1 -0
  156. package/dist/schema.graphql +701 -2
  157. package/dist/sdk/actions/analytics-action-provider.d.ts +15 -0
  158. package/dist/sdk/actions/analytics-action-provider.js +19 -0
  159. package/dist/sdk/actions/analytics-action-provider.js.map +1 -0
  160. package/dist/sdk/actions/index.d.ts +3 -1
  161. package/dist/sdk/actions/index.js +3 -1
  162. package/dist/sdk/actions/payment-action-provider.d.ts +22 -0
  163. package/dist/sdk/actions/payment-action-provider.js +26 -0
  164. package/dist/sdk/actions/payment-action-provider.js.map +1 -0
  165. package/dist/sdk/actions/types.d.ts +6 -2
  166. package/dist/sdk/analytics/analytics-destination.d.ts +43 -0
  167. package/dist/sdk/analytics/analytics-destination.js +90 -0
  168. package/dist/sdk/analytics/analytics-destination.js.map +1 -0
  169. package/dist/sdk/analytics/use-analytics-actions.d.ts +57 -0
  170. package/dist/sdk/analytics/use-analytics-actions.js +108 -0
  171. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -0
  172. package/dist/sdk/cart/use-cart-actions.js +1 -1
  173. package/dist/sdk/data/entity-resolution-context.js +31 -0
  174. package/dist/sdk/data/entity-resolution-context.js.map +1 -0
  175. package/dist/sdk/data/entity-subscriber.js +18 -8
  176. package/dist/sdk/data/entity-subscriber.js.map +1 -1
  177. package/dist/sdk/data/index.js +1 -0
  178. package/dist/sdk/data/page-reactive-data-root.d.ts +36 -0
  179. package/dist/sdk/data/page-reactive-data-root.js +4 -2
  180. package/dist/sdk/data/page-reactive-data-root.js.map +1 -1
  181. package/dist/sdk/data/reactive-data-provider.js +7 -3
  182. package/dist/sdk/data/reactive-data-provider.js.map +1 -1
  183. package/dist/sdk/data/reactive-list-item-data-provider.js +12 -4
  184. package/dist/sdk/data/reactive-list-item-data-provider.js.map +1 -1
  185. package/dist/sdk/data/use-reactive-data-settled.js +39 -0
  186. package/dist/sdk/data/use-reactive-data-settled.js.map +1 -0
  187. package/dist/sdk/data/use-reactive-entity.js +5 -1
  188. package/dist/sdk/data/use-reactive-entity.js.map +1 -1
  189. package/dist/sdk/generated/fragment-types.js +2 -0
  190. package/dist/sdk/generated/fragment-types.js.map +1 -1
  191. package/dist/sdk/generated/graphql.js.map +1 -1
  192. package/dist/sdk/i18n/messages/en-US.js +3 -0
  193. package/dist/sdk/i18n/messages/en-US.json +3 -0
  194. package/dist/sdk/i18n/messages/en-US.json.d.ts +3 -0
  195. package/dist/sdk/i18n/messages/es-ES.js +3 -0
  196. package/dist/sdk/i18n/messages/es-ES.json +3 -0
  197. package/dist/sdk/i18n/messages/pt-BR.js +3 -0
  198. package/dist/sdk/i18n/messages/pt-BR.json +3 -0
  199. package/dist/sdk/identity/use-identity-actions.js +3 -2
  200. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  201. package/dist/sdk/payment/gateway-origin.js +35 -0
  202. package/dist/sdk/payment/gateway-origin.js.map +1 -0
  203. package/dist/sdk/payment/gateway.d.ts +85 -0
  204. package/dist/sdk/payment/gateway.js +183 -0
  205. package/dist/sdk/payment/gateway.js.map +1 -0
  206. package/dist/sdk/payment/operations.d.ts +52 -0
  207. package/dist/sdk/payment/operations.js +223 -0
  208. package/dist/sdk/payment/operations.js.map +1 -0
  209. package/dist/sdk/payment/security-code-mask.js +71 -0
  210. package/dist/sdk/payment/security-code-mask.js.map +1 -0
  211. package/dist/sdk/payment/submission-registry.d.ts +33 -0
  212. package/dist/sdk/payment/submission-registry.js +58 -0
  213. package/dist/sdk/payment/submission-registry.js.map +1 -0
  214. package/dist/sdk/payment/use-payment-actions.d.ts +78 -0
  215. package/dist/sdk/payment/use-payment-actions.js +276 -0
  216. package/dist/sdk/payment/use-payment-actions.js.map +1 -0
  217. package/dist/sdk/payment/use-place-order.d.ts +65 -0
  218. package/dist/sdk/payment/use-place-order.js +640 -0
  219. package/dist/sdk/payment/use-place-order.js.map +1 -0
  220. package/dist/sdk/shipping/use-shipping-actions.js +1 -1
  221. package/dist/utils/loggable-error-name.js +49 -0
  222. package/dist/utils/loggable-error-name.js.map +1 -0
  223. package/dist/utils/resolve-authored-copy.js +18 -0
  224. package/dist/utils/resolve-authored-copy.js.map +1 -0
  225. package/dist/utils/substitute-template.js +33 -0
  226. package/dist/utils/substitute-template.js.map +1 -0
  227. package/package.json +2 -1
  228. package/src/styles/tailwind.css +34 -0
  229. package/dist/docs/client-profile-form.md +0 -192
  230. package/dist/matter/client-profile-form/client-profile-form.d.ts +0 -126
  231. package/dist/matter/client-profile-form/client-profile-form.js +0 -662
  232. 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 { Field } from "../field/field.js";
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
- /** True when `type` is a document type this build supports (lets a host gate the field by locale). */
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 in DOCUMENTS;
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
- required: {
78
- type: "boolean",
79
- default: false,
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({ value, defaultValue = "", onValueChange, onBlur, invalid = false, errorMessage, label, placeholder, disabled = false, required = false, name = "document", documentType = "CPF", className, ...props }) {
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__ */ jsxs(Field.Root, {
224
+ return /* @__PURE__ */ jsx(DocumentFieldView, {
118
225
  ...props,
226
+ ref,
119
227
  className,
120
228
  name,
121
- invalid: showInvalid,
229
+ label,
230
+ placeholder,
122
231
  disabled,
123
- "data-fs-document-field": "",
124
- children: [
125
- label ? /* @__PURE__ */ jsx(Field.Label, { children: label }) : null,
126
- /* @__PURE__ */ jsx(Field.Control, {
127
- name,
128
- required,
129
- placeholder,
130
- disabled,
131
- value: displayValue,
132
- ...numericOnly ? { inputMode: "numeric" } : {},
133
- onChange: handleChange,
134
- onBlur: handleBlur
135
- }),
136
- showInvalid && errorMessage ? /* @__PURE__ */ jsx(Field.Error, {
137
- match: true,
138
- children: errorMessage
139
- }) : null
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 };