@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.
Files changed (82) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/chunks/{address-country-Dn71jroa.js → address-country-BcEQKw0T.js} +1 -1
  3. package/dist/chunks/{address-finder-O49uDRoR.js → address-finder-BWGINCSA.js} +1 -1
  4. package/dist/chunks/{address-state-ByYatUur.js → address-state-DI3ndhMU.js} +98 -88
  5. package/dist/chunks/{api-DYYsVIub.js → api-BK9b340y.js} +16 -16
  6. package/dist/chunks/{api-D6kJ7kXZ.js → api-Dxl_CKw-.js} +30 -25
  7. package/dist/chunks/{async-nPFRNQ06.js → async-2GdXNFCE.js} +18 -1
  8. package/dist/chunks/{bpoint-D0RKFM4F.js → bpoint-BwtuyN_O.js} +1 -1
  9. package/dist/chunks/{captcha-eu-DERJbXnH.js → captcha-eu-DWLoQFKB.js} +2 -2
  10. package/dist/chunks/{combobox-CWNdx4Fk.js → combobox-Ba08_cHL.js} +259 -258
  11. package/dist/chunks/{conditions-DqNHLTbN.js → conditions-Ceeg6ReZ.js} +113 -110
  12. package/dist/chunks/{date-picker-RUnevF7l.js → date-picker-DaPvMrop.js} +12 -11
  13. package/dist/chunks/{eway-or6dtjoT.js → eway-uw0DzU-L.js} +3 -3
  14. package/dist/chunks/{file-upload-Bm-TXyNB.js → file-upload-B8_mH012.js} +28 -16
  15. package/dist/chunks/{friendly-captcha-v1-GFqCt1Mh.js → friendly-captcha-v1-CVXaXgDO.js} +1 -1
  16. package/dist/chunks/{friendly-captcha-v2-WoW246JG.js → friendly-captcha-v2-DkeIVTKI.js} +1 -1
  17. package/dist/chunks/{go-cardless-DzJ-0FKg.js → go-cardless-c4wLJdOt.js} +1 -1
  18. package/dist/chunks/{google-address-C1P1nTDM.js → google-address-JHYvjwgN.js} +13 -8
  19. package/dist/chunks/{hcaptcha-D0T8aIHz.js → hcaptcha-Dt3ELLlP.js} +2 -2
  20. package/dist/chunks/{loqate-DD1S7PY9.js → loqate-m51iPyO1.js} +1 -1
  21. package/dist/chunks/{mollie-DG8Ju83x.js → mollie-Bn0PX173.js} +1 -1
  22. package/dist/chunks/{moneris-C8m3Fvqu.js → moneris-BRbUvI4t.js} +1 -1
  23. package/dist/chunks/{opayo-B3EvrVxw.js → opayo-BwWdInEP.js} +2 -2
  24. package/dist/chunks/{paddle-BIYR9Um3.js → paddle-CympHVvd.js} +2 -2
  25. package/dist/chunks/{paypal-B_yxIo2d.js → paypal-h75h6wae.js} +2 -2
  26. package/dist/chunks/{payway-CeOzS0sL.js → payway-C9rHTCC_.js} +2 -2
  27. package/dist/chunks/{phone-country-bAqje17P.js → phone-country-B8fv-L5_.js} +1 -1
  28. package/dist/chunks/{place-kit-BE5gi3vo.js → place-kit-BnZGQ-L1.js} +8 -8
  29. package/dist/chunks/{recaptcha-enterprise-CruXddZq.js → recaptcha-enterprise-CUs8BO6V.js} +2 -2
  30. package/dist/chunks/{recaptcha-shared-AQRMDMme.js → recaptcha-shared-DTbTw-MF.js} +2 -2
  31. package/dist/chunks/{recaptcha-v2-checkbox-CCnJYCnG.js → recaptcha-v2-checkbox-P1McW4Pv.js} +2 -2
  32. package/dist/chunks/{recaptcha-v2-invisible-BuVqRRYn.js → recaptcha-v2-invisible-FGDqozSP.js} +2 -2
  33. package/dist/chunks/{recaptcha-v3-CloqhO2A.js → recaptcha-v3-CflUHeZD.js} +2 -2
  34. package/dist/chunks/{repeater-DwE27jI1.js → repeater-CyhDbozO.js} +47 -33
  35. package/dist/chunks/{scripts-CbQ7agX3.js → scripts-DrccjrFv.js} +1 -1
  36. package/dist/chunks/skip-CEW89ExY.js +7 -0
  37. package/dist/chunks/{snaptcha-C04kNGYQ.js → snaptcha-C3QLYxFT.js} +1 -1
  38. package/dist/chunks/{square-DEzxv9D9.js → square-C1z5DPVw.js} +2 -2
  39. package/dist/chunks/{stripe-CVPQGDz3.js → stripe-Dmkdkq1y.js} +2 -2
  40. package/dist/chunks/{summary-BF_8mrXI.js → summary-Bn-g5SDx.js} +1 -1
  41. package/dist/chunks/{table-DnIBdeKF.js → table-j7MlLLCc.js} +1 -1
  42. package/dist/chunks/{turnstile-n9DNyPxi.js → turnstile-ptihQfLl.js} +2 -2
  43. package/dist/chunks/{upload-manager-BbiUosZq.js → upload-manager-BW7l-BhF.js} +248 -236
  44. package/dist/index.js +548 -535
  45. package/dist/js/core/create-formie-client.d.ts.map +1 -1
  46. package/dist/js/core/submit-result-state.d.ts.map +1 -1
  47. package/dist/js/core/submit-result-ui.d.ts +3 -1
  48. package/dist/js/core/submit-result-ui.d.ts.map +1 -1
  49. package/dist/js/modules/address/google-address.d.ts +10 -0
  50. package/dist/js/modules/address/google-address.d.ts.map +1 -1
  51. package/dist/js/modules/address/place-kit.d.ts.map +1 -1
  52. package/dist/js/modules/fields/address-state.d.ts.map +1 -1
  53. package/dist/js/modules/fields/combobox.d.ts.map +1 -1
  54. package/dist/js/modules/fields/conditions/effects.d.ts.map +1 -1
  55. package/dist/js/modules/fields/conditions.d.ts.map +1 -1
  56. package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
  57. package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
  58. package/dist/js/modules/fields/repeater.d.ts.map +1 -1
  59. package/dist/js/modules/fields/upload-manager.d.ts.map +1 -1
  60. package/dist/js/utils/async.d.ts +1 -0
  61. package/dist/js/utils/async.d.ts.map +1 -1
  62. package/dist/js/validation/rules/required.d.ts.map +1 -1
  63. package/dist/js/validation/validator.d.ts.map +1 -1
  64. package/package.json +2 -2
  65. package/src/js/core/create-formie-client.ts +17 -10
  66. package/src/js/core/submit-result-state.ts +17 -0
  67. package/src/js/core/submit-result-ui.ts +4 -3
  68. package/src/js/modules/address/google-address.ts +95 -37
  69. package/src/js/modules/address/place-kit.ts +13 -7
  70. package/src/js/modules/fields/address-country.ts +5 -5
  71. package/src/js/modules/fields/address-state.ts +74 -23
  72. package/src/js/modules/fields/combobox.ts +3 -0
  73. package/src/js/modules/fields/conditions/effects.ts +4 -5
  74. package/src/js/modules/fields/conditions.ts +6 -0
  75. package/src/js/modules/fields/date-picker.ts +6 -3
  76. package/src/js/modules/fields/file-upload.ts +44 -14
  77. package/src/js/modules/fields/phone-country.ts +2 -2
  78. package/src/js/modules/fields/repeater.ts +55 -4
  79. package/src/js/modules/fields/upload-manager.ts +54 -18
  80. package/src/js/utils/async.ts +34 -0
  81. package/src/js/validation/rules/required.ts +12 -0
  82. 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
- limitFiles?: number | null;
46
- sizeMinLimit?: number | null;
47
- sizeLimit?: number | null;
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 limitFiles = options.limitFiles ?? toPositiveInt(dropzone.getAttribute('data-formie-file-limit'));
767
- const sizeLimit = options.sizeLimit ?? parseFloat(dropzone.getAttribute('data-formie-size-max-limit') || '');
768
- const sizeMinLimit = options.sizeMinLimit ?? parseFloat(dropzone.getAttribute('data-formie-size-min-limit') || '');
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 (limitFiles) {
780
- restrictions.maxNumberOfFiles = limitFiles;
792
+ if (maxFiles) {
793
+ restrictions.maxNumberOfFiles = maxFiles;
781
794
  }
782
795
 
783
- if (sizeLimit) {
784
- restrictions.maxFileSize = sizeLimit * 1000 * 1000;
796
+ if (maxFileSize) {
797
+ restrictions.maxFileSize = maxFileSize * 1000 * 1000;
785
798
  }
786
799
 
787
- if (sizeMinLimit) {
788
- restrictions.minFileSize = sizeMinLimit * 1000 * 1000;
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 (!limitFiles) {
923
+ if (!maxFiles) {
890
924
  return null;
891
925
  }
892
926
 
893
- return Math.max(0, limitFiles - countManagedAssets(state));
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.classList.add('is-error');
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.classList.add('is-error');
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
  }
@@ -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
- const errorId = `${handle}-error`;
302
- errorMessages.id = errorMessages.id || `${handle}-errors`;
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