@usefillo/core 0.10.0 → 0.11.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 +344 -18
- package/dist/index.js +741 -138
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ interface JumpRule {
|
|
|
20
20
|
/** Target page id, or "end" to finish the form. */
|
|
21
21
|
to: string | "end";
|
|
22
22
|
}
|
|
23
|
-
type FieldKind = "short_text" | "long_text" | "email" | "url" | "phone" | "number" | "select" | "multi_select" | "dropdown" | "checkbox" | "rating" | "linear_scale" | "ranking" | "matrix" | "signature" | "date" | "file_upload" | "hidden" | "custom";
|
|
23
|
+
type FieldKind = "short_text" | "long_text" | "email" | "url" | "phone" | "number" | "select" | "multi_select" | "dropdown" | "checkbox" | "rating" | "linear_scale" | "ranking" | "matrix" | "signature" | "date" | "file_upload" | "hidden" | "calculated" | "custom";
|
|
24
24
|
type ContentKind = "heading" | "paragraph" | "divider";
|
|
25
25
|
type BlockKind = FieldKind | ContentKind;
|
|
26
26
|
interface SelectOption {
|
|
@@ -61,6 +61,14 @@ interface NumberField extends BaseField {
|
|
|
61
61
|
kind: "number";
|
|
62
62
|
min?: number;
|
|
63
63
|
max?: number;
|
|
64
|
+
/** DISPLAY-ONLY rounding/padding (0–6). Deliberate divergence from
|
|
65
|
+
* calculated.decimals, which rounds the STORED value — see decision 1. */
|
|
66
|
+
decimals?: number;
|
|
67
|
+
prefix?: string;
|
|
68
|
+
suffix?: string;
|
|
69
|
+
/** Locale-aware thousand separators in the SDK input. Display-only;
|
|
70
|
+
* never reaches the wire, the server, or exports. */
|
|
71
|
+
notation?: "grouped";
|
|
64
72
|
}
|
|
65
73
|
interface ChoiceField extends BaseField {
|
|
66
74
|
kind: "select" | "multi_select" | "dropdown";
|
|
@@ -129,6 +137,64 @@ interface FileUploadField extends BaseField {
|
|
|
129
137
|
/** Accepted types, e.g. ["image/*", ".pdf", "video/mp4"]. Empty = anything. */
|
|
130
138
|
accept?: string[];
|
|
131
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* The calculation AST (logic depth P2). A typed tree, never a formula string,
|
|
142
|
+
* and the permanent expression substrate — extending it later means adding
|
|
143
|
+
* ops, never replacing it. v1 is numeric-only: totals and derived values.
|
|
144
|
+
*
|
|
145
|
+
* `if.when` reuses the existing {@link Condition} model verbatim (AND-combined,
|
|
146
|
+
* same ops) — one condition language across visibility, jumps, and calculations.
|
|
147
|
+
*
|
|
148
|
+
* Referenceable operand kinds (v1): `number`, `rating`, `linear_scale`, and
|
|
149
|
+
* `calculated` (chaining). Nothing else coerces implicitly.
|
|
150
|
+
*/
|
|
151
|
+
type CalcExpr =
|
|
152
|
+
/** A numeric field ref. Resolves through the same visibility discipline as
|
|
153
|
+
* conditions: a logic-hidden source reads as unanswered (→ null). */
|
|
154
|
+
{
|
|
155
|
+
op: "value";
|
|
156
|
+
fieldId: string;
|
|
157
|
+
} | {
|
|
158
|
+
op: "const";
|
|
159
|
+
value: number;
|
|
160
|
+
}
|
|
161
|
+
/** n-ary, length >= 1. Any null operand → null result (strict propagation). */
|
|
162
|
+
| {
|
|
163
|
+
op: "add" | "mul" | "min" | "max";
|
|
164
|
+
args: CalcExpr[];
|
|
165
|
+
}
|
|
166
|
+
/** Division by zero (and any non-finite result) → null. */
|
|
167
|
+
| {
|
|
168
|
+
op: "sub" | "div";
|
|
169
|
+
left: CalcExpr;
|
|
170
|
+
right: CalcExpr;
|
|
171
|
+
}
|
|
172
|
+
/** Half-away-from-zero at `decimals` (default 0). */
|
|
173
|
+
| {
|
|
174
|
+
op: "round";
|
|
175
|
+
arg: CalcExpr;
|
|
176
|
+
decimals?: number;
|
|
177
|
+
} | {
|
|
178
|
+
op: "if";
|
|
179
|
+
when: Condition[];
|
|
180
|
+
then: CalcExpr;
|
|
181
|
+
else: CalcExpr;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* A derived numeric value computed from other answers — never an input. The
|
|
185
|
+
* client recomputes it live for piping/conditions/jumps; the server recomputes
|
|
186
|
+
* it authoritatively on submit, so a tampered client value is ignored by
|
|
187
|
+
* construction. A null result means the field is unanswered (key absent).
|
|
188
|
+
*/
|
|
189
|
+
interface CalculatedField extends BaseField {
|
|
190
|
+
kind: "calculated";
|
|
191
|
+
calc: CalcExpr;
|
|
192
|
+
/** Rounds the STORED value, not just the display — one deterministic number
|
|
193
|
+
* everywhere (client preview, server recompute, grid, CSV, webhooks). */
|
|
194
|
+
decimals?: number;
|
|
195
|
+
prefix?: string;
|
|
196
|
+
suffix?: string;
|
|
197
|
+
}
|
|
132
198
|
/**
|
|
133
199
|
* A field type Fillo doesn't ship. You define it in code and supply the
|
|
134
200
|
* renderer via the SDK's `customComponents` map, keyed by `component`. The
|
|
@@ -143,7 +209,7 @@ interface CustomField extends BaseField {
|
|
|
143
209
|
/** Arbitrary options handed to your component. */
|
|
144
210
|
config?: Record<string, unknown>;
|
|
145
211
|
}
|
|
146
|
-
type Field = TextField | PhoneField | NumberField | ChoiceField | CheckboxField | RatingField | LinearScaleField | RankingField | MatrixField | SignatureField | DateField | FileUploadField | HiddenField | CustomField;
|
|
212
|
+
type Field = TextField | PhoneField | NumberField | ChoiceField | CheckboxField | RatingField | LinearScaleField | RankingField | MatrixField | SignatureField | DateField | FileUploadField | HiddenField | CalculatedField | CustomField;
|
|
147
213
|
interface HeadingBlock extends BaseBlock {
|
|
148
214
|
kind: "heading";
|
|
149
215
|
text: string;
|
|
@@ -493,6 +559,49 @@ interface ValidationResult {
|
|
|
493
559
|
*/
|
|
494
560
|
declare function validateResponse(form: FormSchema, data: ResponseData): ValidationResult;
|
|
495
561
|
|
|
562
|
+
/**
|
|
563
|
+
* Evaluate a calc expression to a number, or null for "unanswered".
|
|
564
|
+
* `resolve` maps a fieldId to its effective value and must apply the SAME
|
|
565
|
+
* visibility discipline as conditions (a logic-hidden source resolves to
|
|
566
|
+
* undefined) — the shared fixpoint's resolver does exactly that. Semantics:
|
|
567
|
+
* - an unanswered `value` ref → null; numeric strings bridge exactly like
|
|
568
|
+
* condition gt/lt (shared `conditionNumber`), nothing else coerces;
|
|
569
|
+
* - any null operand → null result (strict propagation, no silent zero);
|
|
570
|
+
* - division by zero and non-finite results → null;
|
|
571
|
+
* - `round` is half-away-from-zero at `decimals` (default 0);
|
|
572
|
+
* - `if` runs its `when` through the standard condition evaluator (AND), so
|
|
573
|
+
* unanswered behavior is identical to visibility/jump rules.
|
|
574
|
+
*/
|
|
575
|
+
declare function evaluateCalc(expr: CalcExpr, resolve: (fieldId: string) => FieldValue): number | null;
|
|
576
|
+
/**
|
|
577
|
+
* Response data with every calculated field's value written in (null or
|
|
578
|
+
* logic-hidden → key absent, i.e. unanswered) and any client-supplied value
|
|
579
|
+
* for a calculated id dropped — the engine, never the wire, owns these keys.
|
|
580
|
+
* Returns `data` unchanged for a calc-free form.
|
|
581
|
+
*
|
|
582
|
+
* THE canonical evaluator, shared verbatim by the controller (live display,
|
|
583
|
+
* piping, conditions) and the server's submit recompute — the contract's
|
|
584
|
+
* "deterministic, identical evaluation on client and server". Before the
|
|
585
|
+
* joint {visible set, calc values} fixpoint runs, answers are reduced to the
|
|
586
|
+
* canonical form the server stores:
|
|
587
|
+
* - value shape: resolveLogicState reads canonical values (trimmed, coerced,
|
|
588
|
+
* empty-as-unanswered — canonical.ts), so `if.when` conditions match the
|
|
589
|
+
* kept answers, never a divergent live shape;
|
|
590
|
+
* - reachability: an answer on a jump-skipped page reads as unanswered, the
|
|
591
|
+
* same pruning validateResponse applies before the server recompute. The
|
|
592
|
+
* prune below can itself flip a jump or visibility rule, so it iterates to
|
|
593
|
+
* a fixed point — each pass keeps a SUBSET of the previous keys (pruning
|
|
594
|
+
* only ever removes), so the loop is monotone, terminates within the key
|
|
595
|
+
* count, and the client (raw live data) and the server (the once-pruned
|
|
596
|
+
* kept set) walk the same shrinking chain to the SAME view.
|
|
597
|
+
*
|
|
598
|
+
* The pruned view is EVALUATION input only: non-calc answers pass through to
|
|
599
|
+
* the output untouched, so the controller keeps a stale answer behind a
|
|
600
|
+
* flipped jump (navigating back restores it) while its calc display, piping,
|
|
601
|
+
* and jump decisions read it as unanswered — exactly what the server stores.
|
|
602
|
+
*/
|
|
603
|
+
declare function computeCalculated(form: FormSchema, data: ResponseData): ResponseData;
|
|
604
|
+
|
|
496
605
|
declare const FILLO_SCHEMA_VERSION: 1;
|
|
497
606
|
/** Injected from package.json at build time (tsup define) — never hand-edited. */
|
|
498
607
|
declare const FILLO_SDK_VERSION: string;
|
|
@@ -514,6 +623,17 @@ declare const FILLO_MIN_SDK_VERSION = "0.4.0";
|
|
|
514
623
|
* @usefillo/*" error instead of a form that silently can't submit.
|
|
515
624
|
*/
|
|
516
625
|
declare const FILLO_CHALLENGE_MIN_SDK_VERSION = "0.9.0";
|
|
626
|
+
/**
|
|
627
|
+
* The floor served INSTEAD of FILLO_MIN_SDK_VERSION for forms whose live
|
|
628
|
+
* schema contains a calculated field: the first release that ships the kind.
|
|
629
|
+
* An older SDK's zod enum strips the unknown block, so it renders the form
|
|
630
|
+
* WITHOUT the calc row — piping shows blanks and any visibility/jump rule
|
|
631
|
+
* reading the calc id misbehaves. That's wrong-form-behavior, not merely
|
|
632
|
+
* missing chrome, so it fails fast with the "update @usefillo/*" error
|
|
633
|
+
* instead (the Turnstile-floor precedent; the form GET picks the max of the
|
|
634
|
+
* applicable floors).
|
|
635
|
+
*/
|
|
636
|
+
declare const FILLO_CALC_MIN_SDK_VERSION = "0.11.0";
|
|
517
637
|
declare function normalizeSettings(value: unknown): FormSettings;
|
|
518
638
|
interface SchemaValidationResult {
|
|
519
639
|
ok: boolean;
|
|
@@ -539,6 +659,76 @@ declare function normalizeFormTheme(input: unknown): FormTheme | null;
|
|
|
539
659
|
*/
|
|
540
660
|
declare function formatAnswer(field: Field, value: FieldValue): string;
|
|
541
661
|
|
|
662
|
+
/**
|
|
663
|
+
* Grouped-number display + parsing for the Number field's `notation:
|
|
664
|
+
* "grouped"` input (design contract: docs/decisions/number-formatting.md).
|
|
665
|
+
* Both helpers are pure, side-effect-free, and DOM-free, so the same code
|
|
666
|
+
* runs in @usefillo/react, @usefillo/dom, and Node. Both are strictly
|
|
667
|
+
* display-only — grouping never touches validation, canonical storage, or
|
|
668
|
+
* the wire (contract decision 2) — and both degrade to canonical text
|
|
669
|
+
* instead of throwing when `Intl` is unavailable.
|
|
670
|
+
*/
|
|
671
|
+
/**
|
|
672
|
+
* Locale-grouped display text for a finite number — the blurred value of a
|
|
673
|
+
* `notation: "grouped"` number input. `maximumFractionDigits` is set
|
|
674
|
+
* explicitly to 20 when `decimals` is unset: Intl's own silent default (3)
|
|
675
|
+
* would truncate/round a longer fraction on display, silently disagreeing
|
|
676
|
+
* with the stored value. When `decimals` is set it pins both the minimum and
|
|
677
|
+
* maximum fraction digits — padding to a stable width, the same contract
|
|
678
|
+
* `formatAnswer`'s toFixed follows. Non-finite input and a missing `Intl`
|
|
679
|
+
* both fall back to canonical text; this must never throw mid-keystroke.
|
|
680
|
+
*/
|
|
681
|
+
declare function formatGroupedNumber(value: number, opts?: {
|
|
682
|
+
locale?: string;
|
|
683
|
+
decimals?: number;
|
|
684
|
+
}): string;
|
|
685
|
+
/**
|
|
686
|
+
* Canonical text (what `Number()` can parse) for a grouped-number input's raw
|
|
687
|
+
* typed text — this is what `setValue` receives on every keystroke (contract
|
|
688
|
+
* decision 2: the input holds grouped text only while unfocused; `data`
|
|
689
|
+
* always holds canonical numerics). Returns the INPUT UNCHANGED when it can't
|
|
690
|
+
* produce a parseable result — validation flags it from there, same as an
|
|
691
|
+
* unformatted number field today.
|
|
692
|
+
*
|
|
693
|
+
* Derives the locale's group/decimal marks via
|
|
694
|
+
* `Intl.NumberFormat(locale).formatToParts(1234567.8)`, then:
|
|
695
|
+
* - strips group separators — several locales (fr-FR among them) use
|
|
696
|
+
* U+00A0/U+202F no-break spaces, so any whitespace-class character is
|
|
697
|
+
* accepted as a group separator whenever the locale's own is whitespace;
|
|
698
|
+
* - maps the locale decimal mark to ".";
|
|
699
|
+
* - ALWAYS also accepts "." as a decimal mark regardless of locale — see
|
|
700
|
+
* {@link resolveDotGroup} for the one locale family where that collides
|
|
701
|
+
* with "." also being the group separator;
|
|
702
|
+
* - preserves a leading minus;
|
|
703
|
+
* - preserves partial typing states ("1234." stays "1234." — `Number()` can
|
|
704
|
+
* still parse it, and rewriting mid-keystroke fights the respondent).
|
|
705
|
+
*
|
|
706
|
+
* Empty/whitespace input and a missing `Intl` both return the input as-is.
|
|
707
|
+
*/
|
|
708
|
+
declare function parseGroupedNumber(text: string, locale?: string): string;
|
|
709
|
+
/**
|
|
710
|
+
* Keystroke-level filter for a formatted number input's raw typed text (the
|
|
711
|
+
* input-quality contract's "optional future: React Aria-style
|
|
712
|
+
* `isValidPartialNumber` keystroke filter", `docs/decisions/input-quality.md`).
|
|
713
|
+
* Permissive on separator PLACEMENT — {@link parseGroupedNumber} and server
|
|
714
|
+
* validation own semantics — this only blocks characters or counts that can
|
|
715
|
+
* never resolve to a number: letters and other symbols, a non-leading "-",
|
|
716
|
+
* or more than one decimal mark.
|
|
717
|
+
*
|
|
718
|
+
* Reuses {@link parseGroupedNumber}'s locale-mark derivation
|
|
719
|
+
* (`Intl.NumberFormat(locale).formatToParts`) and its two special cases: a
|
|
720
|
+
* whitespace-class group separator accepts ANY whitespace character, and
|
|
721
|
+
* where "." is itself the locale's group separator (the de-DE family), "."
|
|
722
|
+
* is unlimited and only the locale's real decimal mark is capped at one.
|
|
723
|
+
*
|
|
724
|
+
* A candidate is validated as a WHOLE string, never character-by-character —
|
|
725
|
+
* a bad paste is rejected in full, not trimmed down to its valid prefix
|
|
726
|
+
* (React Aria's behavior). `Intl`-absent falls back to a minimal
|
|
727
|
+
* digits/"."/"," character class (the leading-"-" rule still applies, since
|
|
728
|
+
* it needs no locale data).
|
|
729
|
+
*/
|
|
730
|
+
declare function isValidPartialNumberText(text: string, locale?: string): boolean;
|
|
731
|
+
|
|
542
732
|
/**
|
|
543
733
|
* Lightweight, dependency-free phone metadata + helpers powering the SDK's
|
|
544
734
|
* phone field. Deliberately small: it carries enough to render a country
|
|
@@ -546,11 +736,18 @@ declare function formatAnswer(field: Field, value: FieldValue): string;
|
|
|
546
736
|
* validation + canonical normalization happen server-side with full
|
|
547
737
|
* libphonenumber data (bundle size is irrelevant there), so this stays lean and
|
|
548
738
|
* the stored value is always E.164.
|
|
739
|
+
*
|
|
740
|
+
* Coverage is every ITU dial code (~all of ISO 3166-1): 70 curated entries
|
|
741
|
+
* below carry full formatting metadata; every other country is packed
|
|
742
|
+
* "iso2+dialCode" data with no metadata (formatNational/isPossiblePhone fall
|
|
743
|
+
* back to grouping-in-3s / plain E.164 possibility). Names are resolved at
|
|
744
|
+
* runtime via Intl.DisplayNames — zero hardcoded English name literals.
|
|
549
745
|
*/
|
|
550
746
|
interface PhoneCountry {
|
|
551
747
|
/** ISO 3166-1 alpha-2, uppercase ("US"). Also drives the flag emoji. */
|
|
552
748
|
iso2: string;
|
|
553
|
-
/**
|
|
749
|
+
/** Localized display name, resolved via `Intl.DisplayNames` (falls back
|
|
750
|
+
* to the iso2 code when that API is unavailable). */
|
|
554
751
|
name: string;
|
|
555
752
|
/** Country calling code without the "+" ("1", "44", "33"). */
|
|
556
753
|
dialCode: string;
|
|
@@ -561,8 +758,22 @@ interface PhoneCountry {
|
|
|
561
758
|
/** Example national number (digits only) for the input placeholder. */
|
|
562
759
|
example: string;
|
|
563
760
|
}
|
|
564
|
-
/**
|
|
761
|
+
/**
|
|
762
|
+
* Every known phone country: the 70 curated entries (rich metadata) then
|
|
763
|
+
* every other ITU dial code (name + dial code only). Order is NOT display
|
|
764
|
+
* order (see PHONE_PICKER_COUNTRIES) — it's the tie-break priority
|
|
765
|
+
* `countryByDialCode` resolves shared codes with, preserved so growing this
|
|
766
|
+
* list from 70 to ~240 doesn't change how any of the original 70 resolve.
|
|
767
|
+
*/
|
|
565
768
|
declare const PHONE_COUNTRIES: PhoneCountry[];
|
|
769
|
+
/**
|
|
770
|
+
* PHONE_COUNTRIES reordered for a picker UI: localized name, Intl.Collator-
|
|
771
|
+
* compared (falls back to iso2 order without Intl.Collator). Kept separate
|
|
772
|
+
* from PHONE_COUNTRIES, whose order countryByDialCode depends on for
|
|
773
|
+
* curated-first tie-breaking — sorting by name would flip those ties (e.g.
|
|
774
|
+
* "Canada" before "United States" would flip which one "+1…" resolves to).
|
|
775
|
+
*/
|
|
776
|
+
declare const PHONE_PICKER_COUNTRIES: PhoneCountry[];
|
|
566
777
|
/** Flag emoji from an ISO-3166 alpha-2 code (regional-indicator letters). */
|
|
567
778
|
declare function flagEmoji(iso2: string): string;
|
|
568
779
|
declare function countryByIso(iso2: string | undefined): PhoneCountry | undefined;
|
|
@@ -576,19 +787,60 @@ declare function formatNational(country: PhoneCountry | undefined, national: str
|
|
|
576
787
|
/** Assemble the canonical E.164 value from a country + national digits. */
|
|
577
788
|
declare function toE164(country: PhoneCountry, national: string): string;
|
|
578
789
|
interface ParsedPhone {
|
|
579
|
-
/** Best-guess country, if the dial code matched one we know.
|
|
790
|
+
/** Best-guess country, if the dial code matched one we know. Always unset
|
|
791
|
+
* while `pending`. */
|
|
580
792
|
country?: PhoneCountry;
|
|
581
|
-
/** National significant digits
|
|
793
|
+
/** National significant digits: dial code stripped once a country is
|
|
794
|
+
* known, otherwise every digit typed after "+" (or all digits, with no
|
|
795
|
+
* "+" and no fallback). */
|
|
582
796
|
national: string;
|
|
583
797
|
/** Canonical E.164 ("+…"), or "" if there were no digits. */
|
|
584
798
|
e164: string;
|
|
799
|
+
/**
|
|
800
|
+
* True for a "+"-prefixed value that hasn't resolved a country: no digits
|
|
801
|
+
* at all ("+"), digits that prefix a real dial code without completing
|
|
802
|
+
* one ("+4"), or digits identical to `ParsePhoneOptions.previousDigits` —
|
|
803
|
+
* a bare "+" just prepended to unchanged, stale digits.
|
|
804
|
+
*
|
|
805
|
+
* `national`/`e164` still carry the typed digits (a caller that ignores
|
|
806
|
+
* `pending` degrades gracefully). The renderer should hold off committing
|
|
807
|
+
* a stored value while pending, and redisplay `raw` verbatim instead of
|
|
808
|
+
* `formatNational(country, national)` — there's no country to format
|
|
809
|
+
* against yet, and reformatting a bare "+" into "" is the corruption this
|
|
810
|
+
* field exists to prevent.
|
|
811
|
+
*/
|
|
812
|
+
pending?: boolean;
|
|
813
|
+
/** The trimmed input, verbatim. Set only when `pending`. */
|
|
814
|
+
raw?: string;
|
|
815
|
+
}
|
|
816
|
+
interface ParsePhoneOptions {
|
|
817
|
+
/**
|
|
818
|
+
* National digits already in the field before this edit (dial code
|
|
819
|
+
* already stripped — the same shape `parsePhone` returns as `national`).
|
|
820
|
+
* Lets `parsePhone` recognize a bare "+" just prepended to unchanged
|
|
821
|
+
* digits and stay `pending` instead of resolving a country from a
|
|
822
|
+
* coincidental prefix match: without this hint, stale digits from a
|
|
823
|
+
* previous, unrelated number always resolve a country the instant "+"
|
|
824
|
+
* lands in front of them (an old "5551234" would silently become
|
|
825
|
+
* Brazilian the moment "+" is typed, since "55" is Brazil's dial code).
|
|
826
|
+
* Pass the pre-edit digits on every keystroke; once they actually change,
|
|
827
|
+
* resolution proceeds normally on the next call.
|
|
828
|
+
*/
|
|
829
|
+
previousDigits?: string;
|
|
585
830
|
}
|
|
586
831
|
/**
|
|
587
832
|
* Parse a stored/typed value into country + national + E.164. Accepts E.164
|
|
588
833
|
* ("+4915123456789"), a bare international number, or — with `fallback` — a
|
|
589
834
|
* national number typed without a dial code.
|
|
835
|
+
*
|
|
836
|
+
* See `ParsedPhone.pending`/`ParsePhoneOptions.previousDigits` for the
|
|
837
|
+
* pending-international-entry contract: a "+"-prefixed value that doesn't
|
|
838
|
+
* resolve a dial code (incl. a lone "+") returns pending instead of
|
|
839
|
+
* silently reverting to `fallback` with an emptied value, and
|
|
840
|
+
* `previousDigits` stops stale leftover digits from resolving a country
|
|
841
|
+
* they never meant to represent, just because "+" was prepended.
|
|
590
842
|
*/
|
|
591
|
-
declare function parsePhone(value: string, fallback?: PhoneCountry): ParsedPhone;
|
|
843
|
+
declare function parsePhone(value: string, fallback?: PhoneCountry, opts?: ParsePhoneOptions): ParsedPhone;
|
|
592
844
|
/**
|
|
593
845
|
* Cheap "could this be a real number?" check used for inline feedback. Length
|
|
594
846
|
* only — never claims more than it knows; the server does authoritative
|
|
@@ -626,8 +878,10 @@ declare function createEmptyForm(title?: string): FormSchema;
|
|
|
626
878
|
* defaults — the model only ever supplies the human-meaningful parts.
|
|
627
879
|
*/
|
|
628
880
|
/**
|
|
629
|
-
* Field kinds the generator may use. Excludes hidden/custom/signature
|
|
630
|
-
* first two are code-only concerns and signature is too niche to draft well
|
|
881
|
+
* Field kinds the generator may use. Excludes hidden/custom/signature (the
|
|
882
|
+
* first two are code-only concerns and signature is too niche to draft well)
|
|
883
|
+
* and calculated (an LLM can't reliably wire a calc AST to stable field ids —
|
|
884
|
+
* owners add calculations deliberately, in the builder or in code).
|
|
631
885
|
*/
|
|
632
886
|
declare const DRAFT_KINDS: readonly BlockKind[];
|
|
633
887
|
/** The LLM-friendly intermediate shape. Everything optional but `kind`. */
|
|
@@ -711,7 +965,7 @@ interface PublishedForm {
|
|
|
711
965
|
/**
|
|
712
966
|
* Whether a submission would be accepted right now. Absent on older servers
|
|
713
967
|
* — renderers must then keep today's behavior. When false, the default
|
|
714
|
-
* renderers show the not-open
|
|
968
|
+
* renderers show the not-open state instead of a fillable form.
|
|
715
969
|
*/
|
|
716
970
|
accepting?: boolean;
|
|
717
971
|
/**
|
|
@@ -725,6 +979,9 @@ interface PublishedForm {
|
|
|
725
979
|
/** Whether new file uploads can start right now. Absent on older servers is
|
|
726
980
|
* treated as available; this never changes whether ordinary answers submit. */
|
|
727
981
|
uploadsAvailable?: boolean;
|
|
982
|
+
/** Server-owned per-file ceiling for the active storage lane. Renderers use
|
|
983
|
+
* the lower of this and the field's configured maxFileSizeMb. */
|
|
984
|
+
uploadFileSizeLimitMb?: number;
|
|
728
985
|
/** Workspace branding state — absent means show the badge (default). */
|
|
729
986
|
branding?: FormBranding;
|
|
730
987
|
/** Human-verification challenge to render before submit, when the form
|
|
@@ -881,7 +1138,7 @@ interface SyncFormResult {
|
|
|
881
1138
|
/**
|
|
882
1139
|
* Whether a submission would be accepted right now. Absent on older servers
|
|
883
1140
|
* — renderers must then keep today's behavior. When false, the default
|
|
884
|
-
* renderers show the not-open
|
|
1141
|
+
* renderers show the not-open state instead of a fillable form.
|
|
885
1142
|
*/
|
|
886
1143
|
accepting?: boolean;
|
|
887
1144
|
/**
|
|
@@ -896,6 +1153,9 @@ interface SyncFormResult {
|
|
|
896
1153
|
* response acceptance so a completed file can still be submitted after the
|
|
897
1154
|
* workspace reaches its upload cap. Absent on older servers means available. */
|
|
898
1155
|
uploadsAvailable?: boolean;
|
|
1156
|
+
/** Server-owned per-file ceiling for the active storage lane. Renderers use
|
|
1157
|
+
* the lower of this and the field's configured maxFileSizeMb. */
|
|
1158
|
+
uploadFileSizeLimitMb?: number;
|
|
899
1159
|
/**
|
|
900
1160
|
* Server-authoritative live snapshot. Present when the incoming code schema
|
|
901
1161
|
* is not the version respondents may submit against yet.
|
|
@@ -1280,13 +1540,35 @@ interface AutoSubmitContext {
|
|
|
1280
1540
|
}
|
|
1281
1541
|
declare function shouldAutoSubmit(field: Field, value: FieldValue, ctx: AutoSubmitContext): boolean;
|
|
1282
1542
|
|
|
1543
|
+
/**
|
|
1544
|
+
* Pure keyboard-navigation math for a roving-tabindex radiogroup (choice
|
|
1545
|
+
* radios, rating, linear_scale, matrix rows) — shared by both renderers so
|
|
1546
|
+
* the arrow/Home/End handling isn't duplicated and can't drift (audit
|
|
1547
|
+
* P2.3: today's `radiogroupKeyDown`/`bindRadioKeys` copies wrap at the
|
|
1548
|
+
* extremes, have no Home/End, and are RTL-blind).
|
|
1549
|
+
*
|
|
1550
|
+
* No DOM/framework dependency: given the pressed key, the currently active
|
|
1551
|
+
* index, and the option count, this returns the new index to select and
|
|
1552
|
+
* focus, or `null` when `key` isn't a navigation key (the caller falls
|
|
1553
|
+
* through to its own handling, e.g. Space/Enter selection).
|
|
1554
|
+
*
|
|
1555
|
+
* `index` is expected in range (`0`..`length-1`, e.g. from a roving-tabindex
|
|
1556
|
+
* helper that already normalizes "nothing selected" to `0` — matching both
|
|
1557
|
+
* renderers' existing `rovingIndex`). Out-of-range input is still handled
|
|
1558
|
+
* safely: the result is clamped to `[0, length-1]` the same as any other
|
|
1559
|
+
* step, with no special-casing for negative or overflowing starting indexes.
|
|
1560
|
+
*/
|
|
1561
|
+
declare function radioGroupStep(key: string, index: number, length: number, opts?: {
|
|
1562
|
+
rtl?: boolean;
|
|
1563
|
+
}): number | null;
|
|
1564
|
+
|
|
1283
1565
|
/**
|
|
1284
1566
|
* The styling contract, shared by every renderer: named slots a consumer can
|
|
1285
1567
|
* attach classes to, and data-* attributes that expose state so utility CSS
|
|
1286
1568
|
* (Tailwind `data-[invalid]:…`) can react to it. Slot names and attribute
|
|
1287
1569
|
* names are public API — renames are breaking; additions are minors.
|
|
1288
1570
|
*/
|
|
1289
|
-
declare const FILLO_SLOTS: readonly ["root", "header", "title", "description", "pageTitle", "progress", "progressFill", "blocks", "field", "label", "fieldDescription", "control", "options", "option", "optionLabel", "error", "footer", "button", "success", "resume", "turnstile"];
|
|
1571
|
+
declare const FILLO_SLOTS: readonly ["root", "header", "title", "description", "pageTitle", "progress", "progressFill", "blocks", "field", "label", "fieldDescription", "control", "options", "option", "optionLabel", "error", "footer", "button", "success", "resume", "turnstile", "calculated"];
|
|
1290
1572
|
type FilloSlot = (typeof FILLO_SLOTS)[number];
|
|
1291
1573
|
/** data-* names emitted alongside the stable fillo-* classes. */
|
|
1292
1574
|
declare const FILLO_DATA_ATTRS: {
|
|
@@ -1371,6 +1653,26 @@ declare const FILLO_THEME_VARS: readonly [{
|
|
|
1371
1653
|
}, {
|
|
1372
1654
|
readonly var: "--fillo-primary-contrast";
|
|
1373
1655
|
}];
|
|
1656
|
+
/**
|
|
1657
|
+
* Infer `colorScheme` for a `FormTheme` with `background` AND `text` set
|
|
1658
|
+
* but no explicit `colorScheme`, from the background's WCAG relative
|
|
1659
|
+
* luminance — fixes the foot-gun where `theme={{background, text}}` alone
|
|
1660
|
+
* left the light palette's muted/border/control-bg/error/primary-contrast
|
|
1661
|
+
* tokens in place, reading as near-white-on-white (≈1.10:1) on a dark
|
|
1662
|
+
* author-chosen background.
|
|
1663
|
+
*
|
|
1664
|
+
* Explicit `colorScheme` always wins (returned unchanged). Only one of
|
|
1665
|
+
* `background`/`text` set, neither set, or a non-`#rgb`/`#rrggbb`
|
|
1666
|
+
* `background` also pass through unchanged — no inference.
|
|
1667
|
+
*
|
|
1668
|
+
* Returns a `FormTheme`, not just the scheme, so a renderer can drop this
|
|
1669
|
+
* in ahead of its existing code with no other change:
|
|
1670
|
+
* `const safeTheme = resolveThemeAppearance(normalizeFormTheme(theme));`
|
|
1671
|
+
* then read `.colorScheme`/`.primary`/… exactly as today — inferred and
|
|
1672
|
+
* explicit schemes cascade through the same shipped
|
|
1673
|
+
* `[data-fillo-color-scheme="dark"]` CSS, so no new CSS is needed.
|
|
1674
|
+
*/
|
|
1675
|
+
declare function resolveThemeAppearance(theme: FormTheme | null): FormTheme | null;
|
|
1374
1676
|
|
|
1375
1677
|
/**
|
|
1376
1678
|
* The default English validation message for a required field left empty.
|
|
@@ -1410,12 +1712,11 @@ interface FilloStrings {
|
|
|
1410
1712
|
successMessage: string;
|
|
1411
1713
|
closed: string;
|
|
1412
1714
|
notLive: string;
|
|
1413
|
-
/** Title of the not-open
|
|
1414
|
-
* in production shows the real form blurred beneath it). */
|
|
1715
|
+
/** Title of the not-open state for a draft/storage-blocked form in production. */
|
|
1415
1716
|
notOpenTitle: string;
|
|
1416
|
-
/** Body of the not-open
|
|
1717
|
+
/** Body of the not-open state. */
|
|
1417
1718
|
notOpenBody: string;
|
|
1418
|
-
/** Title of the closed-flavor
|
|
1719
|
+
/** Title of the closed-flavor state (expired/capped workspace);
|
|
1419
1720
|
* the body reuses `closed`. */
|
|
1420
1721
|
closedTitle: string;
|
|
1421
1722
|
/** Fallback when a submit fails without a server message. */
|
|
@@ -1438,7 +1739,9 @@ declare const DEFAULT_STRINGS: FilloStrings;
|
|
|
1438
1739
|
* Field-level and validation strings the default renderers emit. Kept apart
|
|
1439
1740
|
* from {@link FilloStrings} so parametrized entries can be functions (a
|
|
1440
1741
|
* translation places the value where its grammar needs it) without widening
|
|
1441
|
-
* the documented all-`string` chrome surface.
|
|
1742
|
+
* the documented all-`string` chrome surface. Also the growth point for new
|
|
1743
|
+
* strings in general — unlike `FilloStrings`, nothing depends on this being
|
|
1744
|
+
* an exhaustively-enumerated, closed set.
|
|
1442
1745
|
*/
|
|
1443
1746
|
interface FilloFieldStrings {
|
|
1444
1747
|
/** Validation: a required field was left empty. Mirrors REQUIRED_FIELD_MESSAGE. */
|
|
@@ -1454,6 +1757,8 @@ interface FilloFieldStrings {
|
|
|
1454
1757
|
uploadsDisabled: string;
|
|
1455
1758
|
/** Dropzone copy when the server temporarily refuses new file sessions. */
|
|
1456
1759
|
uploadsUnavailable: string;
|
|
1760
|
+
/** An upload request reached the server, but storage could not accept it. */
|
|
1761
|
+
uploadUnavailable: string;
|
|
1457
1762
|
/** An upload attempt failed with no actionable server message. */
|
|
1458
1763
|
uploadFailed: string;
|
|
1459
1764
|
/** Dropzone call to action; `multiple` is true when several files are allowed. */
|
|
@@ -1468,6 +1773,24 @@ interface FilloFieldStrings {
|
|
|
1468
1773
|
uploadsFailed: (count: number) => string;
|
|
1469
1774
|
/** Screen-reader status: N uploads completed. */
|
|
1470
1775
|
filesUploaded: (count: number) => string;
|
|
1776
|
+
/** Live-region announcement while a submit is in flight — for auto-submit
|
|
1777
|
+
* forms, which have no footer/button to show `submitting` on. */
|
|
1778
|
+
submittingAnnouncement: string;
|
|
1779
|
+
/** Heading on the failed-submit error summary (one `role="alert"` summary
|
|
1780
|
+
* linking to fields, GOV.UK pattern — replaces N competing per-field alerts). */
|
|
1781
|
+
errorSummaryTitle: string;
|
|
1782
|
+
/** Live-region announcement after a ranking move: "«label», position n of m". */
|
|
1783
|
+
rankingPosition: (label: string, position: number, count: number) => string;
|
|
1784
|
+
/** Live-region announcement once a phone country-picker selection commits
|
|
1785
|
+
* (focus moves straight to the national input, so nothing else announces it). */
|
|
1786
|
+
phoneCountrySelected: (name: string) => string;
|
|
1787
|
+
/** Live-region announcement of the phone country-picker's filtered result
|
|
1788
|
+
* count as the respondent types in the search box. */
|
|
1789
|
+
phoneResultsCount: (count: number) => string;
|
|
1790
|
+
/** Accessible name/state for an empty signature canvas. */
|
|
1791
|
+
signatureEmpty: string;
|
|
1792
|
+
/** Accessible name/state for a signed signature canvas. */
|
|
1793
|
+
signatureSigned: string;
|
|
1471
1794
|
}
|
|
1472
1795
|
declare const DEFAULT_FIELD_STRINGS: FilloFieldStrings;
|
|
1473
1796
|
/** Everything the default renderers can localize — chrome + field/validation. */
|
|
@@ -1503,6 +1826,9 @@ interface SyncedForm {
|
|
|
1503
1826
|
/** Whether new file uploads can start right now. Independent from response
|
|
1504
1827
|
* acceptance; absent on older servers means available. */
|
|
1505
1828
|
uploadsAvailable?: boolean;
|
|
1829
|
+
/** Server-owned per-file ceiling for the active storage lane. Renderers use
|
|
1830
|
+
* the lower of this and the field's configured maxFileSizeMb. */
|
|
1831
|
+
uploadFileSizeLimitMb?: number;
|
|
1506
1832
|
/** Server-authoritative live snapshot when local code is not live yet. */
|
|
1507
1833
|
resolvedSchema?: FormSchema;
|
|
1508
1834
|
resolvedTheme?: FormTheme | null;
|
|
@@ -1711,4 +2037,4 @@ declare class Sha1 {
|
|
|
1711
2037
|
}
|
|
1712
2038
|
declare const sha1Base64: (bytes: Uint8Array) => string;
|
|
1713
2039
|
|
|
1714
|
-
export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, 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_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_POPOVER_VIEWPORT_GAP, type ParagraphBlock, 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, conditionsMet, contentHash, countryByDialCode, countryByIso, countryByTimeZone, createBlock, createClient, createEmptyForm, createFormController, createId, defineForm, digitsOnly, flagEmoji, formSchemasEqual, formatAnswer, formatNational, isAutoSubmitBlock, isBlockVisible, isBuildTimeDevEnv, isCodeForm, isField, isFilloError, isLikelyDevEnv, isPossiblePhone, isTerminalPage, needsExplicitSubmit, normalizeFormSchema, normalizeFormTheme, normalizeSettings, parsePhone, pipeBlock, positionPhonePopover, prefillFromParams, provisionWorkspace, reachableFieldIds, reachableFields, reachablePageIds, reachablePageSequence, resolveNextPage, resolveSlotClass, resolveStrings, resolveText, responseScopeValue, schemaFromJsx, sha1Base64, shouldAutoSubmit, slotClass, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields, visiblePageBlocks, when };
|
|
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 };
|