@usefillo/core 0.9.0 → 0.10.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/README.md CHANGED
@@ -1,9 +1,23 @@
1
- # @usefillo/core
1
+ <p align="center">
2
+ <a href="https://fillo.so">
3
+ <img src="https://fillo.so/brand/readme-banner.png" alt="Fillo — forms inside your product, with your UI." />
4
+ </a>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://fillo.so/docs">Docs</a> ·
9
+ <a href="https://fillo.so/guides">Guides</a> ·
10
+ <a href="https://fillo.so/examples">Examples</a> ·
11
+ <a href="https://fillo.so/changelog">Changelog</a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@usefillo/core"><img src="https://img.shields.io/npm/v/@usefillo/core" alt="npm version" /></a>
16
+ <img src="https://img.shields.io/npm/l/@usefillo/core" alt="MIT license" />
17
+ </p>
2
18
 
3
19
  The framework-agnostic core of [Fillo](https://fillo.so) — forms that render **inside your own product**, with your UI, no iframe.
4
20
 
5
- ### 📚 Full documentation → **[fillo.so/docs](https://fillo.so/docs)**
6
-
7
21
  This package holds the shared foundation: the form schema, validation, the conditional-logic engine, response prefill/piping, and a JS client for provider-aware browser-direct uploads, with resume support where the storage provider offers it. It has **zero framework dependencies**.
8
22
 
9
23
  Most apps don't install this directly — you install a renderer (**[@usefillo/react](https://www.npmjs.com/package/@usefillo/react)** or **[@usefillo/dom](https://www.npmjs.com/package/@usefillo/dom)**), which re-exports everything here you need for embedding. Reach for `@usefillo/core` when you're building your own renderer or working with forms on the server.
@@ -28,9 +42,7 @@ const form = defineForm({
28
42
 
29
43
  Published renderer embeds can fetch by `formId` without a key. Use a client object for custom API origins, uploads, or submissions from your own renderer; add a publishable key only when syncing `defineForm()` schemas from code.
30
44
 
31
- Schema settings include `submitMode: "auto"` for one-tap forms that should submit after a complete discrete answer instead of rendering the first submit button, and `responseLimit: { by: "browser", onRepeat: "keep" }` for browser-scoped duplicate prevention. Themes include `colorScheme: "light" | "dark" | "auto"` for renderer defaults.
32
-
33
- Key exports: `createClient` / `FilloClient`, `defineForm`, `validateResponse`, `validateField`, `visibleBlocks`, and the schema types (`FormSchema`, `Field`, `FieldKind`, `FormTheme`, `ResponseData`, …).
45
+ The full export surface — client methods, schema types, validation, conditional logic, appearance and localization contracts — is documented in the [API reference](https://fillo.so/docs/reference).
34
46
 
35
47
  ## Links
36
48
 
@@ -38,10 +50,3 @@ Key exports: `createClient` / `FilloClient`, `defineForm`, `validateResponse`, `
38
50
  - **Website:** [fillo.so](https://fillo.so)
39
51
 
40
52
  MIT licensed.
41
-
42
- ## Additional authoring surface
43
-
44
- Framework-neutral pieces the renderers share: `defineForm` / `schemaFromJsx`
45
- (the JSX compiler), `when()` condition builder, the `FilloAppearance` styling
46
- contract (slots + data-attribute names), `FilloStrings` localization defaults,
47
- and `createClient({ baseUrl })` for staging/self-hosted targets.
package/dist/index.d.ts CHANGED
@@ -708,6 +708,23 @@ interface PublishedForm {
708
708
  theme: FormTheme | null;
709
709
  /** True when the server says the form cannot accept responses. */
710
710
  closed?: boolean;
711
+ /**
712
+ * Whether a submission would be accepted right now. Absent on older servers
713
+ * — renderers must then keep today's behavior. When false, the default
714
+ * renderers show the not-open overlay instead of a fillable form.
715
+ */
716
+ accepting?: boolean;
717
+ /**
718
+ * Companion to `accepting` — present only when it is false.
719
+ * `draft`: not published yet; `expired`/`capped`: the unclaimed preview
720
+ * workspace hit its time window or response cap. Storage reasons may be
721
+ * returned for drafts or by older servers; published upload readiness is
722
+ * represented independently by `uploadsAvailable`.
723
+ */
724
+ acceptingReason?: "draft" | "expired" | "capped" | "storage_required" | "storage_full";
725
+ /** Whether new file uploads can start right now. Absent on older servers is
726
+ * treated as available; this never changes whether ordinary answers submit. */
727
+ uploadsAvailable?: boolean;
711
728
  /** Workspace branding state — absent means show the badge (default). */
712
729
  branding?: FormBranding;
713
730
  /** Human-verification challenge to render before submit, when the form
@@ -861,6 +878,24 @@ interface SyncFormResult {
861
878
  status?: "draft" | "published";
862
879
  /** Changes were staged as a draft for a human to publish. */
863
880
  staged?: boolean;
881
+ /**
882
+ * Whether a submission would be accepted right now. Absent on older servers
883
+ * — renderers must then keep today's behavior. When false, the default
884
+ * renderers show the not-open overlay instead of a fillable form.
885
+ */
886
+ accepting?: boolean;
887
+ /**
888
+ * Companion to `accepting` — present only when it is false.
889
+ * `draft`: not published yet; `expired`/`capped`: the unclaimed preview
890
+ * workspace hit its time window or response cap; `storage_required`: the
891
+ * form needs a connected storage destination before it can go live;
892
+ * `storage_full`: Fillo's temporary upload allowance is exhausted.
893
+ */
894
+ acceptingReason?: "draft" | "expired" | "capped" | "storage_required" | "storage_full";
895
+ /** Whether new file uploads can start right now. This is independent from
896
+ * response acceptance so a completed file can still be submitted after the
897
+ * workspace reaches its upload cap. Absent on older servers means available. */
898
+ uploadsAvailable?: boolean;
864
899
  /**
865
900
  * Server-authoritative live snapshot. Present when the incoming code schema
866
901
  * is not the version respondents may submit against yet.
@@ -872,7 +907,23 @@ interface SyncFormResult {
872
907
  code: string;
873
908
  message: string;
874
909
  };
910
+ /**
911
+ * Human-readable storage heads-up for the form owner. This can be advisory
912
+ * while uploads and responses remain available; never use it to gate UI.
913
+ */
875
914
  warning?: string;
915
+ /**
916
+ * Machine-readable owner advisory for `warning`. Hard unavailability uses
917
+ * `"storage_required"`; transit threshold advisories use distinct codes.
918
+ * Point the human at `warningUrl`; use `uploadsAvailable`, not this field,
919
+ * to gate new file controls.
920
+ */
921
+ warningCode?: string;
922
+ /**
923
+ * Absolute dashboard URL where a human connects a storage destination.
924
+ * Present whenever `warningCode` is.
925
+ */
926
+ warningUrl?: string;
876
927
  }
877
928
  declare class FilloClient {
878
929
  /** Server origin this client targets, normalized (no trailing slash). */
@@ -1070,6 +1121,15 @@ interface FormControllerOptions {
1070
1121
  * runs; failure sets `submitError` and the respondent can retry.
1071
1122
  */
1072
1123
  resolveFormId?: () => Promise<string>;
1124
+ /**
1125
+ * Surface the REAL {@link resolveFormId} failure (message + machine code)
1126
+ * in `submitError` instead of the respondent-safe "This form is
1127
+ * unavailable." fallback. Dev chrome only: renderers set it from the same
1128
+ * gate as their other developer surfaces (preview prop / dev environment),
1129
+ * so production visitors never see integration details such as keys,
1130
+ * origins, or deployment commands.
1131
+ */
1132
+ verboseResolutionErrors?: boolean;
1073
1133
  /**
1074
1134
  * Host-app account context (identify()): who is filling this form, by your
1075
1135
  * own user id. Sent with the submission and recorded as an unverified
@@ -1350,6 +1410,14 @@ interface FilloStrings {
1350
1410
  successMessage: string;
1351
1411
  closed: string;
1352
1412
  notLive: string;
1413
+ /** Title of the not-open overlay card (a draft/storage-blocked form rendered
1414
+ * in production shows the real form blurred beneath it). */
1415
+ notOpenTitle: string;
1416
+ /** Body of the not-open overlay card. */
1417
+ notOpenBody: string;
1418
+ /** Title of the closed-flavor overlay card (expired/capped workspace);
1419
+ * the body reuses `closed`. */
1420
+ closedTitle: string;
1353
1421
  /** Fallback when a submit fails without a server message. */
1354
1422
  submitFailed: string;
1355
1423
  loadFailedNotFound: string;
@@ -1384,6 +1452,8 @@ interface FilloFieldStrings {
1384
1452
  uploadRetry: string;
1385
1453
  /** Dropzone copy when uploads can't run (no client / preview). */
1386
1454
  uploadsDisabled: string;
1455
+ /** Dropzone copy when the server temporarily refuses new file sessions. */
1456
+ uploadsUnavailable: string;
1387
1457
  /** An upload attempt failed with no actionable server message. */
1388
1458
  uploadFailed: string;
1389
1459
  /** Dropzone call to action; `multiple` is true when several files are allowed. */
@@ -1419,6 +1489,20 @@ interface SyncedForm {
1419
1489
  status?: "draft" | "published";
1420
1490
  /** Changes were staged as a draft for a human to publish. */
1421
1491
  staged?: boolean;
1492
+ /** Whether a submission would be accepted right now. Absent on older
1493
+ * servers — renderers must then keep the status-based behavior. */
1494
+ accepting?: boolean;
1495
+ /**
1496
+ * Companion to `accepting` — present only when it is false. `draft`: not
1497
+ * published yet; `expired`/`capped`: the unclaimed preview workspace hit its
1498
+ * time window or response cap; `storage_required`: the form needs a
1499
+ * connected storage destination before it can go live; `storage_full`:
1500
+ * Fillo's temporary upload allowance is exhausted.
1501
+ */
1502
+ acceptingReason?: "draft" | "expired" | "capped" | "storage_required" | "storage_full";
1503
+ /** Whether new file uploads can start right now. Independent from response
1504
+ * acceptance; absent on older servers means available. */
1505
+ uploadsAvailable?: boolean;
1422
1506
  /** Server-authoritative live snapshot when local code is not live yet. */
1423
1507
  resolvedSchema?: FormSchema;
1424
1508
  resolvedTheme?: FormTheme | null;
@@ -1428,6 +1512,15 @@ interface SyncedForm {
1428
1512
  message: string;
1429
1513
  };
1430
1514
  warning?: string;
1515
+ /**
1516
+ * Machine-readable owner advisory for `warning`. Hard unavailability uses
1517
+ * `"storage_required"`; advisory thresholds use distinct codes. Use
1518
+ * `uploadsAvailable`, not this field, to decide whether an upload may start.
1519
+ */
1520
+ warningCode?: string;
1521
+ /** Absolute dashboard URL where a human connects a storage destination.
1522
+ * Present whenever `warningCode` is. */
1523
+ warningUrl?: string;
1431
1524
  }
1432
1525
  /**
1433
1526
  * A form whose structure lives in user code. Development and explicit
@@ -1543,6 +1636,37 @@ interface WhenBuilder {
1543
1636
  }
1544
1637
  declare function when(fieldId: string): WhenBuilder;
1545
1638
 
1639
+ /**
1640
+ * The build-time half of the dev check: `NODE_ENV` alone, no hostname
1641
+ * inspection. It evaluates identically on the server and in the browser for
1642
+ * the same bundle, which is what SSR hydration needs — the server has no
1643
+ * `window`, so a hostname-aware check would disagree with the client's first
1644
+ * paint. Renderers use this as the server/hydration snapshot and upgrade to
1645
+ * {@link isLikelyDevEnv} after hydration.
1646
+ */
1647
+ declare function isBuildTimeDevEnv(): boolean;
1648
+ /**
1649
+ * Whether this runtime looks like local development, so renderers can show
1650
+ * actionable dev surfaces (draft banner, missing-client warning, local schema
1651
+ * render) instead of the deliberately quiet production states.
1652
+ *
1653
+ * The build-time `NODE_ENV` signal alone misses real local setups: the
1654
+ * standalone `<script>` bundle has no `process` at all (so it always read as
1655
+ * production), `vite preview` / `next start` serve a production build on
1656
+ * localhost, and some bundlers never define `NODE_ENV`. So a browser whose
1657
+ * hostname is localhost/loopback also counts as development. Real
1658
+ * deployments keep production semantics because they serve from real
1659
+ * hostnames — see {@link isLocalHostname} for why mDNS `*.local` names and
1660
+ * private-LAN addresses are deliberately excluded.
1661
+ *
1662
+ * SSR-safe to CALL (with no `window`, only the `NODE_ENV` check applies), but
1663
+ * NOT hydration-safe for render output: the server pass can't see the page
1664
+ * hostname, so under `next start` on localhost it disagrees with the client.
1665
+ * Render paths should hydrate from {@link isBuildTimeDevEnv} and upgrade to
1666
+ * this check after hydration (see the React renderer's useIsDevEnv()).
1667
+ */
1668
+ declare function isLikelyDevEnv(): boolean;
1669
+
1546
1670
  /**
1547
1671
  * Build initial response data from URL query parameters — Tally-style
1548
1672
  * prefilling. Hidden fields read their configured paramName; every other
@@ -1587,4 +1711,4 @@ declare class Sha1 {
1587
1711
  }
1588
1712
  declare const sha1Base64: (bytes: Uint8Array) => string;
1589
1713
 
1590
- 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, isCodeForm, isField, isFilloError, 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 };
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 };
package/dist/index.js CHANGED
@@ -501,6 +501,9 @@ var DEFAULT_STRINGS = {
501
501
  successMessage: "Your response has been recorded.",
502
502
  closed: "This form is no longer accepting responses.",
503
503
  notLive: "This form isn't live yet.",
504
+ notOpenTitle: "Not open just yet",
505
+ notOpenBody: "This form is still being set up. Check back soon.",
506
+ closedTitle: "Responses are closed",
504
507
  submitFailed: "This form can't submit right now. Please try again in a moment.",
505
508
  loadFailedNotFound: "Form not found \u2014 check the form id and that it's published.",
506
509
  loadFailedNetwork: "Couldn't reach the server \u2014 check your connection or CORS.",
@@ -517,6 +520,7 @@ var DEFAULT_FIELD_STRINGS = {
517
520
  alreadyAnswered: "You've already answered this form.",
518
521
  uploadRetry: "Retry",
519
522
  uploadsDisabled: "Uploads are disabled in preview",
523
+ uploadsUnavailable: "Uploads are temporarily unavailable",
520
524
  uploadFailed: "Upload failed \u2014 try again",
521
525
  dropzoneTitle: (multiple) => `Drop ${multiple ? "files" : "a file"} here or click to browse`,
522
526
  dropzoneHint: (maxMb) => `Up to ${maxMb} MB per file`,
@@ -772,7 +776,7 @@ var schemaShape = z2.object({
772
776
  });
773
777
  var MAX_SCHEMA_VERSION = 1;
774
778
  var FILLO_SCHEMA_VERSION = 1;
775
- var FILLO_SDK_VERSION = true ? "0.9.0" : "0.0.0-dev";
779
+ var FILLO_SDK_VERSION = true ? "0.10.0" : "0.0.0-dev";
776
780
  var FILLO_MIN_SDK_VERSION = "0.4.0";
777
781
  var FILLO_CHALLENGE_MIN_SDK_VERSION = "0.9.0";
778
782
  function str(value, max, fallback = "") {
@@ -2471,9 +2475,12 @@ function submitFailureMessage(err) {
2471
2475
  if (isFilloError(err) && err.status && err.status > 0 && err.message) return err.message;
2472
2476
  return "Couldn't reach the server \u2014 check your connection and try again. If this keeps happening, a browser extension, firewall, or the site's Content-Security-Policy may be blocking the request.";
2473
2477
  }
2474
- function syncResolutionFailureMessage(err) {
2478
+ function syncResolutionFailureMessage(err, verbose) {
2475
2479
  if (isFilloError(err)) {
2476
2480
  const status = err.status ?? 0;
2481
+ if (verbose && status > 0 && err.message) {
2482
+ return err.code ? `${err.message} (${err.code})` : err.message;
2483
+ }
2477
2484
  const definitive = status > 0 && status < 500 && status !== 408 && status !== 429;
2478
2485
  if (definitive) return "This form is unavailable.";
2479
2486
  }
@@ -2849,7 +2856,10 @@ function createFormController(options2) {
2849
2856
  formId = await options2.resolveFormId();
2850
2857
  } catch (err) {
2851
2858
  status = "idle";
2852
- submitError = syncResolutionFailureMessage(err);
2859
+ submitError = syncResolutionFailureMessage(
2860
+ err,
2861
+ options2.verboseResolutionErrors === true
2862
+ );
2853
2863
  notify();
2854
2864
  throw err;
2855
2865
  }
@@ -3148,7 +3158,7 @@ function formSchemasEqual(left, right) {
3148
3158
  return JSON.stringify(canonicalize(left)) === JSON.stringify(canonicalize(right));
3149
3159
  }
3150
3160
  var syncTtlMs = (status) => status === "published" ? 36e5 : 6e4;
3151
- var isVolatileSync = (result) => Boolean(result.staged || result.resolvedSchema || result.syncError);
3161
+ var isVolatileSync = (result) => Boolean(result.staged || result.resolvedSchema || result.syncError || result.accepting === false);
3152
3162
  function storageKey(client, handle) {
3153
3163
  return `fillo:sync:v3:${client.baseUrl}|${client.key}|${handle}`;
3154
3164
  }
@@ -3167,7 +3177,7 @@ function readStoredSync(client, handle, hash) {
3167
3177
  }
3168
3178
  }
3169
3179
  function writeStoredSync(client, handle, hash, r) {
3170
- if (r.staged || r.resolvedSchema || r.syncError) return;
3180
+ if (r.staged || r.resolvedSchema || r.syncError || r.accepting === false) return;
3171
3181
  if (r.challenge) return;
3172
3182
  try {
3173
3183
  globalThis.localStorage?.setItem(
@@ -3548,6 +3558,23 @@ function when(fieldId) {
3548
3558
  };
3549
3559
  }
3550
3560
 
3561
+ // src/dev-env.ts
3562
+ function isLocalHostname(hostname) {
3563
+ const host = hostname.toLowerCase();
3564
+ return host === "localhost" || host.endsWith(".localhost") || // Loopback ADDRESSES only — anchored so `127.0.0.1.example.com` stays a
3565
+ // real (production) hostname.
3566
+ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(host) || host === "::1" || host === "[::1]" || host === "0.0.0.0";
3567
+ }
3568
+ function isBuildTimeDevEnv() {
3569
+ return typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
3570
+ }
3571
+ function isLikelyDevEnv() {
3572
+ if (isBuildTimeDevEnv()) return true;
3573
+ if (typeof window === "undefined") return false;
3574
+ const hostname = window.location?.hostname;
3575
+ return typeof hostname === "string" && isLocalHostname(hostname);
3576
+ }
3577
+
3551
3578
  // src/piping.ts
3552
3579
  var TOKEN = /\{\{\s*([\w-]+)\s*\}\}/g;
3553
3580
  function resolveText(text, data, form) {
@@ -3618,9 +3645,11 @@ export {
3618
3645
  formatNational,
3619
3646
  isAutoSubmitBlock,
3620
3647
  isBlockVisible,
3648
+ isBuildTimeDevEnv,
3621
3649
  isCodeForm,
3622
3650
  isField,
3623
3651
  isFilloError,
3652
+ isLikelyDevEnv,
3624
3653
  isPossiblePhone,
3625
3654
  isTerminalPage,
3626
3655
  needsExplicitSubmit,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefillo/core",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Form schema, validation, logic engine and JS client for Fillo. Framework-agnostic.",
5
5
  "license": "MIT",
6
6
  "keywords": [