@verbb/formie-browser 1.0.4 → 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 (182) hide show
  1. package/CHANGELOG.md +62 -22
  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/submit-result-ui.d.ts.map +1 -1
  77. package/dist/js/core/validation-focus.d.ts +4 -0
  78. package/dist/js/core/validation-focus.d.ts.map +1 -0
  79. package/dist/js/modules/address/api.d.ts +1 -0
  80. package/dist/js/modules/address/api.d.ts.map +1 -1
  81. package/dist/js/modules/address/constants.d.ts +22 -1
  82. package/dist/js/modules/address/constants.d.ts.map +1 -1
  83. package/dist/js/modules/address/host.d.ts +2 -2
  84. package/dist/js/modules/address/host.d.ts.map +1 -1
  85. package/dist/js/modules/address/loqate.d.ts.map +1 -1
  86. package/dist/js/modules/fields/address-country.d.ts +3 -0
  87. package/dist/js/modules/fields/address-country.d.ts.map +1 -0
  88. package/dist/js/modules/fields/address-state.d.ts +3 -0
  89. package/dist/js/modules/fields/address-state.d.ts.map +1 -0
  90. package/dist/js/modules/fields/calculations.d.ts.map +1 -1
  91. package/dist/js/modules/fields/combobox.d.ts +17 -0
  92. package/dist/js/modules/fields/combobox.d.ts.map +1 -0
  93. package/dist/js/modules/fields/custom-google-maps.d.ts +64 -0
  94. package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -0
  95. package/dist/js/modules/fields/custom-link.d.ts +3 -0
  96. package/dist/js/modules/fields/custom-link.d.ts.map +1 -0
  97. package/dist/js/modules/fields/custom-maps.d.ts +3 -0
  98. package/dist/js/modules/fields/custom-maps.d.ts.map +1 -0
  99. package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
  100. package/dist/js/modules/fields/index.d.ts.map +1 -1
  101. package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
  102. package/dist/js/submit/pipeline.d.ts.map +1 -1
  103. package/dist/js/utils/country-from-ip.d.ts +7 -0
  104. package/dist/js/utils/country-from-ip.d.ts.map +1 -0
  105. package/dist/js/utils/field-references.d.ts +1 -0
  106. package/dist/js/utils/field-references.d.ts.map +1 -1
  107. package/dist/js/utils/field-references.row-scope.d.ts +11 -0
  108. package/dist/js/utils/field-references.row-scope.d.ts.map +1 -0
  109. package/package.json +6 -3
  110. package/src/css/formie-theme.css +2 -0
  111. package/src/css/theme/fields/_address-state.css +94 -0
  112. package/src/css/theme/fields/_combobox.css +246 -0
  113. package/src/css/theme/fields/_select.css +13 -0
  114. package/src/js/core/create-formie-client.ts +27 -0
  115. package/src/js/core/submit-result-ui.ts +4 -4
  116. package/src/js/core/validation-focus.ts +117 -0
  117. package/src/js/modules/address/api.ts +7 -0
  118. package/src/js/modules/address/constants.ts +82 -2
  119. package/src/js/modules/address/host.ts +16 -6
  120. package/src/js/modules/address/loqate.ts +3 -17
  121. package/src/js/modules/fields/address-country.ts +156 -0
  122. package/src/js/modules/fields/address-state.ts +946 -0
  123. package/src/js/modules/fields/calculations.ts +32 -0
  124. package/src/js/modules/fields/combobox.ts +188 -0
  125. package/src/js/modules/fields/custom-google-maps.ts +416 -0
  126. package/src/js/modules/fields/custom-link.ts +95 -0
  127. package/src/js/modules/fields/custom-maps.ts +339 -0
  128. package/src/js/modules/fields/date-picker.ts +96 -3
  129. package/src/js/modules/fields/index.ts +6 -0
  130. package/src/js/modules/fields/phone-country.ts +6 -0
  131. package/src/js/submit/pipeline.ts +11 -1
  132. package/src/js/utils/country-from-ip.ts +54 -0
  133. package/src/js/utils/field-references.row-scope.ts +303 -0
  134. package/src/js/utils/field-references.ts +1 -0
  135. package/dist/chunks/address-finder-BU4roxT6.js +0 -37
  136. package/dist/chunks/bpoint-uNg5j6rt.js +0 -26
  137. package/dist/chunks/calculations-C9t6Zop9.js +0 -147
  138. package/dist/chunks/captcha-eu-BAPzHNts.js +0 -34
  139. package/dist/chunks/checkbox-radio-CHeLNVIK.js +0 -141
  140. package/dist/chunks/conditions-DKxVCsjD.js +0 -459
  141. package/dist/chunks/date-picker-B3iQ797Z.js +0 -4994
  142. package/dist/chunks/eway-DCielIML.js +0 -74
  143. package/dist/chunks/file-upload-DP2TOUYV.js +0 -316
  144. package/dist/chunks/friendly-captcha-v1-DdlcGiFY.js +0 -33
  145. package/dist/chunks/friendly-captcha-v2-76w-YnFm.js +0 -38
  146. package/dist/chunks/go-cardless-rwCkzbNK.js +0 -24
  147. package/dist/chunks/google-address-BD8IZ86V.js +0 -181
  148. package/dist/chunks/hcaptcha-BNNTL6uf.js +0 -56
  149. package/dist/chunks/hidden-CW6Rcf1N.js +0 -31
  150. package/dist/chunks/index-BEsfee-D.js +0 -1839
  151. package/dist/chunks/index-BP7Unm5Y.js +0 -863
  152. package/dist/chunks/index-D6npYxvr.js +0 -4423
  153. package/dist/chunks/loqate-B-ZNS65B.js +0 -55
  154. package/dist/chunks/mollie-BDAZ-1X4.js +0 -24
  155. package/dist/chunks/moneris-B1b0uEtY.js +0 -101
  156. package/dist/chunks/opayo-CC5hraHo.js +0 -122
  157. package/dist/chunks/paddle-B4GmrDsq.js +0 -60
  158. package/dist/chunks/paypal-BFS6XsIS.js +0 -100
  159. package/dist/chunks/payway-DekoJead.js +0 -56
  160. package/dist/chunks/phone-country-Cm5YUeXI.js +0 -3765
  161. package/dist/chunks/place-kit-DizZWV_e.js +0 -42
  162. package/dist/chunks/placekit-autocomplete.esm-LAwfbPZl.js +0 -1299
  163. package/dist/chunks/recaptcha-enterprise-BbOvO7JZ.js +0 -71
  164. package/dist/chunks/recaptcha-shared-Dwg1eVFS.js +0 -15
  165. package/dist/chunks/recaptcha-v2-checkbox-SY3ti5iQ.js +0 -41
  166. package/dist/chunks/recaptcha-v2-invisible-DSmQnn6c.js +0 -59
  167. package/dist/chunks/recaptcha-v3-DNh7YCnR.js +0 -28
  168. package/dist/chunks/repeater-DM0vuFNQ.js +0 -124
  169. package/dist/chunks/rich-text-C8aGYp70.js +0 -304
  170. package/dist/chunks/scripts-CKTFtxPO.js +0 -36
  171. package/dist/chunks/sdk-3SicTWby.js +0 -1559
  172. package/dist/chunks/shared--BU5CFoL.js +0 -80
  173. package/dist/chunks/signature-pFhZfH7d.js +0 -550
  174. package/dist/chunks/snaptcha-VuOUOaLj.js +0 -8
  175. package/dist/chunks/square-BR9pvToy.js +0 -46
  176. package/dist/chunks/stripe-OHUwoV9g.js +0 -202
  177. package/dist/chunks/styles-C3aqgtek.js +0 -21
  178. package/dist/chunks/summary-Da7Ktg3N.js +0 -126
  179. package/dist/chunks/table-C0PmR9bI.js +0 -95
  180. package/dist/chunks/text-limit-mLEwZDIa.js +0 -144
  181. package/dist/chunks/turnstile-9tM7q_LK.js +0 -59
  182. package/dist/chunks/utils-BntI3W4S.js +0 -6149
@@ -0,0 +1,946 @@
1
+ import type { FormieModuleDefinition } from '#contracts/modules';
2
+ import { ADDRESS_SELECTORS } from '#modules/address/constants';
3
+ import { initFormieCombobox } from '#modules/fields/combobox';
4
+ import { dispatchFieldEvent, getModuleFieldContainers } from '#modules/fields/shared';
5
+ import { createDebug } from '#utils/debug';
6
+
7
+ const STATE_DYNAMIC_SELECTOR = '[data-formie-address-state-dynamic]';
8
+ const STATE_INPUT_SELECTOR = '[data-formie-address-state-input]';
9
+ const AUTOFILL_ANCHOR_SELECTOR = '[data-formie-address-state-autofill-anchor]';
10
+ const AUTOFILL_ANIMATION_NAME = 'formie-address-autofill-start';
11
+ const COUNTRY_SELECTOR = ADDRESS_SELECTORS.country;
12
+ const MODULE_ID = 'address-state';
13
+ const DEFAULT_SUBDIVISIONS_ACTION = 'formie/address/subdivisions';
14
+ const SELECT_THEME_CLASSES = ['formie-select', 'formie-dropdown-input'] as const;
15
+ const AUTOFILL_SWEEP_DELAYS_MS = [0, 100, 300] as const;
16
+
17
+ const debug = createDebug('fields', 'address-state');
18
+
19
+ type SubdivisionOption = {
20
+ label: string;
21
+ value: string;
22
+ name?: string;
23
+ short?: string;
24
+ };
25
+
26
+ type SubdivisionsResponse = {
27
+ countryCode?: string;
28
+ administrativeAreaType?: string | null;
29
+ administrativeAreaLabel?: string;
30
+ administrativeAreaUsed?: boolean;
31
+ administrativeAreaRequired?: boolean;
32
+ subdivisions?: SubdivisionOption[];
33
+ };
34
+
35
+ type AddressStateOptions = {
36
+ inputMode?: string;
37
+ hideWhenUnused?: boolean;
38
+ useSearchable?: boolean;
39
+ useDatalist?: boolean;
40
+ optionLabel?: 'name' | 'short';
41
+ optionValue?: 'name' | 'short';
42
+ countryOptionValue?: 'short' | 'full';
43
+ placeholder?: string | null;
44
+ subdivisionsAction?: string;
45
+ };
46
+
47
+ type AddressStateControl = HTMLInputElement | HTMLSelectElement;
48
+
49
+ type ComboboxSelect = HTMLSelectElement & {
50
+ _formieTomSelect?: {
51
+ getValue: () => string;
52
+ setValue: (value: string, silent?: boolean) => void;
53
+ };
54
+ };
55
+
56
+ type FieldState = {
57
+ addressRoot: HTMLElement;
58
+ stateField: HTMLElement;
59
+ stateControl: AddressStateControl;
60
+ countryControl: HTMLInputElement | HTMLSelectElement | null;
61
+ autofillAnchor: HTMLInputElement | null;
62
+ pendingStateValue: string;
63
+ datalistId: string;
64
+ comboboxCleanup: (() => void) | null;
65
+ skeletonEl: HTMLElement | null;
66
+ fetchingAnnouncementEl: HTMLElement | null;
67
+ autofillSweepTimers: number[];
68
+ countryChangeTimer: number | null;
69
+ lastCountry: string;
70
+ fetchGeneration: number;
71
+ required: boolean;
72
+ lastSubdivisions: SubdivisionOption[];
73
+ };
74
+
75
+ const subdivisionDataCache = new Map<string, SubdivisionsResponse | null>();
76
+ const subdivisionInflight = new Map<string, Promise<SubdivisionsResponse | null>>();
77
+
78
+ function getAddressRoot(field: HTMLElement): HTMLElement | null {
79
+ return field.closest('[data-formie-field-type="address"]')
80
+ || field.closest('[data-formie-address-field-layout]')?.closest('[data-formie-field]')
81
+ || field.closest('[data-formie-field]');
82
+ }
83
+
84
+ function getCountryControl(addressRoot: HTMLElement): HTMLInputElement | HTMLSelectElement | null {
85
+ const el = addressRoot.querySelector(COUNTRY_SELECTOR);
86
+
87
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
88
+ return el;
89
+ }
90
+
91
+ return null;
92
+ }
93
+
94
+ function getStateControl(field: HTMLElement): AddressStateControl | null {
95
+ const el = field.querySelector(STATE_DYNAMIC_SELECTOR);
96
+
97
+ if (el instanceof HTMLInputElement || el instanceof HTMLSelectElement) {
98
+ return el;
99
+ }
100
+
101
+ return null;
102
+ }
103
+
104
+ function getFieldLabel(field: HTMLElement): HTMLElement | null {
105
+ return field.querySelector('[data-formie-field-label]');
106
+ }
107
+
108
+ function dispatchControlEvents(control: AddressStateControl): void {
109
+ control.dispatchEvent(new Event('input', { bubbles: true }));
110
+ control.dispatchEvent(new Event('change', { bubbles: true }));
111
+ }
112
+
113
+ function syncComboboxValue(control: AddressStateControl, value: string): void {
114
+ if (!(control instanceof HTMLSelectElement)) {
115
+ return;
116
+ }
117
+
118
+ const combobox = (control as ComboboxSelect)._formieTomSelect;
119
+
120
+ if (!combobox) {
121
+ return;
122
+ }
123
+
124
+ if (combobox.getValue() !== value) {
125
+ combobox.setValue(value, true);
126
+ }
127
+ }
128
+
129
+ function setControlValue(control: AddressStateControl, value: string): void {
130
+ if (control.value !== value) {
131
+ control.value = value;
132
+ dispatchControlEvents(control);
133
+ return;
134
+ }
135
+
136
+ syncComboboxValue(control, value);
137
+ }
138
+
139
+ function syncAutofillAnchor(fieldState: FieldState, value: string): void {
140
+ if (!fieldState.autofillAnchor) {
141
+ return;
142
+ }
143
+
144
+ fieldState.autofillAnchor.value = value;
145
+ }
146
+
147
+ function stashPendingStateValue(fieldState: FieldState): void {
148
+ const fromAnchor = fieldState.autofillAnchor?.value?.trim() || '';
149
+ const fromControl = fieldState.stateControl.value?.trim() || '';
150
+ const next = fromAnchor || fromControl;
151
+
152
+ if (next) {
153
+ fieldState.pendingStateValue = next;
154
+ }
155
+ }
156
+
157
+ function getEffectiveStateValue(fieldState: FieldState): string {
158
+ return fieldState.pendingStateValue?.trim()
159
+ || fieldState.autofillAnchor?.value?.trim()
160
+ || fieldState.stateControl.value?.trim()
161
+ || '';
162
+ }
163
+
164
+ function ensureAutofillAnchor(fieldState: FieldState): HTMLInputElement {
165
+ if (fieldState.autofillAnchor) {
166
+ return fieldState.autofillAnchor;
167
+ }
168
+
169
+ const existing = fieldState.addressRoot.querySelector(AUTOFILL_ANCHOR_SELECTOR);
170
+
171
+ if (existing instanceof HTMLInputElement) {
172
+ fieldState.autofillAnchor = existing;
173
+ } else {
174
+ const anchor = document.createElement('input');
175
+ anchor.type = 'text';
176
+ anchor.setAttribute('data-formie-address-state-autofill-anchor', 'true');
177
+ anchor.setAttribute('autocomplete', 'address-level1');
178
+ anchor.setAttribute('tabindex', '-1');
179
+ anchor.setAttribute('aria-hidden', 'true');
180
+ anchor.className = 'formie-sr-only';
181
+ fieldState.addressRoot.appendChild(anchor);
182
+ fieldState.autofillAnchor = anchor;
183
+ }
184
+
185
+ fieldState.stateControl.setAttribute('autocomplete', 'off');
186
+
187
+ if (fieldState.stateControl.value && !fieldState.autofillAnchor.value) {
188
+ fieldState.autofillAnchor.value = fieldState.stateControl.value;
189
+ }
190
+
191
+ return fieldState.autofillAnchor;
192
+ }
193
+
194
+ function reconcileAutofillValue(fieldState: FieldState): void {
195
+ stashPendingStateValue(fieldState);
196
+
197
+ const pending = getEffectiveStateValue(fieldState);
198
+
199
+ if (!pending) {
200
+ return;
201
+ }
202
+
203
+ const subdivisions = fieldState.lastSubdivisions;
204
+ const resolved = subdivisions.length > 0
205
+ ? (resolveOptionMatch(pending, subdivisions) || pending)
206
+ : pending;
207
+
208
+ setControlValue(fieldState.stateControl, resolved);
209
+ syncComboboxValue(fieldState.stateControl, resolved);
210
+ syncAutofillAnchor(fieldState, resolved);
211
+ fieldState.pendingStateValue = resolved;
212
+ }
213
+
214
+ function buildSubdivisionsUrl(action: string, country: string, optionLabel: string, optionValue: string): string {
215
+ const url = new URL(action.startsWith('/') ? action : `/actions/${action}`, window.location.origin);
216
+ url.searchParams.set('country', country);
217
+ url.searchParams.set('optionLabel', optionLabel);
218
+ url.searchParams.set('optionValue', optionValue);
219
+
220
+ return url.toString();
221
+ }
222
+
223
+ function getSubdivisionsCacheKey(
224
+ country: string,
225
+ optionLabel: string,
226
+ optionValue: string,
227
+ action: string,
228
+ ): string {
229
+ return [country, optionLabel, optionValue, action].join('|');
230
+ }
231
+
232
+ function hasResolvedSubdivisions(cacheKey: string): boolean {
233
+ return subdivisionDataCache.has(cacheKey);
234
+ }
235
+
236
+ async function fetchSubdivisions(
237
+ country: string,
238
+ optionLabel: string,
239
+ optionValue: string,
240
+ action: string,
241
+ ): Promise<SubdivisionsResponse | null> {
242
+ const cacheKey = getSubdivisionsCacheKey(country, optionLabel, optionValue, action);
243
+
244
+ if (subdivisionDataCache.has(cacheKey)) {
245
+ return subdivisionDataCache.get(cacheKey) || null;
246
+ }
247
+
248
+ if (!subdivisionInflight.has(cacheKey)) {
249
+ subdivisionInflight.set(cacheKey, (async() => {
250
+ try {
251
+ const response = await fetch(buildSubdivisionsUrl(action, country, optionLabel, optionValue), {
252
+ headers: {
253
+ Accept: 'application/json',
254
+ },
255
+ });
256
+
257
+ if (!response.ok) {
258
+ subdivisionDataCache.set(cacheKey, null);
259
+ return null;
260
+ }
261
+
262
+ const data = await response.json() as SubdivisionsResponse;
263
+ subdivisionDataCache.set(cacheKey, data);
264
+
265
+ return data;
266
+ } catch (error) {
267
+ debug.warn('Failed fetching subdivisions.', { country, error });
268
+ subdivisionDataCache.set(cacheKey, null);
269
+ return null;
270
+ } finally {
271
+ subdivisionInflight.delete(cacheKey);
272
+ }
273
+ })());
274
+ }
275
+
276
+ return subdivisionInflight.get(cacheKey) || null;
277
+ }
278
+
279
+ function resolveOptionMatch(value: string, subdivisions: SubdivisionOption[]): string | null {
280
+ const normalized = value.trim().toLowerCase();
281
+
282
+ if (!normalized) {
283
+ return null;
284
+ }
285
+
286
+ for (const option of subdivisions) {
287
+ if (option.value.toLowerCase() === normalized) {
288
+ return option.value;
289
+ }
290
+
291
+ if ((option.name || '').toLowerCase() === normalized) {
292
+ return option.value;
293
+ }
294
+
295
+ if ((option.short || '').toLowerCase() === normalized) {
296
+ return option.value;
297
+ }
298
+
299
+ if (option.label.toLowerCase() === normalized) {
300
+ return option.value;
301
+ }
302
+ }
303
+
304
+ return null;
305
+ }
306
+
307
+ function copyControlAttributes(source: AddressStateControl, target: AddressStateControl): void {
308
+ const preserve = [
309
+ 'id',
310
+ 'name',
311
+ 'required',
312
+ 'disabled',
313
+ 'placeholder',
314
+ 'aria-describedby',
315
+ 'data-formie-input-id',
316
+ 'data-formie-input-type',
317
+ 'data-formie-input-error-state',
318
+ 'data-formie-address-state-dynamic',
319
+ 'data-formie-address-state-hide-when-unused',
320
+ 'data-formie-address-state-use-searchable',
321
+ 'data-formie-address-state-use-datalist',
322
+ 'data-formie-address-state-option-label',
323
+ 'data-formie-address-state-option-value',
324
+ ];
325
+
326
+ preserve.forEach((attribute) => {
327
+ const value = source.getAttribute(attribute);
328
+
329
+ if (value === null) {
330
+ target.removeAttribute(attribute);
331
+ return;
332
+ }
333
+
334
+ target.setAttribute(attribute, value);
335
+ });
336
+ }
337
+
338
+ function applyTextThemeClasses(template: AddressStateControl, input: HTMLInputElement): void {
339
+ input.className = template.className;
340
+ }
341
+
342
+ function applySelectThemeClasses(template: AddressStateControl, select: HTMLSelectElement): void {
343
+ const classes: string[] = [...SELECT_THEME_CLASSES];
344
+
345
+ if (template.classList.contains('formie-input-error')) {
346
+ classes.push('formie-input-error');
347
+ }
348
+
349
+ select.className = classes.join(' ');
350
+ }
351
+
352
+ function replaceControl(
353
+ current: AddressStateControl,
354
+ next: AddressStateControl,
355
+ ): AddressStateControl {
356
+ current.parentNode?.replaceChild(next, current);
357
+ return next;
358
+ }
359
+
360
+ function createTextControl(template: AddressStateControl, value: string): HTMLInputElement {
361
+ const input = document.createElement('input');
362
+ input.type = 'text';
363
+ copyControlAttributes(template, input);
364
+ applyTextThemeClasses(template, input);
365
+ input.setAttribute('data-formie-input-type', 'text');
366
+ input.setAttribute('data-formie-address-state-input', 'true');
367
+ input.setAttribute('data-formie-single-line-text-input', 'true');
368
+ input.setAttribute('autocomplete', 'off');
369
+ input.removeAttribute('data-formie-combobox-input');
370
+ input.value = value;
371
+
372
+ return input;
373
+ }
374
+
375
+ function populateSelectOptions(
376
+ select: HTMLSelectElement,
377
+ subdivisions: SubdivisionOption[],
378
+ placeholder: string | null,
379
+ selectedValue = '',
380
+ ): void {
381
+ select.innerHTML = '';
382
+
383
+ const emptyOption = document.createElement('option');
384
+ emptyOption.value = '';
385
+ emptyOption.textContent = placeholder || '';
386
+ select.appendChild(emptyOption);
387
+
388
+ subdivisions.forEach((option) => {
389
+ const optionEl = document.createElement('option');
390
+ optionEl.value = option.value;
391
+ optionEl.textContent = option.label;
392
+ select.appendChild(optionEl);
393
+ });
394
+
395
+ select.value = resolveOptionMatch(selectedValue, subdivisions) || selectedValue;
396
+ }
397
+
398
+ function createSelectControl(
399
+ template: AddressStateControl,
400
+ value: string,
401
+ subdivisions: SubdivisionOption[],
402
+ placeholder: string | null,
403
+ ): HTMLSelectElement {
404
+ const select = document.createElement('select');
405
+ copyControlAttributes(template, select);
406
+ applySelectThemeClasses(template, select);
407
+ select.setAttribute('data-formie-input-type', 'select');
408
+ select.setAttribute('data-formie-address-state-input', 'true');
409
+ select.setAttribute('data-formie-select', 'true');
410
+ select.setAttribute('data-formie-dropdown-input', 'true');
411
+ select.removeAttribute('data-formie-single-line-text-input');
412
+ select.setAttribute('autocomplete', 'off');
413
+ populateSelectOptions(select, subdivisions, placeholder, value);
414
+
415
+ return select;
416
+ }
417
+
418
+ function syncDatalist(
419
+ control: HTMLInputElement,
420
+ datalistId: string,
421
+ subdivisions: SubdivisionOption[],
422
+ useDatalist: boolean,
423
+ ): void {
424
+ const existing = control.list;
425
+
426
+ if (!useDatalist || subdivisions.length === 0) {
427
+ control.removeAttribute('list');
428
+
429
+ if (existing) {
430
+ existing.remove();
431
+ }
432
+
433
+ return;
434
+ }
435
+
436
+ let datalist = control.ownerDocument.getElementById(datalistId) as HTMLDataListElement | null;
437
+
438
+ if (!datalist) {
439
+ datalist = control.ownerDocument.createElement('datalist');
440
+ datalist.id = datalistId;
441
+ control.insertAdjacentElement('afterend', datalist);
442
+ }
443
+
444
+ datalist.innerHTML = '';
445
+ subdivisions.forEach((option) => {
446
+ const optionEl = control.ownerDocument.createElement('option');
447
+ optionEl.value = option.label;
448
+ datalist?.appendChild(optionEl);
449
+ });
450
+
451
+ control.setAttribute('list', datalistId);
452
+ }
453
+
454
+ function setFieldVisibility(fieldState: FieldState, visible: boolean): void {
455
+ const { stateField, stateControl, required } = fieldState;
456
+
457
+ stateField.classList.toggle('formie-conditionally-hidden', !visible);
458
+ stateField.toggleAttribute('data-formie-conditionally-hidden', !visible);
459
+
460
+ if (!visible) {
461
+ stateControl.required = false;
462
+ stateControl.disabled = true;
463
+ return;
464
+ }
465
+
466
+ stateControl.disabled = false;
467
+ stateControl.required = required;
468
+ }
469
+
470
+ function ensureFetchingAnnouncement(fieldState: FieldState): HTMLElement {
471
+ if (fieldState.fetchingAnnouncementEl) {
472
+ return fieldState.fetchingAnnouncementEl;
473
+ }
474
+
475
+ const announcement = document.createElement('div');
476
+ announcement.className = 'formie-sr-only';
477
+ announcement.setAttribute('data-formie-address-state-fetching-announce', 'true');
478
+ announcement.setAttribute('aria-live', 'polite');
479
+ announcement.setAttribute('aria-atomic', 'true');
480
+ fieldState.addressRoot.appendChild(announcement);
481
+ fieldState.fetchingAnnouncementEl = announcement;
482
+
483
+ return announcement;
484
+ }
485
+
486
+ function createStateSkeleton(): HTMLElement {
487
+ const skeleton = document.createElement('div');
488
+ skeleton.className = 'formie-address-state-skeleton';
489
+ skeleton.setAttribute('data-formie-address-state-skeleton', 'true');
490
+ skeleton.setAttribute('aria-hidden', 'true');
491
+
492
+ const input = document.createElement('div');
493
+ input.className = 'formie-address-state-skeleton-input';
494
+ skeleton.appendChild(input);
495
+
496
+ return skeleton;
497
+ }
498
+
499
+ function showFetchingUI(fieldState: FieldState): void {
500
+ fieldState.addressRoot.setAttribute('data-formie-address-state-fetching', 'true');
501
+ fieldState.countryControl?.setAttribute('aria-busy', 'true');
502
+ fieldState.stateControl.setAttribute('aria-hidden', 'true');
503
+ fieldState.stateControl.setAttribute('tabindex', '-1');
504
+
505
+ fieldState.stateField.classList.remove('formie-conditionally-hidden');
506
+ fieldState.stateField.removeAttribute('data-formie-conditionally-hidden');
507
+ fieldState.stateField.setAttribute('data-formie-address-state-skeleton-active', 'true');
508
+
509
+ const controlWrapper = fieldState.stateField.querySelector('[data-formie-field-control]');
510
+
511
+ if (controlWrapper instanceof HTMLElement) {
512
+ if (!fieldState.skeletonEl) {
513
+ fieldState.skeletonEl = createStateSkeleton();
514
+ controlWrapper.appendChild(fieldState.skeletonEl);
515
+ }
516
+
517
+ fieldState.skeletonEl.removeAttribute('hidden');
518
+ }
519
+
520
+ ensureFetchingAnnouncement(fieldState).textContent = 'Loading state or province options for the selected country.';
521
+ }
522
+
523
+ function hideFetchingUI(fieldState: FieldState): void {
524
+ fieldState.addressRoot.removeAttribute('data-formie-address-state-fetching');
525
+ fieldState.countryControl?.removeAttribute('aria-busy');
526
+ fieldState.stateControl.removeAttribute('aria-hidden');
527
+ fieldState.stateControl.removeAttribute('tabindex');
528
+ fieldState.stateField.removeAttribute('data-formie-address-state-skeleton-active');
529
+ fieldState.skeletonEl?.setAttribute('hidden', 'hidden');
530
+
531
+ if (fieldState.fetchingAnnouncementEl) {
532
+ fieldState.fetchingAnnouncementEl.textContent = '';
533
+ }
534
+ }
535
+
536
+ function updateFieldLabel(stateField: HTMLElement, label: string): void {
537
+ const labelEl = getFieldLabel(stateField);
538
+
539
+ if (!labelEl) {
540
+ return;
541
+ }
542
+
543
+ const required = labelEl.querySelector('[data-formie-field-required]');
544
+ labelEl.textContent = label;
545
+
546
+ if (required) {
547
+ labelEl.appendChild(required);
548
+ }
549
+ }
550
+
551
+ function initSearchableSelect(
552
+ select: HTMLSelectElement,
553
+ placeholder: string | null,
554
+ ): () => void {
555
+ select.setAttribute('data-formie-combobox-input', 'true');
556
+
557
+ dispatchFieldEvent(select, 'combobox', 'before-init', {
558
+ select,
559
+ options: { placeholder },
560
+ });
561
+
562
+ const cleanup = initFormieCombobox(
563
+ select as Parameters<typeof initFormieCombobox>[0],
564
+ { placeholder },
565
+ );
566
+
567
+ dispatchFieldEvent(select, 'combobox', 'after-init', {
568
+ combobox: (select as Parameters<typeof initFormieCombobox>[0])._formieTomSelect,
569
+ options: { placeholder },
570
+ });
571
+
572
+ return cleanup;
573
+ }
574
+
575
+ async function applyCountryState(
576
+ fieldState: FieldState,
577
+ options: AddressStateOptions,
578
+ ): Promise<void> {
579
+ const {
580
+ hideWhenUnused = true,
581
+ useSearchable = true,
582
+ useDatalist = true,
583
+ optionLabel = 'name',
584
+ optionValue = 'name',
585
+ placeholder = null,
586
+ subdivisionsAction = DEFAULT_SUBDIVISIONS_ACTION,
587
+ } = options;
588
+
589
+ const country = fieldState.countryControl?.value?.trim() || '';
590
+ stashPendingStateValue(fieldState);
591
+ const currentValue = getEffectiveStateValue(fieldState);
592
+ const fetchGeneration = ++fieldState.fetchGeneration;
593
+
594
+ fieldState.comboboxCleanup?.();
595
+ fieldState.comboboxCleanup = null;
596
+
597
+ if (!country) {
598
+ updateFieldLabel(
599
+ fieldState.stateField,
600
+ fieldState.stateField.dataset.formieAddressStateDefaultLabel || 'State / Province',
601
+ );
602
+
603
+ hideFetchingUI(fieldState);
604
+
605
+ if (hideWhenUnused) {
606
+ setFieldVisibility(fieldState, false);
607
+ setControlValue(fieldState.stateControl, '');
608
+ syncAutofillAnchor(fieldState, '');
609
+ fieldState.pendingStateValue = '';
610
+ fieldState.lastSubdivisions = [];
611
+ return;
612
+ }
613
+
614
+ setFieldVisibility(fieldState, true);
615
+
616
+ if (fieldState.stateControl instanceof HTMLSelectElement) {
617
+ const textControl = createTextControl(fieldState.stateControl, currentValue);
618
+ fieldState.stateControl = replaceControl(fieldState.stateControl, textControl);
619
+ } else {
620
+ fieldState.stateControl.disabled = true;
621
+ fieldState.stateControl.placeholder = placeholder || fieldState.stateControl.placeholder;
622
+ }
623
+
624
+ fieldState.lastSubdivisions = [];
625
+ return;
626
+ }
627
+
628
+ const cacheKey = getSubdivisionsCacheKey(country, optionLabel, optionValue, subdivisionsAction);
629
+ const shouldShowFetchingUI = !hasResolvedSubdivisions(cacheKey);
630
+
631
+ if (shouldShowFetchingUI) {
632
+ showFetchingUI(fieldState);
633
+ }
634
+
635
+ const response = await fetchSubdivisions(country, optionLabel, optionValue, subdivisionsAction);
636
+
637
+ if (fetchGeneration !== fieldState.fetchGeneration) {
638
+ return;
639
+ }
640
+
641
+ const subdivisions = response?.subdivisions || [];
642
+ const administrativeAreaUsed = response?.administrativeAreaUsed ?? true;
643
+ const administrativeAreaLabel = response?.administrativeAreaLabel || 'State / Province';
644
+ fieldState.lastSubdivisions = subdivisions;
645
+
646
+ hideFetchingUI(fieldState);
647
+
648
+ if (hideWhenUnused && !administrativeAreaUsed) {
649
+ setFieldVisibility(fieldState, false);
650
+ setControlValue(fieldState.stateControl, '');
651
+ syncAutofillAnchor(fieldState, '');
652
+ fieldState.pendingStateValue = '';
653
+ return;
654
+ }
655
+
656
+ setFieldVisibility(fieldState, true);
657
+ updateFieldLabel(fieldState.stateField, administrativeAreaLabel);
658
+
659
+ if (subdivisions.length > 0) {
660
+ const select = fieldState.stateControl instanceof HTMLSelectElement
661
+ ? fieldState.stateControl
662
+ : createSelectControl(fieldState.stateControl, currentValue, subdivisions, placeholder);
663
+
664
+ if (fieldState.stateControl !== select) {
665
+ fieldState.stateControl = replaceControl(fieldState.stateControl, select);
666
+ } else {
667
+ applySelectThemeClasses(fieldState.stateControl, select);
668
+ populateSelectOptions(select, subdivisions, placeholder, select.value);
669
+ }
670
+
671
+ if (useSearchable) {
672
+ fieldState.comboboxCleanup = initSearchableSelect(select, placeholder);
673
+ } else {
674
+ select.removeAttribute('data-formie-combobox-input');
675
+ }
676
+
677
+ setControlValue(select, resolveOptionMatch(currentValue, subdivisions) || currentValue);
678
+ reconcileAutofillValue(fieldState);
679
+ return;
680
+ }
681
+
682
+ const textControl = fieldState.stateControl instanceof HTMLInputElement
683
+ ? fieldState.stateControl
684
+ : createTextControl(fieldState.stateControl, currentValue);
685
+
686
+ if (fieldState.stateControl !== textControl) {
687
+ fieldState.stateControl = replaceControl(fieldState.stateControl, textControl);
688
+ }
689
+
690
+ textControl.disabled = false;
691
+ syncDatalist(textControl, fieldState.datalistId, subdivisions, useDatalist);
692
+ setControlValue(textControl, currentValue);
693
+ reconcileAutofillValue(fieldState);
694
+ }
695
+
696
+ function scheduleAutofillSweep(fieldState: FieldState, options: AddressStateOptions): void {
697
+ const sweep = () => {
698
+ stashPendingStateValue(fieldState);
699
+
700
+ if (!fieldState.countryControl?.value?.trim()) {
701
+ return;
702
+ }
703
+
704
+ fieldState.lastCountry = '';
705
+
706
+ void applyCountryState(fieldState, options).then(() => {
707
+ reconcileAutofillValue(fieldState);
708
+ });
709
+ };
710
+
711
+ AUTOFILL_SWEEP_DELAYS_MS.forEach((delay) => {
712
+ const timer = window.setTimeout(sweep, delay);
713
+ fieldState.autofillSweepTimers.push(timer);
714
+ });
715
+ }
716
+
717
+ function clearScheduledAutofillTimers(fieldState: FieldState): void {
718
+ if (fieldState.countryChangeTimer !== null) {
719
+ window.clearTimeout(fieldState.countryChangeTimer);
720
+ fieldState.countryChangeTimer = null;
721
+ }
722
+
723
+ fieldState.autofillSweepTimers.forEach((timer) => {
724
+ window.clearTimeout(timer);
725
+ });
726
+ fieldState.autofillSweepTimers = [];
727
+ }
728
+
729
+ function initAddressStateField(field: HTMLElement, options: AddressStateOptions): () => void {
730
+ const addressRoot = getAddressRoot(field);
731
+
732
+ if (!addressRoot) {
733
+ debug.warn('Address root not found; skipping field.');
734
+ return () => {};
735
+ }
736
+
737
+ const stateControl = getStateControl(field);
738
+
739
+ if (!stateControl) {
740
+ debug.warn('Dynamic state control not found; skipping field.');
741
+ return () => {};
742
+ }
743
+
744
+ const labelEl = getFieldLabel(field);
745
+
746
+ if (labelEl && !field.dataset.formieAddressStateDefaultLabel) {
747
+ field.dataset.formieAddressStateDefaultLabel = labelEl.textContent?.trim() || 'State / Province';
748
+ }
749
+
750
+ const fieldState: FieldState = {
751
+ addressRoot,
752
+ stateField: field,
753
+ stateControl,
754
+ countryControl: getCountryControl(addressRoot),
755
+ autofillAnchor: null,
756
+ pendingStateValue: stateControl.value?.trim() || '',
757
+ datalistId: `formie-address-state-datalist-${stateControl.getAttribute('data-formie-input-id') || Math.random().toString(36).slice(2)}`,
758
+ comboboxCleanup: null,
759
+ skeletonEl: null,
760
+ fetchingAnnouncementEl: null,
761
+ autofillSweepTimers: [],
762
+ countryChangeTimer: null,
763
+ lastCountry: '',
764
+ fetchGeneration: 0,
765
+ required: stateControl.required,
766
+ lastSubdivisions: [],
767
+ };
768
+
769
+ ensureAutofillAnchor(fieldState);
770
+
771
+ const refresh = (force = false) => {
772
+ const country = fieldState.countryControl?.value?.trim() || '';
773
+
774
+ if (!force && country === fieldState.lastCountry) {
775
+ return;
776
+ }
777
+
778
+ const previousCountry = fieldState.lastCountry;
779
+ const previousValue = getEffectiveStateValue(fieldState);
780
+ fieldState.lastCountry = country;
781
+
782
+ void applyCountryState(fieldState, options).then(() => {
783
+ reconcileAutofillValue(fieldState);
784
+
785
+ if (!previousCountry || !country || previousCountry === country) {
786
+ return;
787
+ }
788
+
789
+ const resolved = resolveOptionMatch(previousValue, fieldState.lastSubdivisions);
790
+
791
+ if (!resolved && !getEffectiveStateValue(fieldState)) {
792
+ setControlValue(fieldState.stateControl, '');
793
+ syncAutofillAnchor(fieldState, '');
794
+ }
795
+ });
796
+ };
797
+
798
+ const onCountryChange = () => {
799
+ if (fieldState.countryChangeTimer !== null) {
800
+ window.clearTimeout(fieldState.countryChangeTimer);
801
+ }
802
+
803
+ fieldState.countryChangeTimer = window.setTimeout(() => {
804
+ fieldState.countryChangeTimer = null;
805
+ stashPendingStateValue(fieldState);
806
+ refresh(true);
807
+ }, 50);
808
+ };
809
+
810
+ const onAddressInput = (event: Event) => {
811
+ const target = event.target;
812
+
813
+ if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement)) {
814
+ return;
815
+ }
816
+
817
+ const isAnchor = target === fieldState.autofillAnchor;
818
+ const isStateControl = target.matches(STATE_DYNAMIC_SELECTOR)
819
+ || target.matches(STATE_INPUT_SELECTOR);
820
+
821
+ if (!isAnchor && !isStateControl) {
822
+ return;
823
+ }
824
+
825
+ if (isStateControl && !isAnchor) {
826
+ syncAutofillAnchor(fieldState, target.value);
827
+ }
828
+
829
+ stashPendingStateValue(fieldState);
830
+
831
+ if (!fieldState.countryControl?.value?.trim()) {
832
+ return;
833
+ }
834
+
835
+ if (fieldState.lastSubdivisions.length > 0) {
836
+ reconcileAutofillValue(fieldState);
837
+ return;
838
+ }
839
+
840
+ fieldState.lastCountry = '';
841
+
842
+ void applyCountryState(fieldState, options).then(() => {
843
+ reconcileAutofillValue(fieldState);
844
+ });
845
+ };
846
+
847
+ const onAutofillAnimationStart = (event: AnimationEvent) => {
848
+ if (event.animationName !== AUTOFILL_ANIMATION_NAME) {
849
+ return;
850
+ }
851
+
852
+ const target = event.target;
853
+
854
+ if (!(target instanceof HTMLInputElement || target instanceof HTMLSelectElement)) {
855
+ return;
856
+ }
857
+
858
+ if (!addressRoot.contains(target)) {
859
+ return;
860
+ }
861
+
862
+ stashPendingStateValue(fieldState);
863
+
864
+ if (!fieldState.countryControl?.value?.trim()) {
865
+ return;
866
+ }
867
+
868
+ fieldState.lastCountry = '';
869
+
870
+ void applyCountryState(fieldState, options).then(() => {
871
+ reconcileAutofillValue(fieldState);
872
+ });
873
+ };
874
+
875
+ const onProviderPopulate = (event: Event) => {
876
+ const detail = (event as CustomEvent).detail as { state?: string } | undefined;
877
+ const stateValue = detail?.state?.trim();
878
+
879
+ if (!stateValue) {
880
+ refresh();
881
+ return;
882
+ }
883
+
884
+ fieldState.pendingStateValue = stateValue;
885
+ syncAutofillAnchor(fieldState, stateValue);
886
+
887
+ void applyCountryState(fieldState, options).then(() => {
888
+ setControlValue(fieldState.stateControl, stateValue);
889
+ syncComboboxValue(fieldState.stateControl, stateValue);
890
+ fieldState.lastCountry = fieldState.countryControl?.value?.trim() || '';
891
+ });
892
+ };
893
+
894
+ fieldState.countryControl?.addEventListener('change', onCountryChange);
895
+ fieldState.countryControl?.addEventListener('input', onCountryChange);
896
+ addressRoot.addEventListener('input', onAddressInput);
897
+ addressRoot.addEventListener('change', onAddressInput);
898
+ addressRoot.addEventListener('animationstart', onAutofillAnimationStart);
899
+ addressRoot.addEventListener('formie:address:google:populate', onProviderPopulate);
900
+ addressRoot.addEventListener('formie:address:address-finder:populate', onProviderPopulate);
901
+ addressRoot.addEventListener('formie:address:loqate:populate', onProviderPopulate);
902
+ addressRoot.addEventListener('formie:address:place-kit:populate', onProviderPopulate);
903
+
904
+ refresh();
905
+ scheduleAutofillSweep(fieldState, options);
906
+
907
+ return () => {
908
+ clearScheduledAutofillTimers(fieldState);
909
+ hideFetchingUI(fieldState);
910
+ fieldState.skeletonEl?.remove();
911
+ fieldState.fetchingAnnouncementEl?.remove();
912
+ fieldState.autofillAnchor?.remove();
913
+ fieldState.comboboxCleanup?.();
914
+ fieldState.countryControl?.removeEventListener('change', onCountryChange);
915
+ fieldState.countryControl?.removeEventListener('input', onCountryChange);
916
+ addressRoot.removeEventListener('input', onAddressInput);
917
+ addressRoot.removeEventListener('change', onAddressInput);
918
+ addressRoot.removeEventListener('animationstart', onAutofillAnimationStart);
919
+ addressRoot.removeEventListener('formie:address:google:populate', onProviderPopulate);
920
+ addressRoot.removeEventListener('formie:address:address-finder:populate', onProviderPopulate);
921
+ addressRoot.removeEventListener('formie:address:loqate:populate', onProviderPopulate);
922
+ addressRoot.removeEventListener('formie:address:place-kit:populate', onProviderPopulate);
923
+ };
924
+ }
925
+
926
+ export const addressStateModule: FormieModuleDefinition = {
927
+ id: MODULE_ID,
928
+ kind: 'field',
929
+ match: (ctx) => {
930
+ return !!ctx.target.querySelector(STATE_DYNAMIC_SELECTOR);
931
+ },
932
+ setup: async (ctx) => {
933
+ const options = (ctx.options || {}) as AddressStateOptions;
934
+ const fields = getModuleFieldContainers(ctx);
935
+ const cleanups = fields.map((field) => initAddressStateField(field, options));
936
+
937
+ debug.log('Module setup.', { fieldCount: fields.length });
938
+
939
+ return {
940
+ destroy: () => {
941
+ cleanups.forEach((cleanup) => cleanup());
942
+ debug.log('Module destroy.', { fieldCount: fields.length });
943
+ },
944
+ };
945
+ },
946
+ };