@usefillo/core 0.24.1 → 0.25.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
@@ -401,16 +401,31 @@ interface FormSchema {
401
401
  pages: FormPage[];
402
402
  settings: FormSettings;
403
403
  }
404
- interface FormTheme {
405
- /** Omit to inherit the host page. Use "auto" to follow the visitor's system directly. */
406
- colorScheme?: "light" | "dark" | "auto";
404
+ /** The colors (and logo) of one appearance. */
405
+ interface FormThemePalette {
407
406
  /** Accent for buttons, focus rings, selection. */
408
407
  primary?: string;
409
408
  background?: string;
410
409
  text?: string;
410
+ /**
411
+ * Logo shown above the form on Fillo-hosted pages: the id of an image
412
+ * uploaded to the workspace. Embedded forms leave branding to the host app.
413
+ */
414
+ logo?: string;
415
+ }
416
+ interface FormTheme extends FormThemePalette {
417
+ /** Omit to inherit the host page. Use "auto" to follow the visitor's system directly. */
418
+ colorScheme?: "light" | "dark" | "auto";
411
419
  /** Border radius scale, e.g. "8px". */
412
420
  radius?: string;
413
421
  fontFamily?: string;
422
+ /**
423
+ * Dark-mode colors, for a form that follows a light/dark setting
424
+ * (`colorScheme` omitted or "auto"). The top-level colors are then the
425
+ * light palette. An unset dark color uses the default dark token; an unset
426
+ * dark logo reuses the top-level logo. Ignored when `colorScheme` is fixed.
427
+ */
428
+ dark?: FormThemePalette;
414
429
  }
415
430
  /** Branding the server tells the SDK to render. Server-owned so the wording /
416
431
  * link can change centrally without a new SDK release. */
@@ -1980,6 +1995,8 @@ declare const FILLO_THEME_VARS: readonly [{
1980
1995
  readonly var: "--fillo-muted";
1981
1996
  }, {
1982
1997
  readonly var: "--fillo-border";
1998
+ }, {
1999
+ readonly var: "--fillo-control-border";
1983
2000
  }, {
1984
2001
  readonly var: "--fillo-control-bg";
1985
2002
  }, {
@@ -2006,6 +2023,30 @@ declare const FILLO_THEME_VARS: readonly [{
2006
2023
  * `[data-fillo-color-scheme="dark"]` CSS, so no new CSS is needed.
2007
2024
  */
2008
2025
  declare function resolveThemeAppearance(theme: FormTheme | null): FormTheme | null;
2026
+ /**
2027
+ * True when the theme switches palettes with the light/dark setting: it has
2028
+ * dark colors and its scheme is not fixed (omitted or "auto"). A dark logo
2029
+ * alone switches nothing.
2030
+ */
2031
+ declare function themeHasDarkPalette(theme: FormTheme | null | undefined): boolean;
2032
+ interface ThemeDomAttributes {
2033
+ /** Inline custom properties for the form root (`--fillo-primary`, …). */
2034
+ vars: Record<string, string>;
2035
+ /** `data-fillo-*` attributes for the form root. */
2036
+ attrs: Record<string, string>;
2037
+ }
2038
+ /**
2039
+ * Turn an untrusted theme into the form root's inline custom properties and
2040
+ * data attributes — one implementation for every renderer.
2041
+ *
2042
+ * A theme with a dark palette (see themeHasDarkPalette) emits each color as
2043
+ * `light-dark(light, dark)`, so it follows the form's own color-scheme: the
2044
+ * host page's when `colorScheme` is omitted, the visitor's device on "auto".
2045
+ * `data-fillo-on-primary` (and `-dark`) name the text tone that reads on a
2046
+ * custom accent; the stylesheet maps them to `--fillo-primary-contrast`, which
2047
+ * stays overridable from consumer CSS.
2048
+ */
2049
+ declare function resolveThemeDom(theme: FormTheme | null | undefined): ThemeDomAttributes | null;
2009
2050
 
2010
2051
  /** Result of syncing a code-defined form: its canonical id, slug, and branding. */
2011
2052
  interface SyncedForm {
@@ -2252,4 +2293,4 @@ declare class Sha1 {
2252
2293
  }
2253
2294
  declare const sha1Base64: (bytes: Uint8Array) => string;
2254
2295
 
2255
- export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CalcExpr, type CalculatedField, type ChallengeConfig, type ChallengeTheme, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CreatedDraft, type CustomField, DEFAULT_FIELD_STRINGS, DEFAULT_RESPONDENT_ERROR_STRINGS, DEFAULT_STRINGS, DRAFT_KINDS, type DateField, type DividerBlock, FILLO_CALC_MIN_SDK_VERSION, FILLO_CHALLENGE_MIN_SDK_VERSION, FILLO_DATA_ATTRS, FILLO_GROUP_MIN_SDK_VERSION, FILLO_MIN_SDK_VERSION, FILLO_SCHEMA_VERSION, FILLO_SDK_VERSION, FILLO_SLOTS, FILLO_THEME_VARS, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, type FilloAppearance, FilloClient, type FilloClientOptions, FilloError, type FilloFieldStrings, FilloJsxError, type FilloRendererStrings, type FilloRespondent, type FilloSlot, type FilloStrings, type FormBranding, type FormController, type FormControllerOptions, type FormControllerState, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormStatus, type FormTheme, type GroupInstanceValue, type HeadingBlock, type HiddenField, JSX_BLOCK_COMPONENTS, JSX_BLOCK_SPECS, type JsonValue, type JsxFormMeta, type JumpRule, type LinearScaleField, MAX_FILES_PER_FIELD, type MatrixField, type NextPage, type NumberField, PHONE_COUNTRIES, PHONE_PICKER_COUNTRIES, PHONE_POPOVER_VIEWPORT_GAP, type ParagraphBlock, type ParsePhoneOptions, type ParsedPhone, type PhoneCountry, type PhoneField, type PhonePopoverPlacement, type ProvisionWorkspaceResult, type PublishedForm, REQUIRED_FIELD_MESSAGE, type RankingField, type RatingField, type RepeatingGroupField, type RespondentErrorOverrides, type RespondentErrorStrings, type ResponseData, type ResponseDraft, type ResponseLimit, type SchemaValidationResult, type SelectOption, Sha1, type SignatureField, type SlotClass, type SlotState, type SubmitMeta, type SubmitResult, type SyncFormResult, type SyncedForm, type TextField, type TrustPolicy, type UploadFileOptions, type UploadProgress, type UploadSession, type UploadStatus, type UploadTransport, type ValidationResult, type WhenBuilder, allFields, assembleForm, codeFormFromJsx, computeCalculated, conditionsMet, contentHash, countryByDialCode, countryByIso, countryByTimeZone, createBlock, createClient, createEmptyForm, createFormController, createId, defineForm, digitsOnly, evaluateCalc, flagEmoji, formSchemasEqual, formatAnswer, formatGroupedNumber, formatNational, isAutoSubmitBlock, isBlockVisible, isBuildTimeDevEnv, isCodeForm, isField, isFilloError, isLikelyDevEnv, isPossiblePhone, isTerminalPage, isValidPartialNumberText, localeForNotation, needsExplicitSubmit, normalizeFormSchema, normalizeFormTheme, normalizeSettings, parseGroupedNumber, parsePhone, pipeBlock, positionPhonePopover, prefillFromParams, provisionWorkspace, radioGroupStep, reachableFieldIds, reachableFields, reachablePageIds, reachablePageSequence, requiredFieldMessage, resolveNextPage, resolveSlotClass, resolveStrings, resolveText, resolveThemeAppearance, respondentErrorStringsFor, responseScopeValue, schemaFromJsx, sha1Base64, shouldAutoSubmit, slotClass, syncCodeForm, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields, visibleGroupChildren, visiblePageBlocks, when };
2296
+ export { type AutoSubmitContext, BLOCK_KIND_META, type BaseField, type Block, type BlockKind, CONTENT_KINDS, type CalcExpr, type CalculatedField, type ChallengeConfig, type ChallengeTheme, type CheckboxField, type ChoiceField, type CodeForm, type Condition, type ConditionOp, type ContentBlock, type ContentKind, type CreatedDraft, type CustomField, DEFAULT_FIELD_STRINGS, DEFAULT_RESPONDENT_ERROR_STRINGS, DEFAULT_STRINGS, DRAFT_KINDS, type DateField, type DividerBlock, FILLO_CALC_MIN_SDK_VERSION, FILLO_CHALLENGE_MIN_SDK_VERSION, FILLO_DATA_ATTRS, FILLO_GROUP_MIN_SDK_VERSION, FILLO_MIN_SDK_VERSION, FILLO_SCHEMA_VERSION, FILLO_SDK_VERSION, FILLO_SLOTS, FILLO_THEME_VARS, type Field, type FieldKind, type FieldSpec, type FieldValue, type FileUploadField, type FileValue, type FilloAppearance, FilloClient, type FilloClientOptions, FilloError, type FilloFieldStrings, FilloJsxError, type FilloRendererStrings, type FilloRespondent, type FilloSlot, type FilloStrings, type FormBranding, type FormController, type FormControllerOptions, type FormControllerState, type FormDraftSpec, type FormPage, type FormSchema, type FormSettings, type FormStatus, type FormTheme, type FormThemePalette, type GroupInstanceValue, type HeadingBlock, type HiddenField, JSX_BLOCK_COMPONENTS, JSX_BLOCK_SPECS, type JsonValue, type JsxFormMeta, type JumpRule, type LinearScaleField, MAX_FILES_PER_FIELD, type MatrixField, type NextPage, type NumberField, PHONE_COUNTRIES, PHONE_PICKER_COUNTRIES, PHONE_POPOVER_VIEWPORT_GAP, type ParagraphBlock, type ParsePhoneOptions, type ParsedPhone, type PhoneCountry, type PhoneField, type PhonePopoverPlacement, type ProvisionWorkspaceResult, type PublishedForm, REQUIRED_FIELD_MESSAGE, type RankingField, type RatingField, type RepeatingGroupField, type RespondentErrorOverrides, type RespondentErrorStrings, type ResponseData, type ResponseDraft, type ResponseLimit, type SchemaValidationResult, type SelectOption, Sha1, type SignatureField, type SlotClass, type SlotState, type SubmitMeta, type SubmitResult, type SyncFormResult, type SyncedForm, type TextField, type ThemeDomAttributes, 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, resolveThemeDom, respondentErrorStringsFor, responseScopeValue, schemaFromJsx, sha1Base64, shouldAutoSubmit, slotClass, syncCodeForm, themeHasDarkPalette, toE164, validateField, validateFormSchema, validateResponse, visibleBlocks, visibleFields, visibleGroupChildren, visiblePageBlocks, when };
package/dist/index.js CHANGED
@@ -1114,7 +1114,7 @@ var schemaShape = z2.object({
1114
1114
  });
1115
1115
  var MAX_SCHEMA_VERSION = 1;
1116
1116
  var FILLO_SCHEMA_VERSION = 1;
1117
- var FILLO_SDK_VERSION = true ? "0.24.1" : "0.0.0-dev";
1117
+ var FILLO_SDK_VERSION = true ? "0.25.0" : "0.0.0-dev";
1118
1118
  var FILLO_MIN_SDK_VERSION = "0.4.0";
1119
1119
  var FILLO_CHALLENGE_MIN_SDK_VERSION = "0.9.0";
1120
1120
  var FILLO_CALC_MIN_SDK_VERSION = "0.11.0";
@@ -1893,17 +1893,30 @@ function safeCssToken(value, max) {
1893
1893
  if (/[;{}<>]/.test(token) || /url\s*\(/i.test(token)) return void 0;
1894
1894
  return token;
1895
1895
  }
1896
+ function themeAssetId(value) {
1897
+ return typeof value === "string" && /^[A-Za-z0-9_-]{8,64}$/.test(value) ? value : void 0;
1898
+ }
1899
+ function normalizeThemePalette(input) {
1900
+ if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
1901
+ const rec = input;
1902
+ const palette = cleanObject({
1903
+ primary: safeCssToken(rec.primary, 200),
1904
+ background: safeCssToken(rec.background, 200),
1905
+ text: safeCssToken(rec.text, 200),
1906
+ logo: themeAssetId(rec.logo)
1907
+ });
1908
+ return Object.keys(palette).length > 0 ? palette : void 0;
1909
+ }
1896
1910
  function normalizeFormTheme(input) {
1897
1911
  if (!input || typeof input !== "object" || Array.isArray(input)) return null;
1898
1912
  const rec = input;
1899
1913
  const colorScheme = rec.colorScheme === "light" || rec.colorScheme === "dark" || rec.colorScheme === "auto" ? rec.colorScheme : void 0;
1900
1914
  const theme = cleanObject({
1901
1915
  colorScheme,
1902
- primary: safeCssToken(rec.primary, 200),
1903
- background: safeCssToken(rec.background, 200),
1904
- text: safeCssToken(rec.text, 200),
1916
+ ...normalizeThemePalette(rec),
1905
1917
  radius: safeCssToken(rec.radius, 64),
1906
- fontFamily: safeCssToken(rec.fontFamily, 200)
1918
+ fontFamily: safeCssToken(rec.fontFamily, 200),
1919
+ dark: normalizeThemePalette(rec.dark)
1907
1920
  });
1908
1921
  return Object.keys(theme).length > 0 ? theme : null;
1909
1922
  }
@@ -4194,6 +4207,7 @@ var FILLO_THEME_VARS = [
4194
4207
  { var: "--fillo-font", themeProp: "fontFamily" },
4195
4208
  { var: "--fillo-muted" },
4196
4209
  { var: "--fillo-border" },
4210
+ { var: "--fillo-control-border" },
4197
4211
  { var: "--fillo-control-bg" },
4198
4212
  { var: "--fillo-error" },
4199
4213
  { var: "--fillo-primary-contrast" }
@@ -4222,13 +4236,71 @@ function relativeLuminance(token) {
4222
4236
  return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
4223
4237
  }
4224
4238
  function resolveThemeAppearance(theme) {
4225
- if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark" || !theme.background) {
4239
+ if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark" || !theme.background || // A form with dark colors of its own follows the light/dark setting.
4240
+ themeHasDarkPalette(theme)) {
4226
4241
  return theme;
4227
4242
  }
4228
4243
  const luminance = relativeLuminance(theme.background);
4229
4244
  if (luminance == null) return theme;
4230
4245
  return { ...theme, colorScheme: luminance > DARK_LUMINANCE_THRESHOLD ? "light" : "dark" };
4231
4246
  }
4247
+ function themeHasDarkPalette(theme) {
4248
+ if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark") return false;
4249
+ const dark = theme.dark;
4250
+ return Boolean(dark && (dark.primary || dark.background || dark.text));
4251
+ }
4252
+ var ON_LIGHT_TEXT = "#18181b";
4253
+ function onColorTone(color) {
4254
+ const luminance = relativeLuminance(color);
4255
+ if (luminance == null) return null;
4256
+ const withWhite = 1.05 / (luminance + 0.05);
4257
+ const withDark = (luminance + 0.05) / ((relativeLuminance(ON_LIGHT_TEXT) ?? 0) + 0.05);
4258
+ return withWhite >= withDark ? "light" : "dark";
4259
+ }
4260
+ var PALETTE_VARS = [
4261
+ ["primary", "--fillo-primary"],
4262
+ ["background", "--fillo-bg"],
4263
+ ["text", "--fillo-text"]
4264
+ ];
4265
+ var DEFAULT_PALETTE = {
4266
+ light: { primary: "#18181b", background: "transparent", text: "#18181b" },
4267
+ dark: { primary: "#fafafa", background: "transparent", text: "#f4f4f5" }
4268
+ };
4269
+ function resolveThemeDom(theme) {
4270
+ const safe = resolveThemeAppearance(normalizeFormTheme(theme));
4271
+ if (!safe) return null;
4272
+ const vars = {};
4273
+ const attrs = {};
4274
+ if (safe.colorScheme) attrs["data-fillo-color-scheme"] = safe.colorScheme;
4275
+ const dark = themeHasDarkPalette(safe) ? safe.dark : void 0;
4276
+ for (const [key, name] of PALETTE_VARS) {
4277
+ const light = safe[key];
4278
+ if (!dark) {
4279
+ if (light) vars[name] = light;
4280
+ continue;
4281
+ }
4282
+ const darkValue = dark[key];
4283
+ if (!light && !darkValue) continue;
4284
+ vars[name] = `light-dark(${light ?? DEFAULT_PALETTE.light[key]}, ${darkValue ?? DEFAULT_PALETTE.dark[key]})`;
4285
+ }
4286
+ if (safe.radius) vars["--fillo-radius"] = safe.radius;
4287
+ if (safe.fontFamily) vars["--fillo-font"] = safe.fontFamily;
4288
+ if (dark) {
4289
+ const lightTone = onColorTone(safe.primary ?? DEFAULT_PALETTE.light.primary);
4290
+ const darkTone = onColorTone(dark.primary ?? DEFAULT_PALETTE.dark.primary);
4291
+ if (lightTone && darkTone) {
4292
+ attrs["data-fillo-on-primary"] = lightTone;
4293
+ attrs["data-fillo-on-primary-dark"] = darkTone;
4294
+ }
4295
+ } else if (safe.primary) {
4296
+ const tone = onColorTone(safe.primary);
4297
+ if (tone) attrs["data-fillo-on-primary"] = tone;
4298
+ }
4299
+ if (safe.primary || safe.background || safe.text || dark) {
4300
+ attrs["data-fillo-has-color-theme"] = "";
4301
+ }
4302
+ return { vars, attrs };
4303
+ }
4232
4304
 
4233
4305
  // src/define.ts
4234
4306
  function defineForm(def) {
@@ -4864,6 +4936,7 @@ export {
4864
4936
  resolveStrings,
4865
4937
  resolveText,
4866
4938
  resolveThemeAppearance,
4939
+ resolveThemeDom,
4867
4940
  respondentErrorStringsFor,
4868
4941
  responseScopeValue,
4869
4942
  schemaFromJsx,
@@ -4871,6 +4944,7 @@ export {
4871
4944
  shouldAutoSubmit,
4872
4945
  slotClass,
4873
4946
  syncCodeForm,
4947
+ themeHasDarkPalette,
4874
4948
  toE164,
4875
4949
  validateField,
4876
4950
  validateFormSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefillo/core",
3
- "version": "0.24.1",
3
+ "version": "0.25.0",
4
4
  "description": "Form schema, validation, logic engine and JS client for Fillo. Framework-agnostic.",
5
5
  "license": "MIT",
6
6
  "keywords": [