@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,13 +1,370 @@
1
1
  "use client";
2
2
  import { defineComponent } from "../../core/dist/define-component.js";
3
3
  import "../../core/dist/index.js";
4
+ import { Checkbox } from "../checkbox/checkbox.js";
5
+ import { registerFieldClearer } from "./bound-field-registry.js";
6
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef } from "react";
4
7
  import { jsx } from "react/jsx-runtime";
5
8
  import { Field } from "@base-ui/react/field";
9
+ import { useController, useFormContext, useFormState } from "react-hook-form";
6
10
  //#region src/particles/field/field.tsx
11
+ /**
12
+ * The one place `RegisterOptions` is named as a value type. `FieldRule`'s members are declared
13
+ * independently of react-hook-form's (Decision 5), but each already carries react-hook-form's own
14
+ * runtime shape (a bare value, or `{ value, message }`), so this is an assertion, not a per-member
15
+ * transform. `field-rule.type-test.tsx` pins the boundary from the other side: it fails to compile if
16
+ * `FieldRule` ever widens back to a bare `RegisterOptions` alias.
17
+ */
18
+ function toRegisterOptions(rule) {
19
+ return rule;
20
+ }
21
+ /**
22
+ * react-hook-form's own reading of `required`: `.value` for the object form
23
+ * (`{ value: boolean; message: string }`), plain truthiness otherwise (a boolean or a message
24
+ * string). A message string is itself the copy, which is why the two are read together.
25
+ */
26
+ function readBaseline(rule) {
27
+ const required = rule?.required;
28
+ if (typeof required === "object" && required !== null) return {
29
+ required: Boolean(required.value),
30
+ message: required.value ? required.message : void 0
31
+ };
32
+ if (typeof required === "string") return {
33
+ required: true,
34
+ message: required
35
+ };
36
+ return { required: Boolean(required) };
37
+ }
38
+ /** A `validate` member is either one function or a record of named ones; downstream only wants the record. */
39
+ function normaliseValidators(validate) {
40
+ if (!validate) return {};
41
+ return typeof validate === "function" ? { validate } : { ...validate };
42
+ }
43
+ /**
44
+ * Composes `validator` into `rule` under `name`, alongside whatever the rule already validates —
45
+ * never instead of it. The two composites reach for this to keep their own format check while a
46
+ * fieldset-supplied validator stays in force; before it, both discarded their own validation whole
47
+ * the moment the fieldset supplied any.
48
+ */
49
+ function mergeFieldValidator(rule, name, validator) {
50
+ return {
51
+ ...rule,
52
+ validate: {
53
+ ...normaliseValidators(rule?.validate),
54
+ [name]: validator
55
+ }
56
+ };
57
+ }
58
+ /**
59
+ * The one resolution of `required = baselineRequired || authorRaise`. The fieldset's `FieldRule` is
60
+ * the baseline; a control may raise a field to required and never lower one, so `raise.required`
61
+ * only ever adds. Read-only deliberately plays no part: a field that is read-only and required stays
62
+ * required, and an unsatisfiable form is the declared baseline's fault, not this function's.
63
+ *
64
+ * Baseline copy wins over the raise's, matching react-hook-form's own order — it evaluates `required`
65
+ * before `validate`, so the fieldset's message was already the one a shopper saw whenever both existed.
66
+ *
67
+ * `required` catches an empty string but not a space-filled one (`inputValue === ''` is
68
+ * react-hook-form's only emptiness test for a string), so the blank check goes in beside it. It is
69
+ * installed last, so it cannot be displaced by a same-named validator from the rule.
70
+ */
71
+ function resolveRequiredness(rule, raise) {
72
+ const baseline = readBaseline(rule);
73
+ const required = baseline.required || Boolean(raise?.required);
74
+ if (!required) return {
75
+ required: false,
76
+ rule
77
+ };
78
+ const message = baseline.message ?? (baseline.required ? void 0 : raise?.message);
79
+ const notBlank = (value) => String(value ?? "").trim().length > 0 || message || false;
80
+ return {
81
+ required,
82
+ message,
83
+ rule: {
84
+ ...mergeFieldValidator(rule, "notBlank", notBlank),
85
+ required: message ?? true
86
+ }
87
+ };
88
+ }
89
+ const FieldContext = createContext(null);
90
+ /** Reads the field contract. Returns null when no binding is above the caller. */
91
+ function useField() {
92
+ return useContext(FieldContext);
93
+ }
94
+ /**
95
+ * Per (control, field name), the seed the *fieldset supplied* for that name — `contract.defaultValue`
96
+ * as handed down, not the value any one part narrowed it to before calling `resetField`. The
97
+ * distinction only shows up where the RFC says two parts may bind one name ("A fieldset registers
98
+ * once, collecting the field names its own children mount, refcounted so that a part remounting, or
99
+ * two parts binding one name, never unregisters a name still in use"): a text part and a checkbox
100
+ * part over the same name narrow one seed to two different types, so a ledger holding the narrowed
101
+ * value disagrees with itself on every render — neither part's entry ever satisfies the other's
102
+ * `seedsEqual` check, and the two reset each other forever. Keyed on what the fieldset supplied, both
103
+ * parts read the same entry, the first to run adopts and the rest skip. The second part's own
104
+ * `useController` registration still carries its own narrowing, so it reads dirty against a baseline
105
+ * it did not set — the honest outcome, and better than a hang.
106
+ *
107
+ * Keyed by the `control` object itself rather than by any `FieldBinding` component instance. Adoption is a
108
+ * property of the field name within this form, not of whichever component instance happens to be
109
+ * mounted right now: a fieldset's own conditional control (the `documentType` gate on
110
+ * `ClientProfileDocumentControl`, a country-descriptor swap in `ShippingAddressFieldset`) unmounts and
111
+ * remounts its `FieldBinding` with a brand-new instance that remembers nothing, while the shopper's
112
+ * edit survives the unmount in `control._formValues` — nothing in this codebase sets
113
+ * `shouldUnregister`. A `useRef` latch scoped to the instance cannot see that history and re-adopts the
114
+ * stale seed over the surviving edit; this ledger can, because it outlives the instance. A `WeakMap` so
115
+ * a form that itself unmounts (a fresh `useForm()` call, a fresh `control`) starts over empty, exactly
116
+ * as it should.
117
+ *
118
+ * A `Map` of seeds, not a `Set` of names: recording only that a name was adopted cannot distinguish a
119
+ * seed already in force from a different one that supersedes it, so a later `defaultValue` could never
120
+ * reach the form — that was the whole bug this ledger replaces. `has(name)` is read separately from
121
+ * `get(name)` everywhere below, so "never adopted" and "adopted `undefined`" stay distinguishable
122
+ * states.
123
+ */
124
+ const adoptedSeeds = /* @__PURE__ */ new WeakMap();
125
+ function adoptedSeedsFor(control) {
126
+ let seeds = adoptedSeeds.get(control);
127
+ if (!seeds) {
128
+ seeds = /* @__PURE__ */ new Map();
129
+ adoptedSeeds.set(control, seeds);
130
+ }
131
+ return seeds;
132
+ }
133
+ /**
134
+ * Structural rather than referential equality between two seeds, mirroring `valuesEqual` in
135
+ * `form.tsx:128-130` and its documented reasoning: a field's seed is a primitive or plain record a
136
+ * bound Control's contract carries (a string, a boolean, a small object) — never a function or a
137
+ * circular structure — so a `JSON.stringify` comparison is a safe stand-in for a real deep-equal.
138
+ * Duplicated rather than imported: `field.tsx` imports nothing from `form.tsx`, and the particles are
139
+ * deliberately independent modules.
140
+ *
141
+ * `Object.is` is rejected here even though neither shipped shape's `normalise` (`TEXT_FIELD_VALUE`,
142
+ * `BOOLEAN_FIELD_VALUE`) can hand this function anything but a primitive today — this guard is
143
+ * defensive against a shape that does not exist yet, not against a reachable path. The day a shape's
144
+ * `normalise` returns a record or an array, a fieldset that hands out a freshly-allocated seed with
145
+ * the same content on every render would fail `Object.is` against itself, and the
146
+ * resetField → formState → render → resetField chain below would never terminate (measured: swapping
147
+ * in `Object.is` hung the suite past 150 seconds, until a render ceiling was added that turns the same
148
+ * mutation into a named failure in seconds). Do not remove this as dead code on the
149
+ * grounds that no object-shaped seed reaches it today — the regression test that pins it is
150
+ * `field.test.tsx`'s `'terminates when the seed is a fresh, structurally identical object literal on
151
+ * every render'`, which exercises `useSeedAdoption` through a test-only shape precisely because no
152
+ * shipped shape can.
153
+ */
154
+ function seedsEqual(a, b) {
155
+ return a === b || JSON.stringify(a) === JSON.stringify(b);
156
+ }
157
+ /**
158
+ * react-hook-form copies a per-field `defaultValue` into control._defaultValues only when
159
+ * shouldUnregister is set, which nothing here sets — so without this, the pristine comparison for a
160
+ * seeded field runs against `undefined` and any touched field stays dirty for good. Adopted once per
161
+ * (control, name) for a given seed, tracked in `adoptedSeeds`, which survives this component instance
162
+ * unmounting and remounting — so a remount with the same stale seed prop never overwrites a shopper's
163
+ * live edit, and a later, *different* seed reaches a field the shopper has not touched without the
164
+ * shopper having to reload the page.
165
+ *
166
+ * Called from `useBoundField`, not from `FieldBinding` (relocated, T57): the caller is now the very
167
+ * part whose own `useController` call registers `name`. `useController` calls `control.register(name,
168
+ * …)` from a `useRef` initialiser during the first render, and `register` adds to `_names.mount`
169
+ * synchronously — before that render returns, let alone before this effect's first run. So the mount
170
+ * guard below is satisfied on this effect's very first run for the caller's own name; the ordering
171
+ * `FieldBinding` could not rely on (a descendant control mounting on a *later* commit than the binding
172
+ * around it) no longer arises, because binder and adopter are now one and the same component instance.
173
+ * The T-5 test (`field.test.tsx`, a control that shows up on a later render than its enclosing
174
+ * `FieldBinding`) still passes — not because that race is still possible, but because nothing here
175
+ * special-cases it away: the guard simply reads true the one time it is asked.
176
+ *
177
+ * No dependency array all the same: this effect must still re-run on every render, because the guards
178
+ * below can otherwise miss their only remaining chance — the dirty-then-clean case. A seed arriving
179
+ * while the field is dirty is skipped by the third guard and never recorded; the only later signal that
180
+ * it can now be adopted is the field itself turning clean, which changes neither `name` nor the
181
+ * normalised seed this hook receives, so a dependency array keyed to its own arguments would never
182
+ * re-invoke it. Re-running on every render costs nothing extra here: every guard below but the
183
+ * adopting one is a no-op, and useFormContext() already forces a re-render on every formState emission
184
+ * regardless (Invariant 4 is violated in letter, not by this effect), so this adds no new re-render of
185
+ * its own.
186
+ *
187
+ * The first guard below is what does termination, not just correctness: react-hook-form rebuilds
188
+ * formState on every emission and FormProvider memoises its context value with formState in its own
189
+ * dependency list, so useFormContext()'s return value changes identity after every resetField call.
190
+ * Without that guard, that identity change would drive another render, which would call resetField
191
+ * again, which would change formState again — an unbounded loop. Its early return breaks it: once the
192
+ * ledger's recorded seed for this name is equal (by `seedsEqual`) to the current `defaultValue`, every
193
+ * further run for that name exits before reaching resetField — which is also why it must be the first
194
+ * check after the ledger lookup, ahead of even the mount guard.
195
+ *
196
+ * Precondition this re-seed relies on and does not itself enforce: `defaultValue` regressing to a
197
+ * reactive fragment's incomplete-read fallback once the field has already adopted real data.
198
+ * Probing this against `useReactiveEntity`'s fallback expression (`use-reactive-entity.ts:57`), mounting
199
+ * a clean field over real, complete data and then trying to make `fragmentResult.complete` flip back to
200
+ * false: measured as **reachable**, not merely suspected. Two of the three probes reproduced it —
201
+ * evicting a field the fragment selects (`cache.evict({ id, fieldName })`, then `cache.gc()`) and
202
+ * moving the root query's entity id to one never written to the cache (a session or orderForm change)
203
+ * both regress the read, and this effect cannot tell that apart from a genuine, saved change: the
204
+ * second probe blanked a clean control from "Ada" to "", and an independent audit reproduced it.
205
+ * `client.clearStore()` on a still-mounted, still-referenced entity did not reproduce it. The
206
+ * fieldsets close that gap above this hook: they withhold their controls until `useEntityDataResolved`
207
+ * reports their entity resolved, so a regressed read never reaches this effect through a fieldset.
208
+ */
209
+ function useSeedAdoption(name, suppliedSeed, narrowedSeed) {
210
+ const form = useFormContext();
211
+ useEffect(() => {
212
+ if (!form) return;
213
+ const seeds = adoptedSeedsFor(form.control);
214
+ if (seeds.has(name) && seedsEqual(seeds.get(name), suppliedSeed)) return;
215
+ if (!form.control._names.mount.has(name)) return;
216
+ if (seeds.has(name) && form.getFieldState(name).isDirty) return;
217
+ form.resetField(name, { defaultValue: narrowedSeed });
218
+ seeds.set(name, suppliedSeed);
219
+ });
220
+ }
221
+ function FieldBinding({ name, label, placeholder, defaultValue, rule, options, disabled, readOnly, autoComplete, inputMode, children }) {
222
+ const contract = useMemo(() => ({
223
+ name,
224
+ label,
225
+ placeholder,
226
+ defaultValue,
227
+ rule,
228
+ options,
229
+ disabled,
230
+ readOnly,
231
+ autoComplete,
232
+ inputMode
233
+ }), [
234
+ name,
235
+ label,
236
+ placeholder,
237
+ defaultValue,
238
+ rule,
239
+ options,
240
+ disabled,
241
+ readOnly,
242
+ autoComplete,
243
+ inputMode
244
+ ]);
245
+ return /* @__PURE__ */ jsx(FieldContext.Provider, {
246
+ value: contract,
247
+ children
248
+ });
249
+ }
250
+ /** A text-shaped bound field: empty is `''`, and a non-string seed narrows to `''`. */
251
+ const TEXT_FIELD_VALUE = {
252
+ empty: "",
253
+ normalise: (seed) => typeof seed === "string" ? seed : ""
254
+ };
255
+ /** A boolean-shaped bound field: empty is `false`, and any seed narrows through `Boolean(...)`. */
256
+ const BOOLEAN_FIELD_VALUE = {
257
+ empty: false,
258
+ normalise: (seed) => Boolean(seed)
259
+ };
260
+ /**
261
+ * The only caller of `useController` in this package (I-1). Registers the field with react-hook-form,
262
+ * adopts the same normalised seed as the dirty baseline (Decision 1), and registers a clearer that
263
+ * writes the shape's own empty value for as long as the part stays mounted (FR-5) — three facts a
264
+ * bound part previously scattered across its own call site, `FieldBinding` and `FormClear`.
265
+ *
266
+ * The effective rule is `rule ?? contract.rule`: a part with no rule of its own (`BoundFieldControl`,
267
+ * `BoundFieldCheckbox`, `BoundFieldSelectRoot`) falls back to the contract's; `BoundPhoneField` and
268
+ * `BoundDocumentField` pass their own `useMemo`-derived rule, which already merges the particle's
269
+ * format check into `contract.rule` rather than replacing it.
270
+ *
271
+ * Requiredness resolves here rather than at each call site (FR: one chokepoint), so no bound control
272
+ * can register a rule whose enforcement and announcement disagree, or forget the blank check. A
273
+ * control that wants its field required passes a `raise`; only `FieldSelectTrigger`, which announces
274
+ * from a different component than the one that binds, calls `resolveRequiredness` for itself.
275
+ */
276
+ function useBoundField(contract, shape, rule, raise) {
277
+ const { control, setValue } = useFormContext();
278
+ const raiseRequired = raise?.required;
279
+ const raiseMessage = raise?.message;
280
+ const requiredness = useMemo(() => resolveRequiredness(rule ?? contract.rule, {
281
+ required: raiseRequired,
282
+ message: raiseMessage
283
+ }), [
284
+ rule,
285
+ contract.rule,
286
+ raiseRequired,
287
+ raiseMessage
288
+ ]);
289
+ const seed = shape.normalise(contract.defaultValue);
290
+ const { field } = useController({
291
+ name: contract.name,
292
+ rules: toRegisterOptions(requiredness.rule),
293
+ defaultValue: seed
294
+ });
295
+ useSeedAdoption(contract.name, contract.defaultValue, seed);
296
+ useEffect(() => registerFieldClearer(control, contract.name, () => setValue(contract.name, shape.empty, { shouldDirty: true })), [
297
+ control,
298
+ contract.name,
299
+ setValue,
300
+ shape.empty
301
+ ]);
302
+ return {
303
+ ...field,
304
+ required: requiredness.required
305
+ };
306
+ }
307
+ /** Exported so those tasks import it rather than redefine it. */
308
+ const FieldsetFieldContext = createContext(null);
309
+ /** Unchanged: the registry above the caller, or null. Now carrying a namespace. */
310
+ function useFieldsetContext() {
311
+ return useContext(FieldsetFieldContext);
312
+ }
313
+ /**
314
+ * Registers `${namespace}.${key}` with the enclosing fieldset while the caller is mounted, and returns
315
+ * that full name. Throws during render when a registry is above the caller and declares a different
316
+ * namespace. Registers nothing and throws nothing when no registry is above. Holds no state: the
317
+ * refcount belongs to the provider.
318
+ */
319
+ function useFieldsetField(namespace, key) {
320
+ const registry = useFieldsetContext();
321
+ const name = `${namespace}.${key}`;
322
+ if (registry && registry.namespace !== namespace) throw new Error(`A control asked to register "${name}", but the fieldset registry above it owns the namespace "${registry.namespace}". A registry serves exactly one namespace, declared by the fieldset that provides it.`);
323
+ useEffect(() => {
324
+ if (!registry) return;
325
+ return registry.registerFieldName(name);
326
+ }, [registry, name]);
327
+ return name;
328
+ }
329
+ /**
330
+ * The field-name bookkeeping a fieldset provides. Refcounted, so two callers of one name collapse to one
331
+ * entry however they arrived — including a caller that reaches `registerFieldName` directly.
332
+ */
333
+ function useFieldsetFieldNames(namespace) {
334
+ const counts = useRef(/* @__PURE__ */ new Map());
335
+ const registerFieldName = useCallback((name) => {
336
+ counts.current.set(name, (counts.current.get(name) ?? 0) + 1);
337
+ let released = false;
338
+ return () => {
339
+ if (released) return;
340
+ released = true;
341
+ const remaining = (counts.current.get(name) ?? 0) - 1;
342
+ if (remaining > 0) counts.current.set(name, remaining);
343
+ else counts.current.delete(name);
344
+ };
345
+ }, []);
346
+ const getFieldNames = useCallback(() => Array.from(counts.current.keys()), []);
347
+ return {
348
+ registry: useMemo(() => ({
349
+ namespace,
350
+ registerFieldName
351
+ }), [namespace, registerFieldName]),
352
+ getFieldNames
353
+ };
354
+ }
355
+ /**
356
+ * Throws during render when an ancestor fieldset is already providing field registration for this
357
+ * subtree. Every fieldset calls it in one line.
358
+ */
359
+ function useNoAncestorFieldset(componentName) {
360
+ const ancestor = useFieldsetContext();
361
+ if (ancestor) throw new Error(`${componentName} cannot be nested inside another fieldset. An ancestor fieldset owning the namespace "${ancestor.namespace}" already provides field registration for this subtree, so this fieldset's controls would register under that namespace instead of their own. Compose fieldsets as siblings under the same FormRoot instead of nesting them.`);
362
+ }
7
363
  const FieldRootDefinition = defineComponent({
8
364
  name: "FieldRoot",
9
365
  importPath: "@vtex/faststore/components",
10
366
  component: FieldRoot,
367
+ providesContext: "field",
11
368
  editor: {
12
369
  label: "Field Root",
13
370
  category: "layout"
@@ -17,34 +374,37 @@ const FieldRootDefinition = defineComponent({
17
374
  default: false,
18
375
  label: "Disabled"
19
376
  } },
20
- guideline: "Form field container that connects a label, input, and validation messages via ARIA. Use Field.Control as the input element, or place a compatible Base UI input (Input, Checkbox, Select) as a direct child. Set invalid=true to trigger Field.Error display."
377
+ guideline: "Form field container that connects a label, input, and validation messages via ARIA. Use Field.Control as the input element, or place a compatible Base UI input (Input, Checkbox, Select) as a direct child. Set invalid=true to trigger Field.Error display. Wrapping a bound Control feeds invalid/dirty/touched from react-hook-form automatically."
21
378
  });
22
379
  const FieldLabelDefinition = defineComponent({
23
380
  name: "FieldLabel",
24
381
  importPath: "@vtex/faststore/components",
25
382
  component: FieldLabel,
383
+ requiresContext: "field",
26
384
  editor: {
27
385
  label: "Field Label",
28
386
  category: "layout"
29
387
  },
30
388
  schema: {},
31
- guideline: "Accessible label inside Field.Root. Automatically associated with Field.Control's input via ARIA. Place before Field.Control in the DOM."
389
+ guideline: "Accessible label inside Field.Root. Automatically associated with Field.Control's input via ARIA. Place before Field.Control in the DOM. Under a bound Control, a childless Field.Label renders the field contract's label."
32
390
  });
33
391
  const FieldControlDefinition = defineComponent({
34
392
  name: "FieldControl",
35
393
  importPath: "@vtex/faststore/components",
36
394
  component: FieldControl,
395
+ requiresContext: "field",
37
396
  editor: {
38
397
  label: "Field Control",
39
398
  category: "layout"
40
399
  },
41
400
  schema: {},
42
- guideline: "Renders a native `<input>` element directly. It IS the input — do not place any child inside it. Pass input attributes (type, placeholder, required, etc.) directly as props. It is automatically linked to Field.Label and Field.Error via ARIA."
401
+ guideline: "Renders a native `<input>` element directly. It IS the input — do not place any child inside it. Pass input attributes (type, placeholder, required, etc.) directly as props. It is automatically linked to Field.Label and Field.Error via ARIA. Under a bound Control, it registers with react-hook-form using the field contract's name, seed value, and validation rule."
43
402
  });
44
403
  const FieldDescriptionDefinition = defineComponent({
45
404
  name: "FieldDescription",
46
405
  importPath: "@vtex/faststore/components",
47
406
  component: FieldDescription,
407
+ requiresContext: "field",
48
408
  editor: {
49
409
  label: "Field Description",
50
410
  category: "layout"
@@ -56,17 +416,19 @@ const FieldErrorDefinition = defineComponent({
56
416
  name: "FieldError",
57
417
  importPath: "@vtex/faststore/components",
58
418
  component: FieldError,
419
+ requiresContext: "field",
59
420
  editor: {
60
421
  label: "Field Error",
61
422
  category: "layout"
62
423
  },
63
424
  schema: {},
64
- guideline: "Validation error message inside Field.Root. Shown when the field is in an invalid state. Linked to the control via aria-describedby."
425
+ guideline: "Validation error message inside Field.Root. Shown when the field is in an invalid state. Linked to the control via aria-describedby. Under a bound Control, it renders react-hook-form's error message for the field contract's name."
65
426
  });
66
427
  const FieldValidityDefinition = defineComponent({
67
428
  name: "FieldValidity",
68
429
  importPath: "@vtex/faststore/components",
69
430
  component: FieldValidity,
431
+ requiresContext: "field",
70
432
  editor: {
71
433
  label: "Field Validity",
72
434
  category: "layout"
@@ -74,7 +436,30 @@ const FieldValidityDefinition = defineComponent({
74
436
  schema: {},
75
437
  guideline: "Validation feedback slot using a render prop for custom messages derived from the field's validity state. Use for dynamic messages when Field.Error text is not fixed."
76
438
  });
439
+ const FieldCheckboxDefinition = defineComponent({
440
+ name: "FieldCheckbox",
441
+ importPath: "@vtex/faststore/components",
442
+ component: FieldCheckbox,
443
+ requiresContext: "field",
444
+ editor: {
445
+ label: "Field Checkbox",
446
+ category: "action"
447
+ },
448
+ schema: {},
449
+ guideline: "Bound boolean control over the shipped Checkbox particle: reads the enclosing field contract and drives Checkbox.Root through react-hook-form. Compose Checkbox.Indicator as a child for the visual check mark. Place inside Field.Root, the only ancestor that provides the field context it requires today — a bound Control will satisfy it too, once one ships."
450
+ });
77
451
  function FieldRoot({ disabled, invalid, name, className, children, ...props }) {
452
+ const contract = useField();
453
+ const formContext = useFormContext();
454
+ if (contract && formContext) return /* @__PURE__ */ jsx(BoundFieldRoot, {
455
+ ...props,
456
+ contract,
457
+ disabled,
458
+ invalid,
459
+ name,
460
+ className,
461
+ children
462
+ });
78
463
  return /* @__PURE__ */ jsx(Field.Root, {
79
464
  ...props,
80
465
  disabled,
@@ -85,7 +470,31 @@ function FieldRoot({ disabled, invalid, name, className, children, ...props }) {
85
470
  children
86
471
  });
87
472
  }
473
+ function BoundFieldRoot({ contract, disabled, invalid, name, className, children, ...props }) {
474
+ const { getFieldState, control } = useFormContext();
475
+ const { invalid: rhfInvalid, isDirty, isTouched } = getFieldState(contract.name, useFormState({ name: contract.name }));
476
+ const isRegistered = control._names.mount.has(contract.name);
477
+ return /* @__PURE__ */ jsx(Field.Root, {
478
+ ...props,
479
+ name: name ?? contract.name,
480
+ disabled: disabled ?? contract.disabled,
481
+ invalid: invalid ?? rhfInvalid,
482
+ dirty: isRegistered ? isDirty : void 0,
483
+ touched: isRegistered ? isTouched : void 0,
484
+ className,
485
+ "data-fs-field-root": "",
486
+ children
487
+ });
488
+ }
88
489
  function FieldLabel({ className, children, ...props }) {
490
+ const contract = useField();
491
+ const formContext = useFormContext();
492
+ if (contract && formContext) return /* @__PURE__ */ jsx(BoundFieldLabel, {
493
+ ...props,
494
+ contract,
495
+ className,
496
+ children
497
+ });
89
498
  return /* @__PURE__ */ jsx(Field.Label, {
90
499
  ...props,
91
500
  className,
@@ -93,9 +502,43 @@ function FieldLabel({ className, children, ...props }) {
93
502
  children
94
503
  });
95
504
  }
505
+ function BoundFieldLabel({ contract, className, children, ...props }) {
506
+ return /* @__PURE__ */ jsx(Field.Label, {
507
+ ...props,
508
+ className,
509
+ "data-fs-field-label": "",
510
+ children: children ?? contract.label
511
+ });
512
+ }
96
513
  function FieldControl({ className, ...props }) {
514
+ const contract = useField();
515
+ const formContext = useFormContext();
516
+ if (contract && formContext) return /* @__PURE__ */ jsx(BoundFieldControl, {
517
+ ...props,
518
+ contract,
519
+ className
520
+ });
521
+ return /* @__PURE__ */ jsx(Field.Control, {
522
+ ...props,
523
+ className,
524
+ "data-fs-field-control": ""
525
+ });
526
+ }
527
+ function BoundFieldControl({ contract, placeholder, disabled, readOnly, autoComplete, inputMode, className, ...props }) {
528
+ const field = useBoundField(contract, TEXT_FIELD_VALUE);
97
529
  return /* @__PURE__ */ jsx(Field.Control, {
98
530
  ...props,
531
+ name: field.name,
532
+ value: field.value,
533
+ onChange: field.onChange,
534
+ onBlur: field.onBlur,
535
+ ref: field.ref,
536
+ placeholder: placeholder ?? contract.placeholder,
537
+ disabled: disabled ?? contract.disabled,
538
+ readOnly: readOnly ?? contract.readOnly,
539
+ autoComplete: autoComplete ?? contract.autoComplete,
540
+ inputMode: inputMode ?? contract.inputMode,
541
+ "aria-required": field.required || void 0,
99
542
  className,
100
543
  "data-fs-field-control": ""
101
544
  });
@@ -109,6 +552,14 @@ function FieldDescription({ className, children, ...props }) {
109
552
  });
110
553
  }
111
554
  function FieldError({ className, children, ...props }) {
555
+ const contract = useField();
556
+ const formContext = useFormContext();
557
+ if (contract && formContext) return /* @__PURE__ */ jsx(BoundFieldError, {
558
+ ...props,
559
+ contract,
560
+ className,
561
+ children
562
+ });
112
563
  return /* @__PURE__ */ jsx(Field.Error, {
113
564
  ...props,
114
565
  className,
@@ -116,18 +567,58 @@ function FieldError({ className, children, ...props }) {
116
567
  children
117
568
  });
118
569
  }
570
+ function BoundFieldError({ contract, className, children, match: _match, ...props }) {
571
+ const { getFieldState } = useFormContext();
572
+ const { error } = getFieldState(contract.name, useFormState({ name: contract.name }));
573
+ if (!error) return null;
574
+ if (!children && !error.message) return null;
575
+ return /* @__PURE__ */ jsx(Field.Error, {
576
+ ...props,
577
+ match: true,
578
+ className,
579
+ "data-fs-field-error": "",
580
+ children: children ?? error.message
581
+ });
582
+ }
119
583
  function FieldValidity({ children }) {
120
584
  return /* @__PURE__ */ jsx(Field.Validity, { children });
121
585
  }
586
+ function FieldCheckbox({ className, children }) {
587
+ const contract = useField();
588
+ const formContext = useFormContext();
589
+ if (contract && formContext) return /* @__PURE__ */ jsx(BoundFieldCheckbox, {
590
+ contract,
591
+ className,
592
+ children
593
+ });
594
+ return /* @__PURE__ */ jsx(Checkbox.Root, {
595
+ className,
596
+ children
597
+ });
598
+ }
599
+ function BoundFieldCheckbox({ contract, className, children }) {
600
+ const field = useBoundField(contract, BOOLEAN_FIELD_VALUE);
601
+ return /* @__PURE__ */ jsx(Checkbox.Root, {
602
+ checked: field.value,
603
+ onCheckedChange: field.onChange,
604
+ onBlur: field.onBlur,
605
+ ref: field.ref,
606
+ disabled: contract.disabled,
607
+ className,
608
+ "data-fs-field-checkbox": "",
609
+ children
610
+ });
611
+ }
122
612
  const Field$1 = {
123
613
  Root: FieldRoot,
124
614
  Label: FieldLabel,
125
615
  Control: FieldControl,
126
616
  Description: FieldDescription,
127
617
  Error: FieldError,
128
- Validity: FieldValidity
618
+ Validity: FieldValidity,
619
+ Checkbox: FieldCheckbox
129
620
  };
130
621
  //#endregion
131
- export { Field$1 as Field, FieldControl, FieldControlDefinition, FieldDescription, FieldDescriptionDefinition, FieldError, FieldErrorDefinition, FieldLabel, FieldLabelDefinition, FieldRoot, FieldRootDefinition, FieldValidity, FieldValidityDefinition };
622
+ export { BOOLEAN_FIELD_VALUE, Field$1 as Field, FieldBinding, FieldCheckbox, FieldCheckboxDefinition, FieldControl, FieldControlDefinition, FieldDescription, FieldDescriptionDefinition, FieldError, FieldErrorDefinition, FieldLabel, FieldLabelDefinition, FieldRoot, FieldRootDefinition, FieldValidity, FieldValidityDefinition, FieldsetFieldContext, TEXT_FIELD_VALUE, mergeFieldValidator, resolveRequiredness, useBoundField, useField, useFieldsetContext, useFieldsetField, useFieldsetFieldNames, useNoAncestorFieldset };
132
623
 
133
624
  //# sourceMappingURL=field.js.map