@verbb/formie-browser 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/dist/chunks/{address-country-Dn71jroa.js → address-country-BcEQKw0T.js} +1 -1
- package/dist/chunks/{address-finder-O49uDRoR.js → address-finder-BWGINCSA.js} +1 -1
- package/dist/chunks/{address-state-ByYatUur.js → address-state-DI3ndhMU.js} +98 -88
- package/dist/chunks/{api-DYYsVIub.js → api-BK9b340y.js} +16 -16
- package/dist/chunks/{api-D6kJ7kXZ.js → api-Dxl_CKw-.js} +30 -25
- package/dist/chunks/{async-nPFRNQ06.js → async-2GdXNFCE.js} +18 -1
- package/dist/chunks/{bpoint-D0RKFM4F.js → bpoint-BwtuyN_O.js} +1 -1
- package/dist/chunks/{captcha-eu-DERJbXnH.js → captcha-eu-DWLoQFKB.js} +2 -2
- package/dist/chunks/{combobox-CWNdx4Fk.js → combobox-Ba08_cHL.js} +259 -258
- package/dist/chunks/{conditions-DqNHLTbN.js → conditions-Ceeg6ReZ.js} +113 -110
- package/dist/chunks/{date-picker-RUnevF7l.js → date-picker-DaPvMrop.js} +12 -11
- package/dist/chunks/{eway-or6dtjoT.js → eway-uw0DzU-L.js} +3 -3
- package/dist/chunks/{file-upload-Bm-TXyNB.js → file-upload-B8_mH012.js} +28 -16
- package/dist/chunks/{friendly-captcha-v1-GFqCt1Mh.js → friendly-captcha-v1-CVXaXgDO.js} +1 -1
- package/dist/chunks/{friendly-captcha-v2-WoW246JG.js → friendly-captcha-v2-DkeIVTKI.js} +1 -1
- package/dist/chunks/{go-cardless-DzJ-0FKg.js → go-cardless-c4wLJdOt.js} +1 -1
- package/dist/chunks/{google-address-C1P1nTDM.js → google-address-JHYvjwgN.js} +13 -8
- package/dist/chunks/{hcaptcha-D0T8aIHz.js → hcaptcha-Dt3ELLlP.js} +2 -2
- package/dist/chunks/{loqate-DD1S7PY9.js → loqate-m51iPyO1.js} +1 -1
- package/dist/chunks/{mollie-DG8Ju83x.js → mollie-Bn0PX173.js} +1 -1
- package/dist/chunks/{moneris-C8m3Fvqu.js → moneris-BRbUvI4t.js} +1 -1
- package/dist/chunks/{opayo-B3EvrVxw.js → opayo-BwWdInEP.js} +2 -2
- package/dist/chunks/{paddle-BIYR9Um3.js → paddle-CympHVvd.js} +2 -2
- package/dist/chunks/{paypal-B_yxIo2d.js → paypal-h75h6wae.js} +2 -2
- package/dist/chunks/{payway-CeOzS0sL.js → payway-C9rHTCC_.js} +2 -2
- package/dist/chunks/{phone-country-bAqje17P.js → phone-country-B8fv-L5_.js} +1 -1
- package/dist/chunks/{place-kit-BE5gi3vo.js → place-kit-BnZGQ-L1.js} +8 -8
- package/dist/chunks/{recaptcha-enterprise-CruXddZq.js → recaptcha-enterprise-CUs8BO6V.js} +2 -2
- package/dist/chunks/{recaptcha-shared-AQRMDMme.js → recaptcha-shared-DTbTw-MF.js} +2 -2
- package/dist/chunks/{recaptcha-v2-checkbox-CCnJYCnG.js → recaptcha-v2-checkbox-P1McW4Pv.js} +2 -2
- package/dist/chunks/{recaptcha-v2-invisible-BuVqRRYn.js → recaptcha-v2-invisible-FGDqozSP.js} +2 -2
- package/dist/chunks/{recaptcha-v3-CloqhO2A.js → recaptcha-v3-CflUHeZD.js} +2 -2
- package/dist/chunks/{repeater-DwE27jI1.js → repeater-CyhDbozO.js} +47 -33
- package/dist/chunks/{scripts-CbQ7agX3.js → scripts-DrccjrFv.js} +1 -1
- package/dist/chunks/skip-CEW89ExY.js +7 -0
- package/dist/chunks/{snaptcha-C04kNGYQ.js → snaptcha-C3QLYxFT.js} +1 -1
- package/dist/chunks/{square-DEzxv9D9.js → square-C1z5DPVw.js} +2 -2
- package/dist/chunks/{stripe-CVPQGDz3.js → stripe-Dmkdkq1y.js} +2 -2
- package/dist/chunks/{summary-BF_8mrXI.js → summary-Bn-g5SDx.js} +1 -1
- package/dist/chunks/{table-DnIBdeKF.js → table-j7MlLLCc.js} +1 -1
- package/dist/chunks/{turnstile-n9DNyPxi.js → turnstile-ptihQfLl.js} +2 -2
- package/dist/chunks/{upload-manager-BbiUosZq.js → upload-manager-BW7l-BhF.js} +248 -236
- package/dist/index.js +548 -535
- package/dist/js/core/create-formie-client.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/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/fields/address-state.d.ts.map +1 -1
- 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/repeater.d.ts.map +1 -1
- package/dist/js/modules/fields/upload-manager.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/validation/rules/required.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/js/core/create-formie-client.ts +17 -10
- package/src/js/core/submit-result-state.ts +17 -0
- package/src/js/core/submit-result-ui.ts +4 -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/fields/address-country.ts +5 -5
- package/src/js/modules/fields/address-state.ts +74 -23
- package/src/js/modules/fields/combobox.ts +3 -0
- package/src/js/modules/fields/conditions/effects.ts +4 -5
- package/src/js/modules/fields/conditions.ts +6 -0
- package/src/js/modules/fields/date-picker.ts +6 -3
- package/src/js/modules/fields/file-upload.ts +44 -14
- package/src/js/modules/fields/phone-country.ts +2 -2
- package/src/js/modules/fields/repeater.ts +55 -4
- package/src/js/modules/fields/upload-manager.ts +54 -18
- package/src/js/utils/async.ts +34 -0
- package/src/js/validation/rules/required.ts +12 -0
- package/src/js/validation/validator.ts +14 -2
|
@@ -42,9 +42,9 @@ type UploadManagerOptions = {
|
|
|
42
42
|
uploadEndpoint?: string;
|
|
43
43
|
deleteEndpoint?: string;
|
|
44
44
|
hydrateEndpoint?: string;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
maxFiles?: number | null;
|
|
46
|
+
minFileSize?: number | null;
|
|
47
|
+
maxFileSize?: number | null;
|
|
48
48
|
accept?: string | null;
|
|
49
49
|
restrictFiles?: boolean;
|
|
50
50
|
};
|
|
@@ -508,6 +508,19 @@ function markUploadComplete(listItem: HTMLElement): void {
|
|
|
508
508
|
}, UPLOAD_COMPLETE_DISPLAY_MS);
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
function markUploadFailed(listItem: HTMLElement): void {
|
|
512
|
+
const { track, bar, label } = getProgressElements(listItem);
|
|
513
|
+
|
|
514
|
+
listItem.classList.add('is-error');
|
|
515
|
+
listItem.classList.remove('is-uploading', 'is-upload-complete', 'is-processing', 'is-preparing');
|
|
516
|
+
track?.setAttribute('data-indeterminate', 'false');
|
|
517
|
+
bar?.setAttribute('data-progress', 'error');
|
|
518
|
+
|
|
519
|
+
if (label) {
|
|
520
|
+
label.textContent = 'Upload failed';
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
511
524
|
function createSortButton(direction: 'up' | 'down'): HTMLButtonElement {
|
|
512
525
|
const label = direction === 'up' ? 'Move file up' : 'Move file down';
|
|
513
526
|
const button = document.createElement('button');
|
|
@@ -763,9 +776,9 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
763
776
|
const uploadEndpoint = getEndpoint(dropzone, options.uploadEndpoint, 'data-formie-file-upload-upload-endpoint', '/actions/formie/file-upload/upload');
|
|
764
777
|
const deleteEndpoint = getEndpoint(dropzone, options.deleteEndpoint, 'data-formie-file-upload-delete-endpoint', '/actions/formie/file-upload/delete');
|
|
765
778
|
const hydrateEndpoint = getEndpoint(dropzone, options.hydrateEndpoint, 'data-formie-file-upload-hydrate-endpoint', '/actions/formie/file-upload/hydrate');
|
|
766
|
-
const
|
|
767
|
-
const
|
|
768
|
-
const
|
|
779
|
+
const maxFiles = options.maxFiles ?? toPositiveInt(dropzone.getAttribute('data-formie-file-limit'));
|
|
780
|
+
const maxFileSize = options.maxFileSize ?? parseFloat(dropzone.getAttribute('data-formie-size-max-limit') || '');
|
|
781
|
+
const minFileSize = options.minFileSize ?? parseFloat(dropzone.getAttribute('data-formie-size-min-limit') || '');
|
|
769
782
|
const accept = options.accept ?? dropzone.getAttribute('accept') ?? browseInput.accept;
|
|
770
783
|
const allowedFileTypes = parseAcceptTypes(accept);
|
|
771
784
|
|
|
@@ -776,16 +789,16 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
776
789
|
allowedFileTypes?: string[] | null;
|
|
777
790
|
} = {};
|
|
778
791
|
|
|
779
|
-
if (
|
|
780
|
-
restrictions.maxNumberOfFiles =
|
|
792
|
+
if (maxFiles) {
|
|
793
|
+
restrictions.maxNumberOfFiles = maxFiles;
|
|
781
794
|
}
|
|
782
795
|
|
|
783
|
-
if (
|
|
784
|
-
restrictions.maxFileSize =
|
|
796
|
+
if (maxFileSize) {
|
|
797
|
+
restrictions.maxFileSize = maxFileSize * 1000 * 1000;
|
|
785
798
|
}
|
|
786
799
|
|
|
787
|
-
if (
|
|
788
|
-
restrictions.minFileSize =
|
|
800
|
+
if (minFileSize) {
|
|
801
|
+
restrictions.minFileSize = minFileSize * 1000 * 1000;
|
|
789
802
|
}
|
|
790
803
|
|
|
791
804
|
if (allowedFileTypes) {
|
|
@@ -797,6 +810,27 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
797
810
|
restrictions,
|
|
798
811
|
});
|
|
799
812
|
|
|
813
|
+
const selectionError = document.createElement('div');
|
|
814
|
+
selectionError.className = 'formie-upload-manager-error';
|
|
815
|
+
selectionError.setAttribute('role', 'alert');
|
|
816
|
+
selectionError.hidden = true;
|
|
817
|
+
root.append(selectionError);
|
|
818
|
+
|
|
819
|
+
// Uppy stores restriction messages for UI plugins; this custom UI must render them.
|
|
820
|
+
uppy.on('info-visible', () => {
|
|
821
|
+
const messages = uppy.getState().info.filter((info) => info.type === 'error');
|
|
822
|
+
|
|
823
|
+
if (messages.length) {
|
|
824
|
+
selectionError.textContent = [...new Set(messages.map((info) => info.message))].join(' ');
|
|
825
|
+
selectionError.hidden = false;
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
const clearSelectionError = () => {
|
|
830
|
+
selectionError.textContent = '';
|
|
831
|
+
selectionError.hidden = true;
|
|
832
|
+
};
|
|
833
|
+
|
|
800
834
|
// Resolve once for Uppy's allow-list; per-request meta/header refresh
|
|
801
835
|
// still re-reads the live form token (including custom csrfTokenName).
|
|
802
836
|
const initialCsrf = getFormCsrfToken(resolvedForm);
|
|
@@ -886,11 +920,11 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
886
920
|
};
|
|
887
921
|
|
|
888
922
|
const remainingUploadCapacity = (): number | null => {
|
|
889
|
-
if (!
|
|
923
|
+
if (!maxFiles) {
|
|
890
924
|
return null;
|
|
891
925
|
}
|
|
892
926
|
|
|
893
|
-
return Math.max(0,
|
|
927
|
+
return Math.max(0, maxFiles - countManagedAssets(state));
|
|
894
928
|
};
|
|
895
929
|
|
|
896
930
|
const setDropzoneActive = (active: boolean) => {
|
|
@@ -1147,8 +1181,7 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
1147
1181
|
const assetId = toPositiveInt(body.assetId);
|
|
1148
1182
|
|
|
1149
1183
|
if (!body.success || !assetId) {
|
|
1150
|
-
managedFile.listItem
|
|
1151
|
-
managedFile.listItem.classList.remove('is-uploading', 'is-upload-complete', 'is-processing', 'is-preparing');
|
|
1184
|
+
markUploadFailed(managedFile.listItem);
|
|
1152
1185
|
|
|
1153
1186
|
if (errorEl instanceof HTMLElement) {
|
|
1154
1187
|
errorEl.hidden = false;
|
|
@@ -1184,8 +1217,7 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
1184
1217
|
}
|
|
1185
1218
|
|
|
1186
1219
|
const errorEl = managedFile.listItem.querySelector('[data-formie-upload-manager-error]');
|
|
1187
|
-
managedFile.listItem
|
|
1188
|
-
managedFile.listItem.classList.remove('is-uploading', 'is-upload-complete', 'is-processing', 'is-preparing');
|
|
1220
|
+
markUploadFailed(managedFile.listItem);
|
|
1189
1221
|
|
|
1190
1222
|
if (errorEl instanceof HTMLElement) {
|
|
1191
1223
|
errorEl.hidden = false;
|
|
@@ -1254,6 +1286,7 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
1254
1286
|
|
|
1255
1287
|
const files = Array.from(browseInput.files);
|
|
1256
1288
|
browseInput.value = '';
|
|
1289
|
+
clearSelectionError();
|
|
1257
1290
|
|
|
1258
1291
|
try {
|
|
1259
1292
|
uppy.addFiles(files.map((file) => {
|
|
@@ -1294,6 +1327,7 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
1294
1327
|
}
|
|
1295
1328
|
|
|
1296
1329
|
const files = Array.from(transfer.files);
|
|
1330
|
+
clearSelectionError();
|
|
1297
1331
|
|
|
1298
1332
|
try {
|
|
1299
1333
|
uppy.addFiles(files.map((file) => {
|
|
@@ -1311,6 +1345,7 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
1311
1345
|
});
|
|
1312
1346
|
|
|
1313
1347
|
const resetHandler = () => {
|
|
1348
|
+
clearSelectionError();
|
|
1314
1349
|
state.files.forEach((managedFile) => {
|
|
1315
1350
|
managedFile.listItem.remove();
|
|
1316
1351
|
});
|
|
@@ -1326,6 +1361,7 @@ function bindUploadManagerField(field: HTMLElement, form: HTMLFormElement | null
|
|
|
1326
1361
|
return () => {
|
|
1327
1362
|
resolvedForm?.removeEventListener(FORM_RESET_EVENT, resetHandler);
|
|
1328
1363
|
uppy.destroy();
|
|
1364
|
+
selectionError.remove();
|
|
1329
1365
|
state.files = [];
|
|
1330
1366
|
};
|
|
1331
1367
|
}
|
package/src/js/utils/async.ts
CHANGED
|
@@ -79,3 +79,37 @@ export function waitForElement(
|
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
export function waitForElementRemoval(
|
|
84
|
+
selector: string,
|
|
85
|
+
root: ParentNode = document,
|
|
86
|
+
timeoutMs = 10000,
|
|
87
|
+
): Promise<void> {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
if (!root.querySelector(selector)) {
|
|
90
|
+
resolve();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let timeoutId: number | null = null;
|
|
95
|
+
const observer = new MutationObserver(() => {
|
|
96
|
+
if (root.querySelector(selector)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
observer.disconnect();
|
|
101
|
+
if (timeoutId !== null) window.clearTimeout(timeoutId);
|
|
102
|
+
resolve();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
observer.observe(root, {
|
|
106
|
+
childList: true,
|
|
107
|
+
subtree: true,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
timeoutId = window.setTimeout(() => {
|
|
111
|
+
observer.disconnect();
|
|
112
|
+
reject(new Error(`Timed out waiting for "${selector}" to be removed.`));
|
|
113
|
+
}, Math.max(timeoutMs, 0));
|
|
114
|
+
});
|
|
115
|
+
}
|
|
@@ -7,6 +7,18 @@ const required: ValidationRuleDefinition = {
|
|
|
7
7
|
return true;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
if (input instanceof HTMLInputElement && input.type === 'file') {
|
|
11
|
+
if (input.files?.length) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const field = input.closest('[data-formie-field-handle]');
|
|
16
|
+
const name = input.name.replace(/\[\]$/, '');
|
|
17
|
+
|
|
18
|
+
return Array.from(field?.querySelectorAll<HTMLInputElement>('input[type="hidden"][data-formie-file-upload-asset-id]') || [])
|
|
19
|
+
.some((asset) => !asset.disabled && asset.name.startsWith(`${name}[`) && Number(asset.getAttribute('data-formie-file-upload-asset-id')) > 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
if (input.type === 'checkbox' || input.type === 'radio') {
|
|
11
23
|
const checkboxInputs = input.form?.querySelectorAll(`[name="${input.name}"]:not([type="hidden"]):not([disabled])`) || [];
|
|
12
24
|
|
|
@@ -298,8 +298,10 @@ export class FormieValidator {
|
|
|
298
298
|
errorMessages.innerHTML = '';
|
|
299
299
|
|
|
300
300
|
const handle = fieldContainer.getAttribute('data-formie-field-handle') || 'field';
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
errorMessages.id = errorMessages.id || `${input.id || `${this.form.id}-${handle}`}-errors`;
|
|
302
|
+
// Use the rendered field's scoped region so matching handles in other
|
|
303
|
+
// forms cannot capture this control's accessible error reference.
|
|
304
|
+
const errorId = `${errorMessages.id}-1`;
|
|
303
305
|
applyErrorAriaLive(
|
|
304
306
|
errorMessages,
|
|
305
307
|
resolveValidationErrorAriaLive(this.config.errorAriaLive, this.submitted),
|
|
@@ -485,6 +487,16 @@ export class FormieValidator {
|
|
|
485
487
|
return isPainted(richTextHost);
|
|
486
488
|
}
|
|
487
489
|
|
|
490
|
+
// Searchable selects keep their value in a hidden native control. Its
|
|
491
|
+
// adjacent combobox determines whether that value is active for validation.
|
|
492
|
+
if (element instanceof HTMLSelectElement && element.hasAttribute('data-formie-combobox-input')) {
|
|
493
|
+
const combobox = element.nextElementSibling;
|
|
494
|
+
|
|
495
|
+
if (combobox instanceof HTMLElement && combobox.classList.contains('formie-combobox')) {
|
|
496
|
+
return isPainted(combobox);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
488
500
|
return isPainted(element);
|
|
489
501
|
}
|
|
490
502
|
|