@verbb/formie-browser 1.0.13 → 1.0.15

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 (389) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/dist/chunks/{address-country-Rx6GQTND.js → address-country-BcEQKw0T.js} +11 -5
  3. package/dist/chunks/{address-finder-k7_BXaRe.js → address-finder-BWGINCSA.js} +3 -3
  4. package/dist/chunks/address-state-DI3ndhMU.js +365 -0
  5. package/dist/chunks/{api-BUb6P-xu.js → api-BK9b340y.js} +93 -63
  6. package/dist/chunks/{api-DqpfkZWL.js → api-Dxl_CKw-.js} +202 -193
  7. package/dist/chunks/{api-sPqGbOww.js → api-ghNr2uxX.js} +15 -9
  8. package/dist/chunks/{async-nPFRNQ06.js → async-2GdXNFCE.js} +18 -1
  9. package/dist/chunks/{bpoint-Dlq34XH1.js → bpoint-BwtuyN_O.js} +3 -3
  10. package/dist/chunks/calculations-YEoVCG2q.js +310 -0
  11. package/dist/chunks/{captcha-eu-CGm8Lbrj.js → captcha-eu-DWLoQFKB.js} +6 -5
  12. package/dist/chunks/{checkbox-radio-BY2aTCOG.js → checkbox-radio-gsm8QPJg.js} +74 -75
  13. package/dist/chunks/{combobox-C0EuExSI.js → combobox-Ba08_cHL.js} +340 -328
  14. package/dist/chunks/conditions-Ceeg6ReZ.js +467 -0
  15. package/dist/chunks/country-from-ip-DDj_mFki.js +29 -0
  16. package/dist/chunks/csrf-DxHg_ZYt.js +61 -0
  17. package/dist/chunks/{custom-google-maps-BsaSirEZ.js → custom-google-maps-C4l2S3GO.js} +9 -3
  18. package/dist/chunks/{custom-link-CJ1-FjEM.js → custom-link-CLazgKYH.js} +8 -2
  19. package/dist/chunks/{custom-maps-DYW_pBid.js → custom-maps-DqcAlD0v.js} +10 -4
  20. package/dist/chunks/{date-picker-F9BUBDSW.js → date-picker-DaPvMrop.js} +467 -443
  21. package/dist/chunks/{dist-D6i-VNgM.js → dist-ZLeW0zZ3.js} +1281 -453
  22. package/dist/chunks/{eway-D9_m8ooj.js → eway-uw0DzU-L.js} +5 -5
  23. package/dist/chunks/field-references.resolver-Ca8MguFv.js +104 -0
  24. package/dist/chunks/{file-upload-rqfZNqlH.js → file-upload-B8_mH012.js} +119 -99
  25. package/dist/chunks/friendly-captcha-v1-CVXaXgDO.js +34 -0
  26. package/dist/chunks/{friendly-captcha-v2-CLSAbkqx.js → friendly-captcha-v2-DkeIVTKI.js} +6 -5
  27. package/dist/chunks/{friendly-challenge-8uPatWqk.js → friendly-challenge-DPG9MJb1.js} +111 -102
  28. package/dist/chunks/{go-cardless-DAT3ZaQf.js → go-cardless-c4wLJdOt.js} +2 -2
  29. package/dist/chunks/google-address-JHYvjwgN.js +168 -0
  30. package/dist/chunks/{hcaptcha-CGLAmgjO.js → hcaptcha-Dt3ELLlP.js} +4 -4
  31. package/dist/chunks/{hidden-D7_Ch-QN.js → hidden-BZhXnNPe.js} +8 -2
  32. package/dist/chunks/http-C2QdHuWO.js +27 -0
  33. package/dist/chunks/{leaflet-src-j-lHNmhR.js → leaflet-src-DE6rv6M-.js} +42 -46
  34. package/dist/chunks/{loqate-CJPzBmSm.js → loqate-m51iPyO1.js} +3 -3
  35. package/dist/chunks/{mollie-Bibhl67p.js → mollie-Bn0PX173.js} +2 -2
  36. package/dist/chunks/{moneris-H2wSqk9h.js → moneris-BRbUvI4t.js} +3 -3
  37. package/dist/chunks/opayo-BwWdInEP.js +188 -0
  38. package/dist/chunks/{paddle-DkvIxQdG.js → paddle-CympHVvd.js} +4 -4
  39. package/dist/chunks/{password-validation-D06uH9va.js → password-validation-C8_uO_c_.js} +18 -12
  40. package/dist/chunks/{paypal-CQpAF7Ua.js → paypal-h75h6wae.js} +8 -6
  41. package/dist/chunks/{payway-CwDkC7YZ.js → payway-C9rHTCC_.js} +4 -4
  42. package/dist/chunks/{phone-country-D3ayr60L.js → phone-country-B8fv-L5_.js} +80 -63
  43. package/dist/chunks/{place-kit-BGHklIZ-.js → place-kit-BnZGQ-L1.js} +11 -11
  44. package/dist/chunks/{placekit-autocomplete.esm-DWVabQlq.js → placekit-autocomplete.esm-CZZERYur.js} +11 -15
  45. package/dist/chunks/{recaptcha-enterprise-XPId5-bS.js → recaptcha-enterprise-CUs8BO6V.js} +4 -4
  46. package/dist/chunks/{recaptcha-shared-CLYJvojk.js → recaptcha-shared-DTbTw-MF.js} +2 -2
  47. package/dist/chunks/{recaptcha-v2-checkbox-Cy-0PEPc.js → recaptcha-v2-checkbox-P1McW4Pv.js} +4 -4
  48. package/dist/chunks/{recaptcha-v2-invisible-BwRH540O.js → recaptcha-v2-invisible-FGDqozSP.js} +4 -4
  49. package/dist/chunks/{recaptcha-v3-No4Ykrhk.js → recaptcha-v3-CflUHeZD.js} +4 -4
  50. package/dist/chunks/{repeater-BVnPUqTj.js → repeater-CyhDbozO.js} +60 -40
  51. package/dist/chunks/request-profile-DhwkeCpS.js +10 -0
  52. package/dist/chunks/rich-text-uV4o_pGk.js +361 -0
  53. package/dist/chunks/{chunk-3b4jIN3o.js → rolldown-runtime-DtPi1Y-2.js} +2 -2
  54. package/dist/chunks/{scripts-CbQ7agX3.js → scripts-DrccjrFv.js} +1 -1
  55. package/dist/chunks/{sdk-BhHAYgga.js → sdk-lkwSvlpM.js} +282 -234
  56. package/dist/chunks/{shared-Bx9s0i0P.js → shared-OKhSay59.js} +27 -21
  57. package/dist/chunks/{signature-IOhuicje.js → signature-7cldFbqj.js} +138 -112
  58. package/dist/chunks/skip-CEW89ExY.js +7 -0
  59. package/dist/chunks/{snaptcha-DKx98oon.js → snaptcha-C3QLYxFT.js} +2 -2
  60. package/dist/chunks/{square-DwdcOI1G.js → square-C1z5DPVw.js} +9 -7
  61. package/dist/chunks/{stripe-DaoG2FT1.js → stripe-Dmkdkq1y.js} +11 -12
  62. package/dist/chunks/{summary-Dks0gXS3.js → summary-Bn-g5SDx.js} +48 -46
  63. package/dist/chunks/{survey-likert-DkE6bR7B.js → survey-likert-fk1hUPff.js} +7 -1
  64. package/dist/chunks/{survey-rank-DN6x4IZM.js → survey-rank-BsLGl2LX.js} +37 -25
  65. package/dist/chunks/{survey-rating-BnsNL9U5.js → survey-rating-DVS6e6fM.js} +17 -5
  66. package/dist/chunks/{table-CAjCJHrL.js → table-j7MlLLCc.js} +22 -16
  67. package/dist/chunks/{text-limit-BGaY0jhI.js → text-limit-LGMPhFuC.js} +40 -34
  68. package/dist/chunks/{theme-classes-Tv7q7ToE.js → theme-classes-DAQuEqdP.js} +1 -1
  69. package/dist/chunks/{turnstile-DJakPzQD.js → turnstile-ptihQfLl.js} +4 -4
  70. package/dist/chunks/{upload-manager-W-QDgxKD.js → upload-manager-BW7l-BhF.js} +459 -387
  71. package/dist/chunks/{utils-Vqp8i_RM.js → utils-DpUBPadZ.js} +20 -14
  72. package/dist/css/formie-base.css +104 -1
  73. package/dist/css/theme/_messages.css +2 -0
  74. package/dist/css/theme/_tokens.css +31 -25
  75. package/dist/css/theme/forms/_form.css +5 -1
  76. package/dist/index.d.ts +5 -4
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +1779 -1415
  79. package/dist/js/contracts/client.d.ts +5 -3
  80. package/dist/js/contracts/client.d.ts.map +1 -1
  81. package/dist/js/contracts/common.d.ts +2 -2
  82. package/dist/js/contracts/common.d.ts.map +1 -1
  83. package/dist/js/contracts/modules.d.ts +37 -12
  84. package/dist/js/contracts/modules.d.ts.map +1 -1
  85. package/dist/js/contracts/schema.d.ts +20 -38
  86. package/dist/js/contracts/schema.d.ts.map +1 -1
  87. package/dist/js/contracts/theme.d.ts +4 -1
  88. package/dist/js/contracts/theme.d.ts.map +1 -1
  89. package/dist/js/core/client-rendered-modules.d.ts +17 -0
  90. package/dist/js/core/client-rendered-modules.d.ts.map +1 -0
  91. package/dist/js/core/create-formie-client.d.ts.map +1 -1
  92. package/dist/js/core/field-error-aria.d.ts +23 -0
  93. package/dist/js/core/field-error-aria.d.ts.map +1 -0
  94. package/dist/js/core/hydrate-modules.d.ts +10 -8
  95. package/dist/js/core/hydrate-modules.d.ts.map +1 -1
  96. package/dist/js/core/submit-result-state.d.ts.map +1 -1
  97. package/dist/js/core/submit-result-ui.d.ts +3 -1
  98. package/dist/js/core/submit-result-ui.d.ts.map +1 -1
  99. package/dist/js/core/validation-focus.d.ts.map +1 -1
  100. package/dist/js/modules/address/address-finder.d.ts +1 -1
  101. package/dist/js/modules/address/address-finder.d.ts.map +1 -1
  102. package/dist/js/modules/address/factories.d.ts +3 -3
  103. package/dist/js/modules/address/factories.d.ts.map +1 -1
  104. package/dist/js/modules/address/google-address.d.ts +12 -1
  105. package/dist/js/modules/address/google-address.d.ts.map +1 -1
  106. package/dist/js/modules/address/index.d.ts +2 -2
  107. package/dist/js/modules/address/index.d.ts.map +1 -1
  108. package/dist/js/modules/address/loqate.d.ts +1 -1
  109. package/dist/js/modules/address/loqate.d.ts.map +1 -1
  110. package/dist/js/modules/address/place-kit.d.ts +1 -1
  111. package/dist/js/modules/address/place-kit.d.ts.map +1 -1
  112. package/dist/js/modules/captchas/captcha-eu.d.ts +1 -1
  113. package/dist/js/modules/captchas/captcha-eu.d.ts.map +1 -1
  114. package/dist/js/modules/captchas/factories.d.ts +11 -11
  115. package/dist/js/modules/captchas/factories.d.ts.map +1 -1
  116. package/dist/js/modules/captchas/friendly-captcha-v1.d.ts +1 -1
  117. package/dist/js/modules/captchas/friendly-captcha-v1.d.ts.map +1 -1
  118. package/dist/js/modules/captchas/friendly-captcha-v2.d.ts +1 -1
  119. package/dist/js/modules/captchas/friendly-captcha-v2.d.ts.map +1 -1
  120. package/dist/js/modules/captchas/hcaptcha.d.ts +1 -1
  121. package/dist/js/modules/captchas/hcaptcha.d.ts.map +1 -1
  122. package/dist/js/modules/captchas/index.d.ts +2 -2
  123. package/dist/js/modules/captchas/index.d.ts.map +1 -1
  124. package/dist/js/modules/captchas/recaptcha-enterprise.d.ts +1 -1
  125. package/dist/js/modules/captchas/recaptcha-enterprise.d.ts.map +1 -1
  126. package/dist/js/modules/captchas/recaptcha-v2-checkbox.d.ts +1 -1
  127. package/dist/js/modules/captchas/recaptcha-v2-checkbox.d.ts.map +1 -1
  128. package/dist/js/modules/captchas/recaptcha-v2-invisible.d.ts +1 -1
  129. package/dist/js/modules/captchas/recaptcha-v2-invisible.d.ts.map +1 -1
  130. package/dist/js/modules/captchas/recaptcha-v3.d.ts +1 -1
  131. package/dist/js/modules/captchas/recaptcha-v3.d.ts.map +1 -1
  132. package/dist/js/modules/captchas/snaptcha.d.ts +1 -1
  133. package/dist/js/modules/captchas/snaptcha.d.ts.map +1 -1
  134. package/dist/js/modules/captchas/turnstile.d.ts +1 -1
  135. package/dist/js/modules/captchas/turnstile.d.ts.map +1 -1
  136. package/dist/js/modules/fields/address-country.d.ts +2 -2
  137. package/dist/js/modules/fields/address-country.d.ts.map +1 -1
  138. package/dist/js/modules/fields/address-state.d.ts +2 -2
  139. package/dist/js/modules/fields/address-state.d.ts.map +1 -1
  140. package/dist/js/modules/fields/calculations.d.ts +2 -2
  141. package/dist/js/modules/fields/calculations.d.ts.map +1 -1
  142. package/dist/js/modules/fields/checkbox-radio.d.ts +2 -2
  143. package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
  144. package/dist/js/modules/fields/combobox.d.ts +2 -2
  145. package/dist/js/modules/fields/combobox.d.ts.map +1 -1
  146. package/dist/js/modules/fields/conditions/config.d.ts.map +1 -1
  147. package/dist/js/modules/fields/conditions/effects.d.ts +1 -0
  148. package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
  149. package/dist/js/modules/fields/conditions/evaluator.d.ts +4 -1
  150. package/dist/js/modules/fields/conditions/evaluator.d.ts.map +1 -1
  151. package/dist/js/modules/fields/conditions/references.d.ts.map +1 -1
  152. package/dist/js/modules/fields/conditions/submission-context.d.ts +15 -0
  153. package/dist/js/modules/fields/conditions/submission-context.d.ts.map +1 -0
  154. package/dist/js/modules/fields/conditions/types.d.ts +3 -1
  155. package/dist/js/modules/fields/conditions/types.d.ts.map +1 -1
  156. package/dist/js/modules/fields/conditions/values.d.ts +1 -1
  157. package/dist/js/modules/fields/conditions/values.d.ts.map +1 -1
  158. package/dist/js/modules/fields/conditions.d.ts +2 -2
  159. package/dist/js/modules/fields/conditions.d.ts.map +1 -1
  160. package/dist/js/modules/fields/custom-google-maps.d.ts +2 -2
  161. package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -1
  162. package/dist/js/modules/fields/custom-link.d.ts +2 -2
  163. package/dist/js/modules/fields/custom-link.d.ts.map +1 -1
  164. package/dist/js/modules/fields/custom-maps.d.ts +2 -2
  165. package/dist/js/modules/fields/custom-maps.d.ts.map +1 -1
  166. package/dist/js/modules/fields/date-picker.d.ts +2 -2
  167. package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
  168. package/dist/js/modules/fields/file-upload.d.ts +2 -2
  169. package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
  170. package/dist/js/modules/fields/hidden.d.ts +2 -2
  171. package/dist/js/modules/fields/hidden.d.ts.map +1 -1
  172. package/dist/js/modules/fields/index.d.ts +2 -2
  173. package/dist/js/modules/fields/index.d.ts.map +1 -1
  174. package/dist/js/modules/fields/password-validation.d.ts +2 -2
  175. package/dist/js/modules/fields/password-validation.d.ts.map +1 -1
  176. package/dist/js/modules/fields/phone-country.d.ts +2 -2
  177. package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
  178. package/dist/js/modules/fields/repeater.d.ts +2 -2
  179. package/dist/js/modules/fields/repeater.d.ts.map +1 -1
  180. package/dist/js/modules/fields/rich-text.d.ts +2 -2
  181. package/dist/js/modules/fields/rich-text.d.ts.map +1 -1
  182. package/dist/js/modules/fields/shared.d.ts +1 -0
  183. package/dist/js/modules/fields/shared.d.ts.map +1 -1
  184. package/dist/js/modules/fields/signature.d.ts +2 -2
  185. package/dist/js/modules/fields/signature.d.ts.map +1 -1
  186. package/dist/js/modules/fields/summary.d.ts +2 -2
  187. package/dist/js/modules/fields/summary.d.ts.map +1 -1
  188. package/dist/js/modules/fields/survey-likert.d.ts +2 -2
  189. package/dist/js/modules/fields/survey-likert.d.ts.map +1 -1
  190. package/dist/js/modules/fields/survey-rank.d.ts +2 -2
  191. package/dist/js/modules/fields/survey-rank.d.ts.map +1 -1
  192. package/dist/js/modules/fields/survey-rating.d.ts +2 -2
  193. package/dist/js/modules/fields/survey-rating.d.ts.map +1 -1
  194. package/dist/js/modules/fields/table.d.ts +2 -2
  195. package/dist/js/modules/fields/table.d.ts.map +1 -1
  196. package/dist/js/modules/fields/text-limit.d.ts +2 -2
  197. package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
  198. package/dist/js/modules/fields/upload-manager.d.ts +2 -2
  199. package/dist/js/modules/fields/upload-manager.d.ts.map +1 -1
  200. package/dist/js/modules/loader.d.ts +10 -4
  201. package/dist/js/modules/loader.d.ts.map +1 -1
  202. package/dist/js/modules/payments/bpoint.d.ts +1 -1
  203. package/dist/js/modules/payments/bpoint.d.ts.map +1 -1
  204. package/dist/js/modules/payments/eway.d.ts +1 -1
  205. package/dist/js/modules/payments/eway.d.ts.map +1 -1
  206. package/dist/js/modules/payments/factories.d.ts +7 -7
  207. package/dist/js/modules/payments/factories.d.ts.map +1 -1
  208. package/dist/js/modules/payments/go-cardless.d.ts +1 -1
  209. package/dist/js/modules/payments/go-cardless.d.ts.map +1 -1
  210. package/dist/js/modules/payments/index.d.ts +2 -2
  211. package/dist/js/modules/payments/index.d.ts.map +1 -1
  212. package/dist/js/modules/payments/mollie.d.ts +1 -1
  213. package/dist/js/modules/payments/mollie.d.ts.map +1 -1
  214. package/dist/js/modules/payments/moneris.d.ts +1 -1
  215. package/dist/js/modules/payments/moneris.d.ts.map +1 -1
  216. package/dist/js/modules/payments/opayo.d.ts +1 -1
  217. package/dist/js/modules/payments/opayo.d.ts.map +1 -1
  218. package/dist/js/modules/payments/paddle.d.ts +1 -1
  219. package/dist/js/modules/payments/paddle.d.ts.map +1 -1
  220. package/dist/js/modules/payments/paypal.d.ts +1 -1
  221. package/dist/js/modules/payments/paypal.d.ts.map +1 -1
  222. package/dist/js/modules/payments/payway.d.ts +1 -1
  223. package/dist/js/modules/payments/payway.d.ts.map +1 -1
  224. package/dist/js/modules/payments/square.d.ts +1 -1
  225. package/dist/js/modules/payments/square.d.ts.map +1 -1
  226. package/dist/js/modules/payments/stripe.d.ts +1 -1
  227. package/dist/js/modules/payments/stripe.d.ts.map +1 -1
  228. package/dist/js/modules/payments/utils.d.ts +6 -0
  229. package/dist/js/modules/payments/utils.d.ts.map +1 -1
  230. package/dist/js/modules/registry.d.ts +6 -4
  231. package/dist/js/modules/registry.d.ts.map +1 -1
  232. package/dist/js/submit/pipeline.d.ts.map +1 -1
  233. package/dist/js/theme/browser-theme-state.generated.d.ts +24 -0
  234. package/dist/js/theme/browser-theme-state.generated.d.ts.map +1 -0
  235. package/dist/js/theme/theme-classes.d.ts +3 -3
  236. package/dist/js/theme/theme-classes.d.ts.map +1 -1
  237. package/dist/js/transport/forms-api.d.ts +5 -3
  238. package/dist/js/transport/forms-api.d.ts.map +1 -1
  239. package/dist/js/utils/async.d.ts +1 -0
  240. package/dist/js/utils/async.d.ts.map +1 -1
  241. package/dist/js/utils/country-from-ip.d.ts +2 -2
  242. package/dist/js/utils/country-from-ip.d.ts.map +1 -1
  243. package/dist/js/utils/csrf.d.ts +17 -0
  244. package/dist/js/utils/csrf.d.ts.map +1 -0
  245. package/dist/js/utils/field-references.parser.d.ts +1 -0
  246. package/dist/js/utils/field-references.parser.d.ts.map +1 -1
  247. package/dist/js/utils/field-references.resolver.d.ts.map +1 -1
  248. package/dist/js/utils/field-references.types.d.ts +1 -0
  249. package/dist/js/utils/field-references.types.d.ts.map +1 -1
  250. package/dist/js/utils/http.d.ts +3 -1
  251. package/dist/js/utils/http.d.ts.map +1 -1
  252. package/dist/js/utils/request-profile.d.ts +4 -0
  253. package/dist/js/utils/request-profile.d.ts.map +1 -0
  254. package/dist/js/utils/unload-warning.d.ts.map +1 -1
  255. package/dist/js/validation/rules/email.d.ts.map +1 -1
  256. package/dist/js/validation/rules/match.d.ts.map +1 -1
  257. package/dist/js/validation/rules/number.d.ts.map +1 -1
  258. package/dist/js/validation/rules/required.d.ts.map +1 -1
  259. package/dist/js/validation/rules/shared.d.ts.map +1 -1
  260. package/dist/js/validation/rules/url.d.ts.map +1 -1
  261. package/dist/js/validation/skip.d.ts +7 -0
  262. package/dist/js/validation/skip.d.ts.map +1 -0
  263. package/dist/js/validation/submit-readiness.d.ts.map +1 -1
  264. package/dist/js/validation/validator.d.ts.map +1 -1
  265. package/package.json +43 -12
  266. package/src/css/formie-base.css +104 -1
  267. package/src/css/theme/_messages.css +2 -0
  268. package/src/css/theme/_tokens.css +31 -25
  269. package/src/css/theme/forms/_form.css +5 -1
  270. package/src/index.ts +13 -8
  271. package/src/js/contracts/client.ts +5 -3
  272. package/src/js/contracts/common.ts +5 -6
  273. package/src/js/contracts/modules.ts +41 -12
  274. package/src/js/contracts/schema.ts +21 -43
  275. package/src/js/contracts/theme.ts +6 -1
  276. package/src/js/core/client-rendered-modules.ts +69 -0
  277. package/src/js/core/create-formie-client.ts +132 -86
  278. package/src/js/core/field-error-aria.ts +93 -0
  279. package/src/js/core/hydrate-modules.ts +54 -29
  280. package/src/js/core/submit-result-state.ts +51 -45
  281. package/src/js/core/submit-result-ui.ts +25 -68
  282. package/src/js/core/validation-focus.ts +20 -20
  283. package/src/js/modules/address/address-finder.ts +1 -1
  284. package/src/js/modules/address/factories.ts +15 -13
  285. package/src/js/modules/address/google-address.ts +102 -40
  286. package/src/js/modules/address/index.ts +2 -2
  287. package/src/js/modules/address/loqate.ts +1 -1
  288. package/src/js/modules/address/place-kit.ts +14 -8
  289. package/src/js/modules/captchas/captcha-eu.ts +2 -2
  290. package/src/js/modules/captchas/factories.ts +66 -42
  291. package/src/js/modules/captchas/friendly-captcha-v1.ts +12 -2
  292. package/src/js/modules/captchas/friendly-captcha-v2.ts +4 -2
  293. package/src/js/modules/captchas/hcaptcha.ts +5 -5
  294. package/src/js/modules/captchas/index.ts +2 -2
  295. package/src/js/modules/captchas/recaptcha-enterprise.ts +2 -2
  296. package/src/js/modules/captchas/recaptcha-v2-checkbox.ts +3 -3
  297. package/src/js/modules/captchas/recaptcha-v2-invisible.ts +2 -2
  298. package/src/js/modules/captchas/recaptcha-v3.ts +2 -2
  299. package/src/js/modules/captchas/snaptcha.ts +1 -1
  300. package/src/js/modules/captchas/turnstile.ts +3 -3
  301. package/src/js/modules/fields/address-country.ts +11 -9
  302. package/src/js/modules/fields/address-state.ts +94 -35
  303. package/src/js/modules/fields/calculations.ts +5 -3
  304. package/src/js/modules/fields/checkbox-radio.ts +24 -42
  305. package/src/js/modules/fields/combobox.ts +28 -9
  306. package/src/js/modules/fields/conditions/config.ts +17 -20
  307. package/src/js/modules/fields/conditions/effects.ts +25 -29
  308. package/src/js/modules/fields/conditions/evaluator.ts +21 -29
  309. package/src/js/modules/fields/conditions/references.ts +16 -5
  310. package/src/js/modules/fields/conditions/submission-context.ts +80 -0
  311. package/src/js/modules/fields/conditions/types.ts +3 -1
  312. package/src/js/modules/fields/conditions/values.ts +31 -6
  313. package/src/js/modules/fields/conditions.ts +65 -18
  314. package/src/js/modules/fields/custom-google-maps.ts +5 -3
  315. package/src/js/modules/fields/custom-link.ts +5 -3
  316. package/src/js/modules/fields/custom-maps.ts +5 -3
  317. package/src/js/modules/fields/date-picker.ts +40 -7
  318. package/src/js/modules/fields/file-upload.ts +62 -23
  319. package/src/js/modules/fields/hidden.ts +5 -3
  320. package/src/js/modules/fields/index.ts +2 -2
  321. package/src/js/modules/fields/password-validation.ts +5 -3
  322. package/src/js/modules/fields/phone-country.ts +35 -8
  323. package/src/js/modules/fields/repeater.ts +60 -7
  324. package/src/js/modules/fields/rich-text.ts +20 -4
  325. package/src/js/modules/fields/shared.ts +13 -0
  326. package/src/js/modules/fields/signature.ts +24 -4
  327. package/src/js/modules/fields/summary.ts +8 -20
  328. package/src/js/modules/fields/survey-likert.ts +5 -3
  329. package/src/js/modules/fields/survey-rank.ts +21 -3
  330. package/src/js/modules/fields/survey-rating.ts +17 -3
  331. package/src/js/modules/fields/table.ts +5 -3
  332. package/src/js/modules/fields/text-limit.ts +5 -3
  333. package/src/js/modules/fields/upload-manager.ts +244 -67
  334. package/src/js/modules/loader.ts +345 -281
  335. package/src/js/modules/payments/bpoint.ts +2 -2
  336. package/src/js/modules/payments/eway.ts +2 -2
  337. package/src/js/modules/payments/factories.ts +53 -52
  338. package/src/js/modules/payments/go-cardless.ts +1 -1
  339. package/src/js/modules/payments/index.ts +2 -2
  340. package/src/js/modules/payments/mollie.ts +1 -1
  341. package/src/js/modules/payments/moneris.ts +2 -2
  342. package/src/js/modules/payments/opayo.ts +21 -8
  343. package/src/js/modules/payments/paddle.ts +10 -6
  344. package/src/js/modules/payments/payment.ts +22 -18
  345. package/src/js/modules/payments/paypal.ts +1 -1
  346. package/src/js/modules/payments/payway.ts +2 -2
  347. package/src/js/modules/payments/square.ts +2 -2
  348. package/src/js/modules/payments/stripe.ts +5 -5
  349. package/src/js/modules/payments/utils.ts +14 -0
  350. package/src/js/modules/registry.ts +19 -11
  351. package/src/js/submit/pipeline.ts +10 -11
  352. package/src/js/theme/browser-theme-state.generated.ts +25 -0
  353. package/src/js/theme/theme-classes.ts +3 -3
  354. package/src/js/transport/forms-api.ts +66 -67
  355. package/src/js/utils/async.ts +34 -0
  356. package/src/js/utils/country-from-ip.ts +14 -9
  357. package/src/js/utils/csrf.ts +136 -0
  358. package/src/js/utils/field-references.parser.ts +13 -113
  359. package/src/js/utils/field-references.resolver.ts +27 -64
  360. package/src/js/utils/field-references.row-scope.ts +1 -1
  361. package/src/js/utils/field-references.types.ts +1 -0
  362. package/src/js/utils/http.ts +5 -5
  363. package/src/js/utils/request-profile.ts +10 -0
  364. package/src/js/utils/unload-warning.ts +12 -4
  365. package/src/js/validation/rules/email.ts +3 -5
  366. package/src/js/validation/rules/match.ts +2 -1
  367. package/src/js/validation/rules/minmax.ts +4 -43
  368. package/src/js/validation/rules/number.ts +3 -47
  369. package/src/js/validation/rules/pattern.ts +3 -8
  370. package/src/js/validation/rules/required.ts +14 -1
  371. package/src/js/validation/rules/shared.ts +5 -1
  372. package/src/js/validation/rules/url.ts +3 -10
  373. package/src/js/validation/skip.ts +9 -0
  374. package/src/js/validation/submit-readiness.ts +9 -2
  375. package/src/js/validation/validator.ts +49 -61
  376. package/dist/chunks/address-state-BwE01m2_.js +0 -344
  377. package/dist/chunks/calculations-Dv3hYb8_.js +0 -299
  378. package/dist/chunks/conditions-CPH1snCx.js +0 -424
  379. package/dist/chunks/country-from-ip-BwxKaBuJ.js +0 -26
  380. package/dist/chunks/field-references.resolver-CHwn0G0L.js +0 -145
  381. package/dist/chunks/friendly-captcha-v1-C_2reOXr.js +0 -31
  382. package/dist/chunks/google-address-BH3Zhi06.js +0 -161
  383. package/dist/chunks/http-D-JExro7.js +0 -27
  384. package/dist/chunks/opayo-CR0DtsLa.js +0 -179
  385. package/dist/chunks/rich-text-Bg_--4TG.js +0 -349
  386. package/dist/js/modules/fields/conditions/transforms.d.ts +0 -3
  387. package/dist/js/modules/fields/conditions/transforms.d.ts.map +0 -1
  388. package/src/js/modules/fields/conditions/transforms.ts +0 -206
  389. package/src/js/modules/payments/stub-payment-module.ts +0 -87
@@ -0,0 +1,69 @@
1
+ import { setFormBrowserRequestOptions } from '#utils/request-profile';
2
+ import type { BrowserModuleKind, ClientFormInstance } from '@verbb/formie-core';
3
+ import { hydrateFormieModules } from './hydrate-modules';
4
+ import { ModuleRegistry, clientRenderedModuleRegistry } from '#modules/registry';
5
+
6
+ // These modules are implemented by the shared core state engine or native
7
+ // framework controls. Explicit delegation preserves every declared occurrence.
8
+ const nativeModuleDelegations: Record<string, { kind: BrowserModuleKind; reason: string }> = {
9
+ conditions: { kind: 'core', reason: 'Core evaluates structured conditions and the adapter renders visibility.' },
10
+ repeater: { kind: 'field', reason: 'The adapter owns row markup and core owns row values.' },
11
+ signature: { kind: 'field', reason: 'The adapter owns its signature control and cleanup.' },
12
+ 'file-upload': { kind: 'field', reason: 'The shared transport stages selected files and submits attachment capabilities.' },
13
+ 'upload-manager': { kind: 'field', reason: 'Native file selection uses the shared staged upload transport.' },
14
+ 'checkbox-radio': { kind: 'field', reason: 'Framework controls own checked state.' },
15
+ 'text-limit': { kind: 'field', reason: 'Core validation enforces the structured minimum and maximum rules.' },
16
+ 'date-picker': { kind: 'field', reason: 'The adapter renders the structured date input contract.' },
17
+ };
18
+
19
+ export async function mountClientRenderedModules(root: Element, instance: ClientFormInstance, registry = clientRenderedModuleRegistry) {
20
+ for (const [moduleName, delegation] of Object.entries(nativeModuleDelegations)) {
21
+ const moduleId = `formie:${moduleName}`;
22
+ if (registry.get(moduleId)) continue;
23
+ registry.register({
24
+ moduleId,
25
+ version: 2,
26
+ surfaces: ['client-rendered'],
27
+ kind: delegation.kind,
28
+ match: () => true,
29
+ setup: async(context) => {
30
+ await context.emit('formie:browser:module:delegated', { moduleId, reason: delegation.reason, target: context.target });
31
+ return { destroy: () => undefined };
32
+ },
33
+ });
34
+ }
35
+ const form = root instanceof HTMLFormElement ? root : root.querySelector('form');
36
+ const syncRequest = () => {
37
+ if (!form) return;
38
+ const state = instance.getState();
39
+ setFormBrowserRequestOptions(form, instance.getBrowserRequestOptions());
40
+ form.action = state.definition.submission.endpoint;
41
+ if (state.session.tokens.csrf) form.setAttribute('data-formie-csrf-param', state.session.tokens.csrf.name);
42
+ for (const [name, value] of Object.entries({ handle: state.definition.handle, ...(state.session.tokens.csrf ? { [state.session.tokens.csrf.name]: state.session.tokens.csrf.value } : {}) })) {
43
+ let input = form.querySelector<HTMLInputElement>(`input[type="hidden"][name="${CSS.escape(name)}"]`);
44
+ if (!input) { input = document.createElement('input'); input.type = 'hidden'; input.name = name; form.append(input); }
45
+ input.value = value;
46
+ }
47
+ };
48
+ syncRequest();
49
+ const unsubscribeRequest = instance.subscribe(syncRequest);
50
+ const host = await hydrateFormieModules({ root, modules: instance.getState().definition.modules, surface: 'client-rendered', registry });
51
+ instance.setBrowserModuleGuard(host.assertReady);
52
+ instance.setBrowserModulePreparation(host.prepare);
53
+ const unsubscribe = instance.on('formie:submit:result', (payload) => {
54
+ const result = payload as import('@verbb/formie-core').ClientSubmitResult;
55
+ const completion = result.completion;
56
+ if (result.success && completion) {
57
+ if (completion.behavior === 'redirect' && typeof completion.url === 'string') {
58
+ if (completion.target === 'new-tab') window.open(completion.url, '_blank', 'noopener,noreferrer');
59
+ else window.location.assign(completion.url);
60
+ } else if (completion.behavior === 'reload') {
61
+ window.location.reload();
62
+ } else if (completion.behavior === 'reset') {
63
+ instance.reset();
64
+ }
65
+ }
66
+ void host.result({ ok: result.success, outcome: result.outcome, version: result.version, submissionUid: result.submissionUid, errors: result.errors, session: result.session, completion: result.completion, meta: result as unknown as Record<string, unknown> });
67
+ });
68
+ return { ...host, get instances() { return host.instances; }, get failures() { return host.failures; }, destroy: async() => { unsubscribe(); unsubscribeRequest(); await host.destroy(); } };
69
+ }
@@ -1,3 +1,5 @@
1
+ import { setFormBrowserRequestOptions } from '#utils/request-profile';
2
+ import { assertBrowserModuleManifest } from '@verbb/formie-core';
1
3
  import type {
2
4
  FormieClient,
3
5
  FormieFormInstance,
@@ -7,8 +9,8 @@ import { bindLegacyDomEventCompatibility } from '#compatibility/dom-adapter';
7
9
  import { resolveLegacyCompatibilityOptions } from '#compatibility/event-map';
8
10
  import { bindLegacyValidatorCompatibility } from '#compatibility/validator-adapter';
9
11
  import type { FormAction, FormMode, FormTransport } from '#contracts/common';
10
- import type { FormieModuleDefinition, FormieModuleInstance } from '#contracts/modules';
11
- import type { FormEndpointPayload, FormModuleManifest, FormSubmitResult } from '#contracts/schema';
12
+ import type { BrowserModuleDefinition, BrowserModuleInstance } from '#contracts/modules';
13
+ import type { FormEndpointPayload, BrowserModuleEntry, FormSubmitResult } from '#contracts/schema';
12
14
  import type { ThemeClassMap } from '#contracts/theme';
13
15
  import { dispatchFormieDomEvent } from '#core/dom-events';
14
16
  import { getErrorAriaLivePreference } from '#core/error-aria-live';
@@ -20,16 +22,17 @@ import {
20
22
  hasServerRenderedValidationErrors,
21
23
  } from '#core/validation-focus';
22
24
  import { clearSubmitFeedback, executeAjaxSubmitFlow, shouldKeepSubmitLoading } from '#core/submit-flow';
23
- import { applySubmitResultUi } from '#core/submit-result-ui';
25
+ import { applySubmitResultUi, renderFieldErrors, renderFormErrors } from '#core/submit-result-ui';
24
26
  import { applyPageState, clearSubmitLoading, setSubmitLoading } from '#core/submit-result-state';
25
27
  import { EventBus } from '#events/event-bus';
26
28
  import { ModuleRegistry } from '#modules/registry';
27
- import { loadModulesFromManifest } from '#modules/loader';
29
+ import { loadModulesFromManifest, type BrowserModuleRuntime } from '#modules/loader';
28
30
  import { registerThemeClassMap } from '#theme/theme-classes';
29
31
  import { runSubmitPipeline } from '#submit/pipeline';
30
32
  import { clearSubmissionOnUnload, requestGraphqlRender, requestRefreshTokens, requestRender, requestSetPage } from '#transport/forms-api';
31
33
  import { ensureFormStartedAt } from '#utils/form-started-at';
32
34
  import { createDebug } from '#utils/debug';
35
+ import { waitForElementRemoval } from '#utils/async';
33
36
  import { createFormUnloadWarningGuard } from '#utils/unload-warning';
34
37
  import { getValidationScope } from '#validation/scope';
35
38
  import { FormieValidator } from '#validation/validator';
@@ -41,7 +44,7 @@ type InternalInstanceState = {
41
44
  bus: EventBus;
42
45
  form: HTMLFormElement | null;
43
46
  validator: FormieValidator | null;
44
- modules: FormieModuleInstance[];
47
+ modules: BrowserModuleRuntime;
45
48
  unbinds: Array<() => void>;
46
49
  instance: FormieFormInstance;
47
50
  };
@@ -73,12 +76,12 @@ function parseBooleanOption(value: string | undefined, defaultValue: boolean): b
73
76
  }
74
77
 
75
78
  function inferStaticCacheOnLoadFromDataset(dataset: DOMStringMap): boolean {
76
- if (dataset.formieRefreshTokens != null && dataset.formieRefreshTokens !== '') {
77
- return parseBooleanOption(dataset.formieRefreshTokens, false);
79
+ if (dataset.formieRefreshTokens != null) {
80
+ return parseBooleanOption(dataset.formieRefreshTokens, true);
78
81
  }
79
82
 
80
- if (dataset.formieStaticCache != null && dataset.formieStaticCache !== '') {
81
- return parseBooleanOption(dataset.formieStaticCache, false);
83
+ if (dataset.formieStaticCache != null) {
84
+ return parseBooleanOption(dataset.formieStaticCache, true);
82
85
  }
83
86
 
84
87
  return false;
@@ -90,6 +93,7 @@ function inferOptionsFromElement(target: Element): FormMountOptions {
90
93
  return {
91
94
  mode: 'server-rendered',
92
95
  transport: (dataset.formieTransport as FormTransport) || 'rest',
96
+ profile: dataset.formieRequestProfile as FormMountOptions['profile'],
93
97
  formHandle: dataset.formieHandle,
94
98
  endpoint: dataset.formieEndpoint,
95
99
  staticCache: inferStaticCacheOnLoadFromDataset(dataset),
@@ -99,7 +103,10 @@ function inferOptionsFromElement(target: Element): FormMountOptions {
99
103
  }
100
104
 
101
105
  function normalizeMode(mode: FormMountOptions['mode'] | undefined): FormMode {
102
- return mode || 'server-rendered';
106
+ if (mode && mode !== 'server-rendered') {
107
+ throw new Error('@verbb/formie-browser enhances server-rendered HTML only. Use @verbb/formie-core for client-rendered forms.');
108
+ }
109
+ return 'server-rendered';
103
110
  }
104
111
 
105
112
  function normalizeTransport(transport: FormMountOptions['transport'] | undefined): FormTransport {
@@ -153,7 +160,7 @@ function resolveHeadlessEndpoint(baseOrEndpoint: string | undefined, actionPath:
153
160
  return actionPath;
154
161
  }
155
162
 
156
- if (candidate.includes('/actions/')) {
163
+ if (candidate.includes(actionPath)) {
157
164
  return candidate;
158
165
  }
159
166
 
@@ -206,7 +213,7 @@ function mergeSearchParams(sourceUrl: string | null, destinationUrl: string): st
206
213
  }
207
214
  }
208
215
 
209
- function normalizeHeadlessManagedUrls(target: Element, form: HTMLFormElement, options: FormMountOptions): void {
216
+ function normalizeServerRenderedUrls(target: Element, form: HTMLFormElement, options: FormMountOptions): void {
210
217
  const baseEndpoint = options.endpoint || (target as HTMLElement).dataset.formieEndpoint;
211
218
  const submitAction = resolveHeadlessEndpoint(baseEndpoint, DEFAULT_HEADLESS_SUBMIT_ACTION);
212
219
  const existingAction = form.getAttribute('action');
@@ -228,11 +235,7 @@ function normalizeHeadlessManagedUrls(target: Element, form: HTMLFormElement, op
228
235
  });
229
236
  }
230
237
 
231
- function ensureSupportedHeadlessTransport(transport: FormTransport, mode: FormMode): void {
232
- if (transport === 'graphql' && mode !== 'server-rendered') {
233
- throw new Error(`Formie ${mode} mode does not support GraphQL transport yet.`);
234
- }
235
- }
238
+
236
239
 
237
240
  function parseBooleanDatasetValue(value: string | undefined): boolean {
238
241
  if (value == null) {
@@ -339,6 +342,7 @@ function parseJsonAttribute<T>(element: Element | null, attributeName: string):
339
342
  try {
340
343
  return JSON.parse(rawValue) as T;
341
344
  } catch (error) {
345
+ if (attributeName === 'data-formie-modules') throw new Error('Invalid browser-module manifest JSON. Update Formie and its browser packages together.');
342
346
  console.error(`[formie] Failed to parse ${attributeName}.`, error);
343
347
  return null;
344
348
  }
@@ -354,7 +358,8 @@ function getEmbeddedPayload(target: Element, form: HTMLFormElement | null): Form
354
358
  // Server-rendered forms carry the same minimal payload shape that endpoint-
355
359
  // rendered forms receive, which keeps mount behavior identical in both flows.
356
360
  const modules = parseJsonAttribute<FormEndpointPayload['modules']>(payloadRoot, 'data-formie-modules');
357
- const theme = parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme');
361
+ const theme = parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme-classes')
362
+ || parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme');
358
363
 
359
364
  if (!modules && !theme) {
360
365
  return null;
@@ -425,6 +430,8 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
425
430
  csrfInput.name = refreshTokens.csrf.param;
426
431
  csrfInput.value = refreshTokens.csrf.token;
427
432
  csrfInput.setAttribute('autocomplete', 'off');
433
+ // Match server-rendered CSRF inputs so helpers can resolve custom token names.
434
+ csrfInput.setAttribute('data-formie-csrf', '');
428
435
  form.prepend(csrfInput);
429
436
  }
430
437
  }
@@ -443,6 +450,19 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
443
450
  }
444
451
  }
445
452
 
453
+ if (refreshTokens.uploadCreateToken) {
454
+ let uploadCreateInput = form.querySelector('input[name="uploadCreateToken"]') as HTMLInputElement | null;
455
+
456
+ if (!uploadCreateInput) {
457
+ uploadCreateInput = document.createElement('input');
458
+ uploadCreateInput.type = 'hidden';
459
+ uploadCreateInput.name = 'uploadCreateToken';
460
+ form.append(uploadCreateInput);
461
+ }
462
+
463
+ uploadCreateInput.value = refreshTokens.uploadCreateToken;
464
+ }
465
+
446
466
  if (refreshTokens.captchas && typeof refreshTokens.captchas === 'object') {
447
467
  Object.values(refreshTokens.captchas).forEach((captchaEntry) => {
448
468
  if (!captchaEntry || typeof captchaEntry !== 'object') {
@@ -478,7 +498,6 @@ async function ensureHtmlRender(target: Element, options: FormMountOptions): Pro
478
498
  return options.payload;
479
499
  }
480
500
 
481
- ensureSupportedHeadlessTransport(transport, mode);
482
501
 
483
502
  const hasForm = !!getFormFromTarget(target);
484
503
  const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
@@ -501,11 +520,11 @@ async function ensureHtmlRender(target: Element, options: FormMountOptions): Pro
501
520
  ? resolveGraphqlEndpoint(options, target)
502
521
  : resolveHtmlRenderEndpoint(options, target);
503
522
  const payload = transport === 'graphql'
504
- ? await requestGraphqlRender(endpoint, formHandle, renderOptions)
523
+ ? await requestGraphqlRender(endpoint, formHandle, renderOptions, options)
505
524
  : await requestRender(endpoint, formHandle, {
506
525
  ...renderOptions,
507
526
  endpoint,
508
- });
527
+ }, options);
509
528
 
510
529
  if (payload?.html) {
511
530
  (target as HTMLElement).innerHTML = payload.html;
@@ -519,7 +538,6 @@ async function refreshTokensAfterSubmitIfNeeded(target: Element, options: FormMo
519
538
  return;
520
539
  }
521
540
 
522
- ensureSupportedHeadlessTransport(normalizeTransport(options.transport), normalizeMode(options.mode));
523
541
 
524
542
  const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
525
543
  if (!formHandle) {
@@ -531,7 +549,7 @@ async function refreshTokensAfterSubmitIfNeeded(target: Element, options: FormMo
531
549
  const endpoint = resolveRefreshTokensEndpoint(options, target);
532
550
  const renderIdInput = form.querySelector('input[name="renderId"]') as HTMLInputElement | null;
533
551
  const renderId = renderIdInput?.value || undefined;
534
- const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
552
+ const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
535
553
  applyRefreshTokensToForm(form, refreshTokens);
536
554
  dispatchFormieDomEvent(target, 'formie:refresh-tokens:refreshed', refreshTokens);
537
555
  }
@@ -543,7 +561,10 @@ function bindFormEvents(
543
561
  bus: EventBus,
544
562
  validator: FormieValidator | null,
545
563
  unbinds: Array<() => void>,
546
- ): void {
564
+ ): () => void {
565
+ let captureBaseline = () => {};
566
+ form.dataset.formieRequestProfile = options.profile ?? 'same-origin-browser';
567
+ if (options.profile === 'cross-origin-public') form.dataset.formieSubmitMethod = 'ajax';
547
568
  const submitMethod = String(
548
569
  form.dataset.formieSubmitMethod || '',
549
570
  ).trim().toLowerCase();
@@ -567,6 +588,7 @@ function bindFormEvents(
567
588
  return !hasInternalNavigation(form);
568
589
  },
569
590
  });
591
+ captureBaseline = unloadWarning.scheduleBaselineCapture;
570
592
  const handleSubmitResult = (event: Event): void => {
571
593
  if (!(event instanceof CustomEvent)) {
572
594
  return;
@@ -578,7 +600,7 @@ function bindFormEvents(
578
600
  return;
579
601
  }
580
602
 
581
- if (result.action === 'save') {
603
+ if (result.action === 'save' || result.outcome === 'revised') {
582
604
  unloadWarning.scheduleBaselineCapture();
583
605
  }
584
606
  };
@@ -625,8 +647,7 @@ function bindFormEvents(
625
647
  return;
626
648
  }
627
649
 
628
- // Ajax multipage navigation updates UI immediately, then persists page
629
- // continuity in the background so refresh/recovery stays in sync.
650
+ // The server validates forward navigation before selecting the page.
630
651
  event.preventDefault();
631
652
 
632
653
  const currentTarget = event.currentTarget as HTMLAnchorElement | null;
@@ -637,7 +658,6 @@ function bindFormEvents(
637
658
  return;
638
659
  }
639
660
 
640
- applyPageState(form, nextPageId);
641
661
  dispatchFormieDomEvent(target, 'formie:page:navigate', {
642
662
  pageId: nextPageId,
643
663
  href,
@@ -645,6 +665,15 @@ function bindFormEvents(
645
665
 
646
666
  try {
647
667
  const response = await requestSetPage(href, form, nextPageId);
668
+ if (response.pageId) applyPageState(form, String(response.pageId));
669
+ if (!response.success) {
670
+ const { form: formErrors = [], ...fieldErrors } = response.errors ?? {};
671
+ clearSubmitFeedback(form);
672
+ renderFieldErrors(form, fieldErrors);
673
+ renderFormErrors(form, formErrors);
674
+ focusFirstValidationError(form);
675
+ return;
676
+ }
648
677
 
649
678
  dispatchFormieDomEvent(target, 'formie:page:navigate:after', {
650
679
  pageId: nextPageId,
@@ -829,6 +858,8 @@ function bindFormEvents(
829
858
  unbinds.push(() => {
830
859
  form.removeEventListener('submit', submitHandler);
831
860
  });
861
+
862
+ return captureBaseline;
832
863
  }
833
864
 
834
865
  async function refreshTokensIfNeeded(target: Element, options: FormMountOptions, form: HTMLFormElement | null): Promise<void> {
@@ -840,7 +871,6 @@ async function refreshTokensIfNeeded(target: Element, options: FormMountOptions,
840
871
  return;
841
872
  }
842
873
 
843
- ensureSupportedHeadlessTransport(normalizeTransport(options.transport), normalizeMode(options.mode));
844
874
 
845
875
  const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
846
876
  const endpoint = resolveRefreshTokensEndpoint(options, target);
@@ -853,7 +883,7 @@ async function refreshTokensIfNeeded(target: Element, options: FormMountOptions,
853
883
 
854
884
  // Refresh-tokens-before-ready is the cache-safe path: forms can render from SSR/cache
855
885
  // and still receive fresh transport tokens before the user submits anything.
856
- const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
886
+ const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
857
887
 
858
888
  if (!refreshTokens || !form) {
859
889
  return;
@@ -869,14 +899,14 @@ export function createFormieClient(): FormieClient {
869
899
  const moduleRegistry = new ModuleRegistry();
870
900
  const pendingVisibilityMounts = new Map<Element, () => void>();
871
901
  const pendingUnmounts = new Map<Element, Promise<void>>();
872
- const stageNames: Array<'prepare' | 'normalize' | 'validate' | 'screen' | 'authorize' | 'dispatch' | 'finalize'> = [
902
+ const stageNames: Array<'prepare' | 'normalize' | 'validate' | 'challenge' | 'payment' | 'send' | 'result'> = [
873
903
  'prepare',
874
904
  'normalize',
875
905
  'validate',
876
- 'screen',
877
- 'authorize',
878
- 'dispatch',
879
- 'finalize',
906
+ 'challenge',
907
+ 'payment',
908
+ 'send',
909
+ 'result',
880
910
  ];
881
911
 
882
912
  const unmount = async(target: Element): Promise<void> => {
@@ -913,10 +943,7 @@ export function createFormieClient(): FormieClient {
913
943
  state.validator?.destroy();
914
944
  state.validator = null;
915
945
 
916
- for (const moduleInstance of state.modules) {
917
- await moduleInstance.destroy();
918
- }
919
- state.modules = [];
946
+ await state.modules.destroy();
920
947
 
921
948
  state.bus.clear();
922
949
  instances.delete(target);
@@ -965,20 +992,37 @@ export function createFormieClient(): FormieClient {
965
992
  mode: normalizeMode(options.mode ?? mergedFromDom.mode),
966
993
  transport: normalizeTransport(options.transport ?? mergedFromDom.transport),
967
994
  };
968
- const compatibilityOptions = resolveLegacyCompatibilityOptions(normalizedOptions.compatibility);
995
+ const renderPayload = await ensureHtmlRender(target, normalizedOptions);
996
+ const form = getFormFromTarget(target);
969
997
 
970
- if (normalizedOptions.mode !== 'server-rendered' && !getFormFromTarget(target)) {
971
- throw new Error(`Formie ${normalizedOptions.mode} mode is not implemented yet in the browser client.`);
998
+ // Fetched HTML carries the plugin setting on the form, not its mount wrapper.
999
+ if (options.compatibility === undefined && form?.hasAttribute('data-formie-compatibility')) {
1000
+ normalizedOptions.compatibility = inferOptionsFromElement(form).compatibility;
972
1001
  }
973
1002
 
974
- const renderPayload = await ensureHtmlRender(target, normalizedOptions);
975
- const form = getFormFromTarget(target);
1003
+ const compatibilityOptions = resolveLegacyCompatibilityOptions(normalizedOptions.compatibility);
1004
+ if (form) setFormBrowserRequestOptions(form, normalizedOptions);
976
1005
  normalizedOptions.staticCache =
977
1006
  options.staticCache ??
978
1007
  (form
979
1008
  ? inferStaticCacheOnLoadFromDataset(form.dataset)
980
1009
  : inferStaticCacheOnLoadFromDataset((target as HTMLElement).dataset));
981
- const embeddedPayload = getEmbeddedPayload(target, form);
1010
+ let embeddedPayload: FormEndpointPayload | null;
1011
+ try {
1012
+ embeddedPayload = getEmbeddedPayload(target, form);
1013
+ if (renderPayload?.modules) assertBrowserModuleManifest(renderPayload.modules);
1014
+ if (embeddedPayload?.modules) assertBrowserModuleManifest(embeddedPayload.modules);
1015
+ } catch (error) {
1016
+ // Unknown semantics must not fall back to an unguarded native send.
1017
+ if (form) {
1018
+ form.addEventListener('submit', (event) => { event.preventDefault(); event.stopImmediatePropagation(); }, true);
1019
+ const alert = document.createElement('div');
1020
+ alert.setAttribute('role', 'alert');
1021
+ alert.textContent = 'This form requires a compatible Formie browser package. Update Formie and its browser packages together.';
1022
+ form.prepend(alert);
1023
+ }
1024
+ throw error;
1025
+ }
982
1026
  const payload = renderPayload || embeddedPayload
983
1027
  ? {
984
1028
  ...(renderPayload || {}),
@@ -987,14 +1031,13 @@ export function createFormieClient(): FormieClient {
987
1031
  : null;
988
1032
  const themeClassMap = payload?.theme as ThemeClassMap | undefined;
989
1033
  const stateStore: Record<string, unknown> = {};
990
- const moduleManifest = ((payload?.modules || []) as FormModuleManifest[]).filter((item) => {
991
- return !!item?.id && !!item?.type;
992
- });
1034
+ const moduleManifest = payload?.modules ?? { contractVersion: 2, surface: 'server-rendered' as const, entries: [] };
1035
+ assertBrowserModuleManifest(moduleManifest);
993
1036
  debug.log('Resolved mount payload.', {
994
1037
  target: getTargetDebugLabel(target),
995
1038
  hasRenderPayload: !!renderPayload,
996
1039
  hasEmbeddedPayload: !!embeddedPayload,
997
- moduleCount: moduleManifest.length,
1040
+ moduleCount: moduleManifest.entries.length,
998
1041
  });
999
1042
  const resolvedThemeClassMap = registerThemeClassMap(target, themeClassMap, form);
1000
1043
 
@@ -1028,16 +1071,8 @@ export function createFormieClient(): FormieClient {
1028
1071
  if (form) {
1029
1072
  ensureFormStartedAt(form);
1030
1073
 
1031
- if (normalizedOptions.themeConfig && typeof normalizedOptions.themeConfig === 'object') {
1032
- form.setAttribute('data-formie-theme-config', JSON.stringify(normalizedOptions.themeConfig));
1033
- }
1034
-
1035
- if (normalizedOptions.theme && normalizedOptions.theme !== 'formie') {
1036
- form.setAttribute('data-formie-frontend-theme', normalizedOptions.theme);
1037
- }
1038
-
1039
1074
  if (renderPayload || normalizedOptions.endpoint || (target as HTMLElement).dataset.formieEndpoint) {
1040
- normalizeHeadlessManagedUrls(target, form, normalizedOptions);
1075
+ normalizeServerRenderedUrls(target, form, normalizedOptions);
1041
1076
  }
1042
1077
 
1043
1078
  if (normalizedOptions.mode === 'server-rendered' && hasServerRenderedValidationErrors(form)) {
@@ -1054,6 +1089,13 @@ export function createFormieClient(): FormieClient {
1054
1089
  });
1055
1090
  }
1056
1091
 
1092
+ // Craft's async CSRF element initializes the session. Let it finish before
1093
+ // captcha modules request session-bound tokens, otherwise the concurrent
1094
+ // responses can race and replace each other's session cookie.
1095
+ if (form?.querySelector('craft-csrf-input')) {
1096
+ await waitForElementRemoval('craft-csrf-input', form);
1097
+ }
1098
+
1057
1099
  // Modules run against the mounted DOM surface, not against server payload
1058
1100
  // objects, so the loader resolves real targets before calling setup().
1059
1101
  const modules = await loadModulesFromManifest(moduleManifest, {
@@ -1061,7 +1103,7 @@ export function createFormieClient(): FormieClient {
1061
1103
  matchContext: {
1062
1104
  root: target,
1063
1105
  form,
1064
- mode: normalizedOptions.mode,
1106
+ surface: moduleManifest.surface,
1065
1107
  },
1066
1108
  setupContext: {
1067
1109
  formId: id,
@@ -1069,6 +1111,7 @@ export function createFormieClient(): FormieClient {
1069
1111
  form,
1070
1112
  target,
1071
1113
  scope: 'form',
1114
+ surface: moduleManifest.surface,
1072
1115
  state: stateStore,
1073
1116
  on: (eventName, callback) => {
1074
1117
  return bus.on(eventName, callback);
@@ -1088,7 +1131,7 @@ export function createFormieClient(): FormieClient {
1088
1131
  });
1089
1132
  debug.log('Module setup complete.', {
1090
1133
  target: getTargetDebugLabel(target),
1091
- moduleInstances: modules.length,
1134
+ moduleInstances: modules.instances.length,
1092
1135
  });
1093
1136
 
1094
1137
  const instance: FormieFormInstance = {
@@ -1184,7 +1227,7 @@ export function createFormieClient(): FormieClient {
1184
1227
  }
1185
1228
 
1186
1229
  if (form) {
1187
- bindFormEvents(target, form, normalizedOptions, bus, validator, unbinds);
1230
+ const captureBaseline = bindFormEvents(target, form, normalizedOptions, bus, validator, unbinds);
1188
1231
 
1189
1232
  if (validator) {
1190
1233
  unbinds.push(bindSubmitReadiness(form, validator, target));
@@ -1195,23 +1238,28 @@ export function createFormieClient(): FormieClient {
1195
1238
 
1196
1239
  // Recapture after captcha/token hydration and field-module init settle so
1197
1240
  // programmatic post-mount value changes do not leave a false dirty state.
1198
- form.dispatchEvent(new CustomEvent('formie:state:reset'));
1199
- window.setTimeout(() => {
1200
- form.dispatchEvent(new CustomEvent('formie:state:reset'));
1201
- }, 350);
1241
+ captureBaseline();
1242
+ const baselineTimer = window.setTimeout(captureBaseline, 350);
1243
+ unbinds.push(() => window.clearTimeout(baselineTimer));
1202
1244
  }
1203
1245
 
1204
1246
  stageNames.forEach((stageName) => {
1205
- // Stage fan-out keeps submit pipeline ownership centralized while still
1206
- // letting modules participate before and after each stage.
1207
1247
  const beforeDomUnbind = bus.on(`formie:stage:${stageName}:before`, async(payload) => {
1208
1248
  dispatchFormieDomEvent(target, `formie:stage:${stageName}:before`, payload);
1209
1249
  });
1210
1250
 
1211
1251
  const beforeUnbind = bus.on(`formie:stage:${stageName}:before`, async(payload) => {
1212
- for (const moduleInstance of modules) {
1213
- if (moduleInstance.onBeforeStage) {
1214
- await moduleInstance.onBeforeStage(payload as Parameters<NonNullable<FormieModuleInstance['onBeforeStage']>>[0]);
1252
+ const hookStage = { core: 'prepare', field: 'prepare', address: 'prepare', captcha: 'challenge', payment: 'payment' } as const;
1253
+ const stagePayload = payload as Parameters<NonNullable<BrowserModuleInstance['beforeSubmit']>>[0] & { stage: string };
1254
+
1255
+ if (stageName === 'prepare') {
1256
+ try { modules.assertReady(); }
1257
+ catch { stagePayload.abort('A required form feature could not start. Reload the page or contact the site administrator.'); return; }
1258
+ }
1259
+ for (const moduleInstance of modules.instances) {
1260
+ if (moduleInstance.beforeSubmit && hookStage[moduleInstance.kind ?? 'core'] === stageName) {
1261
+ const { form, action, formData, abort, isAborted, abortReason } = stagePayload;
1262
+ await moduleInstance.beforeSubmit({ form, action, formData, abort, isAborted, abortReason });
1215
1263
  }
1216
1264
  }
1217
1265
  });
@@ -1220,19 +1268,7 @@ export function createFormieClient(): FormieClient {
1220
1268
  dispatchFormieDomEvent(target, `formie:stage:${stageName}:after`, payload);
1221
1269
  });
1222
1270
 
1223
- const afterUnbind = bus.on(`formie:stage:${stageName}:after`, async(payload) => {
1224
- const stagePayload = payload as {
1225
- result?: FormSubmitResult;
1226
- } & Parameters<NonNullable<FormieModuleInstance['onAfterStage']>>[0];
1227
-
1228
- for (const moduleInstance of modules) {
1229
- if (moduleInstance.onAfterStage) {
1230
- await moduleInstance.onAfterStage(stagePayload, stagePayload.result);
1231
- }
1232
- }
1233
- });
1234
-
1235
- unbinds.push(beforeDomUnbind, beforeUnbind, afterDomUnbind, afterUnbind);
1271
+ unbinds.push(beforeDomUnbind, beforeUnbind, afterDomUnbind);
1236
1272
  });
1237
1273
 
1238
1274
  const submitBeforeUnbind = bus.on('formie:submit:before', async(payload) => {
@@ -1241,6 +1277,16 @@ export function createFormieClient(): FormieClient {
1241
1277
 
1242
1278
  const submitAfterUnbind = bus.on('formie:submit:after', async(payload) => {
1243
1279
  dispatchFormieDomEvent(target, 'formie:submit:after', payload);
1280
+
1281
+ if (!form) return;
1282
+ const result = payload as FormSubmitResult;
1283
+ if (result.code === 'PREFLIGHT_COMPLETE') return;
1284
+ const context = {
1285
+ form,
1286
+ action: result.action ?? 'submit' as const,
1287
+ formData: new FormData(form),
1288
+ };
1289
+ for (const moduleInstance of modules.instances) await moduleInstance.afterSubmit?.(context, result);
1244
1290
  });
1245
1291
 
1246
1292
  const submitFinalBeforeUnbind = bus.on('formie:submit:final:before', async(payload) => {
@@ -1435,7 +1481,7 @@ export function createFormieClient(): FormieClient {
1435
1481
  return;
1436
1482
  }
1437
1483
 
1438
- const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
1484
+ const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
1439
1485
 
1440
1486
  if (!refreshTokens) {
1441
1487
  return;
@@ -1446,7 +1492,7 @@ export function createFormieClient(): FormieClient {
1446
1492
  };
1447
1493
 
1448
1494
  const registerModule = (
1449
- moduleDefinition: FormieModuleDefinition,
1495
+ moduleDefinition: BrowserModuleDefinition,
1450
1496
  options?: Parameters<ModuleRegistry['register']>[1],
1451
1497
  ): boolean => {
1452
1498
  return moduleRegistry.register(moduleDefinition, options);
@@ -1456,7 +1502,7 @@ export function createFormieClient(): FormieClient {
1456
1502
  moduleRegistry.unregister(moduleId);
1457
1503
  };
1458
1504
 
1459
- const getRegisteredModules = (): FormieModuleDefinition[] => {
1505
+ const getRegisteredModules = (): BrowserModuleDefinition[] => {
1460
1506
  return moduleRegistry.getAll();
1461
1507
  };
1462
1508