@whop/elements-react-native 0.1.0

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 (339) hide show
  1. package/README.md +179 -0
  2. package/android/build.gradle +64 -0
  3. package/android/consumer-rules.pro +3 -0
  4. package/android/gradle.properties +5 -0
  5. package/android/src/main/java/com/whop/elements/WhopAddressSuggestionsModule.kt +102 -0
  6. package/android/src/main/java/com/whop/elements/WhopAuthSessionModule.kt +126 -0
  7. package/android/src/main/java/com/whop/elements/WhopElementsPackage.kt +39 -0
  8. package/android/src/main/java/com/whop/elements/WhopPayButtonManager.kt +74 -0
  9. package/android/src/main/java/com/whop/elements/WhopPayButtonPressEvent.kt +17 -0
  10. package/android/src/main/java/com/whop/elements/WhopPayButtonView.kt +178 -0
  11. package/android/src/main/java/com/whop/elements/WhopPlatformPayModule.kt +212 -0
  12. package/assets/whop-wordmark-dark.png +0 -0
  13. package/assets/whop-wordmark-dark@2x.png +0 -0
  14. package/assets/whop-wordmark-dark@3x.png +0 -0
  15. package/assets/whop-wordmark-light.png +0 -0
  16. package/assets/whop-wordmark-light@2x.png +0 -0
  17. package/assets/whop-wordmark-light@3x.png +0 -0
  18. package/ios/Package.swift +59 -0
  19. package/ios/Sources/ElementsReactNative/WhopAddressSuggestionsImpl.swift +189 -0
  20. package/ios/Sources/ElementsReactNative/WhopAuthSessionImpl.swift +106 -0
  21. package/ios/Sources/ElementsReactNative/WhopPayButtonImpl.swift +89 -0
  22. package/ios/Sources/ElementsReactNative/WhopPlatformPayImpl.swift +181 -0
  23. package/ios/generated/WhopAddressSuggestionsModule.h +11 -0
  24. package/ios/generated/WhopAddressSuggestionsModule.mm +46 -0
  25. package/ios/generated/WhopAuthSessionModule.h +11 -0
  26. package/ios/generated/WhopAuthSessionModule.mm +45 -0
  27. package/ios/generated/WhopElementsShims.h +35 -0
  28. package/ios/generated/WhopPayButtonComponentView.h +12 -0
  29. package/ios/generated/WhopPayButtonComponentView.mm +82 -0
  30. package/ios/generated/WhopPlatformPayModule.h +11 -0
  31. package/ios/generated/WhopPlatformPayModule.mm +51 -0
  32. package/lib/module/api/client.js +139 -0
  33. package/lib/module/api/client.js.map +1 -0
  34. package/lib/module/api/client.test.js +137 -0
  35. package/lib/module/api/client.test.js.map +1 -0
  36. package/lib/module/api/types.js +2 -0
  37. package/lib/module/api/types.js.map +1 -0
  38. package/lib/module/controller/actions.js +148 -0
  39. package/lib/module/controller/actions.js.map +1 -0
  40. package/lib/module/controller/actions.test.js +102 -0
  41. package/lib/module/controller/actions.test.js.map +1 -0
  42. package/lib/module/controller/store.js +158 -0
  43. package/lib/module/controller/store.js.map +1 -0
  44. package/lib/module/core/address/country-data.js +1370 -0
  45. package/lib/module/core/address/country-data.js.map +1 -0
  46. package/lib/module/core/address/country-name.js +38 -0
  47. package/lib/module/core/address/country-name.js.map +1 -0
  48. package/lib/module/core/address/country-names.generated.js +249 -0
  49. package/lib/module/core/address/country-names.generated.js.map +1 -0
  50. package/lib/module/core/address/engine.js +245 -0
  51. package/lib/module/core/address/engine.js.map +1 -0
  52. package/lib/module/core/address/engine.test.js +216 -0
  53. package/lib/module/core/address/engine.test.js.map +1 -0
  54. package/lib/module/core/address/form-state.js +409 -0
  55. package/lib/module/core/address/form-state.js.map +1 -0
  56. package/lib/module/core/address/form-state.test.js +516 -0
  57. package/lib/module/core/address/form-state.test.js.map +1 -0
  58. package/lib/module/core/address/places.js +36 -0
  59. package/lib/module/core/address/places.js.map +1 -0
  60. package/lib/module/core/address/required-coverage.test.js +57 -0
  61. package/lib/module/core/address/required-coverage.test.js.map +1 -0
  62. package/lib/module/core/address/suggest.js +102 -0
  63. package/lib/module/core/address/suggest.js.map +1 -0
  64. package/lib/module/core/address/suggest.test.js +156 -0
  65. package/lib/module/core/address/suggest.test.js.map +1 -0
  66. package/lib/module/core/address/use-suggestions.js +33 -0
  67. package/lib/module/core/address/use-suggestions.js.map +1 -0
  68. package/lib/module/core/payments/address-change.js +91 -0
  69. package/lib/module/core/payments/address-change.js.map +1 -0
  70. package/lib/module/core/payments/billing-merge.js +62 -0
  71. package/lib/module/core/payments/billing-merge.js.map +1 -0
  72. package/lib/module/core/payments/browser-info.js +42 -0
  73. package/lib/module/core/payments/browser-info.js.map +1 -0
  74. package/lib/module/core/payments/bt.js +151 -0
  75. package/lib/module/core/payments/bt.js.map +1 -0
  76. package/lib/module/core/payments/bt.test.js +23 -0
  77. package/lib/module/core/payments/bt.test.js.map +1 -0
  78. package/lib/module/core/payments/currency.js +37 -0
  79. package/lib/module/core/payments/currency.js.map +1 -0
  80. package/lib/module/core/payments/detail.js +159 -0
  81. package/lib/module/core/payments/detail.js.map +1 -0
  82. package/lib/module/core/payments/detail.test.js +225 -0
  83. package/lib/module/core/payments/detail.test.js.map +1 -0
  84. package/lib/module/core/payments/errors.js +115 -0
  85. package/lib/module/core/payments/errors.js.map +1 -0
  86. package/lib/module/core/payments/mint.js +89 -0
  87. package/lib/module/core/payments/mint.js.map +1 -0
  88. package/lib/module/core/payments/plan.js +131 -0
  89. package/lib/module/core/payments/plan.js.map +1 -0
  90. package/lib/module/core/payments/refusals.js +17 -0
  91. package/lib/module/core/payments/refusals.js.map +1 -0
  92. package/lib/module/core/payments/secure-fields.js +87 -0
  93. package/lib/module/core/payments/secure-fields.js.map +1 -0
  94. package/lib/module/core/payments/tax-id-catalog.js +616 -0
  95. package/lib/module/core/payments/tax-id-catalog.js.map +1 -0
  96. package/lib/module/core/payments/wallet-contact.js +42 -0
  97. package/lib/module/core/payments/wallet-contact.js.map +1 -0
  98. package/lib/module/core/payments/wallet-contact.test.js +77 -0
  99. package/lib/module/core/payments/wallet-contact.test.js.map +1 -0
  100. package/lib/module/elements/address.js +467 -0
  101. package/lib/module/elements/address.js.map +1 -0
  102. package/lib/module/elements/branding.js +114 -0
  103. package/lib/module/elements/branding.js.map +1 -0
  104. package/lib/module/elements/card-fields/context.js +254 -0
  105. package/lib/module/elements/card-fields/context.js.map +1 -0
  106. package/lib/module/elements/card-fields/fields.js +309 -0
  107. package/lib/module/elements/card-fields/fields.js.map +1 -0
  108. package/lib/module/elements/card-fields/index.js +30 -0
  109. package/lib/module/elements/card-fields/index.js.map +1 -0
  110. package/lib/module/elements/card.js +107 -0
  111. package/lib/module/elements/card.js.map +1 -0
  112. package/lib/module/elements/email.js +84 -0
  113. package/lib/module/elements/email.js.map +1 -0
  114. package/lib/module/elements/payment/declared-fields.js +186 -0
  115. package/lib/module/elements/payment/declared-fields.js.map +1 -0
  116. package/lib/module/elements/payment/index.js +448 -0
  117. package/lib/module/elements/payment/index.js.map +1 -0
  118. package/lib/module/elements/payment/wallet.js +132 -0
  119. package/lib/module/elements/payment/wallet.js.map +1 -0
  120. package/lib/module/elements/tax-id.js +100 -0
  121. package/lib/module/elements/tax-id.js.map +1 -0
  122. package/lib/module/i18n/catalogs.js +103 -0
  123. package/lib/module/i18n/catalogs.js.map +1 -0
  124. package/lib/module/i18n/index.js +45 -0
  125. package/lib/module/i18n/index.js.map +1 -0
  126. package/lib/module/index.js +27 -0
  127. package/lib/module/index.js.map +1 -0
  128. package/lib/module/namespace.js +174 -0
  129. package/lib/module/namespace.js.map +1 -0
  130. package/lib/module/native/index.js +128 -0
  131. package/lib/module/native/index.js.map +1 -0
  132. package/lib/module/package.json +1 -0
  133. package/lib/module/provider.js +56 -0
  134. package/lib/module/provider.js.map +1 -0
  135. package/lib/module/specs/NativeWhopAddressSuggestions.js +6 -0
  136. package/lib/module/specs/NativeWhopAddressSuggestions.js.map +1 -0
  137. package/lib/module/specs/NativeWhopAuthSession.js +10 -0
  138. package/lib/module/specs/NativeWhopAuthSession.js.map +1 -0
  139. package/lib/module/specs/NativeWhopPlatformPay.js +10 -0
  140. package/lib/module/specs/NativeWhopPlatformPay.js.map +1 -0
  141. package/lib/module/specs/WhopPayButtonNativeComponent.ts +22 -0
  142. package/lib/module/theme/appearance.js +85 -0
  143. package/lib/module/theme/appearance.js.map +1 -0
  144. package/lib/module/theme/appearance.test.js +124 -0
  145. package/lib/module/theme/appearance.test.js.map +1 -0
  146. package/lib/module/theme/provider.js +25 -0
  147. package/lib/module/theme/provider.js.map +1 -0
  148. package/lib/module/theme/resolve.js +69 -0
  149. package/lib/module/theme/resolve.js.map +1 -0
  150. package/lib/module/theme/scales.generated.js +419 -0
  151. package/lib/module/theme/scales.generated.js.map +1 -0
  152. package/lib/module/ui/element-frame.js +83 -0
  153. package/lib/module/ui/element-frame.js.map +1 -0
  154. package/lib/module/ui/payment-method-icon.js +69 -0
  155. package/lib/module/ui/payment-method-icon.js.map +1 -0
  156. package/lib/module/ui/primitives.js +266 -0
  157. package/lib/module/ui/primitives.js.map +1 -0
  158. package/lib/module/ui/select.js +281 -0
  159. package/lib/module/ui/select.js.map +1 -0
  160. package/lib/module/ui/suggestions.js +71 -0
  161. package/lib/module/ui/suggestions.js.map +1 -0
  162. package/lib/module/ui/whop-logo.js +42 -0
  163. package/lib/module/ui/whop-logo.js.map +1 -0
  164. package/lib/typescript/package.json +1 -0
  165. package/lib/typescript/src/api/client.d.ts +87 -0
  166. package/lib/typescript/src/api/client.d.ts.map +1 -0
  167. package/lib/typescript/src/api/types.d.ts +175 -0
  168. package/lib/typescript/src/api/types.d.ts.map +1 -0
  169. package/lib/typescript/src/controller/actions.d.ts +42 -0
  170. package/lib/typescript/src/controller/actions.d.ts.map +1 -0
  171. package/lib/typescript/src/controller/store.d.ts +90 -0
  172. package/lib/typescript/src/controller/store.d.ts.map +1 -0
  173. package/lib/typescript/src/core/address/country-data.d.ts +4 -0
  174. package/lib/typescript/src/core/address/country-data.d.ts.map +1 -0
  175. package/lib/typescript/src/core/address/country-name.d.ts +2 -0
  176. package/lib/typescript/src/core/address/country-name.d.ts.map +1 -0
  177. package/lib/typescript/src/core/address/country-names.generated.d.ts +2 -0
  178. package/lib/typescript/src/core/address/country-names.generated.d.ts.map +1 -0
  179. package/lib/typescript/src/core/address/engine.d.ts +97 -0
  180. package/lib/typescript/src/core/address/engine.d.ts.map +1 -0
  181. package/lib/typescript/src/core/address/form-state.d.ts +144 -0
  182. package/lib/typescript/src/core/address/form-state.d.ts.map +1 -0
  183. package/lib/typescript/src/core/address/places.d.ts +23 -0
  184. package/lib/typescript/src/core/address/places.d.ts.map +1 -0
  185. package/lib/typescript/src/core/address/suggest.d.ts +47 -0
  186. package/lib/typescript/src/core/address/suggest.d.ts.map +1 -0
  187. package/lib/typescript/src/core/address/use-suggestions.d.ts +3 -0
  188. package/lib/typescript/src/core/address/use-suggestions.d.ts.map +1 -0
  189. package/lib/typescript/src/core/payments/address-change.d.ts +52 -0
  190. package/lib/typescript/src/core/payments/address-change.d.ts.map +1 -0
  191. package/lib/typescript/src/core/payments/billing-merge.d.ts +39 -0
  192. package/lib/typescript/src/core/payments/billing-merge.d.ts.map +1 -0
  193. package/lib/typescript/src/core/payments/browser-info.d.ts +12 -0
  194. package/lib/typescript/src/core/payments/browser-info.d.ts.map +1 -0
  195. package/lib/typescript/src/core/payments/bt.d.ts +55 -0
  196. package/lib/typescript/src/core/payments/bt.d.ts.map +1 -0
  197. package/lib/typescript/src/core/payments/currency.d.ts +7 -0
  198. package/lib/typescript/src/core/payments/currency.d.ts.map +1 -0
  199. package/lib/typescript/src/core/payments/detail.d.ts +53 -0
  200. package/lib/typescript/src/core/payments/detail.d.ts.map +1 -0
  201. package/lib/typescript/src/core/payments/errors.d.ts +62 -0
  202. package/lib/typescript/src/core/payments/errors.d.ts.map +1 -0
  203. package/lib/typescript/src/core/payments/mint.d.ts +74 -0
  204. package/lib/typescript/src/core/payments/mint.d.ts.map +1 -0
  205. package/lib/typescript/src/core/payments/plan.d.ts +69 -0
  206. package/lib/typescript/src/core/payments/plan.d.ts.map +1 -0
  207. package/lib/typescript/src/core/payments/refusals.d.ts +12 -0
  208. package/lib/typescript/src/core/payments/refusals.d.ts.map +1 -0
  209. package/lib/typescript/src/core/payments/secure-fields.d.ts +33 -0
  210. package/lib/typescript/src/core/payments/secure-fields.d.ts.map +1 -0
  211. package/lib/typescript/src/core/payments/tax-id-catalog.d.ts +481 -0
  212. package/lib/typescript/src/core/payments/tax-id-catalog.d.ts.map +1 -0
  213. package/lib/typescript/src/core/payments/wallet-contact.d.ts +40 -0
  214. package/lib/typescript/src/core/payments/wallet-contact.d.ts.map +1 -0
  215. package/lib/typescript/src/elements/address.d.ts +55 -0
  216. package/lib/typescript/src/elements/address.d.ts.map +1 -0
  217. package/lib/typescript/src/elements/branding.d.ts +18 -0
  218. package/lib/typescript/src/elements/branding.d.ts.map +1 -0
  219. package/lib/typescript/src/elements/card-fields/context.d.ts +67 -0
  220. package/lib/typescript/src/elements/card-fields/context.d.ts.map +1 -0
  221. package/lib/typescript/src/elements/card-fields/fields.d.ts +15 -0
  222. package/lib/typescript/src/elements/card-fields/fields.d.ts.map +1 -0
  223. package/lib/typescript/src/elements/card-fields/index.d.ts +21 -0
  224. package/lib/typescript/src/elements/card-fields/index.d.ts.map +1 -0
  225. package/lib/typescript/src/elements/card.d.ts +21 -0
  226. package/lib/typescript/src/elements/card.d.ts.map +1 -0
  227. package/lib/typescript/src/elements/email.d.ts +31 -0
  228. package/lib/typescript/src/elements/email.d.ts.map +1 -0
  229. package/lib/typescript/src/elements/payment/declared-fields.d.ts +43 -0
  230. package/lib/typescript/src/elements/payment/declared-fields.d.ts.map +1 -0
  231. package/lib/typescript/src/elements/payment/index.d.ts +37 -0
  232. package/lib/typescript/src/elements/payment/index.d.ts.map +1 -0
  233. package/lib/typescript/src/elements/payment/wallet.d.ts +39 -0
  234. package/lib/typescript/src/elements/payment/wallet.d.ts.map +1 -0
  235. package/lib/typescript/src/elements/tax-id.d.ts +41 -0
  236. package/lib/typescript/src/elements/tax-id.d.ts.map +1 -0
  237. package/lib/typescript/src/i18n/catalogs.d.ts +7 -0
  238. package/lib/typescript/src/i18n/catalogs.d.ts.map +1 -0
  239. package/lib/typescript/src/i18n/index.d.ts +17 -0
  240. package/lib/typescript/src/i18n/index.d.ts.map +1 -0
  241. package/lib/typescript/src/index.d.ts +42 -0
  242. package/lib/typescript/src/index.d.ts.map +1 -0
  243. package/lib/typescript/src/namespace.d.ts +36 -0
  244. package/lib/typescript/src/namespace.d.ts.map +1 -0
  245. package/lib/typescript/src/native/index.d.ts +62 -0
  246. package/lib/typescript/src/native/index.d.ts.map +1 -0
  247. package/lib/typescript/src/provider.d.ts +37 -0
  248. package/lib/typescript/src/provider.d.ts.map +1 -0
  249. package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts +13 -0
  250. package/lib/typescript/src/specs/NativeWhopAddressSuggestions.d.ts.map +1 -0
  251. package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts +20 -0
  252. package/lib/typescript/src/specs/NativeWhopAuthSession.d.ts.map +1 -0
  253. package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts +24 -0
  254. package/lib/typescript/src/specs/NativeWhopPlatformPay.d.ts.map +1 -0
  255. package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts +21 -0
  256. package/lib/typescript/src/specs/WhopPayButtonNativeComponent.d.ts.map +1 -0
  257. package/lib/typescript/src/theme/appearance.d.ts +71 -0
  258. package/lib/typescript/src/theme/appearance.d.ts.map +1 -0
  259. package/lib/typescript/src/theme/provider.d.ts +11 -0
  260. package/lib/typescript/src/theme/provider.d.ts.map +1 -0
  261. package/lib/typescript/src/theme/resolve.d.ts +19 -0
  262. package/lib/typescript/src/theme/resolve.d.ts.map +1 -0
  263. package/lib/typescript/src/theme/scales.generated.d.ts +18 -0
  264. package/lib/typescript/src/theme/scales.generated.d.ts.map +1 -0
  265. package/lib/typescript/src/ui/element-frame.d.ts +32 -0
  266. package/lib/typescript/src/ui/element-frame.d.ts.map +1 -0
  267. package/lib/typescript/src/ui/payment-method-icon.d.ts +21 -0
  268. package/lib/typescript/src/ui/payment-method-icon.d.ts.map +1 -0
  269. package/lib/typescript/src/ui/primitives.d.ts +94 -0
  270. package/lib/typescript/src/ui/primitives.d.ts.map +1 -0
  271. package/lib/typescript/src/ui/select.d.ts +25 -0
  272. package/lib/typescript/src/ui/select.d.ts.map +1 -0
  273. package/lib/typescript/src/ui/suggestions.d.ts +15 -0
  274. package/lib/typescript/src/ui/suggestions.d.ts.map +1 -0
  275. package/lib/typescript/src/ui/whop-logo.d.ts +4 -0
  276. package/lib/typescript/src/ui/whop-logo.d.ts.map +1 -0
  277. package/package.json +121 -0
  278. package/scripts/check-swift.sh +27 -0
  279. package/scripts/codegen-shims.mjs +409 -0
  280. package/scripts/gen-country-names.mjs +58 -0
  281. package/scripts/gen-theme.mjs +124 -0
  282. package/scripts/gen-wordmark.mjs +68 -0
  283. package/scripts/mirror.mjs +112 -0
  284. package/src/api/client.ts +202 -0
  285. package/src/api/types.ts +166 -0
  286. package/src/controller/actions.ts +197 -0
  287. package/src/controller/store.ts +201 -0
  288. package/src/core/address/country-data.ts +927 -0
  289. package/src/core/address/country-name.ts +36 -0
  290. package/src/core/address/country-names.generated.ts +246 -0
  291. package/src/core/address/engine.ts +349 -0
  292. package/src/core/address/form-state.ts +479 -0
  293. package/src/core/address/places.ts +39 -0
  294. package/src/core/address/suggest.ts +153 -0
  295. package/src/core/address/use-suggestions.ts +37 -0
  296. package/src/core/payments/address-change.ts +121 -0
  297. package/src/core/payments/billing-merge.ts +91 -0
  298. package/src/core/payments/browser-info.ts +43 -0
  299. package/src/core/payments/bt.ts +176 -0
  300. package/src/core/payments/currency.ts +54 -0
  301. package/src/core/payments/detail.ts +204 -0
  302. package/src/core/payments/errors.ts +171 -0
  303. package/src/core/payments/mint.ts +130 -0
  304. package/src/core/payments/plan.ts +198 -0
  305. package/src/core/payments/refusals.ts +24 -0
  306. package/src/core/payments/secure-fields.ts +85 -0
  307. package/src/core/payments/tax-id-catalog.ts +320 -0
  308. package/src/core/payments/wallet-contact.ts +56 -0
  309. package/src/elements/address.tsx +570 -0
  310. package/src/elements/branding.tsx +87 -0
  311. package/src/elements/card-fields/context.tsx +310 -0
  312. package/src/elements/card-fields/fields.tsx +281 -0
  313. package/src/elements/card-fields/index.tsx +37 -0
  314. package/src/elements/card.tsx +101 -0
  315. package/src/elements/email.tsx +100 -0
  316. package/src/elements/payment/declared-fields.tsx +238 -0
  317. package/src/elements/payment/index.tsx +497 -0
  318. package/src/elements/payment/wallet.tsx +155 -0
  319. package/src/elements/tax-id.tsx +143 -0
  320. package/src/i18n/catalogs.ts +100 -0
  321. package/src/i18n/index.tsx +46 -0
  322. package/src/index.ts +68 -0
  323. package/src/namespace.tsx +231 -0
  324. package/src/native/index.ts +173 -0
  325. package/src/provider.tsx +76 -0
  326. package/src/specs/NativeWhopAddressSuggestions.ts +14 -0
  327. package/src/specs/NativeWhopAuthSession.ts +21 -0
  328. package/src/specs/NativeWhopPlatformPay.ts +25 -0
  329. package/src/specs/WhopPayButtonNativeComponent.ts +22 -0
  330. package/src/theme/appearance.ts +150 -0
  331. package/src/theme/provider.tsx +28 -0
  332. package/src/theme/resolve.ts +68 -0
  333. package/src/theme/scales.generated.ts +427 -0
  334. package/src/ui/element-frame.tsx +94 -0
  335. package/src/ui/payment-method-icon.tsx +55 -0
  336. package/src/ui/primitives.tsx +269 -0
  337. package/src/ui/select.tsx +255 -0
  338. package/src/ui/suggestions.tsx +69 -0
  339. package/src/ui/whop-logo.tsx +37 -0
@@ -0,0 +1,216 @@
1
+ "use strict";
2
+
3
+ // ported verbatim from frontend/apps/web/src/elements/address-form/engine.test.ts — proves the port against the same assertions
4
+
5
+ /**
6
+ * The ported Chromium format engine, including the fixes over the developer-platform fork:
7
+ * zipRegex is enforced, upper flags apply at emit, subdivision iso codes read the real
8
+ * `sub_isoids` key, and ISO2 normalizes once at the boundary.
9
+ */
10
+ import { describe, expect, it } from 'vitest';
11
+ import { currencyCountry, emitAddress, loadCountryCatalog, normalizeCountry, parseCountryFormat, postalCodeMatches } from "./engine.js";
12
+ const US_RAW = {
13
+ fmt: '%N%n%O%n%A%n%C, %S %Z',
14
+ require: 'ACSZ',
15
+ upper: 'CS',
16
+ zip: '(\\d{5})(?:[ \\-](\\d{4}))?',
17
+ zipex: '95014,22162-1010',
18
+ zip_name_type: 'zip',
19
+ state_name_type: 'state',
20
+ sub_keys: 'AL~AK~CA',
21
+ sub_names: 'Alabama~Alaska~California',
22
+ sub_isoids: 'AL~AK~CA'
23
+ };
24
+ describe('parseCountryFormat', () => {
25
+ it('maps fmt tokens to ordered fields with per-country required and upper flags', () => {
26
+ const format = parseCountryFormat('us', US_RAW);
27
+ expect(format.country).toBe('US');
28
+ expect(format.fields.map(field => field.type)).toEqual(['name', 'organization', 'line1', 'city', 'state', 'postal_code']);
29
+ expect(format.fields.find(field => field.type === 'name')?.required).toBe(false);
30
+ for (const type of ['line1', 'city', 'state', 'postal_code']) {
31
+ expect(format.fields.find(field => field.type === type)?.required).toBe(true);
32
+ }
33
+ expect(format.fields.find(field => field.type === 'city')?.upper).toBe(true);
34
+ expect(format.fields.find(field => field.type === 'state')?.upper).toBe(true);
35
+ expect(format.fields.find(field => field.type === 'line1')?.upper).toBe(false);
36
+ expect(format.fields.find(field => field.type === 'postal_code')?.postalPlaceholder).toBe('95014');
37
+ expect(format.stateLabel).toBe('state');
38
+ expect(format.postalLabel).toBe('zip');
39
+ });
40
+ it('reads subdivision iso codes from sub_isoids (the fork read a key the data never had)', () => {
41
+ const format = parseCountryFormat('US', US_RAW);
42
+ expect(format.stateOptions).toEqual([{
43
+ key: 'AL',
44
+ name: 'Alabama',
45
+ iso: 'AL'
46
+ }, {
47
+ key: 'AK',
48
+ name: 'Alaska',
49
+ iso: 'AK'
50
+ }, {
51
+ key: 'CA',
52
+ name: 'California',
53
+ iso: 'CA'
54
+ }]);
55
+ });
56
+ it('prefers the latin lfmt over fmt and keeps token order + D/X fidelity', () => {
57
+ const format = parseCountryFormat('JP', {
58
+ fmt: '〒%Z%n%S%n%A%n%O%n%N',
59
+ lfmt: '%N%n%O%n%A, %S%n%Z',
60
+ require: 'ASZ'
61
+ });
62
+ expect(format.fields.map(field => field.type)).toEqual(['name', 'organization', 'line1', 'state', 'postal_code']);
63
+ const sortable = parseCountryFormat('XX', {
64
+ fmt: '%A%n%D%n%C%n%X'
65
+ });
66
+ expect(sortable.fields.map(field => field.type)).toEqual(['line1', 'dependent_locality', 'city', 'sorting_code']);
67
+ });
68
+ it('reads only %-prefixed tokens as fields — literal print text (SE-, CH-, SINGAPORE, GUERNSEY) passes through', () => {
69
+ const se = parseCountryFormat('SE', {
70
+ fmt: '%O%n%N%n%A%nSE-%Z %C',
71
+ require: 'ACZ'
72
+ });
73
+ expect(se.fields.map(field => field.type)).toEqual(['organization', 'name', 'line1', 'postal_code', 'city']);
74
+ const ch = parseCountryFormat('CH', {
75
+ fmt: '%O%n%N%n%A%nCH-%Z %C',
76
+ require: 'ACZ'
77
+ });
78
+ expect(ch.fields.map(field => field.type)).toEqual(['organization', 'name', 'line1', 'postal_code', 'city']);
79
+ const sg = parseCountryFormat('SG', {
80
+ fmt: '%N%n%O%n%A%nSINGAPORE %Z',
81
+ require: 'AZ'
82
+ });
83
+ expect(sg.fields.map(field => field.type)).toEqual(['name', 'organization', 'line1', 'postal_code']);
84
+ const gg = parseCountryFormat('GG', {
85
+ fmt: '%N%n%O%n%A%n%C%nGUERNSEY%n%Z',
86
+ require: 'ACZ'
87
+ });
88
+ expect(gg.fields.map(field => field.type)).toEqual(['name', 'organization', 'line1', 'city', 'postal_code']);
89
+ const md = parseCountryFormat('MD', {
90
+ fmt: '%N%n%O%n%A%nMD-%Z %C'
91
+ });
92
+ expect(md.fields.some(field => field.type === 'dependent_locality')).toBe(false);
93
+ });
94
+ it('aligns the UZ subdivision override with the kept sub_isoids column', async () => {
95
+ const catalog = await loadCountryCatalog();
96
+ const uz = parseCountryFormat('UZ', catalog.UZ);
97
+ const isoByName = Object.fromEntries(uz.stateOptions.map(option => [option.name, option.iso]));
98
+ expect(isoByName['Tashkent']).toBe('TK');
99
+ expect(isoByName['Tashkent Region']).toBe('TO');
100
+ expect(isoByName['Karakalpakstan']).toBe('QR');
101
+ expect(isoByName['Andijan Region']).toBe('AN');
102
+ expect(isoByName['Xorazm Region']).toBe('XO');
103
+ });
104
+ it('keeps the common-country grid orders stable through the tokenizer (US/DE/GB/JP)', async () => {
105
+ const catalog = await loadCountryCatalog();
106
+ const gridOf = country => parseCountryFormat(country, catalog[country]).fields.map(field => field.type).filter(type => type === 'line1' || type === 'city' || type === 'state' || type === 'postal_code');
107
+ expect(gridOf('US')).toEqual(['line1', 'city', 'state', 'postal_code']);
108
+ expect(gridOf('DE')).toEqual(['line1', 'postal_code', 'city']);
109
+ expect(gridOf('GB')).toEqual(['line1', 'city', 'postal_code']);
110
+ expect(gridOf('JP')).toEqual(['line1', 'state', 'postal_code']);
111
+ });
112
+ it('falls back to line1/postal/city when the country has no format string', () => {
113
+ const format = parseCountryFormat('ZZ', {});
114
+ expect(format.fields.map(field => field.type)).toEqual(['line1', 'postal_code', 'city']);
115
+ expect(format.fields[0]?.required).toBe(true);
116
+ });
117
+ it('applies the subdivision overrides the dataset lacks', () => {
118
+ const format = parseCountryFormat('CR', {
119
+ fmt: '%N%n%O%n%A%n%S, %C%n%Z'
120
+ });
121
+ expect(format.stateOptions.map(option => option.key)).toContain('San José');
122
+ });
123
+ it('accepts the AR postal forms merchants actually enter, not just the full CPA', () => {
124
+ const {
125
+ zipRegex
126
+ } = parseCountryFormat('AR', {
127
+ zip: '[A-HJ-NP-Z]\\d{4}[A-Z]{3}'
128
+ });
129
+ expect(postalCodeMatches(zipRegex, '3560')).toBe(true);
130
+ expect(postalCodeMatches(zipRegex, 'S3560')).toBe(true);
131
+ expect(postalCodeMatches(zipRegex, 'S3560ABC')).toBe(true);
132
+ expect(postalCodeMatches(zipRegex, '356')).toBe(false);
133
+ expect(postalCodeMatches(zipRegex, 'S3560AB')).toBe(false);
134
+ });
135
+ });
136
+ describe('postalCodeMatches (zipRegex enforced — dead data in the fork)', () => {
137
+ const zip = US_RAW.zip;
138
+ it('anchors the country regex over the whole value', () => {
139
+ expect(postalCodeMatches(zip, '94107')).toBe(true);
140
+ expect(postalCodeMatches(zip, '94107-1234')).toBe(true);
141
+ expect(postalCodeMatches(zip, '9410')).toBe(false);
142
+ expect(postalCodeMatches(zip, '94107x')).toBe(false);
143
+ });
144
+ it('passes empty values and unparseable regexes through', () => {
145
+ expect(postalCodeMatches(zip, '')).toBe(true);
146
+ expect(postalCodeMatches(undefined, 'anything')).toBe(true);
147
+ expect(postalCodeMatches('([', '123')).toBe(true);
148
+ });
149
+ });
150
+ describe('emitAddress (upper flags at EMIT, ISO2 at the edge)', () => {
151
+ const format = parseCountryFormat('US', US_RAW);
152
+ it('uppercases exactly the flagged fields, trims, and drops empties', () => {
153
+ const emitted = emitAddress(format, {
154
+ name: ' Ada Lovelace ',
155
+ line1: '123 Main St',
156
+ line2: '',
157
+ city: 'san francisco',
158
+ state: 'ca',
159
+ postal_code: '94107'
160
+ });
161
+ expect(emitted).toEqual({
162
+ country: 'US',
163
+ name: 'Ada Lovelace',
164
+ line1: '123 Main St',
165
+ city: 'SAN FRANCISCO',
166
+ state: 'CA',
167
+ postal_code: '94107'
168
+ });
169
+ });
170
+ it('keeps split names, and organization_type only rides with an organization', () => {
171
+ const emitted = emitAddress(format, {
172
+ first_name: 'Ada',
173
+ last_name: 'Lovelace',
174
+ organization: 'Analytical Engines',
175
+ organization_type: 'business',
176
+ line1: '1 Engine Way',
177
+ postal_code: '94107'
178
+ });
179
+ expect(emitted.first_name).toBe('Ada');
180
+ expect(emitted.last_name).toBe('Lovelace');
181
+ expect(emitted.organization_type).toBe('business');
182
+ const withoutOrg = emitAddress(format, {
183
+ organization_type: 'business',
184
+ line1: '1 Engine Way'
185
+ });
186
+ expect(withoutOrg.organization_type).toBeUndefined();
187
+ });
188
+ });
189
+ describe('boundaries and helpers', () => {
190
+ it('normalizeCountry uppercases once at the edge and rejects non-ISO2 shapes', () => {
191
+ expect(normalizeCountry('de')).toBe('DE');
192
+ expect(normalizeCountry(' us ')).toBe('US');
193
+ expect(normalizeCountry('USA')).toBeUndefined();
194
+ expect(normalizeCountry('')).toBeUndefined();
195
+ expect(normalizeCountry(undefined)).toBeUndefined();
196
+ });
197
+ it('the generated catalog loads lazily and parses through the same engine', async () => {
198
+ const catalog = await loadCountryCatalog();
199
+ const us = parseCountryFormat('US', catalog.US);
200
+ expect(us.fields.some(field => field.type === 'state')).toBe(true);
201
+ expect(us.stateOptions.length).toBeGreaterThan(50);
202
+ expect(postalCodeMatches(us.zipRegex, '94107')).toBe(true);
203
+ expect(postalCodeMatches(us.zipRegex, 'ABC')).toBe(false);
204
+ const de = parseCountryFormat('DE', catalog.DE);
205
+ expect(de.fields.map(field => field.type)).toContain('postal_code');
206
+ });
207
+ });
208
+ describe('currencyCountry (the detect-country fallback hint)', () => {
209
+ it('maps single-country currencies only', () => {
210
+ expect(currencyCountry('USD')).toBe('US');
211
+ expect(currencyCountry('gbp')).toBe('GB');
212
+ expect(currencyCountry('eur')).toBeUndefined();
213
+ expect(currencyCountry(undefined)).toBeUndefined();
214
+ });
215
+ });
216
+ //# sourceMappingURL=engine.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["describe","expect","it","currencyCountry","emitAddress","loadCountryCatalog","normalizeCountry","parseCountryFormat","postalCodeMatches","US_RAW","fmt","require","upper","zip","zipex","zip_name_type","state_name_type","sub_keys","sub_names","sub_isoids","format","country","toBe","fields","map","field","type","toEqual","find","required","postalPlaceholder","stateLabel","postalLabel","stateOptions","key","name","iso","lfmt","sortable","se","ch","sg","gg","md","some","catalog","uz","UZ","isoByName","Object","fromEntries","option","gridOf","filter","toContain","zipRegex","undefined","emitted","line1","line2","city","state","postal_code","first_name","last_name","organization","organization_type","withoutOrg","toBeUndefined","us","US","length","toBeGreaterThan","de","DE"],"sourceRoot":"../../../../src","sources":["core/address/engine.test.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,409 @@
1
+ "use strict";
2
+
3
+ // ported verbatim from frontend/apps/web/src/elements/address-form/form-state.ts — keep in sync when the web element changes
4
+
5
+ /**
6
+ * The address form's ONE state atom (useReducer shape) + the schema-driven central validator.
7
+ * Country lives IN the atom; hydration is ONE seed (defaultValues > detected IP country >
8
+ * currency-country hint > US) computed before first paint; reveal-vs-autofill is reducer
9
+ * state, not component-local flags. Pure module — the jsdom suites drive it directly.
10
+ */
11
+
12
+ import { emitAddress, normalizeCountry, postalCodeMatches } from "./engine.js";
13
+ export function allowedCountryList(config, catalogCountries) {
14
+ const raw = config.allowedCountries ?? [];
15
+ const allowed = raw.map(code => normalizeCountry(code)).filter(code => Boolean(code));
16
+ // Fail LOUD, never open: a list where every entry failed ISO2 normalization (['USA']) is a
17
+ // config error — silently widening to the whole catalog would undo the merchant's restriction
18
+ // on a typo. Same refusal bar as the charge config's plan-XOR gate.
19
+ if (raw.length > 0 && allowed.length === 0) {
20
+ throw new Error(`allowedCountries has no valid ISO2 entries (got ${JSON.stringify(raw)}) — use two-letter codes like 'US'`);
21
+ }
22
+ return allowed.length > 0 ? allowed : catalogCountries;
23
+ }
24
+ function clampCountry(candidate, allowed) {
25
+ return candidate && allowed.includes(candidate) ? candidate : undefined;
26
+ }
27
+ export function seedAddressForm(config, seed, catalogCountries) {
28
+ const allowed = allowedCountryList(config, catalogCountries);
29
+ const country = clampCountry(normalizeCountry(seed.defaultValues?.address?.country), allowed) ?? clampCountry(normalizeCountry(seed.detectedCountry), allowed) ?? clampCountry(normalizeCountry(seed.countryHint), allowed) ?? clampCountry('US', allowed) ?? allowed.at(0) ?? 'US';
30
+ const preset = seed.defaultValues?.address ?? {};
31
+ const values = {};
32
+ const putSeed = (key, value) => {
33
+ if (value?.trim()) values[key] = value;
34
+ };
35
+ putSeed('name', seed.defaultValues?.name ?? preset.name);
36
+ putSeed('first_name', preset.first_name);
37
+ putSeed('last_name', preset.last_name);
38
+ putSeed('organization', preset.organization);
39
+ putSeed('line1', preset.line1);
40
+ putSeed('line2', preset.line2);
41
+ putSeed('city', preset.city);
42
+ putSeed('state', preset.state);
43
+ putSeed('postal_code', preset.postal_code);
44
+ const seededBeyondLine1 = Boolean(values.city || values.state || values.postal_code || values.line2);
45
+ return {
46
+ country,
47
+ values,
48
+ organizationType: preset.organization_type ?? '',
49
+ custom: {},
50
+ revealed: config.scope === 'minimal' || seededBeyondLine1,
51
+ line2Open: config.line2 === 'always' || Boolean(values.line2),
52
+ selecting: false,
53
+ touched: {},
54
+ showAllErrors: false
55
+ };
56
+ }
57
+ const GRID_FIELDS = new Set(['city', 'state', 'postal_code', 'line2']);
58
+ export function addressFormReducer(state, action) {
59
+ switch (action.type) {
60
+ case 'edit':
61
+ {
62
+ const values = {
63
+ ...state.values,
64
+ [action.field]: action.value
65
+ };
66
+ // Two autofill-reveal rails: a value landing in a still-hidden grid field (Chrome fills
67
+ // hidden SELECTS despite skipping hidden inputs), and an `autofilled` edit — an
68
+ // inputType-less/insertReplacementText input event on a visible field, the shape every
69
+ // engine's fill produces and typing never does.
70
+ const landedHidden = !state.revealed && GRID_FIELDS.has(action.field) && action.value.trim().length > 0;
71
+ const autofillReveal = Boolean(action.autofilled) && action.value.trim().length > 0;
72
+ // An autofill commit also opens line2 in toggle mode, in the SAME state commit that
73
+ // reveals the grid: a conditionally-rendered line2 has no control for Chrome's refill
74
+ // pass to land in, so an autofilled line2 value would be silently dropped. Once open it
75
+ // stays open — re-collapsing on an empty settle would be another control-list mutation.
76
+ const autofillCommit = landedHidden || autofillReveal;
77
+ return {
78
+ ...state,
79
+ values,
80
+ revealed: state.revealed || autofillCommit,
81
+ line2Open: state.line2Open || autofillCommit || action.field === 'line2' && action.value.trim().length > 0,
82
+ // ANY value activity cancels a pending pick — a fill landing on another cell (autofill
83
+ // probe, refill pass) mid-resolve invalidates the resolve at the line1 cell, and a
84
+ // stranded `selecting` would block the suggestions popover until the next line1 edit.
85
+ selecting: false
86
+ };
87
+ }
88
+ case 'edit-custom':
89
+ return {
90
+ ...state,
91
+ custom: {
92
+ ...state.custom,
93
+ [action.key]: action.value
94
+ }
95
+ };
96
+ case 'set-organization-type':
97
+ return {
98
+ ...state,
99
+ organizationType: action.value
100
+ };
101
+ case 'set-country':
102
+ {
103
+ const country = normalizeCountry(action.country);
104
+ if (!country || country === state.country) return state;
105
+ // A country switch keeps line1/line2/city but drops the state selection (subdivision
106
+ // keys are country-scoped) — postal stays and revalidates under the new zipRegex. It also
107
+ // cancels an in-flight place selection: the buyer's country choice supersedes the pick
108
+ // (the line1 cell drops the resolve on the distribution clock).
109
+ return {
110
+ ...state,
111
+ country,
112
+ values: {
113
+ ...state.values,
114
+ state: undefined
115
+ },
116
+ selecting: false
117
+ };
118
+ }
119
+ case 'begin-select':
120
+ return {
121
+ ...state,
122
+ selecting: true
123
+ };
124
+ case 'select-failed':
125
+ return {
126
+ ...state,
127
+ selecting: false
128
+ };
129
+ case 'apply-place':
130
+ {
131
+ const {
132
+ parsed,
133
+ allowed
134
+ } = action;
135
+ if (!placeAllowed(allowed, parsed)) return {
136
+ ...state,
137
+ selecting: false
138
+ };
139
+ // Locality grid = WHOLE-REPLACE on purpose: a chosen place is one address, and mixing a
140
+ // prior postal/city with the new street is a franken-address AVS hazard (a place-omitted
141
+ // cell stays empty and focus-first-empty lands there). line1 keeps the typed entry only
142
+ // when the parse has no street line; line2 (subpremise) is place-data almost never, so
143
+ // the user's unit number survives the pick.
144
+ const values = {
145
+ ...state.values,
146
+ line1: parsed.line1 ?? state.values.line1,
147
+ line2: parsed.line2 ?? state.values.line2,
148
+ city: parsed.city ?? '',
149
+ state: parsed.state ?? '',
150
+ postal_code: parsed.postal_code ?? ''
151
+ };
152
+ return {
153
+ ...state,
154
+ country: parsed.country ?? state.country,
155
+ values,
156
+ revealed: true,
157
+ line2Open: state.line2Open || Boolean(parsed.line2),
158
+ selecting: false
159
+ };
160
+ }
161
+ case 'reveal':
162
+ {
163
+ const line2Open = state.line2Open || Boolean(action.autofill);
164
+ if (state.revealed && line2Open === state.line2Open) return state;
165
+ return {
166
+ ...state,
167
+ revealed: true,
168
+ line2Open
169
+ };
170
+ }
171
+ case 'toggle-line2':
172
+ return {
173
+ ...state,
174
+ line2Open: !state.line2Open
175
+ };
176
+ case 'blur':
177
+ {
178
+ const touched = {
179
+ ...state.touched,
180
+ [action.field]: true
181
+ };
182
+ const revealOnLine1 = action.field === 'line1' && Boolean(state.values.line1?.trim());
183
+ return {
184
+ ...state,
185
+ touched,
186
+ revealed: state.revealed || revealOnLine1
187
+ };
188
+ }
189
+ case 'show-errors':
190
+ return {
191
+ ...state,
192
+ showAllErrors: true
193
+ };
194
+ default:
195
+ return state;
196
+ }
197
+ }
198
+ /** The commit-point allow gate ([] = unrestricted): a restrictive list declines a foreign OR
199
+ * country-less parse — unknown provenance must not land foreign locality data under the locked
200
+ * ISO2. Shared by the reducer arm and the placeSelected reaction (which must know BEFORE
201
+ * reducing whether to mark the commit `placed` — a declined pick must not bump placeSeq). */
202
+ /** The Places-gate allow list: [] = unrestricted. DISTINCT from allowedCountryList (the
203
+ * country-select options, which falls back to the whole catalog) — feeding that catalog
204
+ * fallback into placeAllowed would wrongly decline catalog-missing territories on
205
+ * unrestricted configs. */
206
+ /**
207
+ * The floor a scope implies, which is what makes `scope="minimal"` mean anything.
208
+ *
209
+ * Minimal renders EXACTLY the floor, so an empty floor renders an empty grid: the element shipped a
210
+ * country select and nothing else, and a buyer could complete that form while the card confirm then
211
+ * refused at the AVS check for a postal code never collected. Web wires the same value at
212
+ * apps/web/src/elements/payments/address.tsx:317.
213
+ */
214
+ export function requiredFloorFor(scope) {
215
+ return scope === 'minimal' ? ['postal_code'] : undefined;
216
+ }
217
+ export function placeAllowList(config, catalogCountries) {
218
+ return config.allowedCountries?.length ? allowedCountryList(config, catalogCountries) : [];
219
+ }
220
+ export function placeAllowed(allowed, parsed) {
221
+ if (allowed.length === 0) return true;
222
+ return Boolean(parsed.country && allowed.includes(parsed.country));
223
+ }
224
+ const CONTRACT_GRID_KEYS = new Set(['line1', 'city', 'state', 'postal_code']);
225
+
226
+ /** The schema for a (country, config) pair: which grid fields exist and which fields are
227
+ * required. Full scope = the country format's fields (its `require` flags) ∪ the floor;
228
+ * minimal scope = EXACTLY the floor (plus country, the form's spine). */
229
+ export function planFields(config, format) {
230
+ const floor = new Set((config.requiredFloor ?? []).filter(key => key !== 'country'));
231
+ const required = new Set(['country']);
232
+ const gridFields = [];
233
+ const formatHas = type => format.fields.some(field => field.type === type);
234
+ const formatRequires = type => format.fields.some(field => field.type === type && field.required);
235
+
236
+ // Minimal = EXACTLY the floor (the method-declared payment minimum — no universal
237
+ // postal/AVS baseline); full = the country format, widened by the floor.
238
+ const includeGrid = type => {
239
+ if (config.scope === 'minimal') return floor.has(type);
240
+ return formatHas(type) || floor.has(type);
241
+ };
242
+ const orderedTypes = [];
243
+ for (const field of format.fields) {
244
+ if (field.type === 'line1' || field.type === 'city' || field.type === 'state' || field.type === 'postal_code') {
245
+ orderedTypes.push(field.type);
246
+ }
247
+ }
248
+ for (const key of ['line1', 'city', 'state', 'postal_code']) {
249
+ if (!orderedTypes.includes(key)) orderedTypes.push(key);
250
+ }
251
+ for (const type of orderedTypes) {
252
+ if (includeGrid(type)) gridFields.push(type);
253
+ }
254
+ for (const type of gridFields) {
255
+ if (config.scope === 'minimal') {
256
+ required.add(type);
257
+ } else if (formatRequires(type) || floor.has(type)) {
258
+ required.add(type);
259
+ }
260
+ }
261
+ if (config.scope === 'full' && gridFields.includes('line1')) required.add('line1');
262
+ if (config.name === 'combined') required.add('name');
263
+ if (config.name === 'split') {
264
+ required.add('first_name');
265
+ required.add('last_name');
266
+ }
267
+ if (config.name === 'none' && floor.has('name')) required.add('name');
268
+ if (config.organization !== 'none' && floor.has('organization')) required.add('organization');
269
+ for (const field of config.customFields ?? []) {
270
+ if (field.required) required.add(`custom:${field.key}`);
271
+ }
272
+ return {
273
+ gridFields,
274
+ required
275
+ };
276
+ }
277
+
278
+ /** Custom-field values are short by nature (tax ids, VAT numbers) — anything past this bound is
279
+ * invalid WITHOUT running the merchant's pattern, which also removes the buyer-controlled half
280
+ * of a catastrophic-backtracking blowup (cost scales with tested input length; the pattern
281
+ * author is the embedding page's owner, so the residual risk is self-scoped jank). */
282
+ const CUSTOM_FORMAT_INPUT_CAP = 320;
283
+ const customFormatCache = new Map();
284
+ function customFieldPattern(format) {
285
+ let compiled = customFormatCache.get(format);
286
+ if (compiled === undefined) {
287
+ try {
288
+ compiled = new RegExp(format);
289
+ } catch {
290
+ compiled = null;
291
+ }
292
+ customFormatCache.set(format, compiled);
293
+ }
294
+ return compiled;
295
+ }
296
+ function customFieldMatches(spec, value) {
297
+ if (!spec.format || !value) return true;
298
+ if (value.length > CUSTOM_FORMAT_INPUT_CAP) return false;
299
+ return customFieldPattern(spec.format)?.test(value) ?? true;
300
+ }
301
+ export function validateAddressForm(config, format, state) {
302
+ const plan = planFields(config, format);
303
+ const errors = {};
304
+ const has = key => Boolean(state.values[key]?.trim());
305
+ for (const key of plan.required) {
306
+ if (key === 'country') continue;
307
+ if (key.startsWith('custom:')) {
308
+ if (!state.custom[key.slice('custom:'.length)]?.trim()) errors[key] = 'required';
309
+ continue;
310
+ }
311
+ if (CONTRACT_GRID_KEYS.has(key) && !plan.gridFields.includes(key)) continue;
312
+ if (!has(key)) errors[key] = 'required';
313
+ }
314
+ const postal = state.values.postal_code?.trim() ?? '';
315
+ if (plan.gridFields.includes('postal_code') && postal && !postalCodeMatches(format.zipRegex, postal)) {
316
+ errors.postal_code = 'invalid';
317
+ }
318
+ for (const spec of config.customFields ?? []) {
319
+ const key = `custom:${spec.key}`;
320
+ if (errors[key]) continue;
321
+ const value = state.custom[spec.key]?.trim() ?? '';
322
+ if (value && !customFieldMatches(spec, value)) errors[key] = 'invalid';
323
+ }
324
+ return {
325
+ errors,
326
+ complete: Object.keys(errors).length === 0
327
+ };
328
+ }
329
+
330
+ /** Compact = city and postal share ONE row (format order within it), state below. */
331
+ export function pairedGridRows(gridFields) {
332
+ const grid = gridFields.filter(key => key !== 'line1');
333
+ const pair = grid.filter(key => key !== 'state');
334
+ const rows = [];
335
+ if (pair.length > 0) rows.push(pair);
336
+ if (grid.includes('state')) rows.push(['state']);
337
+ return rows;
338
+ }
339
+ const TOP_CORNERS = {
340
+ solo: 'rounded-t-[7px]',
341
+ first: 'rounded-tl-[7px]',
342
+ last: 'rounded-tr-[7px]'
343
+ };
344
+ const BOTTOM_CORNERS = {
345
+ solo: 'rounded-b-[7px]',
346
+ first: 'rounded-bl-[7px]',
347
+ last: 'rounded-br-[7px]'
348
+ };
349
+
350
+ /** Box-shadows follow the element's OWN border-radius, so the compact group's rounded
351
+ * container cannot round the focus/invalid rings its cells paint — first- and last-row cells
352
+ * carry their corner radii themselves (7px = the container's 8px radius minus its 1px
353
+ * border), each cell of a two-column row rounding only its outer corner. Derived from the
354
+ * same config + field plan that orders the rows: country formats drop and reorder fields,
355
+ * and until the grid reveals the visible group ends at line1. */
356
+ export function compactCellCorners(config, gridFields, visible) {
357
+ const customRows = position => (config.customFields ?? []).filter(field => field.position === position).map(field => [`custom:${field.key}`]);
358
+ const rows = [];
359
+ if (config.name === 'split') rows.push(['first_name', 'last_name']);else if (config.name === 'combined') rows.push(['name']);
360
+ rows.push(...customRows('after_name'));
361
+ if (config.organization === 'name_with_type') rows.push(['organization', 'organization_type']);else if (config.organization === 'name') rows.push(['organization']);
362
+ rows.push(...customRows('after_organization'), ...customRows('before_country'));
363
+ rows.push(['country']);
364
+ if (gridFields.includes('line1')) rows.push(['line1']);
365
+ if (visible.revealed) {
366
+ if (visible.line2) rows.push(['line2']);
367
+ rows.push(...pairedGridRows(gridFields), ...customRows('after_address'));
368
+ }
369
+ const classes = {};
370
+ const mark = (row, corner) => {
371
+ row?.forEach((key, index) => {
372
+ const cls = row.length === 1 ? corner.solo : index === 0 ? corner.first : corner.last;
373
+ classes[key] = classes[key] ? `${classes[key]} ${cls}` : cls;
374
+ });
375
+ };
376
+ mark(rows[0], TOP_CORNERS);
377
+ mark(rows[rows.length - 1], BOTTOM_CORNERS);
378
+ return classes;
379
+ }
380
+ export function snapshotAddressForm(config, format, state) {
381
+ const validation = validateAddressForm(config, format, state);
382
+ const custom = {};
383
+ for (const spec of config.customFields ?? []) {
384
+ const value = state.custom[spec.key]?.trim();
385
+ if (value) custom[spec.key] = value;
386
+ }
387
+ // The payload carries only what the CURRENT plan collects — a scope narrowing must not keep
388
+ // emitting fields the form no longer shows. State retains the values, so widening back
389
+ // restores them.
390
+ const planned = new Set(planFields(config, format).gridFields);
391
+ return {
392
+ complete: validation.complete,
393
+ address: emitAddress(format, {
394
+ name: config.name === 'combined' ? state.values.name : undefined,
395
+ first_name: config.name === 'split' ? state.values.first_name : undefined,
396
+ last_name: config.name === 'split' ? state.values.last_name : undefined,
397
+ organization: config.organization === 'none' ? undefined : state.values.organization,
398
+ organization_type: config.organization === 'name_with_type' ? state.organizationType : '',
399
+ line1: planned.has('line1') ? state.values.line1 : undefined,
400
+ line2: planned.has('line1') && config.line2 !== 'never' ? state.values.line2 : undefined,
401
+ city: planned.has('city') ? state.values.city : undefined,
402
+ state: planned.has('state') ? state.values.state : undefined,
403
+ postal_code: planned.has('postal_code') ? state.values.postal_code : undefined
404
+ }),
405
+ custom,
406
+ errors: validation.errors
407
+ };
408
+ }
409
+ //# sourceMappingURL=form-state.js.map