@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,832 @@
1
+ "use client";
2
+ import { defineComponent } from "../../core/dist/define-component.js";
3
+ import "../../core/dist/index.js";
4
+ import { Field } from "../../particles/field/field.js";
5
+ import { Icon } from "../../particles/icon/icon.js";
6
+ import { Fieldset } from "../../particles/fieldset/fieldset.js";
7
+ import { useCardCapture, useRegisterCardCapture } from "./card-capture.js";
8
+ import { applyMask } from "../../particles/shared/input-mask.js";
9
+ import "../../particles/document-field/document-field.js";
10
+ import { SECURITY_CODE_MASK_STYLE, isSecurityCodeComplete } from "../../sdk/payment/security-code-mask.js";
11
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ //#region src/matter/card-entry/card-entry.tsx
14
+ /** Visual/attribute order used for first-invalid focus and the `data-fs-card-entry-field` values. */
15
+ const FIELD_ATTR = {
16
+ number: "number",
17
+ holderName: "holder-name",
18
+ expiration: "expiration",
19
+ cvv: "cvv"
20
+ };
21
+ const VALIDATION_ORDER = [
22
+ "number",
23
+ "holderName",
24
+ "expiration",
25
+ "cvv"
26
+ ];
27
+ const DEFAULT_VALIDATOR = {
28
+ numberMask: null,
29
+ numberRegex: null,
30
+ cvvMask: null,
31
+ cvvRegex: null,
32
+ cvvRequired: true,
33
+ expirationRequired: true,
34
+ holderNameRequired: true
35
+ };
36
+ const DEFAULT_NUMBER_MASK = "9999 9999 9999 9999";
37
+ const DEFAULT_EXPIRATION_MASK = "99/99";
38
+ const DEFAULT_CVV_MASK = "999";
39
+ function countDigitSlots(mask) {
40
+ return mask.split("").filter((char) => char === "9").length;
41
+ }
42
+ /** Slot capacity of a (possibly pipe-delimited) mask — the widest alternative's count (F13). */
43
+ function maskCapacity(mask) {
44
+ return Math.max(...mask.split("|").map(countDigitSlots));
45
+ }
46
+ /**
47
+ * The narrowest alternative's slot count — `maskCapacity`'s floor, and the length at which a
48
+ * security code counts as fully typed (T173).
49
+ *
50
+ * The ceiling is the right answer for truncation (`maskDigits` must let the widest alternative's
51
+ * digits through) and the wrong one for masking: one method id can cover a 3-and-4-digit family with
52
+ * a single `'999|9999'` mask, and a shopper holding the 3-digit card of that family never reaches a
53
+ * fourth digit — so a ceiling-derived length leaves their complete, valid code legible for the rest
54
+ * of the flow. `SavedCardCvvField` errs on the same side for the same reason. A malformed
55
+ * alternative with no slots (`'999|'`) floors to zero, which masks from the first digit: early,
56
+ * never late.
57
+ */
58
+ function narrowestMaskCapacity(mask) {
59
+ return Math.min(...mask.split("|").map(countDigitSlots));
60
+ }
61
+ function toDigits(value) {
62
+ return value.replace(/\D/g, "");
63
+ }
64
+ /**
65
+ * Strips non-digits, truncates to the mask's slot capacity, then applies it progressively. A
66
+ * pipe-delimited mask (`applyMask` already selects among the alternatives internally) needs the
67
+ * *widest* alternative's capacity here (F13) — counting `9`s across the whole pipe-joined string, as
68
+ * before this fix, sums every alternative's slots instead of taking the largest one, letting far more
69
+ * digits through than any single alternative can hold. Mirrors `document-field.tsx`'s own
70
+ * `Math.max(...mask.split('|').map(countSlots))` for the identical reason.
71
+ */
72
+ function maskDigits(rawValue, mask) {
73
+ const digitsOnly = toDigits(rawValue);
74
+ return applyMask(digitsOnly.slice(0, maskCapacity(mask)), mask);
75
+ }
76
+ /** A malformed store-configured regex is swallowed (treated as no match), never thrown. */
77
+ function safeTest(pattern, value) {
78
+ try {
79
+ return new RegExp(pattern).test(value);
80
+ } catch {
81
+ return false;
82
+ }
83
+ }
84
+ /**
85
+ * Relaxes a store's `numberRegex` so it matches on a BIN-length prefix instead of only once the
86
+ * shopper has typed the network's full PAN length (F1, specs/card-entry.md Decision 1). T1's own
87
+ * regexes are full-length anchored — the real Visa validator is `^4[0-9]{12}(?:[0-9]{3})?$` (13 or
88
+ * 16 digits, exactly) — so testing the *whole* typed digit string against it as-is cannot resolve
89
+ * before the last keystroke, and resolves intermittently for lengths in between (13 matches, 14–15
90
+ * don't, 16 matches again): the badge, and every field gated on `detectedMethod`, flicker on, off,
91
+ * and on again while the shopper is still typing. Dropping the trailing `$` and widening every
92
+ * bounded repeat quantifier to also accept fewer repetitions (`{12}` → `{0,12}`, `{n,m}` → `{0,m}`,
93
+ * `{n,}` → `{0,}`) keeps every *fixed* discriminating character exactly as strict as the store
94
+ * configured (Mastercard's `[1-5]` second digit still requires that literal digit once typed), while
95
+ * letting the length-representing tail resolve as soon as it's admissible rather than only once it's
96
+ * complete. Live detection only — the unmodified regex is still what `validateDraft` re-tests at
97
+ * submission time.
98
+ */
99
+ function relaxForLiveDetection(pattern) {
100
+ return pattern.replace(/\$$/, "").replace(/\{(\d+)(,)?(\d+)?\}/g, (_match, min, hasComma, max) => hasComma && max === void 0 ? "{0,}" : `{0,${max ?? min}}`);
101
+ }
102
+ /** Every method whose *relaxed* (BIN-prefix) transform of `validator.numberRegex` admits `digits`. */
103
+ function relaxedCandidates(methods, digits) {
104
+ return methods.filter((method) => method.validator.numberRegex && safeTest(relaxForLiveDetection(method.validator.numberRegex), digits));
105
+ }
106
+ /**
107
+ * First method whose *exact*, unrelaxed `validator.numberRegex` matches `digits` in full — the same
108
+ * test `validateDraft` re-runs at submission time.
109
+ */
110
+ function exactMatch(methods, digits) {
111
+ return methods.find((method) => method.validator.numberRegex && safeTest(method.validator.numberRegex, digits)) ?? null;
112
+ }
113
+ /**
114
+ * First method whose `validator.numberRegex` admits `digits` as a BIN-level prefix, or `null`.
115
+ *
116
+ * N1: relaxing every store regex into a pure prefix test (F1, above) means two networks whose fixed
117
+ * prefixes *nest* — one network's whole discriminating prefix is itself a prefix of a different,
118
+ * longer network's real PAN — both keep "relax-matching" once the shorter network's fixed prefix is
119
+ * satisfied, including at the longer network's own full, syntactically valid length, where the exact,
120
+ * unrelaxed regex alone correctly discriminates by length. Left as pure first-match-wins over the
121
+ * relaxed test, the answer at full PAN length silently depends on which order the store's `methods`
122
+ * happen to arrive in — something this component does not control.
123
+ *
124
+ * The fix tries the **exact** regex first, across every method in `methods` order, and only falls back
125
+ * to the relaxed (prefix) match for input too short to satisfy any exact pattern yet. This restores the
126
+ * exact regex's authority the instant a full PAN is present, which resolves the nesting-prefix case
127
+ * above deterministically regardless of order. **It is not, however, order-independent in general**
128
+ * (T22, V2): two configured networks' exact patterns can both match the very same full-length input —
129
+ * e.g. a real Visa regex and an Elo BIN range both match `4011788888888888` (Elo is issued on
130
+ * Visa-range BINs) — and `exactMatch` below is a plain `methods.find(...)`, so **the first exact match
131
+ * in `methods` order wins the tie**. This is pre-existing (pre-N1 code used the same `find` over exact
132
+ * regexes) and is not introduced or fixed by N1; N1 only shrank how much of the input space stays
133
+ * order-dependent. A caller that needs a specific tie winner must order `methods` by specificity itself
134
+ * before calling — this function does not infer specificity from a regex. See also `resolveNumberMask`
135
+ * below: this function alone cannot help if the field's own mask capacity has already been capped
136
+ * narrower than the digit count an exact match needs to resolve, and see `resolveNetworkMethod` below
137
+ * `resolveNumberMask` (T22, V3/V4) for this component's own order-independent wrapper around the
138
+ * *relaxed*-fallback branch, which this component's own call sites use instead of calling this function
139
+ * directly.
140
+ */
141
+ function detectNetworkMethod(methods, digits) {
142
+ if (!digits) return null;
143
+ return exactMatch(methods, digits) ?? relaxedCandidates(methods, digits)[0] ?? null;
144
+ }
145
+ /**
146
+ * The number mask to apply for the currently typed `digits` (N1, part 2 — the mechanism Part 1 alone
147
+ * cannot fix). Masking with only the single method `detectNetworkMethod` currently resolves to can cap
148
+ * the field's capacity below what a *different*, still-relaxed-matching method would need once more
149
+ * digits arrive — e.g. a 14-digit network's own mask, while the shopper is mid-typing a 19-digit
150
+ * network whose fixed prefix nests inside the shorter one's. Once that happens, every keystroke past 14
151
+ * truncates straight back down to 14 digits, forever — the shopper can never reach the length at which
152
+ * the exact regex (Part 1, above) would resolve correctly, trading a cosmetic mis-mask for a hard block.
153
+ *
154
+ * Never resolves narrower than the widest `numberMask` among every method whose *relaxed* regex still
155
+ * matches, so the field's capacity always keeps pace with the longest network still in contention; only
156
+ * once a network drops out of contention (or an exact match resolves) does the mask narrow back down.
157
+ */
158
+ function resolveNumberMask(methods, digits) {
159
+ const candidateMasks = relaxedCandidates(methods, digits).map((method) => method.validator.numberMask).filter((mask) => Boolean(mask));
160
+ if (candidateMasks.length === 0) return DEFAULT_NUMBER_MASK;
161
+ return candidateMasks.reduce((widest, mask) => maskCapacity(mask) > maskCapacity(widest) ? mask : widest);
162
+ }
163
+ /**
164
+ * The method this component treats as "detected" at the current `digits` — the single source both the
165
+ * render-scoped badge/validator and `handleNumberChange`'s dispatch decision read from, so the two can
166
+ * never disagree (FR-2). See the banner above for why this is not `detectNetworkMethod(methods, digits)`
167
+ * called directly: the exact-match branch here always uses `methods` in the store's own configured
168
+ * order (T22-2 — ties are the store's call, never a derived signal), and the relaxed-fallback branch
169
+ * only resolves when it is unambiguous, rather than silently favoring whichever candidate array order
170
+ * happens to put first (T22-1).
171
+ */
172
+ function resolveNetworkMethod(methods, digits) {
173
+ if (!digits) return null;
174
+ const exact = exactMatch(methods, digits);
175
+ if (exact) return exact;
176
+ const candidates = relaxedCandidates(methods, digits);
177
+ return candidates.length === 1 ? candidates[0] ?? null : null;
178
+ }
179
+ /** The length this component slices its BIN to — the same eight digits `gateway.ts` sends as the
180
+ * payment body's BIN. */
181
+ const BIN_LENGTH = 8;
182
+ /**
183
+ * The BIN a selection carries: the first `BIN_LENGTH` of `digits`, or `null` while `digits` holds
184
+ * fewer — no selection is sent for a partial BIN. Callers pass the masked field's own digits, so "the
185
+ * gate is open" and "the BIN is complete" are one fact. Exported for direct unit testing; the package
186
+ * barrel (`src/components.ts`) does not re-export it.
187
+ */
188
+ function resolveCompleteBin(digits) {
189
+ return digits.length >= BIN_LENGTH ? digits.slice(0, BIN_LENGTH) : null;
190
+ }
191
+ /**
192
+ * Whether the N3/V4 effect should re-publish `detectedMethod` now that `draft.path` is `'new-card'`.
193
+ *
194
+ * - `previousPath === 'saved-card'`: only a genuine *return* from the saved-card detour re-dispatches —
195
+ * the very first activation transitions `null` → `'new-card'` too, and `handleNumberChange`'s own
196
+ * dispatch already covers that first selection; re-dispatching here as well would double-count it.
197
+ * - `revealedByCardMode` (T191, FR-12): the other return — the card payment was collapsed on the
198
+ * previous commit and card mode is what brought it back. Omitted means it was not. Both returns share
199
+ * this one guard and the effect's one send, so they never both send for one commit.
200
+ * - `detectedMethod !== null`: nothing to (re-)publish yet.
201
+ * - `lastDispatched === null`: `handleNumberChange` has not already dispatched this exact commit (V4) —
202
+ * otherwise this would double-post the identical `{ methodId, bin }` pair.
203
+ */
204
+ function shouldRedispatchOnReturnToNewCard({ previousPath, revealedByCardMode = false, detectedMethod, lastDispatched }) {
205
+ return (previousPath === "saved-card" || revealedByCardMode) && detectedMethod !== null && lastDispatched === null;
206
+ }
207
+ const NETWORK_ICON_BY_KEY = {
208
+ visa: "visa",
209
+ mastercard: "mastercard",
210
+ amex: "amex",
211
+ americanexpress: "amex",
212
+ diners: "diners",
213
+ dinersclub: "diners",
214
+ elo: "elo-card"
215
+ };
216
+ function networkIconName(name) {
217
+ const key = name.toLowerCase().replace(/[^a-z]/g, "");
218
+ return NETWORK_ICON_BY_KEY[key] ?? null;
219
+ }
220
+ /**
221
+ * Standard Luhn checksum. Rejects non-digit strings and anything shorter than 12 digits — no real
222
+ * card network issues a PAN shorter than that.
223
+ */
224
+ function isValidLuhn(digits) {
225
+ if (digits.length < 12 || !/^\d+$/.test(digits)) return false;
226
+ let sum = 0;
227
+ let double = false;
228
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
229
+ let digit = Number(digits[i]);
230
+ if (double) {
231
+ digit *= 2;
232
+ if (digit > 9) digit -= 9;
233
+ }
234
+ sum += digit;
235
+ double = !double;
236
+ }
237
+ return sum % 10 === 0;
238
+ }
239
+ const EXPIRATION_FORMAT = /^(0[1-9]|1[0-2])\/(\d{2})$/;
240
+ /** `MM/YY` structural check: month 01–12, two-digit year. */
241
+ function isExpirationFormatValid(value) {
242
+ return EXPIRATION_FORMAT.test(value);
243
+ }
244
+ /**
245
+ * True when the printed `MM/YY` has not yet ended — cards are valid through the end of their
246
+ * printed month. `now` is injectable for deterministic tests.
247
+ */
248
+ function isExpirationInFuture(value, now = /* @__PURE__ */ new Date()) {
249
+ const match = EXPIRATION_FORMAT.exec(value);
250
+ if (!match) return false;
251
+ const month = Number(match[1]);
252
+ const year = 2e3 + Number(match[2]);
253
+ return new Date(year, month, 0, 23, 59, 59, 999).getTime() >= now.getTime();
254
+ }
255
+ /**
256
+ * Normalizes a 4-digit-year expiration (`MM/YYYY`, e.g. from a browser/password-manager `cc-exp`
257
+ * autofill, or the equivalent pasted digits) down to `MM/YY` before masking (F3) — mirrors
258
+ * `gateway.ts`'s own `toDueDate` two-slice scheme (month = first two digits, year = last two).
259
+ * Without this, the mask's blind 4-digit slot cap truncated `122030` (from an autofilled `12/2030`)
260
+ * to `1220` — keeping the *first* two digits of the year instead of the last two — silently turning
261
+ * a card valid through 2030 into one that reads as already expired in 2020.
262
+ *
263
+ * T172, Decision 5: the recombination applies to a genuine six-digit shape **only**. Applied to any
264
+ * count above four — F3's original rule — it also claims the far more common overflow case: the
265
+ * field already holds a complete `MM/YY` and the shopper types one more digit, so the raw value is
266
+ * five digits (`12/25` + `9` → `12259`). Recombining that yields `12` + `59` = `12/59`: the year's
267
+ * first digit is dropped, the second slides down a place, and the new digit takes the tens place — a
268
+ * value the shopper never typed as a sequence, with no keystroke ever visibly refused.
269
+ *
270
+ * Digit count alone still separates the three real input shapes (typed digit-by-digit ≤ 4,
271
+ * autofill/paste of `MM/YYYY` = 6, accidental overflow = anything else). Fix round 1 (review finding
272
+ * #1): every other length refuses the keystroke by returning `previousDigits` — whatever the field
273
+ * already held — rather than `digits.slice(0, 4)`. Slicing the *new* raw string only reconstructs the
274
+ * original value when the intruding digit lands at the very end; caught anywhere else (e.g. the caret
275
+ * placed right after the `/`), the first four characters of the new string mix a surviving old digit
276
+ * with the intruding one, silently producing a date the shopper never typed or saw form (`12/30`,
277
+ * caret after the `/`, types `9` → raw `12930` → `slice(0, 4)` = `1293` → `12/93`). Returning
278
+ * `previousDigits` makes the refusal caret-position independent: the displayed value is provably
279
+ * unchanged, not merely unchanged for the one caret position a naive slice happens to get right. No
280
+ * known browser or password manager delivers `MM/YYYY` in anything but six digits; a future variant is
281
+ * a deliberate, reviewed addition here rather than a silent gap.
282
+ */
283
+ function normalizeExpirationDigits(digits, previousDigits) {
284
+ if (digits.length <= 4) return digits;
285
+ if (digits.length === 6) return `${digits.slice(0, 2)}${digits.slice(-2)}`;
286
+ return previousDigits;
287
+ }
288
+ function isValidCvv(value, validator) {
289
+ if (validator.cvvRegex) return safeTest(validator.cvvRegex, value);
290
+ return /^\d{3,4}$/.test(value);
291
+ }
292
+ /**
293
+ * Whether network detection could ever resolve to `method`. A method with no `numberRegex` never can
294
+ * (`resolveNetworkMethod`/`detectNetworkMethod` both require one): a promissory line of credit, a store
295
+ * credit balance, or a card-group method configured without a pattern. Not the section's card
296
+ * expression, `isCardMethod`, which counts that last one as a card (T191, Decision 1 and FR-14).
297
+ *
298
+ * Its one caller is `validateDraft`'s own `networkDetectionPossible` gate below (the F7/N2
299
+ * validation-message fix) — a single call site reads as dead weight here, and it is not.
300
+ */
301
+ function isCardShapedMethod(method) {
302
+ return Boolean(method.validator.numberRegex);
303
+ }
304
+ function validateDraft(draft, validator, methods, messages) {
305
+ const errors = {};
306
+ const digits = toDigits(draft.number);
307
+ const networkDetectionPossible = methods.some(isCardShapedMethod);
308
+ if (!digits) errors.number = messages.numberRequired;
309
+ else if (!isValidLuhn(digits)) errors.number = messages.numberInvalid;
310
+ else if (networkDetectionPossible && !(validator.numberRegex && safeTest(validator.numberRegex, digits))) errors.number = messages.numberInvalid;
311
+ if (validator.holderNameRequired && !draft.holderName.trim()) errors.holderName = messages.holderNameRequired;
312
+ if (validator.expirationRequired) {
313
+ if (!draft.expiration.trim()) errors.expiration = messages.expirationRequired;
314
+ else if (!isExpirationFormatValid(draft.expiration)) errors.expiration = messages.expirationInvalid;
315
+ else if (!isExpirationInFuture(draft.expiration)) errors.expiration = messages.expirationExpired;
316
+ }
317
+ if (validator.cvvRequired) {
318
+ if (!draft.cvv.trim()) errors.cvv = messages.cvvRequired;
319
+ else if (!isValidCvv(draft.cvv, validator)) errors.cvv = messages.cvvInvalid;
320
+ }
321
+ return errors;
322
+ }
323
+ const CARD_ENTRY_INVALID_DATA_CODE = "INVALID_CARD_DATA";
324
+ const INVALID_CARD_DATA_ERROR = {
325
+ code: CARD_ENTRY_INVALID_DATA_CODE,
326
+ message: "Enter valid card details before placing the order."
327
+ };
328
+ /**
329
+ * The submission and pre-flight refusal for a card form the shopper cannot currently see (T74,
330
+ * Decision 5), and for a visible form's valid card under a persisted method that cannot take it: one
331
+ * this section does not serve, one that is not a card (T191, FR-13), or another card network than the
332
+ * typed number's (T191 fix round 1, finding R1). Distinct from `INVALID_CARD_DATA`: the card may be
333
+ * perfectly valid — the section is simply not on a method this form can pay it with. A fourth
334
+ * client-synthesized `CardCaptureError` in `matter/`, beside `CARD_ENTRY_INVALID_DATA_CODE`.
335
+ */
336
+ const CARD_ENTRY_COLLAPSED_CODE = "CARD_ENTRY_COLLAPSED";
337
+ const CARD_ENTRY_COLLAPSED_ERROR = {
338
+ code: CARD_ENTRY_COLLAPSED_CODE,
339
+ message: "This card is not available for the currently selected payment method."
340
+ };
341
+ const CardEntryDefinition = defineComponent({
342
+ name: "CardEntry",
343
+ importPath: "@vtex/faststore/components",
344
+ component: CardEntry,
345
+ requiresContext: "card-capture",
346
+ editor: {
347
+ label: "Card Entry",
348
+ category: "commerce"
349
+ },
350
+ schema: {
351
+ legend: {
352
+ type: "string",
353
+ default: "Card details",
354
+ label: "Legend"
355
+ },
356
+ numberLabel: {
357
+ type: "string",
358
+ default: "Card number",
359
+ label: "Number label"
360
+ },
361
+ numberPlaceholder: {
362
+ type: "string",
363
+ label: "Number placeholder"
364
+ },
365
+ holderNameLabel: {
366
+ type: "string",
367
+ default: "Cardholder name",
368
+ label: "Holder name label"
369
+ },
370
+ holderNamePlaceholder: {
371
+ type: "string",
372
+ label: "Holder name placeholder"
373
+ },
374
+ expirationLabel: {
375
+ type: "string",
376
+ default: "Expiration date",
377
+ label: "Expiration label"
378
+ },
379
+ expirationPlaceholder: {
380
+ type: "string",
381
+ default: "MM/YY",
382
+ label: "Expiration placeholder"
383
+ },
384
+ cvvLabel: {
385
+ type: "string",
386
+ default: "Security code",
387
+ label: "CVV label"
388
+ },
389
+ cvvPlaceholder: {
390
+ type: "string",
391
+ label: "CVV placeholder"
392
+ },
393
+ cvvDescription: {
394
+ type: "string",
395
+ default: "The 3-4 digit number on your card",
396
+ label: "CVV description"
397
+ },
398
+ numberRequiredMessage: {
399
+ type: "string",
400
+ default: "Card number is required",
401
+ label: "Number required message"
402
+ },
403
+ numberInvalidMessage: {
404
+ type: "string",
405
+ default: "Enter a valid card number",
406
+ label: "Number invalid message"
407
+ },
408
+ holderNameRequiredMessage: {
409
+ type: "string",
410
+ default: "Cardholder name is required",
411
+ label: "Holder name required message"
412
+ },
413
+ expirationRequiredMessage: {
414
+ type: "string",
415
+ default: "Expiration date is required",
416
+ label: "Expiration required message"
417
+ },
418
+ expirationInvalidMessage: {
419
+ type: "string",
420
+ default: "Enter a valid expiration date",
421
+ label: "Expiration invalid message"
422
+ },
423
+ expirationExpiredMessage: {
424
+ type: "string",
425
+ default: "This card has expired",
426
+ label: "Expiration expired message"
427
+ },
428
+ cvvRequiredMessage: {
429
+ type: "string",
430
+ default: "Security code is required",
431
+ label: "CVV required message"
432
+ },
433
+ cvvInvalidMessage: {
434
+ type: "string",
435
+ default: "Enter a valid security code",
436
+ label: "CVV invalid message"
437
+ },
438
+ disabled: {
439
+ type: "boolean",
440
+ default: false,
441
+ label: "Disabled"
442
+ }
443
+ },
444
+ guideline: "New-card entry: masked number with live network detection, holder name, expiration, and CVV, each shown and required per the detected network's validator config from the 'card-capture' context. Must be placed inside a card-capture provider: PaymentSectionRoot or PaymentSectionController at checkout. Owns the card values itself — it builds the card and hands it only to the submission target its provider supplies; no other component ever needs the card number or CVV. Card number and CVV are never schema props, fragment fields, or action payloads — do not add one. See card-entry.md."
445
+ });
446
+ function CardEntry({ legend = "Card details", numberLabel = "Card number", numberPlaceholder, holderNameLabel = "Cardholder name", holderNamePlaceholder, expirationLabel = "Expiration date", expirationPlaceholder = "MM/YY", cvvLabel = "Security code", cvvPlaceholder, cvvDescription = "The 3-4 digit number on your card", numberRequiredMessage = "Card number is required", numberInvalidMessage = "Enter a valid card number", holderNameRequiredMessage = "Cardholder name is required", expirationRequiredMessage = "Expiration date is required", expirationInvalidMessage = "Enter a valid expiration date", expirationExpiredMessage = "This card has expired", cvvRequiredMessage = "Security code is required", cvvInvalidMessage = "Enter a valid security code", disabled = false, className }) {
447
+ const { networks: methods, busy: placing, clearOn, onNetworkChange, selection } = useCardCapture();
448
+ const draftPath = selection?.path ?? null;
449
+ const draftBin = selection?.bin ?? null;
450
+ const collapsed = selection?.collapsed ?? false;
451
+ const cardModeActive = selection?.cardModeActive ?? false;
452
+ const selectedMethodId = selection?.selectedMethodId ?? null;
453
+ const selectionOutOfScope = selection?.selectionOutOfScope ?? false;
454
+ const selectionNonCard = selection?.selectionNonCard ?? false;
455
+ const selectMethod = selection?.selectMethod;
456
+ const validationMessages = useMemo(() => ({
457
+ numberRequired: numberRequiredMessage,
458
+ numberInvalid: numberInvalidMessage,
459
+ holderNameRequired: holderNameRequiredMessage,
460
+ expirationRequired: expirationRequiredMessage,
461
+ expirationInvalid: expirationInvalidMessage,
462
+ expirationExpired: expirationExpiredMessage,
463
+ cvvRequired: cvvRequiredMessage,
464
+ cvvInvalid: cvvInvalidMessage
465
+ }), [
466
+ numberRequiredMessage,
467
+ numberInvalidMessage,
468
+ holderNameRequiredMessage,
469
+ expirationRequiredMessage,
470
+ expirationInvalidMessage,
471
+ expirationExpiredMessage,
472
+ cvvRequiredMessage,
473
+ cvvInvalidMessage
474
+ ]);
475
+ const [number, setNumber] = useState("");
476
+ const [holderName, setHolderName] = useState("");
477
+ const [expiration, setExpiration] = useState("");
478
+ const [cvv, setCvv] = useState("");
479
+ const [fieldErrors, setFieldErrors] = useState({});
480
+ const rootRef = useRef(null);
481
+ const lastDispatchedRef = useRef(null);
482
+ const previousDraftPathRef = useRef(draftPath);
483
+ const reportedNetworkIdRef = useRef(null);
484
+ const previousCollapsedRef = useRef(collapsed);
485
+ const digits = toDigits(number);
486
+ const detectedMethod = useMemo(() => resolveNetworkMethod(methods, digits), [methods, digits]);
487
+ const validator = detectedMethod?.validator ?? DEFAULT_VALIDATOR;
488
+ const selectionOtherNetwork = selectedMethodId !== null && detectedMethod !== null && detectedMethod.id !== selectedMethodId;
489
+ const detectedNetworkIcon = detectedMethod ? networkIconName(detectedMethod.name) : null;
490
+ const cvvComplete = isSecurityCodeComplete(cvv, narrowestMaskCapacity(validator.cvvMask ?? DEFAULT_CVV_MASK));
491
+ const detectedNetworkId = detectedMethod?.id ?? null;
492
+ useEffect(() => {
493
+ if (reportedNetworkIdRef.current === detectedNetworkId) return;
494
+ reportedNetworkIdRef.current = detectedNetworkId;
495
+ onNetworkChange?.(detectedNetworkId);
496
+ }, [detectedNetworkId, onNetworkChange]);
497
+ useEffect(() => {
498
+ const previousPath = previousDraftPathRef.current;
499
+ previousDraftPathRef.current = draftPath;
500
+ const revealedByCardMode = previousCollapsedRef.current && !collapsed && cardModeActive;
501
+ previousCollapsedRef.current = collapsed;
502
+ if (draftPath !== "new-card") {
503
+ lastDispatchedRef.current = null;
504
+ return;
505
+ }
506
+ if (!shouldRedispatchOnReturnToNewCard({
507
+ previousPath,
508
+ revealedByCardMode,
509
+ detectedMethod,
510
+ lastDispatched: lastDispatchedRef.current
511
+ }) || !detectedMethod) return;
512
+ const bin = resolveCompleteBin(digits);
513
+ if (bin === null) return;
514
+ lastDispatchedRef.current = {
515
+ methodId: detectedMethod.id,
516
+ bin
517
+ };
518
+ selectMethod?.({
519
+ methodId: detectedMethod.id,
520
+ bin
521
+ });
522
+ }, [
523
+ draftPath,
524
+ collapsed,
525
+ cardModeActive,
526
+ detectedMethod,
527
+ digits,
528
+ selectMethod
529
+ ]);
530
+ useEffect(() => {
531
+ if (collapsed) {
532
+ setCvv("");
533
+ setFieldErrors((prev) => {
534
+ if (!("cvv" in prev)) return prev;
535
+ const next = { ...prev };
536
+ delete next.cvv;
537
+ return next;
538
+ });
539
+ lastDispatchedRef.current = null;
540
+ }
541
+ }, [collapsed]);
542
+ const focusField = useCallback((key) => {
543
+ (rootRef.current?.querySelector(`[data-fs-card-entry-field="${FIELD_ATTR[key]}"] input`))?.focus();
544
+ }, []);
545
+ function clearFieldError(key) {
546
+ setFieldErrors((prev) => {
547
+ if (!(key in prev)) return prev;
548
+ const next = { ...prev };
549
+ delete next[key];
550
+ return next;
551
+ });
552
+ }
553
+ function runFieldValidation(key, draftValue) {
554
+ const errors = validateDraft(draftValue, validator, methods, validationMessages);
555
+ setFieldErrors((prev) => {
556
+ const next = { ...prev };
557
+ if (errors[key]) next[key] = errors[key];
558
+ else delete next[key];
559
+ return next;
560
+ });
561
+ }
562
+ function handleNumberChange(event) {
563
+ const rawDigits = toDigits(event.target.value);
564
+ const matched = resolveNetworkMethod(methods, rawDigits);
565
+ const masked = maskDigits(rawDigits, resolveNumberMask(methods, rawDigits));
566
+ setNumber(masked);
567
+ clearFieldError("number");
568
+ const nextDigits = toDigits(masked);
569
+ if (nextDigits.length > 0 && draftPath !== "new-card") selection?.selectNewCard();
570
+ const bin = resolveCompleteBin(nextDigits);
571
+ if (matched) {
572
+ const last = lastDispatchedRef.current;
573
+ if (selection && bin !== null && (last?.methodId !== matched.id || last.bin !== bin || draftBin !== last.bin)) {
574
+ selection.setDraftBin(bin);
575
+ lastDispatchedRef.current = {
576
+ methodId: matched.id,
577
+ bin
578
+ };
579
+ selection.selectMethod({
580
+ methodId: matched.id,
581
+ bin
582
+ });
583
+ }
584
+ } else selection?.setDraftBin(null);
585
+ }
586
+ function handleNumberBlur() {
587
+ runFieldValidation("number", {
588
+ number,
589
+ holderName,
590
+ expiration,
591
+ cvv
592
+ });
593
+ }
594
+ function handleHolderNameChange(event) {
595
+ setHolderName(event.target.value);
596
+ clearFieldError("holderName");
597
+ }
598
+ function handleHolderNameBlur() {
599
+ runFieldValidation("holderName", {
600
+ number,
601
+ holderName,
602
+ expiration,
603
+ cvv
604
+ });
605
+ }
606
+ function handleExpirationChange(event) {
607
+ const digits = normalizeExpirationDigits(toDigits(event.target.value), toDigits(expiration));
608
+ setExpiration(maskDigits(digits, DEFAULT_EXPIRATION_MASK));
609
+ clearFieldError("expiration");
610
+ }
611
+ function handleExpirationBlur() {
612
+ runFieldValidation("expiration", {
613
+ number,
614
+ holderName,
615
+ expiration,
616
+ cvv
617
+ });
618
+ }
619
+ function handleCvvChange(event) {
620
+ setCvv(maskDigits(event.target.value, validator.cvvMask ?? DEFAULT_CVV_MASK));
621
+ clearFieldError("cvv");
622
+ }
623
+ function handleCvvBlur() {
624
+ runFieldValidation("cvv", {
625
+ number,
626
+ holderName,
627
+ expiration,
628
+ cvv
629
+ });
630
+ }
631
+ const checkDraft = useCallback(() => {
632
+ if (collapsed) return [CARD_ENTRY_COLLAPSED_ERROR];
633
+ const errors = validateDraft({
634
+ number,
635
+ holderName,
636
+ expiration,
637
+ cvv
638
+ }, validator, methods, validationMessages);
639
+ if (Object.keys(errors).length === 0) return selectionOutOfScope || selectionNonCard || selectionOtherNetwork ? [CARD_ENTRY_COLLAPSED_ERROR] : [];
640
+ setFieldErrors(errors);
641
+ const firstInvalid = VALIDATION_ORDER.find((key) => errors[key]);
642
+ if (firstInvalid) focusField(firstInvalid);
643
+ return [INVALID_CARD_DATA_ERROR];
644
+ }, [
645
+ collapsed,
646
+ number,
647
+ holderName,
648
+ expiration,
649
+ cvv,
650
+ validator,
651
+ methods,
652
+ focusField,
653
+ validationMessages,
654
+ selectionOutOfScope,
655
+ selectionNonCard,
656
+ selectionOtherNetwork
657
+ ]);
658
+ const capture = useCallback(async (send) => {
659
+ const refusals = checkDraft();
660
+ if (refusals.length > 0) return refusals;
661
+ const card = {
662
+ number,
663
+ securityCode: cvv,
664
+ expirationDate: expiration,
665
+ holderName: holderName.trim()
666
+ };
667
+ let succeeded = false;
668
+ try {
669
+ const userErrors = await send(card);
670
+ succeeded = userErrors.length === 0;
671
+ return userErrors;
672
+ } finally {
673
+ if (succeeded || clearOn === "any-outcome") {
674
+ setNumber("");
675
+ setHolderName("");
676
+ setExpiration("");
677
+ setCvv("");
678
+ }
679
+ }
680
+ }, [
681
+ checkDraft,
682
+ number,
683
+ holderName,
684
+ expiration,
685
+ cvv,
686
+ clearOn
687
+ ]);
688
+ useRegisterCardCapture(capture, checkDraft);
689
+ return /* @__PURE__ */ jsx("div", {
690
+ hidden: collapsed,
691
+ "data-fs-card-entry-collapse-boundary": "",
692
+ children: /* @__PURE__ */ jsx("div", {
693
+ ref: rootRef,
694
+ className,
695
+ ...placing || collapsed ? { inert: true } : {},
696
+ "aria-disabled": placing || void 0,
697
+ "data-fs-card-entry": "",
698
+ ...placing ? { "data-fs-card-entry-placing": "" } : {},
699
+ ...disabled ? { "data-fs-card-entry-disabled": "" } : {},
700
+ ...collapsed ? { "data-fs-card-entry-collapsed": "" } : {},
701
+ children: /* @__PURE__ */ jsxs(Fieldset.Root, {
702
+ disabled: disabled || collapsed,
703
+ "data-fs-card-entry-fieldset": "",
704
+ children: [
705
+ /* @__PURE__ */ jsx(Fieldset.Legend, { children: legend }),
706
+ /* @__PURE__ */ jsx("div", {
707
+ "data-fs-card-entry-field": "number",
708
+ children: /* @__PURE__ */ jsxs(Field.Root, {
709
+ invalid: Boolean(fieldErrors.number),
710
+ disabled,
711
+ "data-fs-card-entry-number": "",
712
+ children: [
713
+ /* @__PURE__ */ jsx(Field.Label, { children: numberLabel }),
714
+ /* @__PURE__ */ jsxs("div", {
715
+ "data-fs-card-entry-number-control": "",
716
+ children: [/* @__PURE__ */ jsx(Field.Control, {
717
+ name: "cardNumber",
718
+ inputMode: "numeric",
719
+ autoComplete: "cc-number",
720
+ placeholder: numberPlaceholder,
721
+ value: number,
722
+ onChange: handleNumberChange,
723
+ onBlur: handleNumberBlur,
724
+ disabled
725
+ }), detectedMethod ? /* @__PURE__ */ jsx("div", {
726
+ "data-fs-card-entry-networks": "",
727
+ children: /* @__PURE__ */ jsx("span", {
728
+ "data-fs-card-entry-network": "",
729
+ "data-fs-card-entry-network-id": detectedMethod.id,
730
+ children: detectedNetworkIcon ? /* @__PURE__ */ jsx(Icon, {
731
+ name: detectedNetworkIcon,
732
+ "data-fs-card-entry-network-icon": ""
733
+ }) : /* @__PURE__ */ jsx("span", {
734
+ "data-fs-card-entry-network-fallback": "",
735
+ children: detectedMethod.name
736
+ })
737
+ })
738
+ }) : null]
739
+ }),
740
+ fieldErrors.number ? /* @__PURE__ */ jsx(Field.Error, {
741
+ match: true,
742
+ children: fieldErrors.number
743
+ }) : null
744
+ ]
745
+ })
746
+ }),
747
+ validator.holderNameRequired ? /* @__PURE__ */ jsx("div", {
748
+ "data-fs-card-entry-field": "holder-name",
749
+ children: /* @__PURE__ */ jsxs(Field.Root, {
750
+ invalid: Boolean(fieldErrors.holderName),
751
+ disabled,
752
+ children: [
753
+ /* @__PURE__ */ jsx(Field.Label, { children: holderNameLabel }),
754
+ /* @__PURE__ */ jsx(Field.Control, {
755
+ name: "holderName",
756
+ autoComplete: "cc-name",
757
+ placeholder: holderNamePlaceholder,
758
+ value: holderName,
759
+ onChange: handleHolderNameChange,
760
+ onBlur: handleHolderNameBlur,
761
+ disabled
762
+ }),
763
+ fieldErrors.holderName ? /* @__PURE__ */ jsx(Field.Error, {
764
+ match: true,
765
+ children: fieldErrors.holderName
766
+ }) : null
767
+ ]
768
+ })
769
+ }) : null,
770
+ validator.expirationRequired ? /* @__PURE__ */ jsx("div", {
771
+ "data-fs-card-entry-field": "expiration",
772
+ children: /* @__PURE__ */ jsxs(Field.Root, {
773
+ invalid: Boolean(fieldErrors.expiration),
774
+ disabled,
775
+ children: [
776
+ /* @__PURE__ */ jsx(Field.Label, { children: expirationLabel }),
777
+ /* @__PURE__ */ jsx(Field.Control, {
778
+ name: "expiration",
779
+ inputMode: "numeric",
780
+ autoComplete: "cc-exp",
781
+ placeholder: expirationPlaceholder,
782
+ value: expiration,
783
+ onChange: handleExpirationChange,
784
+ onBlur: handleExpirationBlur,
785
+ disabled
786
+ }),
787
+ fieldErrors.expiration ? /* @__PURE__ */ jsx(Field.Error, {
788
+ match: true,
789
+ children: fieldErrors.expiration
790
+ }) : null
791
+ ]
792
+ })
793
+ }) : null,
794
+ validator.cvvRequired ? /* @__PURE__ */ jsx("div", {
795
+ "data-fs-card-entry-field": "cvv",
796
+ children: /* @__PURE__ */ jsxs(Field.Root, {
797
+ invalid: Boolean(fieldErrors.cvv),
798
+ disabled,
799
+ children: [
800
+ /* @__PURE__ */ jsx(Field.Label, { children: cvvLabel }),
801
+ /* @__PURE__ */ jsx(Field.Control, {
802
+ name: "cvv",
803
+ type: "text",
804
+ inputMode: "numeric",
805
+ autoComplete: "cc-csc",
806
+ placeholder: cvvPlaceholder,
807
+ value: cvv,
808
+ onChange: handleCvvChange,
809
+ onBlur: handleCvvBlur,
810
+ disabled,
811
+ style: cvvComplete ? SECURITY_CODE_MASK_STYLE : void 0
812
+ }),
813
+ cvvDescription ? /* @__PURE__ */ jsx(Field.Description, {
814
+ "data-fs-card-entry-cvv-description": "",
815
+ children: cvvDescription
816
+ }) : null,
817
+ fieldErrors.cvv ? /* @__PURE__ */ jsx(Field.Error, {
818
+ match: true,
819
+ children: fieldErrors.cvv
820
+ }) : null
821
+ ]
822
+ })
823
+ }) : null
824
+ ]
825
+ })
826
+ })
827
+ });
828
+ }
829
+ //#endregion
830
+ export { CARD_ENTRY_COLLAPSED_CODE, CARD_ENTRY_INVALID_DATA_CODE, CardEntry, CardEntryDefinition, NETWORK_ICON_BY_KEY, detectNetworkMethod, isExpirationFormatValid, isExpirationInFuture, isValidLuhn, resolveCompleteBin, resolveNetworkMethod, shouldRedispatchOnReturnToNewCard };
831
+
832
+ //# sourceMappingURL=card-entry.js.map