@verbb/formie-browser 1.0.14 → 1.0.16
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 +66 -0
- package/dist/chunks/{address-country-Dn71jroa.js → address-country-DLJF-OkC.js} +5 -9
- package/dist/chunks/{address-finder-O49uDRoR.js → address-finder-QK1m1eyv.js} +2 -2
- package/dist/chunks/{address-state-ByYatUur.js → address-state-CHKit8AW.js} +113 -112
- package/dist/chunks/{api-DYYsVIub.js → api-BPtH5RBA.js} +162 -130
- package/dist/chunks/{api-D6kJ7kXZ.js → api-BR2Gqy37.js} +313 -303
- package/dist/chunks/{api-ghNr2uxX.js → api-CmuOZ0i3.js} +1 -1
- package/dist/chunks/{async-nPFRNQ06.js → async-2GdXNFCE.js} +18 -1
- package/dist/chunks/{bpoint-D0RKFM4F.js → bpoint-GvYfyPjD.js} +1 -1
- package/dist/chunks/{calculations-YEoVCG2q.js → calculations-DeEmdUDq.js} +5 -7
- package/dist/chunks/{captcha-eu-DERJbXnH.js → captcha-eu-eU_M6snp.js} +2 -2
- package/dist/chunks/{checkbox-radio-gsm8QPJg.js → checkbox-radio-CSXo6svm.js} +4 -12
- package/dist/chunks/{combobox-CWNdx4Fk.js → combobox-CJh2DoSO.js} +323 -363
- package/dist/chunks/{conditions-DqNHLTbN.js → conditions-Dz3068K0.js} +140 -153
- package/dist/chunks/{country-from-ip-DDj_mFki.js → country-from-ip-BqQ-BqLH.js} +1 -1
- package/dist/chunks/{custom-google-maps-C4l2S3GO.js → custom-google-maps-rNm5VFbb.js} +2 -6
- package/dist/chunks/{custom-link-CLazgKYH.js → custom-link-CLqFCssQ.js} +1 -1
- package/dist/chunks/{custom-maps-DqcAlD0v.js → custom-maps-DgjYQDLj.js} +4 -8
- package/dist/chunks/{date-picker-RUnevF7l.js → date-picker-BVJxJVYT.js} +42 -58
- package/dist/chunks/{debug-BV0DvdHx.js → debug-rhzqP1j2.js} +2 -14
- package/dist/chunks/{dist-ZLeW0zZ3.js → dist-CJReDlDK.js} +13 -8
- package/dist/chunks/{eway-or6dtjoT.js → eway-BZuTBAUW.js} +3 -3
- package/dist/chunks/{field-references.resolver-Ca8MguFv.js → field-references.resolver-uSHf5-NP.js} +1 -1
- package/dist/chunks/{file-upload-Bm-TXyNB.js → file-upload-vEUr_EuP.js} +131 -113
- package/dist/chunks/{friendly-captcha-v1-GFqCt1Mh.js → friendly-captcha-v1-C_cF1WD2.js} +2 -2
- package/dist/chunks/{friendly-captcha-v2-WoW246JG.js → friendly-captcha-v2-BenESH-n.js} +2 -2
- package/dist/chunks/{friendly-challenge-DPG9MJb1.js → friendly-challenge-BgPBEMXx.js} +21 -35
- package/dist/chunks/{go-cardless-DzJ-0FKg.js → go-cardless-DWZeB0Bq.js} +2 -6
- package/dist/chunks/{google-address-C1P1nTDM.js → google-address-DQR6rsbh.js} +15 -10
- package/dist/chunks/{hcaptcha-D0T8aIHz.js → hcaptcha-COilLmRT.js} +2 -2
- package/dist/chunks/{hidden-BZhXnNPe.js → hidden-Bc2TpcgF.js} +1 -1
- package/dist/chunks/{http-C2QdHuWO.js → http-Dj_skqaa.js} +1 -1
- package/dist/chunks/{leaflet-src-DE6rv6M-.js → leaflet-src-Cg-LUOD8.js} +74 -122
- package/dist/chunks/{loqate-DD1S7PY9.js → loqate-ZZClcacr.js} +2 -2
- package/dist/chunks/{mollie-DG8Ju83x.js → mollie-8OMXIYa8.js} +2 -6
- package/dist/chunks/{moneris-C8m3Fvqu.js → moneris-qYHJ9EFy.js} +1 -1
- package/dist/chunks/opayo-DlWPEnla.js +159 -0
- package/dist/chunks/{paddle-BIYR9Um3.js → paddle-CArfWih0.js} +2 -2
- package/dist/chunks/{password-validation-C8_uO_c_.js → password-validation-HGOU9kf3.js} +2 -2
- package/dist/chunks/{paypal-B_yxIo2d.js → paypal-BcaMCMGY.js} +2 -2
- package/dist/chunks/{payway-CeOzS0sL.js → payway-vxC7_bfR.js} +4 -12
- package/dist/chunks/{phone-country-bAqje17P.js → phone-country-Bx9EG50h.js} +124 -141
- package/dist/chunks/{place-kit-BE5gi3vo.js → place-kit-BKk8xDXf.js} +10 -10
- package/dist/chunks/{placekit-autocomplete.esm-CZZERYur.js → placekit-autocomplete.esm-D2A_0iVU.js} +2 -5
- package/dist/chunks/{recaptcha-enterprise-CruXddZq.js → recaptcha-enterprise-Cx6HUpMa.js} +6 -12
- package/dist/chunks/recaptcha-shared-CcMI-r8t.js +33 -0
- package/dist/chunks/{recaptcha-v2-checkbox-CCnJYCnG.js → recaptcha-v2-checkbox-DQ9BJzcv.js} +2 -2
- package/dist/chunks/{recaptcha-v2-invisible-BuVqRRYn.js → recaptcha-v2-invisible-BP0T8Pan.js} +2 -2
- package/dist/chunks/recaptcha-v3-DKMGQ27K.js +38 -0
- package/dist/chunks/{repeater-DwE27jI1.js → repeater-BR0RUvTs.js} +50 -40
- package/dist/chunks/{rich-text-uV4o_pGk.js → rich-text-H2A6cm1t.js} +33 -11
- package/dist/chunks/{rolldown-runtime-DtPi1Y-2.js → rolldown-runtime-VHNaYcLd.js} +1 -1
- package/dist/chunks/{scripts-CbQ7agX3.js → scripts-DrccjrFv.js} +1 -1
- package/dist/chunks/{sdk-lkwSvlpM.js → sdk-_cmrRYgp.js} +4 -16
- package/dist/chunks/{shared-OKhSay59.js → shared-CJ1LIEh3.js} +1 -3
- package/dist/chunks/{signature-7cldFbqj.js → signature-CfKA_poI.js} +9 -41
- package/dist/chunks/skip-CEW89ExY.js +7 -0
- package/dist/chunks/{snaptcha-C04kNGYQ.js → snaptcha-P3EoSQud.js} +1 -1
- package/dist/chunks/{square-DEzxv9D9.js → square-Dyz0p_GU.js} +2 -2
- package/dist/chunks/{stripe-CVPQGDz3.js → stripe-Bfd_jp_k.js} +4 -7
- package/dist/chunks/{summary-BF_8mrXI.js → summary-B3CAebLV.js} +45 -37
- package/dist/chunks/{survey-likert-fk1hUPff.js → survey-likert-CEtUceag.js} +1 -1
- package/dist/chunks/{survey-rank-BsLGl2LX.js → survey-rank-Dtt97y6s.js} +3 -7
- package/dist/chunks/{survey-rating-DVS6e6fM.js → survey-rating-mlDMI0uq.js} +9 -23
- package/dist/chunks/{table-DnIBdeKF.js → table-0k6Md0IO.js} +2 -2
- package/dist/chunks/{text-limit-LGMPhFuC.js → text-limit-CTVML6s7.js} +10 -10
- package/dist/chunks/{turnstile-n9DNyPxi.js → turnstile-Sp4ubowK.js} +2 -2
- package/dist/chunks/{upload-manager-BbiUosZq.js → upload-manager-Ccg3d_4I.js} +258 -260
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +743 -734
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/dom-events.d.ts +5 -0
- package/dist/js/core/dom-events.d.ts.map +1 -1
- package/dist/js/core/page-client-event.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 +3 -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/google-address.d.ts +10 -0
- package/dist/js/modules/address/google-address.d.ts.map +1 -1
- package/dist/js/modules/address/place-kit.d.ts.map +1 -1
- package/dist/js/modules/captchas/factories.d.ts.map +1 -1
- package/dist/js/modules/captchas/host.d.ts +4 -1
- package/dist/js/modules/captchas/host.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-shared.d.ts +1 -1
- package/dist/js/modules/captchas/recaptcha-shared.d.ts.map +1 -1
- package/dist/js/modules/captchas/recaptcha-v3.d.ts.map +1 -1
- package/dist/js/modules/fields/address-state.d.ts.map +1 -1
- package/dist/js/modules/fields/combobox.d.ts +2 -0
- package/dist/js/modules/fields/combobox.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
- package/dist/js/modules/fields/conditions.d.ts.map +1 -1
- package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
- package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
- package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
- package/dist/js/modules/fields/repeater.d.ts.map +1 -1
- package/dist/js/modules/fields/rich-text.d.ts.map +1 -1
- package/dist/js/modules/fields/summary.d.ts.map +1 -1
- package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
- package/dist/js/modules/fields/upload-manager.d.ts.map +1 -1
- package/dist/js/modules/payments/factories.d.ts.map +1 -1
- package/dist/js/modules/payments/opayo.d.ts.map +1 -1
- package/dist/js/submit/pipeline.d.ts +2 -0
- package/dist/js/submit/pipeline.d.ts.map +1 -1
- package/dist/js/utils/async.d.ts +1 -0
- package/dist/js/utils/async.d.ts.map +1 -1
- package/dist/js/utils/form-started-at.d.ts +1 -0
- package/dist/js/utils/form-started-at.d.ts.map +1 -1
- package/dist/js/utils/unload-warning.d.ts +4 -0
- package/dist/js/utils/unload-warning.d.ts.map +1 -1
- package/dist/js/validation/rules/required.d.ts.map +1 -1
- package/dist/js/validation/submit-readiness.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts +1 -0
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -0
- package/src/js/core/create-formie-client.ts +37 -28
- package/src/js/core/dom-events.ts +30 -1
- package/src/js/core/page-client-event.ts +5 -7
- package/src/js/core/submit-result-state.ts +30 -2
- package/src/js/core/submit-result-ui.ts +25 -3
- package/src/js/core/validation-focus.ts +17 -3
- package/src/js/modules/address/google-address.ts +95 -37
- package/src/js/modules/address/place-kit.ts +13 -7
- package/src/js/modules/captchas/factories.ts +241 -101
- package/src/js/modules/captchas/host.ts +16 -0
- package/src/js/modules/captchas/recaptcha-enterprise.ts +1 -1
- package/src/js/modules/captchas/recaptcha-shared.ts +23 -5
- package/src/js/modules/captchas/recaptcha-v3.ts +23 -14
- package/src/js/modules/fields/address-country.ts +5 -5
- package/src/js/modules/fields/address-state.ts +80 -27
- package/src/js/modules/fields/combobox.ts +22 -1
- package/src/js/modules/fields/conditions/effects.ts +4 -5
- package/src/js/modules/fields/conditions.ts +9 -0
- package/src/js/modules/fields/date-picker.ts +6 -3
- package/src/js/modules/fields/file-upload.ts +55 -18
- package/src/js/modules/fields/phone-country.ts +25 -2
- package/src/js/modules/fields/repeater.ts +55 -4
- package/src/js/modules/fields/rich-text.ts +33 -0
- package/src/js/modules/fields/summary.ts +63 -10
- package/src/js/modules/fields/text-limit.ts +11 -0
- package/src/js/modules/fields/upload-manager.ts +54 -18
- package/src/js/modules/payments/factories.ts +87 -54
- package/src/js/modules/payments/opayo.ts +7 -0
- package/src/js/submit/pipeline.ts +16 -9
- package/src/js/utils/async.ts +34 -0
- package/src/js/utils/form-started-at.ts +19 -4
- package/src/js/utils/unload-warning.ts +63 -7
- package/src/js/validation/rules/required.ts +18 -0
- package/src/js/validation/submit-readiness.ts +2 -0
- package/src/js/validation/validator.ts +29 -6
- package/src/vendor.d.ts +2 -0
- package/dist/chunks/opayo-B3EvrVxw.js +0 -188
- package/dist/chunks/recaptcha-shared-AQRMDMme.js +0 -27
- package/dist/chunks/recaptcha-v3-CloqhO2A.js +0 -28
|
@@ -1,6 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { FormieFormInstance } from '#contracts/client';
|
|
2
|
+
import { FORMIE_HTML_EVENT_NAMES, toDomEventName } from '#utils/event-names';
|
|
3
|
+
|
|
4
|
+
const formEventInstances = new WeakMap<Element, FormieFormInstance>();
|
|
5
|
+
const lifecycleEvents = new Set<string>([...FORMIE_HTML_EVENT_NAMES, 'formie:state:reset']);
|
|
6
|
+
|
|
7
|
+
export function bindFormEventInstance(root: Element, form: HTMLFormElement | null, instance: FormieFormInstance): () => void {
|
|
8
|
+
// Wrapper mounts also dispatch lifecycle events from their inner form.
|
|
9
|
+
const targets = new Set<Element>(form ? [root, form] : [root]);
|
|
10
|
+
targets.forEach((target) => formEventInstances.set(target, instance));
|
|
11
|
+
return () => {
|
|
12
|
+
targets.forEach((target) => {
|
|
13
|
+
if (formEventInstances.get(target) === instance) formEventInstances.delete(target);
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function withFormEventInstance<T extends object>(target: Element, detail: T): T & { instance?: FormieFormInstance } {
|
|
19
|
+
const instance = formEventInstances.get(target);
|
|
20
|
+
// Retain payload identity: synchronous hooks can modify the active result.
|
|
21
|
+
return instance ? Object.assign(detail, { instance }) : detail;
|
|
22
|
+
}
|
|
2
23
|
|
|
3
24
|
export function dispatchFormieDomEvent(target: Element, eventName: string, detail: unknown): void {
|
|
25
|
+
// Early setup and standalone module hydration have no mounted form instance.
|
|
26
|
+
// Custom module events retain their own payload shapes.
|
|
27
|
+
if (lifecycleEvents.has(eventName) || eventName.startsWith('formie:stage:')) {
|
|
28
|
+
if (detail == null) detail = formEventInstances.has(target) ? {} : detail;
|
|
29
|
+
if (detail && typeof detail === 'object' && !Array.isArray(detail)) {
|
|
30
|
+
detail = withFormEventInstance(target, detail);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
4
33
|
target.dispatchEvent(new CustomEvent(toDomEventName(eventName), {
|
|
5
34
|
bubbles: true,
|
|
6
35
|
detail,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dispatchFormieDomEvent } from '#core/dom-events';
|
|
1
2
|
import { createDebug } from '#utils/debug';
|
|
2
3
|
|
|
3
4
|
const debug = createDebug('general', 'page-client-event');
|
|
@@ -114,13 +115,10 @@ export function dispatchResolvedClientEvents(form: HTMLFormElement, events: Reso
|
|
|
114
115
|
events.forEach((item) => {
|
|
115
116
|
win.dataLayer!.push(item.payload);
|
|
116
117
|
|
|
117
|
-
form
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
payload: item.payload,
|
|
122
|
-
},
|
|
123
|
-
}));
|
|
118
|
+
dispatchFormieDomEvent(form, 'formie:client-event', {
|
|
119
|
+
event: item.event,
|
|
120
|
+
payload: item.payload,
|
|
121
|
+
});
|
|
124
122
|
});
|
|
125
123
|
|
|
126
124
|
debug.log('Dispatched resolved client events.', {
|
|
@@ -5,6 +5,7 @@ import { dispatchPageClientEventForSubmit, dispatchResolvedClientEvents } from '
|
|
|
5
5
|
import { syncPageTabErrors } from '#core/page-tab-errors';
|
|
6
6
|
import { addThemeClasses, removeThemeClasses, toggleThemeClasses } from '#theme/theme-classes';
|
|
7
7
|
import { createDebug } from '#utils/debug';
|
|
8
|
+
import { resetFormStartedAt } from '#utils/form-started-at';
|
|
8
9
|
import { getFormStateEventName, normalizeFormieEventName } from '#utils/event-names';
|
|
9
10
|
import type { FormieValidator } from '#validation/validator';
|
|
10
11
|
import { syncSubmitReadiness } from '#validation/submit-readiness';
|
|
@@ -371,6 +372,17 @@ export function applyPageState(form: HTMLFormElement, nextPageId: string): void
|
|
|
371
372
|
}
|
|
372
373
|
|
|
373
374
|
function syncSubmissionIdentity(form: HTMLFormElement, result: FormSubmitResult): void {
|
|
375
|
+
if (result.meta?.resetSubmissionIdentity === true) {
|
|
376
|
+
// Saved spam is terminal. Preserve the entered values for correction,
|
|
377
|
+
// but do not try to continue the rejected record on the next submit.
|
|
378
|
+
removeHiddenInput(form, 'submissionId');
|
|
379
|
+
removeHiddenInput(form, 'submissionUid');
|
|
380
|
+
removeHiddenInput(form, 'progressId');
|
|
381
|
+
setHiddenInputValue(form, 'expectedVersion', '0');
|
|
382
|
+
clearResumeTokenState(form);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
374
386
|
const session = result.meta?.session as { version?: number } | undefined;
|
|
375
387
|
const version = session?.version ?? result.meta?.version;
|
|
376
388
|
if (typeof version === 'number') {
|
|
@@ -449,6 +461,15 @@ function resetSubmissionState(form: HTMLFormElement, options: ResetSubmissionSta
|
|
|
449
461
|
// A full state reset is broader than the native form reset: it also clears
|
|
450
462
|
// hidden continuity inputs, resume-token state, page UI, and live validation.
|
|
451
463
|
form.reset();
|
|
464
|
+
// Summary modules on hidden pages are unmounted and cannot clear themselves.
|
|
465
|
+
form.querySelectorAll('[data-formie-summary-token]').forEach((element) => {
|
|
466
|
+
if (element instanceof HTMLInputElement) {
|
|
467
|
+
element.remove();
|
|
468
|
+
} else {
|
|
469
|
+
element.removeAttribute('data-formie-summary-token');
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
form.querySelectorAll('[data-formie-summary-blocks]').forEach((element) => element.replaceChildren());
|
|
452
473
|
if (!options.preserveHiddenState) {
|
|
453
474
|
setFormHiddenState(form, false);
|
|
454
475
|
}
|
|
@@ -458,16 +479,17 @@ function resetSubmissionState(form: HTMLFormElement, options: ResetSubmissionSta
|
|
|
458
479
|
removeHiddenInput(form, 'progressId');
|
|
459
480
|
removeHiddenInput(form, 'pageId');
|
|
460
481
|
clearResumeTokenState(form);
|
|
482
|
+
resetFormStartedAt(form);
|
|
461
483
|
validator?.resetLiveState();
|
|
462
484
|
|
|
463
485
|
if (firstPageId) {
|
|
464
486
|
applyPageState(form, firstPageId);
|
|
465
|
-
form
|
|
487
|
+
dispatchFormieDomEvent(form, getFormStateEventName('reset'), undefined);
|
|
466
488
|
return;
|
|
467
489
|
}
|
|
468
490
|
|
|
469
491
|
syncPageTabErrors(form);
|
|
470
|
-
form
|
|
492
|
+
dispatchFormieDomEvent(form, getFormStateEventName('reset'), undefined);
|
|
471
493
|
}
|
|
472
494
|
|
|
473
495
|
function shouldResetSubmissionState(result: FormSubmitResult): boolean {
|
|
@@ -542,6 +564,12 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
|
|
|
542
564
|
return;
|
|
543
565
|
}
|
|
544
566
|
|
|
567
|
+
// Provider handlers may navigate as soon as their action event is dispatched.
|
|
568
|
+
// Treat checkout navigation like the generic redirect before invoking them.
|
|
569
|
+
if (result.payment?.action?.type === 'redirect' && result.payment.action.url) {
|
|
570
|
+
markInternalNavigation(form, 'payment-redirect');
|
|
571
|
+
}
|
|
572
|
+
|
|
545
573
|
// Dispatch payment/redirect events from submitData (e.g. formie:payment:mollie:redirect)
|
|
546
574
|
const eventDispatchResult = dispatchSubmitDataEvents(form, result);
|
|
547
575
|
|
|
@@ -363,7 +363,7 @@ function shouldRenderSuccessMessage(form: HTMLFormElement, result: FormSubmitRes
|
|
|
363
363
|
return getConfiguredCompletionBehavior(form) === 'message' && getSuccessMessagePosition(form) !== '';
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
export function renderFormSuccess(form: HTMLFormElement, message: string): void {
|
|
366
|
+
export function renderFormSuccess(form: HTMLFormElement, message: string, options: { hideForm?: boolean } = {}): void {
|
|
367
367
|
const position = getSuccessMessagePosition(form);
|
|
368
368
|
|
|
369
369
|
if (!position) {
|
|
@@ -384,10 +384,17 @@ export function renderFormSuccess(form: HTMLFormElement, message: string): void
|
|
|
384
384
|
successNode.innerHTML = message;
|
|
385
385
|
container.appendChild(successNode);
|
|
386
386
|
|
|
387
|
-
if (shouldHideFormOnSuccess(form)) {
|
|
387
|
+
if (options.hideForm !== false && shouldHideFormOnSuccess(form)) {
|
|
388
388
|
setFormHiddenState(form, true);
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
const scrollToTop = form.dataset.formieScrollToTop?.trim().toLowerCase();
|
|
392
|
+
|
|
393
|
+
if (scrollToTop !== undefined && ['', 'true', '1'].includes(scrollToTop)) {
|
|
394
|
+
// Scroll after hiding the form so the message's final position is used.
|
|
395
|
+
container.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
396
|
+
}
|
|
397
|
+
|
|
391
398
|
const timeoutMs = getSuccessMessageTimeoutMs(form);
|
|
392
399
|
|
|
393
400
|
if (timeoutMs !== null) {
|
|
@@ -407,8 +414,23 @@ export function applySubmitResultUi(form: HTMLFormElement, result: FormSubmitRes
|
|
|
407
414
|
clearAriaInvalid(form);
|
|
408
415
|
|
|
409
416
|
if (result.ok) {
|
|
417
|
+
const summaryTokens = result.meta?.summaryTokens;
|
|
418
|
+
|
|
419
|
+
// Hidden-page modules mount only after navigation. Persist access before
|
|
420
|
+
// that happens so the new summary instance can read its saved submission.
|
|
421
|
+
if (summaryTokens && typeof summaryTokens === 'object') {
|
|
422
|
+
form.querySelectorAll<HTMLElement>('[data-formie-field-uid]').forEach((field) => {
|
|
423
|
+
const token = (summaryTokens as Record<string, unknown>)[field.dataset.formieFieldUid || ''];
|
|
424
|
+
|
|
425
|
+
if (typeof token === 'string' && token) {
|
|
426
|
+
field.querySelector('[data-formie-summary-container]')?.setAttribute('data-formie-summary-token', token);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
410
431
|
if (shouldRenderSuccessMessage(form, result)) {
|
|
411
|
-
|
|
432
|
+
// Saving progress acknowledges persistence without completing the form.
|
|
433
|
+
renderFormSuccess(form, result.message || '', { hideForm: result.action !== 'save' });
|
|
412
434
|
}
|
|
413
435
|
|
|
414
436
|
return;
|
|
@@ -14,20 +14,34 @@ function getFirstErroredField(form: HTMLFormElement): HTMLElement | null {
|
|
|
14
14
|
}) || null;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
function getVisibleControl(control: HTMLElement): HTMLElement {
|
|
18
|
+
if (control instanceof HTMLTextAreaElement) {
|
|
19
|
+
const richText = (control as HTMLTextAreaElement & { richText?: { content: HTMLElement } }).richText;
|
|
20
|
+
|
|
21
|
+
if (richText) {
|
|
22
|
+
return richText.content;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return control;
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
function getFieldFocusTarget(field: HTMLElement): HTMLElement | null {
|
|
18
30
|
const invalidControl = Array.from(field.querySelectorAll('[aria-invalid="true"]')).find((node) => {
|
|
19
31
|
return !isValidationSkipped(node);
|
|
20
32
|
}) as HTMLElement | undefined;
|
|
21
33
|
|
|
22
34
|
if (invalidControl) {
|
|
23
|
-
return invalidControl;
|
|
35
|
+
return getVisibleControl(invalidControl);
|
|
24
36
|
}
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
const control = Array.from(field.querySelectorAll(
|
|
27
39
|
'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled])',
|
|
28
40
|
)).find((node) => {
|
|
29
41
|
return !isValidationSkipped(node);
|
|
30
|
-
}) as HTMLElement | undefined
|
|
42
|
+
}) as HTMLElement | undefined;
|
|
43
|
+
|
|
44
|
+
return control ? getVisibleControl(control) : null;
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
function getFormLevelErrorTarget(form: HTMLFormElement): HTMLElement | null {
|
|
@@ -30,7 +30,8 @@ async function loadGoogleMapsScript(apiKey: string): Promise<GoogleMaps> {
|
|
|
30
30
|
if (maps?.places?.PlaceAutocompleteElement) {
|
|
31
31
|
return existing as GoogleMaps;
|
|
32
32
|
}
|
|
33
|
-
const imp = maps as
|
|
33
|
+
const imp = maps as
|
|
34
|
+
{ importLibrary?: (name: string) => Promise<unknown> } | undefined;
|
|
34
35
|
if (typeof imp?.importLibrary === 'function') {
|
|
35
36
|
await imp.importLibrary('places');
|
|
36
37
|
}
|
|
@@ -44,7 +45,8 @@ async function loadGoogleMapsScript(apiKey: string): Promise<GoogleMaps> {
|
|
|
44
45
|
const existingScript = document.getElementById(SCRIPT_ID);
|
|
45
46
|
if (existingScript) {
|
|
46
47
|
const google = await ensureGlobal<GoogleMaps>('google', 10000);
|
|
47
|
-
const maps = google?.maps as
|
|
48
|
+
const maps = google?.maps as
|
|
49
|
+
{ importLibrary?: (name: string) => Promise<unknown> } | undefined;
|
|
48
50
|
if (typeof maps?.importLibrary === 'function') {
|
|
49
51
|
await maps.importLibrary('places');
|
|
50
52
|
}
|
|
@@ -80,7 +82,8 @@ async function loadGoogleMapsScript(apiKey: string): Promise<GoogleMaps> {
|
|
|
80
82
|
});
|
|
81
83
|
|
|
82
84
|
const google = await ready;
|
|
83
|
-
const maps = google?.maps as
|
|
85
|
+
const maps = google?.maps as
|
|
86
|
+
{ importLibrary?: (name: string) => Promise<unknown> } | undefined;
|
|
84
87
|
if (typeof maps?.importLibrary === 'function') {
|
|
85
88
|
await maps.importLibrary('places');
|
|
86
89
|
}
|
|
@@ -152,7 +155,13 @@ type AddressComponent = {
|
|
|
152
155
|
long_text?: string;
|
|
153
156
|
};
|
|
154
157
|
|
|
155
|
-
|
|
158
|
+
type GooglePostalAddress = {
|
|
159
|
+
addressLines?: unknown[];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function parseAddressComponents(
|
|
163
|
+
components: AddressComponent[],
|
|
164
|
+
): Record<string, string> {
|
|
156
165
|
const map = componentMap();
|
|
157
166
|
const out: Record<string, string> = {};
|
|
158
167
|
|
|
@@ -162,10 +171,11 @@ function parseAddressComponents(components: AddressComponent[]): Record<string,
|
|
|
162
171
|
if (!type || !map[type as keyof typeof map]) continue;
|
|
163
172
|
|
|
164
173
|
const fieldKey = map[type as keyof typeof map];
|
|
165
|
-
const val =
|
|
166
|
-
??
|
|
167
|
-
|
|
168
|
-
??
|
|
174
|
+
const val =
|
|
175
|
+
(comp as unknown as Record<string, string>)[fieldKey] ??
|
|
176
|
+
comp.short_text ??
|
|
177
|
+
comp.long_text ??
|
|
178
|
+
'';
|
|
169
179
|
|
|
170
180
|
out[type] = val;
|
|
171
181
|
}
|
|
@@ -173,17 +183,26 @@ function parseAddressComponents(components: AddressComponent[]): Record<string,
|
|
|
173
183
|
return out;
|
|
174
184
|
}
|
|
175
185
|
|
|
176
|
-
function buildAddressFromComponents(
|
|
186
|
+
export function buildAddressFromComponents(
|
|
187
|
+
formData: Record<string, string>,
|
|
188
|
+
postalAddress?: GooglePostalAddress,
|
|
189
|
+
): {
|
|
177
190
|
address1: string;
|
|
178
191
|
city: string;
|
|
179
192
|
state: string;
|
|
180
193
|
zip: string;
|
|
181
194
|
country: string;
|
|
182
195
|
} {
|
|
183
|
-
|
|
196
|
+
const postalAddressLines = postalAddress?.addressLines?.filter(
|
|
197
|
+
(line): line is string =>
|
|
198
|
+
typeof line === 'string' && line.trim() !== '',
|
|
199
|
+
);
|
|
200
|
+
let address1 = postalAddressLines?.join(', ') || '';
|
|
184
201
|
|
|
185
|
-
if (formData.street_number || formData.route) {
|
|
186
|
-
address1 = [formData.street_number, formData.route]
|
|
202
|
+
if (!address1 && (formData.street_number || formData.route)) {
|
|
203
|
+
address1 = [formData.street_number, formData.route]
|
|
204
|
+
.filter(Boolean)
|
|
205
|
+
.join(' ');
|
|
187
206
|
|
|
188
207
|
if (formData.subpremise) {
|
|
189
208
|
address1 = `${formData.subpremise}/${address1}`;
|
|
@@ -203,7 +222,8 @@ function hasCountryRestriction(options: Record<string, unknown>): boolean {
|
|
|
203
222
|
const componentRestrictions = options.componentRestrictions;
|
|
204
223
|
|
|
205
224
|
if (componentRestrictions && typeof componentRestrictions === 'object') {
|
|
206
|
-
const country = (componentRestrictions as { country?: unknown })
|
|
225
|
+
const country = (componentRestrictions as { country?: unknown })
|
|
226
|
+
.country;
|
|
207
227
|
|
|
208
228
|
if (Array.isArray(country) ? country.length > 0 : Boolean(country)) {
|
|
209
229
|
return true;
|
|
@@ -215,7 +235,9 @@ function hasCountryRestriction(options: Record<string, unknown>): boolean {
|
|
|
215
235
|
return Array.isArray(includedRegionCodes) && includedRegionCodes.length > 0;
|
|
216
236
|
}
|
|
217
237
|
|
|
218
|
-
export function buildGoogleAutocompleteOptions(
|
|
238
|
+
export function buildGoogleAutocompleteOptions(
|
|
239
|
+
provider: GoogleAddressProviderOptions,
|
|
240
|
+
): Record<string, unknown> {
|
|
219
241
|
const options = { types: ['geocode'], ...(provider.options || {}) };
|
|
220
242
|
const country = provider.countryDefaultValue?.trim().toLowerCase();
|
|
221
243
|
|
|
@@ -247,10 +269,13 @@ export const googleAddressModule = defineAddressModule<
|
|
|
247
269
|
},
|
|
248
270
|
mount: async ({ api, field, services, provider }) => {
|
|
249
271
|
const input = services.input.getAutocomplete();
|
|
250
|
-
const PlaceAutocompleteElement =
|
|
272
|
+
const PlaceAutocompleteElement =
|
|
273
|
+
api?.maps?.places?.PlaceAutocompleteElement;
|
|
251
274
|
|
|
252
275
|
if (!input || typeof PlaceAutocompleteElement !== 'function') {
|
|
253
|
-
console.warn(
|
|
276
|
+
console.warn(
|
|
277
|
+
'[formie] Google Places API not ready; address autocomplete skipped.',
|
|
278
|
+
);
|
|
254
279
|
return null;
|
|
255
280
|
}
|
|
256
281
|
|
|
@@ -303,20 +328,40 @@ export const googleAddressModule = defineAddressModule<
|
|
|
303
328
|
wrapper.appendChild(input);
|
|
304
329
|
|
|
305
330
|
const onSelect = async (ev: Event): Promise<void> => {
|
|
306
|
-
const e = ev as unknown as {
|
|
331
|
+
const e = ev as unknown as {
|
|
332
|
+
placePrediction?: {
|
|
333
|
+
toPlace: () => Promise<{
|
|
334
|
+
addressComponents?: unknown[];
|
|
335
|
+
formattedAddress?: string;
|
|
336
|
+
postalAddress?: GooglePostalAddress;
|
|
337
|
+
fetchFields: (opts: {
|
|
338
|
+
fields: string[];
|
|
339
|
+
}) => Promise<void>;
|
|
340
|
+
}>;
|
|
341
|
+
};
|
|
342
|
+
};
|
|
307
343
|
const pred = e.placePrediction;
|
|
308
344
|
|
|
309
345
|
if (!pred) return;
|
|
310
346
|
|
|
311
347
|
const place = await pred.toPlace();
|
|
312
|
-
await place.fetchFields({
|
|
348
|
+
await place.fetchFields({
|
|
349
|
+
fields: [
|
|
350
|
+
'addressComponents',
|
|
351
|
+
'formattedAddress',
|
|
352
|
+
'postalAddress',
|
|
353
|
+
],
|
|
354
|
+
});
|
|
313
355
|
|
|
314
356
|
if (!place.addressComponents) return;
|
|
315
357
|
|
|
316
358
|
const formData = parseAddressComponents(
|
|
317
359
|
place.addressComponents as AddressComponent[],
|
|
318
360
|
);
|
|
319
|
-
const address = buildAddressFromComponents(
|
|
361
|
+
const address = buildAddressFromComponents(
|
|
362
|
+
formData,
|
|
363
|
+
place.postalAddress,
|
|
364
|
+
);
|
|
320
365
|
|
|
321
366
|
setFieldValues(services, {
|
|
322
367
|
...address,
|
|
@@ -324,15 +369,19 @@ export const googleAddressModule = defineAddressModule<
|
|
|
324
369
|
});
|
|
325
370
|
|
|
326
371
|
field.dispatchEvent(
|
|
327
|
-
new CustomEvent(
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
372
|
+
new CustomEvent(
|
|
373
|
+
getAddressProviderEventName('google', 'populate'),
|
|
374
|
+
{
|
|
375
|
+
bubbles: true,
|
|
376
|
+
detail: {
|
|
377
|
+
addressProvider: 'google',
|
|
378
|
+
place,
|
|
379
|
+
formattedAddress: place.formattedAddress,
|
|
380
|
+
addressComponents: place.addressComponents,
|
|
381
|
+
postalAddress: place.postalAddress,
|
|
382
|
+
},
|
|
334
383
|
},
|
|
335
|
-
|
|
384
|
+
),
|
|
336
385
|
);
|
|
337
386
|
};
|
|
338
387
|
autocomplete.addEventListener('gmp-select', onSelect as EventListener);
|
|
@@ -342,9 +391,14 @@ export const googleAddressModule = defineAddressModule<
|
|
|
342
391
|
onCurrentLocation: async (position, { field, services, provider }) => {
|
|
343
392
|
const { latitude, longitude } = position.coords;
|
|
344
393
|
const form = services.form;
|
|
345
|
-
const actionUrl =
|
|
346
|
-
|
|
347
|
-
const
|
|
394
|
+
const actionUrl =
|
|
395
|
+
provider.geocodeEndpoint || form?.action || window.location.href;
|
|
396
|
+
const fieldHandle = field
|
|
397
|
+
.getAttribute('data-formie-field-handle')
|
|
398
|
+
?.trim();
|
|
399
|
+
const formHandle = (
|
|
400
|
+
form?.querySelector('[name="handle"]') as HTMLInputElement | null
|
|
401
|
+
)?.value?.trim();
|
|
348
402
|
|
|
349
403
|
if (!formHandle || !fieldHandle) return;
|
|
350
404
|
|
|
@@ -356,14 +410,18 @@ export const googleAddressModule = defineAddressModule<
|
|
|
356
410
|
formData.append('handle', formHandle);
|
|
357
411
|
formData.append('fieldHandle', fieldHandle);
|
|
358
412
|
|
|
359
|
-
const response = await browserRequest(
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
413
|
+
const response = await browserRequest(
|
|
414
|
+
actionUrl,
|
|
415
|
+
{
|
|
416
|
+
method: 'POST',
|
|
417
|
+
body: formData,
|
|
418
|
+
headers: {
|
|
419
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
420
|
+
Accept: 'application/json',
|
|
421
|
+
},
|
|
365
422
|
},
|
|
366
|
-
|
|
423
|
+
getFormBrowserRequestOptions(form),
|
|
424
|
+
);
|
|
367
425
|
|
|
368
426
|
const data = await response.json();
|
|
369
427
|
|
|
@@ -58,13 +58,6 @@ export const placeKitModule = defineAddressModule<
|
|
|
58
58
|
const picker = api(provider.apiKey, options as { target: HTMLInputElement });
|
|
59
59
|
|
|
60
60
|
picker.on('pick', (value, item: PlaceKitItem) => {
|
|
61
|
-
field.dispatchEvent(
|
|
62
|
-
new CustomEvent(getAddressProviderEventName('place-kit', 'populate'), {
|
|
63
|
-
bubbles: true,
|
|
64
|
-
detail: { addressProvider: 'place-kit', addressComponents: item },
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
|
|
68
61
|
let address1 = '';
|
|
69
62
|
|
|
70
63
|
if (item.street) {
|
|
@@ -87,6 +80,19 @@ export const placeKitModule = defineAddressModule<
|
|
|
87
80
|
services.input.setValue('state', item.administrative || '');
|
|
88
81
|
services.input.setValue('zip', typeof zip === 'string' ? zip : '');
|
|
89
82
|
services.input.setValue('country', countryValue || item.country || '');
|
|
83
|
+
|
|
84
|
+
// Notify dependent state controls after all address values are applied.
|
|
85
|
+
// Keep the provider value available while country options are loading.
|
|
86
|
+
field.dispatchEvent(
|
|
87
|
+
new CustomEvent(getAddressProviderEventName('place-kit', 'populate'), {
|
|
88
|
+
bubbles: true,
|
|
89
|
+
detail: {
|
|
90
|
+
addressProvider: 'place-kit',
|
|
91
|
+
addressComponents: item,
|
|
92
|
+
state: item.administrative || '',
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
90
96
|
});
|
|
91
97
|
|
|
92
98
|
return picker;
|