@usefillo/core 0.24.1 → 0.26.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
@@ -95,6 +95,11 @@ interface RatingField extends BaseField {
95
95
  }
96
96
  interface DateField extends BaseField {
97
97
  kind: "date";
98
+ /**
99
+ * Start on today's date — the respondent's own day, set by their device once
100
+ * the form loads (the server can't know their time zone). They can change it.
101
+ */
102
+ defaultToday?: boolean;
98
103
  }
99
104
  interface LinearScaleField extends BaseField {
100
105
  kind: "linear_scale";
@@ -300,8 +305,12 @@ interface ResponseLimit {
300
305
  * and anonymous embeds can still dedupe.
301
306
  * - "identify": the identify() respondent (HMAC-verifiable). The strong
302
307
  * path; only embedded surfaces that call identify() are recognized.
308
+ * - "email": a verified email address. On the form's Fillo link the person
309
+ * confirms their email with a 6-digit code before filling in, and the
310
+ * device stays confirmed for 30 days. Embedded forms can't show that
311
+ * step — use "identify" there.
303
312
  */
304
- by: "browser" | "field" | "identify";
313
+ by: "browser" | "field" | "identify" | "email";
305
314
  /** The email/phone field whose answer identifies the person. `by: "field"` only. */
306
315
  field?: string;
307
316
  /**
@@ -315,7 +324,8 @@ interface ResponseLimit {
315
324
  * response (the first answer stands); "update" edits it in place (re-anchored
316
325
  * to the current schema, `response.updated` webhook, previous answers
317
326
  * prefilled for a verified respondent). "update" applies to `by: "identify"`
318
- * only — a self-claim/browser must not overwrite another person's response.
327
+ * and `by: "email"` only — a self-claim/browser must not overwrite another
328
+ * person's response.
319
329
  */
320
330
  onRepeat: "keep" | "update";
321
331
  }
@@ -401,16 +411,31 @@ interface FormSchema {
401
411
  pages: FormPage[];
402
412
  settings: FormSettings;
403
413
  }
404
- interface FormTheme {
405
- /** Omit to inherit the host page. Use "auto" to follow the visitor's system directly. */
406
- colorScheme?: "light" | "dark" | "auto";
414
+ /** The colors (and logo) of one appearance. */
415
+ interface FormThemePalette {
407
416
  /** Accent for buttons, focus rings, selection. */
408
417
  primary?: string;
409
418
  background?: string;
410
419
  text?: string;
420
+ /**
421
+ * Logo shown above the form on Fillo-hosted pages: the id of an image
422
+ * uploaded to the workspace. Embedded forms leave branding to the host app.
423
+ */
424
+ logo?: string;
425
+ }
426
+ interface FormTheme extends FormThemePalette {
427
+ /** Omit to inherit the host page. Use "auto" to follow the visitor's system directly. */
428
+ colorScheme?: "light" | "dark" | "auto";
411
429
  /** Border radius scale, e.g. "8px". */
412
430
  radius?: string;
413
431
  fontFamily?: string;
432
+ /**
433
+ * Dark-mode colors, for a form that follows a light/dark setting
434
+ * (`colorScheme` omitted or "auto"). The top-level colors are then the
435
+ * light palette. An unset dark color uses the default dark token; an unset
436
+ * dark logo reuses the top-level logo. Ignored when `colorScheme` is fixed.
437
+ */
438
+ dark?: FormThemePalette;
414
439
  }
415
440
  /** Branding the server tells the SDK to render. Server-owned so the wording /
416
441
  * link can change centrally without a new SDK release. */
@@ -1980,6 +2005,8 @@ declare const FILLO_THEME_VARS: readonly [{
1980
2005
  readonly var: "--fillo-muted";
1981
2006
  }, {
1982
2007
  readonly var: "--fillo-border";
2008
+ }, {
2009
+ readonly var: "--fillo-control-border";
1983
2010
  }, {
1984
2011
  readonly var: "--fillo-control-bg";
1985
2012
  }, {
@@ -2006,6 +2033,30 @@ declare const FILLO_THEME_VARS: readonly [{
2006
2033
  * `[data-fillo-color-scheme="dark"]` CSS, so no new CSS is needed.
2007
2034
  */
2008
2035
  declare function resolveThemeAppearance(theme: FormTheme | null): FormTheme | null;
2036
+ /**
2037
+ * True when the theme switches palettes with the light/dark setting: it has
2038
+ * dark colors and its scheme is not fixed (omitted or "auto"). A dark logo
2039
+ * alone switches nothing.
2040
+ */
2041
+ declare function themeHasDarkPalette(theme: FormTheme | null | undefined): boolean;
2042
+ interface ThemeDomAttributes {
2043
+ /** Inline custom properties for the form root (`--fillo-primary`, …). */
2044
+ vars: Record<string, string>;
2045
+ /** `data-fillo-*` attributes for the form root. */
2046
+ attrs: Record<string, string>;
2047
+ }
2048
+ /**
2049
+ * Turn an untrusted theme into the form root's inline custom properties and
2050
+ * data attributes — one implementation for every renderer.
2051
+ *
2052
+ * A theme with a dark palette (see themeHasDarkPalette) emits each color as
2053
+ * `light-dark(light, dark)`, so it follows the form's own color-scheme: the
2054
+ * host page's when `colorScheme` is omitted, the visitor's device on "auto".
2055
+ * `data-fillo-on-primary` (and `-dark`) name the text tone that reads on a
2056
+ * custom accent; the stylesheet maps them to `--fillo-primary-contrast`, which
2057
+ * stays overridable from consumer CSS.
2058
+ */
2059
+ declare function resolveThemeDom(theme: FormTheme | null | undefined): ThemeDomAttributes | null;
2009
2060
 
2010
2061
  /** Result of syncing a code-defined form: its canonical id, slug, and branding. */
2011
2062
  interface SyncedForm {
@@ -2208,6 +2259,15 @@ declare function isBuildTimeDevEnv(): boolean;
2208
2259
  */
2209
2260
  declare function isLikelyDevEnv(): boolean;
2210
2261
 
2262
+ /** A device's local calendar date as YYYY-MM-DD — the value a date field holds. */
2263
+ declare function localDateString(now?: Date): string;
2264
+ /**
2265
+ * Fill each unanswered date field that starts on today (`defaultToday`). Only
2266
+ * the respondent's device knows their "today" (time zone), so renderers apply
2267
+ * this in the browser after hydration, never on the server. Returns `data`
2268
+ * itself when nothing needed filling.
2269
+ */
2270
+ declare function applyDateDefaults(form: FormSchema, data: ResponseData, now?: Date): ResponseData;
2211
2271
  /**
2212
2272
  * Build initial response data from URL query parameters — Tally-style
2213
2273
  * prefilling. Hidden fields read their configured paramName; every other
@@ -2252,4 +2312,4 @@ declare class Sha1 {
2252
2312
  }
2253
2313
  declare const sha1Base64: (bytes: Uint8Array) => string;
2254
2314
 
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 };
2315
+ 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, applyDateDefaults, 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, localDateString, 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.26.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";
@@ -1560,12 +1560,12 @@ function normalizeUrl(value) {
1560
1560
  function normalizeResponseLimit(value) {
1561
1561
  if (!value || typeof value !== "object") return void 0;
1562
1562
  const rec = value;
1563
- const by = rec.by === "browser" || rec.by === "field" || rec.by === "identify" ? rec.by : void 0;
1563
+ const by = rec.by === "browser" || rec.by === "field" || rec.by === "identify" || rec.by === "email" ? rec.by : void 0;
1564
1564
  if (!by) return void 0;
1565
1565
  const field2 = by === "field" ? optionalStr(rec.field, 128) : void 0;
1566
1566
  if (by === "field" && !field2) return void 0;
1567
1567
  const scopeField = optionalStr(rec.scopeField, 128);
1568
- const onRepeat = rec.onRepeat === "update" && by === "identify" ? "update" : "keep";
1568
+ const onRepeat = rec.onRepeat === "update" && (by === "identify" || by === "email") ? "update" : "keep";
1569
1569
  return {
1570
1570
  by,
1571
1571
  ...field2 ? { field: field2 } : {},
@@ -1675,8 +1675,12 @@ function normalizeBlock(input) {
1675
1675
  });
1676
1676
  }
1677
1677
  case "signature":
1678
- case "date":
1679
1678
  return cleanObject(baseField(rec, kind));
1679
+ case "date":
1680
+ return cleanObject({
1681
+ ...baseField(rec, kind),
1682
+ defaultToday: rec.defaultToday === true ? true : void 0
1683
+ });
1680
1684
  case "rating": {
1681
1685
  const max = int(rec.max, 1, 10, 5);
1682
1686
  return cleanObject({
@@ -1893,17 +1897,30 @@ function safeCssToken(value, max) {
1893
1897
  if (/[;{}<>]/.test(token) || /url\s*\(/i.test(token)) return void 0;
1894
1898
  return token;
1895
1899
  }
1900
+ function themeAssetId(value) {
1901
+ return typeof value === "string" && /^[A-Za-z0-9_-]{8,64}$/.test(value) ? value : void 0;
1902
+ }
1903
+ function normalizeThemePalette(input) {
1904
+ if (!input || typeof input !== "object" || Array.isArray(input)) return void 0;
1905
+ const rec = input;
1906
+ const palette = cleanObject({
1907
+ primary: safeCssToken(rec.primary, 200),
1908
+ background: safeCssToken(rec.background, 200),
1909
+ text: safeCssToken(rec.text, 200),
1910
+ logo: themeAssetId(rec.logo)
1911
+ });
1912
+ return Object.keys(palette).length > 0 ? palette : void 0;
1913
+ }
1896
1914
  function normalizeFormTheme(input) {
1897
1915
  if (!input || typeof input !== "object" || Array.isArray(input)) return null;
1898
1916
  const rec = input;
1899
1917
  const colorScheme = rec.colorScheme === "light" || rec.colorScheme === "dark" || rec.colorScheme === "auto" ? rec.colorScheme : void 0;
1900
1918
  const theme = cleanObject({
1901
1919
  colorScheme,
1902
- primary: safeCssToken(rec.primary, 200),
1903
- background: safeCssToken(rec.background, 200),
1904
- text: safeCssToken(rec.text, 200),
1920
+ ...normalizeThemePalette(rec),
1905
1921
  radius: safeCssToken(rec.radius, 64),
1906
- fontFamily: safeCssToken(rec.fontFamily, 200)
1922
+ fontFamily: safeCssToken(rec.fontFamily, 200),
1923
+ dark: normalizeThemePalette(rec.dark)
1907
1924
  });
1908
1925
  return Object.keys(theme).length > 0 ? theme : null;
1909
1926
  }
@@ -3228,6 +3245,20 @@ function parseDecimal(raw) {
3228
3245
  const n = Number(s);
3229
3246
  return Number.isFinite(n) ? n : null;
3230
3247
  }
3248
+ function localDateString(now = /* @__PURE__ */ new Date()) {
3249
+ const month = String(now.getMonth() + 1).padStart(2, "0");
3250
+ const day = String(now.getDate()).padStart(2, "0");
3251
+ return `${now.getFullYear()}-${month}-${day}`;
3252
+ }
3253
+ function applyDateDefaults(form, data, now = /* @__PURE__ */ new Date()) {
3254
+ let next = null;
3255
+ for (const field2 of allFields(form)) {
3256
+ if (field2.kind !== "date" || !field2.defaultToday || data[field2.id] !== void 0) continue;
3257
+ next = next ?? { ...data };
3258
+ next[field2.id] = localDateString(now);
3259
+ }
3260
+ return next ?? data;
3261
+ }
3231
3262
  function prefillFromParams(form, params) {
3232
3263
  const data = {};
3233
3264
  for (const field2 of allFields(form)) {
@@ -3505,6 +3536,11 @@ function createFormController(options2) {
3505
3536
  notify();
3506
3537
  }
3507
3538
  }
3539
+ const withDates = applyDateDefaults(form, data);
3540
+ if (withDates !== data && status === "idle") {
3541
+ data = computeCalculated(form, withDates);
3542
+ notify();
3543
+ }
3508
3544
  if (status === "idle" && hasSubmittedOnce(form, formId, data)) {
3509
3545
  status = "submitted";
3510
3546
  restoredSubmission = true;
@@ -4040,6 +4076,7 @@ function createFormController(options2) {
4040
4076
  if (data[key] === void 0) data[key] = value;
4041
4077
  }
4042
4078
  }
4079
+ data = applyDateDefaults(form, data);
4043
4080
  data = computeCalculated(form, data);
4044
4081
  errors = {};
4045
4082
  pageIndex = 0;
@@ -4194,6 +4231,7 @@ var FILLO_THEME_VARS = [
4194
4231
  { var: "--fillo-font", themeProp: "fontFamily" },
4195
4232
  { var: "--fillo-muted" },
4196
4233
  { var: "--fillo-border" },
4234
+ { var: "--fillo-control-border" },
4197
4235
  { var: "--fillo-control-bg" },
4198
4236
  { var: "--fillo-error" },
4199
4237
  { var: "--fillo-primary-contrast" }
@@ -4222,13 +4260,71 @@ function relativeLuminance(token) {
4222
4260
  return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
4223
4261
  }
4224
4262
  function resolveThemeAppearance(theme) {
4225
- if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark" || !theme.background) {
4263
+ if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark" || !theme.background || // A form with dark colors of its own follows the light/dark setting.
4264
+ themeHasDarkPalette(theme)) {
4226
4265
  return theme;
4227
4266
  }
4228
4267
  const luminance = relativeLuminance(theme.background);
4229
4268
  if (luminance == null) return theme;
4230
4269
  return { ...theme, colorScheme: luminance > DARK_LUMINANCE_THRESHOLD ? "light" : "dark" };
4231
4270
  }
4271
+ function themeHasDarkPalette(theme) {
4272
+ if (!theme || theme.colorScheme === "light" || theme.colorScheme === "dark") return false;
4273
+ const dark = theme.dark;
4274
+ return Boolean(dark && (dark.primary || dark.background || dark.text));
4275
+ }
4276
+ var ON_LIGHT_TEXT = "#18181b";
4277
+ function onColorTone(color) {
4278
+ const luminance = relativeLuminance(color);
4279
+ if (luminance == null) return null;
4280
+ const withWhite = 1.05 / (luminance + 0.05);
4281
+ const withDark = (luminance + 0.05) / ((relativeLuminance(ON_LIGHT_TEXT) ?? 0) + 0.05);
4282
+ return withWhite >= withDark ? "light" : "dark";
4283
+ }
4284
+ var PALETTE_VARS = [
4285
+ ["primary", "--fillo-primary"],
4286
+ ["background", "--fillo-bg"],
4287
+ ["text", "--fillo-text"]
4288
+ ];
4289
+ var DEFAULT_PALETTE = {
4290
+ light: { primary: "#18181b", background: "transparent", text: "#18181b" },
4291
+ dark: { primary: "#fafafa", background: "transparent", text: "#f4f4f5" }
4292
+ };
4293
+ function resolveThemeDom(theme) {
4294
+ const safe = resolveThemeAppearance(normalizeFormTheme(theme));
4295
+ if (!safe) return null;
4296
+ const vars = {};
4297
+ const attrs = {};
4298
+ if (safe.colorScheme) attrs["data-fillo-color-scheme"] = safe.colorScheme;
4299
+ const dark = themeHasDarkPalette(safe) ? safe.dark : void 0;
4300
+ for (const [key, name] of PALETTE_VARS) {
4301
+ const light = safe[key];
4302
+ if (!dark) {
4303
+ if (light) vars[name] = light;
4304
+ continue;
4305
+ }
4306
+ const darkValue = dark[key];
4307
+ if (!light && !darkValue) continue;
4308
+ vars[name] = `light-dark(${light ?? DEFAULT_PALETTE.light[key]}, ${darkValue ?? DEFAULT_PALETTE.dark[key]})`;
4309
+ }
4310
+ if (safe.radius) vars["--fillo-radius"] = safe.radius;
4311
+ if (safe.fontFamily) vars["--fillo-font"] = safe.fontFamily;
4312
+ if (dark) {
4313
+ const lightTone = onColorTone(safe.primary ?? DEFAULT_PALETTE.light.primary);
4314
+ const darkTone = onColorTone(dark.primary ?? DEFAULT_PALETTE.dark.primary);
4315
+ if (lightTone && darkTone) {
4316
+ attrs["data-fillo-on-primary"] = lightTone;
4317
+ attrs["data-fillo-on-primary-dark"] = darkTone;
4318
+ }
4319
+ } else if (safe.primary) {
4320
+ const tone = onColorTone(safe.primary);
4321
+ if (tone) attrs["data-fillo-on-primary"] = tone;
4322
+ }
4323
+ if (safe.primary || safe.background || safe.text || dark) {
4324
+ attrs["data-fillo-has-color-theme"] = "";
4325
+ }
4326
+ return { vars, attrs };
4327
+ }
4232
4328
 
4233
4329
  // src/define.ts
4234
4330
  function defineForm(def) {
@@ -4418,7 +4514,7 @@ var JSX_BLOCK_SPECS = [
4418
4514
  field("Ranking", "ranking", ["options"], { optionChildren: true }),
4419
4515
  field("Matrix", "matrix", ["rows", "columns"]),
4420
4516
  field("Signature", "signature", []),
4421
- field("Date", "date", []),
4517
+ field("Date", "date", ["defaultToday"]),
4422
4518
  field("FileUpload", "file_upload", ["maxFiles", "maxFileSizeMb", "accept"]),
4423
4519
  field("Hidden", "hidden", ["paramName", "defaultValue"]),
4424
4520
  field("Calculated", "calculated", ["calc", "decimals", "prefix", "suffix"]),
@@ -4811,6 +4907,7 @@ export {
4811
4907
  REQUIRED_FIELD_MESSAGE,
4812
4908
  Sha1,
4813
4909
  allFields,
4910
+ applyDateDefaults,
4814
4911
  assembleForm,
4815
4912
  codeFormFromJsx,
4816
4913
  computeCalculated,
@@ -4842,6 +4939,7 @@ export {
4842
4939
  isPossiblePhone,
4843
4940
  isTerminalPage,
4844
4941
  isValidPartialNumberText,
4942
+ localDateString,
4845
4943
  localeForNotation,
4846
4944
  needsExplicitSubmit,
4847
4945
  normalizeFormSchema,
@@ -4864,6 +4962,7 @@ export {
4864
4962
  resolveStrings,
4865
4963
  resolveText,
4866
4964
  resolveThemeAppearance,
4965
+ resolveThemeDom,
4867
4966
  respondentErrorStringsFor,
4868
4967
  responseScopeValue,
4869
4968
  schemaFromJsx,
@@ -4871,6 +4970,7 @@ export {
4871
4970
  shouldAutoSubmit,
4872
4971
  slotClass,
4873
4972
  syncCodeForm,
4973
+ themeHasDarkPalette,
4874
4974
  toE164,
4875
4975
  validateField,
4876
4976
  validateFormSchema,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefillo/core",
3
- "version": "0.24.1",
3
+ "version": "0.26.0",
4
4
  "description": "Form schema, validation, logic engine and JS client for Fillo. Framework-agnostic.",
5
5
  "license": "MIT",
6
6
  "keywords": [