@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
|
@@ -16,7 +16,7 @@ type ComboboxSelect = HTMLSelectElement & {
|
|
|
16
16
|
|
|
17
17
|
type AddressCountryOptions = {
|
|
18
18
|
countryPreselectFromIp?: boolean;
|
|
19
|
-
|
|
19
|
+
allowedCountryCodes?: string[];
|
|
20
20
|
countryOptionValue?: 'short' | 'full';
|
|
21
21
|
countryFromIpAction?: string;
|
|
22
22
|
};
|
|
@@ -33,14 +33,14 @@ function resolveCountrySelectValue(
|
|
|
33
33
|
return countryCode.toUpperCase();
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function isCountryAllowed(countryCode: string,
|
|
37
|
-
if (!
|
|
36
|
+
function isCountryAllowed(countryCode: string, allowedCountryCodes: string[] = []): boolean {
|
|
37
|
+
if (!allowedCountryCodes.length) {
|
|
38
38
|
return true;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
const upperCode = countryCode.toUpperCase();
|
|
42
42
|
|
|
43
|
-
return
|
|
43
|
+
return allowedCountryCodes.some((allowed) => allowed.toUpperCase() === upperCode);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function optionExists(select: HTMLSelectElement, value: string): boolean {
|
|
@@ -91,7 +91,7 @@ async function preselectCountry(
|
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
if (!isCountryAllowed(response.countryCode, options.
|
|
94
|
+
if (!isCountryAllowed(response.countryCode, options.allowedCountryCodes)) {
|
|
95
95
|
debug.log('Detected country is not in the allowed list; skipping preselect.', {
|
|
96
96
|
countryCode: response.countryCode,
|
|
97
97
|
});
|
|
@@ -39,6 +39,7 @@ type AddressStateOptions = {
|
|
|
39
39
|
inputMode?: string;
|
|
40
40
|
hideWhenUnused?: boolean;
|
|
41
41
|
useSearchable?: boolean;
|
|
42
|
+
searchableOptionLimit?: number | null;
|
|
42
43
|
useDatalist?: boolean;
|
|
43
44
|
optionLabel?: 'name' | 'short';
|
|
44
45
|
optionValue?: 'name' | 'short';
|
|
@@ -58,6 +59,9 @@ type ComboboxSelect = HTMLSelectElement & {
|
|
|
58
59
|
|
|
59
60
|
type FieldState = {
|
|
60
61
|
addressRoot: HTMLElement;
|
|
62
|
+
isCpEdit: boolean;
|
|
63
|
+
initialCountry: string;
|
|
64
|
+
initialValue: string;
|
|
61
65
|
stateField: HTMLElement;
|
|
62
66
|
stateControl: AddressStateControl;
|
|
63
67
|
countryControl: HTMLInputElement | HTMLSelectElement | null;
|
|
@@ -105,7 +109,8 @@ function getStateControl(field: HTMLElement): AddressStateControl | null {
|
|
|
105
109
|
}
|
|
106
110
|
|
|
107
111
|
function getFieldLabel(field: HTMLElement): HTMLElement | null {
|
|
108
|
-
return field.querySelector('[data-formie-field-label]')
|
|
112
|
+
return field.querySelector('[data-formie-field-label]')
|
|
113
|
+
|| (field.querySelector('[data-formie-address-state-cp]') ? field.querySelector(':scope > .heading > label') : null);
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
function dispatchControlEvents(control: AddressStateControl): void {
|
|
@@ -130,13 +135,15 @@ function syncComboboxValue(control: AddressStateControl, value: string): void {
|
|
|
130
135
|
}
|
|
131
136
|
|
|
132
137
|
function setControlValue(control: AddressStateControl, value: string): void {
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
const previousValue = control.value;
|
|
139
|
+
control.value = value;
|
|
140
|
+
syncComboboxValue(control, control.value);
|
|
141
|
+
|
|
142
|
+
// A select rejects values outside its options. Dispatch only actual changes
|
|
143
|
+
// so an unmatched autofill value cannot recursively trigger reconciliation.
|
|
144
|
+
if (control.value !== previousValue) {
|
|
135
145
|
dispatchControlEvents(control);
|
|
136
|
-
return;
|
|
137
146
|
}
|
|
138
|
-
|
|
139
|
-
syncComboboxValue(control, value);
|
|
140
147
|
}
|
|
141
148
|
|
|
142
149
|
function syncAutofillAnchor(fieldState: FieldState, value: string): void {
|
|
@@ -181,6 +188,7 @@ function ensureAutofillAnchor(fieldState: FieldState): HTMLInputElement {
|
|
|
181
188
|
anchor.setAttribute('tabindex', '-1');
|
|
182
189
|
anchor.setAttribute('aria-hidden', 'true');
|
|
183
190
|
anchor.className = 'formie-sr-only';
|
|
191
|
+
anchor.hidden = fieldState.isCpEdit;
|
|
184
192
|
fieldState.addressRoot.appendChild(anchor);
|
|
185
193
|
fieldState.autofillAnchor = anchor;
|
|
186
194
|
}
|
|
@@ -204,14 +212,13 @@ function reconcileAutofillValue(fieldState: FieldState): void {
|
|
|
204
212
|
}
|
|
205
213
|
|
|
206
214
|
const subdivisions = fieldState.lastSubdivisions;
|
|
207
|
-
const resolved =
|
|
208
|
-
? (resolveOptionMatch(pending, subdivisions) ||
|
|
215
|
+
const resolved = fieldState.stateControl instanceof HTMLSelectElement
|
|
216
|
+
? (resolveOptionMatch(pending, subdivisions) || '')
|
|
209
217
|
: pending;
|
|
210
218
|
|
|
211
|
-
setControlValue(fieldState.stateControl, resolved);
|
|
212
|
-
syncComboboxValue(fieldState.stateControl, resolved);
|
|
213
219
|
syncAutofillAnchor(fieldState, resolved);
|
|
214
220
|
fieldState.pendingStateValue = resolved;
|
|
221
|
+
setControlValue(fieldState.stateControl, resolved);
|
|
215
222
|
}
|
|
216
223
|
|
|
217
224
|
function buildSubdivisionsUrl(action: string, country: string, optionLabel: string, optionValue: string): string {
|
|
@@ -320,6 +327,7 @@ function copyControlAttributes(source: AddressStateControl, target: AddressState
|
|
|
320
327
|
'data-formie-input-type',
|
|
321
328
|
'data-formie-input-error-state',
|
|
322
329
|
'data-formie-address-state-dynamic',
|
|
330
|
+
'data-formie-address-state-cp',
|
|
323
331
|
'data-formie-address-state-hide-when-unused',
|
|
324
332
|
'data-formie-address-state-use-searchable',
|
|
325
333
|
'data-formie-address-state-use-datalist',
|
|
@@ -340,7 +348,7 @@ function copyControlAttributes(source: AddressStateControl, target: AddressState
|
|
|
340
348
|
}
|
|
341
349
|
|
|
342
350
|
function applyTextThemeClasses(template: AddressStateControl, input: HTMLInputElement): void {
|
|
343
|
-
input.className = template.className;
|
|
351
|
+
input.className = template.hasAttribute('data-formie-address-state-cp') ? 'text fullwidth' : template.className;
|
|
344
352
|
}
|
|
345
353
|
|
|
346
354
|
function applySelectThemeClasses(template: AddressStateControl, select: HTMLSelectElement): void {
|
|
@@ -461,11 +469,16 @@ function setFieldVisibility(fieldState: FieldState, visible: boolean): void {
|
|
|
461
469
|
const { stateField, stateControl, required } = fieldState;
|
|
462
470
|
|
|
463
471
|
toggleThemeClasses(stateField, stateField, 'conditionalHidden', !visible);
|
|
464
|
-
|
|
472
|
+
// Use module-owned visibility: the generic hidden attribute unmounts modules,
|
|
473
|
+
// which would remove the country listener needed to show this field again.
|
|
474
|
+
stateField.toggleAttribute('data-formie-address-state-hidden', !visible);
|
|
475
|
+
|
|
465
476
|
|
|
466
477
|
if (!visible) {
|
|
467
478
|
stateControl.required = false;
|
|
468
|
-
|
|
479
|
+
// Keep the empty value in Craft's serialized form data so hiding the
|
|
480
|
+
// field during initialization does not count as an unsaved edit.
|
|
481
|
+
stateControl.disabled = !fieldState.isCpEdit;
|
|
469
482
|
return;
|
|
470
483
|
}
|
|
471
484
|
|
|
@@ -479,7 +492,7 @@ function ensureFetchingAnnouncement(fieldState: FieldState): HTMLElement {
|
|
|
479
492
|
}
|
|
480
493
|
|
|
481
494
|
const announcement = document.createElement('div');
|
|
482
|
-
announcement.className = 'formie-sr-only';
|
|
495
|
+
announcement.className = fieldState.isCpEdit ? 'visually-hidden' : 'formie-sr-only';
|
|
483
496
|
announcement.setAttribute('data-formie-address-state-fetching-announce', 'true');
|
|
484
497
|
announcement.setAttribute('aria-live', 'polite');
|
|
485
498
|
announcement.setAttribute('aria-atomic', 'true');
|
|
@@ -509,7 +522,7 @@ function showFetchingUI(fieldState: FieldState): void {
|
|
|
509
522
|
fieldState.stateControl.setAttribute('tabindex', '-1');
|
|
510
523
|
|
|
511
524
|
toggleThemeClasses(fieldState.stateField, fieldState.stateField, 'conditionalHidden', false);
|
|
512
|
-
fieldState.stateField.removeAttribute('data-formie-
|
|
525
|
+
fieldState.stateField.removeAttribute('data-formie-address-state-hidden');
|
|
513
526
|
fieldState.stateField.setAttribute('data-formie-address-state-skeleton-active', 'true');
|
|
514
527
|
|
|
515
528
|
const controlWrapper = fieldState.stateField.querySelector('[data-formie-field-control]');
|
|
@@ -546,7 +559,7 @@ function updateFieldLabel(stateField: HTMLElement, label: string): void {
|
|
|
546
559
|
return;
|
|
547
560
|
}
|
|
548
561
|
|
|
549
|
-
const required = labelEl.querySelector('[data-formie-field-required]');
|
|
562
|
+
const required = labelEl.querySelector('[data-formie-field-required], .required');
|
|
550
563
|
labelEl.textContent = label;
|
|
551
564
|
|
|
552
565
|
if (required) {
|
|
@@ -557,22 +570,23 @@ function updateFieldLabel(stateField: HTMLElement, label: string): void {
|
|
|
557
570
|
function initSearchableSelect(
|
|
558
571
|
select: HTMLSelectElement,
|
|
559
572
|
placeholder: string | null,
|
|
573
|
+
maxOptions: number | null | undefined,
|
|
560
574
|
): () => void {
|
|
561
575
|
select.setAttribute('data-formie-combobox-input', 'true');
|
|
562
576
|
|
|
563
577
|
dispatchFieldEvent(select, 'combobox', 'before-init', {
|
|
564
578
|
select,
|
|
565
|
-
options: { placeholder },
|
|
579
|
+
options: { placeholder, maxOptions },
|
|
566
580
|
});
|
|
567
581
|
|
|
568
582
|
const cleanup = initFormieCombobox(
|
|
569
583
|
select as Parameters<typeof initFormieCombobox>[0],
|
|
570
|
-
{ placeholder },
|
|
584
|
+
{ placeholder, maxOptions },
|
|
571
585
|
);
|
|
572
586
|
|
|
573
587
|
dispatchFieldEvent(select, 'combobox', 'after-init', {
|
|
574
588
|
combobox: (select as Parameters<typeof initFormieCombobox>[0])._formieTomSelect,
|
|
575
|
-
options: { placeholder },
|
|
589
|
+
options: { placeholder, maxOptions },
|
|
576
590
|
});
|
|
577
591
|
|
|
578
592
|
return cleanup;
|
|
@@ -595,6 +609,10 @@ async function applyCountryState(
|
|
|
595
609
|
const country = fieldState.countryControl?.value?.trim() || '';
|
|
596
610
|
stashPendingStateValue(fieldState);
|
|
597
611
|
const currentValue = getEffectiveStateValue(fieldState);
|
|
612
|
+
// Opening an existing submission must not discard a historical state value
|
|
613
|
+
// merely because current country metadata no longer includes it.
|
|
614
|
+
const preserveStoredValue = fieldState.isCpEdit && !!currentValue
|
|
615
|
+
&& country === fieldState.initialCountry && currentValue === fieldState.initialValue;
|
|
598
616
|
const fetchGeneration = ++fieldState.fetchGeneration;
|
|
599
617
|
|
|
600
618
|
fieldState.comboboxCleanup?.();
|
|
@@ -608,7 +626,7 @@ async function applyCountryState(
|
|
|
608
626
|
|
|
609
627
|
hideFetchingUI(fieldState);
|
|
610
628
|
|
|
611
|
-
if (hideWhenUnused) {
|
|
629
|
+
if (hideWhenUnused && !preserveStoredValue) {
|
|
612
630
|
setFieldVisibility(fieldState, false);
|
|
613
631
|
setControlValue(fieldState.stateControl, '');
|
|
614
632
|
syncAutofillAnchor(fieldState, '');
|
|
@@ -623,7 +641,7 @@ async function applyCountryState(
|
|
|
623
641
|
const textControl = createTextControl(fieldState.stateControl, currentValue);
|
|
624
642
|
fieldState.stateControl = replaceControl(fieldState.stateControl, textControl);
|
|
625
643
|
} else {
|
|
626
|
-
fieldState.stateControl.disabled =
|
|
644
|
+
fieldState.stateControl.disabled = !fieldState.isCpEdit && !preserveStoredValue;
|
|
627
645
|
fieldState.stateControl.placeholder = placeholder || fieldState.stateControl.placeholder;
|
|
628
646
|
}
|
|
629
647
|
|
|
@@ -644,14 +662,17 @@ async function applyCountryState(
|
|
|
644
662
|
return;
|
|
645
663
|
}
|
|
646
664
|
|
|
647
|
-
const subdivisions = response?.subdivisions || [];
|
|
665
|
+
const subdivisions = [...(response?.subdivisions || [])];
|
|
666
|
+
if (preserveStoredValue && subdivisions.length && !resolveOptionMatch(currentValue, subdivisions)) {
|
|
667
|
+
subdivisions.push({ label: currentValue, value: currentValue });
|
|
668
|
+
}
|
|
648
669
|
const administrativeAreaUsed = response?.administrativeAreaUsed ?? true;
|
|
649
670
|
const administrativeAreaLabel = response?.administrativeAreaLabel || 'State / Province';
|
|
650
671
|
fieldState.lastSubdivisions = subdivisions;
|
|
651
672
|
|
|
652
673
|
hideFetchingUI(fieldState);
|
|
653
674
|
|
|
654
|
-
if (hideWhenUnused && !administrativeAreaUsed) {
|
|
675
|
+
if (hideWhenUnused && !administrativeAreaUsed && !preserveStoredValue) {
|
|
655
676
|
setFieldVisibility(fieldState, false);
|
|
656
677
|
setControlValue(fieldState.stateControl, '');
|
|
657
678
|
syncAutofillAnchor(fieldState, '');
|
|
@@ -675,7 +696,7 @@ async function applyCountryState(
|
|
|
675
696
|
}
|
|
676
697
|
|
|
677
698
|
if (useSearchable) {
|
|
678
|
-
fieldState.comboboxCleanup = initSearchableSelect(select, placeholder);
|
|
699
|
+
fieldState.comboboxCleanup = initSearchableSelect(select, placeholder, options.searchableOptionLimit);
|
|
679
700
|
} else {
|
|
680
701
|
select.removeAttribute('data-formie-combobox-input');
|
|
681
702
|
}
|
|
@@ -755,6 +776,9 @@ function initAddressStateField(field: HTMLElement, options: AddressStateOptions)
|
|
|
755
776
|
|
|
756
777
|
const fieldState: FieldState = {
|
|
757
778
|
addressRoot,
|
|
779
|
+
isCpEdit: stateControl.hasAttribute('data-formie-address-state-cp'),
|
|
780
|
+
initialCountry: getCountryControl(addressRoot)?.value?.trim() || '',
|
|
781
|
+
initialValue: stateControl.value?.trim() || '',
|
|
758
782
|
stateField: field,
|
|
759
783
|
stateControl,
|
|
760
784
|
countryControl: getCountryControl(addressRoot),
|
|
@@ -772,6 +796,10 @@ function initAddressStateField(field: HTMLElement, options: AddressStateOptions)
|
|
|
772
796
|
lastSubdivisions: [],
|
|
773
797
|
};
|
|
774
798
|
|
|
799
|
+
const initialStateValue = stateControl.value;
|
|
800
|
+
const form = field.closest('form');
|
|
801
|
+
let disposed = false;
|
|
802
|
+
let resetTimer: number | null = null;
|
|
775
803
|
ensureAutofillAnchor(fieldState);
|
|
776
804
|
|
|
777
805
|
const refresh = (force = false) => {
|
|
@@ -830,6 +858,7 @@ function initAddressStateField(field: HTMLElement, options: AddressStateOptions)
|
|
|
830
858
|
|
|
831
859
|
if (isStateControl && !isAnchor) {
|
|
832
860
|
syncAutofillAnchor(fieldState, target.value);
|
|
861
|
+
fieldState.pendingStateValue = target.value;
|
|
833
862
|
}
|
|
834
863
|
|
|
835
864
|
stashPendingStateValue(fieldState);
|
|
@@ -891,12 +920,31 @@ function initAddressStateField(field: HTMLElement, options: AddressStateOptions)
|
|
|
891
920
|
syncAutofillAnchor(fieldState, stateValue);
|
|
892
921
|
|
|
893
922
|
void applyCountryState(fieldState, options).then(() => {
|
|
894
|
-
|
|
895
|
-
|
|
923
|
+
// Keep the normalized option value, which may differ from a provider's
|
|
924
|
+
// abbreviation (for example "CA" when options store "California").
|
|
925
|
+
reconcileAutofillValue(fieldState);
|
|
896
926
|
fieldState.lastCountry = fieldState.countryControl?.value?.trim() || '';
|
|
897
927
|
});
|
|
898
928
|
};
|
|
899
929
|
|
|
930
|
+
const onReset = (event: Event) => {
|
|
931
|
+
if (resetTimer !== null) window.clearTimeout(resetTimer);
|
|
932
|
+
// Enhanced selects finish their own reset in microtasks. Rebuild the
|
|
933
|
+
// dependent options after those controls have restored the country.
|
|
934
|
+
resetTimer = window.setTimeout(() => {
|
|
935
|
+
resetTimer = null;
|
|
936
|
+
if (event.defaultPrevented || disposed) return;
|
|
937
|
+
clearScheduledAutofillTimers(fieldState);
|
|
938
|
+
// The current select may contain another country's options. Keep the
|
|
939
|
+
// original value pending until the reset country's options are ready.
|
|
940
|
+
fieldState.stateControl.value = '';
|
|
941
|
+
fieldState.pendingStateValue = initialStateValue;
|
|
942
|
+
syncAutofillAnchor(fieldState, initialStateValue);
|
|
943
|
+
refresh(true);
|
|
944
|
+
}, 0);
|
|
945
|
+
};
|
|
946
|
+
form?.addEventListener('reset', onReset);
|
|
947
|
+
|
|
900
948
|
fieldState.countryControl?.addEventListener('change', onCountryChange);
|
|
901
949
|
fieldState.countryControl?.addEventListener('input', onCountryChange);
|
|
902
950
|
addressRoot.addEventListener('input', onAddressInput);
|
|
@@ -907,10 +955,15 @@ function initAddressStateField(field: HTMLElement, options: AddressStateOptions)
|
|
|
907
955
|
addressRoot.addEventListener('formie:address:loqate:populate', onProviderPopulate);
|
|
908
956
|
addressRoot.addEventListener('formie:address:place-kit:populate', onProviderPopulate);
|
|
909
957
|
|
|
910
|
-
|
|
958
|
+
// Apply the empty-country visibility state on first mount as well.
|
|
959
|
+
refresh(true);
|
|
911
960
|
scheduleAutofillSweep(fieldState, options);
|
|
912
961
|
|
|
913
962
|
return () => {
|
|
963
|
+
disposed = true;
|
|
964
|
+
if (resetTimer !== null) window.clearTimeout(resetTimer);
|
|
965
|
+
fieldState.fetchGeneration++;
|
|
966
|
+
form?.removeEventListener('reset', onReset);
|
|
914
967
|
clearScheduledAutofillTimers(fieldState);
|
|
915
968
|
hideFetchingUI(fieldState);
|
|
916
969
|
fieldState.skeletonEl?.remove();
|
|
@@ -6,6 +6,8 @@ import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/sh
|
|
|
6
6
|
import { ensureModuleStyles } from '#modules/styles';
|
|
7
7
|
import { getThemeClasses } from '#theme/theme-classes';
|
|
8
8
|
import { createDebug } from '#utils/debug';
|
|
9
|
+
import { t } from '#utils/i18n';
|
|
10
|
+
import { VALIDATION_SKIP_ATTR } from '#validation/skip';
|
|
9
11
|
|
|
10
12
|
const SELECT_SELECTOR = 'select[data-formie-combobox-input]';
|
|
11
13
|
const MODULE_ID = 'combobox';
|
|
@@ -20,6 +22,8 @@ const NATIVE_THEME_CLASSES = [
|
|
|
20
22
|
|
|
21
23
|
export type FormieComboboxOptions = {
|
|
22
24
|
multiple?: boolean;
|
|
25
|
+
maxOptions?: number | null;
|
|
26
|
+
allowClear?: boolean;
|
|
23
27
|
placeholder?: string | null;
|
|
24
28
|
};
|
|
25
29
|
|
|
@@ -86,8 +90,23 @@ export function initFormieCombobox(select: SelectElement, options: FormieCombobo
|
|
|
86
90
|
|
|
87
91
|
const mergedOptions: Record<string, unknown> = {
|
|
88
92
|
create: false,
|
|
93
|
+
// Limit rendered results while retaining the full option list for searching.
|
|
94
|
+
maxOptions: options.maxOptions === undefined ? 100 : options.maxOptions,
|
|
89
95
|
maxItems: multiple ? null : 1,
|
|
90
|
-
plugins: multiple ? ['remove_button'] :
|
|
96
|
+
plugins: multiple ? ['remove_button'] : options.allowClear ? {
|
|
97
|
+
remove_button: {
|
|
98
|
+
// Use a native button so clearing a single answer works with keyboard input too.
|
|
99
|
+
html: () => {
|
|
100
|
+
const button = document.createElement('button');
|
|
101
|
+
button.type = 'button';
|
|
102
|
+
button.className = 'remove';
|
|
103
|
+
button.title = t('Clear selection');
|
|
104
|
+
button.setAttribute('aria-label', button.title);
|
|
105
|
+
button.textContent = '×';
|
|
106
|
+
return button;
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
} : [],
|
|
91
110
|
hideSelected: multiple ? true : null,
|
|
92
111
|
clearAfterSelect: multiple,
|
|
93
112
|
closeAfterSelect: !multiple,
|
|
@@ -116,6 +135,8 @@ export function initFormieCombobox(select: SelectElement, options: FormieCombobo
|
|
|
116
135
|
});
|
|
117
136
|
|
|
118
137
|
const instance = new TomSelect(select, mergedOptions) as TomSelectInstance;
|
|
138
|
+
// The search query is temporary; the native select carries the field value.
|
|
139
|
+
instance.control_input.setAttribute(VALIDATION_SKIP_ATTR, '');
|
|
119
140
|
removeEmptyOptionFromCombobox(instance);
|
|
120
141
|
cleanLeakedThemeClasses(instance.wrapper, select);
|
|
121
142
|
|
|
@@ -47,9 +47,10 @@ function clearConditionNodeValues(node: Element): void {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
function setVisibilityState(node: Element, hidden: boolean): boolean {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const
|
|
50
|
+
// Navigation owns page-hidden. A matching condition must not reveal an
|
|
51
|
+
// inactive page, or clear its values just because the visitor moved on.
|
|
52
|
+
const hiddenAttr = CONDITIONAL_HIDDEN_ATTR;
|
|
53
|
+
const hiddenClasses = getThemeClasses(node, 'conditionalHidden');
|
|
53
54
|
const wasHidden = node.hasAttribute(hiddenAttr);
|
|
54
55
|
|
|
55
56
|
if (hidden) {
|
|
@@ -190,9 +191,7 @@ function clearCpMutedState(node: Element): void {
|
|
|
190
191
|
|
|
191
192
|
function clearHideState(node: Element): void {
|
|
192
193
|
node.removeAttribute(CONDITIONAL_HIDDEN_ATTR);
|
|
193
|
-
node.removeAttribute(PAGE_HIDDEN_ATTR);
|
|
194
194
|
node.classList.remove(...getThemeClasses(node, 'conditionalHidden'));
|
|
195
|
-
node.classList.remove(...getThemeClasses(node, 'pageHidden'));
|
|
196
195
|
}
|
|
197
196
|
|
|
198
197
|
function syncAncestorRows(node: Element): void {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import type { ConditionEntry, ConditionInput } from '#modules/fields/conditions/types';
|
|
11
11
|
import { getConditionInputEventNames } from '#modules/fields/conditions/values';
|
|
12
12
|
import { createDebug } from '#utils/debug';
|
|
13
|
+
import { getFormStateEventName } from '#utils/event-names';
|
|
13
14
|
|
|
14
15
|
const debug = createDebug('conditions');
|
|
15
16
|
|
|
@@ -212,8 +213,13 @@ export const conditionsModule: BrowserModuleDefinition = {
|
|
|
212
213
|
};
|
|
213
214
|
|
|
214
215
|
ctx.form.addEventListener('reset', resetHandler);
|
|
216
|
+
// Canonical resets have already restored values. Settle conditions
|
|
217
|
+
// before the unload guard captures the form's new clean state.
|
|
218
|
+
const stateResetEvent = getFormStateEventName('reset');
|
|
219
|
+
ctx.form.addEventListener(stateResetEvent, evaluateAll);
|
|
215
220
|
sourceUnbinds.push(() => {
|
|
216
221
|
ctx.form?.removeEventListener('reset', resetHandler);
|
|
222
|
+
ctx.form?.removeEventListener(stateResetEvent, evaluateAll);
|
|
217
223
|
});
|
|
218
224
|
|
|
219
225
|
// CP status menu (etc.) updates `data-formie-submission` and fires this event.
|
|
@@ -294,6 +300,9 @@ export const conditionsModule: BrowserModuleDefinition = {
|
|
|
294
300
|
});
|
|
295
301
|
|
|
296
302
|
rebuild();
|
|
303
|
+
// Initial visibility and disabled state belong to module setup. Finish
|
|
304
|
+
// them before the form captures its clean baseline or announces readiness.
|
|
305
|
+
evaluateAll();
|
|
297
306
|
|
|
298
307
|
await ctx.emit('formie:module:conditions:init', {
|
|
299
308
|
count: entries.length,
|
|
@@ -90,8 +90,9 @@ function normalizeOffsetDate(input: string | null | undefined, type: 'min' | 'ma
|
|
|
90
90
|
return null;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
const absoluteDate = parseSubmissionDate(input);
|
|
94
|
+
if (absoluteDate) {
|
|
95
|
+
return absoluteDate;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
// Form config may express relative min/max bounds like "+2 weeks", which are
|
|
@@ -209,7 +210,9 @@ function formatSubmissionDate(date: Date, options: DatePickerOptions): string {
|
|
|
209
210
|
}
|
|
210
211
|
|
|
211
212
|
function parseSubmissionDate(value: string): Date | null {
|
|
212
|
-
|
|
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);
|
|
213
216
|
|
|
214
217
|
if (Number.isNaN(parsed.getTime())) {
|
|
215
218
|
return null;
|
|
@@ -3,6 +3,7 @@ import fileUploadCss from '#theme-css/fields/_file.css?inline';
|
|
|
3
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
|
+
import { appendFormCsrfToFormData } from '#utils/csrf';
|
|
6
7
|
import { getFieldModuleEventName, getFileUploadEventName, getFormStateEventName } from '#utils/event-names';
|
|
7
8
|
import { requestJson } from '#utils/http';
|
|
8
9
|
|
|
@@ -30,6 +31,9 @@ type UploadedAsset = {
|
|
|
30
31
|
assetId: number | null;
|
|
31
32
|
filename: string;
|
|
32
33
|
url?: string | null;
|
|
34
|
+
uploadUid?: string | null;
|
|
35
|
+
attachToken?: string | null;
|
|
36
|
+
viewToken?: string | null;
|
|
33
37
|
};
|
|
34
38
|
|
|
35
39
|
type FileUploadHydrateResponse = {
|
|
@@ -151,6 +155,9 @@ function normalizeUploadedAsset(candidate: unknown): UploadedAsset | null {
|
|
|
151
155
|
assetId,
|
|
152
156
|
filename,
|
|
153
157
|
url,
|
|
158
|
+
uploadUid: toTrimmedString(candidate.uploadUid) || null,
|
|
159
|
+
attachToken: toTrimmedString(candidate.attachToken) || null,
|
|
160
|
+
viewToken: toTrimmedString(candidate.viewToken ?? candidate.uploadToken) || null,
|
|
154
161
|
};
|
|
155
162
|
}
|
|
156
163
|
|
|
@@ -190,6 +197,9 @@ function readUploadedAssetsFromHiddenInputs(field: HTMLElement, input: HTMLInput
|
|
|
190
197
|
return {
|
|
191
198
|
assetId: toPositiveInt(hiddenInput.getAttribute(HIDDEN_INPUT_VALUE_ATTR)) ?? toPositiveInt(hiddenInput.value),
|
|
192
199
|
filename: '',
|
|
200
|
+
viewToken: hiddenInput.getAttribute('data-formie-file-upload-token'),
|
|
201
|
+
uploadUid: hiddenInput.name.endsWith('[uploadUid]') ? hiddenInput.value : null,
|
|
202
|
+
attachToken: getHiddenInputs(field).find((entry) => entry.name === hiddenInput.name.replace(/\[uploadUid\]$/, '[attachToken]'))?.value,
|
|
193
203
|
};
|
|
194
204
|
}).filter((asset) => {
|
|
195
205
|
return asset.assetId !== null;
|
|
@@ -203,6 +213,7 @@ function mergeUploadedAssets(assetIds: UploadedAsset[], summaryItems: UploadedAs
|
|
|
203
213
|
|
|
204
214
|
return assetIds.map((asset, index) => {
|
|
205
215
|
return {
|
|
216
|
+
...asset,
|
|
206
217
|
assetId: asset.assetId,
|
|
207
218
|
filename: summaryItems[index]?.filename || '',
|
|
208
219
|
};
|
|
@@ -248,8 +259,8 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
248
259
|
});
|
|
249
260
|
|
|
250
261
|
validator.addValidator('fileSizeMinLimit', ({ input }) => {
|
|
251
|
-
const
|
|
252
|
-
const sizeBytes =
|
|
262
|
+
const maxFileSize = parseFloat(input.getAttribute('data-formie-size-min-limit') || '');
|
|
263
|
+
const sizeBytes = maxFileSize * 1000 * 1000;
|
|
253
264
|
|
|
254
265
|
if (input.type !== 'file' || !sizeBytes || !('files' in input) || !input.files?.length) {
|
|
255
266
|
return true;
|
|
@@ -266,8 +277,8 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
266
277
|
});
|
|
267
278
|
|
|
268
279
|
validator.addValidator('fileSizeMaxLimit', ({ input }) => {
|
|
269
|
-
const
|
|
270
|
-
const sizeBytes =
|
|
280
|
+
const maxFileSize = parseFloat(input.getAttribute('data-formie-size-max-limit') || '');
|
|
281
|
+
const sizeBytes = maxFileSize * 1000 * 1000;
|
|
271
282
|
|
|
272
283
|
if (input.type !== 'file' || !sizeBytes || !('files' in input) || !input.files?.length) {
|
|
273
284
|
return true;
|
|
@@ -366,12 +377,26 @@ function updateUploadedAssetInputs(field: HTMLElement, input: HTMLInputElement,
|
|
|
366
377
|
|
|
367
378
|
const hiddenInput = document.createElement('input');
|
|
368
379
|
hiddenInput.type = 'hidden';
|
|
369
|
-
|
|
370
|
-
hiddenInput.
|
|
380
|
+
const hasCapability = asset.uploadUid && asset.attachToken;
|
|
381
|
+
hiddenInput.name = `${baseName}[${index}][${hasCapability ? 'uploadUid' : 'assetId'}]`;
|
|
382
|
+
hiddenInput.value = hasCapability ? asset.uploadUid! : String(asset.assetId);
|
|
371
383
|
hiddenInput.setAttribute(HIDDEN_INPUT_ENTRY_ATTR, 'true');
|
|
372
384
|
hiddenInput.setAttribute(HIDDEN_INPUT_VALUE_ATTR, String(asset.assetId));
|
|
385
|
+
if (asset.viewToken) {
|
|
386
|
+
hiddenInput.setAttribute('data-formie-file-upload-token', asset.viewToken);
|
|
387
|
+
}
|
|
373
388
|
insertionPoint.insertAdjacentElement('afterend', hiddenInput);
|
|
374
389
|
insertionPoint = hiddenInput;
|
|
390
|
+
|
|
391
|
+
if (hasCapability) {
|
|
392
|
+
const tokenInput = document.createElement('input');
|
|
393
|
+
tokenInput.type = 'hidden';
|
|
394
|
+
tokenInput.name = `${baseName}[${index}][attachToken]`;
|
|
395
|
+
tokenInput.value = asset.attachToken!;
|
|
396
|
+
tokenInput.setAttribute(HIDDEN_INPUT_ENTRY_ATTR, 'true');
|
|
397
|
+
insertionPoint.insertAdjacentElement('afterend', tokenInput);
|
|
398
|
+
insertionPoint = tokenInput;
|
|
399
|
+
}
|
|
375
400
|
});
|
|
376
401
|
|
|
377
402
|
input.value = '';
|
|
@@ -391,7 +416,7 @@ function getFormHandle(form: HTMLFormElement | null): string {
|
|
|
391
416
|
return form.getAttribute('data-formie-handle')?.trim() || '';
|
|
392
417
|
}
|
|
393
418
|
|
|
394
|
-
function buildHydrateFormData(field: HTMLElement, input: HTMLInputElement,
|
|
419
|
+
function buildHydrateFormData(field: HTMLElement, input: HTMLInputElement, assets: UploadedAsset[]): FormData {
|
|
395
420
|
const form = input.form;
|
|
396
421
|
const body = new FormData();
|
|
397
422
|
const handle = getFormHandle(form);
|
|
@@ -405,16 +430,27 @@ function buildHydrateFormData(field: HTMLElement, input: HTMLInputElement, asset
|
|
|
405
430
|
body.append('fieldHandle', fieldHandle);
|
|
406
431
|
}
|
|
407
432
|
|
|
408
|
-
const
|
|
433
|
+
const passthroughNames = ['submissionUid', 'resumeToken', 'submissionEditToken', 'siteId', 'renderId', 'draftContext', 'draftContextToken'] as const;
|
|
409
434
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
435
|
+
passthroughNames.forEach((name) => {
|
|
436
|
+
const contextInput = form?.querySelector(`input[name="${name}"]`);
|
|
437
|
+
|
|
438
|
+
if (contextInput instanceof HTMLInputElement && contextInput.value.trim()) {
|
|
439
|
+
body.append(name, contextInput.value.trim());
|
|
440
|
+
}
|
|
441
|
+
});
|
|
413
442
|
|
|
414
|
-
|
|
415
|
-
|
|
443
|
+
assets.forEach((asset) => {
|
|
444
|
+
if (asset.assetId) {
|
|
445
|
+
body.append('assetIds[]', String(asset.assetId));
|
|
446
|
+
if (asset.viewToken) {
|
|
447
|
+
body.append(`uploadTokens[${asset.assetId}]`, asset.viewToken);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
416
450
|
});
|
|
417
451
|
|
|
452
|
+
appendFormCsrfToFormData(body, form);
|
|
453
|
+
|
|
418
454
|
return body;
|
|
419
455
|
}
|
|
420
456
|
|
|
@@ -433,7 +469,7 @@ async function hydrateUploadedAssets(field: HTMLElement, input: HTMLInputElement
|
|
|
433
469
|
const response = await requestJson<FileUploadHydrateResponse>(hydrateEndpoint, {
|
|
434
470
|
profile: field.closest('form')?.dataset.formieRequestProfile as import('@verbb/formie-core').BrowserRequestOptions['profile'],
|
|
435
471
|
method: 'POST',
|
|
436
|
-
body: buildHydrateFormData(field, input,
|
|
472
|
+
body: buildHydrateFormData(field, input, uploadedAssets),
|
|
437
473
|
});
|
|
438
474
|
const hydratedAssets = normalizeUploadedAssets(response.assets);
|
|
439
475
|
|
|
@@ -452,6 +488,7 @@ async function hydrateUploadedAssets(field: HTMLElement, input: HTMLInputElement
|
|
|
452
488
|
|
|
453
489
|
const hydratedAsset = hydratedMap.get(asset.assetId)!;
|
|
454
490
|
return {
|
|
491
|
+
...asset,
|
|
455
492
|
assetId: asset.assetId,
|
|
456
493
|
filename: hydratedAsset.filename || asset.filename,
|
|
457
494
|
url: hydratedAsset.url ?? asset.url,
|
|
@@ -480,24 +517,24 @@ function getUploadedAssetsFromEvent(event: Event, matcher: FileUploadEventMatche
|
|
|
480
517
|
|
|
481
518
|
if (detailInputKey !== '' && detailInputKey === matcher.inputKey) {
|
|
482
519
|
matchedByInputKey = true;
|
|
483
|
-
candidates.push(detail.assets, detail.assetIds, detail.data);
|
|
520
|
+
candidates.push(detail.uploadedAssets, detail.assets, detail.assetIds, detail.data);
|
|
484
521
|
}
|
|
485
522
|
|
|
486
523
|
if (detailInputName !== '' && detailInputName === matcher.inputName) {
|
|
487
524
|
matchedByInputName = true;
|
|
488
|
-
candidates.push(detail.assets, detail.assetIds, detail.data);
|
|
525
|
+
candidates.push(detail.uploadedAssets, detail.assets, detail.assetIds, detail.data);
|
|
489
526
|
}
|
|
490
527
|
|
|
491
528
|
if (detailFieldHandle === matcher.fieldHandle) {
|
|
492
529
|
matchedByFieldHandle = true;
|
|
493
|
-
candidates.push(detail.assets, detail.assetIds, detail.data);
|
|
530
|
+
candidates.push(detail.uploadedAssets, detail.assets, detail.assetIds, detail.data);
|
|
494
531
|
}
|
|
495
532
|
|
|
496
533
|
if (nestedData) {
|
|
497
534
|
const nestedAssetCandidates = [
|
|
535
|
+
nestedData.uploadedAssets,
|
|
498
536
|
nestedData.assets,
|
|
499
537
|
nestedData.assetIds,
|
|
500
|
-
nestedData.uploadedAssets,
|
|
501
538
|
nestedData.data,
|
|
502
539
|
];
|
|
503
540
|
|