@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
@@ -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
 
@@ -0,0 +1,39 @@
1
+ export type FieldErrorPosition = 'above' | 'below';
2
+
3
+ export function getFieldErrorPosition(fieldNode: Element): FieldErrorPosition {
4
+ const layout = fieldNode.querySelector('[data-formie-field-layout]');
5
+ const position = layout?.getAttribute('data-formie-error-position')?.trim();
6
+
7
+ return position === 'above' ? 'above' : 'below';
8
+ }
9
+
10
+ export function ensureFieldErrorContainer(
11
+ fieldNode: Element,
12
+ applyTheme?: (container: HTMLElement) => void,
13
+ ): HTMLElement {
14
+ const existing = fieldNode.querySelector('[data-formie-field-errors]') as HTMLElement | null;
15
+
16
+ if (existing) {
17
+ return existing;
18
+ }
19
+
20
+ const content = fieldNode.querySelector('[data-formie-field-content]') as HTMLElement | null;
21
+ const control = fieldNode.querySelector('[data-formie-field-control]') as HTMLElement | null;
22
+ const position = getFieldErrorPosition(fieldNode);
23
+ const container = document.createElement('div');
24
+
25
+ container.setAttribute('data-formie-field-errors', 'true');
26
+ applyTheme?.(container);
27
+
28
+ if (content && control) {
29
+ if (position === 'above') {
30
+ content.insertBefore(container, control);
31
+ } else {
32
+ content.appendChild(container);
33
+ }
34
+ } else {
35
+ fieldNode.appendChild(container);
36
+ }
37
+
38
+ return container;
39
+ }
@@ -102,6 +102,16 @@ function getConfiguredSubmitAction(form: HTMLFormElement): string {
102
102
  return (form.dataset.formieSubmitAction || '').trim();
103
103
  }
104
104
 
105
+ function getResolvedSubmitAction(form: HTMLFormElement, result: FormSubmitResult): string {
106
+ const fromResponse = result.meta?.effectiveSubmitAction;
107
+
108
+ if (typeof fromResponse === 'string' && fromResponse.trim() !== '') {
109
+ return fromResponse.trim();
110
+ }
111
+
112
+ return getConfiguredSubmitAction(form);
113
+ }
114
+
105
115
  function shouldHideFormOnSuccess(form: HTMLFormElement): boolean {
106
116
  const rawValue = form.dataset.formieSubmitActionFormHide;
107
117
 
@@ -580,7 +590,7 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
580
590
  }
581
591
 
582
592
  if (action === 'submit' && !result.redirect?.url) {
583
- const configuredSubmitAction = getConfiguredSubmitAction(form);
593
+ const configuredSubmitAction = getResolvedSubmitAction(form, result);
584
594
  const preserveHiddenState = configuredSubmitAction === 'message' && shouldHideFormOnSuccess(form);
585
595
 
586
596
  if (configuredSubmitAction === 'reload') {
@@ -603,7 +613,7 @@ export function applySubmitResultState(form: HTMLFormElement, result: FormSubmit
603
613
  }
604
614
 
605
615
  if (action === 'submit' && result.redirect?.url && result.redirect.target === 'new-tab') {
606
- const configuredSubmitAction = getConfiguredSubmitAction(form);
616
+ const configuredSubmitAction = getResolvedSubmitAction(form, result);
607
617
  const preserveHiddenState = configuredSubmitAction === 'message' && shouldHideFormOnSuccess(form);
608
618
  clearPendingFinalSubmitReset(form);
609
619
  resetSubmissionState(form, { preserveHiddenState });
@@ -1,5 +1,7 @@
1
1
  import type { FormSubmitResult } from '#contracts/schema';
2
+ import { ensureFieldErrorContainer as resolveFieldErrorContainer } from '#core/field-error-container';
2
3
  import { syncPageTabErrors } from '#core/page-tab-errors';
4
+ import { focusFirstValidationError } from '#core/validation-focus';
3
5
  import { setFormHiddenState } from '#core/submit-result-state';
4
6
  import { addThemeClasses, removeThemeClasses } from '#theme/theme-classes';
5
7
 
@@ -150,16 +152,9 @@ function ensureFormSuccessContainer(form: HTMLFormElement, position: string): HT
150
152
  }
151
153
 
152
154
  function ensureFieldErrorContainer(fieldNode: Element): HTMLElement {
153
- let container = fieldNode.querySelector('[data-formie-field-errors]') as HTMLElement | null;
154
-
155
- if (!container) {
156
- container = document.createElement('div');
157
- container.setAttribute('data-formie-field-errors', 'true');
155
+ return resolveFieldErrorContainer(fieldNode, (container) => {
158
156
  addThemeClasses(container, fieldNode, 'fieldErrors');
159
- fieldNode.appendChild(container);
160
- }
161
-
162
- return container;
157
+ });
163
158
  }
164
159
 
165
160
  function removeDescribedBy(input: HTMLElement, describedById: string): void {
@@ -439,10 +434,9 @@ export function applySubmitResultUi(form: HTMLFormElement, result: FormSubmitRes
439
434
 
440
435
  if (result.formErrors?.length) {
441
436
  renderFormErrors(form, result.formErrors);
442
- return;
443
- }
444
-
445
- if (!result.fieldErrors && result.message) {
437
+ } else if (!result.fieldErrors && result.message) {
446
438
  renderFormErrors(form, [result.message]);
447
439
  }
440
+
441
+ focusFirstValidationError(form);
448
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
+ }