@verbb/formie-browser 1.0.3 → 1.0.5

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 (206) hide show
  1. package/CHANGELOG.md +65 -17
  2. package/dist/chunks/address-country-Rx6GQTND.js +68 -0
  3. package/dist/chunks/address-finder-yXU5yMjG.js +30 -0
  4. package/dist/chunks/address-state-BQ0xGV9n.js +344 -0
  5. package/dist/chunks/api-CmwLRq_n.js +780 -0
  6. package/dist/chunks/api-DMK8NSUI.js +407 -0
  7. package/dist/chunks/api-sPqGbOww.js +125 -0
  8. package/dist/chunks/async-nPFRNQ06.js +25 -0
  9. package/dist/chunks/bpoint-DSjqRv63.js +24 -0
  10. package/dist/chunks/calculations-CgVJcQ2P.js +299 -0
  11. package/dist/chunks/captcha-eu-CWJb64_j.js +31 -0
  12. package/dist/chunks/checkbox-radio-DQ0H67Tj.js +125 -0
  13. package/dist/chunks/chunk-3b4jIN3o.js +13 -0
  14. package/dist/chunks/combobox-D-2b42wn.js +1954 -0
  15. package/dist/chunks/conditions-CZ3FU5QO.js +412 -0
  16. package/dist/chunks/constants-DVcJAvc5.js +38 -0
  17. package/dist/chunks/country-from-ip-BwxKaBuJ.js +26 -0
  18. package/dist/chunks/custom-google-maps-BsaSirEZ.js +194 -0
  19. package/dist/chunks/custom-link-CJ1-FjEM.js +46 -0
  20. package/dist/chunks/custom-maps-DYW_pBid.js +161 -0
  21. package/dist/chunks/date-picker-5CA_-zGb.js +5311 -0
  22. package/dist/chunks/debug-BV0DvdHx.js +41 -0
  23. package/dist/chunks/dist-BF5GU3_9.js +2674 -0
  24. package/dist/chunks/event-names-BCI2FLD8.js +51 -0
  25. package/dist/chunks/eway-D3ctTbEk.js +70 -0
  26. package/dist/chunks/field-references.keys-58ZSTrCW.js +21 -0
  27. package/dist/chunks/field-references.resolver-CHwn0G0L.js +145 -0
  28. package/dist/chunks/file-upload-D49m8-DR.js +287 -0
  29. package/dist/chunks/friendly-captcha-v1-ByXzENYZ.js +31 -0
  30. package/dist/chunks/friendly-captcha-v2-BPdlmhQu.js +36 -0
  31. package/dist/chunks/friendly-challenge-8uPatWqk.js +832 -0
  32. package/dist/chunks/go-cardless-BwASSYSK.js +23 -0
  33. package/dist/chunks/google-address-OhKilPWE.js +161 -0
  34. package/dist/chunks/hcaptcha-CdCd9oEi.js +54 -0
  35. package/dist/chunks/hidden-D7_Ch-QN.js +26 -0
  36. package/dist/chunks/http-D-JExro7.js +27 -0
  37. package/dist/chunks/i18n-BY1ds1BL.js +121 -0
  38. package/dist/chunks/leaflet-src-j-lHNmhR.js +4585 -0
  39. package/dist/chunks/loqate-Cp2zvwpE.js +76 -0
  40. package/dist/chunks/mollie-COG-ko2D.js +23 -0
  41. package/dist/chunks/moneris-CMH8PlRc.js +117 -0
  42. package/dist/chunks/opayo-D2jsR5vh.js +113 -0
  43. package/dist/chunks/paddle-DxF88RfC.js +55 -0
  44. package/dist/chunks/paypal-b5pnwrA2.js +95 -0
  45. package/dist/chunks/payway-BJYu9DIR.js +55 -0
  46. package/dist/chunks/phone-country-D3ayr60L.js +2645 -0
  47. package/dist/chunks/place-kit-BGHklIZ-.js +48 -0
  48. package/dist/chunks/placekit-autocomplete.esm-DWVabQlq.js +1236 -0
  49. package/dist/chunks/recaptcha-enterprise-BSwi8IVX.js +65 -0
  50. package/dist/chunks/recaptcha-shared-B4zAescW.js +15 -0
  51. package/dist/chunks/recaptcha-v2-checkbox-CnDQmsKa.js +40 -0
  52. package/dist/chunks/recaptcha-v2-invisible-Cz5uIlp-.js +53 -0
  53. package/dist/chunks/recaptcha-v3-BtbbP111.js +27 -0
  54. package/dist/chunks/repeater-B5leVxZU.js +102 -0
  55. package/dist/chunks/rich-text-Cv8ADyk-.js +349 -0
  56. package/dist/chunks/scripts-6CYSUGKQ.js +24 -0
  57. package/dist/chunks/sdk-BhHAYgga.js +1397 -0
  58. package/dist/chunks/shared-Bx9s0i0P.js +100 -0
  59. package/dist/chunks/signature-CpgqHCEx.js +463 -0
  60. package/dist/chunks/snaptcha-vz1RnZ15.js +8 -0
  61. package/dist/chunks/square-BP8RWAKB.js +40 -0
  62. package/dist/chunks/stripe-MihN63uC.js +184 -0
  63. package/dist/chunks/styles-BfoIZwJp.js +19 -0
  64. package/dist/chunks/summary-D3AjxpYN.js +114 -0
  65. package/dist/chunks/table-C-lkQN6I.js +78 -0
  66. package/dist/chunks/text-limit-B09elPDB.js +125 -0
  67. package/dist/chunks/theme-classes-Tv7q7ToE.js +44 -0
  68. package/dist/chunks/turnstile-D_Pi1CKM.js +58 -0
  69. package/dist/chunks/utils-Vqp8i_RM.js +49401 -0
  70. package/dist/css/formie-theme.css +2 -0
  71. package/dist/css/theme/fields/_address-state.css +94 -0
  72. package/dist/css/theme/fields/_combobox.css +246 -0
  73. package/dist/css/theme/fields/_select.css +13 -0
  74. package/dist/index.js +2447 -39
  75. package/dist/js/core/create-formie-client.d.ts.map +1 -1
  76. package/dist/js/core/field-error-container.d.ts +4 -0
  77. package/dist/js/core/field-error-container.d.ts.map +1 -0
  78. package/dist/js/core/submit-result-state.d.ts.map +1 -1
  79. package/dist/js/core/submit-result-ui.d.ts.map +1 -1
  80. package/dist/js/core/validation-focus.d.ts +4 -0
  81. package/dist/js/core/validation-focus.d.ts.map +1 -0
  82. package/dist/js/modules/address/api.d.ts +1 -0
  83. package/dist/js/modules/address/api.d.ts.map +1 -1
  84. package/dist/js/modules/address/constants.d.ts +22 -1
  85. package/dist/js/modules/address/constants.d.ts.map +1 -1
  86. package/dist/js/modules/address/host.d.ts +2 -2
  87. package/dist/js/modules/address/host.d.ts.map +1 -1
  88. package/dist/js/modules/address/loqate.d.ts.map +1 -1
  89. package/dist/js/modules/fields/address-country.d.ts +3 -0
  90. package/dist/js/modules/fields/address-country.d.ts.map +1 -0
  91. package/dist/js/modules/fields/address-state.d.ts +3 -0
  92. package/dist/js/modules/fields/address-state.d.ts.map +1 -0
  93. package/dist/js/modules/fields/calculations.d.ts.map +1 -1
  94. package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
  95. package/dist/js/modules/fields/combobox.d.ts +17 -0
  96. package/dist/js/modules/fields/combobox.d.ts.map +1 -0
  97. package/dist/js/modules/fields/custom-google-maps.d.ts +64 -0
  98. package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -0
  99. package/dist/js/modules/fields/custom-link.d.ts +3 -0
  100. package/dist/js/modules/fields/custom-link.d.ts.map +1 -0
  101. package/dist/js/modules/fields/custom-maps.d.ts +3 -0
  102. package/dist/js/modules/fields/custom-maps.d.ts.map +1 -0
  103. package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
  104. package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
  105. package/dist/js/modules/fields/index.d.ts.map +1 -1
  106. package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
  107. package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
  108. package/dist/js/submit/pipeline.d.ts.map +1 -1
  109. package/dist/js/utils/country-from-ip.d.ts +7 -0
  110. package/dist/js/utils/country-from-ip.d.ts.map +1 -0
  111. package/dist/js/utils/field-references.d.ts +1 -0
  112. package/dist/js/utils/field-references.d.ts.map +1 -1
  113. package/dist/js/utils/field-references.row-scope.d.ts +11 -0
  114. package/dist/js/utils/field-references.row-scope.d.ts.map +1 -0
  115. package/dist/js/utils/i18n.d.ts.map +1 -1
  116. package/dist/js/validation/rules/email.d.ts.map +1 -1
  117. package/dist/js/validation/rules/number.d.ts.map +1 -1
  118. package/dist/js/validation/validator.d.ts.map +1 -1
  119. package/package.json +6 -3
  120. package/src/css/formie-theme.css +2 -0
  121. package/src/css/theme/fields/_address-state.css +94 -0
  122. package/src/css/theme/fields/_combobox.css +246 -0
  123. package/src/css/theme/fields/_select.css +13 -0
  124. package/src/js/core/create-formie-client.ts +27 -0
  125. package/src/js/core/field-error-container.ts +39 -0
  126. package/src/js/core/submit-result-state.ts +12 -2
  127. package/src/js/core/submit-result-ui.ts +7 -13
  128. package/src/js/core/validation-focus.ts +117 -0
  129. package/src/js/modules/address/api.ts +7 -0
  130. package/src/js/modules/address/constants.ts +82 -2
  131. package/src/js/modules/address/host.ts +16 -6
  132. package/src/js/modules/address/loqate.ts +3 -17
  133. package/src/js/modules/fields/address-country.ts +156 -0
  134. package/src/js/modules/fields/address-state.ts +946 -0
  135. package/src/js/modules/fields/calculations.ts +32 -0
  136. package/src/js/modules/fields/checkbox-radio.ts +21 -15
  137. package/src/js/modules/fields/combobox.ts +188 -0
  138. package/src/js/modules/fields/custom-google-maps.ts +416 -0
  139. package/src/js/modules/fields/custom-link.ts +95 -0
  140. package/src/js/modules/fields/custom-maps.ts +339 -0
  141. package/src/js/modules/fields/date-picker.ts +96 -3
  142. package/src/js/modules/fields/file-upload.ts +12 -9
  143. package/src/js/modules/fields/index.ts +6 -0
  144. package/src/js/modules/fields/phone-country.ts +6 -0
  145. package/src/js/modules/fields/text-limit.ts +87 -22
  146. package/src/js/submit/pipeline.ts +11 -1
  147. package/src/js/utils/country-from-ip.ts +54 -0
  148. package/src/js/utils/field-references.row-scope.ts +303 -0
  149. package/src/js/utils/field-references.ts +1 -0
  150. package/src/js/utils/i18n.ts +184 -5
  151. package/src/js/validation/rules/email.ts +3 -2
  152. package/src/js/validation/rules/match.ts +2 -2
  153. package/src/js/validation/rules/minmax.ts +4 -4
  154. package/src/js/validation/rules/number.ts +9 -5
  155. package/src/js/validation/rules/pattern.ts +4 -4
  156. package/src/js/validation/rules/required.ts +1 -1
  157. package/src/js/validation/rules/url.ts +1 -1
  158. package/src/js/validation/validator.ts +7 -7
  159. package/dist/chunks/address-finder-BjMuEiwF.js +0 -37
  160. package/dist/chunks/bpoint-KudsBCo2.js +0 -26
  161. package/dist/chunks/calculations-DlGSkQ3W.js +0 -147
  162. package/dist/chunks/captcha-eu-CterYFok.js +0 -34
  163. package/dist/chunks/checkbox-radio-CBvgk3hR.js +0 -136
  164. package/dist/chunks/conditions-DYy1-Hwt.js +0 -459
  165. package/dist/chunks/date-picker-BlD--zfe.js +0 -4994
  166. package/dist/chunks/eway-qwc-B5Us.js +0 -74
  167. package/dist/chunks/file-upload-CIgHrXfp.js +0 -316
  168. package/dist/chunks/friendly-captcha-v1-EDFac33c.js +0 -33
  169. package/dist/chunks/friendly-captcha-v2-BDpSvFGR.js +0 -38
  170. package/dist/chunks/go-cardless-DuDMVsnz.js +0 -24
  171. package/dist/chunks/google-address-Bmjp01pH.js +0 -181
  172. package/dist/chunks/hcaptcha-BTQ6An6g.js +0 -56
  173. package/dist/chunks/hidden-DbPdOxKb.js +0 -31
  174. package/dist/chunks/index-BEsfee-D.js +0 -1839
  175. package/dist/chunks/index-BP7Unm5Y.js +0 -863
  176. package/dist/chunks/index-BcqwpPNE.js +0 -4314
  177. package/dist/chunks/loqate-BdNGv93o.js +0 -55
  178. package/dist/chunks/mollie-DFOjTuGX.js +0 -24
  179. package/dist/chunks/moneris-BbFfl-EL.js +0 -101
  180. package/dist/chunks/opayo-CmgYa9ya.js +0 -122
  181. package/dist/chunks/paddle-BUi7w1_o.js +0 -60
  182. package/dist/chunks/paypal-A-ePfh6k.js +0 -100
  183. package/dist/chunks/payway-BqFCbhyo.js +0 -56
  184. package/dist/chunks/phone-country-DPMiqk4L.js +0 -3765
  185. package/dist/chunks/place-kit-CX7tamAg.js +0 -42
  186. package/dist/chunks/placekit-autocomplete.esm-LAwfbPZl.js +0 -1299
  187. package/dist/chunks/recaptcha-enterprise-DxF6cW95.js +0 -71
  188. package/dist/chunks/recaptcha-shared-DhGcbiaq.js +0 -15
  189. package/dist/chunks/recaptcha-v2-checkbox-R9EfbZ4k.js +0 -41
  190. package/dist/chunks/recaptcha-v2-invisible-DV9hswAp.js +0 -59
  191. package/dist/chunks/recaptcha-v3-V0xc-Edh.js +0 -28
  192. package/dist/chunks/repeater-DKUT6TGI.js +0 -124
  193. package/dist/chunks/rich-text-SH7KiaKH.js +0 -304
  194. package/dist/chunks/scripts-D9N2d5rN.js +0 -36
  195. package/dist/chunks/sdk-3SicTWby.js +0 -1559
  196. package/dist/chunks/shared-BmntPmnW.js +0 -80
  197. package/dist/chunks/signature-BA3tQsGT.js +0 -550
  198. package/dist/chunks/snaptcha-DV7p6J9k.js +0 -8
  199. package/dist/chunks/square-Bs-ndqYK.js +0 -46
  200. package/dist/chunks/stripe-DkZAHxtn.js +0 -202
  201. package/dist/chunks/styles-C3aqgtek.js +0 -21
  202. package/dist/chunks/summary-Dq2i9TQk.js +0 -126
  203. package/dist/chunks/table-Cu4WfVcw.js +0 -95
  204. package/dist/chunks/text-limit-2ycmB-Ae.js +0 -135
  205. package/dist/chunks/turnstile-CId90tRr.js +0 -59
  206. package/dist/chunks/utils-BntI3W4S.js +0 -6149
@@ -12,9 +12,21 @@ import {
12
12
  import type { FormieModuleDefinition } from '#contracts/modules';
13
13
  import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
14
14
  import { buildFieldValueRegistry, fieldKeyToInputName, normalizeFieldKey, resolveFieldReferenceLive } from '#utils/field-references';
15
+ import { getRowScopedWatchNames, resolveRowScopedFieldReference, type RowScopeParams } from '#utils/field-references.row-scope';
15
16
  import { createDebug } from '#utils/debug';
16
17
 
17
18
  const INPUT_SELECTOR = 'input[data-formie-calculation-input]';
19
+
20
+ function getRowScopeParams(variable: CalculationVariable): RowScopeParams {
21
+ const scoped = variable as CalculationVariable & RowScopeParams;
22
+
23
+ return {
24
+ scope: scoped.scope,
25
+ index: scoped.index,
26
+ rows: scoped.rows,
27
+ fieldKind: scoped.fieldKind,
28
+ };
29
+ }
18
30
  const MODULE_ID = 'calculations';
19
31
  const debug = createDebug('fields', 'calculations');
20
32
 
@@ -27,6 +39,15 @@ function resolveVariables(
27
39
  const variables: Record<string, unknown> = {};
28
40
 
29
41
  variableEntries.forEach(([variableKey, variable]) => {
42
+ const scopeParams = getRowScopeParams(variable);
43
+ const scope = String(scopeParams.scope || '').trim();
44
+
45
+ if (scope) {
46
+ const resolved = resolveRowScopedFieldReference(variable.sourceKey || '', scopeParams, registry);
47
+ variables[variableKey] = readCalculationVariableValue(variable, resolved.value);
48
+ return;
49
+ }
50
+
30
51
  const resolved = resolveFieldReferenceLive(variable.sourceKey || '', registry);
31
52
  variables[variableKey] = readCalculationVariableValue(variable, resolved.value);
32
53
  });
@@ -39,6 +60,17 @@ function getWatchNames(root: Element, variableEntries: CalculationVariableEntry[
39
60
  const watchNames = new Set<string>();
40
61
 
41
62
  variableEntries.forEach(([, variable]) => {
63
+ const scopeParams = getRowScopeParams(variable);
64
+ const scope = String(scopeParams.scope || '').trim();
65
+
66
+ if (scope) {
67
+ getRowScopedWatchNames(variable.sourceKey || '', scopeParams, registry).forEach((name) => {
68
+ watchNames.add(name);
69
+ });
70
+
71
+ return;
72
+ }
73
+
42
74
  const key = normalizeFieldKey(variable.sourceKey || '');
43
75
  const entry = registry.get(key);
44
76
 
@@ -31,6 +31,14 @@ function getMinMaxRule(getRule: (rule: string) => unknown): { min: number | null
31
31
  };
32
32
  }
33
33
 
34
+ function getSelectedOptionCount(field: HTMLElement): number {
35
+ return Array.from(field.querySelectorAll('input[type="checkbox"]')).filter((input): input is HTMLInputElement => {
36
+ return input instanceof HTMLInputElement && !isToggleCheckbox(input);
37
+ }).filter((input) => {
38
+ return input.checked;
39
+ }).length;
40
+ }
41
+
34
42
  function registerValidators(form: HTMLFormElement | null): void {
35
43
  // Several checkbox/radio module instances can exist in one form, but the
36
44
  // custom validator should only be registered once per form lifecycle.
@@ -42,12 +50,7 @@ function registerValidators(form: HTMLFormElement | null): void {
42
50
  return true;
43
51
  }
44
52
 
45
- const inputs = Array.from(field.querySelectorAll('input[type="checkbox"]')).filter((input): input is HTMLInputElement => {
46
- return input instanceof HTMLInputElement && !isToggleCheckbox(input);
47
- });
48
- const selected = inputs.filter((input) => {
49
- return input.checked;
50
- }).length;
53
+ const selected = getSelectedOptionCount(field);
51
54
  const { min, max } = getMinMaxRule(getRule);
52
55
 
53
56
  if (min !== null && selected < min) {
@@ -61,21 +64,24 @@ function registerValidators(form: HTMLFormElement | null): void {
61
64
  return true;
62
65
  },
63
66
  ({ field, label, t, getRule }) => {
64
- const { min, max } = field ? getMinMaxRule(getRule) : { min: null, max: null };
65
-
66
- if (min !== null && max !== null) {
67
- return t('{attribute} must select between {min} and {max}.', { attribute: label, min, max });
67
+ if (!field) {
68
+ return t('{label} has an invalid value.', { label });
68
69
  }
69
70
 
70
- if (min !== null) {
71
- return t('{attribute} must select no less than {min}.', { attribute: label, min });
71
+ const selected = getSelectedOptionCount(field);
72
+ const { min, max } = getMinMaxRule(getRule);
73
+
74
+ if (min !== null && selected < min) {
75
+ return field.getAttribute('data-formie-validation-min-options-message')
76
+ ?? t('{label} should contain at least {min, number} {min, plural, one{option} other{options}}.', { label, min });
72
77
  }
73
78
 
74
- if (max !== null) {
75
- return t('{attribute} must select no greater than {max}.', { attribute: label, max });
79
+ if (max !== null && selected > max) {
80
+ return field.getAttribute('data-formie-validation-max-options-message')
81
+ ?? t('{label} should contain at most {max, number} {max, plural, one{option} other{options}}.', { label, max });
76
82
  }
77
83
 
78
- return t('{attribute} has an invalid value.', { attribute: label });
84
+ return t('{label} has an invalid value.', { label });
79
85
  },
80
86
  );
81
87
  });
@@ -0,0 +1,188 @@
1
+ import TomSelect from 'tom-select/dist/esm/tom-select.complete.js';
2
+
3
+ import type { FormieModuleDefinition } from '#contracts/modules';
4
+ import comboboxThemeCss from '#theme-css/fields/_combobox.css?inline';
5
+ import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
6
+ import { ensureModuleStyles } from '#modules/styles';
7
+ import { createDebug } from '#utils/debug';
8
+
9
+ const SELECT_SELECTOR = 'select[data-formie-combobox-input]';
10
+ const MODULE_ID = 'combobox';
11
+ const debug = createDebug('fields', 'combobox');
12
+
13
+ ensureModuleStyles(MODULE_ID, [comboboxThemeCss]);
14
+
15
+ const NATIVE_THEME_CLASSES = [
16
+ 'formie-select',
17
+ 'formie-dropdown-input',
18
+ 'formie-input-error',
19
+ ] as const;
20
+
21
+ export type FormieComboboxOptions = {
22
+ multiple?: boolean;
23
+ placeholder?: string | null;
24
+ };
25
+
26
+ type TomSelectInstance = InstanceType<typeof TomSelect> & {
27
+ wrapper: HTMLElement;
28
+ dropdown?: HTMLElement;
29
+ };
30
+
31
+ type SelectElement = HTMLSelectElement & {
32
+ _formieTomSelect?: TomSelectInstance;
33
+ };
34
+
35
+ function stripNativeThemeClasses(select: HTMLSelectElement): string[] {
36
+ const removed: string[] = [];
37
+
38
+ NATIVE_THEME_CLASSES.forEach((className) => {
39
+ if (select.classList.contains(className)) {
40
+ select.classList.remove(className);
41
+ removed.push(className);
42
+ }
43
+ });
44
+
45
+ return removed;
46
+ }
47
+
48
+ function restoreNativeThemeClasses(select: HTMLSelectElement, classNames: string[]): void {
49
+ classNames.forEach((className) => {
50
+ select.classList.add(className);
51
+ });
52
+ }
53
+
54
+ function cleanLeakedThemeClasses(element: HTMLElement): void {
55
+ NATIVE_THEME_CLASSES.forEach((className) => {
56
+ element.classList.remove(className);
57
+ });
58
+ }
59
+
60
+ function resolvePlaceholder(select: HTMLSelectElement, configuredPlaceholder?: string | null): string | null {
61
+ const configured = configuredPlaceholder?.trim();
62
+
63
+ if (configured) {
64
+ return configured;
65
+ }
66
+
67
+ const emptyOption = select.querySelector('option[value=""]');
68
+
69
+ return emptyOption?.textContent?.trim() || null;
70
+ }
71
+
72
+ function removeEmptyOptionFromCombobox(instance: TomSelectInstance): void {
73
+ if (instance.options['']) {
74
+ instance.removeOption('', true);
75
+ }
76
+ }
77
+
78
+ export function initFormieCombobox(select: SelectElement, options: FormieComboboxOptions = {}): () => void {
79
+ select._formieTomSelect?.destroy();
80
+
81
+ const multiple = options.multiple === true;
82
+ const restoredClasses = stripNativeThemeClasses(select);
83
+ const placeholder = resolvePlaceholder(select, options.placeholder);
84
+
85
+ const mergedOptions: Record<string, unknown> = {
86
+ create: false,
87
+ maxItems: multiple ? null : 1,
88
+ plugins: multiple ? ['remove_button'] : [],
89
+ hideSelected: multiple ? true : null,
90
+ clearAfterSelect: multiple,
91
+ closeAfterSelect: !multiple,
92
+ allowEmptyOption: !multiple,
93
+ openOnFocus: true,
94
+ diacritics: true,
95
+ // Tom Select copies the native select class attribute onto its wrapper,
96
+ // which would duplicate Formie select chrome if left on the <select>.
97
+ copyClassesToDropdown: false,
98
+ wrapperClass: 'ts-wrapper formie-combobox',
99
+ onChange: () => {
100
+ // Mirror Tom Select changes back through the native select so validation,
101
+ // conditions, and calculations respond to combobox-driven updates.
102
+ select.dispatchEvent(new Event('input', { bubbles: true }));
103
+ select.dispatchEvent(new Event('change', { bubbles: true }));
104
+ },
105
+ };
106
+
107
+ if (placeholder) {
108
+ mergedOptions.placeholder = placeholder;
109
+ }
110
+
111
+ dispatchFieldEvent(select, MODULE_ID, 'before-init', {
112
+ select,
113
+ options: mergedOptions,
114
+ });
115
+
116
+ const instance = new TomSelect(select, mergedOptions) as TomSelectInstance;
117
+ removeEmptyOptionFromCombobox(instance);
118
+ cleanLeakedThemeClasses(instance.wrapper);
119
+
120
+ if (instance.dropdown) {
121
+ cleanLeakedThemeClasses(instance.dropdown);
122
+ }
123
+
124
+ // Tom Select keeps the native select as a preceding sibling; ensure it never
125
+ // remains visible once enhanced (notably for multi-select listboxes).
126
+ select.style.display = 'none';
127
+
128
+ select._formieTomSelect = instance;
129
+
130
+ debug.log('Initialized.', {
131
+ inputName: select.name,
132
+ multiple,
133
+ });
134
+
135
+ dispatchFieldEvent(select, MODULE_ID, 'after-init', {
136
+ combobox: instance,
137
+ options: mergedOptions,
138
+ });
139
+
140
+ return () => {
141
+ instance.destroy();
142
+ select.style.removeProperty('display');
143
+ restoreNativeThemeClasses(select, restoredClasses);
144
+ delete select._formieTomSelect;
145
+ debug.log('Destroyed.', {
146
+ inputName: select.name,
147
+ });
148
+ };
149
+ }
150
+
151
+ export const comboboxModule: FormieModuleDefinition = {
152
+ id: MODULE_ID,
153
+ kind: 'field',
154
+ match: (ctx) => {
155
+ return !!ctx.target.querySelector(SELECT_SELECTOR);
156
+ },
157
+ setup: async (ctx) => {
158
+ const options = (ctx.options || {}) as FormieComboboxOptions;
159
+ const fields = getModuleFieldContainers(ctx);
160
+ const cleanups = fields.map((field) => {
161
+ const select = field.querySelector(SELECT_SELECTOR);
162
+
163
+ if (!(select instanceof HTMLSelectElement)) {
164
+ debug.warn('Field missing combobox select; skipping.');
165
+ return () => { };
166
+ }
167
+
168
+ return initFormieCombobox(select as SelectElement, options);
169
+ });
170
+
171
+ debug.log('Module setup.', { fieldCount: fields.length });
172
+
173
+ await ctx.emit('formie:module:combobox:init', {
174
+ count: cleanups.length,
175
+ });
176
+
177
+ return {
178
+ destroy: () => {
179
+ cleanups.forEach((cleanup) => {
180
+ cleanup();
181
+ });
182
+
183
+ debug.log('Module destroy.', { fieldCount: fields.length });
184
+ void ctx.emit('formie:module:combobox:destroy', {});
185
+ },
186
+ };
187
+ },
188
+ };