@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,246 @@
1
+ /*
2
+ * Combobox styles are intentionally unlayered. The combobox module injects this
3
+ * sheet at runtime, and unlayered third-party CSS would otherwise win over
4
+ * @layer formie-theme rules.
5
+ *
6
+ * Tom Select default CSS is not imported; these rules provide the minimum
7
+ * structure plus Formie theme styling for searchable dropdowns.
8
+ */
9
+
10
+ .formie-field .ts-wrapper.formie-combobox {
11
+ width: 100%;
12
+ display: block;
13
+ position: relative;
14
+ min-height: 0;
15
+ border: 0;
16
+ padding: 0;
17
+ background: none;
18
+ box-shadow: none;
19
+ }
20
+
21
+ .formie-field select[data-formie-combobox-input].tomselected,
22
+ .formie-field select[data-formie-combobox-input].ts-hidden-accessible {
23
+ display: none !important;
24
+ }
25
+
26
+ .formie-field .ts-wrapper.formie-combobox .ts-control {
27
+ box-sizing: border-box;
28
+ display: flex;
29
+ flex-wrap: wrap;
30
+ align-items: center;
31
+ position: relative;
32
+ overflow: hidden;
33
+ z-index: 1;
34
+ width: 100%;
35
+ border: var(--formie-border-width) solid var(--formie-color-border);
36
+ border-radius: var(--formie-radius-sm);
37
+ background-color: var(--formie-color-surface);
38
+ background-image: none;
39
+ min-height: var(--formie-control-height);
40
+ padding: calc(var(--formie-control-padding-y) - 1px) var(--formie-control-padding-x);
41
+ box-shadow: none;
42
+ font-size: var(--formie-control-font-size);
43
+ line-height: var(--formie-line-height-tight);
44
+ color: var(--formie-color-text);
45
+ transition: border-color 150ms ease, box-shadow 150ms ease, background-color 150ms ease;
46
+ gap: var(--formie-space-1);
47
+ }
48
+
49
+ .formie-field .ts-wrapper.formie-combobox.single .ts-control {
50
+ --ts-pr-min: var(--formie-control-padding-x);
51
+ --ts-pr-caret: calc(var(--formie-select-indicator-size) + var(--formie-space-1));
52
+ padding-right: calc(var(--formie-control-padding-x) + var(--formie-select-indicator-size) + var(--formie-space-2)) !important;
53
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='none'%3E%3Cpath d='M7 7l3-3 3 3m0 6l-3 3-3-3' stroke='%239CA3AF' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
54
+ background-position: right var(--formie-space-2) center;
55
+ background-repeat: no-repeat;
56
+ background-size: var(--formie-select-indicator-size) var(--formie-select-indicator-size);
57
+ }
58
+
59
+ .formie-field .ts-wrapper.formie-combobox.single .ts-control::after {
60
+ display: none;
61
+ }
62
+
63
+ .formie-field .ts-wrapper.formie-combobox.multi .ts-control {
64
+ padding: calc(var(--formie-control-padding-y) - 2px) var(--formie-control-padding-x) calc(var(--formie-control-padding-y) - 4px);
65
+ --ts-pr-min: var(--formie-control-padding-x);
66
+ }
67
+
68
+ .formie-field .ts-wrapper.formie-combobox.multi.has-items .ts-control {
69
+ padding-left: var(--formie-control-padding-x);
70
+ }
71
+
72
+ .formie-field .ts-wrapper.formie-combobox .ts-control > input {
73
+ flex: 1 1 auto;
74
+ min-width: 7rem;
75
+ max-width: 100%;
76
+ margin: 0 !important;
77
+ padding: 0 !important;
78
+ min-height: 0 !important;
79
+ max-height: none !important;
80
+ border: 0 !important;
81
+ background: none !important;
82
+ box-shadow: none !important;
83
+ color: var(--formie-color-text);
84
+ font: inherit;
85
+ font-size: var(--formie-control-font-size);
86
+ line-height: inherit;
87
+ }
88
+
89
+ .formie-field .ts-wrapper.formie-combobox .ts-control > input:focus {
90
+ outline: none !important;
91
+ }
92
+
93
+ .formie-field .ts-wrapper.formie-combobox.has-items .ts-control > input {
94
+ margin: 0 var(--formie-space-1) !important;
95
+ }
96
+
97
+ .formie-field .ts-wrapper.formie-combobox.input-hidden .ts-control > input {
98
+ opacity: 0;
99
+ position: absolute;
100
+ left: -10000px;
101
+ }
102
+
103
+ .formie-field .ts-wrapper.formie-combobox.single .ts-control,
104
+ .formie-field .ts-wrapper.formie-combobox.single .ts-control > input {
105
+ cursor: pointer;
106
+ }
107
+
108
+ .formie-field .ts-wrapper.formie-combobox.single.input-active .ts-control,
109
+ .formie-field .ts-wrapper.formie-combobox.single.input-active .ts-control > input {
110
+ cursor: text;
111
+ }
112
+
113
+ .formie-field .ts-wrapper.formie-combobox .ts-control .items-placeholder {
114
+ color: var(--formie-color-text-muted);
115
+ font: inherit;
116
+ font-size: var(--formie-control-font-size);
117
+ }
118
+
119
+ .formie-field .ts-wrapper.formie-combobox .ts-control > input::placeholder {
120
+ color: var(--formie-color-text-muted);
121
+ }
122
+
123
+ .formie-field .ts-wrapper.formie-combobox.focus .ts-control,
124
+ .formie-field .ts-wrapper.formie-combobox .ts-control:focus-within,
125
+ .formie-field .ts-wrapper.formie-combobox.dropdown-active .ts-control {
126
+ outline: 0;
127
+ border-color: var(--formie-color-focus-ring);
128
+ box-shadow: var(--formie-shadow-focus);
129
+ border-radius: var(--formie-radius-sm);
130
+ }
131
+
132
+ .formie-field-has-error .ts-wrapper.formie-combobox .ts-control {
133
+ border-color: var(--formie-color-danger);
134
+ }
135
+
136
+ .formie-field-has-error .ts-wrapper.formie-combobox.focus .ts-control,
137
+ .formie-field-has-error .ts-wrapper.formie-combobox .ts-control:focus-within,
138
+ .formie-field-has-error .ts-wrapper.formie-combobox.dropdown-active .ts-control {
139
+ border-color: var(--formie-color-danger);
140
+ box-shadow: var(--formie-shadow-danger-focus);
141
+ }
142
+
143
+ .formie-field .ts-wrapper.formie-combobox.multi .ts-control > .item,
144
+ .formie-field .ts-wrapper.formie-combobox.multi .ts-control [data-value] {
145
+ display: inline-flex;
146
+ align-items: center;
147
+ gap: var(--formie-space-1);
148
+ margin: 0 var(--formie-space-1) var(--formie-space-1) 0;
149
+ padding: 0 var(--formie-space-2);
150
+ background: var(--formie-color-surface-muted, rgba(15, 23, 42, 0.06));
151
+ background-image: none;
152
+ border: var(--formie-border-width) solid var(--formie-color-border);
153
+ border-radius: var(--formie-radius-sm);
154
+ box-shadow: none;
155
+ color: var(--formie-color-text);
156
+ text-shadow: none;
157
+ line-height: calc(var(--formie-control-height) - var(--formie-space-3));
158
+ }
159
+
160
+ .formie-field .ts-wrapper.formie-combobox.multi .ts-control > .item.active,
161
+ .formie-field .ts-wrapper.formie-combobox.multi .ts-control [data-value].active {
162
+ background: var(--formie-color-surface-muted, rgba(15, 23, 42, 0.06));
163
+ color: var(--formie-color-text);
164
+ }
165
+
166
+ .formie-field .ts-wrapper.formie-combobox.plugin-remove_button .item {
167
+ display: inline-flex;
168
+ align-items: center;
169
+ gap: var(--formie-space-1);
170
+ }
171
+
172
+ .formie-field .ts-wrapper.formie-combobox.plugin-remove_button:not(.rtl) .item {
173
+ padding-right: var(--formie-space-2) !important;
174
+ }
175
+
176
+ .formie-field .ts-wrapper.formie-combobox.plugin-remove_button .item .remove {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ margin: 0;
181
+ padding: 0;
182
+ border: 0;
183
+ background: none;
184
+ box-shadow: none;
185
+ text-decoration: none;
186
+ color: var(--formie-color-text-muted);
187
+ font-size: 1.125em;
188
+ line-height: 1;
189
+ cursor: pointer;
190
+ }
191
+
192
+ .formie-field .ts-wrapper.formie-combobox.plugin-remove_button .item .remove:hover,
193
+ .formie-field .ts-wrapper.formie-combobox.plugin-remove_button .item .remove:focus {
194
+ background: none;
195
+ text-decoration: none;
196
+ color: var(--formie-color-text);
197
+ }
198
+
199
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown {
200
+ position: absolute;
201
+ top: 100%;
202
+ left: 0;
203
+ width: 100%;
204
+ z-index: var(--formie-z-popover, 30);
205
+ border: var(--formie-border-width) solid var(--formie-color-border);
206
+ border-radius: var(--formie-radius-sm);
207
+ background: var(--formie-color-surface);
208
+ box-shadow: var(--formie-shadow-popover, 0 8px 24px rgba(15, 23, 42, 0.12));
209
+ margin-top: var(--formie-space-1);
210
+ box-sizing: border-box;
211
+ }
212
+
213
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown-content {
214
+ overflow: hidden auto;
215
+ max-height: 200px;
216
+ scroll-behavior: smooth;
217
+ }
218
+
219
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .option,
220
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .optgroup-header,
221
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .no-results {
222
+ color: var(--formie-color-text);
223
+ padding: var(--formie-space-2) var(--formie-control-padding-x);
224
+ }
225
+
226
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown [data-selectable] {
227
+ cursor: pointer;
228
+ }
229
+
230
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .option.active,
231
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .option:hover,
232
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .active {
233
+ background: var(--formie-color-surface-muted, rgba(15, 23, 42, 0.05));
234
+ color: var(--formie-color-text);
235
+ }
236
+
237
+ .formie-field .ts-wrapper.formie-combobox .ts-dropdown .highlight {
238
+ background: rgba(59, 130, 246, 0.18);
239
+ border-radius: 2px;
240
+ }
241
+
242
+ .formie-field .ts-wrapper.formie-combobox.disabled .ts-control {
243
+ opacity: 0.6;
244
+ background-color: var(--formie-color-surface-muted, rgba(15, 23, 42, 0.04));
245
+ cursor: not-allowed;
246
+ }
@@ -34,4 +34,17 @@
34
34
  border-color: var(--formie-color-danger);
35
35
  box-shadow: var(--formie-shadow-danger-focus);
36
36
  }
37
+
38
+ /* Tom Select copies native select classes onto its wrapper; keep combobox chrome on .ts-control only. */
39
+ .formie-field .ts-wrapper.formie-combobox,
40
+ .formie-field .ts-wrapper.formie-combobox.formie-select,
41
+ .formie-field .ts-wrapper.formie-combobox.formie-dropdown-input {
42
+ border: 0;
43
+ padding: 0;
44
+ min-height: 0;
45
+ background: none;
46
+ background-image: none;
47
+ box-shadow: none;
48
+ appearance: none;
49
+ }
37
50
  }
@@ -13,6 +13,11 @@ import type { ThemeClassMap } from '#contracts/theme';
13
13
  import { dispatchFormieDomEvent } from '#core/dom-events';
14
14
  import { dispatchPageClientEventForSubmit } from '#core/page-client-event';
15
15
  import { syncPageTabErrors } from '#core/page-tab-errors';
16
+ import {
17
+ enhanceServerRenderedFieldErrors,
18
+ focusFirstValidationError,
19
+ hasServerRenderedValidationErrors,
20
+ } from '#core/validation-focus';
16
21
  import { clearSubmitFeedback, executeAjaxSubmitFlow, shouldKeepSubmitLoading } from '#core/submit-flow';
17
22
  import { applySubmitResultUi } from '#core/submit-result-ui';
18
23
  import { applyPageState, clearSubmitLoading, setSubmitLoading } from '#core/submit-result-state';
@@ -24,6 +29,7 @@ import { runSubmitPipeline } from '#submit/pipeline';
24
29
  import { clearSubmissionOnUnload, requestGraphqlRender, requestRefreshTokens, requestRender, requestSetPage } from '#transport/forms-api';
25
30
  import { createDebug } from '#utils/debug';
26
31
  import { createFormUnloadWarningGuard } from '#utils/unload-warning';
32
+ import { getValidationScope } from '#validation/scope';
27
33
  import { FormieValidator } from '#validation/validator';
28
34
  import { bindSubmitReadiness } from '#validation/submit-readiness';
29
35
 
@@ -745,6 +751,22 @@ function bindFormEvents(
745
751
  allowNativeSubmit = false;
746
752
  clearInternalNavigation(form);
747
753
  clearSubmitLoading(form);
754
+
755
+ if (validator && shouldValidateOnSubmit(form)) {
756
+ const { scope, final } = getValidationScope(form);
757
+ const validationErrors = validator.submit(final ? form : scope, { final });
758
+
759
+ if (validationErrors.length > 0) {
760
+ applySubmitResultUi(form, {
761
+ ok: false,
762
+ stage: 'validate',
763
+ code: 'VALIDATION_FAILED',
764
+ message: validator.config.errorMessage || 'Validation failed.',
765
+ fieldErrors: validator.getFieldErrors(validationErrors),
766
+ formErrors: [validator.config.errorMessage || 'Validation failed.'],
767
+ });
768
+ }
769
+ }
748
770
  };
749
771
 
750
772
  if (typeof form.requestSubmit === 'function') {
@@ -996,6 +1018,11 @@ export function createFormieClient(): FormieClient {
996
1018
  normalizeHeadlessManagedUrls(target, form, normalizedOptions);
997
1019
  }
998
1020
 
1021
+ if (normalizedOptions.mode === 'server-rendered' && hasServerRenderedValidationErrors(form)) {
1022
+ enhanceServerRenderedFieldErrors(form);
1023
+ focusFirstValidationError(form);
1024
+ }
1025
+
999
1026
  syncPageTabErrors(form);
1000
1027
  }
1001
1028
 
@@ -1,6 +1,7 @@
1
1
  import type { FormSubmitResult } from '#contracts/schema';
2
2
  import { ensureFieldErrorContainer as resolveFieldErrorContainer } from '#core/field-error-container';
3
3
  import { syncPageTabErrors } from '#core/page-tab-errors';
4
+ import { focusFirstValidationError } from '#core/validation-focus';
4
5
  import { setFormHiddenState } from '#core/submit-result-state';
5
6
  import { addThemeClasses, removeThemeClasses } from '#theme/theme-classes';
6
7
 
@@ -433,10 +434,9 @@ export function applySubmitResultUi(form: HTMLFormElement, result: FormSubmitRes
433
434
 
434
435
  if (result.formErrors?.length) {
435
436
  renderFormErrors(form, result.formErrors);
436
- return;
437
- }
438
-
439
- if (!result.fieldErrors && result.message) {
437
+ } else if (!result.fieldErrors && result.message) {
440
438
  renderFormErrors(form, [result.message]);
441
439
  }
440
+
441
+ focusFirstValidationError(form);
442
442
  }
@@ -0,0 +1,117 @@
1
+ import { addThemeClasses } from '#theme/theme-classes';
2
+
3
+ function appendDescribedBy(input: HTMLElement, describedById: string): void {
4
+ const current = (input.getAttribute('aria-describedby') || '').trim();
5
+ const items = current ? current.split(/\s+/) : [];
6
+
7
+ if (!items.includes(describedById)) {
8
+ items.push(describedById);
9
+ }
10
+
11
+ input.setAttribute('aria-describedby', items.join(' ').trim());
12
+ }
13
+
14
+ function getFirstErroredField(form: HTMLFormElement): HTMLElement | null {
15
+ const fields = Array.from(form.querySelectorAll('[data-formie-field-handle]')) as HTMLElement[];
16
+
17
+ return fields.find((field) => {
18
+ if (field.getAttribute('data-formie-field-has-error') === 'true') {
19
+ return true;
20
+ }
21
+
22
+ return field.querySelector('[data-formie-field-error]') !== null;
23
+ }) || null;
24
+ }
25
+
26
+ function getFieldFocusTarget(field: HTMLElement): HTMLElement | null {
27
+ const invalidControl = field.querySelector('[aria-invalid="true"]') as HTMLElement | null;
28
+
29
+ if (invalidControl) {
30
+ return invalidControl;
31
+ }
32
+
33
+ return field.querySelector(
34
+ 'input:not([type="hidden"]):not([disabled]), select:not([disabled]), textarea:not([disabled])',
35
+ ) as HTMLElement | null;
36
+ }
37
+
38
+ function getFormLevelErrorTarget(form: HTMLFormElement): HTMLElement | null {
39
+ return form.querySelector(
40
+ '[data-formie-message-error], [data-formie-error-container], [data-formie-errors]',
41
+ ) as HTMLElement | null;
42
+ }
43
+
44
+ export function enhanceServerRenderedFieldErrors(form: HTMLFormElement): void {
45
+ form.querySelectorAll('[data-formie-field-handle]').forEach((fieldNode) => {
46
+ const field = fieldNode as HTMLElement;
47
+ const hasServerError = field.getAttribute('data-formie-field-has-error') === 'true'
48
+ || field.querySelector('[data-formie-field-error]') !== null;
49
+
50
+ if (!hasServerError) {
51
+ return;
52
+ }
53
+
54
+ field.setAttribute('data-formie-field-has-error', 'true');
55
+ addThemeClasses(field, form, 'fieldLayoutError');
56
+
57
+ const errorContainer = field.querySelector('[data-formie-field-errors]') as HTMLElement | null;
58
+ const errorContainerId = errorContainer?.id || '';
59
+ const primaryError = field.querySelector('[data-formie-field-error]') as HTMLElement | null;
60
+ const primaryErrorId = primaryError?.id || '';
61
+
62
+ field.querySelectorAll('input, select, textarea').forEach((input) => {
63
+ const element = input as HTMLElement;
64
+ element.setAttribute('aria-invalid', 'true');
65
+ addThemeClasses(element, form, 'fieldControlError');
66
+ element.setAttribute('data-formie-input-has-error', 'true');
67
+
68
+ if (errorContainerId) {
69
+ appendDescribedBy(element, errorContainerId);
70
+ }
71
+
72
+ if (primaryErrorId) {
73
+ element.setAttribute('aria-errormessage', primaryErrorId);
74
+ }
75
+ });
76
+ });
77
+ }
78
+
79
+ export function hasServerRenderedValidationErrors(form: HTMLFormElement): boolean {
80
+ return !!getFirstErroredField(form) || !!getFormLevelErrorTarget(form);
81
+ }
82
+
83
+ export function focusFirstValidationError(form: HTMLFormElement): boolean {
84
+ const field = getFirstErroredField(form);
85
+
86
+ if (field) {
87
+ const target = getFieldFocusTarget(field);
88
+
89
+ if (target) {
90
+ target.scrollIntoView({ behavior: 'smooth', block: 'center' });
91
+
92
+ if (typeof target.focus === 'function') {
93
+ try {
94
+ target.focus({ preventScroll: true });
95
+ } catch {
96
+ target.focus();
97
+ }
98
+ }
99
+
100
+ return true;
101
+ }
102
+
103
+ field.scrollIntoView({ behavior: 'smooth', block: 'center' });
104
+
105
+ return true;
106
+ }
107
+
108
+ const formError = getFormLevelErrorTarget(form);
109
+
110
+ if (!formError) {
111
+ return false;
112
+ }
113
+
114
+ formError.scrollIntoView({ behavior: 'smooth', block: 'center' });
115
+
116
+ return true;
117
+ }
@@ -8,6 +8,13 @@ import type {
8
8
  AddressModuleOptions as AddressModuleManifestOptions,
9
9
  } from '#modules/address/host';
10
10
 
11
+ export {
12
+ ADDRESS_LEGACY_SELECTORS,
13
+ ADDRESS_SELECTORS,
14
+ findAddressFieldInput,
15
+ type AddressFieldInputKey,
16
+ } from '#modules/address/constants';
17
+
11
18
  // This file is the intended authoring surface for address providers.
12
19
  // Built-in providers import from here so third-party authors can follow the same patterns.
13
20
  export const defineAddressModule = createManagedAddressModule;
@@ -2,7 +2,21 @@
2
2
  export const DEFAULT_AUTOCOMPLETE_SELECTOR = '[data-formie-address-autocomplete-input]';
3
3
  export const ADDRESS_LOCATION_SELECTOR = '[data-formie-address-location]';
4
4
 
5
- /** Selector for address sub-field inputs used when populating from provider. */
5
+ /** Keys for address sub-field inputs targeted by provider modules. */
6
+ export type AddressFieldInputKey =
7
+ | 'autoComplete'
8
+ | 'address1'
9
+ | 'address2'
10
+ | 'address3'
11
+ | 'city'
12
+ | 'state'
13
+ | 'zip'
14
+ | 'country';
15
+
16
+ /**
17
+ * Canonical address sub-field selectors (PHP output, UI reference, provider modules).
18
+ * All address-owned runtime hooks use the `data-formie-address-*` namespace.
19
+ */
6
20
  export const ADDRESS_SELECTORS = {
7
21
  autoComplete: '[data-formie-address-autocomplete-input]',
8
22
  address1: '[data-formie-address-line1-input]',
@@ -12,4 +26,70 @@ export const ADDRESS_SELECTORS = {
12
26
  state: '[data-formie-address-state-input]',
13
27
  zip: '[data-formie-address-zip-input]',
14
28
  country: '[data-formie-address-country-input]',
15
- } as const;
29
+ } as const satisfies Record<AddressFieldInputKey, string>;
30
+
31
+ /**
32
+ * Legacy bare hooks still present on forms saved before the selector normalization.
33
+ * `findAddressFieldInput()` checks canonical selectors first, then these.
34
+ */
35
+ export const ADDRESS_LEGACY_SELECTORS = {
36
+ autoComplete: '[data-formie-address-autocomplete-input]',
37
+ address1: '[data-address1]',
38
+ address2: '[data-address2]',
39
+ address3: '[data-address3]',
40
+ city: '[data-city]',
41
+ state: '[data-state]',
42
+ zip: '[data-zip]',
43
+ country: '[data-country]',
44
+ } as const satisfies Record<AddressFieldInputKey, string>;
45
+
46
+ const ADDRESS_FIELD_INPUT_SELECTOR_ORDER: Record<AddressFieldInputKey, readonly string[]> = {
47
+ autoComplete: [
48
+ ADDRESS_SELECTORS.autoComplete,
49
+ ADDRESS_LEGACY_SELECTORS.autoComplete,
50
+ ],
51
+ address1: [
52
+ ADDRESS_SELECTORS.address1,
53
+ ADDRESS_LEGACY_SELECTORS.address1,
54
+ ],
55
+ address2: [
56
+ ADDRESS_SELECTORS.address2,
57
+ ADDRESS_LEGACY_SELECTORS.address2,
58
+ ],
59
+ address3: [
60
+ ADDRESS_SELECTORS.address3,
61
+ ADDRESS_LEGACY_SELECTORS.address3,
62
+ ],
63
+ city: [
64
+ ADDRESS_SELECTORS.city,
65
+ ADDRESS_LEGACY_SELECTORS.city,
66
+ ],
67
+ state: [
68
+ ADDRESS_SELECTORS.state,
69
+ ADDRESS_LEGACY_SELECTORS.state,
70
+ ],
71
+ zip: [
72
+ ADDRESS_SELECTORS.zip,
73
+ ADDRESS_LEGACY_SELECTORS.zip,
74
+ ],
75
+ country: [
76
+ ADDRESS_SELECTORS.country,
77
+ ADDRESS_LEGACY_SELECTORS.country,
78
+ ],
79
+ };
80
+
81
+ /** Resolve an address sub-field input against canonical and legacy selectors. */
82
+ export function findAddressFieldInput(
83
+ scope: ParentNode,
84
+ key: AddressFieldInputKey,
85
+ ): HTMLInputElement | HTMLSelectElement | null {
86
+ for (const selector of ADDRESS_FIELD_INPUT_SELECTOR_ORDER[key]) {
87
+ const candidate = scope.querySelector(selector);
88
+
89
+ if (candidate instanceof HTMLInputElement || candidate instanceof HTMLSelectElement) {
90
+ return candidate;
91
+ }
92
+ }
93
+
94
+ return null;
95
+ }
@@ -1,8 +1,9 @@
1
1
  import type { ModuleSetupContext } from '#contracts/modules';
2
2
  import {
3
3
  ADDRESS_LOCATION_SELECTOR,
4
- ADDRESS_SELECTORS,
5
4
  DEFAULT_AUTOCOMPLETE_SELECTOR,
5
+ findAddressFieldInput,
6
+ type AddressFieldInputKey,
6
7
  } from '#modules/address/constants';
7
8
 
8
9
  type Cleanup = () => void;
@@ -24,7 +25,7 @@ export type AddressHostServices = {
24
25
  form: HTMLFormElement | null;
25
26
  input: {
26
27
  getAutocomplete: () => HTMLInputElement | null;
27
- setValue: (selector: keyof typeof ADDRESS_SELECTORS, value: string, fallback?: string) => void;
28
+ setValue: (selector: AddressFieldInputKey, value: string, fallback?: string) => void;
28
29
  };
29
30
  location: {
30
31
  getButton: () => HTMLElement | null;
@@ -90,12 +91,21 @@ export function createAddressHostServices(
90
91
  return field.querySelector(autocompleteSelector) as HTMLInputElement | null;
91
92
  },
92
93
  setValue: (selectorKey, value, fallback) => {
93
- const selector = ADDRESS_SELECTORS[selectorKey];
94
- const el = field.querySelector(selector) as HTMLInputElement | null;
94
+ const el = findAddressFieldInput(field, selectorKey);
95
95
 
96
- if (el) {
97
- el.value = value || fallback || '';
96
+ if (!el) {
97
+ return;
98
98
  }
99
+
100
+ const nextValue = value || fallback || '';
101
+
102
+ if (el.value === nextValue) {
103
+ return;
104
+ }
105
+
106
+ el.value = nextValue;
107
+ el.dispatchEvent(new Event('input', { bubbles: true }));
108
+ el.dispatchEvent(new Event('change', { bubbles: true }));
99
109
  },
100
110
  },
101
111
  location: {
@@ -1,5 +1,5 @@
1
1
  import { defineAddressModule } from '#modules/address/api';
2
- import { ADDRESS_SELECTORS } from '#modules/address/constants';
2
+ import { findAddressFieldInput, type AddressFieldInputKey } from '#modules/address/constants';
3
3
  import { loadScriptAndEnsureGlobal } from '#utils/scripts';
4
4
 
5
5
  type PcaFieldMode = {
@@ -63,27 +63,13 @@ export const loqateModule = defineAddressModule<
63
63
  throw new Error('Loqate API key is required');
64
64
  }
65
65
 
66
- const handleToSelector: Record<string, string> = {
67
- autoComplete: ADDRESS_SELECTORS.autoComplete,
68
- address1: ADDRESS_SELECTORS.address1,
69
- address2: ADDRESS_SELECTORS.address2,
70
- address3: ADDRESS_SELECTORS.address3,
71
- city: ADDRESS_SELECTORS.city,
72
- state: ADDRESS_SELECTORS.state,
73
- zip: ADDRESS_SELECTORS.zip,
74
- country: ADDRESS_SELECTORS.country,
75
- };
76
-
77
66
  // Loqate PCA expects element to be an id string, name string, or RegExp matching ids – not CSS selectors
78
- const getLoqateElementRef = (handle: string): string => {
67
+ const getLoqateElementRef = (handle: AddressFieldInputKey): string => {
79
68
  if (namespace) {
80
69
  return `${namespace}[${handle}]`;
81
70
  }
82
71
 
83
- const cssSelector = handleToSelector[handle];
84
- const el = cssSelector
85
- ? (field.querySelector(cssSelector) as HTMLInputElement | null)
86
- : null;
72
+ const el = findAddressFieldInput(field, handle);
87
73
 
88
74
  if (el?.name) {
89
75
  return el.name;