@verbb/formie-browser 1.0.13 → 1.0.14

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 (381) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/chunks/{address-country-Rx6GQTND.js → address-country-Dn71jroa.js} +10 -4
  3. package/dist/chunks/{address-finder-k7_BXaRe.js → address-finder-O49uDRoR.js} +2 -2
  4. package/dist/chunks/{address-state-BwE01m2_.js → address-state-ByYatUur.js} +154 -143
  5. package/dist/chunks/{api-DqpfkZWL.js → api-D6kJ7kXZ.js} +183 -179
  6. package/dist/chunks/{api-BUb6P-xu.js → api-DYYsVIub.js} +77 -47
  7. package/dist/chunks/{api-sPqGbOww.js → api-ghNr2uxX.js} +15 -9
  8. package/dist/chunks/{bpoint-Dlq34XH1.js → bpoint-D0RKFM4F.js} +3 -3
  9. package/dist/chunks/calculations-YEoVCG2q.js +310 -0
  10. package/dist/chunks/{captcha-eu-CGm8Lbrj.js → captcha-eu-DERJbXnH.js} +5 -4
  11. package/dist/chunks/{checkbox-radio-BY2aTCOG.js → checkbox-radio-gsm8QPJg.js} +74 -75
  12. package/dist/chunks/{combobox-C0EuExSI.js → combobox-CWNdx4Fk.js} +290 -279
  13. package/dist/chunks/conditions-DqNHLTbN.js +464 -0
  14. package/dist/chunks/country-from-ip-DDj_mFki.js +29 -0
  15. package/dist/chunks/csrf-DxHg_ZYt.js +61 -0
  16. package/dist/chunks/{custom-google-maps-BsaSirEZ.js → custom-google-maps-C4l2S3GO.js} +9 -3
  17. package/dist/chunks/{custom-link-CJ1-FjEM.js → custom-link-CLazgKYH.js} +8 -2
  18. package/dist/chunks/{custom-maps-DYW_pBid.js → custom-maps-DqcAlD0v.js} +10 -4
  19. package/dist/chunks/{date-picker-F9BUBDSW.js → date-picker-RUnevF7l.js} +455 -432
  20. package/dist/chunks/{dist-D6i-VNgM.js → dist-ZLeW0zZ3.js} +1281 -453
  21. package/dist/chunks/{eway-D9_m8ooj.js → eway-or6dtjoT.js} +3 -3
  22. package/dist/chunks/field-references.resolver-Ca8MguFv.js +104 -0
  23. package/dist/chunks/{file-upload-rqfZNqlH.js → file-upload-Bm-TXyNB.js} +104 -96
  24. package/dist/chunks/friendly-captcha-v1-GFqCt1Mh.js +34 -0
  25. package/dist/chunks/{friendly-captcha-v2-CLSAbkqx.js → friendly-captcha-v2-WoW246JG.js} +6 -5
  26. package/dist/chunks/{friendly-challenge-8uPatWqk.js → friendly-challenge-DPG9MJb1.js} +111 -102
  27. package/dist/chunks/{go-cardless-DAT3ZaQf.js → go-cardless-DzJ-0FKg.js} +2 -2
  28. package/dist/chunks/google-address-C1P1nTDM.js +163 -0
  29. package/dist/chunks/{hcaptcha-CGLAmgjO.js → hcaptcha-D0T8aIHz.js} +3 -3
  30. package/dist/chunks/{hidden-D7_Ch-QN.js → hidden-BZhXnNPe.js} +8 -2
  31. package/dist/chunks/http-C2QdHuWO.js +27 -0
  32. package/dist/chunks/{leaflet-src-j-lHNmhR.js → leaflet-src-DE6rv6M-.js} +42 -46
  33. package/dist/chunks/{loqate-CJPzBmSm.js → loqate-DD1S7PY9.js} +2 -2
  34. package/dist/chunks/{mollie-Bibhl67p.js → mollie-DG8Ju83x.js} +2 -2
  35. package/dist/chunks/{moneris-H2wSqk9h.js → moneris-C8m3Fvqu.js} +3 -3
  36. package/dist/chunks/opayo-B3EvrVxw.js +188 -0
  37. package/dist/chunks/{paddle-DkvIxQdG.js → paddle-BIYR9Um3.js} +3 -3
  38. package/dist/chunks/{password-validation-D06uH9va.js → password-validation-C8_uO_c_.js} +18 -12
  39. package/dist/chunks/{paypal-CQpAF7Ua.js → paypal-B_yxIo2d.js} +7 -5
  40. package/dist/chunks/{payway-CwDkC7YZ.js → payway-CeOzS0sL.js} +3 -3
  41. package/dist/chunks/{phone-country-D3ayr60L.js → phone-country-bAqje17P.js} +80 -63
  42. package/dist/chunks/{place-kit-BGHklIZ-.js → place-kit-BE5gi3vo.js} +3 -3
  43. package/dist/chunks/{placekit-autocomplete.esm-DWVabQlq.js → placekit-autocomplete.esm-CZZERYur.js} +11 -15
  44. package/dist/chunks/{recaptcha-enterprise-XPId5-bS.js → recaptcha-enterprise-CruXddZq.js} +4 -4
  45. package/dist/chunks/{recaptcha-shared-CLYJvojk.js → recaptcha-shared-AQRMDMme.js} +1 -1
  46. package/dist/chunks/{recaptcha-v2-checkbox-Cy-0PEPc.js → recaptcha-v2-checkbox-CCnJYCnG.js} +4 -4
  47. package/dist/chunks/{recaptcha-v2-invisible-BwRH540O.js → recaptcha-v2-invisible-BuVqRRYn.js} +4 -4
  48. package/dist/chunks/{recaptcha-v3-No4Ykrhk.js → recaptcha-v3-CloqhO2A.js} +4 -4
  49. package/dist/chunks/{repeater-BVnPUqTj.js → repeater-DwE27jI1.js} +25 -19
  50. package/dist/chunks/request-profile-DhwkeCpS.js +10 -0
  51. package/dist/chunks/rich-text-uV4o_pGk.js +361 -0
  52. package/dist/chunks/{chunk-3b4jIN3o.js → rolldown-runtime-DtPi1Y-2.js} +2 -2
  53. package/dist/chunks/{sdk-BhHAYgga.js → sdk-lkwSvlpM.js} +282 -234
  54. package/dist/chunks/{shared-Bx9s0i0P.js → shared-OKhSay59.js} +27 -21
  55. package/dist/chunks/{signature-IOhuicje.js → signature-7cldFbqj.js} +138 -112
  56. package/dist/chunks/{snaptcha-DKx98oon.js → snaptcha-C04kNGYQ.js} +2 -2
  57. package/dist/chunks/{square-DwdcOI1G.js → square-DEzxv9D9.js} +8 -6
  58. package/dist/chunks/{stripe-DaoG2FT1.js → stripe-CVPQGDz3.js} +10 -11
  59. package/dist/chunks/{summary-Dks0gXS3.js → summary-BF_8mrXI.js} +47 -45
  60. package/dist/chunks/{survey-likert-DkE6bR7B.js → survey-likert-fk1hUPff.js} +7 -1
  61. package/dist/chunks/{survey-rank-DN6x4IZM.js → survey-rank-BsLGl2LX.js} +37 -25
  62. package/dist/chunks/{survey-rating-BnsNL9U5.js → survey-rating-DVS6e6fM.js} +17 -5
  63. package/dist/chunks/{table-CAjCJHrL.js → table-DnIBdeKF.js} +21 -15
  64. package/dist/chunks/{text-limit-BGaY0jhI.js → text-limit-LGMPhFuC.js} +40 -34
  65. package/dist/chunks/{theme-classes-Tv7q7ToE.js → theme-classes-DAQuEqdP.js} +1 -1
  66. package/dist/chunks/{turnstile-DJakPzQD.js → turnstile-n9DNyPxi.js} +3 -3
  67. package/dist/chunks/{upload-manager-W-QDgxKD.js → upload-manager-BbiUosZq.js} +410 -350
  68. package/dist/chunks/{utils-Vqp8i_RM.js → utils-DpUBPadZ.js} +20 -14
  69. package/dist/css/formie-base.css +104 -1
  70. package/dist/css/theme/_messages.css +2 -0
  71. package/dist/css/theme/_tokens.css +31 -25
  72. package/dist/css/theme/forms/_form.css +5 -1
  73. package/dist/index.d.ts +5 -4
  74. package/dist/index.d.ts.map +1 -1
  75. package/dist/index.js +1778 -1427
  76. package/dist/js/contracts/client.d.ts +5 -3
  77. package/dist/js/contracts/client.d.ts.map +1 -1
  78. package/dist/js/contracts/common.d.ts +2 -2
  79. package/dist/js/contracts/common.d.ts.map +1 -1
  80. package/dist/js/contracts/modules.d.ts +37 -12
  81. package/dist/js/contracts/modules.d.ts.map +1 -1
  82. package/dist/js/contracts/schema.d.ts +20 -38
  83. package/dist/js/contracts/schema.d.ts.map +1 -1
  84. package/dist/js/contracts/theme.d.ts +4 -1
  85. package/dist/js/contracts/theme.d.ts.map +1 -1
  86. package/dist/js/core/client-rendered-modules.d.ts +17 -0
  87. package/dist/js/core/client-rendered-modules.d.ts.map +1 -0
  88. package/dist/js/core/create-formie-client.d.ts.map +1 -1
  89. package/dist/js/core/field-error-aria.d.ts +23 -0
  90. package/dist/js/core/field-error-aria.d.ts.map +1 -0
  91. package/dist/js/core/hydrate-modules.d.ts +10 -8
  92. package/dist/js/core/hydrate-modules.d.ts.map +1 -1
  93. package/dist/js/core/submit-result-state.d.ts.map +1 -1
  94. package/dist/js/core/submit-result-ui.d.ts.map +1 -1
  95. package/dist/js/core/validation-focus.d.ts.map +1 -1
  96. package/dist/js/modules/address/address-finder.d.ts +1 -1
  97. package/dist/js/modules/address/address-finder.d.ts.map +1 -1
  98. package/dist/js/modules/address/factories.d.ts +3 -3
  99. package/dist/js/modules/address/factories.d.ts.map +1 -1
  100. package/dist/js/modules/address/google-address.d.ts +2 -1
  101. package/dist/js/modules/address/google-address.d.ts.map +1 -1
  102. package/dist/js/modules/address/index.d.ts +2 -2
  103. package/dist/js/modules/address/index.d.ts.map +1 -1
  104. package/dist/js/modules/address/loqate.d.ts +1 -1
  105. package/dist/js/modules/address/loqate.d.ts.map +1 -1
  106. package/dist/js/modules/address/place-kit.d.ts +1 -1
  107. package/dist/js/modules/address/place-kit.d.ts.map +1 -1
  108. package/dist/js/modules/captchas/captcha-eu.d.ts +1 -1
  109. package/dist/js/modules/captchas/captcha-eu.d.ts.map +1 -1
  110. package/dist/js/modules/captchas/factories.d.ts +11 -11
  111. package/dist/js/modules/captchas/factories.d.ts.map +1 -1
  112. package/dist/js/modules/captchas/friendly-captcha-v1.d.ts +1 -1
  113. package/dist/js/modules/captchas/friendly-captcha-v1.d.ts.map +1 -1
  114. package/dist/js/modules/captchas/friendly-captcha-v2.d.ts +1 -1
  115. package/dist/js/modules/captchas/friendly-captcha-v2.d.ts.map +1 -1
  116. package/dist/js/modules/captchas/hcaptcha.d.ts +1 -1
  117. package/dist/js/modules/captchas/hcaptcha.d.ts.map +1 -1
  118. package/dist/js/modules/captchas/index.d.ts +2 -2
  119. package/dist/js/modules/captchas/index.d.ts.map +1 -1
  120. package/dist/js/modules/captchas/recaptcha-enterprise.d.ts +1 -1
  121. package/dist/js/modules/captchas/recaptcha-enterprise.d.ts.map +1 -1
  122. package/dist/js/modules/captchas/recaptcha-v2-checkbox.d.ts +1 -1
  123. package/dist/js/modules/captchas/recaptcha-v2-checkbox.d.ts.map +1 -1
  124. package/dist/js/modules/captchas/recaptcha-v2-invisible.d.ts +1 -1
  125. package/dist/js/modules/captchas/recaptcha-v2-invisible.d.ts.map +1 -1
  126. package/dist/js/modules/captchas/recaptcha-v3.d.ts +1 -1
  127. package/dist/js/modules/captchas/recaptcha-v3.d.ts.map +1 -1
  128. package/dist/js/modules/captchas/snaptcha.d.ts +1 -1
  129. package/dist/js/modules/captchas/snaptcha.d.ts.map +1 -1
  130. package/dist/js/modules/captchas/turnstile.d.ts +1 -1
  131. package/dist/js/modules/captchas/turnstile.d.ts.map +1 -1
  132. package/dist/js/modules/fields/address-country.d.ts +2 -2
  133. package/dist/js/modules/fields/address-country.d.ts.map +1 -1
  134. package/dist/js/modules/fields/address-state.d.ts +2 -2
  135. package/dist/js/modules/fields/address-state.d.ts.map +1 -1
  136. package/dist/js/modules/fields/calculations.d.ts +2 -2
  137. package/dist/js/modules/fields/calculations.d.ts.map +1 -1
  138. package/dist/js/modules/fields/checkbox-radio.d.ts +2 -2
  139. package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
  140. package/dist/js/modules/fields/combobox.d.ts +2 -2
  141. package/dist/js/modules/fields/combobox.d.ts.map +1 -1
  142. package/dist/js/modules/fields/conditions/config.d.ts.map +1 -1
  143. package/dist/js/modules/fields/conditions/effects.d.ts +1 -0
  144. package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
  145. package/dist/js/modules/fields/conditions/evaluator.d.ts +4 -1
  146. package/dist/js/modules/fields/conditions/evaluator.d.ts.map +1 -1
  147. package/dist/js/modules/fields/conditions/references.d.ts.map +1 -1
  148. package/dist/js/modules/fields/conditions/submission-context.d.ts +15 -0
  149. package/dist/js/modules/fields/conditions/submission-context.d.ts.map +1 -0
  150. package/dist/js/modules/fields/conditions/types.d.ts +3 -1
  151. package/dist/js/modules/fields/conditions/types.d.ts.map +1 -1
  152. package/dist/js/modules/fields/conditions/values.d.ts +1 -1
  153. package/dist/js/modules/fields/conditions/values.d.ts.map +1 -1
  154. package/dist/js/modules/fields/conditions.d.ts +2 -2
  155. package/dist/js/modules/fields/conditions.d.ts.map +1 -1
  156. package/dist/js/modules/fields/custom-google-maps.d.ts +2 -2
  157. package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -1
  158. package/dist/js/modules/fields/custom-link.d.ts +2 -2
  159. package/dist/js/modules/fields/custom-link.d.ts.map +1 -1
  160. package/dist/js/modules/fields/custom-maps.d.ts +2 -2
  161. package/dist/js/modules/fields/custom-maps.d.ts.map +1 -1
  162. package/dist/js/modules/fields/date-picker.d.ts +2 -2
  163. package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
  164. package/dist/js/modules/fields/file-upload.d.ts +2 -2
  165. package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
  166. package/dist/js/modules/fields/hidden.d.ts +2 -2
  167. package/dist/js/modules/fields/hidden.d.ts.map +1 -1
  168. package/dist/js/modules/fields/index.d.ts +2 -2
  169. package/dist/js/modules/fields/index.d.ts.map +1 -1
  170. package/dist/js/modules/fields/password-validation.d.ts +2 -2
  171. package/dist/js/modules/fields/password-validation.d.ts.map +1 -1
  172. package/dist/js/modules/fields/phone-country.d.ts +2 -2
  173. package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
  174. package/dist/js/modules/fields/repeater.d.ts +2 -2
  175. package/dist/js/modules/fields/repeater.d.ts.map +1 -1
  176. package/dist/js/modules/fields/rich-text.d.ts +2 -2
  177. package/dist/js/modules/fields/rich-text.d.ts.map +1 -1
  178. package/dist/js/modules/fields/shared.d.ts +1 -0
  179. package/dist/js/modules/fields/shared.d.ts.map +1 -1
  180. package/dist/js/modules/fields/signature.d.ts +2 -2
  181. package/dist/js/modules/fields/signature.d.ts.map +1 -1
  182. package/dist/js/modules/fields/summary.d.ts +2 -2
  183. package/dist/js/modules/fields/summary.d.ts.map +1 -1
  184. package/dist/js/modules/fields/survey-likert.d.ts +2 -2
  185. package/dist/js/modules/fields/survey-likert.d.ts.map +1 -1
  186. package/dist/js/modules/fields/survey-rank.d.ts +2 -2
  187. package/dist/js/modules/fields/survey-rank.d.ts.map +1 -1
  188. package/dist/js/modules/fields/survey-rating.d.ts +2 -2
  189. package/dist/js/modules/fields/survey-rating.d.ts.map +1 -1
  190. package/dist/js/modules/fields/table.d.ts +2 -2
  191. package/dist/js/modules/fields/table.d.ts.map +1 -1
  192. package/dist/js/modules/fields/text-limit.d.ts +2 -2
  193. package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
  194. package/dist/js/modules/fields/upload-manager.d.ts +2 -2
  195. package/dist/js/modules/fields/upload-manager.d.ts.map +1 -1
  196. package/dist/js/modules/loader.d.ts +10 -4
  197. package/dist/js/modules/loader.d.ts.map +1 -1
  198. package/dist/js/modules/payments/bpoint.d.ts +1 -1
  199. package/dist/js/modules/payments/bpoint.d.ts.map +1 -1
  200. package/dist/js/modules/payments/eway.d.ts +1 -1
  201. package/dist/js/modules/payments/eway.d.ts.map +1 -1
  202. package/dist/js/modules/payments/factories.d.ts +7 -7
  203. package/dist/js/modules/payments/factories.d.ts.map +1 -1
  204. package/dist/js/modules/payments/go-cardless.d.ts +1 -1
  205. package/dist/js/modules/payments/go-cardless.d.ts.map +1 -1
  206. package/dist/js/modules/payments/index.d.ts +2 -2
  207. package/dist/js/modules/payments/index.d.ts.map +1 -1
  208. package/dist/js/modules/payments/mollie.d.ts +1 -1
  209. package/dist/js/modules/payments/mollie.d.ts.map +1 -1
  210. package/dist/js/modules/payments/moneris.d.ts +1 -1
  211. package/dist/js/modules/payments/moneris.d.ts.map +1 -1
  212. package/dist/js/modules/payments/opayo.d.ts +1 -1
  213. package/dist/js/modules/payments/opayo.d.ts.map +1 -1
  214. package/dist/js/modules/payments/paddle.d.ts +1 -1
  215. package/dist/js/modules/payments/paddle.d.ts.map +1 -1
  216. package/dist/js/modules/payments/paypal.d.ts +1 -1
  217. package/dist/js/modules/payments/paypal.d.ts.map +1 -1
  218. package/dist/js/modules/payments/payway.d.ts +1 -1
  219. package/dist/js/modules/payments/payway.d.ts.map +1 -1
  220. package/dist/js/modules/payments/square.d.ts +1 -1
  221. package/dist/js/modules/payments/square.d.ts.map +1 -1
  222. package/dist/js/modules/payments/stripe.d.ts +1 -1
  223. package/dist/js/modules/payments/stripe.d.ts.map +1 -1
  224. package/dist/js/modules/payments/utils.d.ts +6 -0
  225. package/dist/js/modules/payments/utils.d.ts.map +1 -1
  226. package/dist/js/modules/registry.d.ts +6 -4
  227. package/dist/js/modules/registry.d.ts.map +1 -1
  228. package/dist/js/submit/pipeline.d.ts.map +1 -1
  229. package/dist/js/theme/browser-theme-state.generated.d.ts +24 -0
  230. package/dist/js/theme/browser-theme-state.generated.d.ts.map +1 -0
  231. package/dist/js/theme/theme-classes.d.ts +3 -3
  232. package/dist/js/theme/theme-classes.d.ts.map +1 -1
  233. package/dist/js/transport/forms-api.d.ts +5 -3
  234. package/dist/js/transport/forms-api.d.ts.map +1 -1
  235. package/dist/js/utils/country-from-ip.d.ts +2 -2
  236. package/dist/js/utils/country-from-ip.d.ts.map +1 -1
  237. package/dist/js/utils/csrf.d.ts +17 -0
  238. package/dist/js/utils/csrf.d.ts.map +1 -0
  239. package/dist/js/utils/field-references.parser.d.ts +1 -0
  240. package/dist/js/utils/field-references.parser.d.ts.map +1 -1
  241. package/dist/js/utils/field-references.resolver.d.ts.map +1 -1
  242. package/dist/js/utils/field-references.types.d.ts +1 -0
  243. package/dist/js/utils/field-references.types.d.ts.map +1 -1
  244. package/dist/js/utils/http.d.ts +3 -1
  245. package/dist/js/utils/http.d.ts.map +1 -1
  246. package/dist/js/utils/request-profile.d.ts +4 -0
  247. package/dist/js/utils/request-profile.d.ts.map +1 -0
  248. package/dist/js/utils/unload-warning.d.ts.map +1 -1
  249. package/dist/js/validation/rules/email.d.ts.map +1 -1
  250. package/dist/js/validation/rules/match.d.ts.map +1 -1
  251. package/dist/js/validation/rules/number.d.ts.map +1 -1
  252. package/dist/js/validation/rules/required.d.ts.map +1 -1
  253. package/dist/js/validation/rules/shared.d.ts.map +1 -1
  254. package/dist/js/validation/rules/url.d.ts.map +1 -1
  255. package/dist/js/validation/skip.d.ts +7 -0
  256. package/dist/js/validation/skip.d.ts.map +1 -0
  257. package/dist/js/validation/submit-readiness.d.ts.map +1 -1
  258. package/dist/js/validation/validator.d.ts.map +1 -1
  259. package/package.json +43 -12
  260. package/src/css/formie-base.css +104 -1
  261. package/src/css/theme/_messages.css +2 -0
  262. package/src/css/theme/_tokens.css +31 -25
  263. package/src/css/theme/forms/_form.css +5 -1
  264. package/src/index.ts +13 -8
  265. package/src/js/contracts/client.ts +5 -3
  266. package/src/js/contracts/common.ts +5 -6
  267. package/src/js/contracts/modules.ts +41 -12
  268. package/src/js/contracts/schema.ts +21 -43
  269. package/src/js/contracts/theme.ts +6 -1
  270. package/src/js/core/client-rendered-modules.ts +69 -0
  271. package/src/js/core/create-formie-client.ts +116 -77
  272. package/src/js/core/field-error-aria.ts +93 -0
  273. package/src/js/core/hydrate-modules.ts +54 -29
  274. package/src/js/core/submit-result-state.ts +34 -45
  275. package/src/js/core/submit-result-ui.ts +21 -65
  276. package/src/js/core/validation-focus.ts +20 -20
  277. package/src/js/modules/address/address-finder.ts +1 -1
  278. package/src/js/modules/address/factories.ts +15 -13
  279. package/src/js/modules/address/google-address.ts +10 -6
  280. package/src/js/modules/address/index.ts +2 -2
  281. package/src/js/modules/address/loqate.ts +1 -1
  282. package/src/js/modules/address/place-kit.ts +1 -1
  283. package/src/js/modules/captchas/captcha-eu.ts +2 -2
  284. package/src/js/modules/captchas/factories.ts +66 -42
  285. package/src/js/modules/captchas/friendly-captcha-v1.ts +12 -2
  286. package/src/js/modules/captchas/friendly-captcha-v2.ts +4 -2
  287. package/src/js/modules/captchas/hcaptcha.ts +5 -5
  288. package/src/js/modules/captchas/index.ts +2 -2
  289. package/src/js/modules/captchas/recaptcha-enterprise.ts +2 -2
  290. package/src/js/modules/captchas/recaptcha-v2-checkbox.ts +3 -3
  291. package/src/js/modules/captchas/recaptcha-v2-invisible.ts +2 -2
  292. package/src/js/modules/captchas/recaptcha-v3.ts +2 -2
  293. package/src/js/modules/captchas/snaptcha.ts +1 -1
  294. package/src/js/modules/captchas/turnstile.ts +3 -3
  295. package/src/js/modules/fields/address-country.ts +6 -4
  296. package/src/js/modules/fields/address-state.ts +20 -12
  297. package/src/js/modules/fields/calculations.ts +5 -3
  298. package/src/js/modules/fields/checkbox-radio.ts +24 -42
  299. package/src/js/modules/fields/combobox.ts +25 -9
  300. package/src/js/modules/fields/conditions/config.ts +17 -20
  301. package/src/js/modules/fields/conditions/effects.ts +23 -26
  302. package/src/js/modules/fields/conditions/evaluator.ts +21 -29
  303. package/src/js/modules/fields/conditions/references.ts +16 -5
  304. package/src/js/modules/fields/conditions/submission-context.ts +80 -0
  305. package/src/js/modules/fields/conditions/types.ts +3 -1
  306. package/src/js/modules/fields/conditions/values.ts +31 -6
  307. package/src/js/modules/fields/conditions.ts +59 -18
  308. package/src/js/modules/fields/custom-google-maps.ts +5 -3
  309. package/src/js/modules/fields/custom-link.ts +5 -3
  310. package/src/js/modules/fields/custom-maps.ts +5 -3
  311. package/src/js/modules/fields/date-picker.ts +34 -4
  312. package/src/js/modules/fields/file-upload.ts +19 -10
  313. package/src/js/modules/fields/hidden.ts +5 -3
  314. package/src/js/modules/fields/index.ts +2 -2
  315. package/src/js/modules/fields/password-validation.ts +5 -3
  316. package/src/js/modules/fields/phone-country.ts +33 -6
  317. package/src/js/modules/fields/repeater.ts +5 -3
  318. package/src/js/modules/fields/rich-text.ts +20 -4
  319. package/src/js/modules/fields/shared.ts +13 -0
  320. package/src/js/modules/fields/signature.ts +24 -4
  321. package/src/js/modules/fields/summary.ts +8 -20
  322. package/src/js/modules/fields/survey-likert.ts +5 -3
  323. package/src/js/modules/fields/survey-rank.ts +21 -3
  324. package/src/js/modules/fields/survey-rating.ts +17 -3
  325. package/src/js/modules/fields/table.ts +5 -3
  326. package/src/js/modules/fields/text-limit.ts +5 -3
  327. package/src/js/modules/fields/upload-manager.ts +190 -49
  328. package/src/js/modules/loader.ts +345 -281
  329. package/src/js/modules/payments/bpoint.ts +2 -2
  330. package/src/js/modules/payments/eway.ts +2 -2
  331. package/src/js/modules/payments/factories.ts +53 -52
  332. package/src/js/modules/payments/go-cardless.ts +1 -1
  333. package/src/js/modules/payments/index.ts +2 -2
  334. package/src/js/modules/payments/mollie.ts +1 -1
  335. package/src/js/modules/payments/moneris.ts +2 -2
  336. package/src/js/modules/payments/opayo.ts +21 -8
  337. package/src/js/modules/payments/paddle.ts +10 -6
  338. package/src/js/modules/payments/payment.ts +22 -18
  339. package/src/js/modules/payments/paypal.ts +1 -1
  340. package/src/js/modules/payments/payway.ts +2 -2
  341. package/src/js/modules/payments/square.ts +2 -2
  342. package/src/js/modules/payments/stripe.ts +5 -5
  343. package/src/js/modules/payments/utils.ts +14 -0
  344. package/src/js/modules/registry.ts +19 -11
  345. package/src/js/submit/pipeline.ts +10 -11
  346. package/src/js/theme/browser-theme-state.generated.ts +25 -0
  347. package/src/js/theme/theme-classes.ts +3 -3
  348. package/src/js/transport/forms-api.ts +66 -67
  349. package/src/js/utils/country-from-ip.ts +14 -9
  350. package/src/js/utils/csrf.ts +136 -0
  351. package/src/js/utils/field-references.parser.ts +13 -113
  352. package/src/js/utils/field-references.resolver.ts +27 -64
  353. package/src/js/utils/field-references.row-scope.ts +1 -1
  354. package/src/js/utils/field-references.types.ts +1 -0
  355. package/src/js/utils/http.ts +5 -5
  356. package/src/js/utils/request-profile.ts +10 -0
  357. package/src/js/utils/unload-warning.ts +12 -4
  358. package/src/js/validation/rules/email.ts +3 -5
  359. package/src/js/validation/rules/match.ts +2 -1
  360. package/src/js/validation/rules/minmax.ts +4 -43
  361. package/src/js/validation/rules/number.ts +3 -47
  362. package/src/js/validation/rules/pattern.ts +3 -8
  363. package/src/js/validation/rules/required.ts +2 -1
  364. package/src/js/validation/rules/shared.ts +5 -1
  365. package/src/js/validation/rules/url.ts +3 -10
  366. package/src/js/validation/skip.ts +9 -0
  367. package/src/js/validation/submit-readiness.ts +9 -2
  368. package/src/js/validation/validator.ts +35 -59
  369. package/dist/chunks/calculations-Dv3hYb8_.js +0 -299
  370. package/dist/chunks/conditions-CPH1snCx.js +0 -424
  371. package/dist/chunks/country-from-ip-BwxKaBuJ.js +0 -26
  372. package/dist/chunks/field-references.resolver-CHwn0G0L.js +0 -145
  373. package/dist/chunks/friendly-captcha-v1-C_2reOXr.js +0 -31
  374. package/dist/chunks/google-address-BH3Zhi06.js +0 -161
  375. package/dist/chunks/http-D-JExro7.js +0 -27
  376. package/dist/chunks/opayo-CR0DtsLa.js +0 -179
  377. package/dist/chunks/rich-text-Bg_--4TG.js +0 -349
  378. package/dist/js/modules/fields/conditions/transforms.d.ts +0 -3
  379. package/dist/js/modules/fields/conditions/transforms.d.ts.map +0 -1
  380. package/src/js/modules/fields/conditions/transforms.ts +0 -206
  381. 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,11 +22,11 @@ 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';
@@ -41,7 +43,7 @@ type InternalInstanceState = {
41
43
  bus: EventBus;
42
44
  form: HTMLFormElement | null;
43
45
  validator: FormieValidator | null;
44
- modules: FormieModuleInstance[];
46
+ modules: BrowserModuleRuntime;
45
47
  unbinds: Array<() => void>;
46
48
  instance: FormieFormInstance;
47
49
  };
@@ -73,12 +75,12 @@ function parseBooleanOption(value: string | undefined, defaultValue: boolean): b
73
75
  }
74
76
 
75
77
  function inferStaticCacheOnLoadFromDataset(dataset: DOMStringMap): boolean {
76
- if (dataset.formieRefreshTokens != null && dataset.formieRefreshTokens !== '') {
77
- return parseBooleanOption(dataset.formieRefreshTokens, false);
78
+ if (dataset.formieRefreshTokens != null) {
79
+ return parseBooleanOption(dataset.formieRefreshTokens, true);
78
80
  }
79
81
 
80
- if (dataset.formieStaticCache != null && dataset.formieStaticCache !== '') {
81
- return parseBooleanOption(dataset.formieStaticCache, false);
82
+ if (dataset.formieStaticCache != null) {
83
+ return parseBooleanOption(dataset.formieStaticCache, true);
82
84
  }
83
85
 
84
86
  return false;
@@ -90,6 +92,7 @@ function inferOptionsFromElement(target: Element): FormMountOptions {
90
92
  return {
91
93
  mode: 'server-rendered',
92
94
  transport: (dataset.formieTransport as FormTransport) || 'rest',
95
+ profile: dataset.formieRequestProfile as FormMountOptions['profile'],
93
96
  formHandle: dataset.formieHandle,
94
97
  endpoint: dataset.formieEndpoint,
95
98
  staticCache: inferStaticCacheOnLoadFromDataset(dataset),
@@ -99,7 +102,10 @@ function inferOptionsFromElement(target: Element): FormMountOptions {
99
102
  }
100
103
 
101
104
  function normalizeMode(mode: FormMountOptions['mode'] | undefined): FormMode {
102
- return mode || 'server-rendered';
105
+ if (mode && mode !== 'server-rendered') {
106
+ throw new Error('@verbb/formie-browser enhances server-rendered HTML only. Use @verbb/formie-core for client-rendered forms.');
107
+ }
108
+ return 'server-rendered';
103
109
  }
104
110
 
105
111
  function normalizeTransport(transport: FormMountOptions['transport'] | undefined): FormTransport {
@@ -153,7 +159,7 @@ function resolveHeadlessEndpoint(baseOrEndpoint: string | undefined, actionPath:
153
159
  return actionPath;
154
160
  }
155
161
 
156
- if (candidate.includes('/actions/')) {
162
+ if (candidate.includes(actionPath)) {
157
163
  return candidate;
158
164
  }
159
165
 
@@ -228,11 +234,7 @@ function normalizeHeadlessManagedUrls(target: Element, form: HTMLFormElement, op
228
234
  });
229
235
  }
230
236
 
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
- }
237
+
236
238
 
237
239
  function parseBooleanDatasetValue(value: string | undefined): boolean {
238
240
  if (value == null) {
@@ -339,6 +341,7 @@ function parseJsonAttribute<T>(element: Element | null, attributeName: string):
339
341
  try {
340
342
  return JSON.parse(rawValue) as T;
341
343
  } catch (error) {
344
+ if (attributeName === 'data-formie-modules') throw new Error('Invalid browser-module manifest JSON. Update Formie and its browser packages together.');
342
345
  console.error(`[formie] Failed to parse ${attributeName}.`, error);
343
346
  return null;
344
347
  }
@@ -354,7 +357,8 @@ function getEmbeddedPayload(target: Element, form: HTMLFormElement | null): Form
354
357
  // Server-rendered forms carry the same minimal payload shape that endpoint-
355
358
  // rendered forms receive, which keeps mount behavior identical in both flows.
356
359
  const modules = parseJsonAttribute<FormEndpointPayload['modules']>(payloadRoot, 'data-formie-modules');
357
- const theme = parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme');
360
+ const theme = parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme-classes')
361
+ || parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme');
358
362
 
359
363
  if (!modules && !theme) {
360
364
  return null;
@@ -425,6 +429,8 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
425
429
  csrfInput.name = refreshTokens.csrf.param;
426
430
  csrfInput.value = refreshTokens.csrf.token;
427
431
  csrfInput.setAttribute('autocomplete', 'off');
432
+ // Match server-rendered CSRF inputs so helpers can resolve custom token names.
433
+ csrfInput.setAttribute('data-formie-csrf', '');
428
434
  form.prepend(csrfInput);
429
435
  }
430
436
  }
@@ -443,6 +449,19 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
443
449
  }
444
450
  }
445
451
 
452
+ if (refreshTokens.uploadCreateToken) {
453
+ let uploadCreateInput = form.querySelector('input[name="uploadCreateToken"]') as HTMLInputElement | null;
454
+
455
+ if (!uploadCreateInput) {
456
+ uploadCreateInput = document.createElement('input');
457
+ uploadCreateInput.type = 'hidden';
458
+ uploadCreateInput.name = 'uploadCreateToken';
459
+ form.append(uploadCreateInput);
460
+ }
461
+
462
+ uploadCreateInput.value = refreshTokens.uploadCreateToken;
463
+ }
464
+
446
465
  if (refreshTokens.captchas && typeof refreshTokens.captchas === 'object') {
447
466
  Object.values(refreshTokens.captchas).forEach((captchaEntry) => {
448
467
  if (!captchaEntry || typeof captchaEntry !== 'object') {
@@ -478,7 +497,6 @@ async function ensureHtmlRender(target: Element, options: FormMountOptions): Pro
478
497
  return options.payload;
479
498
  }
480
499
 
481
- ensureSupportedHeadlessTransport(transport, mode);
482
500
 
483
501
  const hasForm = !!getFormFromTarget(target);
484
502
  const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
@@ -501,11 +519,11 @@ async function ensureHtmlRender(target: Element, options: FormMountOptions): Pro
501
519
  ? resolveGraphqlEndpoint(options, target)
502
520
  : resolveHtmlRenderEndpoint(options, target);
503
521
  const payload = transport === 'graphql'
504
- ? await requestGraphqlRender(endpoint, formHandle, renderOptions)
522
+ ? await requestGraphqlRender(endpoint, formHandle, renderOptions, options)
505
523
  : await requestRender(endpoint, formHandle, {
506
524
  ...renderOptions,
507
525
  endpoint,
508
- });
526
+ }, options);
509
527
 
510
528
  if (payload?.html) {
511
529
  (target as HTMLElement).innerHTML = payload.html;
@@ -519,7 +537,6 @@ async function refreshTokensAfterSubmitIfNeeded(target: Element, options: FormMo
519
537
  return;
520
538
  }
521
539
 
522
- ensureSupportedHeadlessTransport(normalizeTransport(options.transport), normalizeMode(options.mode));
523
540
 
524
541
  const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
525
542
  if (!formHandle) {
@@ -531,7 +548,7 @@ async function refreshTokensAfterSubmitIfNeeded(target: Element, options: FormMo
531
548
  const endpoint = resolveRefreshTokensEndpoint(options, target);
532
549
  const renderIdInput = form.querySelector('input[name="renderId"]') as HTMLInputElement | null;
533
550
  const renderId = renderIdInput?.value || undefined;
534
- const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
551
+ const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
535
552
  applyRefreshTokensToForm(form, refreshTokens);
536
553
  dispatchFormieDomEvent(target, 'formie:refresh-tokens:refreshed', refreshTokens);
537
554
  }
@@ -543,7 +560,10 @@ function bindFormEvents(
543
560
  bus: EventBus,
544
561
  validator: FormieValidator | null,
545
562
  unbinds: Array<() => void>,
546
- ): void {
563
+ ): () => void {
564
+ let captureBaseline = () => {};
565
+ form.dataset.formieRequestProfile = options.profile ?? 'same-origin-browser';
566
+ if (options.profile === 'cross-origin-public') form.dataset.formieSubmitMethod = 'ajax';
547
567
  const submitMethod = String(
548
568
  form.dataset.formieSubmitMethod || '',
549
569
  ).trim().toLowerCase();
@@ -567,6 +587,7 @@ function bindFormEvents(
567
587
  return !hasInternalNavigation(form);
568
588
  },
569
589
  });
590
+ captureBaseline = unloadWarning.scheduleBaselineCapture;
570
591
  const handleSubmitResult = (event: Event): void => {
571
592
  if (!(event instanceof CustomEvent)) {
572
593
  return;
@@ -578,7 +599,7 @@ function bindFormEvents(
578
599
  return;
579
600
  }
580
601
 
581
- if (result.action === 'save') {
602
+ if (result.action === 'save' || result.outcome === 'revised') {
582
603
  unloadWarning.scheduleBaselineCapture();
583
604
  }
584
605
  };
@@ -625,8 +646,7 @@ function bindFormEvents(
625
646
  return;
626
647
  }
627
648
 
628
- // Ajax multipage navigation updates UI immediately, then persists page
629
- // continuity in the background so refresh/recovery stays in sync.
649
+ // The server validates forward navigation before selecting the page.
630
650
  event.preventDefault();
631
651
 
632
652
  const currentTarget = event.currentTarget as HTMLAnchorElement | null;
@@ -637,7 +657,6 @@ function bindFormEvents(
637
657
  return;
638
658
  }
639
659
 
640
- applyPageState(form, nextPageId);
641
660
  dispatchFormieDomEvent(target, 'formie:page:navigate', {
642
661
  pageId: nextPageId,
643
662
  href,
@@ -645,6 +664,15 @@ function bindFormEvents(
645
664
 
646
665
  try {
647
666
  const response = await requestSetPage(href, form, nextPageId);
667
+ if (response.pageId) applyPageState(form, String(response.pageId));
668
+ if (!response.success) {
669
+ const { form: formErrors = [], ...fieldErrors } = response.errors ?? {};
670
+ clearSubmitFeedback(form);
671
+ renderFieldErrors(form, fieldErrors);
672
+ renderFormErrors(form, formErrors);
673
+ focusFirstValidationError(form);
674
+ return;
675
+ }
648
676
 
649
677
  dispatchFormieDomEvent(target, 'formie:page:navigate:after', {
650
678
  pageId: nextPageId,
@@ -829,6 +857,8 @@ function bindFormEvents(
829
857
  unbinds.push(() => {
830
858
  form.removeEventListener('submit', submitHandler);
831
859
  });
860
+
861
+ return captureBaseline;
832
862
  }
833
863
 
834
864
  async function refreshTokensIfNeeded(target: Element, options: FormMountOptions, form: HTMLFormElement | null): Promise<void> {
@@ -840,7 +870,6 @@ async function refreshTokensIfNeeded(target: Element, options: FormMountOptions,
840
870
  return;
841
871
  }
842
872
 
843
- ensureSupportedHeadlessTransport(normalizeTransport(options.transport), normalizeMode(options.mode));
844
873
 
845
874
  const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
846
875
  const endpoint = resolveRefreshTokensEndpoint(options, target);
@@ -853,7 +882,7 @@ async function refreshTokensIfNeeded(target: Element, options: FormMountOptions,
853
882
 
854
883
  // Refresh-tokens-before-ready is the cache-safe path: forms can render from SSR/cache
855
884
  // and still receive fresh transport tokens before the user submits anything.
856
- const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
885
+ const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
857
886
 
858
887
  if (!refreshTokens || !form) {
859
888
  return;
@@ -869,14 +898,14 @@ export function createFormieClient(): FormieClient {
869
898
  const moduleRegistry = new ModuleRegistry();
870
899
  const pendingVisibilityMounts = new Map<Element, () => void>();
871
900
  const pendingUnmounts = new Map<Element, Promise<void>>();
872
- const stageNames: Array<'prepare' | 'normalize' | 'validate' | 'screen' | 'authorize' | 'dispatch' | 'finalize'> = [
901
+ const stageNames: Array<'prepare' | 'normalize' | 'validate' | 'challenge' | 'payment' | 'send' | 'result'> = [
873
902
  'prepare',
874
903
  'normalize',
875
904
  'validate',
876
- 'screen',
877
- 'authorize',
878
- 'dispatch',
879
- 'finalize',
905
+ 'challenge',
906
+ 'payment',
907
+ 'send',
908
+ 'result',
880
909
  ];
881
910
 
882
911
  const unmount = async(target: Element): Promise<void> => {
@@ -913,10 +942,7 @@ export function createFormieClient(): FormieClient {
913
942
  state.validator?.destroy();
914
943
  state.validator = null;
915
944
 
916
- for (const moduleInstance of state.modules) {
917
- await moduleInstance.destroy();
918
- }
919
- state.modules = [];
945
+ await state.modules.destroy();
920
946
 
921
947
  state.bus.clear();
922
948
  instances.delete(target);
@@ -967,18 +993,31 @@ export function createFormieClient(): FormieClient {
967
993
  };
968
994
  const compatibilityOptions = resolveLegacyCompatibilityOptions(normalizedOptions.compatibility);
969
995
 
970
- if (normalizedOptions.mode !== 'server-rendered' && !getFormFromTarget(target)) {
971
- throw new Error(`Formie ${normalizedOptions.mode} mode is not implemented yet in the browser client.`);
972
- }
973
996
 
974
997
  const renderPayload = await ensureHtmlRender(target, normalizedOptions);
975
998
  const form = getFormFromTarget(target);
999
+ if (form) setFormBrowserRequestOptions(form, normalizedOptions);
976
1000
  normalizedOptions.staticCache =
977
1001
  options.staticCache ??
978
1002
  (form
979
1003
  ? inferStaticCacheOnLoadFromDataset(form.dataset)
980
1004
  : inferStaticCacheOnLoadFromDataset((target as HTMLElement).dataset));
981
- const embeddedPayload = getEmbeddedPayload(target, form);
1005
+ let embeddedPayload: FormEndpointPayload | null;
1006
+ try {
1007
+ embeddedPayload = getEmbeddedPayload(target, form);
1008
+ if (renderPayload?.modules) assertBrowserModuleManifest(renderPayload.modules);
1009
+ if (embeddedPayload?.modules) assertBrowserModuleManifest(embeddedPayload.modules);
1010
+ } catch (error) {
1011
+ // Unknown semantics must not fall back to an unguarded native send.
1012
+ if (form) {
1013
+ form.addEventListener('submit', (event) => { event.preventDefault(); event.stopImmediatePropagation(); }, true);
1014
+ const alert = document.createElement('div');
1015
+ alert.setAttribute('role', 'alert');
1016
+ alert.textContent = 'This form requires a compatible Formie browser package. Update Formie and its browser packages together.';
1017
+ form.prepend(alert);
1018
+ }
1019
+ throw error;
1020
+ }
982
1021
  const payload = renderPayload || embeddedPayload
983
1022
  ? {
984
1023
  ...(renderPayload || {}),
@@ -987,14 +1026,13 @@ export function createFormieClient(): FormieClient {
987
1026
  : null;
988
1027
  const themeClassMap = payload?.theme as ThemeClassMap | undefined;
989
1028
  const stateStore: Record<string, unknown> = {};
990
- const moduleManifest = ((payload?.modules || []) as FormModuleManifest[]).filter((item) => {
991
- return !!item?.id && !!item?.type;
992
- });
1029
+ const moduleManifest = payload?.modules ?? { contractVersion: 2, surface: 'server-rendered' as const, entries: [] };
1030
+ assertBrowserModuleManifest(moduleManifest);
993
1031
  debug.log('Resolved mount payload.', {
994
1032
  target: getTargetDebugLabel(target),
995
1033
  hasRenderPayload: !!renderPayload,
996
1034
  hasEmbeddedPayload: !!embeddedPayload,
997
- moduleCount: moduleManifest.length,
1035
+ moduleCount: moduleManifest.entries.length,
998
1036
  });
999
1037
  const resolvedThemeClassMap = registerThemeClassMap(target, themeClassMap, form);
1000
1038
 
@@ -1028,9 +1066,6 @@ export function createFormieClient(): FormieClient {
1028
1066
  if (form) {
1029
1067
  ensureFormStartedAt(form);
1030
1068
 
1031
- if (normalizedOptions.themeConfig && typeof normalizedOptions.themeConfig === 'object') {
1032
- form.setAttribute('data-formie-theme-config', JSON.stringify(normalizedOptions.themeConfig));
1033
- }
1034
1069
 
1035
1070
  if (normalizedOptions.theme && normalizedOptions.theme !== 'formie') {
1036
1071
  form.setAttribute('data-formie-frontend-theme', normalizedOptions.theme);
@@ -1061,7 +1096,7 @@ export function createFormieClient(): FormieClient {
1061
1096
  matchContext: {
1062
1097
  root: target,
1063
1098
  form,
1064
- mode: normalizedOptions.mode,
1099
+ surface: moduleManifest.surface,
1065
1100
  },
1066
1101
  setupContext: {
1067
1102
  formId: id,
@@ -1069,6 +1104,7 @@ export function createFormieClient(): FormieClient {
1069
1104
  form,
1070
1105
  target,
1071
1106
  scope: 'form',
1107
+ surface: moduleManifest.surface,
1072
1108
  state: stateStore,
1073
1109
  on: (eventName, callback) => {
1074
1110
  return bus.on(eventName, callback);
@@ -1088,7 +1124,7 @@ export function createFormieClient(): FormieClient {
1088
1124
  });
1089
1125
  debug.log('Module setup complete.', {
1090
1126
  target: getTargetDebugLabel(target),
1091
- moduleInstances: modules.length,
1127
+ moduleInstances: modules.instances.length,
1092
1128
  });
1093
1129
 
1094
1130
  const instance: FormieFormInstance = {
@@ -1184,7 +1220,7 @@ export function createFormieClient(): FormieClient {
1184
1220
  }
1185
1221
 
1186
1222
  if (form) {
1187
- bindFormEvents(target, form, normalizedOptions, bus, validator, unbinds);
1223
+ const captureBaseline = bindFormEvents(target, form, normalizedOptions, bus, validator, unbinds);
1188
1224
 
1189
1225
  if (validator) {
1190
1226
  unbinds.push(bindSubmitReadiness(form, validator, target));
@@ -1195,23 +1231,28 @@ export function createFormieClient(): FormieClient {
1195
1231
 
1196
1232
  // Recapture after captcha/token hydration and field-module init settle so
1197
1233
  // 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);
1234
+ captureBaseline();
1235
+ const baselineTimer = window.setTimeout(captureBaseline, 350);
1236
+ unbinds.push(() => window.clearTimeout(baselineTimer));
1202
1237
  }
1203
1238
 
1204
1239
  stageNames.forEach((stageName) => {
1205
- // Stage fan-out keeps submit pipeline ownership centralized while still
1206
- // letting modules participate before and after each stage.
1207
1240
  const beforeDomUnbind = bus.on(`formie:stage:${stageName}:before`, async(payload) => {
1208
1241
  dispatchFormieDomEvent(target, `formie:stage:${stageName}:before`, payload);
1209
1242
  });
1210
1243
 
1211
1244
  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]);
1245
+ const hookStage = { core: 'prepare', field: 'prepare', address: 'prepare', captcha: 'challenge', payment: 'payment' } as const;
1246
+ const stagePayload = payload as Parameters<NonNullable<BrowserModuleInstance['beforeSubmit']>>[0] & { stage: string };
1247
+
1248
+ if (stageName === 'prepare') {
1249
+ try { modules.assertReady(); }
1250
+ catch { stagePayload.abort('A required form feature could not start. Reload the page or contact the site administrator.'); return; }
1251
+ }
1252
+ for (const moduleInstance of modules.instances) {
1253
+ if (moduleInstance.beforeSubmit && hookStage[moduleInstance.kind ?? 'core'] === stageName) {
1254
+ const { form, action, formData, abort, isAborted, abortReason } = stagePayload;
1255
+ await moduleInstance.beforeSubmit({ form, action, formData, abort, isAborted, abortReason });
1215
1256
  }
1216
1257
  }
1217
1258
  });
@@ -1220,19 +1261,7 @@ export function createFormieClient(): FormieClient {
1220
1261
  dispatchFormieDomEvent(target, `formie:stage:${stageName}:after`, payload);
1221
1262
  });
1222
1263
 
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);
1264
+ unbinds.push(beforeDomUnbind, beforeUnbind, afterDomUnbind);
1236
1265
  });
1237
1266
 
1238
1267
  const submitBeforeUnbind = bus.on('formie:submit:before', async(payload) => {
@@ -1241,6 +1270,16 @@ export function createFormieClient(): FormieClient {
1241
1270
 
1242
1271
  const submitAfterUnbind = bus.on('formie:submit:after', async(payload) => {
1243
1272
  dispatchFormieDomEvent(target, 'formie:submit:after', payload);
1273
+
1274
+ if (!form) return;
1275
+ const result = payload as FormSubmitResult;
1276
+ if (result.code === 'PREFLIGHT_COMPLETE') return;
1277
+ const context = {
1278
+ form,
1279
+ action: result.action ?? 'submit' as const,
1280
+ formData: new FormData(form),
1281
+ };
1282
+ for (const moduleInstance of modules.instances) await moduleInstance.afterSubmit?.(context, result);
1244
1283
  });
1245
1284
 
1246
1285
  const submitFinalBeforeUnbind = bus.on('formie:submit:final:before', async(payload) => {
@@ -1435,7 +1474,7 @@ export function createFormieClient(): FormieClient {
1435
1474
  return;
1436
1475
  }
1437
1476
 
1438
- const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
1477
+ const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
1439
1478
 
1440
1479
  if (!refreshTokens) {
1441
1480
  return;
@@ -1446,7 +1485,7 @@ export function createFormieClient(): FormieClient {
1446
1485
  };
1447
1486
 
1448
1487
  const registerModule = (
1449
- moduleDefinition: FormieModuleDefinition,
1488
+ moduleDefinition: BrowserModuleDefinition,
1450
1489
  options?: Parameters<ModuleRegistry['register']>[1],
1451
1490
  ): boolean => {
1452
1491
  return moduleRegistry.register(moduleDefinition, options);
@@ -1456,7 +1495,7 @@ export function createFormieClient(): FormieClient {
1456
1495
  moduleRegistry.unregister(moduleId);
1457
1496
  };
1458
1497
 
1459
- const getRegisteredModules = (): FormieModuleDefinition[] => {
1498
+ const getRegisteredModules = (): BrowserModuleDefinition[] => {
1460
1499
  return moduleRegistry.getAll();
1461
1500
  };
1462
1501
 
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Field-error ARIA helpers.
3
+ *
4
+ * Pair `aria-errormessage` with `aria-describedby` pointing at the same error
5
+ * message id. `aria-errormessage` alone is inconsistently announced (notably
6
+ * iOS Safari + VoiceOver); Adrian Roselli's cross-AT testing found the pairing
7
+ * more reliable without clobbering instruction ids already on describedby.
8
+ */
9
+
10
+ export function appendDescribedBy(input: HTMLElement, describedById: string): void {
11
+ const current = (input.getAttribute('aria-describedby') || '').trim();
12
+ const items = current ? current.split(/\s+/) : [];
13
+
14
+ if (!items.includes(describedById)) {
15
+ items.push(describedById);
16
+ }
17
+
18
+ input.setAttribute('aria-describedby', items.join(' ').trim());
19
+ }
20
+
21
+ export function removeDescribedBy(input: HTMLElement, describedById: string): void {
22
+ const current = (input.getAttribute('aria-describedby') || '').trim();
23
+
24
+ if (!current) {
25
+ return;
26
+ }
27
+
28
+ const filtered = current.split(/\s+/).filter((item) => {
29
+ return item !== describedById;
30
+ });
31
+
32
+ if (filtered.length) {
33
+ input.setAttribute('aria-describedby', filtered.join(' '));
34
+ return;
35
+ }
36
+
37
+ input.removeAttribute('aria-describedby');
38
+ }
39
+
40
+ /**
41
+ * Drop describedby ids whose targets no longer exist (Formie removes error
42
+ * message nodes from the DOM when clearing). Keeps instruction/other ids.
43
+ */
44
+ export function pruneMissingDescribedBy(input: HTMLElement, doc: Document = document): void {
45
+ const current = (input.getAttribute('aria-describedby') || '').trim();
46
+
47
+ if (!current) {
48
+ return;
49
+ }
50
+
51
+ const remaining = current.split(/\s+/).filter((id) => {
52
+ return !!id && !!doc.getElementById(id);
53
+ });
54
+
55
+ if (remaining.length) {
56
+ input.setAttribute('aria-describedby', remaining.join(' '));
57
+ return;
58
+ }
59
+
60
+ input.removeAttribute('aria-describedby');
61
+ }
62
+
63
+ export function setErrorMessageReference(input: HTMLElement, errorMessageId: string): void {
64
+ input.setAttribute('aria-errormessage', errorMessageId);
65
+ // Same id in describedby so AT that ignore aria-errormessage still hear it.
66
+ appendDescribedBy(input, errorMessageId);
67
+ }
68
+
69
+ export function clearErrorMessageReference(input: HTMLElement, errorMessageId: string): void {
70
+ if (input.getAttribute('aria-errormessage') === errorMessageId) {
71
+ input.removeAttribute('aria-errormessage');
72
+ }
73
+
74
+ removeDescribedBy(input, errorMessageId);
75
+ }
76
+
77
+ /**
78
+ * Clear errormessage refs and prune any describedby ids that no longer resolve
79
+ * (covers message nodes removed from the DOM and legacy container ids).
80
+ */
81
+ export function clearFieldErrorAria(input: HTMLElement, errorMessageIds: string[] = []): void {
82
+ errorMessageIds.forEach((errorMessageId) => {
83
+ if (input.getAttribute('aria-errormessage') === errorMessageId) {
84
+ input.removeAttribute('aria-errormessage');
85
+ }
86
+ });
87
+
88
+ if (!errorMessageIds.length && input.hasAttribute('aria-errormessage')) {
89
+ input.removeAttribute('aria-errormessage');
90
+ }
91
+
92
+ pruneMissingDescribedBy(input);
93
+ }