@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.
- package/CHANGELOG.md +65 -17
- package/dist/chunks/address-country-Rx6GQTND.js +68 -0
- package/dist/chunks/address-finder-yXU5yMjG.js +30 -0
- package/dist/chunks/address-state-BQ0xGV9n.js +344 -0
- package/dist/chunks/api-CmwLRq_n.js +780 -0
- package/dist/chunks/api-DMK8NSUI.js +407 -0
- package/dist/chunks/api-sPqGbOww.js +125 -0
- package/dist/chunks/async-nPFRNQ06.js +25 -0
- package/dist/chunks/bpoint-DSjqRv63.js +24 -0
- package/dist/chunks/calculations-CgVJcQ2P.js +299 -0
- package/dist/chunks/captcha-eu-CWJb64_j.js +31 -0
- package/dist/chunks/checkbox-radio-DQ0H67Tj.js +125 -0
- package/dist/chunks/chunk-3b4jIN3o.js +13 -0
- package/dist/chunks/combobox-D-2b42wn.js +1954 -0
- package/dist/chunks/conditions-CZ3FU5QO.js +412 -0
- package/dist/chunks/constants-DVcJAvc5.js +38 -0
- package/dist/chunks/country-from-ip-BwxKaBuJ.js +26 -0
- package/dist/chunks/custom-google-maps-BsaSirEZ.js +194 -0
- package/dist/chunks/custom-link-CJ1-FjEM.js +46 -0
- package/dist/chunks/custom-maps-DYW_pBid.js +161 -0
- package/dist/chunks/date-picker-5CA_-zGb.js +5311 -0
- package/dist/chunks/debug-BV0DvdHx.js +41 -0
- package/dist/chunks/dist-BF5GU3_9.js +2674 -0
- package/dist/chunks/event-names-BCI2FLD8.js +51 -0
- package/dist/chunks/eway-D3ctTbEk.js +70 -0
- package/dist/chunks/field-references.keys-58ZSTrCW.js +21 -0
- package/dist/chunks/field-references.resolver-CHwn0G0L.js +145 -0
- package/dist/chunks/file-upload-D49m8-DR.js +287 -0
- package/dist/chunks/friendly-captcha-v1-ByXzENYZ.js +31 -0
- package/dist/chunks/friendly-captcha-v2-BPdlmhQu.js +36 -0
- package/dist/chunks/friendly-challenge-8uPatWqk.js +832 -0
- package/dist/chunks/go-cardless-BwASSYSK.js +23 -0
- package/dist/chunks/google-address-OhKilPWE.js +161 -0
- package/dist/chunks/hcaptcha-CdCd9oEi.js +54 -0
- package/dist/chunks/hidden-D7_Ch-QN.js +26 -0
- package/dist/chunks/http-D-JExro7.js +27 -0
- package/dist/chunks/i18n-BY1ds1BL.js +121 -0
- package/dist/chunks/leaflet-src-j-lHNmhR.js +4585 -0
- package/dist/chunks/loqate-Cp2zvwpE.js +76 -0
- package/dist/chunks/mollie-COG-ko2D.js +23 -0
- package/dist/chunks/moneris-CMH8PlRc.js +117 -0
- package/dist/chunks/opayo-D2jsR5vh.js +113 -0
- package/dist/chunks/paddle-DxF88RfC.js +55 -0
- package/dist/chunks/paypal-b5pnwrA2.js +95 -0
- package/dist/chunks/payway-BJYu9DIR.js +55 -0
- package/dist/chunks/phone-country-D3ayr60L.js +2645 -0
- package/dist/chunks/place-kit-BGHklIZ-.js +48 -0
- package/dist/chunks/placekit-autocomplete.esm-DWVabQlq.js +1236 -0
- package/dist/chunks/recaptcha-enterprise-BSwi8IVX.js +65 -0
- package/dist/chunks/recaptcha-shared-B4zAescW.js +15 -0
- package/dist/chunks/recaptcha-v2-checkbox-CnDQmsKa.js +40 -0
- package/dist/chunks/recaptcha-v2-invisible-Cz5uIlp-.js +53 -0
- package/dist/chunks/recaptcha-v3-BtbbP111.js +27 -0
- package/dist/chunks/repeater-B5leVxZU.js +102 -0
- package/dist/chunks/rich-text-Cv8ADyk-.js +349 -0
- package/dist/chunks/scripts-6CYSUGKQ.js +24 -0
- package/dist/chunks/sdk-BhHAYgga.js +1397 -0
- package/dist/chunks/shared-Bx9s0i0P.js +100 -0
- package/dist/chunks/signature-CpgqHCEx.js +463 -0
- package/dist/chunks/snaptcha-vz1RnZ15.js +8 -0
- package/dist/chunks/square-BP8RWAKB.js +40 -0
- package/dist/chunks/stripe-MihN63uC.js +184 -0
- package/dist/chunks/styles-BfoIZwJp.js +19 -0
- package/dist/chunks/summary-D3AjxpYN.js +114 -0
- package/dist/chunks/table-C-lkQN6I.js +78 -0
- package/dist/chunks/text-limit-B09elPDB.js +125 -0
- package/dist/chunks/theme-classes-Tv7q7ToE.js +44 -0
- package/dist/chunks/turnstile-D_Pi1CKM.js +58 -0
- package/dist/chunks/utils-Vqp8i_RM.js +49401 -0
- package/dist/css/formie-theme.css +2 -0
- package/dist/css/theme/fields/_address-state.css +94 -0
- package/dist/css/theme/fields/_combobox.css +246 -0
- package/dist/css/theme/fields/_select.css +13 -0
- package/dist/index.js +2447 -39
- package/dist/js/core/create-formie-client.d.ts.map +1 -1
- package/dist/js/core/field-error-container.d.ts +4 -0
- package/dist/js/core/field-error-container.d.ts.map +1 -0
- package/dist/js/core/submit-result-state.d.ts.map +1 -1
- package/dist/js/core/submit-result-ui.d.ts.map +1 -1
- package/dist/js/core/validation-focus.d.ts +4 -0
- package/dist/js/core/validation-focus.d.ts.map +1 -0
- package/dist/js/modules/address/api.d.ts +1 -0
- package/dist/js/modules/address/api.d.ts.map +1 -1
- package/dist/js/modules/address/constants.d.ts +22 -1
- package/dist/js/modules/address/constants.d.ts.map +1 -1
- package/dist/js/modules/address/host.d.ts +2 -2
- package/dist/js/modules/address/host.d.ts.map +1 -1
- package/dist/js/modules/address/loqate.d.ts.map +1 -1
- package/dist/js/modules/fields/address-country.d.ts +3 -0
- package/dist/js/modules/fields/address-country.d.ts.map +1 -0
- package/dist/js/modules/fields/address-state.d.ts +3 -0
- package/dist/js/modules/fields/address-state.d.ts.map +1 -0
- package/dist/js/modules/fields/calculations.d.ts.map +1 -1
- package/dist/js/modules/fields/checkbox-radio.d.ts.map +1 -1
- package/dist/js/modules/fields/combobox.d.ts +17 -0
- package/dist/js/modules/fields/combobox.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-google-maps.d.ts +64 -0
- package/dist/js/modules/fields/custom-google-maps.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-link.d.ts +3 -0
- package/dist/js/modules/fields/custom-link.d.ts.map +1 -0
- package/dist/js/modules/fields/custom-maps.d.ts +3 -0
- package/dist/js/modules/fields/custom-maps.d.ts.map +1 -0
- package/dist/js/modules/fields/date-picker.d.ts.map +1 -1
- package/dist/js/modules/fields/file-upload.d.ts.map +1 -1
- package/dist/js/modules/fields/index.d.ts.map +1 -1
- package/dist/js/modules/fields/phone-country.d.ts.map +1 -1
- package/dist/js/modules/fields/text-limit.d.ts.map +1 -1
- package/dist/js/submit/pipeline.d.ts.map +1 -1
- package/dist/js/utils/country-from-ip.d.ts +7 -0
- package/dist/js/utils/country-from-ip.d.ts.map +1 -0
- package/dist/js/utils/field-references.d.ts +1 -0
- package/dist/js/utils/field-references.d.ts.map +1 -1
- package/dist/js/utils/field-references.row-scope.d.ts +11 -0
- package/dist/js/utils/field-references.row-scope.d.ts.map +1 -0
- package/dist/js/utils/i18n.d.ts.map +1 -1
- package/dist/js/validation/rules/email.d.ts.map +1 -1
- package/dist/js/validation/rules/number.d.ts.map +1 -1
- package/dist/js/validation/validator.d.ts.map +1 -1
- package/package.json +6 -3
- package/src/css/formie-theme.css +2 -0
- package/src/css/theme/fields/_address-state.css +94 -0
- package/src/css/theme/fields/_combobox.css +246 -0
- package/src/css/theme/fields/_select.css +13 -0
- package/src/js/core/create-formie-client.ts +27 -0
- package/src/js/core/field-error-container.ts +39 -0
- package/src/js/core/submit-result-state.ts +12 -2
- package/src/js/core/submit-result-ui.ts +7 -13
- package/src/js/core/validation-focus.ts +117 -0
- package/src/js/modules/address/api.ts +7 -0
- package/src/js/modules/address/constants.ts +82 -2
- package/src/js/modules/address/host.ts +16 -6
- package/src/js/modules/address/loqate.ts +3 -17
- package/src/js/modules/fields/address-country.ts +156 -0
- package/src/js/modules/fields/address-state.ts +946 -0
- package/src/js/modules/fields/calculations.ts +32 -0
- package/src/js/modules/fields/checkbox-radio.ts +21 -15
- package/src/js/modules/fields/combobox.ts +188 -0
- package/src/js/modules/fields/custom-google-maps.ts +416 -0
- package/src/js/modules/fields/custom-link.ts +95 -0
- package/src/js/modules/fields/custom-maps.ts +339 -0
- package/src/js/modules/fields/date-picker.ts +96 -3
- package/src/js/modules/fields/file-upload.ts +12 -9
- package/src/js/modules/fields/index.ts +6 -0
- package/src/js/modules/fields/phone-country.ts +6 -0
- package/src/js/modules/fields/text-limit.ts +87 -22
- package/src/js/submit/pipeline.ts +11 -1
- package/src/js/utils/country-from-ip.ts +54 -0
- package/src/js/utils/field-references.row-scope.ts +303 -0
- package/src/js/utils/field-references.ts +1 -0
- package/src/js/utils/i18n.ts +184 -5
- package/src/js/validation/rules/email.ts +3 -2
- package/src/js/validation/rules/match.ts +2 -2
- package/src/js/validation/rules/minmax.ts +4 -4
- package/src/js/validation/rules/number.ts +9 -5
- package/src/js/validation/rules/pattern.ts +4 -4
- package/src/js/validation/rules/required.ts +1 -1
- package/src/js/validation/rules/url.ts +1 -1
- package/src/js/validation/validator.ts +7 -7
- package/dist/chunks/address-finder-BjMuEiwF.js +0 -37
- package/dist/chunks/bpoint-KudsBCo2.js +0 -26
- package/dist/chunks/calculations-DlGSkQ3W.js +0 -147
- package/dist/chunks/captcha-eu-CterYFok.js +0 -34
- package/dist/chunks/checkbox-radio-CBvgk3hR.js +0 -136
- package/dist/chunks/conditions-DYy1-Hwt.js +0 -459
- package/dist/chunks/date-picker-BlD--zfe.js +0 -4994
- package/dist/chunks/eway-qwc-B5Us.js +0 -74
- package/dist/chunks/file-upload-CIgHrXfp.js +0 -316
- package/dist/chunks/friendly-captcha-v1-EDFac33c.js +0 -33
- package/dist/chunks/friendly-captcha-v2-BDpSvFGR.js +0 -38
- package/dist/chunks/go-cardless-DuDMVsnz.js +0 -24
- package/dist/chunks/google-address-Bmjp01pH.js +0 -181
- package/dist/chunks/hcaptcha-BTQ6An6g.js +0 -56
- package/dist/chunks/hidden-DbPdOxKb.js +0 -31
- package/dist/chunks/index-BEsfee-D.js +0 -1839
- package/dist/chunks/index-BP7Unm5Y.js +0 -863
- package/dist/chunks/index-BcqwpPNE.js +0 -4314
- package/dist/chunks/loqate-BdNGv93o.js +0 -55
- package/dist/chunks/mollie-DFOjTuGX.js +0 -24
- package/dist/chunks/moneris-BbFfl-EL.js +0 -101
- package/dist/chunks/opayo-CmgYa9ya.js +0 -122
- package/dist/chunks/paddle-BUi7w1_o.js +0 -60
- package/dist/chunks/paypal-A-ePfh6k.js +0 -100
- package/dist/chunks/payway-BqFCbhyo.js +0 -56
- package/dist/chunks/phone-country-DPMiqk4L.js +0 -3765
- package/dist/chunks/place-kit-CX7tamAg.js +0 -42
- package/dist/chunks/placekit-autocomplete.esm-LAwfbPZl.js +0 -1299
- package/dist/chunks/recaptcha-enterprise-DxF6cW95.js +0 -71
- package/dist/chunks/recaptcha-shared-DhGcbiaq.js +0 -15
- package/dist/chunks/recaptcha-v2-checkbox-R9EfbZ4k.js +0 -41
- package/dist/chunks/recaptcha-v2-invisible-DV9hswAp.js +0 -59
- package/dist/chunks/recaptcha-v3-V0xc-Edh.js +0 -28
- package/dist/chunks/repeater-DKUT6TGI.js +0 -124
- package/dist/chunks/rich-text-SH7KiaKH.js +0 -304
- package/dist/chunks/scripts-D9N2d5rN.js +0 -36
- package/dist/chunks/sdk-3SicTWby.js +0 -1559
- package/dist/chunks/shared-BmntPmnW.js +0 -80
- package/dist/chunks/signature-BA3tQsGT.js +0 -550
- package/dist/chunks/snaptcha-DV7p6J9k.js +0 -8
- package/dist/chunks/square-Bs-ndqYK.js +0 -46
- package/dist/chunks/stripe-DkZAHxtn.js +0 -202
- package/dist/chunks/styles-C3aqgtek.js +0 -21
- package/dist/chunks/summary-Dq2i9TQk.js +0 -126
- package/dist/chunks/table-Cu4WfVcw.js +0 -95
- package/dist/chunks/text-limit-2ycmB-Ae.js +0 -135
- package/dist/chunks/turnstile-CId90tRr.js +0 -59
- package/dist/chunks/utils-BntI3W4S.js +0 -6149
|
@@ -5,6 +5,7 @@ import { getTextLimitMetrics } from '@verbb/formie-core';
|
|
|
5
5
|
import type { FormieModuleDefinition } from '#contracts/modules';
|
|
6
6
|
import { getModuleFieldTarget, releaseFormValidators, retainFormValidators } from '#modules/fields/shared';
|
|
7
7
|
import { ensureModuleStyles } from '#modules/styles';
|
|
8
|
+
import { t } from '#utils/i18n';
|
|
8
9
|
|
|
9
10
|
const INPUT_SELECTOR = 'input[data-formie-single-line-text-input], textarea[data-formie-multi-line-text-input]';
|
|
10
11
|
const TEXT_LIMIT_VALIDATORS = [
|
|
@@ -15,8 +16,17 @@ const TEXT_LIMIT_VALIDATORS = [
|
|
|
15
16
|
] as const;
|
|
16
17
|
const VALIDATOR_SCOPE = 'text-limit';
|
|
17
18
|
const ALLOW_OVERTYPE_ATTR = 'data-formie-text-limit-allow-overtype';
|
|
19
|
+
const TEXT_LIMIT_CHARACTERS_ALLOWED = '{count, plural, one{character allowed} other{characters allowed}}';
|
|
20
|
+
const TEXT_LIMIT_CHARACTERS_LEFT = '{count, plural, one{character left} other{characters left}}';
|
|
21
|
+
const TEXT_LIMIT_CHARACTERS_OVER = '{count, plural, one{character over limit} other{characters over limit}}';
|
|
22
|
+
const TEXT_LIMIT_WORDS_ALLOWED = '{count, plural, one{word allowed} other{words allowed}}';
|
|
23
|
+
const TEXT_LIMIT_WORDS_LEFT = '{count, plural, one{word left} other{words left}}';
|
|
24
|
+
const TEXT_LIMIT_WORDS_OVER = '{count, plural, one{word over limit} other{words over limit}}';
|
|
18
25
|
const limitTargetCache = new WeakMap<HTMLInputElement | HTMLTextAreaElement, HTMLElement | null>();
|
|
19
26
|
|
|
27
|
+
type TextLimitUnit = 'character' | 'word';
|
|
28
|
+
type TextLimitCounterState = 'allowed' | 'left' | 'over';
|
|
29
|
+
|
|
20
30
|
ensureModuleStyles('text-limit', [textLimitCss]);
|
|
21
31
|
|
|
22
32
|
type TextLimitOptions = {
|
|
@@ -69,10 +79,11 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
69
79
|
|
|
70
80
|
return getTextLimitMetrics(input.value).graphemeCount >= limit;
|
|
71
81
|
}, ({ label, input, t }) => {
|
|
72
|
-
return
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
return input.getAttribute('data-formie-validation-min-characters-message')
|
|
83
|
+
|| t('{label} must be no less than {min} characters.', {
|
|
84
|
+
label,
|
|
85
|
+
min: input.getAttribute('data-formie-min-chars') || '',
|
|
86
|
+
});
|
|
76
87
|
});
|
|
77
88
|
|
|
78
89
|
validator.addValidator('textMaxCharacterLimit', ({ input }) => {
|
|
@@ -92,10 +103,11 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
92
103
|
|
|
93
104
|
return getTextLimitMetrics(input.value).graphemeCount <= limit;
|
|
94
105
|
}, ({ label, input, t }) => {
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
return input.getAttribute('data-formie-validation-max-characters-message')
|
|
107
|
+
|| t('{label} must be no greater than {max} characters.', {
|
|
108
|
+
label,
|
|
109
|
+
max: input.getAttribute('data-formie-max-chars') || '',
|
|
110
|
+
});
|
|
99
111
|
});
|
|
100
112
|
|
|
101
113
|
validator.addValidator('textMinWordLimit', ({ input }) => {
|
|
@@ -111,10 +123,11 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
111
123
|
|
|
112
124
|
return getTextLimitMetrics(input.value).wordCount >= limit;
|
|
113
125
|
}, ({ label, input, t }) => {
|
|
114
|
-
return
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
return input.getAttribute('data-formie-validation-min-words-message')
|
|
127
|
+
|| t('{label} must be no less than {min} words.', {
|
|
128
|
+
label,
|
|
129
|
+
min: input.getAttribute('data-formie-min-words') || '',
|
|
130
|
+
});
|
|
118
131
|
});
|
|
119
132
|
|
|
120
133
|
validator.addValidator('textMaxWordLimit', ({ input }) => {
|
|
@@ -134,10 +147,11 @@ function registerValidators(form: HTMLFormElement | null): void {
|
|
|
134
147
|
|
|
135
148
|
return getTextLimitMetrics(input.value).wordCount <= limit;
|
|
136
149
|
}, ({ label, input, t }) => {
|
|
137
|
-
return
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
return input.getAttribute('data-formie-validation-max-words-message')
|
|
151
|
+
|| t('{label} must be no greater than {max} words.', {
|
|
152
|
+
label,
|
|
153
|
+
max: input.getAttribute('data-formie-max-words') || '',
|
|
154
|
+
});
|
|
141
155
|
});
|
|
142
156
|
});
|
|
143
157
|
}
|
|
@@ -182,14 +196,65 @@ function getLimitTarget(input: HTMLInputElement | HTMLTextAreaElement): HTMLElem
|
|
|
182
196
|
return target;
|
|
183
197
|
}
|
|
184
198
|
|
|
185
|
-
function
|
|
199
|
+
function getTextLimitCounterState(
|
|
200
|
+
input: HTMLInputElement | HTMLTextAreaElement,
|
|
201
|
+
remaining: number,
|
|
202
|
+
unit: TextLimitUnit,
|
|
203
|
+
): TextLimitCounterState {
|
|
204
|
+
const isEmpty = unit === 'character' ? input.value === '' : input.value.trim() === '';
|
|
205
|
+
|
|
206
|
+
if (isEmpty) {
|
|
207
|
+
return 'allowed';
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (remaining < 0) {
|
|
211
|
+
return 'over';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return 'left';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getTextLimitCounterMessageKey(unit: TextLimitUnit, state: TextLimitCounterState): string {
|
|
218
|
+
if (unit === 'character') {
|
|
219
|
+
if (state === 'allowed') {
|
|
220
|
+
return TEXT_LIMIT_CHARACTERS_ALLOWED;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (state === 'over') {
|
|
224
|
+
return TEXT_LIMIT_CHARACTERS_OVER;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return TEXT_LIMIT_CHARACTERS_LEFT;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (state === 'allowed') {
|
|
231
|
+
return TEXT_LIMIT_WORDS_ALLOWED;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (state === 'over') {
|
|
235
|
+
return TEXT_LIMIT_WORDS_OVER;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return TEXT_LIMIT_WORDS_LEFT;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function renderCounter(
|
|
242
|
+
target: HTMLElement,
|
|
243
|
+
input: HTMLInputElement | HTMLTextAreaElement,
|
|
244
|
+
remaining: number,
|
|
245
|
+
limit: number,
|
|
246
|
+
unit: TextLimitUnit,
|
|
247
|
+
): void {
|
|
248
|
+
const state = getTextLimitCounterState(input, remaining, unit);
|
|
249
|
+
const displayCount = state === 'allowed' ? limit : Math.abs(remaining);
|
|
186
250
|
const number = document.createElement('span');
|
|
187
|
-
number.className =
|
|
188
|
-
number.textContent = String(
|
|
251
|
+
number.className = state === 'over' ? 'formie-limit-number formie-limit-number-error' : 'formie-limit-number';
|
|
252
|
+
number.textContent = String(displayCount);
|
|
253
|
+
const messageKey = getTextLimitCounterMessageKey(unit, state);
|
|
189
254
|
|
|
190
255
|
target.replaceChildren(
|
|
191
256
|
number,
|
|
192
|
-
document.createTextNode(` ${
|
|
257
|
+
document.createTextNode(` ${t(messageKey, { count: displayCount })}`)
|
|
193
258
|
);
|
|
194
259
|
}
|
|
195
260
|
|
|
@@ -206,14 +271,14 @@ function updateCounter(input: HTMLInputElement | HTMLTextAreaElement): void {
|
|
|
206
271
|
|
|
207
272
|
if (maxChars > 0) {
|
|
208
273
|
const remaining = maxChars - metrics.graphemeCount;
|
|
209
|
-
renderCounter(target, remaining, 'character');
|
|
274
|
+
renderCounter(target, input, remaining, maxChars, 'character');
|
|
210
275
|
|
|
211
276
|
return;
|
|
212
277
|
}
|
|
213
278
|
|
|
214
279
|
if (maxWords > 0) {
|
|
215
280
|
const remaining = maxWords - metrics.wordCount;
|
|
216
|
-
renderCounter(target, remaining, 'word');
|
|
281
|
+
renderCounter(target, input, remaining, maxWords, 'word');
|
|
217
282
|
}
|
|
218
283
|
}
|
|
219
284
|
|
|
@@ -250,7 +250,17 @@ export async function runSubmitPipeline(
|
|
|
250
250
|
const errors = options.validator.submit(final ? ctx.form : scope, { final });
|
|
251
251
|
|
|
252
252
|
if (errors.length > 0) {
|
|
253
|
-
errors[0]?.input
|
|
253
|
+
const firstErrorInput = errors[0]?.input;
|
|
254
|
+
|
|
255
|
+
if (firstErrorInput) {
|
|
256
|
+
firstErrorInput.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
firstErrorInput.focus({ preventScroll: true });
|
|
260
|
+
} catch {
|
|
261
|
+
firstErrorInput.focus();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
254
264
|
|
|
255
265
|
return {
|
|
256
266
|
ok: false,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export type CountryFromIpResponse = {
|
|
2
|
+
countryCode?: string | null;
|
|
3
|
+
countryName?: string | null;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const DEFAULT_COUNTRY_FROM_IP_ACTION = 'formie/address/country-from-ip';
|
|
7
|
+
|
|
8
|
+
let cachedLookup: Promise<CountryFromIpResponse | null> | null = null;
|
|
9
|
+
|
|
10
|
+
function buildActionUrl(action: string): string {
|
|
11
|
+
return new URL(action.startsWith('/') ? action : `/actions/${action}`, window.location.origin).toString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function fetchCountryFromIp(
|
|
15
|
+
action: string = DEFAULT_COUNTRY_FROM_IP_ACTION,
|
|
16
|
+
): Promise<CountryFromIpResponse | null> {
|
|
17
|
+
if (!cachedLookup) {
|
|
18
|
+
cachedLookup = (async () => {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch(buildActionUrl(action), {
|
|
21
|
+
headers: {
|
|
22
|
+
Accept: 'application/json',
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const data = await response.json() as CountryFromIpResponse;
|
|
31
|
+
|
|
32
|
+
if (!data?.countryCode) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return data;
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
})();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return cachedLookup;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createGeoIpLookup(
|
|
47
|
+
action: string = DEFAULT_COUNTRY_FROM_IP_ACTION,
|
|
48
|
+
): (callback: (countryCode: string) => void) => void {
|
|
49
|
+
return (callback) => {
|
|
50
|
+
void fetchCountryFromIp(action).then((data) => {
|
|
51
|
+
callback(data?.countryCode?.toLowerCase() || '');
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { fieldKeyToInputName, normalizeFieldKey } from '#utils/field-references.keys';
|
|
2
|
+
import { resolveFieldReferenceLive } from '#utils/field-references.resolver';
|
|
3
|
+
import type { FieldValueRegistry, ResolveFieldValueResult } from '#utils/field-references.types';
|
|
4
|
+
|
|
5
|
+
export type RowScopeParams = {
|
|
6
|
+
scope?: string;
|
|
7
|
+
index?: string | number;
|
|
8
|
+
rows?: string;
|
|
9
|
+
fieldKind?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const ROW_SCOPE_VALUES = new Set(['first', 'last', 'index', 'all', 'count', 'rows']);
|
|
13
|
+
|
|
14
|
+
function escapeRegExp(value: string): string {
|
|
15
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function parseRowsExpression(expression: string, rowCount: number): number[] {
|
|
19
|
+
const normalized = String(expression || '').trim().toLowerCase();
|
|
20
|
+
|
|
21
|
+
if (!normalized || rowCount <= 0) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (normalized === 'even') {
|
|
26
|
+
const indices: number[] = [];
|
|
27
|
+
|
|
28
|
+
for (let row = 1; row <= rowCount; row++) {
|
|
29
|
+
if (row % 2 === 0) {
|
|
30
|
+
indices.push(row - 1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return indices;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (normalized === 'odd') {
|
|
38
|
+
const indices: number[] = [];
|
|
39
|
+
|
|
40
|
+
for (let row = 1; row <= rowCount; row++) {
|
|
41
|
+
if (row % 2 === 1) {
|
|
42
|
+
indices.push(row - 1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return indices;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const everyMatch = normalized.match(/^every:(\d+)$/);
|
|
50
|
+
|
|
51
|
+
if (everyMatch) {
|
|
52
|
+
const step = Math.max(1, Number.parseInt(everyMatch[1] || '1', 10));
|
|
53
|
+
const indices: number[] = [];
|
|
54
|
+
|
|
55
|
+
for (let row = 1; row <= rowCount; row += step) {
|
|
56
|
+
indices.push(row - 1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return indices;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const indices: number[] = [];
|
|
63
|
+
|
|
64
|
+
normalized.split(/\s*,\s*/).forEach((segment) => {
|
|
65
|
+
const trimmed = segment.trim();
|
|
66
|
+
|
|
67
|
+
if (!trimmed) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const rangeMatch = trimmed.match(/^(\d+)\s*-\s*(\d+)$/);
|
|
72
|
+
|
|
73
|
+
if (rangeMatch) {
|
|
74
|
+
let start = Number.parseInt(rangeMatch[1] || '0', 10);
|
|
75
|
+
let end = Number.parseInt(rangeMatch[2] || '0', 10);
|
|
76
|
+
|
|
77
|
+
if (start > end) {
|
|
78
|
+
[start, end] = [end, start];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (let row = start; row <= end; row++) {
|
|
82
|
+
if (row >= 1 && row <= rowCount) {
|
|
83
|
+
indices.push(row - 1);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const row = Number.parseInt(trimmed, 10);
|
|
91
|
+
|
|
92
|
+
if (Number.isFinite(row) && row >= 1 && row <= rowCount) {
|
|
93
|
+
indices.push(row - 1);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return [...new Set(indices)].sort((left, right) => left - right);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolveColumnSelector(sourceKey: string): { fieldKey: string; columnKey: string } {
|
|
101
|
+
const normalized = normalizeFieldKey(sourceKey);
|
|
102
|
+
const parts = normalized.split('.').filter(Boolean);
|
|
103
|
+
|
|
104
|
+
if (parts.length < 2) {
|
|
105
|
+
return {
|
|
106
|
+
fieldKey: normalized,
|
|
107
|
+
columnKey: parts[parts.length - 1] || '',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (parts.length >= 3 && /^\d+$/.test(parts[1] || '')) {
|
|
112
|
+
return {
|
|
113
|
+
fieldKey: parts[0] || '',
|
|
114
|
+
columnKey: parts.slice(2).join('.'),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
fieldKey: parts[0] || '',
|
|
120
|
+
columnKey: parts.slice(1).join('.'),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function listColumnCellKeys(fieldKey: string, columnKey: string, registry: FieldValueRegistry): string[] {
|
|
125
|
+
const pattern = new RegExp(`^${escapeRegExp(fieldKey)}\\.(\\d+)\\.${escapeRegExp(columnKey)}$`);
|
|
126
|
+
|
|
127
|
+
return [...registry.keys()]
|
|
128
|
+
.filter((key) => pattern.test(key))
|
|
129
|
+
.sort((left, right) => {
|
|
130
|
+
const leftRow = Number.parseInt(left.split('.')[1] || '0', 10);
|
|
131
|
+
const rightRow = Number.parseInt(right.split('.')[1] || '0', 10);
|
|
132
|
+
|
|
133
|
+
return leftRow - rightRow;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readCellValue(key: string, registry: FieldValueRegistry): string | string[] {
|
|
138
|
+
return resolveFieldReferenceLive(key, registry).value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function getRowScopedWatchNames(
|
|
142
|
+
sourceKey: string,
|
|
143
|
+
params: RowScopeParams,
|
|
144
|
+
registry: FieldValueRegistry,
|
|
145
|
+
): Set<string> {
|
|
146
|
+
const watchNames = new Set<string>();
|
|
147
|
+
const { fieldKey, columnKey } = resolveColumnSelector(sourceKey);
|
|
148
|
+
const scope = String(params.scope || '').trim().toLowerCase();
|
|
149
|
+
|
|
150
|
+
if (!fieldKey || !columnKey || !ROW_SCOPE_VALUES.has(scope)) {
|
|
151
|
+
const fallback = fieldKeyToInputName(sourceKey);
|
|
152
|
+
|
|
153
|
+
if (fallback) {
|
|
154
|
+
watchNames.add(fallback);
|
|
155
|
+
watchNames.add(`${fallback}[]`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return watchNames;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const cellKeys = listColumnCellKeys(fieldKey, columnKey, registry);
|
|
162
|
+
|
|
163
|
+
cellKeys.forEach((cellKey) => {
|
|
164
|
+
const entry = registry.get(cellKey);
|
|
165
|
+
|
|
166
|
+
if (entry?.names?.length) {
|
|
167
|
+
entry.names.forEach((name) => {
|
|
168
|
+
watchNames.add(name);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const fallback = fieldKeyToInputName(cellKey);
|
|
175
|
+
|
|
176
|
+
if (fallback) {
|
|
177
|
+
watchNames.add(fallback);
|
|
178
|
+
watchNames.add(`${fallback}[]`);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return watchNames;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function resolveRowScopedFieldReference(
|
|
186
|
+
sourceKey: string,
|
|
187
|
+
params: RowScopeParams,
|
|
188
|
+
registry: FieldValueRegistry,
|
|
189
|
+
): ResolveFieldValueResult {
|
|
190
|
+
const scope = String(params.scope || '').trim().toLowerCase();
|
|
191
|
+
|
|
192
|
+
if (!scope || !ROW_SCOPE_VALUES.has(scope)) {
|
|
193
|
+
return resolveFieldReferenceLive(sourceKey, registry);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const { fieldKey, columnKey } = resolveColumnSelector(sourceKey);
|
|
197
|
+
|
|
198
|
+
if (!fieldKey || !columnKey) {
|
|
199
|
+
return resolveFieldReferenceLive(sourceKey, registry);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const cellKeys = listColumnCellKeys(fieldKey, columnKey, registry);
|
|
203
|
+
const rowValues = cellKeys.map((cellKey) => readCellValue(cellKey, registry));
|
|
204
|
+
|
|
205
|
+
if (scope === 'count') {
|
|
206
|
+
return {
|
|
207
|
+
key: `${fieldKey}.${columnKey}`,
|
|
208
|
+
value: String(cellKeys.length),
|
|
209
|
+
found: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (scope === 'first') {
|
|
214
|
+
const key = cellKeys[0] || `${fieldKey}.0.${columnKey}`;
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
key,
|
|
218
|
+
value: rowValues[0] ?? '',
|
|
219
|
+
found: cellKeys.length > 0,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (scope === 'last') {
|
|
224
|
+
const key = cellKeys[cellKeys.length - 1] || `${fieldKey}.0.${columnKey}`;
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
key,
|
|
228
|
+
value: rowValues[rowValues.length - 1] ?? '',
|
|
229
|
+
found: cellKeys.length > 0,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (scope === 'index') {
|
|
234
|
+
const index = Number.parseInt(String(params.index ?? '0'), 10);
|
|
235
|
+
const key = `${fieldKey}.${index}.${columnKey}`;
|
|
236
|
+
|
|
237
|
+
return resolveFieldReferenceLive(key, registry);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (scope === 'all') {
|
|
241
|
+
const values = rowValues.flatMap((value) => {
|
|
242
|
+
if (Array.isArray(value)) {
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (value === '') {
|
|
247
|
+
return [];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return [value];
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
key: `${fieldKey}.${columnKey}`,
|
|
255
|
+
value: values,
|
|
256
|
+
found: values.length > 0,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (scope === 'rows') {
|
|
261
|
+
const indices = parseRowsExpression(String(params.rows || ''), cellKeys.length);
|
|
262
|
+
|
|
263
|
+
if (indices.length === 0) {
|
|
264
|
+
return {
|
|
265
|
+
key: `${fieldKey}.${columnKey}`,
|
|
266
|
+
value: '',
|
|
267
|
+
found: false,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (indices.length === 1) {
|
|
272
|
+
const key = cellKeys[indices[0]] || `${fieldKey}.${indices[0]}.${columnKey}`;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
key,
|
|
276
|
+
value: rowValues[indices[0]] ?? '',
|
|
277
|
+
found: true,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const values = indices.flatMap((index) => {
|
|
282
|
+
const value = rowValues[index];
|
|
283
|
+
|
|
284
|
+
if (Array.isArray(value)) {
|
|
285
|
+
return value;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (value === '') {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return [value];
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
key: `${fieldKey}.${columnKey}`,
|
|
297
|
+
value: values,
|
|
298
|
+
found: values.length > 0,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return resolveFieldReferenceLive(sourceKey, registry);
|
|
303
|
+
}
|
|
@@ -2,6 +2,7 @@ export { normalizeFieldKey, fieldKeyToInputName, inputNameToFieldKey } from '#ut
|
|
|
2
2
|
export { parseFieldReference } from '#utils/field-references.parser';
|
|
3
3
|
export { buildFieldValueRegistry } from '#utils/field-references.registry';
|
|
4
4
|
export { resolveFieldReferenceLive, resolveFieldReferenceFromFormData } from '#utils/field-references.resolver';
|
|
5
|
+
export { getRowScopedWatchNames, parseRowsExpression, resolveRowScopedFieldReference } from '#utils/field-references.row-scope';
|
|
5
6
|
export type {
|
|
6
7
|
FieldReferenceTransform,
|
|
7
8
|
FieldValueRegistry,
|