@usefillo/core 0.17.0 → 0.19.0
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 +204 -171
- package/dist/index.js +142 -77
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -400,7 +400,7 @@ interface FormSchema {
|
|
|
400
400
|
settings: FormSettings;
|
|
401
401
|
}
|
|
402
402
|
interface FormTheme {
|
|
403
|
-
/**
|
|
403
|
+
/** Omit to inherit the host page. Use "auto" to follow the visitor's system directly. */
|
|
404
404
|
colorScheme?: "light" | "dark" | "auto";
|
|
405
405
|
/** Accent for buttons, focus rings, selection. */
|
|
406
406
|
primary?: string;
|
|
@@ -1027,7 +1027,7 @@ declare function assembleForm(spec: FormDraftSpec): FormSchema;
|
|
|
1027
1027
|
|
|
1028
1028
|
interface FilloClientOptions {
|
|
1029
1029
|
/**
|
|
1030
|
-
* Publishable
|
|
1030
|
+
* Publishable project key (pk_…) — safe to ship in client code. Required
|
|
1031
1031
|
* only for syncing code-defined forms into the workspace.
|
|
1032
1032
|
*/
|
|
1033
1033
|
key?: string;
|
|
@@ -1176,7 +1176,7 @@ interface SubmitMeta {
|
|
|
1176
1176
|
* webhooks, and integrations can say WHO answered.
|
|
1177
1177
|
*/
|
|
1178
1178
|
interface FilloRespondent {
|
|
1179
|
-
/** Your stable user/account id — the identity key within your
|
|
1179
|
+
/** Your stable user/account id — the identity key within your project. */
|
|
1180
1180
|
id: string;
|
|
1181
1181
|
email?: string;
|
|
1182
1182
|
name?: string;
|
|
@@ -1184,8 +1184,8 @@ interface FilloRespondent {
|
|
|
1184
1184
|
traits?: Record<string, string | number | boolean>;
|
|
1185
1185
|
/**
|
|
1186
1186
|
* Identity verification (optional): hex HMAC-SHA256 of `id`, computed on
|
|
1187
|
-
* YOUR server with the
|
|
1188
|
-
* the
|
|
1187
|
+
* YOUR server with the project identity secret from Fillo settings. Once
|
|
1188
|
+
* the project holds a secret, Fillo records identity only with a valid
|
|
1189
1189
|
* hash — never compute this in the browser or the secret leaks.
|
|
1190
1190
|
*/
|
|
1191
1191
|
hash?: string;
|
|
@@ -1255,6 +1255,10 @@ declare function isFilloError(err: unknown): err is FilloError;
|
|
|
1255
1255
|
interface SyncFormResult {
|
|
1256
1256
|
formId: string;
|
|
1257
1257
|
slug: string;
|
|
1258
|
+
/** Absolute Fillo dashboard URL for reviewing and publishing this form.
|
|
1259
|
+
* This is server-owned and intentionally independent of `client.baseUrl`,
|
|
1260
|
+
* which may point at an app-owned API proxy. */
|
|
1261
|
+
manageUrl?: string;
|
|
1258
1262
|
branding?: FormBranding;
|
|
1259
1263
|
/** Human-verification challenge to render before submit, when the LIVE form
|
|
1260
1264
|
* requires one (staged changes don't gate until published). Absent = no
|
|
@@ -1318,7 +1322,7 @@ declare class FilloClient {
|
|
|
1318
1322
|
/** Server origin this client targets, normalized (no trailing slash). */
|
|
1319
1323
|
readonly baseUrl: string;
|
|
1320
1324
|
private fetch;
|
|
1321
|
-
/** Publishable
|
|
1325
|
+
/** Publishable project key, when configured. */
|
|
1322
1326
|
readonly key?: string;
|
|
1323
1327
|
constructor(options?: FilloClientOptions);
|
|
1324
1328
|
private url;
|
|
@@ -1327,7 +1331,7 @@ declare class FilloClient {
|
|
|
1327
1331
|
getForm(idOrSlug: string): Promise<PublishedForm>;
|
|
1328
1332
|
/**
|
|
1329
1333
|
* Resolve a code-defined form through the workspace identified by the
|
|
1330
|
-
* client's publishable key. Depending on
|
|
1334
|
+
* client's publishable key. Depending on project policy, changed content
|
|
1331
1335
|
* may be staged for review or resolved to the authoritative live snapshot.
|
|
1332
1336
|
* Returns the canonical form id used for submissions.
|
|
1333
1337
|
*/
|
|
@@ -1439,7 +1443,7 @@ declare class FilloClient {
|
|
|
1439
1443
|
}
|
|
1440
1444
|
declare function createClient(options?: FilloClientOptions): FilloClient;
|
|
1441
1445
|
interface ProvisionWorkspaceResult {
|
|
1442
|
-
/** Publishable key (pk_…) for the new workspace. Pass to createClient({ key }). */
|
|
1446
|
+
/** Publishable key (pk_…) for the new workspace's default project. Pass to createClient({ key }). */
|
|
1443
1447
|
key: string;
|
|
1444
1448
|
organizationId: string;
|
|
1445
1449
|
/** The private workspace link is emailed directly; `url` stays null. */
|
|
@@ -1473,6 +1477,181 @@ declare function provisionWorkspace(options: {
|
|
|
1473
1477
|
fetch?: typeof fetch;
|
|
1474
1478
|
}): Promise<ProvisionWorkspaceResult>;
|
|
1475
1479
|
|
|
1480
|
+
/**
|
|
1481
|
+
* The default English validation message for a required field left empty.
|
|
1482
|
+
* `validateField` returns this exact sentinel (core has no strings context),
|
|
1483
|
+
* and renderer layers swap it for field-aware/localized copy. Keep it
|
|
1484
|
+
* identical to `DEFAULT_FIELD_STRINGS.required`.
|
|
1485
|
+
*/
|
|
1486
|
+
declare const REQUIRED_FIELD_MESSAGE = "This field is required";
|
|
1487
|
+
/**
|
|
1488
|
+
* The form-chrome strings the default renderers emit, overridable as a unit so
|
|
1489
|
+
* a localized site never shows stray English at the submit moment.
|
|
1490
|
+
* Schema-authored text (labels, descriptions, success copy set in settings)
|
|
1491
|
+
* always wins over these defaults.
|
|
1492
|
+
*
|
|
1493
|
+
* Field-level and validation copy (required message, upload field, duplicate/
|
|
1494
|
+
* resume notices) lives in {@link FilloFieldStrings} — split out so this
|
|
1495
|
+
* documented, all-`string` surface stays stable while parametrized field
|
|
1496
|
+
* strings can be functions. The renderers resolve both together
|
|
1497
|
+
* ({@link FilloRendererStrings}); the `strings` prop overrides either.
|
|
1498
|
+
*/
|
|
1499
|
+
interface FilloStrings {
|
|
1500
|
+
back: string;
|
|
1501
|
+
next: string;
|
|
1502
|
+
submit: string;
|
|
1503
|
+
submitting: string;
|
|
1504
|
+
uploading: string;
|
|
1505
|
+
/** Suffix on non-required field labels. */
|
|
1506
|
+
optional: string;
|
|
1507
|
+
/** The "Other" free-text choice. */
|
|
1508
|
+
other: string;
|
|
1509
|
+
otherPrompt: string;
|
|
1510
|
+
otherPlaceholder: string;
|
|
1511
|
+
/** Dropdown placeholder when the field sets none. */
|
|
1512
|
+
choosePlaceholder: string;
|
|
1513
|
+
/** Success screen defaults — settings.successTitle/successMessage win. */
|
|
1514
|
+
successTitle: string;
|
|
1515
|
+
successMessage: string;
|
|
1516
|
+
closed: string;
|
|
1517
|
+
notLive: string;
|
|
1518
|
+
/** Title of the not-open state for a draft/storage-blocked form in production. */
|
|
1519
|
+
notOpenTitle: string;
|
|
1520
|
+
/** Body of the not-open state. */
|
|
1521
|
+
notOpenBody: string;
|
|
1522
|
+
/** Title of the closed-flavor state (expired/capped workspace);
|
|
1523
|
+
* the body reuses `closed`. */
|
|
1524
|
+
closedTitle: string;
|
|
1525
|
+
/** Fallback when a submit fails without a server message. */
|
|
1526
|
+
submitFailed: string;
|
|
1527
|
+
loadFailedNotFound: string;
|
|
1528
|
+
loadFailedNetwork: string;
|
|
1529
|
+
loadFailed: string;
|
|
1530
|
+
renderFailed: string;
|
|
1531
|
+
/** Saved-progress notice when a draft restored earlier answers. */
|
|
1532
|
+
resumeNotice: string;
|
|
1533
|
+
/** Upsert-mode notice when the person's previous response was prefilled. */
|
|
1534
|
+
editNotice: string;
|
|
1535
|
+
/** The discard action next to the resume notice. */
|
|
1536
|
+
resumeStartOver: string;
|
|
1537
|
+
/** Shown when the human-verification widget can't load (script blocked). */
|
|
1538
|
+
challengeUnavailable: string;
|
|
1539
|
+
}
|
|
1540
|
+
interface RespondentErrorStrings {
|
|
1541
|
+
submitFailed: string;
|
|
1542
|
+
loadFailedNotFound: string;
|
|
1543
|
+
loadFailedNetwork: string;
|
|
1544
|
+
formUnavailable: string;
|
|
1545
|
+
formClosed: string;
|
|
1546
|
+
submitRateLimited: string;
|
|
1547
|
+
respondentUnrecognized: string;
|
|
1548
|
+
fileUnavailable: string;
|
|
1549
|
+
scopeMissing: string;
|
|
1550
|
+
challengeIncomplete: string;
|
|
1551
|
+
challengeRetry: string;
|
|
1552
|
+
reviewAnswers: string;
|
|
1553
|
+
}
|
|
1554
|
+
type RespondentErrorOverrides = Omit<RespondentErrorStrings, "submitFailed" | "loadFailedNotFound" | "loadFailedNetwork" | "formClosed">;
|
|
1555
|
+
declare const DEFAULT_STRINGS: FilloStrings;
|
|
1556
|
+
declare const DEFAULT_RESPONDENT_ERROR_STRINGS: RespondentErrorStrings;
|
|
1557
|
+
/** Translate the renderer's existing top-level fallbacks plus its optional
|
|
1558
|
+
* respondent-only overrides into the controller's complete error contract. */
|
|
1559
|
+
declare function respondentErrorStringsFor(strings: Pick<FilloStrings, "submitFailed" | "loadFailedNotFound" | "loadFailedNetwork" | "closed"> & Pick<FilloFieldStrings, "respondentErrors">): RespondentErrorStrings;
|
|
1560
|
+
/**
|
|
1561
|
+
* Field-level and validation strings the default renderers emit. Kept apart
|
|
1562
|
+
* from {@link FilloStrings} so parametrized entries can be functions (a
|
|
1563
|
+
* translation places the value where its grammar needs it) without widening
|
|
1564
|
+
* the documented all-`string` chrome surface. Also the growth point for new
|
|
1565
|
+
* strings in general — unlike `FilloStrings`, nothing depends on this being
|
|
1566
|
+
* an exhaustively-enumerated, closed set.
|
|
1567
|
+
*/
|
|
1568
|
+
interface FilloFieldStrings {
|
|
1569
|
+
/** Optional, backward-compatible overrides for respondent error states that
|
|
1570
|
+
* do not already have a top-level renderer string. */
|
|
1571
|
+
respondentErrors?: Partial<RespondentErrorOverrides>;
|
|
1572
|
+
/** Generic/legacy validation copy for a required field left empty. Mirrors
|
|
1573
|
+
* REQUIRED_FIELD_MESSAGE and remains the fallback for custom/hidden fields. */
|
|
1574
|
+
required: string;
|
|
1575
|
+
/** Optional field-aware required copy. Existing `required` overrides keep
|
|
1576
|
+
* their legacy generic behavior unless this function is also supplied. */
|
|
1577
|
+
requiredForField?: (field: Field) => string;
|
|
1578
|
+
/** Notice when a spent/expired resume link couldn't restore progress. */
|
|
1579
|
+
resumeLinkExpired: string;
|
|
1580
|
+
/** Success-screen message when a verified identity re-submits and the form
|
|
1581
|
+
* keeps the first answer (a visible duplicate, not a fresh response). */
|
|
1582
|
+
alreadyAnswered: string;
|
|
1583
|
+
/** Retry control on a failed upload row. */
|
|
1584
|
+
uploadRetry: string;
|
|
1585
|
+
/** Accessible action names for upload rows. The filename is appended by the renderer. */
|
|
1586
|
+
uploadCancel: string;
|
|
1587
|
+
uploadRemove: string;
|
|
1588
|
+
uploadDismiss: string;
|
|
1589
|
+
/** Visible secondary status for an upload in progress. */
|
|
1590
|
+
uploadingFile: (percent: number, size: string) => string;
|
|
1591
|
+
/** Visible secondary status for a completed upload. */
|
|
1592
|
+
uploadedFile: (size: string) => string;
|
|
1593
|
+
/** Dropzone copy when uploads can't run because the form isn't connected. */
|
|
1594
|
+
uploadsDisabled: string;
|
|
1595
|
+
/** Dropzone copy for an explicit render-only preview, which never has transport. */
|
|
1596
|
+
uploadsRenderOnly: string;
|
|
1597
|
+
/** Dropzone copy when the server temporarily refuses new file sessions. */
|
|
1598
|
+
uploadsUnavailable: string;
|
|
1599
|
+
/** An upload request reached the server, but storage could not accept it. */
|
|
1600
|
+
uploadUnavailable: string;
|
|
1601
|
+
/** An upload attempt failed with no actionable server message. */
|
|
1602
|
+
uploadFailed: string;
|
|
1603
|
+
/** Dropzone call to action; `multiple` is true when several files are allowed. */
|
|
1604
|
+
dropzoneTitle: (multiple: boolean) => string;
|
|
1605
|
+
/** Dropzone hint stating the per-file size limit in MB. */
|
|
1606
|
+
dropzoneHint: (maxMb: number) => string;
|
|
1607
|
+
/** A file exceeded the per-file MB limit before upload started. */
|
|
1608
|
+
fileTooLarge: (maxMb: number) => string;
|
|
1609
|
+
/** Screen-reader status: N uploads in progress. */
|
|
1610
|
+
filesUploading: (count: number) => string;
|
|
1611
|
+
/** Screen-reader status: N uploads failed. */
|
|
1612
|
+
uploadsFailed: (count: number) => string;
|
|
1613
|
+
/** Screen-reader status: N uploads completed. */
|
|
1614
|
+
filesUploaded: (count: number) => string;
|
|
1615
|
+
/** Live-region announcement while a submit is in flight — for auto-submit
|
|
1616
|
+
* forms, which have no footer/button to show `submitting` on. */
|
|
1617
|
+
submittingAnnouncement: string;
|
|
1618
|
+
/** @deprecated Retained for localization compatibility. Default renderers
|
|
1619
|
+
* now focus the first invalid control and show field-aware inline guidance. */
|
|
1620
|
+
errorSummaryTitle: string;
|
|
1621
|
+
/** Live-region announcement after a ranking move: "«label», position n of m". */
|
|
1622
|
+
rankingPosition: (label: string, position: number, count: number) => string;
|
|
1623
|
+
/** Repeating group: the Add button's default label. */
|
|
1624
|
+
groupAdd: string;
|
|
1625
|
+
/** Repeating group instance heading — "«item» n of m". */
|
|
1626
|
+
groupInstanceLabel: (item: string, position: number, count: number) => string;
|
|
1627
|
+
/** Repeating group per-instance Remove button accessible label. */
|
|
1628
|
+
groupRemoveLabel: (item: string, position: number) => string;
|
|
1629
|
+
/** Live-region announcement after adding an instance. */
|
|
1630
|
+
groupInstanceAdded: (item: string, position: number, count: number) => string;
|
|
1631
|
+
/** Live-region announcement after removing an instance. */
|
|
1632
|
+
groupInstanceRemoved: (item: string, count: number) => string;
|
|
1633
|
+
/** Live-region announcement once a phone country-picker selection commits
|
|
1634
|
+
* (focus moves straight to the national input, so nothing else announces it). */
|
|
1635
|
+
phoneCountrySelected: (name: string) => string;
|
|
1636
|
+
/** Live-region announcement of the phone country-picker's filtered result
|
|
1637
|
+
* count as the respondent types in the search box. */
|
|
1638
|
+
phoneResultsCount: (count: number) => string;
|
|
1639
|
+
/** Accessible name/state for an empty signature canvas. */
|
|
1640
|
+
signatureEmpty: string;
|
|
1641
|
+
/** Accessible name/state for a signed signature canvas. */
|
|
1642
|
+
signatureSigned: string;
|
|
1643
|
+
}
|
|
1644
|
+
declare const DEFAULT_FIELD_STRINGS: FilloFieldStrings;
|
|
1645
|
+
/** Everything the default renderers can localize — chrome + field/validation. */
|
|
1646
|
+
type FilloRendererStrings = FilloStrings & FilloFieldStrings;
|
|
1647
|
+
/** Resolve the renderer copy for core's stable required-field sentinel. */
|
|
1648
|
+
declare function requiredFieldMessage(field: Field, strings: Pick<FilloFieldStrings, "required" | "requiredForField">): string;
|
|
1649
|
+
/** Merge overrides over the built-in chrome + field defaults. Accepts a partial
|
|
1650
|
+
* of the full renderer surface so the `strings` prop can override either set.
|
|
1651
|
+
* A legacy generic `required` override disables the new field-aware default
|
|
1652
|
+
* unless the caller explicitly supplies `requiredForField` too. */
|
|
1653
|
+
declare function resolveStrings(overrides?: Partial<FilloRendererStrings>): FilloRendererStrings;
|
|
1654
|
+
|
|
1476
1655
|
type FormStatus = "idle" | "submitting" | "submitted" | "error";
|
|
1477
1656
|
interface FormControllerOptions {
|
|
1478
1657
|
form: FormSchema;
|
|
@@ -1521,6 +1700,10 @@ interface FormControllerOptions {
|
|
|
1521
1700
|
* origins, or deployment commands.
|
|
1522
1701
|
*/
|
|
1523
1702
|
verboseResolutionErrors?: boolean;
|
|
1703
|
+
/** Safe respondent-facing fallbacks used when an API or transport error is
|
|
1704
|
+
* intentionally hidden. Renderers pass their resolved `strings` overrides so
|
|
1705
|
+
* this boundary never replaces localized copy with the English defaults. */
|
|
1706
|
+
respondentErrorStrings?: Partial<RespondentErrorStrings>;
|
|
1524
1707
|
/**
|
|
1525
1708
|
* Host-app account context (identify()): who is filling this form, by your
|
|
1526
1709
|
* own user id. Sent with the submission and recorded as an unverified
|
|
@@ -1631,6 +1814,8 @@ interface FormController {
|
|
|
1631
1814
|
client?: FilloClient;
|
|
1632
1815
|
/** Late-bind identify() context — host sessions often resolve after mount. */
|
|
1633
1816
|
respondent?: FilloRespondent;
|
|
1817
|
+
/** Replace localized respondent-safe error copy after a locale change. */
|
|
1818
|
+
respondentErrorStrings?: Partial<RespondentErrorStrings>;
|
|
1634
1819
|
}): void;
|
|
1635
1820
|
/**
|
|
1636
1821
|
* Persist any unsaved draft progress right now (settings.saveProgress
|
|
@@ -1785,16 +1970,15 @@ declare const FILLO_THEME_VARS: readonly [{
|
|
|
1785
1970
|
readonly var: "--fillo-primary-contrast";
|
|
1786
1971
|
}];
|
|
1787
1972
|
/**
|
|
1788
|
-
* Infer `colorScheme` for a `FormTheme` with `background`
|
|
1789
|
-
*
|
|
1790
|
-
*
|
|
1791
|
-
*
|
|
1792
|
-
* tokens in place, reading as near-white-on-white (≈1.10:1) on a dark
|
|
1793
|
-
* author-chosen background.
|
|
1973
|
+
* Infer `colorScheme` for a `FormTheme` with a fixed hex `background` and no
|
|
1974
|
+
* fixed light/dark scheme. A fixed background cannot safely keep following
|
|
1975
|
+
* the visitor's OS: a light-only host viewed on a dark OS otherwise gets dark
|
|
1976
|
+
* controls on a white form (and the reverse for dark backgrounds).
|
|
1794
1977
|
*
|
|
1795
|
-
* Explicit `
|
|
1796
|
-
*
|
|
1797
|
-
*
|
|
1978
|
+
* Explicit `light`/`dark` always wins. An absent scheme or `auto` is resolved
|
|
1979
|
+
* from the background's WCAG relative luminance, even when `text` is omitted,
|
|
1980
|
+
* so the remaining muted/border/control/error/contrast tokens stay readable.
|
|
1981
|
+
* A missing or non-`#rgb`/`#rrggbb` background passes through unchanged.
|
|
1798
1982
|
*
|
|
1799
1983
|
* Returns a `FormTheme`, not just the scheme, so a renderer can drop this
|
|
1800
1984
|
* in ahead of its existing code with no other change:
|
|
@@ -1805,163 +1989,12 @@ declare const FILLO_THEME_VARS: readonly [{
|
|
|
1805
1989
|
*/
|
|
1806
1990
|
declare function resolveThemeAppearance(theme: FormTheme | null): FormTheme | null;
|
|
1807
1991
|
|
|
1808
|
-
/**
|
|
1809
|
-
* The default English validation message for a required field left empty.
|
|
1810
|
-
* `validateField` returns this exact sentinel (core has no strings context),
|
|
1811
|
-
* and renderer layers swap it for field-aware/localized copy. Keep it
|
|
1812
|
-
* identical to `DEFAULT_FIELD_STRINGS.required`.
|
|
1813
|
-
*/
|
|
1814
|
-
declare const REQUIRED_FIELD_MESSAGE = "This field is required";
|
|
1815
|
-
/**
|
|
1816
|
-
* The form-chrome strings the default renderers emit, overridable as a unit so
|
|
1817
|
-
* a localized site never shows stray English at the submit moment.
|
|
1818
|
-
* Schema-authored text (labels, descriptions, success copy set in settings)
|
|
1819
|
-
* always wins over these defaults.
|
|
1820
|
-
*
|
|
1821
|
-
* Field-level and validation copy (required message, upload field, duplicate/
|
|
1822
|
-
* resume notices) lives in {@link FilloFieldStrings} — split out so this
|
|
1823
|
-
* documented, all-`string` surface stays stable while parametrized field
|
|
1824
|
-
* strings can be functions. The renderers resolve both together
|
|
1825
|
-
* ({@link FilloRendererStrings}); the `strings` prop overrides either.
|
|
1826
|
-
*/
|
|
1827
|
-
interface FilloStrings {
|
|
1828
|
-
back: string;
|
|
1829
|
-
next: string;
|
|
1830
|
-
submit: string;
|
|
1831
|
-
submitting: string;
|
|
1832
|
-
uploading: string;
|
|
1833
|
-
/** Suffix on non-required field labels. */
|
|
1834
|
-
optional: string;
|
|
1835
|
-
/** The "Other" free-text choice. */
|
|
1836
|
-
other: string;
|
|
1837
|
-
otherPrompt: string;
|
|
1838
|
-
otherPlaceholder: string;
|
|
1839
|
-
/** Dropdown placeholder when the field sets none. */
|
|
1840
|
-
choosePlaceholder: string;
|
|
1841
|
-
/** Success screen defaults — settings.successTitle/successMessage win. */
|
|
1842
|
-
successTitle: string;
|
|
1843
|
-
successMessage: string;
|
|
1844
|
-
closed: string;
|
|
1845
|
-
notLive: string;
|
|
1846
|
-
/** Title of the not-open state for a draft/storage-blocked form in production. */
|
|
1847
|
-
notOpenTitle: string;
|
|
1848
|
-
/** Body of the not-open state. */
|
|
1849
|
-
notOpenBody: string;
|
|
1850
|
-
/** Title of the closed-flavor state (expired/capped workspace);
|
|
1851
|
-
* the body reuses `closed`. */
|
|
1852
|
-
closedTitle: string;
|
|
1853
|
-
/** Fallback when a submit fails without a server message. */
|
|
1854
|
-
submitFailed: string;
|
|
1855
|
-
loadFailedNotFound: string;
|
|
1856
|
-
loadFailedNetwork: string;
|
|
1857
|
-
loadFailed: string;
|
|
1858
|
-
renderFailed: string;
|
|
1859
|
-
/** Saved-progress notice when a draft restored earlier answers. */
|
|
1860
|
-
resumeNotice: string;
|
|
1861
|
-
/** Upsert-mode notice when the person's previous response was prefilled. */
|
|
1862
|
-
editNotice: string;
|
|
1863
|
-
/** The discard action next to the resume notice. */
|
|
1864
|
-
resumeStartOver: string;
|
|
1865
|
-
/** Shown when the human-verification widget can't load (script blocked). */
|
|
1866
|
-
challengeUnavailable: string;
|
|
1867
|
-
}
|
|
1868
|
-
declare const DEFAULT_STRINGS: FilloStrings;
|
|
1869
|
-
/**
|
|
1870
|
-
* Field-level and validation strings the default renderers emit. Kept apart
|
|
1871
|
-
* from {@link FilloStrings} so parametrized entries can be functions (a
|
|
1872
|
-
* translation places the value where its grammar needs it) without widening
|
|
1873
|
-
* the documented all-`string` chrome surface. Also the growth point for new
|
|
1874
|
-
* strings in general — unlike `FilloStrings`, nothing depends on this being
|
|
1875
|
-
* an exhaustively-enumerated, closed set.
|
|
1876
|
-
*/
|
|
1877
|
-
interface FilloFieldStrings {
|
|
1878
|
-
/** Generic/legacy validation copy for a required field left empty. Mirrors
|
|
1879
|
-
* REQUIRED_FIELD_MESSAGE and remains the fallback for custom/hidden fields. */
|
|
1880
|
-
required: string;
|
|
1881
|
-
/** Optional field-aware required copy. Existing `required` overrides keep
|
|
1882
|
-
* their legacy generic behavior unless this function is also supplied. */
|
|
1883
|
-
requiredForField?: (field: Field) => string;
|
|
1884
|
-
/** Notice when a spent/expired resume link couldn't restore progress. */
|
|
1885
|
-
resumeLinkExpired: string;
|
|
1886
|
-
/** Success-screen message when a verified identity re-submits and the form
|
|
1887
|
-
* keeps the first answer (a visible duplicate, not a fresh response). */
|
|
1888
|
-
alreadyAnswered: string;
|
|
1889
|
-
/** Retry control on a failed upload row. */
|
|
1890
|
-
uploadRetry: string;
|
|
1891
|
-
/** Accessible action names for upload rows. The filename is appended by the renderer. */
|
|
1892
|
-
uploadCancel: string;
|
|
1893
|
-
uploadRemove: string;
|
|
1894
|
-
uploadDismiss: string;
|
|
1895
|
-
/** Visible secondary status for an upload in progress. */
|
|
1896
|
-
uploadingFile: (percent: number, size: string) => string;
|
|
1897
|
-
/** Visible secondary status for a completed upload. */
|
|
1898
|
-
uploadedFile: (size: string) => string;
|
|
1899
|
-
/** Dropzone copy when uploads can't run because the form isn't connected. */
|
|
1900
|
-
uploadsDisabled: string;
|
|
1901
|
-
/** Dropzone copy for an explicit render-only preview, which never has transport. */
|
|
1902
|
-
uploadsRenderOnly: string;
|
|
1903
|
-
/** Dropzone copy when the server temporarily refuses new file sessions. */
|
|
1904
|
-
uploadsUnavailable: string;
|
|
1905
|
-
/** An upload request reached the server, but storage could not accept it. */
|
|
1906
|
-
uploadUnavailable: string;
|
|
1907
|
-
/** An upload attempt failed with no actionable server message. */
|
|
1908
|
-
uploadFailed: string;
|
|
1909
|
-
/** Dropzone call to action; `multiple` is true when several files are allowed. */
|
|
1910
|
-
dropzoneTitle: (multiple: boolean) => string;
|
|
1911
|
-
/** Dropzone hint stating the per-file size limit in MB. */
|
|
1912
|
-
dropzoneHint: (maxMb: number) => string;
|
|
1913
|
-
/** A file exceeded the per-file MB limit before upload started. */
|
|
1914
|
-
fileTooLarge: (maxMb: number) => string;
|
|
1915
|
-
/** Screen-reader status: N uploads in progress. */
|
|
1916
|
-
filesUploading: (count: number) => string;
|
|
1917
|
-
/** Screen-reader status: N uploads failed. */
|
|
1918
|
-
uploadsFailed: (count: number) => string;
|
|
1919
|
-
/** Screen-reader status: N uploads completed. */
|
|
1920
|
-
filesUploaded: (count: number) => string;
|
|
1921
|
-
/** Live-region announcement while a submit is in flight — for auto-submit
|
|
1922
|
-
* forms, which have no footer/button to show `submitting` on. */
|
|
1923
|
-
submittingAnnouncement: string;
|
|
1924
|
-
/** @deprecated Retained for localization compatibility. Default renderers
|
|
1925
|
-
* now focus the first invalid control and show field-aware inline guidance. */
|
|
1926
|
-
errorSummaryTitle: string;
|
|
1927
|
-
/** Live-region announcement after a ranking move: "«label», position n of m". */
|
|
1928
|
-
rankingPosition: (label: string, position: number, count: number) => string;
|
|
1929
|
-
/** Repeating group: the Add button's default label. */
|
|
1930
|
-
groupAdd: string;
|
|
1931
|
-
/** Repeating group instance heading — "«item» n of m". */
|
|
1932
|
-
groupInstanceLabel: (item: string, position: number, count: number) => string;
|
|
1933
|
-
/** Repeating group per-instance Remove button accessible label. */
|
|
1934
|
-
groupRemoveLabel: (item: string, position: number) => string;
|
|
1935
|
-
/** Live-region announcement after adding an instance. */
|
|
1936
|
-
groupInstanceAdded: (item: string, position: number, count: number) => string;
|
|
1937
|
-
/** Live-region announcement after removing an instance. */
|
|
1938
|
-
groupInstanceRemoved: (item: string, count: number) => string;
|
|
1939
|
-
/** Live-region announcement once a phone country-picker selection commits
|
|
1940
|
-
* (focus moves straight to the national input, so nothing else announces it). */
|
|
1941
|
-
phoneCountrySelected: (name: string) => string;
|
|
1942
|
-
/** Live-region announcement of the phone country-picker's filtered result
|
|
1943
|
-
* count as the respondent types in the search box. */
|
|
1944
|
-
phoneResultsCount: (count: number) => string;
|
|
1945
|
-
/** Accessible name/state for an empty signature canvas. */
|
|
1946
|
-
signatureEmpty: string;
|
|
1947
|
-
/** Accessible name/state for a signed signature canvas. */
|
|
1948
|
-
signatureSigned: string;
|
|
1949
|
-
}
|
|
1950
|
-
declare const DEFAULT_FIELD_STRINGS: FilloFieldStrings;
|
|
1951
|
-
/** Everything the default renderers can localize — chrome + field/validation. */
|
|
1952
|
-
type FilloRendererStrings = FilloStrings & FilloFieldStrings;
|
|
1953
|
-
/** Resolve the renderer copy for core's stable required-field sentinel. */
|
|
1954
|
-
declare function requiredFieldMessage(field: Field, strings: Pick<FilloFieldStrings, "required" | "requiredForField">): string;
|
|
1955
|
-
/** Merge overrides over the built-in chrome + field defaults. Accepts a partial
|
|
1956
|
-
* of the full renderer surface so the `strings` prop can override either set.
|
|
1957
|
-
* A legacy generic `required` override disables the new field-aware default
|
|
1958
|
-
* unless the caller explicitly supplies `requiredForField` too. */
|
|
1959
|
-
declare function resolveStrings(overrides?: Partial<FilloRendererStrings>): FilloRendererStrings;
|
|
1960
|
-
|
|
1961
1992
|
/** Result of syncing a code-defined form: its canonical id, slug, and branding. */
|
|
1962
1993
|
interface SyncedForm {
|
|
1963
1994
|
formId: string;
|
|
1964
1995
|
slug: string;
|
|
1996
|
+
/** Absolute Fillo dashboard URL for reviewing and publishing this form. */
|
|
1997
|
+
manageUrl?: string;
|
|
1965
1998
|
branding?: FormBranding;
|
|
1966
1999
|
/** Human-verification challenge to render before submit, when the LIVE form
|
|
1967
2000
|
* requires one (staged changes don't gate until published). Absent = no
|
|
@@ -2201,4 +2234,4 @@ declare class Sha1 {
|
|
|
2201
2234
|
}
|
|
2202
2235
|
declare const sha1Base64: (bytes: Uint8Array) => string;
|
|
2203
2236
|
|
|
2204
|
-
export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CalcExpr, type CalculatedField, type ChallengeConfig, type ChallengeTheme, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CreatedDraft, type CustomField, DEFAULT_FIELD_STRINGS, DEFAULT_STRINGS, DRAFT_KINDS, type DateField, type DividerBlock, FILLO_CALC_MIN_SDK_VERSION, FILLO_CHALLENGE_MIN_SDK_VERSION, FILLO_DATA_ATTRS, FILLO_GROUP_MIN_SDK_VERSION, FILLO_MIN_SDK_VERSION, FILLO_SCHEMA_VERSION, FILLO_SDK_VERSION, FILLO_SLOTS, FILLO_THEME_VARS, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, type FilloAppearance, FilloClient, type FilloClientOptions, FilloError, type FilloFieldStrings, FilloJsxError, type FilloRendererStrings, type FilloRespondent, type FilloSlot, type FilloStrings, type FormBranding, type FormController, type FormControllerOptions, type FormControllerState, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormStatus, type FormTheme, type GroupInstanceValue, type HeadingBlock, type HiddenField, JSX_BLOCK_COMPONENTS, JSX_BLOCK_SPECS, type JsonValue, type JsxFormMeta, type JumpRule, type LinearScaleField, type MatrixField, type NextPage, type NumberField, PHONE_COUNTRIES, PHONE_PICKER_COUNTRIES, PHONE_POPOVER_VIEWPORT_GAP, type ParagraphBlock, type ParsePhoneOptions, type ParsedPhone, type PhoneCountry, type PhoneField, type PhonePopoverPlacement, type ProvisionWorkspaceResult, type PublishedForm, REQUIRED_FIELD_MESSAGE, type RankingField, type RatingField, type RepeatingGroupField, type ResponseData, type ResponseDraft, type ResponseLimit, type SchemaValidationResult, type SelectOption, Sha1, type SignatureField, type SlotClass, type SlotState, type SubmitMeta, type SubmitResult, type SyncFormResult, type SyncedForm, type TextField, type TrustPolicy, type UploadFileOptions, type UploadProgress, type UploadSession, type UploadStatus, type UploadTransport, type ValidationResult, type WhenBuilder, allFields, assembleForm, codeFormFromJsx, computeCalculated, conditionsMet, contentHash, countryByDialCode, countryByIso, countryByTimeZone, createBlock, createClient, createEmptyForm, createFormController, createId, defineForm, digitsOnly, evaluateCalc, flagEmoji, formSchemasEqual, formatAnswer, formatGroupedNumber, formatNational, isAutoSubmitBlock, isBlockVisible, isBuildTimeDevEnv, isCodeForm, isField, isFilloError, isLikelyDevEnv, isPossiblePhone, isTerminalPage, isValidPartialNumberText, localeForNotation, needsExplicitSubmit, normalizeFormSchema, normalizeFormTheme, normalizeSettings, parseGroupedNumber, parsePhone, pipeBlock, positionPhonePopover, prefillFromParams, provisionWorkspace, radioGroupStep, reachableFieldIds, reachableFields, reachablePageIds, reachablePageSequence, requiredFieldMessage, resolveNextPage, resolveSlotClass, resolveStrings, resolveText, resolveThemeAppearance, responseScopeValue, schemaFromJsx, sha1Base64, shouldAutoSubmit, slotClass, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields, visibleGroupChildren, visiblePageBlocks, when };
|
|
2237
|
+
export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CalcExpr, type CalculatedField, type ChallengeConfig, type ChallengeTheme, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CreatedDraft, type CustomField, DEFAULT_FIELD_STRINGS, DEFAULT_RESPONDENT_ERROR_STRINGS, DEFAULT_STRINGS, DRAFT_KINDS, type DateField, type DividerBlock, FILLO_CALC_MIN_SDK_VERSION, FILLO_CHALLENGE_MIN_SDK_VERSION, FILLO_DATA_ATTRS, FILLO_GROUP_MIN_SDK_VERSION, FILLO_MIN_SDK_VERSION, FILLO_SCHEMA_VERSION, FILLO_SDK_VERSION, FILLO_SLOTS, FILLO_THEME_VARS, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, type FilloAppearance, FilloClient, type FilloClientOptions, FilloError, type FilloFieldStrings, FilloJsxError, type FilloRendererStrings, type FilloRespondent, type FilloSlot, type FilloStrings, type FormBranding, type FormController, type FormControllerOptions, type FormControllerState, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormStatus, type FormTheme, type GroupInstanceValue, type HeadingBlock, type HiddenField, JSX_BLOCK_COMPONENTS, JSX_BLOCK_SPECS, type JsonValue, type JsxFormMeta, type JumpRule, type LinearScaleField, type MatrixField, type NextPage, type NumberField, PHONE_COUNTRIES, PHONE_PICKER_COUNTRIES, PHONE_POPOVER_VIEWPORT_GAP, type ParagraphBlock, type ParsePhoneOptions, type ParsedPhone, type PhoneCountry, type PhoneField, type PhonePopoverPlacement, type ProvisionWorkspaceResult, type PublishedForm, REQUIRED_FIELD_MESSAGE, type RankingField, type RatingField, type RepeatingGroupField, type RespondentErrorOverrides, type RespondentErrorStrings, type ResponseData, type ResponseDraft, type ResponseLimit, type SchemaValidationResult, type SelectOption, Sha1, type SignatureField, type SlotClass, type SlotState, type SubmitMeta, type SubmitResult, type SyncFormResult, type SyncedForm, type TextField, type TrustPolicy, type UploadFileOptions, type UploadProgress, type UploadSession, type UploadStatus, type UploadTransport, type ValidationResult, type WhenBuilder, allFields, assembleForm, codeFormFromJsx, computeCalculated, conditionsMet, contentHash, countryByDialCode, countryByIso, countryByTimeZone, createBlock, createClient, createEmptyForm, createFormController, createId, defineForm, digitsOnly, evaluateCalc, flagEmoji, formSchemasEqual, formatAnswer, formatGroupedNumber, formatNational, isAutoSubmitBlock, isBlockVisible, isBuildTimeDevEnv, isCodeForm, isField, isFilloError, isLikelyDevEnv, isPossiblePhone, isTerminalPage, isValidPartialNumberText, localeForNotation, needsExplicitSubmit, normalizeFormSchema, normalizeFormTheme, normalizeSettings, parseGroupedNumber, parsePhone, pipeBlock, positionPhonePopover, prefillFromParams, provisionWorkspace, radioGroupStep, reachableFieldIds, reachableFields, reachablePageIds, reachablePageSequence, requiredFieldMessage, resolveNextPage, resolveSlotClass, resolveStrings, resolveText, resolveThemeAppearance, respondentErrorStringsFor, responseScopeValue, schemaFromJsx, sha1Base64, shouldAutoSubmit, slotClass, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields, visibleGroupChildren, visiblePageBlocks, when };
|
package/dist/index.js
CHANGED
|
@@ -737,15 +737,39 @@ var DEFAULT_STRINGS = {
|
|
|
737
737
|
notOpenBody: "The form owner is still setting things up. Please check back soon.",
|
|
738
738
|
closedTitle: "Responses are closed",
|
|
739
739
|
submitFailed: "This form can't submit right now. Please try again in a moment.",
|
|
740
|
-
loadFailedNotFound: "Form not found
|
|
741
|
-
loadFailedNetwork: "Couldn't reach the server \u2014 check your connection
|
|
740
|
+
loadFailedNotFound: "Form not found. Check the link or ask the form owner for help.",
|
|
741
|
+
loadFailedNetwork: "Couldn't reach the server \u2014 check your connection and try again.",
|
|
742
742
|
loadFailed: "This form could not be loaded.",
|
|
743
743
|
renderFailed: "This form could not be rendered.",
|
|
744
744
|
resumeNotice: "Picked up where you left off.",
|
|
745
745
|
editNotice: "You're updating your earlier response.",
|
|
746
746
|
resumeStartOver: "Start over",
|
|
747
|
-
challengeUnavailable: "The verification check couldn't load. Refresh the page
|
|
747
|
+
challengeUnavailable: "The verification check couldn't load. Refresh the page and try again."
|
|
748
748
|
};
|
|
749
|
+
var DEFAULT_RESPONDENT_ERROR_STRINGS = {
|
|
750
|
+
submitFailed: DEFAULT_STRINGS.submitFailed,
|
|
751
|
+
loadFailedNotFound: DEFAULT_STRINGS.loadFailedNotFound,
|
|
752
|
+
loadFailedNetwork: DEFAULT_STRINGS.loadFailedNetwork,
|
|
753
|
+
formUnavailable: "This form is unavailable.",
|
|
754
|
+
formClosed: DEFAULT_STRINGS.closed,
|
|
755
|
+
submitRateLimited: "Too many responses are being submitted right now. Wait a moment, then try again.",
|
|
756
|
+
respondentUnrecognized: "This form couldn't verify who is responding. Ask the form owner for help.",
|
|
757
|
+
fileUnavailable: "One of the uploaded files is no longer available. Remove it and upload it again.",
|
|
758
|
+
scopeMissing: "We couldn't tell which entry this response belongs to. Answer the required field and try again.",
|
|
759
|
+
challengeIncomplete: "Please complete the verification check, then submit.",
|
|
760
|
+
challengeRetry: "That verification didn't go through. Please complete the check again and resubmit.",
|
|
761
|
+
reviewAnswers: "We couldn't submit \u2014 please review your answers."
|
|
762
|
+
};
|
|
763
|
+
function respondentErrorStringsFor(strings) {
|
|
764
|
+
return {
|
|
765
|
+
...DEFAULT_RESPONDENT_ERROR_STRINGS,
|
|
766
|
+
...strings.respondentErrors,
|
|
767
|
+
submitFailed: strings.submitFailed,
|
|
768
|
+
loadFailedNotFound: strings.loadFailedNotFound,
|
|
769
|
+
loadFailedNetwork: strings.loadFailedNetwork,
|
|
770
|
+
formClosed: strings.closed
|
|
771
|
+
};
|
|
772
|
+
}
|
|
749
773
|
var REQUIRED_BY_KIND = {
|
|
750
774
|
short_text: "Enter your answer",
|
|
751
775
|
long_text: "Enter your answer",
|
|
@@ -1088,7 +1112,7 @@ var schemaShape = z2.object({
|
|
|
1088
1112
|
});
|
|
1089
1113
|
var MAX_SCHEMA_VERSION = 1;
|
|
1090
1114
|
var FILLO_SCHEMA_VERSION = 1;
|
|
1091
|
-
var FILLO_SDK_VERSION = true ? "0.
|
|
1115
|
+
var FILLO_SDK_VERSION = true ? "0.19.0" : "0.0.0-dev";
|
|
1092
1116
|
var FILLO_MIN_SDK_VERSION = "0.4.0";
|
|
1093
1117
|
var FILLO_CHALLENGE_MIN_SDK_VERSION = "0.9.0";
|
|
1094
1118
|
var FILLO_CALC_MIN_SDK_VERSION = "0.11.0";
|
|
@@ -2520,7 +2544,7 @@ var FilloClient = class {
|
|
|
2520
2544
|
/** Server origin this client targets, normalized (no trailing slash). */
|
|
2521
2545
|
baseUrl;
|
|
2522
2546
|
fetch;
|
|
2523
|
-
/** Publishable
|
|
2547
|
+
/** Publishable project key, when configured. */
|
|
2524
2548
|
key;
|
|
2525
2549
|
constructor(options2 = {}) {
|
|
2526
2550
|
this.baseUrl = options2.baseUrl ? options2.baseUrl.replace(/\/$/, "") : options2.sameOrigin ? "" : DEFAULT_BASE_URL;
|
|
@@ -2556,7 +2580,7 @@ var FilloClient = class {
|
|
|
2556
2580
|
}
|
|
2557
2581
|
/**
|
|
2558
2582
|
* Resolve a code-defined form through the workspace identified by the
|
|
2559
|
-
* client's publishable key. Depending on
|
|
2583
|
+
* client's publishable key. Depending on project policy, changed content
|
|
2560
2584
|
* may be staged for review or resolved to the authoritative live snapshot.
|
|
2561
2585
|
* Returns the canonical form id used for submissions.
|
|
2562
2586
|
*/
|
|
@@ -2594,8 +2618,16 @@ var FilloClient = class {
|
|
|
2594
2618
|
throw asRequestError(err);
|
|
2595
2619
|
}
|
|
2596
2620
|
if (res.status === 422) {
|
|
2597
|
-
const body2 = await res.json();
|
|
2598
|
-
|
|
2621
|
+
const body2 = await res.json().catch(() => null);
|
|
2622
|
+
if (body2?.errors && typeof body2.errors === "object" && !Array.isArray(body2.errors)) {
|
|
2623
|
+
return { ok: false, errors: body2.errors };
|
|
2624
|
+
}
|
|
2625
|
+
throw new FilloError(
|
|
2626
|
+
typeof body2?.error === "string" && body2.error ? body2.error : "Submit failed: 422",
|
|
2627
|
+
422,
|
|
2628
|
+
retryAfterSec(res),
|
|
2629
|
+
typeof body2?.code === "string" && body2.code ? body2.code : void 0
|
|
2630
|
+
);
|
|
2599
2631
|
}
|
|
2600
2632
|
if (!res.ok) {
|
|
2601
2633
|
const { message, code } = await errorDetails(res, `Submit failed: ${res.status}`);
|
|
@@ -2701,13 +2733,10 @@ var FilloClient = class {
|
|
|
2701
2733
|
}
|
|
2702
2734
|
/** Current state of an upload session — used to resume after interruption. */
|
|
2703
2735
|
getUploadSession(sessionId, token, signal = AbortSignal.timeout(UPLOAD_CONTROL_TIMEOUT_MS)) {
|
|
2704
|
-
return this.json(
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
signal
|
|
2709
|
-
}
|
|
2710
|
-
);
|
|
2736
|
+
return this.json(`/uploads/${encodeURIComponent(sessionId)}`, {
|
|
2737
|
+
...token ? { headers: { "X-Fillo-Upload-Token": token } } : {},
|
|
2738
|
+
signal
|
|
2739
|
+
});
|
|
2711
2740
|
}
|
|
2712
2741
|
/**
|
|
2713
2742
|
* Provider-aware browser-direct upload. Creates a session, uses the storage
|
|
@@ -2738,21 +2767,18 @@ var FilloClient = class {
|
|
|
2738
2767
|
} else {
|
|
2739
2768
|
const requestId = createId(24);
|
|
2740
2769
|
const requestToken = createId(32);
|
|
2741
|
-
const createSession = () => this.json(
|
|
2742
|
-
|
|
2743
|
-
{
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
signal: this.uploadSignal(options2, UPLOAD_CONTROL_TIMEOUT_MS)
|
|
2754
|
-
}
|
|
2755
|
-
);
|
|
2770
|
+
const createSession = () => this.json(`/forms/${encodeURIComponent(formId)}/uploads`, {
|
|
2771
|
+
method: "POST",
|
|
2772
|
+
body: JSON.stringify({
|
|
2773
|
+
requestId,
|
|
2774
|
+
uploadToken: requestToken,
|
|
2775
|
+
fieldId: options2.fieldId,
|
|
2776
|
+
fileName: name,
|
|
2777
|
+
size: file.size,
|
|
2778
|
+
mime
|
|
2779
|
+
}),
|
|
2780
|
+
signal: this.uploadSignal(options2, UPLOAD_CONTROL_TIMEOUT_MS)
|
|
2781
|
+
});
|
|
2756
2782
|
const deadline = Date.now() + UPLOAD_CONTROL_TIMEOUT_MS;
|
|
2757
2783
|
let backoffMs = 250;
|
|
2758
2784
|
for (; ; ) {
|
|
@@ -2764,10 +2790,7 @@ var FilloClient = class {
|
|
|
2764
2790
|
if (!isControlRetryable(error, true)) throw error;
|
|
2765
2791
|
const remaining = deadline - Date.now();
|
|
2766
2792
|
if (remaining <= 0) throw error;
|
|
2767
|
-
await waitForRetry(
|
|
2768
|
-
Math.min(backoffMs, remaining),
|
|
2769
|
-
options2.signal
|
|
2770
|
-
);
|
|
2793
|
+
await waitForRetry(Math.min(backoffMs, remaining), options2.signal);
|
|
2771
2794
|
backoffMs = Math.min(backoffMs * 2, 5e3);
|
|
2772
2795
|
}
|
|
2773
2796
|
}
|
|
@@ -2777,7 +2800,8 @@ var FilloClient = class {
|
|
|
2777
2800
|
uploadToken: session.token
|
|
2778
2801
|
});
|
|
2779
2802
|
if (session.status === "complete" && session.file) return session.file;
|
|
2780
|
-
if (session.status === "aborted")
|
|
2803
|
+
if (session.status === "aborted")
|
|
2804
|
+
throw new FilloError("This upload session was cancelled", 409);
|
|
2781
2805
|
let completeBody;
|
|
2782
2806
|
if (session.transport?.type === "gdrive") {
|
|
2783
2807
|
await this.driveUploadLoop(session, session.transport.uploadUrl, file, options2);
|
|
@@ -2788,17 +2812,16 @@ var FilloClient = class {
|
|
|
2788
2812
|
} else if (session.transport?.type === "box") {
|
|
2789
2813
|
completeBody = await this.boxUpload(session.transport, file, options2);
|
|
2790
2814
|
} else {
|
|
2791
|
-
throw new FilloError(
|
|
2815
|
+
throw new FilloError(
|
|
2816
|
+
"This form's storage doesn't support uploads \u2014 connect Drive, S3, or Box."
|
|
2817
|
+
);
|
|
2792
2818
|
}
|
|
2793
|
-
const finalize = () => this.json(
|
|
2794
|
-
|
|
2795
|
-
{
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
signal: this.uploadSignal(options2, UPLOAD_CONTROL_TIMEOUT_MS)
|
|
2800
|
-
}
|
|
2801
|
-
);
|
|
2819
|
+
const finalize = () => this.json(`/uploads/${encodeURIComponent(session.id)}/complete`, {
|
|
2820
|
+
method: "POST",
|
|
2821
|
+
headers: session.token ? { "X-Fillo-Upload-Token": session.token } : void 0,
|
|
2822
|
+
...completeBody ? { body: JSON.stringify(completeBody) } : {},
|
|
2823
|
+
signal: this.uploadSignal(options2, UPLOAD_CONTROL_TIMEOUT_MS)
|
|
2824
|
+
});
|
|
2802
2825
|
let done;
|
|
2803
2826
|
try {
|
|
2804
2827
|
done = await finalize();
|
|
@@ -2811,10 +2834,7 @@ var FilloClient = class {
|
|
|
2811
2834
|
const reconciled = await this.getUploadSession(
|
|
2812
2835
|
session.id,
|
|
2813
2836
|
session.token,
|
|
2814
|
-
this.uploadSignal(
|
|
2815
|
-
options2,
|
|
2816
|
-
Math.max(1, deadline - Date.now())
|
|
2817
|
-
)
|
|
2837
|
+
this.uploadSignal(options2, Math.max(1, deadline - Date.now()))
|
|
2818
2838
|
);
|
|
2819
2839
|
if (reconciled.status === "complete" && reconciled.file) {
|
|
2820
2840
|
return reconciled.file;
|
|
@@ -2829,10 +2849,7 @@ var FilloClient = class {
|
|
|
2829
2849
|
}
|
|
2830
2850
|
const remaining = deadline - Date.now();
|
|
2831
2851
|
if (remaining <= 0) throw error;
|
|
2832
|
-
await waitForRetry(
|
|
2833
|
-
Math.min(backoffMs, remaining),
|
|
2834
|
-
options2.signal
|
|
2835
|
-
);
|
|
2852
|
+
await waitForRetry(Math.min(backoffMs, remaining), options2.signal);
|
|
2836
2853
|
backoffMs = Math.min(backoffMs * 2, 5e3);
|
|
2837
2854
|
}
|
|
2838
2855
|
}
|
|
@@ -2894,7 +2911,8 @@ var FilloClient = class {
|
|
|
2894
2911
|
throw new FilloError(`Box session failed: ${r.status}`, r.status);
|
|
2895
2912
|
return r;
|
|
2896
2913
|
});
|
|
2897
|
-
if (!sessionRes.ok)
|
|
2914
|
+
if (!sessionRes.ok)
|
|
2915
|
+
throw new FilloError(`Box session failed: ${sessionRes.status}`, sessionRes.status);
|
|
2898
2916
|
const session = await sessionRes.json();
|
|
2899
2917
|
const whole = new Sha1();
|
|
2900
2918
|
const parts = [];
|
|
@@ -2929,7 +2947,11 @@ var FilloClient = class {
|
|
|
2929
2947
|
const commitRes = await this.retry(options2, async () => {
|
|
2930
2948
|
const r = await this.fetch(session.session_endpoints.commit, {
|
|
2931
2949
|
method: "POST",
|
|
2932
|
-
headers: {
|
|
2950
|
+
headers: {
|
|
2951
|
+
...auth,
|
|
2952
|
+
"Content-Type": "application/json",
|
|
2953
|
+
Digest: `sha=${bytesToBase64(whole.digest())}`
|
|
2954
|
+
},
|
|
2933
2955
|
body: JSON.stringify({ parts }),
|
|
2934
2956
|
signal: this.uploadSignal(options2)
|
|
2935
2957
|
});
|
|
@@ -2937,7 +2959,8 @@ var FilloClient = class {
|
|
|
2937
2959
|
throw new FilloError(`Box commit failed: ${r.status}`, r.status);
|
|
2938
2960
|
return r;
|
|
2939
2961
|
});
|
|
2940
|
-
if (!commitRes.ok)
|
|
2962
|
+
if (!commitRes.ok)
|
|
2963
|
+
throw new FilloError(`Box commit failed: ${commitRes.status}`, commitRes.status);
|
|
2941
2964
|
const committed = await commitRes.json();
|
|
2942
2965
|
return { providerFileId: committed.entries?.[0]?.id };
|
|
2943
2966
|
}
|
|
@@ -3029,7 +3052,10 @@ var FilloClient = class {
|
|
|
3029
3052
|
report(offset);
|
|
3030
3053
|
if (offset <= startedAt) {
|
|
3031
3054
|
if (++stalls >= MAX_CHUNK_RETRIES) {
|
|
3032
|
-
throw new FilloError(
|
|
3055
|
+
throw new FilloError(
|
|
3056
|
+
"Drive upload stalled \u2014 the storage endpoint stopped accepting progress",
|
|
3057
|
+
0
|
|
3058
|
+
);
|
|
3033
3059
|
}
|
|
3034
3060
|
} else {
|
|
3035
3061
|
stalls = 0;
|
|
@@ -3247,7 +3273,7 @@ function warnNoFormId() {
|
|
|
3247
3273
|
if (warnedNoFormId) return;
|
|
3248
3274
|
warnedNoFormId = true;
|
|
3249
3275
|
console.warn(
|
|
3250
|
-
"[fillo] This form has a client but no submission target \u2014 a bare schema can't sync. Wrap it in defineForm({ id, pages }) (or author it with <Fillo.Form id=\u2026>) so it gets a
|
|
3276
|
+
"[fillo] This form has a client but no submission target \u2014 a bare schema can't sync. Wrap it in defineForm({ id, pages }) (or author it with <Fillo.Form id=\u2026>) so it gets a project handle, or pass formId for a dashboard-built form: https://fillo.so/docs"
|
|
3251
3277
|
);
|
|
3252
3278
|
}
|
|
3253
3279
|
function warnNoClient() {
|
|
@@ -3357,25 +3383,45 @@ function isDraftGone(err) {
|
|
|
3357
3383
|
return isFilloError(err) && (err.status === 401 || err.status === 403 || err.status === 404);
|
|
3358
3384
|
}
|
|
3359
3385
|
var DRAFT_DEBOUNCE_MS = 1500;
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3386
|
+
function submitFailureMessage(err, strings) {
|
|
3387
|
+
if (isFilloError(err)) {
|
|
3388
|
+
const status = err.status ?? 0;
|
|
3389
|
+
if (status === 404) return strings.loadFailedNotFound;
|
|
3390
|
+
switch (err.code) {
|
|
3391
|
+
case "submit_rate_limited":
|
|
3392
|
+
return strings.submitRateLimited;
|
|
3393
|
+
case "workspace_unavailable":
|
|
3394
|
+
case "provision_closed":
|
|
3395
|
+
return strings.formClosed;
|
|
3396
|
+
case "respondent_unrecognized":
|
|
3397
|
+
return strings.respondentUnrecognized;
|
|
3398
|
+
case "invalid_file_reference":
|
|
3399
|
+
return strings.fileUnavailable;
|
|
3400
|
+
case "response_scope_missing":
|
|
3401
|
+
return strings.scopeMissing;
|
|
3402
|
+
}
|
|
3403
|
+
if (status > 0) return strings.submitFailed;
|
|
3404
|
+
}
|
|
3405
|
+
return strings.loadFailedNetwork;
|
|
3406
|
+
}
|
|
3407
|
+
function syncResolutionFailureMessage(err, verbose, strings) {
|
|
3367
3408
|
if (isFilloError(err)) {
|
|
3368
3409
|
const status = err.status ?? 0;
|
|
3369
3410
|
if (verbose && status > 0 && err.message) {
|
|
3370
3411
|
return err.code ? `${err.message} (${err.code})` : err.message;
|
|
3371
3412
|
}
|
|
3372
3413
|
const definitive = status > 0 && status < 500 && status !== 408 && status !== 429;
|
|
3373
|
-
if (definitive) return
|
|
3414
|
+
if (definitive) return strings.formUnavailable;
|
|
3415
|
+
if (status > 0) return strings.submitFailed;
|
|
3374
3416
|
}
|
|
3375
|
-
return submitFailureMessage(err);
|
|
3417
|
+
return submitFailureMessage(err, strings);
|
|
3376
3418
|
}
|
|
3377
3419
|
function createFormController(options2) {
|
|
3378
3420
|
const { onChange, onSubmitted } = options2;
|
|
3421
|
+
let respondentErrorStrings = {
|
|
3422
|
+
...DEFAULT_RESPONDENT_ERROR_STRINGS,
|
|
3423
|
+
...options2.respondentErrorStrings
|
|
3424
|
+
};
|
|
3379
3425
|
const initial = normalizeFormSchema(options2.form);
|
|
3380
3426
|
if (!initial.ok) throw new Error(`Invalid form schema: ${initial.error}`);
|
|
3381
3427
|
let form = initial.schema;
|
|
@@ -3604,7 +3650,12 @@ function createFormController(options2) {
|
|
|
3604
3650
|
try {
|
|
3605
3651
|
if (ref) {
|
|
3606
3652
|
try {
|
|
3607
|
-
await c.saveDraft(
|
|
3653
|
+
await c.saveDraft(
|
|
3654
|
+
ref.id,
|
|
3655
|
+
ref.token,
|
|
3656
|
+
payload,
|
|
3657
|
+
keepalive ? { keepalive: true } : void 0
|
|
3658
|
+
);
|
|
3608
3659
|
draftRef = ref;
|
|
3609
3660
|
return;
|
|
3610
3661
|
} catch (err) {
|
|
@@ -3772,7 +3823,8 @@ function createFormController(options2) {
|
|
|
3772
3823
|
status = "idle";
|
|
3773
3824
|
submitError = syncResolutionFailureMessage(
|
|
3774
3825
|
err,
|
|
3775
|
-
options2.verboseResolutionErrors === true
|
|
3826
|
+
options2.verboseResolutionErrors === true,
|
|
3827
|
+
respondentErrorStrings
|
|
3776
3828
|
);
|
|
3777
3829
|
notify();
|
|
3778
3830
|
throw err;
|
|
@@ -3785,7 +3837,7 @@ function createFormController(options2) {
|
|
|
3785
3837
|
const challengeToken = options2.challengeRequired ? options2.getChallengeToken?.() : void 0;
|
|
3786
3838
|
if (options2.challengeRequired && !challengeToken) {
|
|
3787
3839
|
status = "idle";
|
|
3788
|
-
submitError =
|
|
3840
|
+
submitError = respondentErrorStrings.challengeIncomplete;
|
|
3789
3841
|
notify();
|
|
3790
3842
|
return;
|
|
3791
3843
|
}
|
|
@@ -3807,12 +3859,12 @@ function createFormController(options2) {
|
|
|
3807
3859
|
} catch (err) {
|
|
3808
3860
|
status = "idle";
|
|
3809
3861
|
if (isFilloError(err) && err.code === "challenge_failed") {
|
|
3810
|
-
submitError =
|
|
3862
|
+
submitError = respondentErrorStrings.challengeRetry;
|
|
3811
3863
|
options2.onChallengeFailed?.();
|
|
3812
3864
|
notify();
|
|
3813
3865
|
return;
|
|
3814
3866
|
}
|
|
3815
|
-
submitError = submitFailureMessage(err);
|
|
3867
|
+
submitError = submitFailureMessage(err, respondentErrorStrings);
|
|
3816
3868
|
notify();
|
|
3817
3869
|
throw err;
|
|
3818
3870
|
}
|
|
@@ -3822,14 +3874,15 @@ function createFormController(options2) {
|
|
|
3822
3874
|
(p) => visiblePageBlocks(form, p, data).some((b) => b.id in errors)
|
|
3823
3875
|
);
|
|
3824
3876
|
if (firstBad >= 0) pageIndex = firstBad;
|
|
3825
|
-
else submitError =
|
|
3877
|
+
else submitError = respondentErrorStrings.reviewAnswers;
|
|
3826
3878
|
status = "idle";
|
|
3827
3879
|
notify();
|
|
3828
3880
|
return;
|
|
3829
3881
|
}
|
|
3830
3882
|
submissionKind = res.duplicate ? "duplicate" : res.updated ? "updated" : "created";
|
|
3831
3883
|
if (sessionId) client.reportProgress(sessionId, { completed: true });
|
|
3832
|
-
if (visitorKey)
|
|
3884
|
+
if (visitorKey)
|
|
3885
|
+
markSubmitted(visitorSubmissionKeyId(form, formId, result.data), res.responseId);
|
|
3833
3886
|
discardDraftState();
|
|
3834
3887
|
status = "submitted";
|
|
3835
3888
|
notify();
|
|
@@ -3864,6 +3917,12 @@ function createFormController(options2) {
|
|
|
3864
3917
|
if ("formId" in ctx) formId = ctx.formId;
|
|
3865
3918
|
if ("client" in ctx) client = ctx.client;
|
|
3866
3919
|
if ("respondent" in ctx) respondent = ctx.respondent;
|
|
3920
|
+
if (ctx.respondentErrorStrings) {
|
|
3921
|
+
respondentErrorStrings = {
|
|
3922
|
+
...DEFAULT_RESPONDENT_ERROR_STRINGS,
|
|
3923
|
+
...ctx.respondentErrorStrings
|
|
3924
|
+
};
|
|
3925
|
+
}
|
|
3867
3926
|
if (ctx.form && ctx.form !== form) {
|
|
3868
3927
|
const next2 = normalizeFormSchema(ctx.form);
|
|
3869
3928
|
if (!next2.ok) throw new Error(`Invalid form schema: ${next2.error}`);
|
|
@@ -4073,7 +4132,9 @@ function relativeLuminance(token) {
|
|
|
4073
4132
|
return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
|
|
4074
4133
|
}
|
|
4075
4134
|
function resolveThemeAppearance(theme) {
|
|
4076
|
-
if (!theme || theme.colorScheme ||
|
|
4135
|
+
if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark" || !theme.background) {
|
|
4136
|
+
return theme;
|
|
4137
|
+
}
|
|
4077
4138
|
const luminance = relativeLuminance(theme.background);
|
|
4078
4139
|
if (luminance == null) return theme;
|
|
4079
4140
|
return { ...theme, colorScheme: luminance > DARK_LUMINANCE_THRESHOLD ? "light" : "dark" };
|
|
@@ -4140,14 +4201,14 @@ var isVolatileSync = (result) => Boolean(
|
|
|
4140
4201
|
result.staged || result.resolvedSchema || result.syncError || result.accepting === false || result.uploadsAvailable === false
|
|
4141
4202
|
);
|
|
4142
4203
|
function storageKey(client, handle) {
|
|
4143
|
-
return `fillo:sync:
|
|
4204
|
+
return `fillo:sync:v5:${client.baseUrl}|${client.key}|${handle}`;
|
|
4144
4205
|
}
|
|
4145
4206
|
function readStoredSync(client, handle, hash) {
|
|
4146
4207
|
try {
|
|
4147
4208
|
const raw = globalThis.localStorage?.getItem(storageKey(client, handle));
|
|
4148
4209
|
if (!raw) return null;
|
|
4149
4210
|
const parsed = JSON.parse(raw);
|
|
4150
|
-
if (typeof parsed.formId !== "string" || typeof parsed.slug !== "string" || parsed.hash !== hash || typeof parsed.ts !== "number" || parsed.uploadFileSizeLimitMb !== void 0 && (typeof parsed.uploadFileSizeLimitMb !== "number" || !Number.isFinite(parsed.uploadFileSizeLimitMb) || parsed.uploadFileSizeLimitMb <= 0) || parsed.status !== void 0 && parsed.status !== "draft" && parsed.status !== "published")
|
|
4211
|
+
if (typeof parsed.formId !== "string" || typeof parsed.slug !== "string" || parsed.hash !== hash || typeof parsed.ts !== "number" || parsed.uploadFileSizeLimitMb !== void 0 && (typeof parsed.uploadFileSizeLimitMb !== "number" || !Number.isFinite(parsed.uploadFileSizeLimitMb) || parsed.uploadFileSizeLimitMb <= 0) || parsed.manageUrl !== void 0 && typeof parsed.manageUrl !== "string" || parsed.status !== void 0 && parsed.status !== "draft" && parsed.status !== "published")
|
|
4151
4212
|
return null;
|
|
4152
4213
|
const ttl = syncTtlMs(parsed.status);
|
|
4153
4214
|
if (Date.now() - parsed.ts > ttl) return null;
|
|
@@ -4165,6 +4226,7 @@ function writeStoredSync(client, handle, hash, r) {
|
|
|
4165
4226
|
JSON.stringify({
|
|
4166
4227
|
formId: r.formId,
|
|
4167
4228
|
slug: r.slug,
|
|
4229
|
+
manageUrl: r.manageUrl,
|
|
4168
4230
|
status: r.status,
|
|
4169
4231
|
uploadFileSizeLimitMb: r.uploadFileSizeLimitMb,
|
|
4170
4232
|
hash,
|
|
@@ -4193,6 +4255,7 @@ function syncCodeForm(client, form, opts) {
|
|
|
4193
4255
|
const promise2 = Promise.resolve({
|
|
4194
4256
|
formId: stored.formId,
|
|
4195
4257
|
slug: stored.slug,
|
|
4258
|
+
manageUrl: stored.manageUrl,
|
|
4196
4259
|
status: stored.status,
|
|
4197
4260
|
uploadFileSizeLimitMb: stored.uploadFileSizeLimitMb
|
|
4198
4261
|
});
|
|
@@ -4634,6 +4697,7 @@ export {
|
|
|
4634
4697
|
BLOCK_KIND_META,
|
|
4635
4698
|
CONTENT_KINDS,
|
|
4636
4699
|
DEFAULT_FIELD_STRINGS,
|
|
4700
|
+
DEFAULT_RESPONDENT_ERROR_STRINGS,
|
|
4637
4701
|
DEFAULT_STRINGS,
|
|
4638
4702
|
DRAFT_KINDS,
|
|
4639
4703
|
FILLO_CALC_MIN_SDK_VERSION,
|
|
@@ -4709,6 +4773,7 @@ export {
|
|
|
4709
4773
|
resolveStrings,
|
|
4710
4774
|
resolveText,
|
|
4711
4775
|
resolveThemeAppearance,
|
|
4776
|
+
respondentErrorStringsFor,
|
|
4712
4777
|
responseScopeValue,
|
|
4713
4778
|
schemaFromJsx,
|
|
4714
4779
|
sha1Base64,
|