@verbb/formie-browser 1.0.12 → 1.0.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/dist/chunks/{address-country-Rx6GQTND.js → address-country-Dn71jroa.js} +10 -4
- package/dist/chunks/{address-finder-k7_BXaRe.js → address-finder-O49uDRoR.js} +2 -2
- package/dist/chunks/{address-state-BwE01m2_.js → address-state-ByYatUur.js} +154 -143
- package/dist/chunks/{api-DqpfkZWL.js → api-D6kJ7kXZ.js} +183 -179
- package/dist/chunks/{api-BUb6P-xu.js → api-DYYsVIub.js} +77 -47
- package/dist/chunks/{api-sPqGbOww.js → api-ghNr2uxX.js} +15 -9
- package/dist/chunks/{bpoint-Dlq34XH1.js → bpoint-D0RKFM4F.js} +3 -3
- package/dist/chunks/calculations-YEoVCG2q.js +310 -0
- package/dist/chunks/{captcha-eu-CGm8Lbrj.js → captcha-eu-DERJbXnH.js} +5 -4
- package/dist/chunks/{checkbox-radio-BY2aTCOG.js → checkbox-radio-gsm8QPJg.js} +74 -75
- package/dist/chunks/{combobox-C0EuExSI.js → combobox-CWNdx4Fk.js} +290 -279
- package/dist/chunks/conditions-DqNHLTbN.js +464 -0
- package/dist/chunks/country-from-ip-DDj_mFki.js +29 -0
- package/dist/chunks/csrf-DxHg_ZYt.js +61 -0
- package/dist/chunks/{custom-google-maps-BsaSirEZ.js → custom-google-maps-C4l2S3GO.js} +9 -3
- package/dist/chunks/{custom-link-CJ1-FjEM.js → custom-link-CLazgKYH.js} +8 -2
- package/dist/chunks/{custom-maps-DYW_pBid.js → custom-maps-DqcAlD0v.js} +10 -4
- package/dist/chunks/{date-picker-5CA_-zGb.js → date-picker-RUnevF7l.js} +456 -430
- package/dist/chunks/{dist-BdUy8m_p.js → dist-ZLeW0zZ3.js} +1280 -452
- package/dist/chunks/{eway-D9_m8ooj.js → eway-or6dtjoT.js} +3 -3
- package/dist/chunks/field-references.resolver-Ca8MguFv.js +104 -0
- package/dist/chunks/{file-upload-rqfZNqlH.js → file-upload-Bm-TXyNB.js} +104 -96
- package/dist/chunks/friendly-captcha-v1-GFqCt1Mh.js +34 -0
- package/dist/chunks/{friendly-captcha-v2-CLSAbkqx.js → friendly-captcha-v2-WoW246JG.js} +6 -5
- package/dist/chunks/{friendly-challenge-8uPatWqk.js → friendly-challenge-DPG9MJb1.js} +111 -102
- package/dist/chunks/{go-cardless-DAT3ZaQf.js → go-cardless-DzJ-0FKg.js} +2 -2
- package/dist/chunks/google-address-C1P1nTDM.js +163 -0
- package/dist/chunks/{hcaptcha-CGLAmgjO.js → hcaptcha-D0T8aIHz.js} +3 -3
- package/dist/chunks/{hidden-D7_Ch-QN.js → hidden-BZhXnNPe.js} +8 -2
- package/dist/chunks/http-C2QdHuWO.js +27 -0
- package/dist/chunks/{leaflet-src-j-lHNmhR.js → leaflet-src-DE6rv6M-.js} +42 -46
- package/dist/chunks/{loqate-CJPzBmSm.js → loqate-DD1S7PY9.js} +2 -2
- package/dist/chunks/{mollie-Bibhl67p.js → mollie-DG8Ju83x.js} +2 -2
- package/dist/chunks/{moneris-H2wSqk9h.js → moneris-C8m3Fvqu.js} +3 -3
- package/dist/chunks/opayo-B3EvrVxw.js +188 -0
- package/dist/chunks/{paddle-DkvIxQdG.js → paddle-BIYR9Um3.js} +3 -3
- package/dist/chunks/{password-validation-T0vDQPCr.js → password-validation-C8_uO_c_.js} +18 -12
- package/dist/chunks/{paypal-CQpAF7Ua.js → paypal-B_yxIo2d.js} +7 -5
- package/dist/chunks/{payway-CwDkC7YZ.js → payway-CeOzS0sL.js} +3 -3
- package/dist/chunks/{phone-country-D3ayr60L.js → phone-country-bAqje17P.js} +80 -63
- package/dist/chunks/{place-kit-BGHklIZ-.js → place-kit-BE5gi3vo.js} +3 -3
- package/dist/chunks/{placekit-autocomplete.esm-DWVabQlq.js → placekit-autocomplete.esm-CZZERYur.js} +11 -15
- package/dist/chunks/{recaptcha-enterprise-XPId5-bS.js → recaptcha-enterprise-CruXddZq.js} +4 -4
- package/dist/chunks/{recaptcha-shared-CLYJvojk.js → recaptcha-shared-AQRMDMme.js} +1 -1
- package/dist/chunks/{recaptcha-v2-checkbox-Cy-0PEPc.js → recaptcha-v2-checkbox-CCnJYCnG.js} +4 -4
- package/dist/chunks/{recaptcha-v2-invisible-BwRH540O.js → recaptcha-v2-invisible-BuVqRRYn.js} +4 -4
- package/dist/chunks/{recaptcha-v3-No4Ykrhk.js → recaptcha-v3-CloqhO2A.js} +4 -4
- package/dist/chunks/{repeater-BVnPUqTj.js → repeater-DwE27jI1.js} +25 -19
- package/dist/chunks/request-profile-DhwkeCpS.js +10 -0
- package/dist/chunks/rich-text-uV4o_pGk.js +361 -0
- package/dist/chunks/{chunk-3b4jIN3o.js → rolldown-runtime-DtPi1Y-2.js} +2 -2
- package/dist/chunks/{sdk-BhHAYgga.js → sdk-lkwSvlpM.js} +282 -234
- package/dist/chunks/{shared-Bx9s0i0P.js → shared-OKhSay59.js} +27 -21
- package/dist/chunks/{signature-xz4QO7CR.js → signature-7cldFbqj.js} +160 -112
- package/dist/chunks/{snaptcha-DKx98oon.js → snaptcha-C04kNGYQ.js} +2 -2
- package/dist/chunks/{square-DwdcOI1G.js → square-DEzxv9D9.js} +8 -6
- package/dist/chunks/{stripe-DaoG2FT1.js → stripe-CVPQGDz3.js} +10 -11
- package/dist/chunks/{summary-Dks0gXS3.js → summary-BF_8mrXI.js} +47 -45
- package/dist/chunks/{survey-likert-DkE6bR7B.js → survey-likert-fk1hUPff.js} +7 -1
- package/dist/chunks/{survey-rank-DN6x4IZM.js → survey-rank-BsLGl2LX.js} +37 -25
- package/dist/chunks/{survey-rating-BnsNL9U5.js → survey-rating-DVS6e6fM.js} +17 -5
- package/dist/chunks/{table-CAjCJHrL.js → table-DnIBdeKF.js} +21 -15
- package/dist/chunks/{text-limit-Bn18WrPU.js → text-limit-LGMPhFuC.js} +40 -34
- package/dist/chunks/{theme-classes-Tv7q7ToE.js → theme-classes-DAQuEqdP.js} +1 -1
- package/dist/chunks/{turnstile-DJakPzQD.js → turnstile-n9DNyPxi.js} +3 -3
- package/dist/chunks/{upload-manager-W-QDgxKD.js → upload-manager-BbiUosZq.js} +410 -350
- package/dist/chunks/{utils-Vqp8i_RM.js → utils-DpUBPadZ.js} +20 -14
- package/dist/css/formie-base.css +104 -1
- package/dist/css/theme/_messages.css +2 -0
- package/dist/css/theme/_tokens.css +31 -25
- package/dist/css/theme/fields/_signature.css +2 -1
- package/dist/css/theme/forms/_form.css +5 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1778 -1425
- package/dist/js/contracts/client.d.ts +5 -3
- package/dist/js/contracts/client.d.ts.map +1 -1
- package/dist/js/contracts/common.d.ts +2 -2
- package/dist/js/contracts/common.d.ts.map +1 -1
- package/dist/js/contracts/modules.d.ts +37 -12
- package/dist/js/contracts/modules.d.ts.map +1 -1
- package/dist/js/contracts/schema.d.ts +20 -38
- package/dist/js/contracts/schema.d.ts.map +1 -1
- package/dist/js/contracts/theme.d.ts +4 -1
- package/dist/js/contracts/theme.d.ts.map +1 -1
- package/dist/js/core/client-rendered-modules.d.ts +17 -0
- package/dist/js/core/client-rendered-modules.d.ts.map +1 -0
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/field-error-aria.d.ts +23 -0
- package/dist/js/core/field-error-aria.d.ts.map +1 -0
- package/dist/js/core/hydrate-modules.d.ts +10 -8
- package/dist/js/core/hydrate-modules.d.ts.map +1 -1
- package/dist/js/core/submit-result-state.d.ts.map +1 -1
- package/dist/js/core/submit-result-ui.d.ts.map +1 -1
- package/dist/js/core/validation-focus.d.ts.map +1 -1
- package/dist/js/modules/address/address-finder.d.ts +1 -1
- package/dist/js/modules/address/address-finder.d.ts.map +1 -1
- package/dist/js/modules/address/factories.d.ts +3 -3
- package/dist/js/modules/address/factories.d.ts.map +1 -1
- package/dist/js/modules/address/google-address.d.ts +2 -1
- package/dist/js/modules/address/google-address.d.ts.map +1 -1
- package/dist/js/modules/address/index.d.ts +2 -2
- package/dist/js/modules/address/index.d.ts.map +1 -1
- package/dist/js/modules/address/loqate.d.ts +1 -1
- package/dist/js/modules/address/loqate.d.ts.map +1 -1
- package/dist/js/modules/address/place-kit.d.ts +1 -1
- package/dist/js/modules/address/place-kit.d.ts.map +1 -1
- package/dist/js/modules/captchas/captcha-eu.d.ts +1 -1
- package/dist/js/modules/captchas/captcha-eu.d.ts.map +1 -1
- package/dist/js/modules/captchas/factories.d.ts +11 -11
- package/dist/js/modules/captchas/factories.d.ts.map +1 -1
- package/dist/js/modules/captchas/friendly-captcha-v1.d.ts +1 -1
- package/dist/js/modules/captchas/friendly-captcha-v1.d.ts.map +1 -1
- package/dist/js/modules/captchas/friendly-captcha-v2.d.ts +1 -1
- package/dist/js/modules/captchas/friendly-captcha-v2.d.ts.map +1 -1
- package/dist/js/modules/captchas/hcaptcha.d.ts +1 -1
- package/dist/js/modules/captchas/hcaptcha.d.ts.map +1 -1
- package/dist/js/modules/captchas/index.d.ts +2 -2
- package/dist/js/modules/captchas/index.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-enterprise.d.ts +1 -1
- package/dist/js/modules/captchas/recaptcha-enterprise.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-v2-checkbox.d.ts +1 -1
- package/dist/js/modules/captchas/recaptcha-v2-checkbox.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-v2-invisible.d.ts +1 -1
- package/dist/js/modules/captchas/recaptcha-v2-invisible.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-v3.d.ts +1 -1
- package/dist/js/modules/captchas/recaptcha-v3.d.ts.map +1 -1
- package/dist/js/modules/captchas/snaptcha.d.ts +1 -1
- package/dist/js/modules/captchas/snaptcha.d.ts.map +1 -1
- package/dist/js/modules/captchas/turnstile.d.ts +1 -1
- package/dist/js/modules/captchas/turnstile.d.ts.map +1 -1
- package/dist/js/modules/fields/address-country.d.ts +2 -2
- package/dist/js/modules/fields/address-country.d.ts.map +1 -1
- package/dist/js/modules/fields/address-state.d.ts +2 -2
- package/dist/js/modules/fields/address-state.d.ts.map +1 -1
- package/dist/js/modules/fields/calculations.d.ts +2 -2
- package/dist/js/modules/fields/calculations.d.ts.map +1 -1
- package/dist/js/modules/fields/checkbox-radio.d.ts +2 -2
- package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
- package/dist/js/modules/fields/combobox.d.ts +2 -2
- package/dist/js/modules/fields/combobox.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/config.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/effects.d.ts +1 -0
- package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/evaluator.d.ts +4 -1
- package/dist/js/modules/fields/conditions/evaluator.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/references.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/submission-context.d.ts +15 -0
- package/dist/js/modules/fields/conditions/submission-context.d.ts.map +1 -0
- package/dist/js/modules/fields/conditions/types.d.ts +3 -1
- package/dist/js/modules/fields/conditions/types.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/values.d.ts +1 -1
- package/dist/js/modules/fields/conditions/values.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions.d.ts +2 -2
- package/dist/js/modules/fields/conditions.d.ts.map +1 -1
- package/dist/js/modules/fields/custom-google-maps.d.ts +2 -2
- package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -1
- package/dist/js/modules/fields/custom-link.d.ts +2 -2
- package/dist/js/modules/fields/custom-link.d.ts.map +1 -1
- package/dist/js/modules/fields/custom-maps.d.ts +2 -2
- package/dist/js/modules/fields/custom-maps.d.ts.map +1 -1
- package/dist/js/modules/fields/date-picker.d.ts +2 -2
- package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
- package/dist/js/modules/fields/file-upload.d.ts +2 -2
- package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
- package/dist/js/modules/fields/hidden.d.ts +2 -2
- package/dist/js/modules/fields/hidden.d.ts.map +1 -1
- package/dist/js/modules/fields/index.d.ts +2 -2
- package/dist/js/modules/fields/index.d.ts.map +1 -1
- package/dist/js/modules/fields/password-validation.d.ts +2 -2
- package/dist/js/modules/fields/password-validation.d.ts.map +1 -1
- package/dist/js/modules/fields/phone-country.d.ts +2 -2
- package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
- package/dist/js/modules/fields/repeater.d.ts +2 -2
- package/dist/js/modules/fields/repeater.d.ts.map +1 -1
- package/dist/js/modules/fields/rich-text.d.ts +2 -2
- package/dist/js/modules/fields/rich-text.d.ts.map +1 -1
- package/dist/js/modules/fields/shared.d.ts +1 -0
- package/dist/js/modules/fields/shared.d.ts.map +1 -1
- package/dist/js/modules/fields/signature.d.ts +2 -2
- package/dist/js/modules/fields/signature.d.ts.map +1 -1
- package/dist/js/modules/fields/summary.d.ts +2 -2
- package/dist/js/modules/fields/summary.d.ts.map +1 -1
- package/dist/js/modules/fields/survey-likert.d.ts +2 -2
- package/dist/js/modules/fields/survey-likert.d.ts.map +1 -1
- package/dist/js/modules/fields/survey-rank.d.ts +2 -2
- package/dist/js/modules/fields/survey-rank.d.ts.map +1 -1
- package/dist/js/modules/fields/survey-rating.d.ts +2 -2
- package/dist/js/modules/fields/survey-rating.d.ts.map +1 -1
- package/dist/js/modules/fields/table.d.ts +2 -2
- package/dist/js/modules/fields/table.d.ts.map +1 -1
- package/dist/js/modules/fields/text-limit.d.ts +2 -2
- package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
- package/dist/js/modules/fields/upload-manager.d.ts +2 -2
- package/dist/js/modules/fields/upload-manager.d.ts.map +1 -1
- package/dist/js/modules/loader.d.ts +10 -4
- package/dist/js/modules/loader.d.ts.map +1 -1
- package/dist/js/modules/payments/bpoint.d.ts +1 -1
- package/dist/js/modules/payments/bpoint.d.ts.map +1 -1
- package/dist/js/modules/payments/eway.d.ts +1 -1
- package/dist/js/modules/payments/eway.d.ts.map +1 -1
- package/dist/js/modules/payments/factories.d.ts +7 -7
- package/dist/js/modules/payments/factories.d.ts.map +1 -1
- package/dist/js/modules/payments/go-cardless.d.ts +1 -1
- package/dist/js/modules/payments/go-cardless.d.ts.map +1 -1
- package/dist/js/modules/payments/index.d.ts +2 -2
- package/dist/js/modules/payments/index.d.ts.map +1 -1
- package/dist/js/modules/payments/mollie.d.ts +1 -1
- package/dist/js/modules/payments/mollie.d.ts.map +1 -1
- package/dist/js/modules/payments/moneris.d.ts +1 -1
- package/dist/js/modules/payments/moneris.d.ts.map +1 -1
- package/dist/js/modules/payments/opayo.d.ts +1 -1
- package/dist/js/modules/payments/opayo.d.ts.map +1 -1
- package/dist/js/modules/payments/paddle.d.ts +1 -1
- package/dist/js/modules/payments/paddle.d.ts.map +1 -1
- package/dist/js/modules/payments/paypal.d.ts +1 -1
- package/dist/js/modules/payments/paypal.d.ts.map +1 -1
- package/dist/js/modules/payments/payway.d.ts +1 -1
- package/dist/js/modules/payments/payway.d.ts.map +1 -1
- package/dist/js/modules/payments/square.d.ts +1 -1
- package/dist/js/modules/payments/square.d.ts.map +1 -1
- package/dist/js/modules/payments/stripe.d.ts +1 -1
- package/dist/js/modules/payments/stripe.d.ts.map +1 -1
- package/dist/js/modules/payments/utils.d.ts +6 -0
- package/dist/js/modules/payments/utils.d.ts.map +1 -1
- package/dist/js/modules/registry.d.ts +6 -4
- package/dist/js/modules/registry.d.ts.map +1 -1
- package/dist/js/submit/pipeline.d.ts.map +1 -1
- package/dist/js/theme/browser-theme-state.generated.d.ts +24 -0
- package/dist/js/theme/browser-theme-state.generated.d.ts.map +1 -0
- package/dist/js/theme/theme-classes.d.ts +3 -3
- package/dist/js/theme/theme-classes.d.ts.map +1 -1
- package/dist/js/transport/forms-api.d.ts +5 -3
- package/dist/js/transport/forms-api.d.ts.map +1 -1
- package/dist/js/utils/country-from-ip.d.ts +2 -2
- package/dist/js/utils/country-from-ip.d.ts.map +1 -1
- package/dist/js/utils/csrf.d.ts +17 -0
- package/dist/js/utils/csrf.d.ts.map +1 -0
- package/dist/js/utils/field-references.parser.d.ts +1 -0
- package/dist/js/utils/field-references.parser.d.ts.map +1 -1
- package/dist/js/utils/field-references.resolver.d.ts.map +1 -1
- package/dist/js/utils/field-references.types.d.ts +1 -0
- package/dist/js/utils/field-references.types.d.ts.map +1 -1
- package/dist/js/utils/http.d.ts +3 -1
- package/dist/js/utils/http.d.ts.map +1 -1
- package/dist/js/utils/request-profile.d.ts +4 -0
- package/dist/js/utils/request-profile.d.ts.map +1 -0
- package/dist/js/utils/unload-warning.d.ts.map +1 -1
- package/dist/js/validation/rules/email.d.ts.map +1 -1
- package/dist/js/validation/rules/match.d.ts.map +1 -1
- package/dist/js/validation/rules/number.d.ts.map +1 -1
- package/dist/js/validation/rules/required.d.ts.map +1 -1
- package/dist/js/validation/rules/shared.d.ts.map +1 -1
- package/dist/js/validation/rules/url.d.ts.map +1 -1
- package/dist/js/validation/skip.d.ts +7 -0
- package/dist/js/validation/skip.d.ts.map +1 -0
- package/dist/js/validation/submit-readiness.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +43 -12
- package/src/css/formie-base.css +104 -1
- package/src/css/theme/_messages.css +2 -0
- package/src/css/theme/_tokens.css +31 -25
- package/src/css/theme/fields/_signature.css +2 -1
- package/src/css/theme/forms/_form.css +5 -1
- package/src/index.ts +13 -8
- package/src/js/contracts/client.ts +5 -3
- package/src/js/contracts/common.ts +5 -6
- package/src/js/contracts/modules.ts +41 -12
- package/src/js/contracts/schema.ts +21 -43
- package/src/js/contracts/theme.ts +6 -1
- package/src/js/core/client-rendered-modules.ts +69 -0
- package/src/js/core/create-formie-client.ts +119 -73
- package/src/js/core/field-error-aria.ts +93 -0
- package/src/js/core/hydrate-modules.ts +54 -29
- package/src/js/core/submit-result-state.ts +34 -45
- package/src/js/core/submit-result-ui.ts +21 -65
- package/src/js/core/validation-focus.ts +20 -20
- package/src/js/modules/address/address-finder.ts +1 -1
- package/src/js/modules/address/factories.ts +15 -13
- package/src/js/modules/address/google-address.ts +10 -6
- package/src/js/modules/address/index.ts +2 -2
- package/src/js/modules/address/loqate.ts +1 -1
- package/src/js/modules/address/place-kit.ts +1 -1
- package/src/js/modules/captchas/captcha-eu.ts +2 -2
- package/src/js/modules/captchas/factories.ts +66 -42
- package/src/js/modules/captchas/friendly-captcha-v1.ts +12 -2
- package/src/js/modules/captchas/friendly-captcha-v2.ts +4 -2
- package/src/js/modules/captchas/hcaptcha.ts +5 -5
- package/src/js/modules/captchas/index.ts +2 -2
- package/src/js/modules/captchas/recaptcha-enterprise.ts +2 -2
- package/src/js/modules/captchas/recaptcha-v2-checkbox.ts +3 -3
- package/src/js/modules/captchas/recaptcha-v2-invisible.ts +2 -2
- package/src/js/modules/captchas/recaptcha-v3.ts +2 -2
- package/src/js/modules/captchas/snaptcha.ts +1 -1
- package/src/js/modules/captchas/turnstile.ts +3 -3
- package/src/js/modules/fields/address-country.ts +6 -4
- package/src/js/modules/fields/address-state.ts +20 -12
- package/src/js/modules/fields/calculations.ts +5 -3
- package/src/js/modules/fields/checkbox-radio.ts +24 -42
- package/src/js/modules/fields/combobox.ts +25 -9
- package/src/js/modules/fields/conditions/config.ts +17 -20
- package/src/js/modules/fields/conditions/effects.ts +23 -26
- package/src/js/modules/fields/conditions/evaluator.ts +21 -29
- package/src/js/modules/fields/conditions/references.ts +16 -5
- package/src/js/modules/fields/conditions/submission-context.ts +80 -0
- package/src/js/modules/fields/conditions/types.ts +3 -1
- package/src/js/modules/fields/conditions/values.ts +31 -6
- package/src/js/modules/fields/conditions.ts +59 -18
- package/src/js/modules/fields/custom-google-maps.ts +5 -3
- package/src/js/modules/fields/custom-link.ts +5 -3
- package/src/js/modules/fields/custom-maps.ts +5 -3
- package/src/js/modules/fields/date-picker.ts +40 -4
- package/src/js/modules/fields/file-upload.ts +19 -10
- package/src/js/modules/fields/hidden.ts +5 -3
- package/src/js/modules/fields/index.ts +2 -2
- package/src/js/modules/fields/password-validation.ts +5 -3
- package/src/js/modules/fields/phone-country.ts +33 -6
- package/src/js/modules/fields/repeater.ts +5 -3
- package/src/js/modules/fields/rich-text.ts +20 -4
- package/src/js/modules/fields/shared.ts +13 -0
- package/src/js/modules/fields/signature.ts +64 -6
- package/src/js/modules/fields/summary.ts +8 -20
- package/src/js/modules/fields/survey-likert.ts +5 -3
- package/src/js/modules/fields/survey-rank.ts +21 -3
- package/src/js/modules/fields/survey-rating.ts +17 -3
- package/src/js/modules/fields/table.ts +5 -3
- package/src/js/modules/fields/text-limit.ts +5 -3
- package/src/js/modules/fields/upload-manager.ts +190 -49
- package/src/js/modules/loader.ts +345 -281
- package/src/js/modules/payments/bpoint.ts +2 -2
- package/src/js/modules/payments/eway.ts +2 -2
- package/src/js/modules/payments/factories.ts +53 -52
- package/src/js/modules/payments/go-cardless.ts +1 -1
- package/src/js/modules/payments/index.ts +2 -2
- package/src/js/modules/payments/mollie.ts +1 -1
- package/src/js/modules/payments/moneris.ts +2 -2
- package/src/js/modules/payments/opayo.ts +21 -8
- package/src/js/modules/payments/paddle.ts +10 -6
- package/src/js/modules/payments/payment.ts +22 -18
- package/src/js/modules/payments/paypal.ts +1 -1
- package/src/js/modules/payments/payway.ts +2 -2
- package/src/js/modules/payments/square.ts +2 -2
- package/src/js/modules/payments/stripe.ts +5 -5
- package/src/js/modules/payments/utils.ts +14 -0
- package/src/js/modules/registry.ts +19 -11
- package/src/js/submit/pipeline.ts +10 -11
- package/src/js/theme/browser-theme-state.generated.ts +25 -0
- package/src/js/theme/theme-classes.ts +3 -3
- package/src/js/transport/forms-api.ts +66 -67
- package/src/js/utils/country-from-ip.ts +14 -9
- package/src/js/utils/csrf.ts +136 -0
- package/src/js/utils/field-references.parser.ts +13 -113
- package/src/js/utils/field-references.resolver.ts +27 -64
- package/src/js/utils/field-references.row-scope.ts +1 -1
- package/src/js/utils/field-references.types.ts +1 -0
- package/src/js/utils/http.ts +5 -5
- package/src/js/utils/request-profile.ts +10 -0
- package/src/js/utils/unload-warning.ts +12 -4
- package/src/js/validation/rules/email.ts +3 -5
- package/src/js/validation/rules/match.ts +2 -1
- package/src/js/validation/rules/minmax.ts +4 -43
- package/src/js/validation/rules/number.ts +3 -47
- package/src/js/validation/rules/pattern.ts +3 -8
- package/src/js/validation/rules/required.ts +2 -1
- package/src/js/validation/rules/shared.ts +5 -1
- package/src/js/validation/rules/url.ts +3 -10
- package/src/js/validation/skip.ts +9 -0
- package/src/js/validation/submit-readiness.ts +9 -2
- package/src/js/validation/validator.ts +35 -59
- package/dist/chunks/calculations-DERi2xaC.js +0 -299
- package/dist/chunks/conditions-65kBcFWW.js +0 -424
- package/dist/chunks/country-from-ip-BwxKaBuJ.js +0 -26
- package/dist/chunks/field-references.resolver-CHwn0G0L.js +0 -145
- package/dist/chunks/friendly-captcha-v1-C_2reOXr.js +0 -31
- package/dist/chunks/google-address-BH3Zhi06.js +0 -161
- package/dist/chunks/http-D-JExro7.js +0 -27
- package/dist/chunks/opayo-CR0DtsLa.js +0 -179
- package/dist/chunks/rich-text-Bg_--4TG.js +0 -349
- package/dist/js/modules/fields/conditions/transforms.d.ts +0 -3
- package/dist/js/modules/fields/conditions/transforms.d.ts.map +0 -1
- package/src/js/modules/fields/conditions/transforms.ts +0 -206
- package/src/js/modules/payments/stub-payment-module.ts +0 -87
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { setFormBrowserRequestOptions } from '#utils/request-profile';
|
|
2
|
+
import type { BrowserModuleKind, ClientFormInstance } from '@verbb/formie-core';
|
|
3
|
+
import { hydrateFormieModules } from './hydrate-modules';
|
|
4
|
+
import { ModuleRegistry, clientRenderedModuleRegistry } from '#modules/registry';
|
|
5
|
+
|
|
6
|
+
// These modules are implemented by the shared core state engine or native
|
|
7
|
+
// framework controls. Explicit delegation preserves every declared occurrence.
|
|
8
|
+
const nativeModuleDelegations: Record<string, { kind: BrowserModuleKind; reason: string }> = {
|
|
9
|
+
conditions: { kind: 'core', reason: 'Core evaluates structured conditions and the adapter renders visibility.' },
|
|
10
|
+
repeater: { kind: 'field', reason: 'The adapter owns row markup and core owns row values.' },
|
|
11
|
+
signature: { kind: 'field', reason: 'The adapter owns its signature control and cleanup.' },
|
|
12
|
+
'file-upload': { kind: 'field', reason: 'The shared transport stages selected files and submits attachment capabilities.' },
|
|
13
|
+
'upload-manager': { kind: 'field', reason: 'Native file selection uses the shared staged upload transport.' },
|
|
14
|
+
'checkbox-radio': { kind: 'field', reason: 'Framework controls own checked state.' },
|
|
15
|
+
'text-limit': { kind: 'field', reason: 'Core validation enforces the structured minimum and maximum rules.' },
|
|
16
|
+
'date-picker': { kind: 'field', reason: 'The adapter renders the structured date input contract.' },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export async function mountClientRenderedModules(root: Element, instance: ClientFormInstance, registry = clientRenderedModuleRegistry) {
|
|
20
|
+
for (const [moduleName, delegation] of Object.entries(nativeModuleDelegations)) {
|
|
21
|
+
const moduleId = `formie:${moduleName}`;
|
|
22
|
+
if (registry.get(moduleId)) continue;
|
|
23
|
+
registry.register({
|
|
24
|
+
moduleId,
|
|
25
|
+
version: 2,
|
|
26
|
+
surfaces: ['client-rendered'],
|
|
27
|
+
kind: delegation.kind,
|
|
28
|
+
match: () => true,
|
|
29
|
+
setup: async(context) => {
|
|
30
|
+
await context.emit('formie:browser:module:delegated', { moduleId, reason: delegation.reason, target: context.target });
|
|
31
|
+
return { destroy: () => undefined };
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const form = root instanceof HTMLFormElement ? root : root.querySelector('form');
|
|
36
|
+
const syncRequest = () => {
|
|
37
|
+
if (!form) return;
|
|
38
|
+
const state = instance.getState();
|
|
39
|
+
setFormBrowserRequestOptions(form, instance.getBrowserRequestOptions());
|
|
40
|
+
form.action = state.definition.submission.endpoint;
|
|
41
|
+
if (state.session.tokens.csrf) form.setAttribute('data-formie-csrf-param', state.session.tokens.csrf.name);
|
|
42
|
+
for (const [name, value] of Object.entries({ handle: state.definition.handle, ...(state.session.tokens.csrf ? { [state.session.tokens.csrf.name]: state.session.tokens.csrf.value } : {}) })) {
|
|
43
|
+
let input = form.querySelector<HTMLInputElement>(`input[type="hidden"][name="${CSS.escape(name)}"]`);
|
|
44
|
+
if (!input) { input = document.createElement('input'); input.type = 'hidden'; input.name = name; form.append(input); }
|
|
45
|
+
input.value = value;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
syncRequest();
|
|
49
|
+
const unsubscribeRequest = instance.subscribe(syncRequest);
|
|
50
|
+
const host = await hydrateFormieModules({ root, modules: instance.getState().definition.modules, surface: 'client-rendered', registry });
|
|
51
|
+
instance.setBrowserModuleGuard(host.assertReady);
|
|
52
|
+
instance.setBrowserModulePreparation(host.prepare);
|
|
53
|
+
const unsubscribe = instance.on('formie:submit:result', (payload) => {
|
|
54
|
+
const result = payload as import('@verbb/formie-core').ClientSubmitResult;
|
|
55
|
+
const completion = result.completion;
|
|
56
|
+
if (result.success && completion) {
|
|
57
|
+
if (completion.behavior === 'redirect' && typeof completion.url === 'string') {
|
|
58
|
+
if (completion.target === 'new-tab') window.open(completion.url, '_blank', 'noopener,noreferrer');
|
|
59
|
+
else window.location.assign(completion.url);
|
|
60
|
+
} else if (completion.behavior === 'reload') {
|
|
61
|
+
window.location.reload();
|
|
62
|
+
} else if (completion.behavior === 'reset') {
|
|
63
|
+
instance.reset();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
void host.result({ ok: result.success, outcome: result.outcome, version: result.version, submissionUid: result.submissionUid, errors: result.errors, session: result.session, completion: result.completion, meta: result as unknown as Record<string, unknown> });
|
|
67
|
+
});
|
|
68
|
+
return { ...host, get instances() { return host.instances; }, get failures() { return host.failures; }, destroy: async() => { unsubscribe(); unsubscribeRequest(); await host.destroy(); } };
|
|
69
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { setFormBrowserRequestOptions } from '#utils/request-profile';
|
|
2
|
+
import { assertBrowserModuleManifest } from '@verbb/formie-core';
|
|
1
3
|
import type {
|
|
2
4
|
FormieClient,
|
|
3
5
|
FormieFormInstance,
|
|
@@ -7,8 +9,8 @@ import { bindLegacyDomEventCompatibility } from '#compatibility/dom-adapter';
|
|
|
7
9
|
import { resolveLegacyCompatibilityOptions } from '#compatibility/event-map';
|
|
8
10
|
import { bindLegacyValidatorCompatibility } from '#compatibility/validator-adapter';
|
|
9
11
|
import type { FormAction, FormMode, FormTransport } from '#contracts/common';
|
|
10
|
-
import type {
|
|
11
|
-
import type { FormEndpointPayload,
|
|
12
|
+
import type { BrowserModuleDefinition, BrowserModuleInstance } from '#contracts/modules';
|
|
13
|
+
import type { FormEndpointPayload, BrowserModuleEntry, FormSubmitResult } from '#contracts/schema';
|
|
12
14
|
import type { ThemeClassMap } from '#contracts/theme';
|
|
13
15
|
import { dispatchFormieDomEvent } from '#core/dom-events';
|
|
14
16
|
import { getErrorAriaLivePreference } from '#core/error-aria-live';
|
|
@@ -20,11 +22,11 @@ import {
|
|
|
20
22
|
hasServerRenderedValidationErrors,
|
|
21
23
|
} from '#core/validation-focus';
|
|
22
24
|
import { clearSubmitFeedback, executeAjaxSubmitFlow, shouldKeepSubmitLoading } from '#core/submit-flow';
|
|
23
|
-
import { applySubmitResultUi } from '#core/submit-result-ui';
|
|
25
|
+
import { applySubmitResultUi, renderFieldErrors, renderFormErrors } from '#core/submit-result-ui';
|
|
24
26
|
import { applyPageState, clearSubmitLoading, setSubmitLoading } from '#core/submit-result-state';
|
|
25
27
|
import { EventBus } from '#events/event-bus';
|
|
26
28
|
import { ModuleRegistry } from '#modules/registry';
|
|
27
|
-
import { loadModulesFromManifest } from '#modules/loader';
|
|
29
|
+
import { loadModulesFromManifest, type BrowserModuleRuntime } from '#modules/loader';
|
|
28
30
|
import { registerThemeClassMap } from '#theme/theme-classes';
|
|
29
31
|
import { runSubmitPipeline } from '#submit/pipeline';
|
|
30
32
|
import { clearSubmissionOnUnload, requestGraphqlRender, requestRefreshTokens, requestRender, requestSetPage } from '#transport/forms-api';
|
|
@@ -41,7 +43,7 @@ type InternalInstanceState = {
|
|
|
41
43
|
bus: EventBus;
|
|
42
44
|
form: HTMLFormElement | null;
|
|
43
45
|
validator: FormieValidator | null;
|
|
44
|
-
modules:
|
|
46
|
+
modules: BrowserModuleRuntime;
|
|
45
47
|
unbinds: Array<() => void>;
|
|
46
48
|
instance: FormieFormInstance;
|
|
47
49
|
};
|
|
@@ -73,12 +75,12 @@ function parseBooleanOption(value: string | undefined, defaultValue: boolean): b
|
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
function inferStaticCacheOnLoadFromDataset(dataset: DOMStringMap): boolean {
|
|
76
|
-
if (dataset.formieRefreshTokens != null
|
|
77
|
-
return parseBooleanOption(dataset.formieRefreshTokens,
|
|
78
|
+
if (dataset.formieRefreshTokens != null) {
|
|
79
|
+
return parseBooleanOption(dataset.formieRefreshTokens, true);
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
if (dataset.formieStaticCache != null
|
|
81
|
-
return parseBooleanOption(dataset.formieStaticCache,
|
|
82
|
+
if (dataset.formieStaticCache != null) {
|
|
83
|
+
return parseBooleanOption(dataset.formieStaticCache, true);
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
return false;
|
|
@@ -90,6 +92,7 @@ function inferOptionsFromElement(target: Element): FormMountOptions {
|
|
|
90
92
|
return {
|
|
91
93
|
mode: 'server-rendered',
|
|
92
94
|
transport: (dataset.formieTransport as FormTransport) || 'rest',
|
|
95
|
+
profile: dataset.formieRequestProfile as FormMountOptions['profile'],
|
|
93
96
|
formHandle: dataset.formieHandle,
|
|
94
97
|
endpoint: dataset.formieEndpoint,
|
|
95
98
|
staticCache: inferStaticCacheOnLoadFromDataset(dataset),
|
|
@@ -99,7 +102,10 @@ function inferOptionsFromElement(target: Element): FormMountOptions {
|
|
|
99
102
|
}
|
|
100
103
|
|
|
101
104
|
function normalizeMode(mode: FormMountOptions['mode'] | undefined): FormMode {
|
|
102
|
-
|
|
105
|
+
if (mode && mode !== 'server-rendered') {
|
|
106
|
+
throw new Error('@verbb/formie-browser enhances server-rendered HTML only. Use @verbb/formie-core for client-rendered forms.');
|
|
107
|
+
}
|
|
108
|
+
return 'server-rendered';
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
function normalizeTransport(transport: FormMountOptions['transport'] | undefined): FormTransport {
|
|
@@ -153,7 +159,7 @@ function resolveHeadlessEndpoint(baseOrEndpoint: string | undefined, actionPath:
|
|
|
153
159
|
return actionPath;
|
|
154
160
|
}
|
|
155
161
|
|
|
156
|
-
if (candidate.includes(
|
|
162
|
+
if (candidate.includes(actionPath)) {
|
|
157
163
|
return candidate;
|
|
158
164
|
}
|
|
159
165
|
|
|
@@ -228,11 +234,7 @@ function normalizeHeadlessManagedUrls(target: Element, form: HTMLFormElement, op
|
|
|
228
234
|
});
|
|
229
235
|
}
|
|
230
236
|
|
|
231
|
-
|
|
232
|
-
if (transport === 'graphql' && mode !== 'server-rendered') {
|
|
233
|
-
throw new Error(`Formie ${mode} mode does not support GraphQL transport yet.`);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
237
|
+
|
|
236
238
|
|
|
237
239
|
function parseBooleanDatasetValue(value: string | undefined): boolean {
|
|
238
240
|
if (value == null) {
|
|
@@ -339,6 +341,7 @@ function parseJsonAttribute<T>(element: Element | null, attributeName: string):
|
|
|
339
341
|
try {
|
|
340
342
|
return JSON.parse(rawValue) as T;
|
|
341
343
|
} catch (error) {
|
|
344
|
+
if (attributeName === 'data-formie-modules') throw new Error('Invalid browser-module manifest JSON. Update Formie and its browser packages together.');
|
|
342
345
|
console.error(`[formie] Failed to parse ${attributeName}.`, error);
|
|
343
346
|
return null;
|
|
344
347
|
}
|
|
@@ -354,7 +357,8 @@ function getEmbeddedPayload(target: Element, form: HTMLFormElement | null): Form
|
|
|
354
357
|
// Server-rendered forms carry the same minimal payload shape that endpoint-
|
|
355
358
|
// rendered forms receive, which keeps mount behavior identical in both flows.
|
|
356
359
|
const modules = parseJsonAttribute<FormEndpointPayload['modules']>(payloadRoot, 'data-formie-modules');
|
|
357
|
-
const theme = parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme')
|
|
360
|
+
const theme = parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme-classes')
|
|
361
|
+
|| parseJsonAttribute<FormEndpointPayload['theme']>(payloadRoot, 'data-formie-theme');
|
|
358
362
|
|
|
359
363
|
if (!modules && !theme) {
|
|
360
364
|
return null;
|
|
@@ -425,6 +429,8 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
|
|
|
425
429
|
csrfInput.name = refreshTokens.csrf.param;
|
|
426
430
|
csrfInput.value = refreshTokens.csrf.token;
|
|
427
431
|
csrfInput.setAttribute('autocomplete', 'off');
|
|
432
|
+
// Match server-rendered CSRF inputs so helpers can resolve custom token names.
|
|
433
|
+
csrfInput.setAttribute('data-formie-csrf', '');
|
|
428
434
|
form.prepend(csrfInput);
|
|
429
435
|
}
|
|
430
436
|
}
|
|
@@ -443,6 +449,19 @@ function applyRefreshTokensToForm(form: HTMLFormElement, refreshTokens: FormEndp
|
|
|
443
449
|
}
|
|
444
450
|
}
|
|
445
451
|
|
|
452
|
+
if (refreshTokens.uploadCreateToken) {
|
|
453
|
+
let uploadCreateInput = form.querySelector('input[name="uploadCreateToken"]') as HTMLInputElement | null;
|
|
454
|
+
|
|
455
|
+
if (!uploadCreateInput) {
|
|
456
|
+
uploadCreateInput = document.createElement('input');
|
|
457
|
+
uploadCreateInput.type = 'hidden';
|
|
458
|
+
uploadCreateInput.name = 'uploadCreateToken';
|
|
459
|
+
form.append(uploadCreateInput);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
uploadCreateInput.value = refreshTokens.uploadCreateToken;
|
|
463
|
+
}
|
|
464
|
+
|
|
446
465
|
if (refreshTokens.captchas && typeof refreshTokens.captchas === 'object') {
|
|
447
466
|
Object.values(refreshTokens.captchas).forEach((captchaEntry) => {
|
|
448
467
|
if (!captchaEntry || typeof captchaEntry !== 'object') {
|
|
@@ -478,7 +497,6 @@ async function ensureHtmlRender(target: Element, options: FormMountOptions): Pro
|
|
|
478
497
|
return options.payload;
|
|
479
498
|
}
|
|
480
499
|
|
|
481
|
-
ensureSupportedHeadlessTransport(transport, mode);
|
|
482
500
|
|
|
483
501
|
const hasForm = !!getFormFromTarget(target);
|
|
484
502
|
const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
|
|
@@ -501,11 +519,11 @@ async function ensureHtmlRender(target: Element, options: FormMountOptions): Pro
|
|
|
501
519
|
? resolveGraphqlEndpoint(options, target)
|
|
502
520
|
: resolveHtmlRenderEndpoint(options, target);
|
|
503
521
|
const payload = transport === 'graphql'
|
|
504
|
-
? await requestGraphqlRender(endpoint, formHandle, renderOptions)
|
|
522
|
+
? await requestGraphqlRender(endpoint, formHandle, renderOptions, options)
|
|
505
523
|
: await requestRender(endpoint, formHandle, {
|
|
506
524
|
...renderOptions,
|
|
507
525
|
endpoint,
|
|
508
|
-
});
|
|
526
|
+
}, options);
|
|
509
527
|
|
|
510
528
|
if (payload?.html) {
|
|
511
529
|
(target as HTMLElement).innerHTML = payload.html;
|
|
@@ -519,7 +537,6 @@ async function refreshTokensAfterSubmitIfNeeded(target: Element, options: FormMo
|
|
|
519
537
|
return;
|
|
520
538
|
}
|
|
521
539
|
|
|
522
|
-
ensureSupportedHeadlessTransport(normalizeTransport(options.transport), normalizeMode(options.mode));
|
|
523
540
|
|
|
524
541
|
const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
|
|
525
542
|
if (!formHandle) {
|
|
@@ -531,7 +548,7 @@ async function refreshTokensAfterSubmitIfNeeded(target: Element, options: FormMo
|
|
|
531
548
|
const endpoint = resolveRefreshTokensEndpoint(options, target);
|
|
532
549
|
const renderIdInput = form.querySelector('input[name="renderId"]') as HTMLInputElement | null;
|
|
533
550
|
const renderId = renderIdInput?.value || undefined;
|
|
534
|
-
const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
|
|
551
|
+
const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
|
|
535
552
|
applyRefreshTokensToForm(form, refreshTokens);
|
|
536
553
|
dispatchFormieDomEvent(target, 'formie:refresh-tokens:refreshed', refreshTokens);
|
|
537
554
|
}
|
|
@@ -543,7 +560,10 @@ function bindFormEvents(
|
|
|
543
560
|
bus: EventBus,
|
|
544
561
|
validator: FormieValidator | null,
|
|
545
562
|
unbinds: Array<() => void>,
|
|
546
|
-
): void {
|
|
563
|
+
): () => void {
|
|
564
|
+
let captureBaseline = () => {};
|
|
565
|
+
form.dataset.formieRequestProfile = options.profile ?? 'same-origin-browser';
|
|
566
|
+
if (options.profile === 'cross-origin-public') form.dataset.formieSubmitMethod = 'ajax';
|
|
547
567
|
const submitMethod = String(
|
|
548
568
|
form.dataset.formieSubmitMethod || '',
|
|
549
569
|
).trim().toLowerCase();
|
|
@@ -567,6 +587,7 @@ function bindFormEvents(
|
|
|
567
587
|
return !hasInternalNavigation(form);
|
|
568
588
|
},
|
|
569
589
|
});
|
|
590
|
+
captureBaseline = unloadWarning.scheduleBaselineCapture;
|
|
570
591
|
const handleSubmitResult = (event: Event): void => {
|
|
571
592
|
if (!(event instanceof CustomEvent)) {
|
|
572
593
|
return;
|
|
@@ -578,7 +599,7 @@ function bindFormEvents(
|
|
|
578
599
|
return;
|
|
579
600
|
}
|
|
580
601
|
|
|
581
|
-
if (result.action === 'save') {
|
|
602
|
+
if (result.action === 'save' || result.outcome === 'revised') {
|
|
582
603
|
unloadWarning.scheduleBaselineCapture();
|
|
583
604
|
}
|
|
584
605
|
};
|
|
@@ -625,8 +646,7 @@ function bindFormEvents(
|
|
|
625
646
|
return;
|
|
626
647
|
}
|
|
627
648
|
|
|
628
|
-
//
|
|
629
|
-
// continuity in the background so refresh/recovery stays in sync.
|
|
649
|
+
// The server validates forward navigation before selecting the page.
|
|
630
650
|
event.preventDefault();
|
|
631
651
|
|
|
632
652
|
const currentTarget = event.currentTarget as HTMLAnchorElement | null;
|
|
@@ -637,7 +657,6 @@ function bindFormEvents(
|
|
|
637
657
|
return;
|
|
638
658
|
}
|
|
639
659
|
|
|
640
|
-
applyPageState(form, nextPageId);
|
|
641
660
|
dispatchFormieDomEvent(target, 'formie:page:navigate', {
|
|
642
661
|
pageId: nextPageId,
|
|
643
662
|
href,
|
|
@@ -645,6 +664,15 @@ function bindFormEvents(
|
|
|
645
664
|
|
|
646
665
|
try {
|
|
647
666
|
const response = await requestSetPage(href, form, nextPageId);
|
|
667
|
+
if (response.pageId) applyPageState(form, String(response.pageId));
|
|
668
|
+
if (!response.success) {
|
|
669
|
+
const { form: formErrors = [], ...fieldErrors } = response.errors ?? {};
|
|
670
|
+
clearSubmitFeedback(form);
|
|
671
|
+
renderFieldErrors(form, fieldErrors);
|
|
672
|
+
renderFormErrors(form, formErrors);
|
|
673
|
+
focusFirstValidationError(form);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
648
676
|
|
|
649
677
|
dispatchFormieDomEvent(target, 'formie:page:navigate:after', {
|
|
650
678
|
pageId: nextPageId,
|
|
@@ -829,6 +857,8 @@ function bindFormEvents(
|
|
|
829
857
|
unbinds.push(() => {
|
|
830
858
|
form.removeEventListener('submit', submitHandler);
|
|
831
859
|
});
|
|
860
|
+
|
|
861
|
+
return captureBaseline;
|
|
832
862
|
}
|
|
833
863
|
|
|
834
864
|
async function refreshTokensIfNeeded(target: Element, options: FormMountOptions, form: HTMLFormElement | null): Promise<void> {
|
|
@@ -840,7 +870,6 @@ async function refreshTokensIfNeeded(target: Element, options: FormMountOptions,
|
|
|
840
870
|
return;
|
|
841
871
|
}
|
|
842
872
|
|
|
843
|
-
ensureSupportedHeadlessTransport(normalizeTransport(options.transport), normalizeMode(options.mode));
|
|
844
873
|
|
|
845
874
|
const formHandle = options.formHandle || (target as HTMLElement).dataset.formieHandle;
|
|
846
875
|
const endpoint = resolveRefreshTokensEndpoint(options, target);
|
|
@@ -853,7 +882,7 @@ async function refreshTokensIfNeeded(target: Element, options: FormMountOptions,
|
|
|
853
882
|
|
|
854
883
|
// Refresh-tokens-before-ready is the cache-safe path: forms can render from SSR/cache
|
|
855
884
|
// and still receive fresh transport tokens before the user submits anything.
|
|
856
|
-
const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
|
|
885
|
+
const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
|
|
857
886
|
|
|
858
887
|
if (!refreshTokens || !form) {
|
|
859
888
|
return;
|
|
@@ -869,14 +898,14 @@ export function createFormieClient(): FormieClient {
|
|
|
869
898
|
const moduleRegistry = new ModuleRegistry();
|
|
870
899
|
const pendingVisibilityMounts = new Map<Element, () => void>();
|
|
871
900
|
const pendingUnmounts = new Map<Element, Promise<void>>();
|
|
872
|
-
const stageNames: Array<'prepare' | 'normalize' | 'validate' | '
|
|
901
|
+
const stageNames: Array<'prepare' | 'normalize' | 'validate' | 'challenge' | 'payment' | 'send' | 'result'> = [
|
|
873
902
|
'prepare',
|
|
874
903
|
'normalize',
|
|
875
904
|
'validate',
|
|
876
|
-
'
|
|
877
|
-
'
|
|
878
|
-
'
|
|
879
|
-
'
|
|
905
|
+
'challenge',
|
|
906
|
+
'payment',
|
|
907
|
+
'send',
|
|
908
|
+
'result',
|
|
880
909
|
];
|
|
881
910
|
|
|
882
911
|
const unmount = async(target: Element): Promise<void> => {
|
|
@@ -913,10 +942,7 @@ export function createFormieClient(): FormieClient {
|
|
|
913
942
|
state.validator?.destroy();
|
|
914
943
|
state.validator = null;
|
|
915
944
|
|
|
916
|
-
|
|
917
|
-
await moduleInstance.destroy();
|
|
918
|
-
}
|
|
919
|
-
state.modules = [];
|
|
945
|
+
await state.modules.destroy();
|
|
920
946
|
|
|
921
947
|
state.bus.clear();
|
|
922
948
|
instances.delete(target);
|
|
@@ -967,18 +993,31 @@ export function createFormieClient(): FormieClient {
|
|
|
967
993
|
};
|
|
968
994
|
const compatibilityOptions = resolveLegacyCompatibilityOptions(normalizedOptions.compatibility);
|
|
969
995
|
|
|
970
|
-
if (normalizedOptions.mode !== 'server-rendered' && !getFormFromTarget(target)) {
|
|
971
|
-
throw new Error(`Formie ${normalizedOptions.mode} mode is not implemented yet in the browser client.`);
|
|
972
|
-
}
|
|
973
996
|
|
|
974
997
|
const renderPayload = await ensureHtmlRender(target, normalizedOptions);
|
|
975
998
|
const form = getFormFromTarget(target);
|
|
999
|
+
if (form) setFormBrowserRequestOptions(form, normalizedOptions);
|
|
976
1000
|
normalizedOptions.staticCache =
|
|
977
1001
|
options.staticCache ??
|
|
978
1002
|
(form
|
|
979
1003
|
? inferStaticCacheOnLoadFromDataset(form.dataset)
|
|
980
1004
|
: inferStaticCacheOnLoadFromDataset((target as HTMLElement).dataset));
|
|
981
|
-
|
|
1005
|
+
let embeddedPayload: FormEndpointPayload | null;
|
|
1006
|
+
try {
|
|
1007
|
+
embeddedPayload = getEmbeddedPayload(target, form);
|
|
1008
|
+
if (renderPayload?.modules) assertBrowserModuleManifest(renderPayload.modules);
|
|
1009
|
+
if (embeddedPayload?.modules) assertBrowserModuleManifest(embeddedPayload.modules);
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
// Unknown semantics must not fall back to an unguarded native send.
|
|
1012
|
+
if (form) {
|
|
1013
|
+
form.addEventListener('submit', (event) => { event.preventDefault(); event.stopImmediatePropagation(); }, true);
|
|
1014
|
+
const alert = document.createElement('div');
|
|
1015
|
+
alert.setAttribute('role', 'alert');
|
|
1016
|
+
alert.textContent = 'This form requires a compatible Formie browser package. Update Formie and its browser packages together.';
|
|
1017
|
+
form.prepend(alert);
|
|
1018
|
+
}
|
|
1019
|
+
throw error;
|
|
1020
|
+
}
|
|
982
1021
|
const payload = renderPayload || embeddedPayload
|
|
983
1022
|
? {
|
|
984
1023
|
...(renderPayload || {}),
|
|
@@ -987,14 +1026,13 @@ export function createFormieClient(): FormieClient {
|
|
|
987
1026
|
: null;
|
|
988
1027
|
const themeClassMap = payload?.theme as ThemeClassMap | undefined;
|
|
989
1028
|
const stateStore: Record<string, unknown> = {};
|
|
990
|
-
const moduleManifest =
|
|
991
|
-
|
|
992
|
-
});
|
|
1029
|
+
const moduleManifest = payload?.modules ?? { contractVersion: 2, surface: 'server-rendered' as const, entries: [] };
|
|
1030
|
+
assertBrowserModuleManifest(moduleManifest);
|
|
993
1031
|
debug.log('Resolved mount payload.', {
|
|
994
1032
|
target: getTargetDebugLabel(target),
|
|
995
1033
|
hasRenderPayload: !!renderPayload,
|
|
996
1034
|
hasEmbeddedPayload: !!embeddedPayload,
|
|
997
|
-
moduleCount: moduleManifest.length,
|
|
1035
|
+
moduleCount: moduleManifest.entries.length,
|
|
998
1036
|
});
|
|
999
1037
|
const resolvedThemeClassMap = registerThemeClassMap(target, themeClassMap, form);
|
|
1000
1038
|
|
|
@@ -1028,9 +1066,6 @@ export function createFormieClient(): FormieClient {
|
|
|
1028
1066
|
if (form) {
|
|
1029
1067
|
ensureFormStartedAt(form);
|
|
1030
1068
|
|
|
1031
|
-
if (normalizedOptions.themeConfig && typeof normalizedOptions.themeConfig === 'object') {
|
|
1032
|
-
form.setAttribute('data-formie-theme-config', JSON.stringify(normalizedOptions.themeConfig));
|
|
1033
|
-
}
|
|
1034
1069
|
|
|
1035
1070
|
if (normalizedOptions.theme && normalizedOptions.theme !== 'formie') {
|
|
1036
1071
|
form.setAttribute('data-formie-frontend-theme', normalizedOptions.theme);
|
|
@@ -1061,7 +1096,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1061
1096
|
matchContext: {
|
|
1062
1097
|
root: target,
|
|
1063
1098
|
form,
|
|
1064
|
-
|
|
1099
|
+
surface: moduleManifest.surface,
|
|
1065
1100
|
},
|
|
1066
1101
|
setupContext: {
|
|
1067
1102
|
formId: id,
|
|
@@ -1069,6 +1104,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1069
1104
|
form,
|
|
1070
1105
|
target,
|
|
1071
1106
|
scope: 'form',
|
|
1107
|
+
surface: moduleManifest.surface,
|
|
1072
1108
|
state: stateStore,
|
|
1073
1109
|
on: (eventName, callback) => {
|
|
1074
1110
|
return bus.on(eventName, callback);
|
|
@@ -1088,7 +1124,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1088
1124
|
});
|
|
1089
1125
|
debug.log('Module setup complete.', {
|
|
1090
1126
|
target: getTargetDebugLabel(target),
|
|
1091
|
-
moduleInstances: modules.length,
|
|
1127
|
+
moduleInstances: modules.instances.length,
|
|
1092
1128
|
});
|
|
1093
1129
|
|
|
1094
1130
|
const instance: FormieFormInstance = {
|
|
@@ -1184,7 +1220,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1184
1220
|
}
|
|
1185
1221
|
|
|
1186
1222
|
if (form) {
|
|
1187
|
-
bindFormEvents(target, form, normalizedOptions, bus, validator, unbinds);
|
|
1223
|
+
const captureBaseline = bindFormEvents(target, form, normalizedOptions, bus, validator, unbinds);
|
|
1188
1224
|
|
|
1189
1225
|
if (validator) {
|
|
1190
1226
|
unbinds.push(bindSubmitReadiness(form, validator, target));
|
|
@@ -1192,19 +1228,31 @@ export function createFormieClient(): FormieClient {
|
|
|
1192
1228
|
}
|
|
1193
1229
|
|
|
1194
1230
|
await refreshTokensIfNeeded(target, normalizedOptions, form);
|
|
1231
|
+
|
|
1232
|
+
// Recapture after captcha/token hydration and field-module init settle so
|
|
1233
|
+
// programmatic post-mount value changes do not leave a false dirty state.
|
|
1234
|
+
captureBaseline();
|
|
1235
|
+
const baselineTimer = window.setTimeout(captureBaseline, 350);
|
|
1236
|
+
unbinds.push(() => window.clearTimeout(baselineTimer));
|
|
1195
1237
|
}
|
|
1196
1238
|
|
|
1197
1239
|
stageNames.forEach((stageName) => {
|
|
1198
|
-
// Stage fan-out keeps submit pipeline ownership centralized while still
|
|
1199
|
-
// letting modules participate before and after each stage.
|
|
1200
1240
|
const beforeDomUnbind = bus.on(`formie:stage:${stageName}:before`, async(payload) => {
|
|
1201
1241
|
dispatchFormieDomEvent(target, `formie:stage:${stageName}:before`, payload);
|
|
1202
1242
|
});
|
|
1203
1243
|
|
|
1204
1244
|
const beforeUnbind = bus.on(`formie:stage:${stageName}:before`, async(payload) => {
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1245
|
+
const hookStage = { core: 'prepare', field: 'prepare', address: 'prepare', captcha: 'challenge', payment: 'payment' } as const;
|
|
1246
|
+
const stagePayload = payload as Parameters<NonNullable<BrowserModuleInstance['beforeSubmit']>>[0] & { stage: string };
|
|
1247
|
+
|
|
1248
|
+
if (stageName === 'prepare') {
|
|
1249
|
+
try { modules.assertReady(); }
|
|
1250
|
+
catch { stagePayload.abort('A required form feature could not start. Reload the page or contact the site administrator.'); return; }
|
|
1251
|
+
}
|
|
1252
|
+
for (const moduleInstance of modules.instances) {
|
|
1253
|
+
if (moduleInstance.beforeSubmit && hookStage[moduleInstance.kind ?? 'core'] === stageName) {
|
|
1254
|
+
const { form, action, formData, abort, isAborted, abortReason } = stagePayload;
|
|
1255
|
+
await moduleInstance.beforeSubmit({ form, action, formData, abort, isAborted, abortReason });
|
|
1208
1256
|
}
|
|
1209
1257
|
}
|
|
1210
1258
|
});
|
|
@@ -1213,19 +1261,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1213
1261
|
dispatchFormieDomEvent(target, `formie:stage:${stageName}:after`, payload);
|
|
1214
1262
|
});
|
|
1215
1263
|
|
|
1216
|
-
|
|
1217
|
-
const stagePayload = payload as {
|
|
1218
|
-
result?: FormSubmitResult;
|
|
1219
|
-
} & Parameters<NonNullable<FormieModuleInstance['onAfterStage']>>[0];
|
|
1220
|
-
|
|
1221
|
-
for (const moduleInstance of modules) {
|
|
1222
|
-
if (moduleInstance.onAfterStage) {
|
|
1223
|
-
await moduleInstance.onAfterStage(stagePayload, stagePayload.result);
|
|
1224
|
-
}
|
|
1225
|
-
}
|
|
1226
|
-
});
|
|
1227
|
-
|
|
1228
|
-
unbinds.push(beforeDomUnbind, beforeUnbind, afterDomUnbind, afterUnbind);
|
|
1264
|
+
unbinds.push(beforeDomUnbind, beforeUnbind, afterDomUnbind);
|
|
1229
1265
|
});
|
|
1230
1266
|
|
|
1231
1267
|
const submitBeforeUnbind = bus.on('formie:submit:before', async(payload) => {
|
|
@@ -1234,6 +1270,16 @@ export function createFormieClient(): FormieClient {
|
|
|
1234
1270
|
|
|
1235
1271
|
const submitAfterUnbind = bus.on('formie:submit:after', async(payload) => {
|
|
1236
1272
|
dispatchFormieDomEvent(target, 'formie:submit:after', payload);
|
|
1273
|
+
|
|
1274
|
+
if (!form) return;
|
|
1275
|
+
const result = payload as FormSubmitResult;
|
|
1276
|
+
if (result.code === 'PREFLIGHT_COMPLETE') return;
|
|
1277
|
+
const context = {
|
|
1278
|
+
form,
|
|
1279
|
+
action: result.action ?? 'submit' as const,
|
|
1280
|
+
formData: new FormData(form),
|
|
1281
|
+
};
|
|
1282
|
+
for (const moduleInstance of modules.instances) await moduleInstance.afterSubmit?.(context, result);
|
|
1237
1283
|
});
|
|
1238
1284
|
|
|
1239
1285
|
const submitFinalBeforeUnbind = bus.on('formie:submit:final:before', async(payload) => {
|
|
@@ -1428,7 +1474,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1428
1474
|
return;
|
|
1429
1475
|
}
|
|
1430
1476
|
|
|
1431
|
-
const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId);
|
|
1477
|
+
const refreshTokens = await requestRefreshTokens(endpoint, formHandle, renderId, options, form?.querySelector<HTMLInputElement>('input[name="requestToken"]')?.value);
|
|
1432
1478
|
|
|
1433
1479
|
if (!refreshTokens) {
|
|
1434
1480
|
return;
|
|
@@ -1439,7 +1485,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1439
1485
|
};
|
|
1440
1486
|
|
|
1441
1487
|
const registerModule = (
|
|
1442
|
-
moduleDefinition:
|
|
1488
|
+
moduleDefinition: BrowserModuleDefinition,
|
|
1443
1489
|
options?: Parameters<ModuleRegistry['register']>[1],
|
|
1444
1490
|
): boolean => {
|
|
1445
1491
|
return moduleRegistry.register(moduleDefinition, options);
|
|
@@ -1449,7 +1495,7 @@ export function createFormieClient(): FormieClient {
|
|
|
1449
1495
|
moduleRegistry.unregister(moduleId);
|
|
1450
1496
|
};
|
|
1451
1497
|
|
|
1452
|
-
const getRegisteredModules = ():
|
|
1498
|
+
const getRegisteredModules = (): BrowserModuleDefinition[] => {
|
|
1453
1499
|
return moduleRegistry.getAll();
|
|
1454
1500
|
};
|
|
1455
1501
|
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field-error ARIA helpers.
|
|
3
|
+
*
|
|
4
|
+
* Pair `aria-errormessage` with `aria-describedby` pointing at the same error
|
|
5
|
+
* message id. `aria-errormessage` alone is inconsistently announced (notably
|
|
6
|
+
* iOS Safari + VoiceOver); Adrian Roselli's cross-AT testing found the pairing
|
|
7
|
+
* more reliable without clobbering instruction ids already on describedby.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export function appendDescribedBy(input: HTMLElement, describedById: string): void {
|
|
11
|
+
const current = (input.getAttribute('aria-describedby') || '').trim();
|
|
12
|
+
const items = current ? current.split(/\s+/) : [];
|
|
13
|
+
|
|
14
|
+
if (!items.includes(describedById)) {
|
|
15
|
+
items.push(describedById);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
input.setAttribute('aria-describedby', items.join(' ').trim());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function removeDescribedBy(input: HTMLElement, describedById: string): void {
|
|
22
|
+
const current = (input.getAttribute('aria-describedby') || '').trim();
|
|
23
|
+
|
|
24
|
+
if (!current) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const filtered = current.split(/\s+/).filter((item) => {
|
|
29
|
+
return item !== describedById;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (filtered.length) {
|
|
33
|
+
input.setAttribute('aria-describedby', filtered.join(' '));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
input.removeAttribute('aria-describedby');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Drop describedby ids whose targets no longer exist (Formie removes error
|
|
42
|
+
* message nodes from the DOM when clearing). Keeps instruction/other ids.
|
|
43
|
+
*/
|
|
44
|
+
export function pruneMissingDescribedBy(input: HTMLElement, doc: Document = document): void {
|
|
45
|
+
const current = (input.getAttribute('aria-describedby') || '').trim();
|
|
46
|
+
|
|
47
|
+
if (!current) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const remaining = current.split(/\s+/).filter((id) => {
|
|
52
|
+
return !!id && !!doc.getElementById(id);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (remaining.length) {
|
|
56
|
+
input.setAttribute('aria-describedby', remaining.join(' '));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
input.removeAttribute('aria-describedby');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function setErrorMessageReference(input: HTMLElement, errorMessageId: string): void {
|
|
64
|
+
input.setAttribute('aria-errormessage', errorMessageId);
|
|
65
|
+
// Same id in describedby so AT that ignore aria-errormessage still hear it.
|
|
66
|
+
appendDescribedBy(input, errorMessageId);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function clearErrorMessageReference(input: HTMLElement, errorMessageId: string): void {
|
|
70
|
+
if (input.getAttribute('aria-errormessage') === errorMessageId) {
|
|
71
|
+
input.removeAttribute('aria-errormessage');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
removeDescribedBy(input, errorMessageId);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Clear errormessage refs and prune any describedby ids that no longer resolve
|
|
79
|
+
* (covers message nodes removed from the DOM and legacy container ids).
|
|
80
|
+
*/
|
|
81
|
+
export function clearFieldErrorAria(input: HTMLElement, errorMessageIds: string[] = []): void {
|
|
82
|
+
errorMessageIds.forEach((errorMessageId) => {
|
|
83
|
+
if (input.getAttribute('aria-errormessage') === errorMessageId) {
|
|
84
|
+
input.removeAttribute('aria-errormessage');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!errorMessageIds.length && input.hasAttribute('aria-errormessage')) {
|
|
89
|
+
input.removeAttribute('aria-errormessage');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
pruneMissingDescribedBy(input);
|
|
93
|
+
}
|