@usefillo/core 0.19.0 → 0.21.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 CHANGED
@@ -589,7 +589,9 @@ declare function resolveNextPage(form: FormSchema, currentPageId: string, data:
589
589
  * it reduces to today's linear behavior. {@link reachablePageIds} is the set of
590
590
  * this exact walk (one walk, no divergence).
591
591
  */
592
- declare function reachablePageSequence(form: FormSchema, data: ResponseData): string[];
592
+ declare function reachablePageSequence(form: FormSchema, data: ResponseData,
593
+ /** Optional starting page for inspecting a branch in a local form preview. */
594
+ startPageId?: string): string[];
593
595
  /**
594
596
  * The page ids reachable for the given data. The unordered set of
595
597
  * {@link reachablePageSequence} — one shared walk keeps the ordered navigation
@@ -1566,6 +1568,8 @@ declare function respondentErrorStringsFor(strings: Pick<FilloStrings, "submitFa
1566
1568
  * an exhaustively-enumerated, closed set.
1567
1569
  */
1568
1570
  interface FilloFieldStrings {
1571
+ /** Browse the next page in a render-only form preview without answering. */
1572
+ previewSkip: string;
1569
1573
  /** Optional, backward-compatible overrides for respondent error states that
1570
1574
  * do not already have a top-level renderer string. */
1571
1575
  respondentErrors?: Partial<RespondentErrorOverrides>;
@@ -1676,6 +1680,12 @@ interface FormControllerOptions {
1676
1680
  * option to change visitor-facing behavior such as branding.
1677
1681
  */
1678
1682
  skipValidation?: boolean;
1683
+ /**
1684
+ * Enable explicit page skipping in a transportless preview. Requires
1685
+ * skipValidation and no client, formId, or resolver. Native next() still
1686
+ * validates; skipPreviewPage() alone bypasses validation and page routing.
1687
+ */
1688
+ previewNavigation?: boolean;
1679
1689
  /**
1680
1690
  * Embedding surface, recorded per response for measurement. "headless" = a
1681
1691
  * bare engine with no Fillo-rendered layout (createFormController /
@@ -1799,6 +1809,8 @@ interface FormController {
1799
1809
  setUploading(fieldId: string, busy: boolean): void;
1800
1810
  /** Validate the current page, then advance. No-op on the last page or with errors. */
1801
1811
  next(): void;
1812
+ /** Browse the next physical page in an explicitly enabled local preview. */
1813
+ skipPreviewPage(): void;
1802
1814
  back(): void;
1803
1815
  submit(): Promise<void>;
1804
1816
  /**
package/dist/index.js CHANGED
@@ -311,11 +311,11 @@ function resolveNextPage(form, currentPageId, data) {
311
311
  }
312
312
  return { linear: true };
313
313
  }
314
- function reachablePageSequence(form, data) {
314
+ function reachablePageSequence(form, data, startPageId) {
315
315
  const seq = [];
316
316
  if (form.pages.length === 0) return seq;
317
317
  const seen = /* @__PURE__ */ new Set();
318
- let index = 0;
318
+ let index = startPageId === void 0 ? 0 : form.pages.findIndex((p) => p.id === startPageId);
319
319
  for (let steps = 0; steps <= form.pages.length; steps++) {
320
320
  const page = form.pages[index];
321
321
  if (!page || seen.has(page.id)) break;
@@ -793,6 +793,7 @@ function defaultRequiredForField(field2) {
793
793
  return REQUIRED_BY_KIND[field2.kind] ?? REQUIRED_FIELD_MESSAGE;
794
794
  }
795
795
  var DEFAULT_FIELD_STRINGS = {
796
+ previewSkip: "Skip",
796
797
  required: REQUIRED_FIELD_MESSAGE,
797
798
  requiredForField: defaultRequiredForField,
798
799
  resumeLinkExpired: "That saved-progress link expired or was already used \u2014 start again below.",
@@ -1112,7 +1113,7 @@ var schemaShape = z2.object({
1112
1113
  });
1113
1114
  var MAX_SCHEMA_VERSION = 1;
1114
1115
  var FILLO_SCHEMA_VERSION = 1;
1115
- var FILLO_SDK_VERSION = true ? "0.19.0" : "0.0.0-dev";
1116
+ var FILLO_SDK_VERSION = true ? "0.21.0" : "0.0.0-dev";
1116
1117
  var FILLO_MIN_SDK_VERSION = "0.4.0";
1117
1118
  var FILLO_CHALLENGE_MIN_SDK_VERSION = "0.9.0";
1118
1119
  var FILLO_CALC_MIN_SDK_VERSION = "0.11.0";
@@ -3431,6 +3432,23 @@ function createFormController(options2) {
3431
3432
  let data = computeCalculated(form, { ...options2.initialData ?? {} });
3432
3433
  let errors = {};
3433
3434
  let pageIndex = 0;
3435
+ let previewStartPageId;
3436
+ const previewHistory = [];
3437
+ function previewNavigationEnabled() {
3438
+ return options2.previewNavigation === true && options2.skipValidation === true && !client && !formId && !options2.resolveFormId;
3439
+ }
3440
+ function navigationSequence() {
3441
+ return reachablePageSequence(
3442
+ form,
3443
+ data,
3444
+ previewNavigationEnabled() ? previewStartPageId : void 0
3445
+ );
3446
+ }
3447
+ function rememberPreviewPage() {
3448
+ if (previewNavigationEnabled()) {
3449
+ previewHistory.push({ pageIndex, startPageId: previewStartPageId });
3450
+ }
3451
+ }
3434
3452
  let status = "idle";
3435
3453
  let restoredSubmission = false;
3436
3454
  let resumeLinkFailed = false;
@@ -3516,7 +3534,7 @@ function createFormController(options2) {
3516
3534
  // Terminal-aware: true when the current page is the last reachable page OR
3517
3535
  // a matched jump rule ends the form here — so the footer reads Submit and
3518
3536
  // its handler submits. Same shared engine the validator reaches with.
3519
- isLastPage: isTerminalPage(form, page.id, data),
3537
+ isLastPage: previewNavigationEnabled() && previewStartPageId !== void 0 ? navigationSequence().at(-1) === page.id : isTerminalPage(form, page.id, data),
3520
3538
  uploading: uploadingFields.size > 0,
3521
3539
  submitError,
3522
3540
  restoredSubmission,
@@ -3545,6 +3563,7 @@ function createFormController(options2) {
3545
3563
  const pageErrors = {};
3546
3564
  for (const block of visiblePageBlocks(form, target, data)) {
3547
3565
  if (!isField(block)) continue;
3566
+ if (previewNavigationEnabled() && block.kind === "file_upload") continue;
3548
3567
  const error = validateField(block, data[block.id]);
3549
3568
  if (error) pageErrors[block.id] = error;
3550
3569
  }
@@ -3748,7 +3767,7 @@ function createFormController(options2) {
3748
3767
  function next() {
3749
3768
  const pageCount = form.pages.length;
3750
3769
  const clamped = Math.min(pageIndex, Math.max(pageCount - 1, 0));
3751
- if (!options2.skipValidation) {
3770
+ if (!options2.skipValidation || options2.previewNavigation === true) {
3752
3771
  const pageErrors = validatePage(clamped);
3753
3772
  errors = pageErrors;
3754
3773
  if (Object.keys(pageErrors).length > 0) {
@@ -3757,14 +3776,15 @@ function createFormController(options2) {
3757
3776
  }
3758
3777
  }
3759
3778
  const current = form.pages[clamped];
3760
- const seq = reachablePageSequence(form, data);
3779
+ const seq = navigationSequence();
3761
3780
  const pos = currentSeqPosition(seq);
3762
- const terminal = (current ? isTerminalPage(form, current.id, data) : true) || pos < 0 || pos + 1 >= seq.length;
3781
+ const terminal = (previewNavigationEnabled() && previewStartPageId !== void 0 ? seq.at(-1) === current?.id : current ? isTerminalPage(form, current.id, data) : true) || pos < 0 || pos + 1 >= seq.length;
3763
3782
  if (terminal) {
3764
3783
  void submit();
3765
3784
  return;
3766
3785
  }
3767
3786
  const nextIndex = form.pages.findIndex((p) => p.id === seq[pos + 1]);
3787
+ rememberPreviewPage();
3768
3788
  pageIndex = nextIndex >= 0 ? nextIndex : Math.min(clamped + 1, pageCount - 1);
3769
3789
  if (sessionId && client) client.reportProgress(sessionId, { furthestPage: pageIndex });
3770
3790
  notify();
@@ -3772,6 +3792,14 @@ function createFormController(options2) {
3772
3792
  }
3773
3793
  function back() {
3774
3794
  errors = {};
3795
+ const previous = previewNavigationEnabled() ? previewHistory.pop() : void 0;
3796
+ if (previous) {
3797
+ pageIndex = Math.min(previous.pageIndex, form.pages.length - 1);
3798
+ previewStartPageId = previous.startPageId;
3799
+ submitError = void 0;
3800
+ notify();
3801
+ return;
3802
+ }
3775
3803
  const seq = reachablePageSequence(form, data);
3776
3804
  const pos = currentSeqPosition(seq);
3777
3805
  if (pos > 0) {
@@ -3784,8 +3812,25 @@ function createFormController(options2) {
3784
3812
  notify();
3785
3813
  checkpointDraft();
3786
3814
  }
3815
+ function skipPreviewPage() {
3816
+ if (!previewNavigationEnabled() || status === "submitting" || status === "submitted" || pageIndex >= form.pages.length - 1)
3817
+ return;
3818
+ rememberPreviewPage();
3819
+ pageIndex += 1;
3820
+ previewStartPageId = form.pages[pageIndex]?.id;
3821
+ errors = {};
3822
+ submitError = void 0;
3823
+ notify();
3824
+ }
3787
3825
  async function submit() {
3788
3826
  if (status === "submitting" || status === "submitted") return;
3827
+ if (previewNavigationEnabled()) {
3828
+ errors = validatePage(pageIndex);
3829
+ if (Object.keys(errors).length > 0) {
3830
+ notify();
3831
+ return;
3832
+ }
3833
+ }
3789
3834
  const result = validateResponse(form, data);
3790
3835
  if (!result.ok) {
3791
3836
  const transportlessPreview = options2.skipValidation && !(client && formId);
@@ -3809,7 +3854,11 @@ function createFormController(options2) {
3809
3854
  const firstBad = form.pages.findIndex(
3810
3855
  (p) => visiblePageBlocks(form, p, data).some((b) => b.id in previewErrors)
3811
3856
  );
3812
- if (firstBad >= 0) pageIndex = firstBad;
3857
+ if (firstBad >= 0) {
3858
+ pageIndex = firstBad;
3859
+ previewStartPageId = void 0;
3860
+ previewHistory.length = 0;
3861
+ }
3813
3862
  notify();
3814
3863
  return;
3815
3864
  }
@@ -3911,6 +3960,7 @@ function createFormController(options2) {
3911
3960
  setValue,
3912
3961
  setUploading,
3913
3962
  next,
3963
+ skipPreviewPage,
3914
3964
  back,
3915
3965
  submit,
3916
3966
  setContext(ctx) {
@@ -3955,6 +4005,8 @@ function createFormController(options2) {
3955
4005
  data = computeCalculated(form, data);
3956
4006
  errors = {};
3957
4007
  pageIndex = 0;
4008
+ previewStartPageId = void 0;
4009
+ previewHistory.length = 0;
3958
4010
  submitError = void 0;
3959
4011
  resumedDraft = false;
3960
4012
  editingPrevious = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefillo/core",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Form schema, validation, logic engine and JS client for Fillo. Framework-agnostic.",
5
5
  "license": "MIT",
6
6
  "keywords": [