@usefillo/core 0.11.0 → 0.12.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 +58 -21
- package/dist/index.js +100 -22
- package/package.json +2 -1
package/dist/index.d.ts
CHANGED
|
@@ -66,9 +66,12 @@ interface NumberField extends BaseField {
|
|
|
66
66
|
decimals?: number;
|
|
67
67
|
prefix?: string;
|
|
68
68
|
suffix?: string;
|
|
69
|
-
/**
|
|
70
|
-
*
|
|
71
|
-
|
|
69
|
+
/** Thousand separators in the SDK input. Display-only; never reaches the
|
|
70
|
+
* wire, the server, or exports. Named by the GROUP separator:
|
|
71
|
+
* - `"grouped"`: detect from the respondent's browser locale;
|
|
72
|
+
* - `"grouped-comma"`: fixed comma groups, dot decimal — `1,234.56`;
|
|
73
|
+
* - `"grouped-dot"`: fixed dot groups, comma decimal — `1.234,56`. */
|
|
74
|
+
notation?: "grouped" | "grouped-comma" | "grouped-dot";
|
|
72
75
|
}
|
|
73
76
|
interface ChoiceField extends BaseField {
|
|
74
77
|
kind: "select" | "multi_select" | "dropdown";
|
|
@@ -660,14 +663,26 @@ declare function normalizeFormTheme(input: unknown): FormTheme | null;
|
|
|
660
663
|
declare function formatAnswer(field: Field, value: FieldValue): string;
|
|
661
664
|
|
|
662
665
|
/**
|
|
663
|
-
* Grouped-number display + parsing for the Number field's `notation
|
|
664
|
-
* "grouped"`
|
|
665
|
-
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
*
|
|
666
|
+
* Grouped-number display + parsing for the Number field's `notation` input
|
|
667
|
+
* — browser-locale detection (`"grouped"`) or an author-fixed style
|
|
668
|
+
* (`"grouped-comma"` = `1,234.56`, `"grouped-dot"` = `1.234,56`); design
|
|
669
|
+
* contract: docs/decisions/number-formatting.md. All helpers are pure,
|
|
670
|
+
* side-effect-free, and DOM-free, so the same code runs in @usefillo/react,
|
|
671
|
+
* @usefillo/dom, and Node. All are strictly display-only — grouping never
|
|
672
|
+
* touches validation, canonical storage, or the wire (contract decision 2)
|
|
673
|
+
* — and all degrade to canonical text instead of throwing when `Intl` is
|
|
674
|
+
* unavailable.
|
|
670
675
|
*/
|
|
676
|
+
/**
|
|
677
|
+
* The `Intl` locale a `notation` value pins the separators to — the single
|
|
678
|
+
* source of the notation→locale map, consumed by BOTH renderers (never
|
|
679
|
+
* duplicate it). Notation values are named by the GROUP separator:
|
|
680
|
+
* - `"grouped"` (and unset): `undefined` — detect from the respondent's
|
|
681
|
+
* browser locale, exactly the pre-fixed-style behavior;
|
|
682
|
+
* - `"grouped-comma"`: `"en-US"` — comma groups, dot decimal, `1,234.56`;
|
|
683
|
+
* - `"grouped-dot"`: `"de-DE"` — dot groups, comma decimal, `1.234,56`.
|
|
684
|
+
*/
|
|
685
|
+
declare function localeForNotation(notation?: "grouped" | "grouped-comma" | "grouped-dot"): string | undefined;
|
|
671
686
|
/**
|
|
672
687
|
* Locale-grouped display text for a finite number — the blurred value of a
|
|
673
688
|
* `notation: "grouped"` number input. `maximumFractionDigits` is set
|
|
@@ -697,8 +712,10 @@ declare function formatGroupedNumber(value: number, opts?: {
|
|
|
697
712
|
* accepted as a group separator whenever the locale's own is whitespace;
|
|
698
713
|
* - maps the locale decimal mark to ".";
|
|
699
714
|
* - ALWAYS also accepts "." as a decimal mark regardless of locale — see
|
|
700
|
-
* {@link
|
|
701
|
-
*
|
|
715
|
+
* {@link resolveAmbiguousGrouping} for the locale families where a "." or
|
|
716
|
+
* "," group separator collides with a decimal reading (a "," group run
|
|
717
|
+
* that isn't exact 3-digit chunking reads as a decimal, so "12,5" under a
|
|
718
|
+
* comma-grouping locale is 12.5, never a silently-stripped 125);
|
|
702
719
|
* - preserves a leading minus;
|
|
703
720
|
* - preserves partial typing states ("1234." stays "1234." — `Number()` can
|
|
704
721
|
* still parse it, and rewriting mid-keystroke fights the respondent).
|
|
@@ -1362,7 +1379,9 @@ interface FormControllerOptions {
|
|
|
1362
1379
|
getHoneypot?: () => string;
|
|
1363
1380
|
/**
|
|
1364
1381
|
* @internal Let preview page navigation move forward without validating the
|
|
1365
|
-
* current page. Submission still validates
|
|
1382
|
+
* current page. Submission still validates every answerable field; a
|
|
1383
|
+
* transportless preview ignores required file-upload fields because its
|
|
1384
|
+
* disabled picker cannot possibly satisfy them. Renderers must not use this
|
|
1366
1385
|
* option to change visitor-facing behavior such as branding.
|
|
1367
1386
|
*/
|
|
1368
1387
|
skipValidation?: boolean;
|
|
@@ -1677,8 +1696,8 @@ declare function resolveThemeAppearance(theme: FormTheme | null): FormTheme | nu
|
|
|
1677
1696
|
/**
|
|
1678
1697
|
* The default English validation message for a required field left empty.
|
|
1679
1698
|
* `validateField` returns this exact sentinel (core has no strings context),
|
|
1680
|
-
* and
|
|
1681
|
-
*
|
|
1699
|
+
* and renderer layers swap it for field-aware/localized copy. Keep it
|
|
1700
|
+
* identical to `DEFAULT_FIELD_STRINGS.required`.
|
|
1682
1701
|
*/
|
|
1683
1702
|
declare const REQUIRED_FIELD_MESSAGE = "This field is required";
|
|
1684
1703
|
/**
|
|
@@ -1744,8 +1763,12 @@ declare const DEFAULT_STRINGS: FilloStrings;
|
|
|
1744
1763
|
* an exhaustively-enumerated, closed set.
|
|
1745
1764
|
*/
|
|
1746
1765
|
interface FilloFieldStrings {
|
|
1747
|
-
/**
|
|
1766
|
+
/** Generic/legacy validation copy for a required field left empty. Mirrors
|
|
1767
|
+
* REQUIRED_FIELD_MESSAGE and remains the fallback for custom/hidden fields. */
|
|
1748
1768
|
required: string;
|
|
1769
|
+
/** Optional field-aware required copy. Existing `required` overrides keep
|
|
1770
|
+
* their legacy generic behavior unless this function is also supplied. */
|
|
1771
|
+
requiredForField?: (field: Field) => string;
|
|
1749
1772
|
/** Notice when a spent/expired resume link couldn't restore progress. */
|
|
1750
1773
|
resumeLinkExpired: string;
|
|
1751
1774
|
/** Success-screen message when a verified identity re-submits and the form
|
|
@@ -1753,8 +1776,18 @@ interface FilloFieldStrings {
|
|
|
1753
1776
|
alreadyAnswered: string;
|
|
1754
1777
|
/** Retry control on a failed upload row. */
|
|
1755
1778
|
uploadRetry: string;
|
|
1756
|
-
/**
|
|
1779
|
+
/** Accessible action names for upload rows. The filename is appended by the renderer. */
|
|
1780
|
+
uploadCancel: string;
|
|
1781
|
+
uploadRemove: string;
|
|
1782
|
+
uploadDismiss: string;
|
|
1783
|
+
/** Visible secondary status for an upload in progress. */
|
|
1784
|
+
uploadingFile: (percent: number, size: string) => string;
|
|
1785
|
+
/** Visible secondary status for a completed upload. */
|
|
1786
|
+
uploadedFile: (size: string) => string;
|
|
1787
|
+
/** Dropzone copy when uploads can't run because the form isn't connected. */
|
|
1757
1788
|
uploadsDisabled: string;
|
|
1789
|
+
/** Dropzone copy for an explicit render-only preview, which never has transport. */
|
|
1790
|
+
uploadsRenderOnly: string;
|
|
1758
1791
|
/** Dropzone copy when the server temporarily refuses new file sessions. */
|
|
1759
1792
|
uploadsUnavailable: string;
|
|
1760
1793
|
/** An upload request reached the server, but storage could not accept it. */
|
|
@@ -1776,8 +1809,8 @@ interface FilloFieldStrings {
|
|
|
1776
1809
|
/** Live-region announcement while a submit is in flight — for auto-submit
|
|
1777
1810
|
* forms, which have no footer/button to show `submitting` on. */
|
|
1778
1811
|
submittingAnnouncement: string;
|
|
1779
|
-
/**
|
|
1780
|
-
*
|
|
1812
|
+
/** @deprecated Retained for localization compatibility. Default renderers
|
|
1813
|
+
* now focus the first invalid control and show field-aware inline guidance. */
|
|
1781
1814
|
errorSummaryTitle: string;
|
|
1782
1815
|
/** Live-region announcement after a ranking move: "«label», position n of m". */
|
|
1783
1816
|
rankingPosition: (label: string, position: number, count: number) => string;
|
|
@@ -1795,8 +1828,12 @@ interface FilloFieldStrings {
|
|
|
1795
1828
|
declare const DEFAULT_FIELD_STRINGS: FilloFieldStrings;
|
|
1796
1829
|
/** Everything the default renderers can localize — chrome + field/validation. */
|
|
1797
1830
|
type FilloRendererStrings = FilloStrings & FilloFieldStrings;
|
|
1831
|
+
/** Resolve the renderer copy for core's stable required-field sentinel. */
|
|
1832
|
+
declare function requiredFieldMessage(field: Field, strings: Pick<FilloFieldStrings, "required" | "requiredForField">): string;
|
|
1798
1833
|
/** Merge overrides over the built-in chrome + field defaults. Accepts a partial
|
|
1799
|
-
* of the full renderer surface so the `strings` prop can override either set.
|
|
1834
|
+
* of the full renderer surface so the `strings` prop can override either set.
|
|
1835
|
+
* A legacy generic `required` override disables the new field-aware default
|
|
1836
|
+
* unless the caller explicitly supplies `requiredForField` too. */
|
|
1800
1837
|
declare function resolveStrings(overrides?: Partial<FilloRendererStrings>): FilloRendererStrings;
|
|
1801
1838
|
|
|
1802
1839
|
/** Result of syncing a code-defined form: its canonical id, slug, and branding. */
|
|
@@ -2037,4 +2074,4 @@ declare class Sha1 {
|
|
|
2037
2074
|
}
|
|
2038
2075
|
declare const sha1Base64: (bytes: Uint8Array) => string;
|
|
2039
2076
|
|
|
2040
|
-
export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CalcExpr, type CalculatedField, type ChallengeConfig, 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_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 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 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, needsExplicitSubmit, normalizeFormSchema, normalizeFormTheme, normalizeSettings, parseGroupedNumber, parsePhone, pipeBlock, positionPhonePopover, prefillFromParams, provisionWorkspace, radioGroupStep, reachableFieldIds, reachableFields, reachablePageIds, reachablePageSequence, resolveNextPage, resolveSlotClass, resolveStrings, resolveText, resolveThemeAppearance, responseScopeValue, schemaFromJsx, sha1Base64, shouldAutoSubmit, slotClass, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields, visiblePageBlocks, when };
|
|
2077
|
+
export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CalcExpr, type CalculatedField, type ChallengeConfig, 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_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 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 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, visiblePageBlocks, when };
|
package/dist/index.js
CHANGED
|
@@ -666,10 +666,12 @@ function positionPhonePopover(anchor, popover) {
|
|
|
666
666
|
popover.style.setProperty("--fillo-phone-popover-max-width", `${Math.floor(maxWidth)}px`);
|
|
667
667
|
popover.style.setProperty("--fillo-phone-popover-offset-x", "0px");
|
|
668
668
|
const width = Math.min(popover.offsetWidth, maxWidth);
|
|
669
|
+
const rtl = getComputedStyle(anchor).direction === "rtl";
|
|
670
|
+
const unshiftedLeft = rtl ? anchorRect.right - width : anchorRect.left;
|
|
669
671
|
let offsetX = 0;
|
|
670
|
-
const rightOverflow =
|
|
672
|
+
const rightOverflow = unshiftedLeft + width - (viewportWidth - gap);
|
|
671
673
|
if (rightOverflow > 0) offsetX -= rightOverflow;
|
|
672
|
-
const leftOverflow = gap - (
|
|
674
|
+
const leftOverflow = gap - (unshiftedLeft + offsetX);
|
|
673
675
|
if (leftOverflow > 0) offsetX += leftOverflow;
|
|
674
676
|
popover.style.setProperty("--fillo-phone-popover-offset-x", `${Math.round(offsetX)}px`);
|
|
675
677
|
return placement;
|
|
@@ -705,12 +707,41 @@ var DEFAULT_STRINGS = {
|
|
|
705
707
|
resumeStartOver: "Start over",
|
|
706
708
|
challengeUnavailable: "The verification check couldn't load. Refresh the page, or check that challenges.cloudflare.com isn't blocked."
|
|
707
709
|
};
|
|
710
|
+
var REQUIRED_BY_KIND = {
|
|
711
|
+
short_text: "Enter your answer",
|
|
712
|
+
long_text: "Enter your answer",
|
|
713
|
+
email: "Enter an email address",
|
|
714
|
+
url: "Enter a URL",
|
|
715
|
+
phone: "Enter a phone number",
|
|
716
|
+
number: "Enter a number",
|
|
717
|
+
select: "Choose an option",
|
|
718
|
+
dropdown: "Choose an option",
|
|
719
|
+
multi_select: "Select at least one option",
|
|
720
|
+
checkbox: "Select this checkbox",
|
|
721
|
+
rating: "Choose a rating",
|
|
722
|
+
linear_scale: "Choose a value",
|
|
723
|
+
ranking: "Rank every option",
|
|
724
|
+
matrix: "Answer every row",
|
|
725
|
+
signature: "Add your signature",
|
|
726
|
+
date: "Enter a date",
|
|
727
|
+
file_upload: "Add a file"
|
|
728
|
+
};
|
|
729
|
+
function defaultRequiredForField(field2) {
|
|
730
|
+
return REQUIRED_BY_KIND[field2.kind] ?? REQUIRED_FIELD_MESSAGE;
|
|
731
|
+
}
|
|
708
732
|
var DEFAULT_FIELD_STRINGS = {
|
|
709
733
|
required: REQUIRED_FIELD_MESSAGE,
|
|
734
|
+
requiredForField: defaultRequiredForField,
|
|
710
735
|
resumeLinkExpired: "That saved-progress link expired or was already used \u2014 start again below.",
|
|
711
736
|
alreadyAnswered: "You've already answered this form.",
|
|
712
737
|
uploadRetry: "Retry",
|
|
713
|
-
|
|
738
|
+
uploadCancel: "Cancel",
|
|
739
|
+
uploadRemove: "Remove",
|
|
740
|
+
uploadDismiss: "Dismiss",
|
|
741
|
+
uploadingFile: (percent, size) => `Uploading \xB7 ${percent}% \xB7 ${size}`,
|
|
742
|
+
uploadedFile: (size) => `Uploaded \xB7 ${size}`,
|
|
743
|
+
uploadsDisabled: "Connect this form to Fillo to enable uploads",
|
|
744
|
+
uploadsRenderOnly: "Uploads are unavailable in this render-only preview",
|
|
714
745
|
uploadsUnavailable: "Uploads are temporarily unavailable",
|
|
715
746
|
uploadUnavailable: "We couldn't upload this file right now. Try again in a moment.",
|
|
716
747
|
uploadFailed: "Upload failed \u2014 try again",
|
|
@@ -721,15 +752,22 @@ var DEFAULT_FIELD_STRINGS = {
|
|
|
721
752
|
uploadsFailed: (count) => `${count} ${count === 1 ? "upload" : "uploads"} failed.`,
|
|
722
753
|
filesUploaded: (count) => `${count} ${count === 1 ? "file" : "files"} uploaded.`,
|
|
723
754
|
submittingAnnouncement: "Submitting your response\u2026",
|
|
724
|
-
errorSummaryTitle: "
|
|
755
|
+
errorSummaryTitle: "Check these fields",
|
|
725
756
|
rankingPosition: (label, position, count) => `${label}, position ${position} of ${count}`,
|
|
726
757
|
phoneCountrySelected: (name) => `${name} selected`,
|
|
727
758
|
phoneResultsCount: (count) => `${count} ${count === 1 ? "result" : "results"}`,
|
|
728
759
|
signatureEmpty: "No signature yet",
|
|
729
760
|
signatureSigned: "Signature saved"
|
|
730
761
|
};
|
|
762
|
+
function requiredFieldMessage(field2, strings) {
|
|
763
|
+
return strings.requiredForField?.(field2) ?? strings.required;
|
|
764
|
+
}
|
|
731
765
|
function resolveStrings(overrides) {
|
|
732
|
-
|
|
766
|
+
const resolved = overrides ? { ...DEFAULT_STRINGS, ...DEFAULT_FIELD_STRINGS, ...overrides } : { ...DEFAULT_STRINGS, ...DEFAULT_FIELD_STRINGS };
|
|
767
|
+
if (overrides && Object.prototype.hasOwnProperty.call(overrides, "required") && !Object.prototype.hasOwnProperty.call(overrides, "requiredForField")) {
|
|
768
|
+
delete resolved.requiredForField;
|
|
769
|
+
}
|
|
770
|
+
return resolved;
|
|
733
771
|
}
|
|
734
772
|
|
|
735
773
|
// src/validation.ts
|
|
@@ -959,7 +997,7 @@ var schemaShape = z2.object({
|
|
|
959
997
|
});
|
|
960
998
|
var MAX_SCHEMA_VERSION = 1;
|
|
961
999
|
var FILLO_SCHEMA_VERSION = 1;
|
|
962
|
-
var FILLO_SDK_VERSION = true ? "0.
|
|
1000
|
+
var FILLO_SDK_VERSION = true ? "0.12.1" : "0.0.0-dev";
|
|
963
1001
|
var FILLO_MIN_SDK_VERSION = "0.4.0";
|
|
964
1002
|
var FILLO_CHALLENGE_MIN_SDK_VERSION = "0.9.0";
|
|
965
1003
|
var FILLO_CALC_MIN_SDK_VERSION = "0.11.0";
|
|
@@ -1346,9 +1384,9 @@ function normalizeBlock(input) {
|
|
|
1346
1384
|
decimals: finite(rec.decimals) === void 0 ? void 0 : int(rec.decimals, 0, 6, 0),
|
|
1347
1385
|
prefix: optionalAffix(rec.prefix, 100),
|
|
1348
1386
|
suffix: optionalAffix(rec.suffix, 100),
|
|
1349
|
-
// The only supported
|
|
1387
|
+
// The only supported values; anything else drops silently rather than
|
|
1350
1388
|
// erroring (an old/garbage value is just "no grouping").
|
|
1351
|
-
notation: rec.notation === "grouped" ? "grouped" : void 0
|
|
1389
|
+
notation: rec.notation === "grouped" ? "grouped" : rec.notation === "grouped-comma" ? "grouped-comma" : rec.notation === "grouped-dot" ? "grouped-dot" : void 0
|
|
1352
1390
|
});
|
|
1353
1391
|
case "select":
|
|
1354
1392
|
case "multi_select":
|
|
@@ -1640,6 +1678,17 @@ function formatAnswer(field2, value) {
|
|
|
1640
1678
|
function clampDecimals2(decimals) {
|
|
1641
1679
|
return Math.max(0, Math.min(6, Math.round(decimals)));
|
|
1642
1680
|
}
|
|
1681
|
+
function localeForNotation(notation) {
|
|
1682
|
+
switch (notation) {
|
|
1683
|
+
case "grouped-comma":
|
|
1684
|
+
return "en-US";
|
|
1685
|
+
case "grouped-dot":
|
|
1686
|
+
return "de-DE";
|
|
1687
|
+
case "grouped":
|
|
1688
|
+
case void 0:
|
|
1689
|
+
return void 0;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1643
1692
|
function formatGroupedNumber(value, opts) {
|
|
1644
1693
|
if (!Number.isFinite(value)) return String(value);
|
|
1645
1694
|
const decimals = opts?.decimals === void 0 ? void 0 : clampDecimals2(opts.decimals);
|
|
@@ -1661,17 +1710,17 @@ function stripAll(text, separator) {
|
|
|
1661
1710
|
function toCanonicalDecimal(text, decimal) {
|
|
1662
1711
|
return decimal === "." ? text : text.split(decimal).join(".");
|
|
1663
1712
|
}
|
|
1664
|
-
function
|
|
1665
|
-
if (decimal !==
|
|
1713
|
+
function resolveAmbiguousGrouping(body, group, decimal) {
|
|
1714
|
+
if (decimal !== group && body.includes(decimal)) {
|
|
1666
1715
|
const i = body.lastIndexOf(decimal);
|
|
1667
|
-
const intPart = stripAll(body.slice(0, i),
|
|
1716
|
+
const intPart = stripAll(body.slice(0, i), group);
|
|
1668
1717
|
const fracPart = body.slice(i + decimal.length);
|
|
1669
1718
|
return `${intPart}.${fracPart}`;
|
|
1670
1719
|
}
|
|
1671
|
-
const segments = body.split(
|
|
1720
|
+
const segments = body.split(group);
|
|
1672
1721
|
if (segments.length === 1) return body;
|
|
1673
1722
|
const isPureGrouping = segments.every(
|
|
1674
|
-
(seg, i) => i === 0 ?
|
|
1723
|
+
(seg, i) => i === 0 ? /^[1-9]\d{0,2}$/.test(seg) : /^\d{3}$/.test(seg)
|
|
1675
1724
|
);
|
|
1676
1725
|
if (isPureGrouping) return segments.join("");
|
|
1677
1726
|
return `${segments.slice(0, -1).join("")}.${segments.at(-1) ?? ""}`;
|
|
@@ -1687,8 +1736,8 @@ function parseGroupedNumber(text, locale) {
|
|
|
1687
1736
|
if (negative) body = body.slice(1);
|
|
1688
1737
|
if (/^\s$/.test(group)) {
|
|
1689
1738
|
body = toCanonicalDecimal(body.replace(/\s/g, ""), decimal);
|
|
1690
|
-
} else if (group === ".") {
|
|
1691
|
-
body =
|
|
1739
|
+
} else if (group === "." || group === ",") {
|
|
1740
|
+
body = resolveAmbiguousGrouping(body, group, decimal);
|
|
1692
1741
|
} else {
|
|
1693
1742
|
body = toCanonicalDecimal(stripAll(body, group), decimal);
|
|
1694
1743
|
}
|
|
@@ -3305,8 +3354,14 @@ function createFormController(options2) {
|
|
|
3305
3354
|
scheduleDraftSave();
|
|
3306
3355
|
}
|
|
3307
3356
|
function setUploading(fieldId, busy) {
|
|
3308
|
-
if (busy)
|
|
3309
|
-
|
|
3357
|
+
if (busy) {
|
|
3358
|
+
uploadingFields.add(fieldId);
|
|
3359
|
+
if (fieldId in errors) {
|
|
3360
|
+
errors = Object.fromEntries(Object.entries(errors).filter(([k]) => k !== fieldId));
|
|
3361
|
+
}
|
|
3362
|
+
} else {
|
|
3363
|
+
uploadingFields.delete(fieldId);
|
|
3364
|
+
}
|
|
3310
3365
|
notify();
|
|
3311
3366
|
}
|
|
3312
3367
|
function currentSeqPosition(seq) {
|
|
@@ -3366,9 +3421,26 @@ function createFormController(options2) {
|
|
|
3366
3421
|
if (status === "submitting" || status === "submitted") return;
|
|
3367
3422
|
const result = validateResponse(form, data);
|
|
3368
3423
|
if (!result.ok) {
|
|
3369
|
-
|
|
3424
|
+
const transportlessPreview = options2.skipValidation && !(client && formId);
|
|
3425
|
+
const previewErrors = transportlessPreview ? Object.fromEntries(
|
|
3426
|
+
Object.entries(result.errors).filter(
|
|
3427
|
+
([fieldId]) => form.pages.every(
|
|
3428
|
+
(page) => page.blocks.every(
|
|
3429
|
+
(block) => !(isField(block) && block.id === fieldId && block.kind === "file_upload")
|
|
3430
|
+
)
|
|
3431
|
+
)
|
|
3432
|
+
)
|
|
3433
|
+
) : result.errors;
|
|
3434
|
+
if (transportlessPreview && Object.keys(previewErrors).length === 0) {
|
|
3435
|
+
errors = {};
|
|
3436
|
+
status = "submitted";
|
|
3437
|
+
notify();
|
|
3438
|
+
emitSubmitted(void 0, data);
|
|
3439
|
+
return;
|
|
3440
|
+
}
|
|
3441
|
+
errors = previewErrors;
|
|
3370
3442
|
const firstBad = form.pages.findIndex(
|
|
3371
|
-
(p) => visiblePageBlocks(form, p, data).some((b) => b.id in
|
|
3443
|
+
(p) => visiblePageBlocks(form, p, data).some((b) => b.id in previewErrors)
|
|
3372
3444
|
);
|
|
3373
3445
|
if (firstBad >= 0) pageIndex = firstBad;
|
|
3374
3446
|
notify();
|
|
@@ -3789,12 +3861,16 @@ function syncCodeForm(client, form, opts) {
|
|
|
3789
3861
|
const content2 = JSON.stringify(form.schema) + JSON.stringify(form.theme ?? null);
|
|
3790
3862
|
const hash = contentHash(content2);
|
|
3791
3863
|
const key = `${client.baseUrl}:${client.key}:${form.id}:${hash}`;
|
|
3864
|
+
const revalidateUploadState = allFields(form.schema).some(
|
|
3865
|
+
(field2) => field2.kind === "file_upload"
|
|
3866
|
+
);
|
|
3792
3867
|
const cached = syncCache.get(key);
|
|
3793
3868
|
if (cached && !opts?.bypassCache) {
|
|
3794
|
-
if (cached.expiresAt === null || cached.expiresAt > Date.now())
|
|
3869
|
+
if (cached.expiresAt === null || !revalidateUploadState && cached.expiresAt > Date.now())
|
|
3870
|
+
return cached.promise;
|
|
3795
3871
|
syncCache.delete(key);
|
|
3796
3872
|
}
|
|
3797
|
-
if (!opts?.bypassCache) {
|
|
3873
|
+
if (!opts?.bypassCache && !revalidateUploadState) {
|
|
3798
3874
|
const stored = readStoredSync(client, form.id, hash);
|
|
3799
3875
|
if (stored) {
|
|
3800
3876
|
const promise2 = Promise.resolve({
|
|
@@ -3816,7 +3892,7 @@ function syncCodeForm(client, form, opts) {
|
|
|
3816
3892
|
promise.then(
|
|
3817
3893
|
(result) => {
|
|
3818
3894
|
if (syncCache.get(key) !== entry) return;
|
|
3819
|
-
if (isVolatileSync(result)) {
|
|
3895
|
+
if (revalidateUploadState || isVolatileSync(result)) {
|
|
3820
3896
|
syncCache.delete(key);
|
|
3821
3897
|
return;
|
|
3822
3898
|
}
|
|
@@ -4252,6 +4328,7 @@ export {
|
|
|
4252
4328
|
isPossiblePhone,
|
|
4253
4329
|
isTerminalPage,
|
|
4254
4330
|
isValidPartialNumberText,
|
|
4331
|
+
localeForNotation,
|
|
4255
4332
|
needsExplicitSubmit,
|
|
4256
4333
|
normalizeFormSchema,
|
|
4257
4334
|
normalizeFormTheme,
|
|
@@ -4267,6 +4344,7 @@ export {
|
|
|
4267
4344
|
reachableFields,
|
|
4268
4345
|
reachablePageIds,
|
|
4269
4346
|
reachablePageSequence,
|
|
4347
|
+
requiredFieldMessage,
|
|
4270
4348
|
resolveNextPage,
|
|
4271
4349
|
resolveSlotClass,
|
|
4272
4350
|
resolveStrings,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usefillo/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Form schema, validation, logic engine and JS client for Fillo. Framework-agnostic.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"zod": "^4.4.3"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
+
"jsdom": "^25.0.1",
|
|
35
36
|
"tsup": "^8.4.0",
|
|
36
37
|
"typescript": "^5.8.3"
|
|
37
38
|
},
|