@usefillo/core 0.5.0 → 0.5.1
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/dist/index.d.ts +24 -10
- package/dist/index.js +324 -76
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -39,8 +39,9 @@ interface PhoneField extends BaseField {
|
|
|
39
39
|
kind: "phone";
|
|
40
40
|
/**
|
|
41
41
|
* Country selected when the form opens (ISO-3166 alpha-2, e.g. "US"). When
|
|
42
|
-
* unset the renderer falls back to the respondent's browser
|
|
43
|
-
* first country in the list. The respondent can always
|
|
42
|
+
* unset the renderer falls back to the respondent's browser timezone, browser
|
|
43
|
+
* locale, then the first country in the list. The respondent can always
|
|
44
|
+
* change it.
|
|
44
45
|
*/
|
|
45
46
|
defaultCountry?: string;
|
|
46
47
|
}
|
|
@@ -351,6 +352,7 @@ declare const PHONE_COUNTRIES: PhoneCountry[];
|
|
|
351
352
|
/** Flag emoji from an ISO-3166 alpha-2 code (regional-indicator letters). */
|
|
352
353
|
declare function flagEmoji(iso2: string): string;
|
|
353
354
|
declare function countryByIso(iso2: string | undefined): PhoneCountry | undefined;
|
|
355
|
+
declare function countryByTimeZone(timeZone: string | undefined): PhoneCountry | undefined;
|
|
354
356
|
/** Country whose dial code is the longest prefix of these digits (no "+"). */
|
|
355
357
|
declare function countryByDialCode(digits: string): PhoneCountry | undefined;
|
|
356
358
|
/** Keep only 0-9 (and a leading "+") from arbitrary input. */
|
|
@@ -465,14 +467,19 @@ interface PublishedForm {
|
|
|
465
467
|
/** Workspace branding state — absent means show the badge (default). */
|
|
466
468
|
branding?: FormBranding;
|
|
467
469
|
}
|
|
468
|
-
|
|
469
|
-
ok:
|
|
470
|
-
responseId
|
|
470
|
+
type SubmitResult = {
|
|
471
|
+
ok: true;
|
|
472
|
+
responseId: string;
|
|
471
473
|
/** True when the API accepted the request as an already-recorded visitor response. */
|
|
472
474
|
duplicate?: boolean;
|
|
475
|
+
errors?: undefined;
|
|
476
|
+
} | {
|
|
477
|
+
ok: false;
|
|
473
478
|
/** fieldId -> message when the server rejects the submission. */
|
|
474
|
-
errors
|
|
475
|
-
|
|
479
|
+
errors: Record<string, string>;
|
|
480
|
+
responseId?: undefined;
|
|
481
|
+
duplicate?: undefined;
|
|
482
|
+
};
|
|
476
483
|
/** Anti-spam signals collected by the renderer. */
|
|
477
484
|
interface SubmitMeta {
|
|
478
485
|
/** Honeypot value — must be empty for humans. */
|
|
@@ -561,6 +568,12 @@ declare class FilloClient {
|
|
|
561
568
|
* Box; we return the new file id for /complete to verify server-side.
|
|
562
569
|
*/
|
|
563
570
|
private boxUpload;
|
|
571
|
+
/**
|
|
572
|
+
* Caller signal (if any) combined with a fresh per-request timeout. Created per
|
|
573
|
+
* fetch so each retry attempt gets its own deadline; the caller's own abort
|
|
574
|
+
* still drives the resumable-upload cancel semantics.
|
|
575
|
+
*/
|
|
576
|
+
private uploadSignal;
|
|
564
577
|
/** Retry a request with exponential backoff; never retries an aborted upload. */
|
|
565
578
|
private retry;
|
|
566
579
|
/**
|
|
@@ -637,8 +650,9 @@ interface FormControllerOptions {
|
|
|
637
650
|
/**
|
|
638
651
|
* Embedding surface. "headless" (a bare engine with no Fillo-rendered layout,
|
|
639
652
|
* i.e. createFormController / <FilloProvider>) is a paid capability the server
|
|
640
|
-
* enforces at submit. The framed renderers (<FilloForm>, renderForm)
|
|
641
|
-
* "default". Defaults to "
|
|
653
|
+
* enforces at submit. The framed renderers (<FilloForm>, renderForm) pass
|
|
654
|
+
* "default" explicitly. Defaults to "headless" — a bare createFormController is
|
|
655
|
+
* itself the headless gate.
|
|
642
656
|
*/
|
|
643
657
|
surface?: "default" | "headless";
|
|
644
658
|
}
|
|
@@ -765,4 +779,4 @@ declare class Sha1 {
|
|
|
765
779
|
}
|
|
766
780
|
declare const sha1Base64: (bytes: Uint8Array) => string;
|
|
767
781
|
|
|
768
|
-
export { BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CustomField, DRAFT_KINDS, type DateField, type DividerBlock, FILLO_SCHEMA_VERSION, FILLO_SDK_VERSION, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, FilloClient, type FilloClientOptions, FilloError, type FormBranding, type FormController, type FormControllerOptions, type FormControllerState, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormStatus, type FormTheme, type HeadingBlock, type HiddenField, type JsonValue, type LinearScaleField, type MatrixField, type NumberField, PHONE_COUNTRIES, type ParagraphBlock, type ParsedPhone, type PhoneCountry, type PhoneField, type ProvisionWorkspaceResult, type PublishedForm, type RankingField, type RatingField, type ResponseData, type SchemaValidationResult, type SelectOption, Sha1, type SignatureField, type SubmitMeta, type SubmitResult, type SyncedForm, type TextField, type UploadFileOptions, type UploadProgress, type UploadSession, type UploadStatus, type UploadTransport, type ValidationResult, allFields, assembleForm, contentHash, countryByDialCode, countryByIso, createBlock, createClient, createEmptyForm, createFormController, createId, defineForm, digitsOnly, flagEmoji, formatAnswer, formatNational, isBlockVisible, isCodeForm, isField, isPossiblePhone, normalizeFormSchema, normalizeFormTheme, parsePhone, pipeBlock, prefillFromParams, provisionWorkspace, resolveText, sha1Base64, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields };
|
|
782
|
+
export { BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CustomField, DRAFT_KINDS, type DateField, type DividerBlock, FILLO_SCHEMA_VERSION, FILLO_SDK_VERSION, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, FilloClient, type FilloClientOptions, FilloError, type FormBranding, type FormController, type FormControllerOptions, type FormControllerState, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormStatus, type FormTheme, type HeadingBlock, type HiddenField, type JsonValue, type LinearScaleField, type MatrixField, type NumberField, PHONE_COUNTRIES, type ParagraphBlock, type ParsedPhone, type PhoneCountry, type PhoneField, type ProvisionWorkspaceResult, type PublishedForm, type RankingField, type RatingField, type ResponseData, type SchemaValidationResult, type SelectOption, Sha1, type SignatureField, type SubmitMeta, type SubmitResult, type SyncedForm, type TextField, type UploadFileOptions, type UploadProgress, type UploadSession, type UploadStatus, type UploadTransport, type ValidationResult, allFields, assembleForm, contentHash, countryByDialCode, countryByIso, countryByTimeZone, createBlock, createClient, createEmptyForm, createFormController, createId, defineForm, digitsOnly, flagEmoji, formatAnswer, formatNational, isBlockVisible, isCodeForm, isField, isPossiblePhone, normalizeFormSchema, normalizeFormTheme, parsePhone, pipeBlock, prefillFromParams, provisionWorkspace, resolveText, sha1Base64, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields };
|
package/dist/index.js
CHANGED
|
@@ -13,8 +13,16 @@ function isAnswered(value) {
|
|
|
13
13
|
if (typeof value === "object") return Object.keys(value).length > 0;
|
|
14
14
|
return true;
|
|
15
15
|
}
|
|
16
|
-
function
|
|
17
|
-
|
|
16
|
+
function valueEquals(value, target) {
|
|
17
|
+
if (value === target) return true;
|
|
18
|
+
if (typeof value === "number" && typeof target === "string" && target.trim() !== "")
|
|
19
|
+
return Number(target) === value;
|
|
20
|
+
if (typeof target === "number" && typeof value === "string" && value.trim() !== "")
|
|
21
|
+
return Number(value) === target;
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
function evalCondition(cond, resolve) {
|
|
25
|
+
const value = resolve(cond.fieldId);
|
|
18
26
|
switch (cond.op) {
|
|
19
27
|
case "answered":
|
|
20
28
|
return isAnswered(value);
|
|
@@ -22,10 +30,11 @@ function evalCondition(cond, data) {
|
|
|
22
30
|
return !isAnswered(value);
|
|
23
31
|
case "eq":
|
|
24
32
|
if (Array.isArray(value)) return value.some((v) => v === cond.value);
|
|
25
|
-
return value
|
|
33
|
+
return valueEquals(value, cond.value);
|
|
26
34
|
case "neq":
|
|
35
|
+
if (!isAnswered(value)) return false;
|
|
27
36
|
if (Array.isArray(value)) return !value.some((v) => v === cond.value);
|
|
28
|
-
return value
|
|
37
|
+
return !valueEquals(value, cond.value);
|
|
29
38
|
case "contains":
|
|
30
39
|
if (typeof value === "string")
|
|
31
40
|
return value.toLowerCase().includes(String(cond.value ?? "").toLowerCase());
|
|
@@ -37,22 +46,45 @@ function evalCondition(cond, data) {
|
|
|
37
46
|
return typeof value === "number" && value < Number(cond.value);
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
|
-
function
|
|
49
|
+
function blockVisibleWith(block, resolve) {
|
|
41
50
|
if (!block.visibleIf || block.visibleIf.length === 0) return true;
|
|
42
|
-
return block.visibleIf.every((cond) => evalCondition(cond,
|
|
51
|
+
return block.visibleIf.every((cond) => evalCondition(cond, resolve));
|
|
52
|
+
}
|
|
53
|
+
function makeResolver(data, scoped, visible) {
|
|
54
|
+
return (fieldId) => scoped.has(fieldId) && !visible.has(fieldId) ? void 0 : data[fieldId];
|
|
55
|
+
}
|
|
56
|
+
function visibleFieldIds(fields, data) {
|
|
57
|
+
const scoped = new Set(fields.map((f) => f.id));
|
|
58
|
+
let visible = scoped;
|
|
59
|
+
for (let i = 0; i <= fields.length; i++) {
|
|
60
|
+
const resolve = makeResolver(data, scoped, visible);
|
|
61
|
+
const next = /* @__PURE__ */ new Set();
|
|
62
|
+
for (const f of fields) if (blockVisibleWith(f, resolve)) next.add(f.id);
|
|
63
|
+
if (next.size === visible.size && [...next].every((id) => visible.has(id))) return next;
|
|
64
|
+
visible = next;
|
|
65
|
+
}
|
|
66
|
+
return visible;
|
|
67
|
+
}
|
|
68
|
+
function isBlockVisible(block, data) {
|
|
69
|
+
return blockVisibleWith(block, (fieldId) => data[fieldId]);
|
|
43
70
|
}
|
|
44
71
|
function visibleBlocks(page, data) {
|
|
45
|
-
|
|
72
|
+
const fields = page.blocks.filter(isField);
|
|
73
|
+
const scoped = new Set(fields.map((f) => f.id));
|
|
74
|
+
const resolve = makeResolver(data, scoped, visibleFieldIds(fields, data));
|
|
75
|
+
return page.blocks.filter((b) => blockVisibleWith(b, resolve));
|
|
46
76
|
}
|
|
47
77
|
function allFields(form) {
|
|
48
78
|
return form.pages.flatMap((p) => p.blocks.filter(isField));
|
|
49
79
|
}
|
|
50
80
|
function visibleFields(form, data) {
|
|
51
|
-
|
|
81
|
+
const fields = allFields(form);
|
|
82
|
+
const visible = visibleFieldIds(fields, data);
|
|
83
|
+
return fields.filter((f) => visible.has(f.id));
|
|
52
84
|
}
|
|
53
85
|
|
|
54
86
|
// src/validation.ts
|
|
55
|
-
import
|
|
87
|
+
import * as z from "zod/mini";
|
|
56
88
|
|
|
57
89
|
// src/phone.ts
|
|
58
90
|
var C = (iso2, name, dialCode, lengths = [], groups = [], example = "") => ({ iso2, name, dialCode, lengths, groups, example });
|
|
@@ -129,6 +161,138 @@ var PHONE_COUNTRIES = [
|
|
|
129
161
|
C("KW", "Kuwait", "965", [8], [4, 4], "50012345")
|
|
130
162
|
];
|
|
131
163
|
var byIso = new Map(PHONE_COUNTRIES.map((c) => [c.iso2, c]));
|
|
164
|
+
var timeZoneIso = {
|
|
165
|
+
"Africa/Cairo": "EG",
|
|
166
|
+
"Africa/Casablanca": "MA",
|
|
167
|
+
"Africa/Johannesburg": "ZA",
|
|
168
|
+
"Africa/Lagos": "NG",
|
|
169
|
+
"Africa/Nairobi": "KE",
|
|
170
|
+
"America/Adak": "US",
|
|
171
|
+
"America/Anchorage": "US",
|
|
172
|
+
"America/Argentina/Buenos_Aires": "AR",
|
|
173
|
+
"America/Argentina/Cordoba": "AR",
|
|
174
|
+
"America/Argentina/Mendoza": "AR",
|
|
175
|
+
"America/Bahia": "BR",
|
|
176
|
+
"America/Belem": "BR",
|
|
177
|
+
"America/Bogota": "CO",
|
|
178
|
+
"America/Boise": "US",
|
|
179
|
+
"America/Boa_Vista": "BR",
|
|
180
|
+
"America/Cancun": "MX",
|
|
181
|
+
"America/Chicago": "US",
|
|
182
|
+
"America/Chihuahua": "MX",
|
|
183
|
+
"America/Campo_Grande": "BR",
|
|
184
|
+
"America/Costa_Rica": "CR",
|
|
185
|
+
"America/Cuiaba": "BR",
|
|
186
|
+
"America/Denver": "US",
|
|
187
|
+
"America/Detroit": "US",
|
|
188
|
+
"America/Edmonton": "CA",
|
|
189
|
+
"America/Fortaleza": "BR",
|
|
190
|
+
"America/Guayaquil": "EC",
|
|
191
|
+
"America/Halifax": "CA",
|
|
192
|
+
"America/Hermosillo": "MX",
|
|
193
|
+
"America/Indiana/Indianapolis": "US",
|
|
194
|
+
"America/Indianapolis": "US",
|
|
195
|
+
"America/Juneau": "US",
|
|
196
|
+
"America/Kentucky/Louisville": "US",
|
|
197
|
+
"America/Lima": "PE",
|
|
198
|
+
"America/Los_Angeles": "US",
|
|
199
|
+
"America/Maceio": "BR",
|
|
200
|
+
"America/Manaus": "BR",
|
|
201
|
+
"America/Matamoros": "MX",
|
|
202
|
+
"America/Mazatlan": "MX",
|
|
203
|
+
"America/Merida": "MX",
|
|
204
|
+
"America/Mexico_City": "MX",
|
|
205
|
+
"America/Moncton": "CA",
|
|
206
|
+
"America/Monterrey": "MX",
|
|
207
|
+
"America/Montevideo": "UY",
|
|
208
|
+
"America/New_York": "US",
|
|
209
|
+
"America/Noronha": "BR",
|
|
210
|
+
"America/Nome": "US",
|
|
211
|
+
"America/North_Dakota/Center": "US",
|
|
212
|
+
"America/Ojinaga": "MX",
|
|
213
|
+
"America/Phoenix": "US",
|
|
214
|
+
"America/Porto_Velho": "BR",
|
|
215
|
+
"America/Punta_Arenas": "CL",
|
|
216
|
+
"America/Recife": "BR",
|
|
217
|
+
"America/Regina": "CA",
|
|
218
|
+
"America/Rio_Branco": "BR",
|
|
219
|
+
"America/Santiago": "CL",
|
|
220
|
+
"America/Sao_Paulo": "BR",
|
|
221
|
+
"America/Sitka": "US",
|
|
222
|
+
"America/St_Johns": "CA",
|
|
223
|
+
"America/Tijuana": "MX",
|
|
224
|
+
"America/Toronto": "CA",
|
|
225
|
+
"America/Vancouver": "CA",
|
|
226
|
+
"America/Whitehorse": "CA",
|
|
227
|
+
"America/Winnipeg": "CA",
|
|
228
|
+
"America/Yakutat": "US",
|
|
229
|
+
"Asia/Bangkok": "TH",
|
|
230
|
+
"Asia/Calcutta": "IN",
|
|
231
|
+
"Asia/Dacca": "BD",
|
|
232
|
+
"Asia/Dhaka": "BD",
|
|
233
|
+
"Asia/Dubai": "AE",
|
|
234
|
+
"Asia/Hong_Kong": "HK",
|
|
235
|
+
"Asia/Ho_Chi_Minh": "VN",
|
|
236
|
+
"Asia/Jakarta": "ID",
|
|
237
|
+
"Asia/Jerusalem": "IL",
|
|
238
|
+
"Asia/Karachi": "PK",
|
|
239
|
+
"Asia/Kolkata": "IN",
|
|
240
|
+
"Asia/Kuala_Lumpur": "MY",
|
|
241
|
+
"Asia/Kuwait": "KW",
|
|
242
|
+
"Asia/Manila": "PH",
|
|
243
|
+
"Asia/Qatar": "QA",
|
|
244
|
+
"Asia/Riyadh": "SA",
|
|
245
|
+
"Asia/Seoul": "KR",
|
|
246
|
+
"Asia/Shanghai": "CN",
|
|
247
|
+
"Asia/Saigon": "VN",
|
|
248
|
+
"Asia/Singapore": "SG",
|
|
249
|
+
"Asia/Taipei": "TW",
|
|
250
|
+
"Asia/Tel_Aviv": "IL",
|
|
251
|
+
"Asia/Tokyo": "JP",
|
|
252
|
+
"Asia/Urumqi": "CN",
|
|
253
|
+
"Atlantic/Reykjavik": "IS",
|
|
254
|
+
"Australia/Adelaide": "AU",
|
|
255
|
+
"Australia/Brisbane": "AU",
|
|
256
|
+
"Australia/Darwin": "AU",
|
|
257
|
+
"Australia/Hobart": "AU",
|
|
258
|
+
"Australia/Melbourne": "AU",
|
|
259
|
+
"Australia/Perth": "AU",
|
|
260
|
+
"Australia/Sydney": "AU",
|
|
261
|
+
"Europe/Amsterdam": "NL",
|
|
262
|
+
"Europe/Athens": "GR",
|
|
263
|
+
"Europe/Belgrade": "RS",
|
|
264
|
+
"Europe/Berlin": "DE",
|
|
265
|
+
"Europe/Bratislava": "SK",
|
|
266
|
+
"Europe/Brussels": "BE",
|
|
267
|
+
"Europe/Bucharest": "RO",
|
|
268
|
+
"Europe/Budapest": "HU",
|
|
269
|
+
"Europe/Copenhagen": "DK",
|
|
270
|
+
"Europe/Dublin": "IE",
|
|
271
|
+
"Europe/Helsinki": "FI",
|
|
272
|
+
"Europe/Zagreb": "HR",
|
|
273
|
+
"Europe/Istanbul": "TR",
|
|
274
|
+
"Europe/Kiev": "UA",
|
|
275
|
+
"Europe/Kyiv": "UA",
|
|
276
|
+
"Europe/Lisbon": "PT",
|
|
277
|
+
"Europe/Ljubljana": "SI",
|
|
278
|
+
"Europe/London": "GB",
|
|
279
|
+
"Europe/Luxembourg": "LU",
|
|
280
|
+
"Europe/Madrid": "ES",
|
|
281
|
+
"Europe/Moscow": "RU",
|
|
282
|
+
"Europe/Oslo": "NO",
|
|
283
|
+
"Europe/Paris": "FR",
|
|
284
|
+
"Europe/Prague": "CZ",
|
|
285
|
+
"Europe/Riga": "LV",
|
|
286
|
+
"Europe/Rome": "IT",
|
|
287
|
+
"Europe/Sofia": "BG",
|
|
288
|
+
"Europe/Stockholm": "SE",
|
|
289
|
+
"Europe/Tallinn": "EE",
|
|
290
|
+
"Europe/Vienna": "AT",
|
|
291
|
+
"Europe/Vilnius": "LT",
|
|
292
|
+
"Europe/Warsaw": "PL",
|
|
293
|
+
"Europe/Zurich": "CH",
|
|
294
|
+
"Pacific/Auckland": "NZ"
|
|
295
|
+
};
|
|
132
296
|
function flagEmoji(iso2) {
|
|
133
297
|
const cc = iso2.toUpperCase();
|
|
134
298
|
if (!/^[A-Z]{2}$/.test(cc)) return "\u{1F3F3}\uFE0F";
|
|
@@ -137,6 +301,9 @@ function flagEmoji(iso2) {
|
|
|
137
301
|
function countryByIso(iso2) {
|
|
138
302
|
return iso2 ? byIso.get(iso2.toUpperCase()) : void 0;
|
|
139
303
|
}
|
|
304
|
+
function countryByTimeZone(timeZone) {
|
|
305
|
+
return countryByIso(timeZone ? timeZoneIso[timeZone] : void 0);
|
|
306
|
+
}
|
|
140
307
|
function countryByDialCode(digits) {
|
|
141
308
|
let best;
|
|
142
309
|
for (const c of PHONE_COUNTRIES) {
|
|
@@ -200,11 +367,11 @@ function isPossiblePhone(value) {
|
|
|
200
367
|
|
|
201
368
|
// src/validation.ts
|
|
202
369
|
var fileValueSchema = z.object({
|
|
203
|
-
fileId: z.string().
|
|
370
|
+
fileId: z.string().check(z.minLength(1)),
|
|
204
371
|
name: z.string(),
|
|
205
|
-
size: z.number().nonnegative(),
|
|
372
|
+
size: z.number().check(z.nonnegative()),
|
|
206
373
|
mime: z.string(),
|
|
207
|
-
url: z.
|
|
374
|
+
url: z.optional(z.string())
|
|
208
375
|
});
|
|
209
376
|
function isEmpty(value) {
|
|
210
377
|
if (value === null || value === void 0) return true;
|
|
@@ -251,7 +418,7 @@ function validateField(field, value) {
|
|
|
251
418
|
}
|
|
252
419
|
case "number": {
|
|
253
420
|
const n = typeof value === "string" ? Number(value) : value;
|
|
254
|
-
if (typeof n !== "number" || Number.
|
|
421
|
+
if (typeof n !== "number" || !Number.isFinite(n)) return "Enter a number";
|
|
255
422
|
if (field.min !== void 0 && n < field.min) return `Must be at least ${field.min}`;
|
|
256
423
|
if (field.max !== void 0 && n > field.max) return `Must be at most ${field.max}`;
|
|
257
424
|
return null;
|
|
@@ -329,6 +496,10 @@ function validateField(field, value) {
|
|
|
329
496
|
case "date": {
|
|
330
497
|
if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(value))
|
|
331
498
|
return "Enter a valid date";
|
|
499
|
+
const [y, m, d] = value.split("-").map(Number);
|
|
500
|
+
const dt = new Date(Date.UTC(y, m - 1, d));
|
|
501
|
+
if (dt.getUTCFullYear() !== y || dt.getUTCMonth() !== m - 1 || dt.getUTCDate() !== d)
|
|
502
|
+
return "Enter a valid date";
|
|
332
503
|
return null;
|
|
333
504
|
}
|
|
334
505
|
case "file_upload": {
|
|
@@ -362,7 +533,7 @@ function normalizeValue(field, value) {
|
|
|
362
533
|
const n = Number(value);
|
|
363
534
|
return Number.isNaN(n) ? value : n;
|
|
364
535
|
}
|
|
365
|
-
if (field.kind === "rating" && typeof value === "string") {
|
|
536
|
+
if ((field.kind === "rating" || field.kind === "linear_scale") && typeof value === "string") {
|
|
366
537
|
const n = Number(value);
|
|
367
538
|
return Number.isNaN(n) ? value : n;
|
|
368
539
|
}
|
|
@@ -371,7 +542,7 @@ function normalizeValue(field, value) {
|
|
|
371
542
|
}
|
|
372
543
|
|
|
373
544
|
// src/schema-validation.ts
|
|
374
|
-
import
|
|
545
|
+
import * as z2 from "zod/mini";
|
|
375
546
|
var FIELD_KINDS = [
|
|
376
547
|
"short_text",
|
|
377
548
|
"long_text",
|
|
@@ -395,27 +566,31 @@ var FIELD_KINDS = [
|
|
|
395
566
|
];
|
|
396
567
|
var CONTENT_KINDS2 = ["heading", "paragraph", "divider"];
|
|
397
568
|
var ALL_KINDS = [...FIELD_KINDS, ...CONTENT_KINDS2];
|
|
398
|
-
var idSchema = z2.string().
|
|
399
|
-
var optionSchema = z2.
|
|
400
|
-
|
|
569
|
+
var idSchema = z2.string().check(z2.minLength(1), z2.maxLength(128));
|
|
570
|
+
var optionSchema = z2.looseObject({
|
|
571
|
+
id: idSchema,
|
|
572
|
+
label: z2.string().check(z2.maxLength(1e3)),
|
|
573
|
+
icon: z2.optional(z2.unknown())
|
|
574
|
+
});
|
|
575
|
+
var blockSchema = z2.looseObject({
|
|
401
576
|
id: idSchema,
|
|
402
577
|
kind: z2.enum(ALL_KINDS),
|
|
403
|
-
label: z2.string().
|
|
404
|
-
options: z2.array(optionSchema).
|
|
405
|
-
rows: z2.array(optionSchema).
|
|
406
|
-
columns: z2.array(optionSchema).
|
|
407
|
-
})
|
|
578
|
+
label: z2.optional(z2.string().check(z2.maxLength(2e3))),
|
|
579
|
+
options: z2.optional(z2.array(optionSchema).check(z2.maxLength(200))),
|
|
580
|
+
rows: z2.optional(z2.array(optionSchema).check(z2.maxLength(200))),
|
|
581
|
+
columns: z2.optional(z2.array(optionSchema).check(z2.maxLength(200)))
|
|
582
|
+
});
|
|
408
583
|
var pageSchema = z2.object({
|
|
409
584
|
id: idSchema,
|
|
410
|
-
title: z2.string().
|
|
411
|
-
blocks: z2.array(blockSchema).
|
|
585
|
+
title: z2.optional(z2.string().check(z2.maxLength(500))),
|
|
586
|
+
blocks: z2.array(blockSchema).check(z2.maxLength(500))
|
|
412
587
|
});
|
|
413
588
|
var schemaShape = z2.object({
|
|
414
589
|
version: z2.literal(1),
|
|
415
|
-
title: z2.string().
|
|
416
|
-
description: z2.string().
|
|
417
|
-
pages: z2.array(pageSchema).
|
|
418
|
-
settings: z2.record(z2.string(), z2.unknown())
|
|
590
|
+
title: z2._default(z2.optional(z2.string().check(z2.maxLength(2e3))), ""),
|
|
591
|
+
description: z2.optional(z2.string().check(z2.maxLength(5e3))),
|
|
592
|
+
pages: z2.array(pageSchema).check(z2.minLength(1), z2.maxLength(50)),
|
|
593
|
+
settings: z2._default(z2.optional(z2.record(z2.string(), z2.unknown())), {})
|
|
419
594
|
});
|
|
420
595
|
var MAX_SCHEMA_VERSION = 1;
|
|
421
596
|
var FILLO_SCHEMA_VERSION = 1;
|
|
@@ -505,7 +680,7 @@ function normalizeSettings(value) {
|
|
|
505
680
|
redirectUrl: normalizeUrl(rec.redirectUrl),
|
|
506
681
|
showProgress: bool(rec.showProgress),
|
|
507
682
|
submissionLimit,
|
|
508
|
-
notifyEmail: z2.email().
|
|
683
|
+
notifyEmail: z2.email().check(z2.maxLength(254)).safeParse(rec.notifyEmail).success ? rec.notifyEmail : void 0,
|
|
509
684
|
sendReceipt: bool(rec.sendReceipt)
|
|
510
685
|
};
|
|
511
686
|
}
|
|
@@ -698,15 +873,20 @@ function formatAnswer(field, value) {
|
|
|
698
873
|
if (value === null || value === void 0 || value === "") return "";
|
|
699
874
|
switch (field.kind) {
|
|
700
875
|
case "file_upload":
|
|
876
|
+
if (!Array.isArray(value)) return String(value);
|
|
701
877
|
return value.map((f) => f.name).join(", ");
|
|
702
878
|
case "select":
|
|
703
879
|
case "dropdown":
|
|
704
880
|
return field.options.find((o) => o.id === value)?.label ?? String(value);
|
|
705
881
|
case "multi_select":
|
|
882
|
+
if (!Array.isArray(value)) return String(value);
|
|
706
883
|
return value.map((id) => field.options.find((o) => o.id === id)?.label ?? id).join(", ");
|
|
707
884
|
case "ranking":
|
|
885
|
+
if (!Array.isArray(value)) return String(value);
|
|
708
886
|
return value.map((id, i) => `${i + 1}. ${field.options.find((o) => o.id === id)?.label ?? id}`).join(", ");
|
|
709
887
|
case "matrix": {
|
|
888
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
889
|
+
return String(value);
|
|
710
890
|
const answers = value;
|
|
711
891
|
return field.rows.filter((row) => answers[row.id]).map((row) => `${row.label}: ${field.columns.find((c) => c.id === answers[row.id])?.label ?? ""}`).join(", ");
|
|
712
892
|
}
|
|
@@ -1083,6 +1263,15 @@ var FilloError = class extends Error {
|
|
|
1083
1263
|
status;
|
|
1084
1264
|
};
|
|
1085
1265
|
var MAX_CHUNK_RETRIES = 3;
|
|
1266
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1267
|
+
function isRetryableStatus(status) {
|
|
1268
|
+
return status >= 500 || status === 429 || status === 408;
|
|
1269
|
+
}
|
|
1270
|
+
function asRequestError(err) {
|
|
1271
|
+
if (err instanceof DOMException && err.name === "TimeoutError")
|
|
1272
|
+
return new FilloError("Request timed out", 0);
|
|
1273
|
+
return err;
|
|
1274
|
+
}
|
|
1086
1275
|
function compareVersions(a, b) {
|
|
1087
1276
|
const left = a.split(".").map((part) => Number(part) || 0);
|
|
1088
1277
|
const right = b.split(".").map((part) => Number(part) || 0);
|
|
@@ -1137,10 +1326,17 @@ var FilloClient = class {
|
|
|
1137
1326
|
return `${this.baseUrl}/api/v1${path}`;
|
|
1138
1327
|
}
|
|
1139
1328
|
async json(path, init) {
|
|
1140
|
-
const
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1329
|
+
const headers = init?.body != null ? { "Content-Type": "application/json", ...init?.headers } : init?.headers;
|
|
1330
|
+
let res;
|
|
1331
|
+
try {
|
|
1332
|
+
res = await this.fetch(this.url(path), {
|
|
1333
|
+
...init,
|
|
1334
|
+
headers,
|
|
1335
|
+
signal: init?.signal ?? AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
1336
|
+
});
|
|
1337
|
+
} catch (err) {
|
|
1338
|
+
throw asRequestError(err);
|
|
1339
|
+
}
|
|
1144
1340
|
if (!res.ok) {
|
|
1145
1341
|
const body = await res.text().catch(() => "");
|
|
1146
1342
|
throw new FilloError(body || `Request failed: ${res.status}`, res.status);
|
|
@@ -1179,11 +1375,17 @@ var FilloClient = class {
|
|
|
1179
1375
|
}
|
|
1180
1376
|
/** Submit a response. Returns per-field errors instead of throwing on validation failure. */
|
|
1181
1377
|
async submit(formId, data, meta) {
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1378
|
+
let res;
|
|
1379
|
+
try {
|
|
1380
|
+
res = await this.fetch(this.url(`/forms/${encodeURIComponent(formId)}/responses`), {
|
|
1381
|
+
method: "POST",
|
|
1382
|
+
headers: { "Content-Type": "application/json" },
|
|
1383
|
+
body: JSON.stringify({ data, meta }),
|
|
1384
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
1385
|
+
});
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
throw asRequestError(err);
|
|
1388
|
+
}
|
|
1187
1389
|
if (res.status === 422) {
|
|
1188
1390
|
const body2 = await res.json();
|
|
1189
1391
|
return { ok: false, errors: body2.errors };
|
|
@@ -1203,7 +1405,8 @@ var FilloClient = class {
|
|
|
1203
1405
|
const res = await this.fetch(this.url(`/forms/${encodeURIComponent(formId)}/sessions`), {
|
|
1204
1406
|
method: "POST",
|
|
1205
1407
|
headers: { "Content-Type": "application/json" },
|
|
1206
|
-
body: JSON.stringify({ pageCount })
|
|
1408
|
+
body: JSON.stringify({ pageCount }),
|
|
1409
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
1207
1410
|
});
|
|
1208
1411
|
if (!res.ok) return null;
|
|
1209
1412
|
const body = await res.json();
|
|
@@ -1219,7 +1422,8 @@ var FilloClient = class {
|
|
|
1219
1422
|
method: "POST",
|
|
1220
1423
|
headers: { "Content-Type": "application/json" },
|
|
1221
1424
|
body: JSON.stringify(data),
|
|
1222
|
-
keepalive: true
|
|
1425
|
+
keepalive: true,
|
|
1426
|
+
signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
|
|
1223
1427
|
}).catch(() => {
|
|
1224
1428
|
});
|
|
1225
1429
|
} catch {
|
|
@@ -1303,10 +1507,17 @@ var FilloClient = class {
|
|
|
1303
1507
|
JSON.stringify({ name: transport.fileName, parent: { id: transport.folderId } })
|
|
1304
1508
|
);
|
|
1305
1509
|
form.append("file", file, transport.fileName);
|
|
1306
|
-
const res = await this.retry(
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1510
|
+
const res = await this.retry(options2, async () => {
|
|
1511
|
+
const r = await this.fetch(transport.uploadUrl, {
|
|
1512
|
+
method: "POST",
|
|
1513
|
+
headers: auth,
|
|
1514
|
+
body: form,
|
|
1515
|
+
signal: this.uploadSignal(options2)
|
|
1516
|
+
});
|
|
1517
|
+
if (!r.ok && isRetryableStatus(r.status))
|
|
1518
|
+
throw new FilloError(`Box upload failed: ${r.status}`, r.status);
|
|
1519
|
+
return r;
|
|
1520
|
+
});
|
|
1310
1521
|
if (!res.ok) throw new FilloError(`Box upload failed: ${res.status}`, res.status);
|
|
1311
1522
|
const body = await res.json();
|
|
1312
1523
|
report(file.size);
|
|
@@ -1318,9 +1529,8 @@ var FilloClient = class {
|
|
|
1318
1529
|
400
|
|
1319
1530
|
);
|
|
1320
1531
|
}
|
|
1321
|
-
const sessionRes = await this.retry(
|
|
1322
|
-
|
|
1323
|
-
() => this.fetch(transport.sessionUrl, {
|
|
1532
|
+
const sessionRes = await this.retry(options2, async () => {
|
|
1533
|
+
const r = await this.fetch(transport.sessionUrl, {
|
|
1324
1534
|
method: "POST",
|
|
1325
1535
|
headers: { ...auth, "Content-Type": "application/json" },
|
|
1326
1536
|
body: JSON.stringify({
|
|
@@ -1328,9 +1538,12 @@ var FilloClient = class {
|
|
|
1328
1538
|
file_size: transport.size,
|
|
1329
1539
|
file_name: transport.fileName
|
|
1330
1540
|
}),
|
|
1331
|
-
signal: options2
|
|
1332
|
-
})
|
|
1333
|
-
|
|
1541
|
+
signal: this.uploadSignal(options2)
|
|
1542
|
+
});
|
|
1543
|
+
if (!r.ok && isRetryableStatus(r.status))
|
|
1544
|
+
throw new FilloError(`Box session failed: ${r.status}`, r.status);
|
|
1545
|
+
return r;
|
|
1546
|
+
});
|
|
1334
1547
|
if (!sessionRes.ok) throw new FilloError(`Box session failed: ${sessionRes.status}`, sessionRes.status);
|
|
1335
1548
|
const session = await sessionRes.json();
|
|
1336
1549
|
const whole = new Sha1();
|
|
@@ -1342,9 +1555,8 @@ var FilloClient = class {
|
|
|
1342
1555
|
const bytes = new Uint8Array(await file.slice(offset, end).arrayBuffer());
|
|
1343
1556
|
whole.update(bytes);
|
|
1344
1557
|
const partDigest = await digestPartBase64(bytes);
|
|
1345
|
-
const res = await this.retry(
|
|
1346
|
-
|
|
1347
|
-
() => this.fetch(session.session_endpoints.upload_part, {
|
|
1558
|
+
const res = await this.retry(options2, async () => {
|
|
1559
|
+
const r = await this.fetch(session.session_endpoints.upload_part, {
|
|
1348
1560
|
method: "PUT",
|
|
1349
1561
|
headers: {
|
|
1350
1562
|
...auth,
|
|
@@ -1353,27 +1565,41 @@ var FilloClient = class {
|
|
|
1353
1565
|
"Content-Range": `bytes ${offset}-${end - 1}/${file.size}`
|
|
1354
1566
|
},
|
|
1355
1567
|
body: bytes,
|
|
1356
|
-
signal: options2
|
|
1357
|
-
})
|
|
1358
|
-
|
|
1568
|
+
signal: this.uploadSignal(options2)
|
|
1569
|
+
});
|
|
1570
|
+
if (!r.ok && isRetryableStatus(r.status))
|
|
1571
|
+
throw new FilloError(`Box part failed: ${r.status}`, r.status);
|
|
1572
|
+
return r;
|
|
1573
|
+
});
|
|
1359
1574
|
if (!res.ok) throw new FilloError(`Box part failed: ${res.status}`, res.status);
|
|
1360
1575
|
parts.push((await res.json()).part);
|
|
1361
1576
|
offset = end;
|
|
1362
1577
|
report(offset);
|
|
1363
1578
|
}
|
|
1364
|
-
const commitRes = await this.retry(
|
|
1365
|
-
|
|
1366
|
-
() => this.fetch(session.session_endpoints.commit, {
|
|
1579
|
+
const commitRes = await this.retry(options2, async () => {
|
|
1580
|
+
const r = await this.fetch(session.session_endpoints.commit, {
|
|
1367
1581
|
method: "POST",
|
|
1368
1582
|
headers: { ...auth, "Content-Type": "application/json", Digest: `sha=${bytesToBase64(whole.digest())}` },
|
|
1369
1583
|
body: JSON.stringify({ parts }),
|
|
1370
|
-
signal: options2
|
|
1371
|
-
})
|
|
1372
|
-
|
|
1584
|
+
signal: this.uploadSignal(options2)
|
|
1585
|
+
});
|
|
1586
|
+
if (!r.ok && isRetryableStatus(r.status))
|
|
1587
|
+
throw new FilloError(`Box commit failed: ${r.status}`, r.status);
|
|
1588
|
+
return r;
|
|
1589
|
+
});
|
|
1373
1590
|
if (!commitRes.ok) throw new FilloError(`Box commit failed: ${commitRes.status}`, commitRes.status);
|
|
1374
1591
|
const committed = await commitRes.json();
|
|
1375
1592
|
return { providerFileId: committed.entries?.[0]?.id };
|
|
1376
1593
|
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Caller signal (if any) combined with a fresh per-request timeout. Created per
|
|
1596
|
+
* fetch so each retry attempt gets its own deadline; the caller's own abort
|
|
1597
|
+
* still drives the resumable-upload cancel semantics.
|
|
1598
|
+
*/
|
|
1599
|
+
uploadSignal(options2) {
|
|
1600
|
+
const timeout = AbortSignal.timeout(DEFAULT_TIMEOUT_MS);
|
|
1601
|
+
return options2.signal ? AbortSignal.any([options2.signal, timeout]) : timeout;
|
|
1602
|
+
}
|
|
1377
1603
|
/** Retry a request with exponential backoff; never retries an aborted upload. */
|
|
1378
1604
|
async retry(options2, fn) {
|
|
1379
1605
|
let attempt = 0;
|
|
@@ -1398,10 +1624,12 @@ var FilloClient = class {
|
|
|
1398
1624
|
let offset = session.uploadedBytes;
|
|
1399
1625
|
const report = makeReporter(file.size, options2);
|
|
1400
1626
|
report(offset);
|
|
1627
|
+
let stalls = 0;
|
|
1401
1628
|
while (offset < file.size) {
|
|
1402
1629
|
options2.signal?.throwIfAborted();
|
|
1403
|
-
const
|
|
1404
|
-
|
|
1630
|
+
const startedAt = offset;
|
|
1631
|
+
let end = Math.min(offset + session.chunkSize, file.size);
|
|
1632
|
+
let chunk = file.slice(offset, end);
|
|
1405
1633
|
let attempt = 0;
|
|
1406
1634
|
for (; ; ) {
|
|
1407
1635
|
try {
|
|
@@ -1411,7 +1639,7 @@ var FilloClient = class {
|
|
|
1411
1639
|
"Content-Range": `bytes ${offset}-${end - 1}/${file.size}`
|
|
1412
1640
|
},
|
|
1413
1641
|
body: chunk,
|
|
1414
|
-
signal: options2
|
|
1642
|
+
signal: this.uploadSignal(options2)
|
|
1415
1643
|
});
|
|
1416
1644
|
if (res.status === 308) {
|
|
1417
1645
|
const range = res.headers.get("Range") ?? res.headers.get("range");
|
|
@@ -1433,9 +1661,19 @@ var FilloClient = class {
|
|
|
1433
1661
|
await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
|
|
1434
1662
|
const fresh = await this.getUploadSession(session.id, session.token);
|
|
1435
1663
|
offset = fresh.uploadedBytes;
|
|
1664
|
+
if (offset >= file.size) break;
|
|
1665
|
+
end = Math.min(offset + session.chunkSize, file.size);
|
|
1666
|
+
chunk = file.slice(offset, end);
|
|
1436
1667
|
}
|
|
1437
1668
|
}
|
|
1438
1669
|
report(offset);
|
|
1670
|
+
if (offset <= startedAt) {
|
|
1671
|
+
if (++stalls >= MAX_CHUNK_RETRIES) {
|
|
1672
|
+
throw new FilloError("Drive upload stalled \u2014 the storage endpoint stopped accepting progress", 0);
|
|
1673
|
+
}
|
|
1674
|
+
} else {
|
|
1675
|
+
stalls = 0;
|
|
1676
|
+
}
|
|
1439
1677
|
}
|
|
1440
1678
|
}
|
|
1441
1679
|
/**
|
|
@@ -1452,7 +1690,7 @@ var FilloClient = class {
|
|
|
1452
1690
|
method: "PUT",
|
|
1453
1691
|
headers: { "Content-Type": file.type || "application/octet-stream" },
|
|
1454
1692
|
body: file,
|
|
1455
|
-
signal: options2
|
|
1693
|
+
signal: this.uploadSignal(options2)
|
|
1456
1694
|
});
|
|
1457
1695
|
if (!res.ok) throw new FilloError(`S3 upload failed: ${res.status}`, res.status);
|
|
1458
1696
|
report(file.size);
|
|
@@ -1567,6 +1805,7 @@ function createFormController(options2) {
|
|
|
1567
1805
|
const uploadingFields = /* @__PURE__ */ new Set();
|
|
1568
1806
|
const listeners = /* @__PURE__ */ new Set();
|
|
1569
1807
|
const mountedAt = Date.now();
|
|
1808
|
+
const idempotencyKey = randomSubmissionKey();
|
|
1570
1809
|
let sessionId = null;
|
|
1571
1810
|
let sessionStarted = false;
|
|
1572
1811
|
function computeSnapshot() {
|
|
@@ -1659,7 +1898,8 @@ function createFormController(options2) {
|
|
|
1659
1898
|
return;
|
|
1660
1899
|
}
|
|
1661
1900
|
if (client && formId) {
|
|
1662
|
-
const
|
|
1901
|
+
const visitorKey = form.settings.submissionLimit === "once_per_visitor" ? ensureSubmissionKey(formId) : void 0;
|
|
1902
|
+
const submissionKey = visitorKey ?? idempotencyKey;
|
|
1663
1903
|
status = "submitting";
|
|
1664
1904
|
notify();
|
|
1665
1905
|
let res;
|
|
@@ -1667,7 +1907,7 @@ function createFormController(options2) {
|
|
|
1667
1907
|
res = await client.submit(formId, result.data, {
|
|
1668
1908
|
hp: options2.getHoneypot?.() ?? "",
|
|
1669
1909
|
elapsedMs: Date.now() - mountedAt,
|
|
1670
|
-
surface: options2.surface ?? "
|
|
1910
|
+
surface: options2.surface ?? "headless",
|
|
1671
1911
|
submissionKey
|
|
1672
1912
|
});
|
|
1673
1913
|
} catch (err) {
|
|
@@ -1682,7 +1922,7 @@ function createFormController(options2) {
|
|
|
1682
1922
|
return;
|
|
1683
1923
|
}
|
|
1684
1924
|
if (sessionId) client.reportProgress(sessionId, { completed: true });
|
|
1685
|
-
if (
|
|
1925
|
+
if (visitorKey) markSubmitted(formId, res.responseId);
|
|
1686
1926
|
status = "submitted";
|
|
1687
1927
|
notify();
|
|
1688
1928
|
emitSubmitted(res.responseId, result.data);
|
|
@@ -1765,6 +2005,12 @@ function syncCodeForm(client, form) {
|
|
|
1765
2005
|
}
|
|
1766
2006
|
|
|
1767
2007
|
// src/prefill.ts
|
|
2008
|
+
function parseDecimal(raw) {
|
|
2009
|
+
const s = raw.trim();
|
|
2010
|
+
if (!/^-?\d*\.?\d+$/.test(s)) return null;
|
|
2011
|
+
const n = Number(s);
|
|
2012
|
+
return Number.isFinite(n) ? n : null;
|
|
2013
|
+
}
|
|
1768
2014
|
function prefillFromParams(form, params) {
|
|
1769
2015
|
const data = {};
|
|
1770
2016
|
for (const field of allFields(form)) {
|
|
@@ -1772,23 +2018,24 @@ function prefillFromParams(form, params) {
|
|
|
1772
2018
|
if (raw === void 0 || raw === "") continue;
|
|
1773
2019
|
switch (field.kind) {
|
|
1774
2020
|
case "number": {
|
|
1775
|
-
const n =
|
|
1776
|
-
if (
|
|
2021
|
+
const n = parseDecimal(raw);
|
|
2022
|
+
if (n === null) break;
|
|
1777
2023
|
if (field.min !== void 0 && n < field.min) break;
|
|
1778
2024
|
if (field.max !== void 0 && n > field.max) break;
|
|
1779
2025
|
data[field.id] = n;
|
|
1780
2026
|
break;
|
|
1781
2027
|
}
|
|
1782
2028
|
case "rating": {
|
|
1783
|
-
const n =
|
|
1784
|
-
if (Number.isInteger(n) && n >= 1 && n <= (field.max ?? 5))
|
|
2029
|
+
const n = parseDecimal(raw);
|
|
2030
|
+
if (n !== null && Number.isInteger(n) && n >= 1 && n <= (field.max ?? 5))
|
|
2031
|
+
data[field.id] = n;
|
|
1785
2032
|
break;
|
|
1786
2033
|
}
|
|
1787
2034
|
case "linear_scale": {
|
|
1788
|
-
const n =
|
|
2035
|
+
const n = parseDecimal(raw);
|
|
1789
2036
|
const min = field.min ?? 1;
|
|
1790
2037
|
const max = field.max ?? 10;
|
|
1791
|
-
if (Number.isInteger(n) && n >= min && n <= max) data[field.id] = n;
|
|
2038
|
+
if (n !== null && Number.isInteger(n) && n >= min && n <= max) data[field.id] = n;
|
|
1792
2039
|
break;
|
|
1793
2040
|
}
|
|
1794
2041
|
case "checkbox":
|
|
@@ -1861,6 +2108,7 @@ export {
|
|
|
1861
2108
|
contentHash,
|
|
1862
2109
|
countryByDialCode,
|
|
1863
2110
|
countryByIso,
|
|
2111
|
+
countryByTimeZone,
|
|
1864
2112
|
createBlock,
|
|
1865
2113
|
createClient,
|
|
1866
2114
|
createEmptyForm,
|