@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
@@ -2,8 +2,8 @@ import flatpickr from 'flatpickr';
2
2
  import * as flatpickrLocales from 'flatpickr/dist/l10n/index.js';
3
3
  import flatpickrCss from 'flatpickr/dist/flatpickr.css?inline';
4
4
 
5
- import type { FormieModuleDefinition } from '#contracts/modules';
6
- import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
5
+ import type { BrowserModuleDefinition } from '#contracts/modules';
6
+ import { bindConditionalClear, dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
7
7
  import { ensureModuleStyles } from '#modules/styles';
8
8
  import { createDebug } from '#utils/debug';
9
9
 
@@ -30,6 +30,8 @@ type DatePickerOptions = {
30
30
 
31
31
  type FlatpickrInstanceLike = {
32
32
  destroy: () => void;
33
+ selectedDates: Date[];
34
+ setDate: (dates: Date[], triggerChange?: boolean) => void;
33
35
  };
34
36
 
35
37
  type FlatpickrInput = HTMLInputElement & {
@@ -43,6 +45,7 @@ type FlatpickrChangeInstance = {
43
45
 
44
46
  function attributesPlugin() {
45
47
  return (instance: { input: HTMLInputElement; altInput?: HTMLInputElement | undefined; loadedPlugins: string[] }) => {
48
+ const originalAttributes = new Map<string, string>();
46
49
  return {
47
50
  onReady: () => {
48
51
  if (!instance.altInput) {
@@ -60,6 +63,7 @@ function attributesPlugin() {
60
63
 
61
64
  const value = instance.input.getAttribute(attribute);
62
65
  if (value !== null) {
66
+ originalAttributes.set(attribute, value);
63
67
  instance.altInput?.setAttribute(attribute, value);
64
68
  }
65
69
 
@@ -68,6 +72,15 @@ function attributesPlugin() {
68
72
 
69
73
  instance.loadedPlugins.push('formie-attributes');
70
74
  },
75
+ onDestroy: () => {
76
+ // Conditional fields and page navigation can mount this input again.
77
+ // Restore the markers and labels moved onto the temporary alt input.
78
+ originalAttributes.forEach((value, attribute) => {
79
+ if (!instance.input.hasAttribute(attribute)) {
80
+ instance.input.setAttribute(attribute, value);
81
+ }
82
+ });
83
+ },
71
84
  };
72
85
  };
73
86
  }
@@ -77,8 +90,9 @@ function normalizeOffsetDate(input: string | null | undefined, type: 'min' | 'ma
77
90
  return null;
78
91
  }
79
92
 
80
- if (!Number.isNaN(Date.parse(input))) {
81
- return new Date(input);
93
+ const absoluteDate = parseSubmissionDate(input);
94
+ if (absoluteDate) {
95
+ return absoluteDate;
82
96
  }
83
97
 
84
98
  // Form config may express relative min/max bounds like "+2 weeks", which are
@@ -196,7 +210,9 @@ function formatSubmissionDate(date: Date, options: DatePickerOptions): string {
196
210
  }
197
211
 
198
212
  function parseSubmissionDate(value: string): Date | null {
199
- const parsed = new Date(value);
213
+ // Date-only field values represent a local calendar day. JavaScript otherwise
214
+ // parses YYYY-MM-DD as UTC and shifts the picker to yesterday west of UTC.
215
+ const parsed = new Date(/^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00` : value);
200
216
 
201
217
  if (Number.isNaN(parsed.getTime())) {
202
218
  return null;
@@ -309,6 +325,19 @@ function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () =
309
325
 
310
326
  const instance = flatpickr(input, mergedOptions) as FlatpickrInstanceLike;
311
327
  input._formieFlatpickr = instance;
328
+ const initialDates = instance.selectedDates.map((date) => new Date(date.getTime()));
329
+ const form = input.form;
330
+ const onReset = (event: Event) => {
331
+ // Flatpickr uses hidden transport inputs, whose values survive native reset.
332
+ // Restore the picker and its range inputs together after the native controls.
333
+ queueMicrotask(() => {
334
+ if (!event.defaultPrevented) {
335
+ instance.setDate(initialDates, true);
336
+ }
337
+ });
338
+ };
339
+ form?.addEventListener('reset', onReset);
340
+ const unbindConditionalClear = bindConditionalClear(input, () => instance.setDate([], true));
312
341
  debug.log('Initialized.', {
313
342
  inputName: input.name,
314
343
  isRange,
@@ -320,6 +349,8 @@ function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () =
320
349
  });
321
350
 
322
351
  return () => {
352
+ unbindConditionalClear();
353
+ form?.removeEventListener('reset', onReset);
323
354
  instance.destroy();
324
355
  delete input._formieFlatpickr;
325
356
  debug.log('Destroyed.', {
@@ -328,8 +359,10 @@ function initDatePicker(input: FlatpickrInput, options: DatePickerOptions): () =
328
359
  };
329
360
  }
330
361
 
331
- export const datePickerModule: FormieModuleDefinition = {
332
- id: MODULE_ID,
362
+ export const datePickerModule: BrowserModuleDefinition = {
363
+ moduleId: `formie:${MODULE_ID}`,
364
+ version: 2,
365
+ surfaces: ['server-rendered', 'client-rendered', 'cp-edit'],
333
366
  kind: 'field',
334
367
  match: (ctx) => {
335
368
  return !!ctx.target.querySelector(INPUT_SELECTOR);
@@ -1,6 +1,6 @@
1
1
  import fileUploadCss from '#theme-css/fields/_file.css?inline';
2
2
 
3
- import type { FormieModuleDefinition } from '#contracts/modules';
3
+ import type { BrowserModuleDefinition } from '#contracts/modules';
4
4
  import { dispatchFieldEvent, releaseFormValidators, retainFormValidators } from '#modules/fields/shared';
5
5
  import { ensureModuleStyles } from '#modules/styles';
6
6
  import { getFieldModuleEventName, getFileUploadEventName, getFormStateEventName } from '#utils/event-names';
@@ -13,6 +13,7 @@ const FILE_UPLOAD_KEY_ATTR = 'data-formie-file-upload-key';
13
13
  const HYDRATE_ENDPOINT_ATTR = 'data-formie-file-upload-hydrate-endpoint';
14
14
  const HIDDEN_INPUT_ANCHOR_ATTR = 'data-formie-file-upload-anchor';
15
15
  const HIDDEN_INPUT_VALUE_ATTR = 'data-formie-file-upload-asset-id';
16
+ const HIDDEN_INPUT_ENTRY_ATTR = 'data-formie-file-upload-entry';
16
17
  const FILE_VALIDATORS = [
17
18
  'fileLimit',
18
19
  'fileSizeMinLimit',
@@ -29,6 +30,9 @@ type UploadedAsset = {
29
30
  assetId: number | null;
30
31
  filename: string;
31
32
  url?: string | null;
33
+ uploadUid?: string | null;
34
+ attachToken?: string | null;
35
+ viewToken?: string | null;
32
36
  };
33
37
 
34
38
  type FileUploadHydrateResponse = {
@@ -93,7 +97,8 @@ function getUploadedAssetInputs(field: HTMLElement, input: HTMLInputElement): HT
93
97
  const assetInputName = getUploadedAssetInputName(input);
94
98
 
95
99
  return getHiddenInputs(field).filter((hiddenInput) => {
96
- return hiddenInput.name === assetInputName && hiddenInput.value.trim() !== '';
100
+ return hiddenInput.hasAttribute(HIDDEN_INPUT_VALUE_ATTR)
101
+ || (hiddenInput.name === assetInputName && hiddenInput.value.trim() !== '');
97
102
  });
98
103
  }
99
104
 
@@ -149,6 +154,9 @@ function normalizeUploadedAsset(candidate: unknown): UploadedAsset | null {
149
154
  assetId,
150
155
  filename,
151
156
  url,
157
+ uploadUid: toTrimmedString(candidate.uploadUid) || null,
158
+ attachToken: toTrimmedString(candidate.attachToken) || null,
159
+ viewToken: toTrimmedString(candidate.viewToken ?? candidate.uploadToken) || null,
152
160
  };
153
161
  }
154
162
 
@@ -186,8 +194,11 @@ function readSummaryItems(summaryRoot: HTMLElement | null): UploadedAsset[] {
186
194
  function readUploadedAssetsFromHiddenInputs(field: HTMLElement, input: HTMLInputElement): UploadedAsset[] {
187
195
  return getUploadedAssetInputs(field, input).map((hiddenInput) => {
188
196
  return {
189
- assetId: toPositiveInt(hiddenInput.value),
197
+ assetId: toPositiveInt(hiddenInput.getAttribute(HIDDEN_INPUT_VALUE_ATTR)) ?? toPositiveInt(hiddenInput.value),
190
198
  filename: '',
199
+ viewToken: hiddenInput.getAttribute('data-formie-file-upload-token'),
200
+ uploadUid: hiddenInput.name.endsWith('[uploadUid]') ? hiddenInput.value : null,
201
+ attachToken: getHiddenInputs(field).find((entry) => entry.name === hiddenInput.name.replace(/\[uploadUid\]$/, '[attachToken]'))?.value,
191
202
  };
192
203
  }).filter((asset) => {
193
204
  return asset.assetId !== null;
@@ -201,6 +212,7 @@ function mergeUploadedAssets(assetIds: UploadedAsset[], summaryItems: UploadedAs
201
212
 
202
213
  return assetIds.map((asset, index) => {
203
214
  return {
215
+ ...asset,
204
216
  assetId: asset.assetId,
205
217
  filename: summaryItems[index]?.filename || '',
206
218
  };
@@ -246,8 +258,8 @@ function registerValidators(form: HTMLFormElement | null): void {
246
258
  });
247
259
 
248
260
  validator.addValidator('fileSizeMinLimit', ({ input }) => {
249
- const sizeLimit = parseFloat(input.getAttribute('data-formie-size-min-limit') || '');
250
- const sizeBytes = sizeLimit * 1000 * 1000;
261
+ const maxFileSize = parseFloat(input.getAttribute('data-formie-size-min-limit') || '');
262
+ const sizeBytes = maxFileSize * 1000 * 1000;
251
263
 
252
264
  if (input.type !== 'file' || !sizeBytes || !('files' in input) || !input.files?.length) {
253
265
  return true;
@@ -264,8 +276,8 @@ function registerValidators(form: HTMLFormElement | null): void {
264
276
  });
265
277
 
266
278
  validator.addValidator('fileSizeMaxLimit', ({ input }) => {
267
- const sizeLimit = parseFloat(input.getAttribute('data-formie-size-max-limit') || '');
268
- const sizeBytes = sizeLimit * 1000 * 1000;
279
+ const maxFileSize = parseFloat(input.getAttribute('data-formie-size-max-limit') || '');
280
+ const sizeBytes = maxFileSize * 1000 * 1000;
269
281
 
270
282
  if (input.type !== 'file' || !sizeBytes || !('files' in input) || !input.files?.length) {
271
283
  return true;
@@ -349,23 +361,41 @@ function updateUploadedAssetInputs(field: HTMLElement, input: HTMLInputElement,
349
361
  let insertionPoint: HTMLInputElement = anchor;
350
362
  // The upload event returns persisted asset ids; convert those back into the
351
363
  // hidden-input shape the backend expects on subsequent submit/save actions.
352
- const anchorName = getUploadedAssetInputName(input);
353
- getUploadedAssetInputs(field, input).forEach((hiddenInput) => {
364
+ const baseName = getFileValueName(input);
365
+ getHiddenInputs(field).filter((hiddenInput) => {
366
+ return hiddenInput.hasAttribute(HIDDEN_INPUT_ENTRY_ATTR)
367
+ || (hiddenInput.name === `${baseName}[]` && hiddenInput.value.trim() !== '');
368
+ }).forEach((hiddenInput) => {
354
369
  hiddenInput.remove();
355
370
  });
356
371
 
357
- uploadedAssets.forEach((asset) => {
372
+ uploadedAssets.forEach((asset, index) => {
358
373
  if (!asset.assetId) {
359
374
  return;
360
375
  }
361
376
 
362
377
  const hiddenInput = document.createElement('input');
363
378
  hiddenInput.type = 'hidden';
364
- hiddenInput.name = anchorName;
365
- hiddenInput.value = String(asset.assetId);
366
- hiddenInput.setAttribute(HIDDEN_INPUT_VALUE_ATTR, 'true');
379
+ const hasCapability = asset.uploadUid && asset.attachToken;
380
+ hiddenInput.name = `${baseName}[${index}][${hasCapability ? 'uploadUid' : 'assetId'}]`;
381
+ hiddenInput.value = hasCapability ? asset.uploadUid! : String(asset.assetId);
382
+ hiddenInput.setAttribute(HIDDEN_INPUT_ENTRY_ATTR, 'true');
383
+ hiddenInput.setAttribute(HIDDEN_INPUT_VALUE_ATTR, String(asset.assetId));
384
+ if (asset.viewToken) {
385
+ hiddenInput.setAttribute('data-formie-file-upload-token', asset.viewToken);
386
+ }
367
387
  insertionPoint.insertAdjacentElement('afterend', hiddenInput);
368
388
  insertionPoint = hiddenInput;
389
+
390
+ if (hasCapability) {
391
+ const tokenInput = document.createElement('input');
392
+ tokenInput.type = 'hidden';
393
+ tokenInput.name = `${baseName}[${index}][attachToken]`;
394
+ tokenInput.value = asset.attachToken!;
395
+ tokenInput.setAttribute(HIDDEN_INPUT_ENTRY_ATTR, 'true');
396
+ insertionPoint.insertAdjacentElement('afterend', tokenInput);
397
+ insertionPoint = tokenInput;
398
+ }
369
399
  });
370
400
 
371
401
  input.value = '';
@@ -385,7 +415,7 @@ function getFormHandle(form: HTMLFormElement | null): string {
385
415
  return form.getAttribute('data-formie-handle')?.trim() || '';
386
416
  }
387
417
 
388
- function buildHydrateFormData(field: HTMLElement, input: HTMLInputElement, assetIds: number[]): FormData {
418
+ function buildHydrateFormData(field: HTMLElement, input: HTMLInputElement, assets: UploadedAsset[]): FormData {
389
419
  const form = input.form;
390
420
  const body = new FormData();
391
421
  const handle = getFormHandle(form);
@@ -405,8 +435,13 @@ function buildHydrateFormData(field: HTMLElement, input: HTMLInputElement, asset
405
435
  body.append('submissionUid', submissionUidInput.value.trim());
406
436
  }
407
437
 
408
- assetIds.forEach((assetId) => {
409
- body.append('assetIds[]', String(assetId));
438
+ assets.forEach((asset) => {
439
+ if (asset.assetId) {
440
+ body.append('assetIds[]', String(asset.assetId));
441
+ if (asset.viewToken) {
442
+ body.append(`uploadTokens[${asset.assetId}]`, asset.viewToken);
443
+ }
444
+ }
410
445
  });
411
446
 
412
447
  return body;
@@ -425,8 +460,9 @@ async function hydrateUploadedAssets(field: HTMLElement, input: HTMLInputElement
425
460
 
426
461
  const hydrateEndpoint = input.getAttribute(HYDRATE_ENDPOINT_ATTR)?.trim() || '/actions/formie/file-upload/hydrate';
427
462
  const response = await requestJson<FileUploadHydrateResponse>(hydrateEndpoint, {
463
+ profile: field.closest('form')?.dataset.formieRequestProfile as import('@verbb/formie-core').BrowserRequestOptions['profile'],
428
464
  method: 'POST',
429
- body: buildHydrateFormData(field, input, assetIds),
465
+ body: buildHydrateFormData(field, input, uploadedAssets),
430
466
  });
431
467
  const hydratedAssets = normalizeUploadedAssets(response.assets);
432
468
 
@@ -445,6 +481,7 @@ async function hydrateUploadedAssets(field: HTMLElement, input: HTMLInputElement
445
481
 
446
482
  const hydratedAsset = hydratedMap.get(asset.assetId)!;
447
483
  return {
484
+ ...asset,
448
485
  assetId: asset.assetId,
449
486
  filename: hydratedAsset.filename || asset.filename,
450
487
  url: hydratedAsset.url ?? asset.url,
@@ -473,24 +510,24 @@ function getUploadedAssetsFromEvent(event: Event, matcher: FileUploadEventMatche
473
510
 
474
511
  if (detailInputKey !== '' && detailInputKey === matcher.inputKey) {
475
512
  matchedByInputKey = true;
476
- candidates.push(detail.assets, detail.assetIds, detail.data);
513
+ candidates.push(detail.uploadedAssets, detail.assets, detail.assetIds, detail.data);
477
514
  }
478
515
 
479
516
  if (detailInputName !== '' && detailInputName === matcher.inputName) {
480
517
  matchedByInputName = true;
481
- candidates.push(detail.assets, detail.assetIds, detail.data);
518
+ candidates.push(detail.uploadedAssets, detail.assets, detail.assetIds, detail.data);
482
519
  }
483
520
 
484
521
  if (detailFieldHandle === matcher.fieldHandle) {
485
522
  matchedByFieldHandle = true;
486
- candidates.push(detail.assets, detail.assetIds, detail.data);
523
+ candidates.push(detail.uploadedAssets, detail.assets, detail.assetIds, detail.data);
487
524
  }
488
525
 
489
526
  if (nestedData) {
490
527
  const nestedAssetCandidates = [
528
+ nestedData.uploadedAssets,
491
529
  nestedData.assets,
492
530
  nestedData.assetIds,
493
- nestedData.uploadedAssets,
494
531
  nestedData.data,
495
532
  ];
496
533
 
@@ -657,8 +694,10 @@ function bindFileUploadInput(field: HTMLElement, input: HTMLInputElement, form:
657
694
  };
658
695
  }
659
696
 
660
- export const fileUploadModule: FormieModuleDefinition = {
661
- id: MODULE_ID,
697
+ export const fileUploadModule: BrowserModuleDefinition = {
698
+ moduleId: `formie:${MODULE_ID}`,
699
+ version: 2,
700
+ surfaces: ['server-rendered', 'client-rendered', 'cp-edit'],
662
701
  kind: 'field',
663
702
  match: (ctx) => {
664
703
  return !!ctx.target.querySelector(INPUT_SELECTOR);
@@ -1,4 +1,4 @@
1
- import type { FormieModuleDefinition } from '#contracts/modules';
1
+ import type { BrowserModuleDefinition } from '#contracts/modules';
2
2
  import { getModuleFieldContainers } from '#modules/fields/shared';
3
3
 
4
4
  const INPUT_SELECTOR = 'input[data-formie-hidden-input]';
@@ -22,8 +22,10 @@ function getCookieValue(name: string): string | null {
22
22
  return null;
23
23
  }
24
24
 
25
- export const hiddenModule: FormieModuleDefinition = {
26
- id: 'hidden',
25
+ export const hiddenModule: BrowserModuleDefinition = {
26
+ moduleId: 'formie:hidden',
27
+ version: 2,
28
+ surfaces: ['server-rendered', 'client-rendered', 'cp-edit'],
27
29
  kind: 'field',
28
30
  match: (ctx) => {
29
31
  return !!ctx.target.querySelector(INPUT_SELECTOR);
@@ -1,6 +1,6 @@
1
- import type { FormieModuleDefinition } from '#contracts/modules';
1
+ import type { BrowserModuleDefinition } from '#contracts/modules';
2
2
 
3
- export const builtinFieldModuleLoaders: Record<string, () => Promise<FormieModuleDefinition>> = {
3
+ export const builtinFieldModuleLoaders: Record<string, () => Promise<BrowserModuleDefinition>> = {
4
4
  // Keep the builtin map flat and explicit so manifest ids remain the source of
5
5
  // truth for lazy-loading first-party field enhancements.
6
6
  'calculations': () => import('#modules/fields/calculations').then((module) => module.calculationsModule),
@@ -1,6 +1,6 @@
1
1
  import { getTextLimitMetrics } from '@verbb/formie-core';
2
2
 
3
- import type { FormieModuleDefinition } from '#contracts/modules';
3
+ import type { BrowserModuleDefinition } from '#contracts/modules';
4
4
  import { getModuleFieldTarget, releaseFormValidators, retainFormValidators } from '#modules/fields/shared';
5
5
 
6
6
  const INPUT_SELECTOR = 'input[data-formie-password-input]';
@@ -104,8 +104,10 @@ function unregisterValidators(form: HTMLFormElement | null): void {
104
104
  releaseFormValidators(form, VALIDATOR_SCOPE, PASSWORD_VALIDATORS);
105
105
  }
106
106
 
107
- export const passwordValidationModule: FormieModuleDefinition = {
108
- id: 'password-validation',
107
+ export const passwordValidationModule: BrowserModuleDefinition = {
108
+ moduleId: 'formie:password-validation',
109
+ version: 2,
110
+ surfaces: ['server-rendered', 'client-rendered', 'cp-edit'],
109
111
  kind: 'field',
110
112
  match: (ctx) => {
111
113
  return !!ctx.target.querySelector(`${INPUT_SELECTOR}[data-formie-password-min-length], ${INPUT_SELECTOR}[data-formie-password-require-uppercase], ${INPUT_SELECTOR}[data-formie-password-require-lowercase], ${INPUT_SELECTOR}[data-formie-password-require-special-character]`);
@@ -1,7 +1,7 @@
1
1
  import intlTelInput from 'intl-tel-input';
2
2
  import intlTelInputCss from 'intl-tel-input/styles?inline';
3
3
 
4
- import type { FormieModuleDefinition } from '#contracts/modules';
4
+ import type { BrowserModuleDefinition } from '#contracts/modules';
5
5
  import { dispatchFieldEvent, getModuleFieldContainers, releaseFormValidators, retainFormValidators } from '#modules/fields/shared';
6
6
  import { ensureModuleStyles } from '#modules/styles';
7
7
  import { createGeoIpLookup } from '#utils/country-from-ip';
@@ -18,7 +18,7 @@ ensureModuleStyles(MODULE_ID, [intlTelInputCss]);
18
18
 
19
19
  type PhoneCountryOptions = {
20
20
  countryDefaultValue?: string;
21
- countryAllowed?: string[];
21
+ allowedCountryCodes?: string[];
22
22
  countryPreselectFromIp?: boolean;
23
23
  countryFromIpAction?: string;
24
24
  language?: string;
@@ -81,7 +81,7 @@ function unregisterValidators(form: HTMLFormElement | null): void {
81
81
  releaseFormValidators(form, VALIDATOR_SCOPE, [PHONE_COUNTRY_VALIDATOR]);
82
82
  }
83
83
 
84
- function buildOptions(options: PhoneCountryOptions): Record<string, unknown> {
84
+ function buildOptions(options: PhoneCountryOptions, form: HTMLFormElement | null): Record<string, unknown> {
85
85
  const intlOptions: Record<string, unknown> = {
86
86
  allowDropdown: true,
87
87
  nationalMode: false,
@@ -93,7 +93,7 @@ function buildOptions(options: PhoneCountryOptions): Record<string, unknown> {
93
93
  loadUtils: () => import('intl-tel-input/utils'),
94
94
  };
95
95
 
96
- const allowedCountries = (options.countryAllowed || [])
96
+ const allowedCountries = (options.allowedCountryCodes || [])
97
97
  .map((item) => {
98
98
  return item.toLowerCase();
99
99
  })
@@ -116,7 +116,7 @@ function buildOptions(options: PhoneCountryOptions): Record<string, unknown> {
116
116
  intlOptions.initialCountry = options.countryDefaultValue.toLowerCase();
117
117
  } else if (options.countryPreselectFromIp) {
118
118
  intlOptions.initialCountry = 'auto';
119
- intlOptions.geoIpLookup = createGeoIpLookup(options.countryFromIpAction);
119
+ intlOptions.geoIpLookup = createGeoIpLookup(options.countryFromIpAction, form);
120
120
  }
121
121
 
122
122
  if (Array.isArray(intlOptions.onlyCountries) && typeof intlOptions.initialCountry === 'string') {
@@ -130,7 +130,7 @@ function buildOptions(options: PhoneCountryOptions): Record<string, unknown> {
130
130
  }
131
131
 
132
132
  function initPhoneField(phoneInput: PhoneInput, countryInput: HTMLInputElement, options: PhoneCountryOptions): () => void {
133
- const intlOptions = buildOptions(options);
133
+ const intlOptions = buildOptions(options, phoneInput.closest('form'));
134
134
 
135
135
  dispatchFieldEvent(phoneInput, MODULE_ID, 'before-init', {
136
136
  phoneCountry: phoneInput,
@@ -163,6 +163,30 @@ function initPhoneField(phoneInput: PhoneInput, countryInput: HTMLInputElement,
163
163
  phoneInput.addEventListener('countrychange', syncCountry);
164
164
  phoneInput.addEventListener('blur', syncCountry);
165
165
  syncCountry();
166
+
167
+ // Keep the country dropdown button in sync when the tel input is enabled/disabled after
168
+ // init (multi-page page-hidden, or conditions). intl-tel-input disables the button to match
169
+ // a disabled input at init, but won't re-enable it when `disabled` is later removed.
170
+ const syncCountryButton = () => {
171
+ const button = phoneInput.closest('.iti')?.querySelector('.iti__selected-country');
172
+
173
+ if (!(button instanceof HTMLElement)) {
174
+ return;
175
+ }
176
+
177
+ if (phoneInput.disabled) {
178
+ button.setAttribute('disabled', 'true');
179
+ } else {
180
+ button.removeAttribute('disabled');
181
+ }
182
+ };
183
+
184
+ syncCountryButton();
185
+ const countryButtonObserver = new MutationObserver(() => {
186
+ syncCountryButton();
187
+ });
188
+ countryButtonObserver.observe(phoneInput, { attributes: true, attributeFilter: ['disabled'] });
189
+
166
190
  debug.log('Initialized.', {
167
191
  inputName: phoneInput.name,
168
192
  restrictedCountries: phoneInput.restrictedCountries,
@@ -177,6 +201,7 @@ function initPhoneField(phoneInput: PhoneInput, countryInput: HTMLInputElement,
177
201
  return () => {
178
202
  phoneInput.removeEventListener('countrychange', syncCountry);
179
203
  phoneInput.removeEventListener('blur', syncCountry);
204
+ countryButtonObserver.disconnect();
180
205
  validator.destroy();
181
206
  delete phoneInput.allowedCountries;
182
207
  delete phoneInput.validator;
@@ -188,8 +213,10 @@ function initPhoneField(phoneInput: PhoneInput, countryInput: HTMLInputElement,
188
213
  };
189
214
  }
190
215
 
191
- export const phoneCountryModule: FormieModuleDefinition = {
192
- id: MODULE_ID,
216
+ export const phoneCountryModule: BrowserModuleDefinition = {
217
+ moduleId: `formie:${MODULE_ID}`,
218
+ version: 2,
219
+ surfaces: ['server-rendered', 'client-rendered', 'cp-edit'],
193
220
  kind: 'field',
194
221
  match: (ctx) => {
195
222
  return !!ctx.target.querySelector(PHONE_SELECTOR);
@@ -1,6 +1,6 @@
1
1
  import repeaterCss from '#theme-css/fields/_repeater.css?inline';
2
2
 
3
- import type { FormieModuleDefinition } from '#contracts/modules';
3
+ import type { BrowserModuleDefinition } from '#contracts/modules';
4
4
  import { dispatchFieldEvent, getTemplateSource, getTemplateSourceHtml } from '#modules/fields/shared';
5
5
  import { ensureModuleStyles } from '#modules/styles';
6
6
  import { sleep } from '#utils/async';
@@ -32,6 +32,18 @@ function getRowCount(field: HTMLElement): number {
32
32
  return field.querySelectorAll(ROW_SELECTOR).length;
33
33
  }
34
34
 
35
+ function focusRow(row: Element | null): boolean {
36
+ const control = Array.from(row?.querySelectorAll<HTMLElement>(
37
+ 'input:not([type="hidden"]), select, textarea, button, a[href], [role="button"], [role="combobox"], [role="textbox"], [contenteditable="true"]',
38
+ ) || []).find((element) => {
39
+ return element.tabIndex >= 0 && !element.matches(':disabled')
40
+ && !element.closest('[hidden], [inert]') && element.getClientRects().length > 0;
41
+ });
42
+
43
+ control?.focus();
44
+ return !!control;
45
+ }
46
+
35
47
  function syncAddButton(addButton: HTMLButtonElement | null, rowCount: number): void {
36
48
  if (!addButton) {
37
49
  return;
@@ -46,7 +58,7 @@ function syncAddButton(addButton: HTMLButtonElement | null, rowCount: number): v
46
58
  addButton.disabled = false;
47
59
  }
48
60
 
49
- function bindRepeaterField(field: HTMLElement): () => void {
61
+ function bindRepeaterField(field: HTMLElement, isCpEdit: boolean): () => void {
50
62
  const container = field.matches(CONTAINER_SELECTOR)
51
63
  ? field
52
64
  : field.querySelector(CONTAINER_SELECTOR);
@@ -57,6 +69,9 @@ function bindRepeaterField(field: HTMLElement): () => void {
57
69
  return () => {};
58
70
  }
59
71
 
72
+ const form = field.closest('form');
73
+ let disposed = false;
74
+ let generation = 0;
60
75
  const removeHandlers = new Map<HTMLElement, EventListener>();
61
76
  // Start after the highest existing row id so SSR rows and client-added rows
62
77
  // keep producing unique `__ROW__` replacements across one repeater instance.
@@ -84,8 +99,17 @@ function bindRepeaterField(field: HTMLElement): () => void {
84
99
  return;
85
100
  }
86
101
 
102
+ const restoreFocus = row.contains(document.activeElement)
103
+ && (!isCpEdit || (event instanceof MouseEvent && event.detail === 0));
104
+ const nextRow = row.nextElementSibling;
105
+ const previousRow = row.previousElementSibling;
87
106
  row.remove();
88
107
  syncAddButton(addButton instanceof HTMLButtonElement ? addButton : null, getRowCount(field));
108
+ // Removing the focused control otherwise sends keyboard users back
109
+ // to the document body. Keep them in the nearest surviving row.
110
+ if (restoreFocus && !focusRow(nextRow) && !focusRow(previousRow)) {
111
+ if (addButton instanceof HTMLButtonElement) addButton.focus();
112
+ }
89
113
  debug.log('Row removed.', {
90
114
  rowCount: getRowCount(field),
91
115
  });
@@ -100,7 +124,7 @@ function bindRepeaterField(field: HTMLElement): () => void {
100
124
  });
101
125
  };
102
126
 
103
- const addRow = async() => {
127
+ const addRow = async(focusNewRow = false) => {
104
128
  if (!(addButton instanceof HTMLButtonElement)) {
105
129
  return;
106
130
  }
@@ -130,11 +154,16 @@ function bindRepeaterField(field: HTMLElement): () => void {
130
154
  return;
131
155
  }
132
156
 
157
+ const addedGeneration = generation;
133
158
  container.appendChild(row);
134
159
  // Give downstream field modules a tick to see the new row before we emit
135
160
  // the public append/init-row lifecycle events used for nested enhancements.
136
161
  await sleep(50);
162
+ if (disposed || addedGeneration !== generation || !row.isConnected) return;
137
163
  bindRemoveButtons();
164
+ // Rows are inserted before Add, so leaving focus there skips the new
165
+ // inputs on Tab. Do not steal focus if the user moved on while mounting.
166
+ if (focusNewRow && document.activeElement === addButton) focusRow(row);
138
167
  syncAddButton(addButton, getRowCount(field));
139
168
  debug.log('Row appended.', {
140
169
  rowCount: getRowCount(field),
@@ -151,7 +180,10 @@ function bindRepeaterField(field: HTMLElement): () => void {
151
180
 
152
181
  const addHandler = (event: Event) => {
153
182
  event.preventDefault();
154
- void addRow();
183
+ // Pointer additions in the CP should not focus Craft's field wrappers
184
+ // and show a focus ring around the newly inserted field.
185
+ const focusNewRow = !isCpEdit || (event instanceof MouseEvent && event.detail === 0);
186
+ void addRow(focusNewRow);
155
187
  };
156
188
 
157
189
  if (addButton instanceof HTMLButtonElement) {
@@ -170,6 +202,23 @@ function bindRepeaterField(field: HTMLElement): () => void {
170
202
  }
171
203
  }
172
204
 
205
+ const initialRows = Array.from(container.children).map((row) => row.cloneNode(true));
206
+
207
+ const onReset = (event: Event) => {
208
+ queueMicrotask(() => {
209
+ if (event.defaultPrevented || disposed) return;
210
+ generation++;
211
+ removeHandlers.forEach((handler, button) => button.removeEventListener('click', handler));
212
+ removeHandlers.clear();
213
+ // Replace rows so removed initial rows return and added rows disappear.
214
+ // The module loader disposes old controls and hydrates the new rows.
215
+ container.replaceChildren(...initialRows.map((row) => row.cloneNode(true)));
216
+ bindRemoveButtons();
217
+ syncAddButton(addButton instanceof HTMLButtonElement ? addButton : null, getRowCount(field));
218
+ });
219
+ };
220
+ form?.addEventListener('reset', onReset);
221
+
173
222
  dispatchFieldEvent(field, MODULE_ID, 'init', {
174
223
  repeater: field,
175
224
  });
@@ -178,6 +227,8 @@ function bindRepeaterField(field: HTMLElement): () => void {
178
227
  });
179
228
 
180
229
  return () => {
230
+ disposed = true;
231
+ form?.removeEventListener('reset', onReset);
181
232
  if (addButton instanceof HTMLButtonElement) {
182
233
  addButton.removeEventListener('click', addHandler);
183
234
  }
@@ -188,8 +239,10 @@ function bindRepeaterField(field: HTMLElement): () => void {
188
239
  };
189
240
  }
190
241
 
191
- export const repeaterModule: FormieModuleDefinition = {
192
- id: MODULE_ID,
242
+ export const repeaterModule: BrowserModuleDefinition = {
243
+ moduleId: `formie:${MODULE_ID}`,
244
+ version: 2,
245
+ surfaces: ['server-rendered', 'client-rendered', 'cp-edit'],
193
246
  kind: 'field',
194
247
  match: (ctx) => {
195
248
  return ctx.target instanceof HTMLElement && (
@@ -209,7 +262,7 @@ export const repeaterModule: FormieModuleDefinition = {
209
262
  });
210
263
 
211
264
  const destroyBindings = fields.map((field) => {
212
- return bindRepeaterField(field);
265
+ return bindRepeaterField(field, ctx.surface === 'cp-edit');
213
266
  });
214
267
  debug.log('Module setup.', { fieldCount: fields.length });
215
268