@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,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
+ }
@@ -1,7 +1,6 @@
1
1
  import type { FormEventUnsubscribe } from '#contracts/client';
2
- import type { FormMode } from '#contracts/common';
3
- import type { FormieModuleDefinition, FormieModuleInstance, ModuleRegistrationOptions } from '#contracts/modules';
4
- import type { FormModuleManifest } from '#contracts/schema';
2
+ import type { BrowserModuleDefinition, BrowserModuleHydrationReport, ModuleRegistrationOptions } from '#contracts/modules';
3
+ import type { BrowserModuleEntry } from '#contracts/schema';
5
4
  import { EventBus } from '#events/event-bus';
6
5
  import { loadModulesFromManifest } from '#modules/loader';
7
6
  import { ModuleRegistry } from '#modules/registry';
@@ -10,34 +9,38 @@ import { createDebug } from '#utils/debug';
10
9
  export type FormieModuleHydratorOptions = {
11
10
  root: Element;
12
11
  form?: HTMLFormElement | null;
13
- modules?: FormModuleManifest[];
14
- mode?: FormMode;
12
+ modules?: import('@verbb/formie-core').BrowserModuleManifest;
13
+ surface?: import('@verbb/formie-core').BrowserSurface;
15
14
  registry?: ModuleRegistry;
16
15
  };
17
16
 
18
- export type FormieModuleHydrator = {
17
+ export type FormieModuleHydrator = BrowserModuleHydrationReport & {
18
+ assertReady: () => void;
19
+ prepare: (action: import('@verbb/formie-core').ClientSubmitAction) => Promise<Record<string, unknown>>;
20
+ result: (result: import('#contracts/schema').FormSubmitResult) => Promise<void>;
21
+ update: (manifest: import('@verbb/formie-core').BrowserModuleManifest) => Promise<void>;
19
22
  destroy: () => Promise<void>;
20
23
  on: (eventName: string, callback: (payload: unknown) => void | Promise<void>) => FormEventUnsubscribe;
21
24
  emit: (eventName: string, payload?: unknown) => Promise<void>;
22
- registerModule: (moduleDefinition: FormieModuleDefinition, options?: ModuleRegistrationOptions) => boolean;
25
+ registerModule: (moduleDefinition: BrowserModuleDefinition, options?: ModuleRegistrationOptions) => boolean;
23
26
  unregisterModule: (moduleId: string) => void;
24
- getRegisteredModules: () => FormieModuleDefinition[];
27
+ getRegisteredModules: () => BrowserModuleDefinition[];
25
28
  };
26
29
 
27
30
  const debug = createDebug('general', 'module-hydrator');
28
31
 
29
32
  export async function hydrateFormieModules(options: FormieModuleHydratorOptions): Promise<FormieModuleHydrator> {
30
33
  const root = options.root;
31
- const form = options.form ?? (root instanceof HTMLFormElement ? root : root.closest('form'));
32
- const modules = options.modules ?? [];
33
- const mode = options.mode ?? 'server-rendered';
34
+ const form = options.form ?? (root instanceof HTMLFormElement ? root : root.closest('form') ?? root.querySelector('form'));
35
+ const surface = options.surface ?? 'cp-edit';
36
+ const modules = options.modules ?? { contractVersion: 2, surface, entries: [] };
34
37
  const registry = options.registry ?? new ModuleRegistry();
35
38
  const bus = new EventBus();
36
39
 
37
40
  // This helper reuses the canonical module manifest loader without mounting the
38
- // full form client, which lets non-frontend hosts like the CP opt into shared
41
+ // full form client, which lets CP edit hosts opt into shared
39
42
  // field modules without inheriting submit or pagination ownership.
40
- const instances = await loadModulesFromManifest(modules, {
43
+ const runtime = await loadModulesFromManifest(modules, {
41
44
  registry,
42
45
  setupContext: {
43
46
  formId: form?.id || (root as HTMLElement).id || 'formie-modules',
@@ -45,31 +48,64 @@ export async function hydrateFormieModules(options: FormieModuleHydratorOptions)
45
48
  form,
46
49
  target: root,
47
50
  scope: 'form',
51
+ surface,
48
52
  state: {},
49
53
  options: {},
50
54
  on: (eventName, callback) => {
51
55
  return bus.on(eventName, callback);
52
56
  },
53
57
  emit: async(eventName, payload) => {
58
+ root.dispatchEvent(new CustomEvent(eventName, { detail: payload, bubbles: true }));
54
59
  await bus.emit(eventName, payload);
55
60
  },
56
61
  },
57
62
  matchContext: {
58
63
  root,
59
64
  form,
60
- mode,
65
+ surface,
61
66
  },
62
67
  });
63
68
 
64
69
  debug.log('Hydrated module manifest.', {
65
- moduleCount: modules.length,
66
- instanceCount: instances.length,
67
- mode,
70
+ moduleCount: modules.entries.length,
71
+ instanceCount: runtime.instances.length,
72
+ surface,
68
73
  });
69
74
 
70
75
  return {
76
+ get instances() { return runtime.instances; },
77
+ get failures() { return runtime.failures; },
78
+ prepare: async(action) => {
79
+ if (!form) throw new Error('Browser modules require a mounted form element.');
80
+ runtime.assertReady();
81
+ runtime.instances.forEach((instance) => instance.assertReady?.());
82
+ let reason: string | undefined;
83
+ const context = {
84
+ form, action: action === 'back' || action === 'save' ? action : 'submit' as const,
85
+ formData: new FormData(form),
86
+ abort: (message?: string) => { reason = message || 'A form feature could not complete.'; },
87
+ isAborted: () => Boolean(reason), abortReason: () => reason,
88
+ };
89
+ const priority = { core: 0, field: 0, address: 0, captcha: 1, payment: 2 };
90
+ for (const instance of [...runtime.instances].sort((a, b) => priority[a.kind ?? 'core'] - priority[b.kind ?? 'core'])) {
91
+ await instance.beforeSubmit?.(context);
92
+ if (reason) throw new Error(reason);
93
+ }
94
+ // Only transient module inputs cross this seam. PHP reads CAPTCHA data
95
+ // and payment fields without allowing arbitrary request/authority overrides.
96
+ return Object.fromEntries(Array.from(form.querySelectorAll<HTMLInputElement>('input[type="hidden"][name]')).map((input) => [input.name, input.value]));
97
+ },
98
+ result: async(result) => {
99
+ if (!form) return;
100
+ const context = { form, action: result.action ?? 'submit' as const, formData: new FormData(form) };
101
+ await bus.emit('formie:submit:result', result);
102
+ for (const instance of runtime.instances) await instance.afterSubmit?.(context, result);
103
+ form.dispatchEvent(new CustomEvent('formie:submit:result', { detail: result, bubbles: true }));
104
+ },
105
+ update: (manifest) => runtime.updateManifest(manifest),
106
+ assertReady: () => { runtime.assertReady(); runtime.instances.forEach((instance) => instance.assertReady?.()); },
71
107
  destroy: async() => {
72
- await destroyModuleInstances(instances);
108
+ await runtime.destroy();
73
109
  bus.clear();
74
110
  },
75
111
  on: (eventName, callback) => {
@@ -89,14 +125,3 @@ export async function hydrateFormieModules(options: FormieModuleHydratorOptions)
89
125
  },
90
126
  };
91
127
  }
92
-
93
- async function destroyModuleInstances(instances: FormieModuleInstance[]): Promise<void> {
94
- for (const instance of instances) {
95
- try {
96
- await instance.destroy();
97
- } catch (error) {
98
- console.error('[formie] Failed to destroy module instance.', error);
99
- debug.warn('Failed destroying module instance.', { error });
100
- }
101
- }
102
- }
@@ -99,22 +99,17 @@ function getProgressState(progress: number): 'start' | 'middle' | 'end' {
99
99
  return 'middle';
100
100
  }
101
101
 
102
- function getConfiguredSubmitAction(form: HTMLFormElement): string {
103
- return (form.dataset.formieSubmitAction || '').trim();
102
+ function getConfiguredCompletionBehavior(form: HTMLFormElement): string {
103
+ return (form.dataset.formieCompletionBehavior || form.dataset.formieSubmitAction || '').trim();
104
104
  }
105
105
 
106
- function getResolvedSubmitAction(form: HTMLFormElement, result: FormSubmitResult): string {
107
- const fromResponse = result.meta?.effectiveSubmitAction;
108
-
109
- if (typeof fromResponse === 'string' && fromResponse.trim() !== '') {
110
- return fromResponse.trim();
111
- }
112
-
113
- return getConfiguredSubmitAction(form);
106
+ function getResolvedCompletionBehavior(form: HTMLFormElement, result: FormSubmitResult): string {
107
+ if (result.completion) return String(result.completion.behavior);
108
+ return getConfiguredCompletionBehavior(form);
114
109
  }
115
110
 
116
111
  function shouldHideFormOnSuccess(form: HTMLFormElement): boolean {
117
- const rawValue = form.dataset.formieSubmitActionFormHide;
112
+ const rawValue = form.dataset.formieHideFormAfterSubmit ?? form.dataset.formieSubmitActionFormHide;
118
113
 
119
114
  if (rawValue === undefined) {
120
115
  return false;
@@ -199,7 +194,7 @@ function applyCompletedProgressState(form: HTMLFormElement): void {
199
194
 
200
195
  function scheduleFinalSubmitReset(form: HTMLFormElement): void {
201
196
  clearPendingFinalSubmitReset(form);
202
- const preserveHiddenState = getConfiguredSubmitAction(form) === 'message' && shouldHideFormOnSuccess(form);
197
+ const preserveHiddenState = getConfiguredCompletionBehavior(form) === 'message' && shouldHideFormOnSuccess(form);
203
198
 
204
199
  if (DEFAULT_FINAL_SUBMIT_RESET_DELAY_MS < 1) {
205
200
  resetSubmissionState(form, { preserveHiddenState });
@@ -300,12 +295,10 @@ export function clearSubmitLoading(form: HTMLFormElement): void {
300
295
  button.removeAttribute('data-formie-loading-text');
301
296
  });
302
297
 
303
- if (form.dataset.formieDisableSubmitUntilValid === 'true') {
304
- const formWithValidation = form as FormWithValidationApi;
298
+ const formWithValidation = form as FormWithValidationApi;
305
299
 
306
- if (formWithValidation.formieValidation) {
307
- syncSubmitReadiness(form, formWithValidation.formieValidation);
308
- }
300
+ if (formWithValidation.formieValidation) {
301
+ syncSubmitReadiness(form, formWithValidation.formieValidation);
309
302
  }
310
303
  }
311
304
 
@@ -370,27 +363,41 @@ export function applyPageState(form: HTMLFormElement, nextPageId: string): void
370
363
  setHiddenInputValue(form, 'pageId', nextPageId);
371
364
  syncPageTabErrors(form);
372
365
 
373
- if (form.dataset.formieDisableSubmitUntilValid === 'true') {
374
- const formWithValidation = form as FormWithValidationApi;
366
+ const formWithValidation = form as FormWithValidationApi;
375
367
 
376
- if (formWithValidation.formieValidation) {
377
- syncSubmitReadiness(form, formWithValidation.formieValidation);
378
- }
368
+ if (formWithValidation.formieValidation) {
369
+ syncSubmitReadiness(form, formWithValidation.formieValidation);
379
370
  }
380
371
  }
381
372
 
382
373
  function syncSubmissionIdentity(form: HTMLFormElement, result: FormSubmitResult): void {
374
+ if (result.meta?.resetSubmissionIdentity === true) {
375
+ // Saved spam is terminal. Preserve the entered values for correction,
376
+ // but do not try to continue the rejected record on the next submit.
377
+ removeHiddenInput(form, 'submissionId');
378
+ removeHiddenInput(form, 'submissionUid');
379
+ removeHiddenInput(form, 'progressId');
380
+ setHiddenInputValue(form, 'expectedVersion', '0');
381
+ clearResumeTokenState(form);
382
+ return;
383
+ }
384
+
385
+ const session = result.meta?.session as { version?: number } | undefined;
386
+ const version = session?.version ?? result.meta?.version;
387
+ if (typeof version === 'number') {
388
+ setHiddenInputValue(form, 'expectedVersion', String(version));
389
+ }
383
390
  const submissionUid = result.meta?.submissionUid;
384
391
  if (typeof submissionUid === 'string' && submissionUid.trim() !== '') {
385
392
  setHiddenInputValue(form, 'submissionUid', submissionUid);
386
393
  }
387
394
 
388
- const continuationToken = (result.meta?.session as { continuation?: { continuationToken?: unknown } } | undefined)
389
- ?.continuation?.continuationToken;
390
- if (typeof continuationToken === 'string' && continuationToken.trim() !== '') {
391
- setHiddenInputValue(form, 'continuationToken', continuationToken);
395
+ const progressId = (result.meta?.session as { continuation?: { progressId?: unknown } } | undefined)
396
+ ?.continuation?.progressId;
397
+ if (typeof progressId === 'string' && progressId.trim() !== '') {
398
+ setHiddenInputValue(form, 'progressId', progressId);
392
399
  } else {
393
- removeHiddenInput(form, 'continuationToken');
400
+ removeHiddenInput(form, 'progressId');
394
401
  }
395
402
  }
396
403
 
@@ -457,8 +464,9 @@ function resetSubmissionState(form: HTMLFormElement, options: ResetSubmissionSta
457
464
  setFormHiddenState(form, false);
458
465
  }
459
466
  removeHiddenInput(form, 'submissionId');
467
+ setHiddenInputValue(form, 'expectedVersion', '0');
460
468
  removeHiddenInput(form, 'submissionUid');
461
- removeHiddenInput(form, 'continuationToken');
469
+ removeHiddenInput(form, 'progressId');
462
470
  removeHiddenInput(form, 'pageId');
463
471
  clearResumeTokenState(form);
464
472
  validator?.resetLiveState();
@@ -508,15 +516,7 @@ function dispatchSubmitDataEvents(form: HTMLFormElement, result: FormSubmitResul
508
516
  }
509
517
  }
510
518
 
511
- // Fallback: some payment action-required responses can carry action payloads
512
- // as `paymentAction`/`paymentDecision` metadata without submitData entries.
513
- const meta = (result.meta || {}) as Record<string, unknown>;
514
- const paymentAction = (
515
- (meta.paymentAction && typeof meta.paymentAction === 'object' ? meta.paymentAction : null)
516
- || (meta.paymentDecision && typeof meta.paymentDecision === 'object'
517
- ? ((meta.paymentDecision as Record<string, unknown>).action as Record<string, unknown> | null)
518
- : null)
519
- ) as Record<string, unknown> | null;
519
+ const paymentAction = result.payment?.action;
520
520
 
521
521
  const actionEvent = paymentAction ? String(paymentAction.event || '') : '';
522
522
  const actionPayload = paymentAction ? paymentAction.payload : undefined;
@@ -553,6 +553,12 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
553
553
  return;
554
554
  }
555
555
 
556
+ // Provider handlers may navigate as soon as their action event is dispatched.
557
+ // Treat checkout navigation like the generic redirect before invoking them.
558
+ if (result.payment?.action?.type === 'redirect' && result.payment.action.url) {
559
+ markInternalNavigation(form, 'payment-redirect');
560
+ }
561
+
556
562
  // Dispatch payment/redirect events from submitData (e.g. formie:payment:mollie:redirect)
557
563
  const eventDispatchResult = dispatchSubmitDataEvents(form, result);
558
564
 
@@ -564,7 +570,7 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
564
570
  });
565
571
  clearPendingFinalSubmitReset(form);
566
572
  if (result.redirect.target === 'new-tab') {
567
- window.open(result.redirect.url, '_blank');
573
+ window.open(result.redirect.url, '_blank', 'noopener,noreferrer');
568
574
  } else {
569
575
  markInternalNavigation(form, 'redirect');
570
576
  window.location.href = result.redirect.url;
@@ -609,18 +615,18 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
609
615
  return;
610
616
  }
611
617
 
612
- if (action === 'submit' && !result.redirect?.url) {
613
- const configuredSubmitAction = getResolvedSubmitAction(form, result);
614
- const preserveHiddenState = configuredSubmitAction === 'message' && shouldHideFormOnSuccess(form);
618
+ if (result.completion && action === 'submit' && !result.redirect?.url) {
619
+ const completionBehavior = getResolvedCompletionBehavior(form, result);
620
+ const preserveHiddenState = completionBehavior === 'message' && shouldHideFormOnSuccess(form);
615
621
 
616
- if (configuredSubmitAction === 'reload') {
622
+ if (completionBehavior === 'reload') {
617
623
  clearPendingFinalSubmitReset(form);
618
624
  markInternalNavigation(form, 'reload');
619
625
  window.location.reload();
620
626
  return;
621
627
  }
622
628
 
623
- if (configuredSubmitAction === 'reset') {
629
+ if (completionBehavior === 'reset') {
624
630
  resetSubmissionState(form);
625
631
  return;
626
632
  }
@@ -633,8 +639,8 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
633
639
  }
634
640
 
635
641
  if (action === 'submit' && result.redirect?.url && result.redirect.target === 'new-tab') {
636
- const configuredSubmitAction = getResolvedSubmitAction(form, result);
637
- const preserveHiddenState = configuredSubmitAction === 'message' && shouldHideFormOnSuccess(form);
642
+ const completionBehavior = getResolvedCompletionBehavior(form, result);
643
+ const preserveHiddenState = completionBehavior === 'message' && shouldHideFormOnSuccess(form);
638
644
  clearPendingFinalSubmitReset(form);
639
645
  resetSubmissionState(form, { preserveHiddenState });
640
646
  return;
@@ -1,5 +1,10 @@
1
1
  import type { FormSubmitResult } from '#contracts/schema';
2
2
  import { applyErrorAriaLive, getErrorAriaLivePreference, resolveSubmitErrorAriaLive } from '#core/error-aria-live';
3
+ import {
4
+ appendDescribedBy,
5
+ clearFieldErrorAria,
6
+ setErrorMessageReference,
7
+ } from '#core/field-error-aria';
3
8
  import { ensureFieldErrorContainer as resolveFieldErrorContainer } from '#core/field-error-container';
4
9
  import { syncPageTabErrors } from '#core/page-tab-errors';
5
10
  import { focusFirstValidationError } from '#core/validation-focus';
@@ -8,8 +13,8 @@ import { addThemeClasses, removeThemeClasses } from '#theme/theme-classes';
8
13
 
9
14
  const successHideTimers = new WeakMap<HTMLFormElement, number>();
10
15
 
11
- function getConfiguredSubmitAction(form: HTMLFormElement): string {
12
- return (form.dataset.formieSubmitAction || '').trim();
16
+ function getConfiguredCompletionBehavior(form: HTMLFormElement): string {
17
+ return (form.dataset.formieCompletionBehavior || form.dataset.formieSubmitAction || '').trim();
13
18
  }
14
19
 
15
20
  function getErrorMessagePosition(form: HTMLFormElement): string {
@@ -17,11 +22,11 @@ function getErrorMessagePosition(form: HTMLFormElement): string {
17
22
  }
18
23
 
19
24
  function getSuccessMessagePosition(form: HTMLFormElement): string {
20
- return (form.dataset.formieSubmitActionMessagePosition || '').trim();
25
+ return (form.dataset.formieSuccessMessagePosition || form.dataset.formieSubmitActionMessagePosition || '').trim();
21
26
  }
22
27
 
23
28
  function getSuccessMessageTimeoutMs(form: HTMLFormElement): number | null {
24
- const rawValue = (form.dataset.formieSubmitActionMessageTimeout || '').trim();
29
+ const rawValue = (form.dataset.formieSuccessMessageTimeout || form.dataset.formieSubmitActionMessageTimeout || '').trim();
25
30
 
26
31
  if (!rawValue) {
27
32
  return null;
@@ -37,7 +42,7 @@ function getSuccessMessageTimeoutMs(form: HTMLFormElement): number | null {
37
42
  }
38
43
 
39
44
  function shouldHideFormOnSuccess(form: HTMLFormElement): boolean {
40
- const rawValue = form.dataset.formieSubmitActionFormHide;
45
+ const rawValue = form.dataset.formieHideFormAfterSubmit ?? form.dataset.formieSubmitActionFormHide;
41
46
 
42
47
  if (rawValue === undefined) {
43
48
  return false;
@@ -160,40 +165,10 @@ function ensureFieldErrorContainer(fieldNode: Element): HTMLElement {
160
165
  });
161
166
  }
162
167
 
163
- function removeDescribedBy(input: HTMLElement, describedById: string): void {
164
- const current = (input.getAttribute('aria-describedby') || '').trim();
165
-
166
- if (!current) {
167
- return;
168
- }
169
-
170
- const nextValue = current.split(/\s+/).filter((item) => {
171
- return item !== describedById;
172
- }).join(' ').trim();
173
-
174
- if (nextValue) {
175
- input.setAttribute('aria-describedby', nextValue);
176
- return;
177
- }
178
-
179
- input.removeAttribute('aria-describedby');
180
- }
181
-
182
- function setErrorMessageReference(input: HTMLElement, errorMessageId: string): void {
183
- input.setAttribute('aria-errormessage', errorMessageId);
184
- }
185
-
186
- function clearErrorMessageReference(input: HTMLElement, errorMessageId: string): void {
187
- if (input.getAttribute('aria-errormessage') === errorMessageId) {
188
- input.removeAttribute('aria-errormessage');
189
- }
190
- }
191
-
192
168
  export function clearFieldErrors(form: HTMLFormElement): void {
193
169
  form.querySelectorAll('[data-formie-field-handle]').forEach((fieldNode) => {
194
170
  const fieldElement = fieldNode as HTMLElement;
195
171
  const container = fieldElement.querySelector('[data-formie-field-errors]') as HTMLElement | null;
196
- const containerId = container?.id || '';
197
172
  const errorMessageIds = Array.from(fieldElement.querySelectorAll('[data-formie-field-error]')).map((node) => {
198
173
  return (node as HTMLElement).id;
199
174
  }).filter(Boolean);
@@ -214,14 +189,7 @@ export function clearFieldErrors(form: HTMLFormElement): void {
214
189
  element.removeAttribute('aria-invalid');
215
190
  removeThemeClasses(element, form, 'fieldControlError');
216
191
  element.removeAttribute('data-formie-input-has-error');
217
-
218
- if (containerId) {
219
- removeDescribedBy(element, containerId);
220
- }
221
-
222
- errorMessageIds.forEach((errorMessageId) => {
223
- clearErrorMessageReference(element, errorMessageId);
224
- });
192
+ clearFieldErrorAria(element, errorMessageIds);
225
193
  });
226
194
  });
227
195
 
@@ -275,7 +243,7 @@ export function clearFormSuccess(form: HTMLFormElement): void {
275
243
  }
276
244
  });
277
245
 
278
- if (!(getConfiguredSubmitAction(form) === 'message' && shouldHideFormOnSuccess(form))) {
246
+ if (!(getConfiguredCompletionBehavior(form) === 'message' && shouldHideFormOnSuccess(form))) {
279
247
  setFormHiddenState(form, false);
280
248
  }
281
249
  }
@@ -286,22 +254,13 @@ export function clearAriaInvalid(form: HTMLFormElement): void {
286
254
  });
287
255
  }
288
256
 
289
- function appendDescribedBy(input: HTMLElement, describedById: string): void {
290
- const current = (input.getAttribute('aria-describedby') || '').trim();
291
- const items = current ? current.split(/\s+/) : [];
292
-
293
- if (!items.includes(describedById)) {
294
- items.push(describedById);
295
- }
296
-
297
- input.setAttribute('aria-describedby', items.join(' ').trim());
298
- }
299
-
300
257
  export function renderFieldErrors(form: HTMLFormElement, fieldErrors: Record<string, string[]>): void {
301
258
  const errorAriaLive = resolveSubmitErrorAriaLive(getErrorAriaLivePreference(form));
302
259
 
303
260
  Object.entries(fieldErrors).forEach(([handle, messages]) => {
304
- const fieldNode = form.querySelector(`[data-formie-field-handle="${handle}"]`);
261
+ const inputName = `fields[${handle.split('.').join('][')}]`;
262
+ const exactInput = form.querySelector(`[name="${CSS.escape(inputName)}"], [name="${CSS.escape(inputName + '[]')}"]`);
263
+ const fieldNode = exactInput?.closest('[data-formie-field-handle]') || form.querySelector(`[data-formie-field-handle="${CSS.escape(handle)}"]`);
305
264
 
306
265
  if (!fieldNode) {
307
266
  return;
@@ -319,7 +278,6 @@ export function renderFieldErrors(form: HTMLFormElement, fieldErrors: Record<str
319
278
  messages.forEach((message, index) => {
320
279
  const errorNode = document.createElement('div');
321
280
  errorNode.setAttribute('data-formie-field-error', 'true');
322
- errorNode.setAttribute('role', 'alert');
323
281
  errorNode.id = `${containerId}-${index + 1}`;
324
282
  addThemeClasses(errorNode, form, 'fieldError');
325
283
  errorNode.textContent = message;
@@ -327,7 +285,8 @@ export function renderFieldErrors(form: HTMLFormElement, fieldErrors: Record<str
327
285
  });
328
286
 
329
287
  // Point every control in the field at the first rendered message while
330
- // still rendering the full list for visual output.
288
+ // still rendering the full list for visual output. Pair aria-errormessage
289
+ // with aria-describedby on that message id (#2946).
331
290
  const primaryErrorId = (container.querySelector('[data-formie-field-error]') as HTMLElement | null)?.id;
332
291
 
333
292
  fieldNode.querySelectorAll('input, select, textarea').forEach((input) => {
@@ -335,7 +294,6 @@ export function renderFieldErrors(form: HTMLFormElement, fieldErrors: Record<str
335
294
  element.setAttribute('aria-invalid', 'true');
336
295
  addThemeClasses(element, form, 'fieldControlError');
337
296
  element.setAttribute('data-formie-input-has-error', 'true');
338
- appendDescribedBy(element, containerId);
339
297
 
340
298
  if (primaryErrorId) {
341
299
  setErrorMessageReference(element, primaryErrorId);
@@ -372,10 +330,9 @@ function isPaymentFollowUpResult(result: FormSubmitResult): boolean {
372
330
  return false;
373
331
  }
374
332
 
375
- const meta = (result.meta || {}) as Record<string, unknown>;
376
- const paymentStatus = String(meta.paymentStatus || '');
333
+ const paymentStatus = result.payment?.status;
377
334
 
378
- return paymentStatus === 'actionRequired' || paymentStatus === 'pending';
335
+ return paymentStatus === 'requiresAction' || paymentStatus === 'pending' || paymentStatus === 'unknown';
379
336
  }
380
337
 
381
338
  export function renderFormNotice(form: HTMLFormElement, message: string): void {
@@ -403,10 +360,10 @@ function shouldRenderSuccessMessage(form: HTMLFormElement, result: FormSubmitRes
403
360
  return true;
404
361
  }
405
362
 
406
- return getConfiguredSubmitAction(form) === 'message' && getSuccessMessagePosition(form) !== '';
363
+ return getConfiguredCompletionBehavior(form) === 'message' && getSuccessMessagePosition(form) !== '';
407
364
  }
408
365
 
409
- export function renderFormSuccess(form: HTMLFormElement, message: string): void {
366
+ export function renderFormSuccess(form: HTMLFormElement, message: string, options: { hideForm?: boolean } = {}): void {
410
367
  const position = getSuccessMessagePosition(form);
411
368
 
412
369
  if (!position) {
@@ -427,7 +384,7 @@ export function renderFormSuccess(form: HTMLFormElement, message: string): void
427
384
  successNode.innerHTML = message;
428
385
  container.appendChild(successNode);
429
386
 
430
- if (shouldHideFormOnSuccess(form)) {
387
+ if (options.hideForm !== false && shouldHideFormOnSuccess(form)) {
431
388
  setFormHiddenState(form, true);
432
389
  }
433
390
 
@@ -451,7 +408,8 @@ export function applySubmitResultUi(form: HTMLFormElement, result: FormSubmitRes
451
408
 
452
409
  if (result.ok) {
453
410
  if (shouldRenderSuccessMessage(form, result)) {
454
- renderFormSuccess(form, result.message || '');
411
+ // Saving progress acknowledges persistence without completing the form.
412
+ renderFormSuccess(form, result.message || '', { hideForm: result.action !== 'save' });
455
413
  }
456
414
 
457
415
  return;
@@ -459,8 +417,7 @@ export function applySubmitResultUi(form: HTMLFormElement, result: FormSubmitRes
459
417
 
460
418
  if (!result.ok) {
461
419
  if (isPaymentFollowUpResult(result)) {
462
- const meta = (result.meta || {}) as Record<string, unknown>;
463
- const message = String(meta.paymentMessage || '').trim();
420
+ const message = String(result.payment?.message || '').trim();
464
421
 
465
422
  if (message) {
466
423
  renderFormNotice(form, message);