@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
@@ -0,0 +1,485 @@
1
+ "use client";
2
+ import { defineComponent } from "../../core/dist/define-component.js";
3
+ import "../../core/dist/index.js";
4
+ import { useIntl } from "../../sdk/i18n/client.js";
5
+ import { clearBoundField } from "../field/bound-field-registry.js";
6
+ import { Button } from "../button/button.js";
7
+ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ import { FormProvider, useForm, useFormContext, useFormState } from "react-hook-form";
10
+ //#region src/particles/form/form.tsx
11
+ const FormRegistryContext = createContext(null);
12
+ /** Throws when no FormRoot is above the caller. */
13
+ function useFormRegistry() {
14
+ const registry = useContext(FormRegistryContext);
15
+ if (!registry) throw new Error("Form parts must be used inside FormRoot.");
16
+ return registry;
17
+ }
18
+ /**
19
+ * Internal-only seam between the six parts in this file. `FormClear` and `FormReset` iterate every
20
+ * registered fieldset's field names (Invariant 11 — the form reaches a field name only through a
21
+ * registration's `getFieldNames()`), which the public `FormRegistry` contract does not expose to a
22
+ * fieldset — it only ever calls `registerFieldset` and reads `failures`. Not exported from this
23
+ * module or the barrel.
24
+ */
25
+ const FieldsetSnapshotContext = createContext(null);
26
+ function useFieldsetSnapshot() {
27
+ const snapshot = useContext(FieldsetSnapshotContext);
28
+ if (!snapshot) throw new Error("Form parts must be used inside FormRoot.");
29
+ return snapshot;
30
+ }
31
+ /**
32
+ * A second, internal-only seam beside `FieldsetSnapshotContext`, for the same reason: `FormEnterSubmit`
33
+ * takes a form-wide keyboard allowance the public `FormRegistry` must not expose to a fieldset. Not
34
+ * exported from this module or the barrel.
35
+ */
36
+ const ImplicitSubmissionContext = createContext(null);
37
+ /**
38
+ * Takes an allowance token for the life of the calling component, so `handleKeyDown` allows implicit
39
+ * submission on this FormRoot's own text-entry inputs while at least one caller holds a token. Throws
40
+ * the same error as every other part when there is no FormRoot above it.
41
+ */
42
+ function useAllowImplicitSubmission() {
43
+ const allow = useContext(ImplicitSubmissionContext);
44
+ const [token] = useState(() => Symbol());
45
+ useEffect(() => {
46
+ if (!allow) return;
47
+ return allow(token);
48
+ }, [allow, token]);
49
+ if (!allow) throw new Error("Form parts must be used inside FormRoot.");
50
+ }
51
+ /**
52
+ * Two namespaces are in conflict when one equals, prefixes or is a descendant of the other —
53
+ * `profile` collides with itself and with `profile.address` alike, because a fieldset under either
54
+ * arrangement would dispatch on the other's edits and marshal its values into the wrong payload.
55
+ */
56
+ function namespacesCollide(a, b) {
57
+ if (a === b) return true;
58
+ return a.startsWith(`${b}.`) || b.startsWith(`${a}.`);
59
+ }
60
+ /**
61
+ * Is any of `names` dirty? React Hook Form nests `dirtyFields` by dot-path segment, so each full
62
+ * field name is walked segment by segment rather than read as a flat key.
63
+ *
64
+ * Callers pass the names a fieldset's mounted controls currently hold, not its whole namespace. With
65
+ * `shouldUnregister` unset, a control that has left the screen keeps its value and its dirty flag,
66
+ * and nothing resets it — reconciliation, `FormReset` and `FormClear` all act on mounted names only.
67
+ * Asking about the namespace would keep that flag reporting the fieldset dirty, so every later
68
+ * submit would dispatch again for a field the shopper can no longer see, and the same holds for a
69
+ * fieldset that has not mounted its controls yet.
70
+ */
71
+ function isAnyFieldDirty(dirtyFields, names) {
72
+ return names.some((name) => {
73
+ let node = dirtyFields;
74
+ for (const segment of name.split(".")) {
75
+ if (typeof node !== "object" || node === null) return false;
76
+ node = node[segment];
77
+ }
78
+ return Boolean(node);
79
+ });
80
+ }
81
+ /**
82
+ * Shallow-unsafe but adequate: field values are the primitives and plain records a bound Control
83
+ * produces (strings, booleans, small objects), never functions or circular structures, so a
84
+ * `JSON.stringify` comparison is a safe stand-in for a real deep-equal. Used only to tell whether a
85
+ * shopper's keystroke landed while a dispatch was in flight (Decision 6) — a false "changed" verdict
86
+ * merely means the field is resubmitted needlessly, never data loss.
87
+ */
88
+ function valuesEqual(a, b) {
89
+ return a === b || JSON.stringify(a) === JSON.stringify(b);
90
+ }
91
+ /**
92
+ * Ceiling on one fieldset's `dispatch`. A dispatch that neither resolves nor rejects — a fetch to a
93
+ * server that accepts the connection and never answers, for which browsers apply no default timeout —
94
+ * would otherwise leave `onValid` awaiting forever, so the submit boundary's `finally` never runs and
95
+ * the re-entrancy guard stays armed for the life of the page. This is not cancellation: the request
96
+ * keeps running and may still reach the server, and the form stops waiting for it.
97
+ *
98
+ * The ceiling is per fieldset, not per submit: `onValid` visits dirty fieldsets one at a time, in
99
+ * registration order, so a submit over *n* hung fieldsets takes up to *n* times this long to release.
100
+ * That is measured and accepted, not overlooked (Decision 3) — the wait stays bounded and
101
+ * self-releasing, since the submit boundary's `finally` always runs once the last ceiling fires.
102
+ */
103
+ const FORM_DISPATCH_TIMEOUT_MS = 3e4;
104
+ /**
105
+ * Resolves with the dispatch's own result, or rejects once `FORM_DISPATCH_TIMEOUT_MS` has elapsed,
106
+ * whichever happens first. The no-op handler on `dispatch` is what stops a late rejection surfacing as
107
+ * an unhandled rejection after the race has already been lost; the timer is cleared either way, so a
108
+ * dispatch that settles normally leaves nothing pending.
109
+ */
110
+ function withDispatchTimeout(dispatch) {
111
+ return new Promise((resolve, reject) => {
112
+ const timer = setTimeout(() => {
113
+ reject(/* @__PURE__ */ new Error(`Dispatch exceeded FORM_DISPATCH_TIMEOUT_MS (${FORM_DISPATCH_TIMEOUT_MS}ms).`));
114
+ }, FORM_DISPATCH_TIMEOUT_MS);
115
+ dispatch.then((userErrors) => {
116
+ clearTimeout(timer);
117
+ resolve(userErrors);
118
+ }, (error) => {
119
+ clearTimeout(timer);
120
+ reject(error);
121
+ });
122
+ });
123
+ }
124
+ /**
125
+ * The HTML spec's text-entry `<input>` types — the ones that block implicit submission for anything
126
+ * other than the field a shopper is typing in. An `<input>` with no `type` attribute reports
127
+ * `type === 'text'`, so it is covered without a separate case.
128
+ */
129
+ const TEXT_ENTRY_INPUT_TYPES = /* @__PURE__ */ new Set([
130
+ "text",
131
+ "search",
132
+ "url",
133
+ "tel",
134
+ "email",
135
+ "password",
136
+ "number",
137
+ "date",
138
+ "month",
139
+ "week",
140
+ "time",
141
+ "datetime-local"
142
+ ]);
143
+ const FormRootDefinition = defineComponent({
144
+ name: "FormRoot",
145
+ importPath: "@vtex/faststore/components",
146
+ component: FormRoot,
147
+ providesContext: "form",
148
+ editor: {
149
+ label: "Form",
150
+ category: "action"
151
+ },
152
+ schema: {},
153
+ guideline: "The only <form> its own fieldset registry dispatches from — a composed CartCouponForm, ShippingSimulatorForm or RegionForm renders its own, and a submit-origin check keeps theirs out of this one. Calls react-hook-form's useForm() once and holds the registry a submit dispatches in registration order. Compose FormSubmit/FormEnterSubmit/FormError/FormClear/FormReset anywhere inside it, and a fieldset as a descendant to give the submit something to persist — FormRoot itself declares no actions."
154
+ });
155
+ const FormSubmitDefinition = defineComponent({
156
+ name: "FormSubmit",
157
+ importPath: "@vtex/faststore/components",
158
+ component: FormSubmit,
159
+ requiresContext: "form",
160
+ editor: {
161
+ label: "Form Submit",
162
+ category: "action"
163
+ },
164
+ schema: { label: {
165
+ type: "string",
166
+ label: "Label"
167
+ } },
168
+ guideline: "Submits every registered fieldset the shopper changed, in registration order, and disables itself while a write is in flight. Renders children, then label, then the message catalog (fs/form.submit) — never a hard-coded English default. A fieldset with nothing changed contributes no request; an invalid field blocks every dispatch and moves focus to it instead of submitting. Enter in a text input does not submit — see FormEnterSubmit for a form where it does."
169
+ });
170
+ const FormEnterSubmitDefinition = defineComponent({
171
+ name: "FormEnterSubmit",
172
+ importPath: "@vtex/faststore/components",
173
+ component: FormEnterSubmit,
174
+ requiresContext: "form",
175
+ editor: {
176
+ label: "Form Enter Submit",
177
+ category: "action"
178
+ },
179
+ schema: { label: {
180
+ type: "string",
181
+ label: "Label"
182
+ } },
183
+ guideline: "Renders the same button as FormSubmit, sharing its children/label/fs/form.submit resolution, and additionally allows Enter to submit this form from any text-entry input while mounted. The allowance is form-scoped: placed inside one fieldset of a multi-fieldset FormRoot, it allows Enter for every text input in the enclosing form, not only that fieldset. Enter that commits an IME candidate never submits."
184
+ });
185
+ const FormErrorDefinition = defineComponent({
186
+ name: "FormError",
187
+ importPath: "@vtex/faststore/components",
188
+ component: FormError,
189
+ requiresContext: "form",
190
+ editor: {
191
+ label: "Form Error",
192
+ category: "content"
193
+ },
194
+ schema: { fallbackMessage: {
195
+ type: "string",
196
+ label: "Fallback message"
197
+ } },
198
+ guideline: "Renders the last submit's write failures grouped by fieldset, or nothing when there were none. Set fallbackMessage for a fieldset whose write rejected rather than resolving with userErrors."
199
+ });
200
+ const FormClearDefinition = defineComponent({
201
+ name: "FormClear",
202
+ importPath: "@vtex/faststore/components",
203
+ component: FormClear,
204
+ requiresContext: "form",
205
+ editor: {
206
+ label: "Form Clear",
207
+ category: "action"
208
+ },
209
+ schema: { label: {
210
+ type: "string",
211
+ label: "Label"
212
+ } },
213
+ guideline: "Empties every field registered by every fieldset inside the enclosing FormRoot, not only the nearest one, but marks dirty only the fields whose value actually changed — a field already empty stays clean. Renders children, then label, then the message catalog (fs/form.clear). The following submit dispatches a write for every fieldset that had something to clear, and nothing for one where clearing was a no-op. There is no way to scope it to a single fieldset; place it only where clearing the whole form is the intended action, not by analogy with a per-field clear."
214
+ });
215
+ const FormResetDefinition = defineComponent({
216
+ name: "FormReset",
217
+ importPath: "@vtex/faststore/components",
218
+ component: FormReset,
219
+ requiresContext: "form",
220
+ editor: {
221
+ label: "Form Reset",
222
+ category: "action"
223
+ },
224
+ schema: { label: {
225
+ type: "string",
226
+ label: "Label"
227
+ } },
228
+ guideline: "Returns every registered field to react-hook-form's baseline for that name and clears its dirty/touched state. Renders children, then label, then the message catalog (fs/form.reset). Before a fieldset's first successful write, that baseline is whatever the field's own seed resolves to — not empty — so Reset reverts to the original value rather than clearing it; only after a successful submit does the baseline become what was last written."
229
+ });
230
+ function FormRoot({ className, children, onKeyDown, ...props }) {
231
+ const form = useForm();
232
+ const { handleSubmit, formState: { dirtyFields, isSubmitting }, getValues, resetField } = form;
233
+ const fieldsets = useRef(/* @__PURE__ */ new Map());
234
+ /**
235
+ * Insertion order of every namespace ever registered, independent of `fieldsets`' own Map order.
236
+ * A namespace's slot here is created once and never moved — re-registering it (the cleanup-then-
237
+ * effect cycle a changed dep triggers) deletes and re-adds the `fieldsets` entry, which would push
238
+ * a plain `Map` iteration to the tail; reading dispatch order through this array instead keeps a
239
+ * namespace in its original position across any number of re-registrations.
240
+ */
241
+ const order = useRef([]);
242
+ const submitting = useRef(false);
243
+ const [failures, setFailures] = useState([]);
244
+ /**
245
+ * One token per mounted part that allows implicit submission (Decision 1). A `Set`, not a boolean or
246
+ * a count: two mounted allowing parts must compose, unmounting one must leave the allowance held by
247
+ * the other in place, and a token's cleanup — including a double-invoked one under StrictMode — must
248
+ * be a no-op once already removed. `handleKeyDown` only ever reads `.size`; nothing renders from it.
249
+ */
250
+ const allowance = useRef(/* @__PURE__ */ new Set());
251
+ const registerFieldset = useCallback((registration) => {
252
+ const { namespace } = registration;
253
+ for (const existing of fieldsets.current.values()) if (namespacesCollide(namespace, existing.namespace)) throw new Error(`FormRoot already has a fieldset registered under namespace "${existing.namespace}", which collides with "${namespace}". Two different fieldsets cannot share a namespace or nest one inside the other — re-registering the same namespace is only valid once its previous registration has been unregistered.`);
254
+ if (!order.current.includes(namespace)) order.current.push(namespace);
255
+ fieldsets.current.set(namespace, registration);
256
+ return () => {
257
+ if (fieldsets.current.get(namespace) === registration) fieldsets.current.delete(namespace);
258
+ };
259
+ }, []);
260
+ const getFieldsetSnapshot = useCallback(() => order.current.flatMap((namespace) => fieldsets.current.get(namespace) ?? []), []);
261
+ const registerAllowance = useCallback((token) => {
262
+ allowance.current.add(token);
263
+ return () => {
264
+ allowance.current.delete(token);
265
+ };
266
+ }, []);
267
+ const onValid = useCallback(async () => {
268
+ setFailures([]);
269
+ const collected = [];
270
+ const snapshot = getFieldsetSnapshot();
271
+ for (const fieldset of snapshot) {
272
+ let preDispatchValues;
273
+ let userErrors;
274
+ try {
275
+ const fieldNames = fieldset.getFieldNames();
276
+ if (!isAnyFieldDirty(dirtyFields, fieldNames)) continue;
277
+ preDispatchValues = fieldNames.map((name) => [name, getValues(name)]);
278
+ const payload = fieldset.toPayload(getValues(fieldset.namespace) ?? {});
279
+ userErrors = await withDispatchTimeout(fieldset.dispatch(payload));
280
+ } catch {
281
+ collected.push({
282
+ label: fieldset.label,
283
+ userErrors: []
284
+ });
285
+ continue;
286
+ }
287
+ if (userErrors.length > 0) {
288
+ collected.push({
289
+ label: fieldset.label,
290
+ userErrors
291
+ });
292
+ continue;
293
+ }
294
+ for (const [name, capturedValue] of preDispatchValues) if (valuesEqual(getValues(name), capturedValue)) resetField(name, { defaultValue: capturedValue });
295
+ }
296
+ setFailures(collected);
297
+ }, [
298
+ dirtyFields,
299
+ getValues,
300
+ resetField,
301
+ getFieldsetSnapshot
302
+ ]);
303
+ const onInvalid = useCallback(() => {
304
+ setFailures([]);
305
+ }, []);
306
+ /**
307
+ * Ignores a submit event whose origin isn't this FormRoot's own <form>. Submit events bubble, so a
308
+ * nested <form>'s own submission — the shape CartCouponForm, ShippingSimulatorForm and RegionForm
309
+ * each render when composed inside a checkout FormRoot — reaches this handler too; without this
310
+ * check, that bubbled event would run react-hook-form's handleSubmit and commit an entity write the
311
+ * shopper never asked this FormRoot to make. Mirrors handleKeyDown's own origin check (there scoped
312
+ * by `target.form`), for the same reason: a nested form is never this one.
313
+ *
314
+ * Two properties of where this check sits are load-bearing. First, it reads `event.currentTarget`
315
+ * before any `await`: React nulls `currentTarget` once the synchronous part of a handler returns,
316
+ * so the same comparison placed after an await degrades to `event.target !== null` — always true —
317
+ * and rejects every submit, a permanently dead submit button with no error raised. It needs no test
318
+ * of its own: inserting an await above this check fails most of the tests in form.test.tsx, and
319
+ * moving it below the awaited `handleSubmit(...)` below fails the nested-form test in *Implicit
320
+ * submission*. Second, it runs before the re-entrancy check immediately below: while this form's own
321
+ * write is in flight, `submitting.current` is `true`, so a bubbled nested submit that reached the
322
+ * re-entrancy check first would call `event.preventDefault()` and cancel a nested form's own
323
+ * submission that this FormRoot never rendered. The last test in *Re-entrancy* pins that second
324
+ * property; nothing caught it before that test existed.
325
+ */
326
+ const handleFormSubmit = useCallback(async (event) => {
327
+ if (event.target !== event.currentTarget) return;
328
+ if (submitting.current) {
329
+ event.preventDefault();
330
+ return;
331
+ }
332
+ submitting.current = true;
333
+ try {
334
+ await handleSubmit(onValid, onInvalid)(event);
335
+ } finally {
336
+ submitting.current = false;
337
+ }
338
+ }, [
339
+ handleSubmit,
340
+ onValid,
341
+ onInvalid
342
+ ]);
343
+ /**
344
+ * Cancels HTML implicit submission by default. A text input inside a form with a default button
345
+ * submits that form on Enter, which for an entity form means committing a write from a keystroke a
346
+ * shopper means as "look this up" or "next field" — before they have finished editing the rest of
347
+ * the sendable unit. That default is form-scoped and opt-in to override: mounting `FormEnterSubmit`
348
+ * anywhere inside this FormRoot allows Enter to submit from any of this form's own text-entry
349
+ * inputs, not only the fieldset the allowing part happens to sit in; a form composing only
350
+ * `FormSubmit` keeps today's cancelling behaviour. Enter that commits an IME candidate is cancelled
351
+ * either way, allowance or not, so choosing a word never submits a half-finished form. Submitting
352
+ * stays fully keyboard-operable either way, through a focusable native `FormSubmit`/`FormEnterSubmit`
353
+ * button, so no capability is removed (WCAG 2.1 SC 2.1.1). Scoped to text-entry inputs by target, so
354
+ * Enter on a button, a link, a `<textarea>`, a `contentEditable` region or a Base UI select trigger is
355
+ * untouched, and scoped to Enter, so typing is untouched. Also scoped to this form's own inputs by
356
+ * `target.form`: a keydown from a nested `<form>`'s own control bubbles up here too, and without that
357
+ * check this guard would cancel implicit submission for a form it never rendered. Composes rather
358
+ * than replaces an author-supplied `onKeyDown`: theirs runs first, unconditionally, before any of
359
+ * this guard's own checks — it can cancel Enter on an allowing form, but it cannot force Enter on a
360
+ * cancelling one, because this guard's own `preventDefault()` runs after it.
361
+ */
362
+ const handleKeyDown = useCallback((event) => {
363
+ onKeyDown?.(event);
364
+ if (event.key !== "Enter" || event.defaultPrevented) return;
365
+ const { target } = event;
366
+ if (!(target instanceof HTMLInputElement) || !TEXT_ENTRY_INPUT_TYPES.has(target.type)) return;
367
+ if (target.form !== event.currentTarget) return;
368
+ if (allowance.current.size > 0 && !event.nativeEvent.isComposing) return;
369
+ event.preventDefault();
370
+ }, [onKeyDown]);
371
+ const registry = useMemo(() => ({
372
+ registerFieldset,
373
+ failures
374
+ }), [registerFieldset, failures]);
375
+ return /* @__PURE__ */ jsx("form", {
376
+ ...props,
377
+ noValidate: true,
378
+ onSubmit: handleFormSubmit,
379
+ onKeyDown: handleKeyDown,
380
+ className,
381
+ "data-fs-form": "",
382
+ "data-fs-form-pending": isSubmitting ? "" : void 0,
383
+ children: /* @__PURE__ */ jsx(FormProvider, {
384
+ ...form,
385
+ children: /* @__PURE__ */ jsx(FormRegistryContext.Provider, {
386
+ value: registry,
387
+ children: /* @__PURE__ */ jsx(FieldsetSnapshotContext.Provider, {
388
+ value: getFieldsetSnapshot,
389
+ children: /* @__PURE__ */ jsx(ImplicitSubmissionContext.Provider, {
390
+ value: registerAllowance,
391
+ children
392
+ })
393
+ })
394
+ })
395
+ })
396
+ });
397
+ }
398
+ /**
399
+ * The shared render for both submit parts: same button, same hooks, same data-fs-form-submit hook.
400
+ * Text resolves `children ?? label ?? formatMessage(...)` (RFC sketch) — never a hard-coded English
401
+ * default — so an unauthored FormEnterSubmit reads correctly in every shipped locale, not only
402
+ * en-US. One id, `fs/form.submit`, covers both parts: they render the same button, so a second id
403
+ * for the Enter variant would only be a second translation of the same word.
404
+ */
405
+ function SubmitButton({ className, label, children }) {
406
+ const { isSubmitting } = useFormState();
407
+ const intl = useIntl();
408
+ return /* @__PURE__ */ jsx(Button, {
409
+ type: "submit",
410
+ disabled: isSubmitting,
411
+ className,
412
+ "data-fs-form-submit": "",
413
+ "data-fs-form-submit-pending": isSubmitting ? "" : void 0,
414
+ children: children ?? label ?? intl.formatMessage({ id: "fs/form.submit" })
415
+ });
416
+ }
417
+ function FormSubmit(props) {
418
+ useFormRegistry();
419
+ return /* @__PURE__ */ jsx(SubmitButton, { ...props });
420
+ }
421
+ /**
422
+ * Renders the same button as `FormSubmit` and additionally allows Enter to submit this FormRoot's own
423
+ * text-entry inputs while mounted — see `handleKeyDown`'s own doc for the form-scoped allowance and
424
+ * the IME carve-out.
425
+ */
426
+ function FormEnterSubmit(props) {
427
+ useFormRegistry();
428
+ useAllowImplicitSubmission();
429
+ return /* @__PURE__ */ jsx(SubmitButton, { ...props });
430
+ }
431
+ function FormError({ className, fallbackMessage = "Something went wrong. Please try again." }) {
432
+ const { failures } = useFormRegistry();
433
+ if (failures.length === 0) return null;
434
+ return /* @__PURE__ */ jsx("div", {
435
+ role: "alert",
436
+ className,
437
+ "data-fs-form-error": "",
438
+ children: failures.map((failure, failureIndex) => /* @__PURE__ */ jsx("ul", {
439
+ "data-fs-form-error-item": failure.label,
440
+ children: failure.userErrors.length > 0 ? failure.userErrors.map((error, errorIndex) => /* @__PURE__ */ jsx("li", { children: error.message }, `${failure.label}-${failureIndex}-${errorIndex}`)) : /* @__PURE__ */ jsx("li", { children: fallbackMessage })
441
+ }, `${failure.label}-${failureIndex}`))
442
+ });
443
+ }
444
+ function FormClear({ className, label, children }) {
445
+ const getFieldsets = useFieldsetSnapshot();
446
+ const { control } = useFormContext();
447
+ const intl = useIntl();
448
+ function handleClear() {
449
+ for (const fieldset of getFieldsets()) for (const name of fieldset.getFieldNames()) clearBoundField(control, name);
450
+ }
451
+ return /* @__PURE__ */ jsx(Button, {
452
+ type: "button",
453
+ onClick: handleClear,
454
+ className,
455
+ "data-fs-form-clear": "",
456
+ children: children ?? label ?? intl.formatMessage({ id: "fs/form.clear" })
457
+ });
458
+ }
459
+ function FormReset({ className, label, children }) {
460
+ const getFieldsets = useFieldsetSnapshot();
461
+ const { resetField } = useFormContext();
462
+ const intl = useIntl();
463
+ function handleReset() {
464
+ for (const fieldset of getFieldsets()) for (const name of fieldset.getFieldNames()) resetField(name);
465
+ }
466
+ return /* @__PURE__ */ jsx(Button, {
467
+ type: "button",
468
+ onClick: handleReset,
469
+ className,
470
+ "data-fs-form-reset": "",
471
+ children: children ?? label ?? intl.formatMessage({ id: "fs/form.reset" })
472
+ });
473
+ }
474
+ const Form = {
475
+ Root: FormRoot,
476
+ Submit: FormSubmit,
477
+ EnterSubmit: FormEnterSubmit,
478
+ Error: FormError,
479
+ Clear: FormClear,
480
+ Reset: FormReset
481
+ };
482
+ //#endregion
483
+ export { FORM_DISPATCH_TIMEOUT_MS, Form, FormClear, FormClearDefinition, FormEnterSubmit, FormEnterSubmitDefinition, FormError, FormErrorDefinition, FormRegistryContext, FormReset, FormResetDefinition, FormRoot, FormRootDefinition, FormSubmit, FormSubmitDefinition, useFormRegistry };
484
+
485
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","names":[],"sources":["../../../src/particles/form/form.tsx"],"sourcesContent":["'use client';\nimport { defineComponent } from '@vtex/fs-core';\nimport type { ComponentPropsWithRef, Context, FormEvent, KeyboardEvent, ReactNode } from 'react';\nimport { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';\nimport { FormProvider, useForm, useFormContext, useFormState } from 'react-hook-form';\nimport { useIntl } from '../../sdk/i18n/client.js';\nimport { Button } from '../button/button.js';\nimport { clearBoundField } from '../field/bound-field-registry.js';\n\n// ---------------------------------------------------------------------------\n// Data models\n// ---------------------------------------------------------------------------\n\n/**\n * The structural minimum every SDK user-error type satisfies. `code` is optional so that every\n * shipped shape is assignable, including `RegionUserError`, the one that declares it optional;\n * `ShippingAddressUpdateErrorEntity`, `ClientProfileUserError`, `CartUserError` and\n * `SessionUserError` all declare `Readonly<{ code: string; message: string }>`. There is no shared\n * user-error type in the SDK to reuse.\n */\nexport type FormUserError = Readonly<{ code?: string; message: string }>;\n\n/** What one fieldset hands the form. The form learns nothing else about it. */\nexport type FieldsetRegistration = Readonly<{\n /** Matches the field-name prefix every one of this fieldset's controls registers under. */\n namespace: string;\n /** Every full, namespaced field name this fieldset currently has mounted. */\n getFieldNames: () => readonly string[];\n /**\n * Closes over the fieldset's own injected `$actions`. Rejecting is allowed; the form catches it.\n * Must settle: the form gives up on a dispatch still pending after `FORM_DISPATCH_TIMEOUT_MS` and\n * reports it as a failed write. Must also be idempotent: giving up on a dispatch does not cancel it,\n * so a timed-out write may already be landing on the server, and the same payload is re-dispatched on\n * the shopper's next submit while the field stays dirty.\n */\n dispatch: (payload: Record<string, unknown>) => Promise<readonly FormUserError[]>;\n /** Shapes react-hook-form's raw values into the mutation's payload, including type coercion. */\n toPayload: (values: Record<string, unknown>) => Record<string, unknown>;\n /**\n * Machine identifier for this fieldset's write. `FormError` renders it as the\n * `data-fs-form-error-item` attribute value and uses it in two React keys; it is never displayed,\n * because the visible text is each `userError`'s own `message` or `FormError`'s `fallbackMessage`.\n * Its value is the kebab-case ASCII rendering of this registration's own `namespace`\n * (`shippingAddress` → `shipping-address`), so a stylesheet selecting\n * `[data-fs-form-error-item=\"…\"]` needs one predictable shape and no diacritics.\n */\n label: string;\n}>;\n\n/**\n * One fieldset's write that did not land. An empty `userErrors` means the dispatch rejected or never\n * settled within `FORM_DISPATCH_TIMEOUT_MS`; either way `FormError` renders `fallbackMessage`.\n */\nexport type FormWriteFailure = Readonly<{\n label: string;\n userErrors: readonly FormUserError[];\n}>;\n\nexport type FormRegistry = Readonly<{\n /** Stable for the life of the FormRoot. Returns the unregister function. */\n registerFieldset: (registration: FieldsetRegistration) => () => void;\n /** The last submit's failures, in dispatch order. */\n failures: readonly FormWriteFailure[];\n}>;\n\n// ---------------------------------------------------------------------------\n// Registry context\n// ---------------------------------------------------------------------------\n\nexport const FormRegistryContext: Context<FormRegistry | null> = createContext<FormRegistry | null>(null);\n\n/** Throws when no FormRoot is above the caller. */\nexport function useFormRegistry(): FormRegistry {\n const registry = useContext(FormRegistryContext);\n if (!registry) throw new Error('Form parts must be used inside FormRoot.');\n return registry;\n}\n\n/**\n * Internal-only seam between the six parts in this file. `FormClear` and `FormReset` iterate every\n * registered fieldset's field names (Invariant 11 — the form reaches a field name only through a\n * registration's `getFieldNames()`), which the public `FormRegistry` contract does not expose to a\n * fieldset — it only ever calls `registerFieldset` and reads `failures`. Not exported from this\n * module or the barrel.\n */\nconst FieldsetSnapshotContext = createContext<(() => readonly FieldsetRegistration[]) | null>(null);\n\nfunction useFieldsetSnapshot(): () => readonly FieldsetRegistration[] {\n const snapshot = useContext(FieldsetSnapshotContext);\n if (!snapshot) throw new Error('Form parts must be used inside FormRoot.');\n return snapshot;\n}\n\n/** Adds one token for as long as the caller is mounted; the returned cleanup removes that token. */\ntype AllowImplicitSubmission = (token: symbol) => () => void;\n\n/**\n * A second, internal-only seam beside `FieldsetSnapshotContext`, for the same reason: `FormEnterSubmit`\n * takes a form-wide keyboard allowance the public `FormRegistry` must not expose to a fieldset. Not\n * exported from this module or the barrel.\n */\nconst ImplicitSubmissionContext: Context<AllowImplicitSubmission | null> =\n createContext<AllowImplicitSubmission | null>(null);\n\n/**\n * Takes an allowance token for the life of the calling component, so `handleKeyDown` allows implicit\n * submission on this FormRoot's own text-entry inputs while at least one caller holds a token. Throws\n * the same error as every other part when there is no FormRoot above it.\n */\nfunction useAllowImplicitSubmission(): void {\n const allow = useContext(ImplicitSubmissionContext);\n // useState's lazy initialiser, not a ref written during render: it runs Symbol() exactly once, on\n // this component instance's first render, and every re-render reads back the same value.\n const [token] = useState(() => Symbol());\n\n useEffect(() => {\n if (!allow) return;\n return allow(token);\n }, [allow, token]);\n\n // All hooks above run unconditionally on every render; only this line is conditional, so throwing\n // here (rather than before the hooks) is not a rules-of-hooks violation.\n if (!allow) throw new Error('Form parts must be used inside FormRoot.');\n}\n\n/**\n * Two namespaces are in conflict when one equals, prefixes or is a descendant of the other —\n * `profile` collides with itself and with `profile.address` alike, because a fieldset under either\n * arrangement would dispatch on the other's edits and marshal its values into the wrong payload.\n */\nfunction namespacesCollide(a: string, b: string): boolean {\n if (a === b) return true;\n return a.startsWith(`${b}.`) || b.startsWith(`${a}.`);\n}\n\n/**\n * Is any of `names` dirty? React Hook Form nests `dirtyFields` by dot-path segment, so each full\n * field name is walked segment by segment rather than read as a flat key.\n *\n * Callers pass the names a fieldset's mounted controls currently hold, not its whole namespace. With\n * `shouldUnregister` unset, a control that has left the screen keeps its value and its dirty flag,\n * and nothing resets it — reconciliation, `FormReset` and `FormClear` all act on mounted names only.\n * Asking about the namespace would keep that flag reporting the fieldset dirty, so every later\n * submit would dispatch again for a field the shopper can no longer see, and the same holds for a\n * fieldset that has not mounted its controls yet.\n */\nfunction isAnyFieldDirty(dirtyFields: Record<string, unknown>, names: readonly string[]): boolean {\n return names.some((name) => {\n let node: unknown = dirtyFields;\n for (const segment of name.split('.')) {\n if (typeof node !== 'object' || node === null) return false;\n node = (node as Record<string, unknown>)[segment];\n }\n return Boolean(node);\n });\n}\n\n/**\n * Shallow-unsafe but adequate: field values are the primitives and plain records a bound Control\n * produces (strings, booleans, small objects), never functions or circular structures, so a\n * `JSON.stringify` comparison is a safe stand-in for a real deep-equal. Used only to tell whether a\n * shopper's keystroke landed while a dispatch was in flight (Decision 6) — a false \"changed\" verdict\n * merely means the field is resubmitted needlessly, never data loss.\n */\nfunction valuesEqual(a: unknown, b: unknown): boolean {\n return a === b || JSON.stringify(a) === JSON.stringify(b);\n}\n\n/**\n * Ceiling on one fieldset's `dispatch`. A dispatch that neither resolves nor rejects — a fetch to a\n * server that accepts the connection and never answers, for which browsers apply no default timeout —\n * would otherwise leave `onValid` awaiting forever, so the submit boundary's `finally` never runs and\n * the re-entrancy guard stays armed for the life of the page. This is not cancellation: the request\n * keeps running and may still reach the server, and the form stops waiting for it.\n *\n * The ceiling is per fieldset, not per submit: `onValid` visits dirty fieldsets one at a time, in\n * registration order, so a submit over *n* hung fieldsets takes up to *n* times this long to release.\n * That is measured and accepted, not overlooked (Decision 3) — the wait stays bounded and\n * self-releasing, since the submit boundary's `finally` always runs once the last ceiling fires.\n */\nexport const FORM_DISPATCH_TIMEOUT_MS = 30_000;\n\n/**\n * Resolves with the dispatch's own result, or rejects once `FORM_DISPATCH_TIMEOUT_MS` has elapsed,\n * whichever happens first. The no-op handler on `dispatch` is what stops a late rejection surfacing as\n * an unhandled rejection after the race has already been lost; the timer is cleared either way, so a\n * dispatch that settles normally leaves nothing pending.\n */\nfunction withDispatchTimeout(dispatch: Promise<readonly FormUserError[]>): Promise<readonly FormUserError[]> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`Dispatch exceeded FORM_DISPATCH_TIMEOUT_MS (${FORM_DISPATCH_TIMEOUT_MS}ms).`));\n }, FORM_DISPATCH_TIMEOUT_MS);\n\n dispatch.then(\n (userErrors) => {\n clearTimeout(timer);\n resolve(userErrors);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n\n/**\n * The HTML spec's text-entry `<input>` types — the ones that block implicit submission for anything\n * other than the field a shopper is typing in. An `<input>` with no `type` attribute reports\n * `type === 'text'`, so it is covered without a separate case.\n */\nconst TEXT_ENTRY_INPUT_TYPES = new Set([\n 'text',\n 'search',\n 'url',\n 'tel',\n 'email',\n 'password',\n 'number',\n 'date',\n 'month',\n 'week',\n 'time',\n 'datetime-local',\n]);\n\n// ---------------------------------------------------------------------------\n// Prop types\n// ---------------------------------------------------------------------------\n\nexport type FormRootProps = Omit<\n ComponentPropsWithRef<'form'>,\n 'onSubmit' | 'action' | 'method' | 'target' | 'encType' | 'dangerouslySetInnerHTML'\n>;\nexport type FormSubmitProps = { className?: string; label?: string; children?: ReactNode };\nexport type FormClearProps = { className?: string; label?: string; children?: ReactNode };\nexport type FormResetProps = { className?: string; label?: string; children?: ReactNode };\nexport type FormErrorProps = { className?: string; fallbackMessage?: string };\n\n// ---------------------------------------------------------------------------\n// Definitions\n// ---------------------------------------------------------------------------\n\nexport const FormRootDefinition = defineComponent<FormRootProps>({\n name: 'FormRoot',\n importPath: '@vtex/faststore/components',\n component: FormRoot,\n providesContext: 'form',\n editor: { label: 'Form', category: 'action' },\n schema: {},\n guideline:\n \"The only <form> its own fieldset registry dispatches from — a composed CartCouponForm, ShippingSimulatorForm or RegionForm renders its own, and a submit-origin check keeps theirs out of this one. Calls react-hook-form's useForm() once and holds the registry a submit dispatches in registration order. Compose FormSubmit/FormEnterSubmit/FormError/FormClear/FormReset anywhere inside it, and a fieldset as a descendant to give the submit something to persist — FormRoot itself declares no actions.\",\n});\n\nexport const FormSubmitDefinition = defineComponent<FormSubmitProps>({\n name: 'FormSubmit',\n importPath: '@vtex/faststore/components',\n component: FormSubmit,\n requiresContext: 'form',\n editor: { label: 'Form Submit', category: 'action' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'Submits every registered fieldset the shopper changed, in registration order, and disables itself while a write is in flight. Renders children, then label, then the message catalog (fs/form.submit) — never a hard-coded English default. A fieldset with nothing changed contributes no request; an invalid field blocks every dispatch and moves focus to it instead of submitting. Enter in a text input does not submit — see FormEnterSubmit for a form where it does.',\n});\n\nexport const FormEnterSubmitDefinition = defineComponent<FormSubmitProps>({\n name: 'FormEnterSubmit',\n importPath: '@vtex/faststore/components',\n component: FormEnterSubmit,\n requiresContext: 'form',\n editor: { label: 'Form Enter Submit', category: 'action' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'Renders the same button as FormSubmit, sharing its children/label/fs/form.submit resolution, and additionally allows Enter to submit this form from any text-entry input while mounted. The allowance is form-scoped: placed inside one fieldset of a multi-fieldset FormRoot, it allows Enter for every text input in the enclosing form, not only that fieldset. Enter that commits an IME candidate never submits.',\n});\n\nexport const FormErrorDefinition = defineComponent<FormErrorProps>({\n name: 'FormError',\n importPath: '@vtex/faststore/components',\n component: FormError,\n requiresContext: 'form',\n editor: { label: 'Form Error', category: 'content' },\n schema: {\n fallbackMessage: { type: 'string', label: 'Fallback message' },\n },\n guideline:\n \"Renders the last submit's write failures grouped by fieldset, or nothing when there were none. Set fallbackMessage for a fieldset whose write rejected rather than resolving with userErrors.\",\n});\n\nexport const FormClearDefinition = defineComponent<FormClearProps>({\n name: 'FormClear',\n importPath: '@vtex/faststore/components',\n component: FormClear,\n requiresContext: 'form',\n editor: { label: 'Form Clear', category: 'action' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n 'Empties every field registered by every fieldset inside the enclosing FormRoot, not only the nearest one, but marks dirty only the fields whose value actually changed — a field already empty stays clean. Renders children, then label, then the message catalog (fs/form.clear). The following submit dispatches a write for every fieldset that had something to clear, and nothing for one where clearing was a no-op. There is no way to scope it to a single fieldset; place it only where clearing the whole form is the intended action, not by analogy with a per-field clear.',\n});\n\nexport const FormResetDefinition = defineComponent<FormResetProps>({\n name: 'FormReset',\n importPath: '@vtex/faststore/components',\n component: FormReset,\n requiresContext: 'form',\n editor: { label: 'Form Reset', category: 'action' },\n schema: {\n label: { type: 'string', label: 'Label' },\n },\n guideline:\n \"Returns every registered field to react-hook-form's baseline for that name and clears its dirty/touched state. Renders children, then label, then the message catalog (fs/form.reset). Before a fieldset's first successful write, that baseline is whatever the field's own seed resolves to — not empty — so Reset reverts to the original value rather than clearing it; only after a successful submit does the baseline become what was last written.\",\n});\n\n// ---------------------------------------------------------------------------\n// Parts\n// ---------------------------------------------------------------------------\n\nexport function FormRoot({ className, children, onKeyDown, ...props }: FormRootProps): ReactNode {\n const form = useForm();\n const {\n handleSubmit,\n // `dirtyFields` is destructured here, in render, on purpose. React Hook Form keeps `dirtyFields`\n // current whether or not anything reads it — the proxy on `formState` decides only whether a\n // change here re-renders the subscriber, not whether the underlying store is kept. What the\n // render-time read buys is freshness: the submit handler below closes over the copy from the\n // render that defined it, and subscribing here is what keeps that copy current by the time a\n // later submit reads it. (The per-fieldset check further down walks each mounted field's\n // dot-path through this object rather than indexing it by a single key — see `isAnyFieldDirty`.)\n formState: { dirtyFields, isSubmitting },\n getValues,\n resetField,\n } = form;\n\n const fieldsets = useRef(new Map<string, FieldsetRegistration>());\n /**\n * Insertion order of every namespace ever registered, independent of `fieldsets`' own Map order.\n * A namespace's slot here is created once and never moved — re-registering it (the cleanup-then-\n * effect cycle a changed dep triggers) deletes and re-adds the `fieldsets` entry, which would push\n * a plain `Map` iteration to the tail; reading dispatch order through this array instead keeps a\n * namespace in its original position across any number of re-registrations.\n */\n const order = useRef<string[]>([]);\n const submitting = useRef(false);\n const [failures, setFailures] = useState<readonly FormWriteFailure[]>([]);\n /**\n * One token per mounted part that allows implicit submission (Decision 1). A `Set`, not a boolean or\n * a count: two mounted allowing parts must compose, unmounting one must leave the allowance held by\n * the other in place, and a token's cleanup — including a double-invoked one under StrictMode — must\n * be a no-op once already removed. `handleKeyDown` only ever reads `.size`; nothing renders from it.\n */\n const allowance = useRef(new Set<symbol>());\n\n const registerFieldset = useCallback((registration: FieldsetRegistration) => {\n const { namespace } = registration;\n\n for (const existing of fieldsets.current.values()) {\n if (namespacesCollide(namespace, existing.namespace)) {\n throw new Error(\n `FormRoot already has a fieldset registered under namespace \"${existing.namespace}\", which collides with \"${namespace}\". Two different fieldsets cannot share a namespace or nest one inside the other — re-registering the same namespace is only valid once its previous registration has been unregistered.`,\n );\n }\n }\n\n if (!order.current.includes(namespace)) {\n order.current.push(namespace);\n }\n fieldsets.current.set(namespace, registration);\n\n return () => {\n if (fieldsets.current.get(namespace) === registration) {\n fieldsets.current.delete(namespace);\n }\n };\n }, []);\n\n const getFieldsetSnapshot = useCallback(\n () => order.current.flatMap((namespace) => fieldsets.current.get(namespace) ?? []),\n [],\n );\n\n const registerAllowance = useCallback((token: symbol) => {\n allowance.current.add(token);\n return () => {\n allowance.current.delete(token);\n };\n }, []);\n\n const onValid = useCallback(async () => {\n setFailures([]);\n const collected: FormWriteFailure[] = [];\n const snapshot = getFieldsetSnapshot();\n\n for (const fieldset of snapshot) {\n let preDispatchValues: ReadonlyArray<readonly [string, unknown]>;\n let userErrors: readonly FormUserError[];\n try {\n const fieldNames = fieldset.getFieldNames();\n if (!isAnyFieldDirty(dirtyFields, fieldNames)) continue;\n preDispatchValues = fieldNames.map((name) => [name, getValues(name)] as const);\n const payload = fieldset.toPayload(getValues(fieldset.namespace) ?? {});\n userErrors = await withDispatchTimeout(fieldset.dispatch(payload));\n } catch {\n // Threw, timed out, or could not build its payload — collected as a failure either way, and\n // the queue moves on: the next fieldset's dispatch never depends on this one's code being\n // sound.\n collected.push({ label: fieldset.label, userErrors: [] });\n continue;\n }\n\n if (userErrors.length > 0) {\n collected.push({ label: fieldset.label, userErrors });\n continue;\n }\n\n // Reconcile against the values captured before the await, not the live ones (Decision 6).\n // A name whose live value still matches what was captured is safe to reconcile: the write\n // targeted exactly that value, so moving the baseline there is invisible. A name whose live\n // value has since diverged means a keystroke landed mid-write — it is left untouched, so it\n // stays dirty and is picked up, unsent, by the next submit.\n //\n // Deliberately outside the try above: the write already succeeded (no userErrors), so a\n // `resetField` that throws here is a reconciliation fault, not a write fault, and must not be\n // reported to the shopper as one.\n for (const [name, capturedValue] of preDispatchValues) {\n if (valuesEqual(getValues(name), capturedValue)) {\n resetField(name, { defaultValue: capturedValue });\n }\n }\n }\n\n setFailures(collected);\n }, [dirtyFields, getValues, resetField, getFieldsetSnapshot]);\n\n const onInvalid = useCallback(() => {\n setFailures([]);\n }, []);\n\n /**\n * Ignores a submit event whose origin isn't this FormRoot's own <form>. Submit events bubble, so a\n * nested <form>'s own submission — the shape CartCouponForm, ShippingSimulatorForm and RegionForm\n * each render when composed inside a checkout FormRoot — reaches this handler too; without this\n * check, that bubbled event would run react-hook-form's handleSubmit and commit an entity write the\n * shopper never asked this FormRoot to make. Mirrors handleKeyDown's own origin check (there scoped\n * by `target.form`), for the same reason: a nested form is never this one.\n *\n * Two properties of where this check sits are load-bearing. First, it reads `event.currentTarget`\n * before any `await`: React nulls `currentTarget` once the synchronous part of a handler returns,\n * so the same comparison placed after an await degrades to `event.target !== null` — always true —\n * and rejects every submit, a permanently dead submit button with no error raised. It needs no test\n * of its own: inserting an await above this check fails most of the tests in form.test.tsx, and\n * moving it below the awaited `handleSubmit(...)` below fails the nested-form test in *Implicit\n * submission*. Second, it runs before the re-entrancy check immediately below: while this form's own\n * write is in flight, `submitting.current` is `true`, so a bubbled nested submit that reached the\n * re-entrancy check first would call `event.preventDefault()` and cancel a nested form's own\n * submission that this FormRoot never rendered. The last test in *Re-entrancy* pins that second\n * property; nothing caught it before that test existed.\n */\n const handleFormSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>) => {\n if (event.target !== event.currentTarget) return;\n\n if (submitting.current) {\n event.preventDefault();\n return;\n }\n\n submitting.current = true;\n try {\n await handleSubmit(onValid, onInvalid)(event);\n } finally {\n // Released after the awaited handler, not inside onValid: react-hook-form flips\n // formState.isSubmitting to false in the statement immediately before its returned promise\n // settles, so releasing here keeps the guard armed for a superset of the window in which\n // isSubmitting is true. Releasing earlier — inside onValid's own finally, where this guard\n // used to live — would open a sliver in which a third submit is accepted while isSubmitting\n // is still true, reintroducing the defect this guard exists to close.\n submitting.current = false;\n }\n },\n [handleSubmit, onValid, onInvalid],\n );\n\n /**\n * Cancels HTML implicit submission by default. A text input inside a form with a default button\n * submits that form on Enter, which for an entity form means committing a write from a keystroke a\n * shopper means as \"look this up\" or \"next field\" — before they have finished editing the rest of\n * the sendable unit. That default is form-scoped and opt-in to override: mounting `FormEnterSubmit`\n * anywhere inside this FormRoot allows Enter to submit from any of this form's own text-entry\n * inputs, not only the fieldset the allowing part happens to sit in; a form composing only\n * `FormSubmit` keeps today's cancelling behaviour. Enter that commits an IME candidate is cancelled\n * either way, allowance or not, so choosing a word never submits a half-finished form. Submitting\n * stays fully keyboard-operable either way, through a focusable native `FormSubmit`/`FormEnterSubmit`\n * button, so no capability is removed (WCAG 2.1 SC 2.1.1). Scoped to text-entry inputs by target, so\n * Enter on a button, a link, a `<textarea>`, a `contentEditable` region or a Base UI select trigger is\n * untouched, and scoped to Enter, so typing is untouched. Also scoped to this form's own inputs by\n * `target.form`: a keydown from a nested `<form>`'s own control bubbles up here too, and without that\n * check this guard would cancel implicit submission for a form it never rendered. Composes rather\n * than replaces an author-supplied `onKeyDown`: theirs runs first, unconditionally, before any of\n * this guard's own checks — it can cancel Enter on an allowing form, but it cannot force Enter on a\n * cancelling one, because this guard's own `preventDefault()` runs after it.\n */\n const handleKeyDown = useCallback(\n (event: KeyboardEvent<HTMLFormElement>) => {\n onKeyDown?.(event);\n if (event.key !== 'Enter' || event.defaultPrevented) return;\n\n const { target } = event;\n if (!(target instanceof HTMLInputElement) || !TEXT_ENTRY_INPUT_TYPES.has(target.type)) return;\n if (target.form !== event.currentTarget) return;\n if (allowance.current.size > 0 && !event.nativeEvent.isComposing) return;\n\n event.preventDefault();\n },\n [onKeyDown],\n );\n\n const registry = useMemo<FormRegistry>(() => ({ registerFieldset, failures }), [registerFieldset, failures]);\n\n return (\n <form\n {...props}\n noValidate\n onSubmit={handleFormSubmit}\n onKeyDown={handleKeyDown}\n className={className}\n data-fs-form=\"\"\n data-fs-form-pending={isSubmitting ? '' : undefined}\n >\n <FormProvider {...form}>\n <FormRegistryContext.Provider value={registry}>\n <FieldsetSnapshotContext.Provider value={getFieldsetSnapshot}>\n <ImplicitSubmissionContext.Provider value={registerAllowance}>\n {children}\n </ImplicitSubmissionContext.Provider>\n </FieldsetSnapshotContext.Provider>\n </FormRegistryContext.Provider>\n </FormProvider>\n </form>\n );\n}\n\n/**\n * The shared render for both submit parts: same button, same hooks, same data-fs-form-submit hook.\n * Text resolves `children ?? label ?? formatMessage(...)` (RFC sketch) — never a hard-coded English\n * default — so an unauthored FormEnterSubmit reads correctly in every shipped locale, not only\n * en-US. One id, `fs/form.submit`, covers both parts: they render the same button, so a second id\n * for the Enter variant would only be a second translation of the same word.\n */\nfunction SubmitButton({ className, label, children }: FormSubmitProps): ReactNode {\n const { isSubmitting } = useFormState();\n const intl = useIntl();\n\n return (\n <Button\n type=\"submit\"\n disabled={isSubmitting}\n className={className}\n data-fs-form-submit=\"\"\n data-fs-form-submit-pending={isSubmitting ? '' : undefined}\n >\n {children ?? label ?? intl.formatMessage({ id: 'fs/form.submit' })}\n </Button>\n );\n}\n\nexport function FormSubmit(props: FormSubmitProps): ReactNode {\n useFormRegistry();\n return <SubmitButton {...props} />;\n}\n\n/**\n * Renders the same button as `FormSubmit` and additionally allows Enter to submit this FormRoot's own\n * text-entry inputs while mounted — see `handleKeyDown`'s own doc for the form-scoped allowance and\n * the IME carve-out.\n */\nexport function FormEnterSubmit(props: FormSubmitProps): ReactNode {\n useFormRegistry();\n useAllowImplicitSubmission();\n return <SubmitButton {...props} />;\n}\n\nexport function FormError({\n className,\n fallbackMessage = 'Something went wrong. Please try again.',\n}: FormErrorProps): ReactNode {\n const { failures } = useFormRegistry();\n\n if (failures.length === 0) return null;\n\n return (\n <div role=\"alert\" className={className} data-fs-form-error=\"\">\n {failures.map((failure, failureIndex) => (\n <ul key={`${failure.label}-${failureIndex}`} data-fs-form-error-item={failure.label}>\n {failure.userErrors.length > 0 ? (\n failure.userErrors.map((error, errorIndex) => (\n <li key={`${failure.label}-${failureIndex}-${errorIndex}`}>{error.message}</li>\n ))\n ) : (\n <li>{fallbackMessage}</li>\n )}\n </ul>\n ))}\n </div>\n );\n}\n\nexport function FormClear({ className, label, children }: FormClearProps): ReactNode {\n const getFieldsets = useFieldsetSnapshot();\n const { control } = useFormContext();\n const intl = useIntl();\n\n // Empties every field registered by every fieldset — not `FormRoot` recognising a field's type\n // (Invariant: it holds no field value and infers no field type). Each bound part registered its own\n // clearer, keyed by its own name, when `useBoundField` mounted it (`particles/field/field.tsx`); a\n // name with none registered — no bound part currently mounted for it — is left untouched.\n function handleClear() {\n for (const fieldset of getFieldsets()) {\n for (const name of fieldset.getFieldNames()) {\n clearBoundField(control, name);\n }\n }\n }\n\n return (\n <Button type=\"button\" onClick={handleClear} className={className} data-fs-form-clear=\"\">\n {children ?? label ?? intl.formatMessage({ id: 'fs/form.clear' })}\n </Button>\n );\n}\n\nexport function FormReset({ className, label, children }: FormResetProps): ReactNode {\n const getFieldsets = useFieldsetSnapshot();\n const { resetField } = useFormContext();\n const intl = useIntl();\n\n function handleReset() {\n for (const fieldset of getFieldsets()) {\n for (const name of fieldset.getFieldNames()) {\n resetField(name);\n }\n }\n }\n\n return (\n <Button type=\"button\" onClick={handleReset} className={className} data-fs-form-reset=\"\">\n {children ?? label ?? intl.formatMessage({ id: 'fs/form.reset' })}\n </Button>\n );\n}\n\nexport const Form = {\n Root: FormRoot,\n Submit: FormSubmit,\n EnterSubmit: FormEnterSubmit,\n Error: FormError,\n Clear: FormClear,\n Reset: FormReset,\n};\n"],"mappings":";;;;;;;;;;AAqEA,MAAa,sBAAoD,cAAmC,IAAI;;AAGxG,SAAgB,kBAAgC;CAC9C,MAAM,WAAW,WAAW,mBAAmB;CAC/C,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,0CAA0C;CACzE,OAAO;AACT;;;;;;;;AASA,MAAM,0BAA0B,cAA8D,IAAI;AAElG,SAAS,sBAA6D;CACpE,MAAM,WAAW,WAAW,uBAAuB;CACnD,IAAI,CAAC,UAAU,MAAM,IAAI,MAAM,0CAA0C;CACzE,OAAO;AACT;;;;;;AAUA,MAAM,4BACJ,cAA8C,IAAI;;;;;;AAOpD,SAAS,6BAAmC;CAC1C,MAAM,QAAQ,WAAW,yBAAyB;CAGlD,MAAM,CAAC,SAAS,eAAe,OAAO,CAAC;CAEvC,gBAAgB;EACd,IAAI,CAAC,OAAO;EACZ,OAAO,MAAM,KAAK;CACpB,GAAG,CAAC,OAAO,KAAK,CAAC;CAIjB,IAAI,CAAC,OAAO,MAAM,IAAI,MAAM,0CAA0C;AACxE;;;;;;AAOA,SAAS,kBAAkB,GAAW,GAAoB;CACxD,IAAI,MAAM,GAAG,OAAO;CACpB,OAAO,EAAE,WAAW,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,GAAG,EAAE,EAAE;AACtD;;;;;;;;;;;;AAaA,SAAS,gBAAgB,aAAsC,OAAmC;CAChG,OAAO,MAAM,MAAM,SAAS;EAC1B,IAAI,OAAgB;EACpB,KAAK,MAAM,WAAW,KAAK,MAAM,GAAG,GAAG;GACrC,IAAI,OAAO,SAAS,YAAY,SAAS,MAAM,OAAO;GACtD,OAAQ,KAAiC;EAC3C;EACA,OAAO,QAAQ,IAAI;CACrB,CAAC;AACH;;;;;;;;AASA,SAAS,YAAY,GAAY,GAAqB;CACpD,OAAO,MAAM,KAAK,KAAK,UAAU,CAAC,MAAM,KAAK,UAAU,CAAC;AAC1D;;;;;;;;;;;;;AAcA,MAAa,2BAA2B;;;;;;;AAQxC,SAAS,oBAAoB,UAAgF;CAC3G,OAAO,IAAI,SAAS,SAAS,WAAW;EACtC,MAAM,QAAQ,iBAAiB;GAC7B,uBAAO,IAAI,MAAM,+CAA+C,yBAAyB,KAAK,CAAC;EACjG,GAAG,wBAAwB;EAE3B,SAAS,MACN,eAAe;GACd,aAAa,KAAK;GAClB,QAAQ,UAAU;EACpB,IACC,UAAU;GACT,aAAa,KAAK;GAClB,OAAO,KAAK;EACd,CACF;CACF,CAAC;AACH;;;;;;AAOA,MAAM,yCAAyB,IAAI,IAAI;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAmBD,MAAa,qBAAqB,gBAA+B;CAC/D,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAQ,UAAU;CAAS;CAC5C,QAAQ,CAAC;CACT,WACE;AACJ,CAAC;AAED,MAAa,uBAAuB,gBAAiC;CACnE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAe,UAAU;CAAS;CACnD,QAAQ,EACN,OAAO;EAAE,MAAM;EAAU,OAAO;CAAQ,EAC1C;CACA,WACE;AACJ,CAAC;AAED,MAAa,4BAA4B,gBAAiC;CACxE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAqB,UAAU;CAAS;CACzD,QAAQ,EACN,OAAO;EAAE,MAAM;EAAU,OAAO;CAAQ,EAC1C;CACA,WACE;AACJ,CAAC;AAED,MAAa,sBAAsB,gBAAgC;CACjE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAc,UAAU;CAAU;CACnD,QAAQ,EACN,iBAAiB;EAAE,MAAM;EAAU,OAAO;CAAmB,EAC/D;CACA,WACE;AACJ,CAAC;AAED,MAAa,sBAAsB,gBAAgC;CACjE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAc,UAAU;CAAS;CAClD,QAAQ,EACN,OAAO;EAAE,MAAM;EAAU,OAAO;CAAQ,EAC1C;CACA,WACE;AACJ,CAAC;AAED,MAAa,sBAAsB,gBAAgC;CACjE,MAAM;CACN,YAAY;CACZ,WAAW;CACX,iBAAiB;CACjB,QAAQ;EAAE,OAAO;EAAc,UAAU;CAAS;CAClD,QAAQ,EACN,OAAO;EAAE,MAAM;EAAU,OAAO;CAAQ,EAC1C;CACA,WACE;AACJ,CAAC;AAMD,SAAgB,SAAS,EAAE,WAAW,UAAU,WAAW,GAAG,SAAmC;CAC/F,MAAM,OAAO,QAAQ;CACrB,MAAM,EACJ,cAQA,WAAW,EAAE,aAAa,gBAC1B,WACA,eACE;CAEJ,MAAM,YAAY,uBAAO,IAAI,IAAkC,CAAC;;;;;;;;CAQhE,MAAM,QAAQ,OAAiB,CAAC,CAAC;CACjC,MAAM,aAAa,OAAO,KAAK;CAC/B,MAAM,CAAC,UAAU,eAAe,SAAsC,CAAC,CAAC;;;;;;;CAOxE,MAAM,YAAY,uBAAO,IAAI,IAAY,CAAC;CAE1C,MAAM,mBAAmB,aAAa,iBAAuC;EAC3E,MAAM,EAAE,cAAc;EAEtB,KAAK,MAAM,YAAY,UAAU,QAAQ,OAAO,GAC9C,IAAI,kBAAkB,WAAW,SAAS,SAAS,GACjD,MAAM,IAAI,MACR,+DAA+D,SAAS,UAAU,0BAA0B,UAAU,yLACxH;EAIJ,IAAI,CAAC,MAAM,QAAQ,SAAS,SAAS,GACnC,MAAM,QAAQ,KAAK,SAAS;EAE9B,UAAU,QAAQ,IAAI,WAAW,YAAY;EAE7C,aAAa;GACX,IAAI,UAAU,QAAQ,IAAI,SAAS,MAAM,cACvC,UAAU,QAAQ,OAAO,SAAS;EAEtC;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,sBAAsB,kBACpB,MAAM,QAAQ,SAAS,cAAc,UAAU,QAAQ,IAAI,SAAS,KAAK,CAAC,CAAC,GACjF,CAAC,CACH;CAEA,MAAM,oBAAoB,aAAa,UAAkB;EACvD,UAAU,QAAQ,IAAI,KAAK;EAC3B,aAAa;GACX,UAAU,QAAQ,OAAO,KAAK;EAChC;CACF,GAAG,CAAC,CAAC;CAEL,MAAM,UAAU,YAAY,YAAY;EACtC,YAAY,CAAC,CAAC;EACd,MAAM,YAAgC,CAAC;EACvC,MAAM,WAAW,oBAAoB;EAErC,KAAK,MAAM,YAAY,UAAU;GAC/B,IAAI;GACJ,IAAI;GACJ,IAAI;IACF,MAAM,aAAa,SAAS,cAAc;IAC1C,IAAI,CAAC,gBAAgB,aAAa,UAAU,GAAG;IAC/C,oBAAoB,WAAW,KAAK,SAAS,CAAC,MAAM,UAAU,IAAI,CAAC,CAAU;IAC7E,MAAM,UAAU,SAAS,UAAU,UAAU,SAAS,SAAS,KAAK,CAAC,CAAC;IACtE,aAAa,MAAM,oBAAoB,SAAS,SAAS,OAAO,CAAC;GACnE,QAAQ;IAIN,UAAU,KAAK;KAAE,OAAO,SAAS;KAAO,YAAY,CAAC;IAAE,CAAC;IACxD;GACF;GAEA,IAAI,WAAW,SAAS,GAAG;IACzB,UAAU,KAAK;KAAE,OAAO,SAAS;KAAO;IAAW,CAAC;IACpD;GACF;GAWA,KAAK,MAAM,CAAC,MAAM,kBAAkB,mBAClC,IAAI,YAAY,UAAU,IAAI,GAAG,aAAa,GAC5C,WAAW,MAAM,EAAE,cAAc,cAAc,CAAC;EAGtD;EAEA,YAAY,SAAS;CACvB,GAAG;EAAC;EAAa;EAAW;EAAY;CAAmB,CAAC;CAE5D,MAAM,YAAY,kBAAkB;EAClC,YAAY,CAAC,CAAC;CAChB,GAAG,CAAC,CAAC;;;;;;;;;;;;;;;;;;;;;CAsBL,MAAM,mBAAmB,YACvB,OAAO,UAAsC;EAC3C,IAAI,MAAM,WAAW,MAAM,eAAe;EAE1C,IAAI,WAAW,SAAS;GACtB,MAAM,eAAe;GACrB;EACF;EAEA,WAAW,UAAU;EACrB,IAAI;GACF,MAAM,aAAa,SAAS,SAAS,CAAC,CAAC,KAAK;EAC9C,UAAU;GAOR,WAAW,UAAU;EACvB;CACF,GACA;EAAC;EAAc;EAAS;CAAS,CACnC;;;;;;;;;;;;;;;;;;;;CAqBA,MAAM,gBAAgB,aACnB,UAA0C;EACzC,YAAY,KAAK;EACjB,IAAI,MAAM,QAAQ,WAAW,MAAM,kBAAkB;EAErD,MAAM,EAAE,WAAW;EACnB,IAAI,EAAE,kBAAkB,qBAAqB,CAAC,uBAAuB,IAAI,OAAO,IAAI,GAAG;EACvF,IAAI,OAAO,SAAS,MAAM,eAAe;EACzC,IAAI,UAAU,QAAQ,OAAO,KAAK,CAAC,MAAM,YAAY,aAAa;EAElE,MAAM,eAAe;CACvB,GACA,CAAC,SAAS,CACZ;CAEA,MAAM,WAAW,eAA6B;EAAE;EAAkB;CAAS,IAAI,CAAC,kBAAkB,QAAQ,CAAC;CAE3G,OACE,oBAAC,QAAD;EACE,GAAI;EACJ,YAAA;EACA,UAAU;EACV,WAAW;EACA;EACX,gBAAa;EACb,wBAAsB,eAAe,KAAK,KAAA;EAE1C,UAAA,oBAAC,cAAD;GAAc,GAAI;GAChB,UAAA,oBAAC,oBAAoB,UAArB;IAA8B,OAAO;IACnC,UAAA,oBAAC,wBAAwB,UAAzB;KAAkC,OAAO;KACvC,UAAA,oBAAC,0BAA0B,UAA3B;MAAoC,OAAO;MACxC;KACiC,CAAA;IACJ,CAAA;GACN,CAAA;EAClB,CAAA;CACV,CAAA;AAEV;;;;;;;;AASA,SAAS,aAAa,EAAE,WAAW,OAAO,YAAwC;CAChF,MAAM,EAAE,iBAAiB,aAAa;CACtC,MAAM,OAAO,QAAQ;CAErB,OACE,oBAAC,QAAD;EACE,MAAK;EACL,UAAU;EACC;EACX,uBAAoB;EACpB,+BAA6B,eAAe,KAAK,KAAA;EAEhD,UAAA,YAAY,SAAS,KAAK,cAAc,EAAE,IAAI,iBAAiB,CAAC;CAC3D,CAAA;AAEZ;AAEA,SAAgB,WAAW,OAAmC;CAC5D,gBAAgB;CAChB,OAAO,oBAAC,cAAD,EAAc,GAAI,MAAQ,CAAA;AACnC;;;;;;AAOA,SAAgB,gBAAgB,OAAmC;CACjE,gBAAgB;CAChB,2BAA2B;CAC3B,OAAO,oBAAC,cAAD,EAAc,GAAI,MAAQ,CAAA;AACnC;AAEA,SAAgB,UAAU,EACxB,WACA,kBAAkB,6CACU;CAC5B,MAAM,EAAE,aAAa,gBAAgB;CAErC,IAAI,SAAS,WAAW,GAAG,OAAO;CAElC,OACE,oBAAC,OAAD;EAAK,MAAK;EAAmB;EAAW,sBAAmB;EACxD,UAAA,SAAS,KAAK,SAAS,iBACtB,oBAAC,MAAD;GAA6C,2BAAyB,QAAQ;GAC3E,UAAA,QAAQ,WAAW,SAAS,IAC3B,QAAQ,WAAW,KAAK,OAAO,eAC7B,oBAAC,MAAD,EAAA,UAA4D,MAAM,QAAY,GAArE,GAAG,QAAQ,MAAM,GAAG,aAAa,GAAG,YAAiC,CAC/E,IAED,oBAAC,MAAD,EAAA,UAAK,gBAAoB,CAAA;EAEzB,GARK,GAAG,QAAQ,MAAM,GAAG,cAQzB,CACL;CACE,CAAA;AAET;AAEA,SAAgB,UAAU,EAAE,WAAW,OAAO,YAAuC;CACnF,MAAM,eAAe,oBAAoB;CACzC,MAAM,EAAE,YAAY,eAAe;CACnC,MAAM,OAAO,QAAQ;CAMrB,SAAS,cAAc;EACrB,KAAK,MAAM,YAAY,aAAa,GAClC,KAAK,MAAM,QAAQ,SAAS,cAAc,GACxC,gBAAgB,SAAS,IAAI;CAGnC;CAEA,OACE,oBAAC,QAAD;EAAQ,MAAK;EAAS,SAAS;EAAwB;EAAW,sBAAmB;EAClF,UAAA,YAAY,SAAS,KAAK,cAAc,EAAE,IAAI,gBAAgB,CAAC;CAC1D,CAAA;AAEZ;AAEA,SAAgB,UAAU,EAAE,WAAW,OAAO,YAAuC;CACnF,MAAM,eAAe,oBAAoB;CACzC,MAAM,EAAE,eAAe,eAAe;CACtC,MAAM,OAAO,QAAQ;CAErB,SAAS,cAAc;EACrB,KAAK,MAAM,YAAY,aAAa,GAClC,KAAK,MAAM,QAAQ,SAAS,cAAc,GACxC,WAAW,IAAI;CAGrB;CAEA,OACE,oBAAC,QAAD;EAAQ,MAAK;EAAS,SAAS;EAAwB;EAAW,sBAAmB;EAClF,UAAA,YAAY,SAAS,KAAK,cAAc,EAAE,IAAI,gBAAgB,CAAC;CAC1D,CAAA;AAEZ;AAEA,MAAa,OAAO;CAClB,MAAM;CACN,QAAQ;CACR,aAAa;CACb,OAAO;CACP,OAAO;CACP,OAAO;AACT"}