@ai-matrx/design-system 0.21.14 → 0.22.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/data-table/copy-helpers.d.cts +2 -1
  3. package/dist/data-table/copy-helpers.d.ts +2 -1
  4. package/dist/data-table/filter-engine.d.cts +2 -1
  5. package/dist/data-table/filter-engine.d.ts +2 -1
  6. package/dist/data-table/host.d.cts +2 -1
  7. package/dist/data-table/host.d.ts +2 -1
  8. package/dist/data-table/index.cjs +1525 -398
  9. package/dist/data-table/index.cjs.map +1 -1
  10. package/dist/data-table/index.d.cts +91 -3
  11. package/dist/data-table/index.d.ts +91 -3
  12. package/dist/data-table/index.js +1526 -395
  13. package/dist/data-table/index.js.map +1 -1
  14. package/dist/data-table/infer-filter.d.cts +2 -1
  15. package/dist/data-table/infer-filter.d.ts +2 -1
  16. package/dist/data-table/layered-filters.d.cts +2 -1
  17. package/dist/data-table/layered-filters.d.ts +2 -1
  18. package/dist/data-table/query-control.d.cts +2 -1
  19. package/dist/data-table/query-control.d.ts +2 -1
  20. package/dist/data-table/types.cjs.map +1 -1
  21. package/dist/data-table/types.d.cts +2 -1
  22. package/dist/data-table/types.d.ts +2 -1
  23. package/dist/data-table/url-state.d.cts +2 -1
  24. package/dist/data-table/url-state.d.ts +2 -1
  25. package/dist/data-table/xlsx.d.cts +2 -1
  26. package/dist/data-table/xlsx.d.ts +2 -1
  27. package/dist/field-formats/index.cjs +741 -0
  28. package/dist/field-formats/index.cjs.map +1 -0
  29. package/dist/field-formats/index.d.cts +45 -0
  30. package/dist/field-formats/index.d.ts +45 -0
  31. package/dist/field-formats/index.js +723 -0
  32. package/dist/field-formats/index.js.map +1 -0
  33. package/dist/types-BdbvlF9-.d.cts +204 -0
  34. package/dist/types-BdbvlF9-.d.ts +204 -0
  35. package/dist/{types-emLxze_m.d.ts → types-D-flTpK0.d.ts} +230 -1
  36. package/dist/{types-BgqIKbhq.d.cts → types-Sn249eoy.d.cts} +230 -1
  37. package/package.json +11 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/field-formats/index.ts","../../src/field-formats/registry.ts","../../src/field-formats/format.ts"],"sourcesContent":["/**\n * THE platform field-format registry — the canonical home.\n *\n * A **format** is a UI-layer semantic type layered on top of a plain storage\n * type: `currency` is not a database type, it is a `number` the UI knows to\n * display as `$1,234.56`, right-align, and edit with a currency-aware input.\n *\n * WHY IT LIVES IN THE DESIGN SYSTEM. This vocabulary was written for one app's\n * `/data` route and then every other table in the platform had to invent its\n * own money column. It is the data table's own vocabulary, it is pure (no\n * React, no DOM, no network, one dependency on `@ai-matrx/kit/format`), and a\n * second copy of it anywhere is a fork. `MatrxColumnDef.format` takes exactly\n * this `FieldFormatConfig`, so every consumer of the primitive — the record\n * store's Grid, an admin table, the app's own `/data` — reads one table of\n * formats and gets the same money, the same percent and the same date.\n *\n * THE FALLBACK LAW — a format may never blank a cell or throw. When the stored\n * value does not fit the declared format, `formatFieldValue` returns\n * `ok: false` together with the BASE-TYPE rendering, and the table shows that\n * in amber so the person can SEE something does not fit instead of staring at\n * an empty cell. Formats are always additive and always non-breaking: strip the\n * format and you are back to exactly what the database holds.\n *\n * Choice option RESOLUTION (hydrating options from a shared pick list) is not\n * here, because it needs a network and an app's own list feature. A caller that\n * has resolved its options passes them as `options.choices`; that is all this\n * module ever reads.\n */\n\nexport * from \"./types\";\nexport {\n FIELD_FORMATS,\n FIELD_FORMAT_LIST,\n FIELD_FORMAT_IDS,\n getFieldFormat,\n defaultFormatForBase,\n formatsForBase,\n groupedFormatsForBase,\n} from \"./registry\";\nexport {\n readFieldFormatConfig,\n resolveFieldFormat,\n formatFieldValue,\n valueFitsFormat,\n parseFieldInput,\n} from \"./format\";\n","/**\n * The format registry. ONE table — add a row here and every consumer (data\n * tables, scope context items, anything that adopts `FieldFormatId`) gets it.\n *\n * Every `format()` returns `null` rather than throwing or returning \"\" when the\n * value does not fit — that is what triggers THE FALLBACK LAW in `format.ts`.\n */\n// THE package duration formatter (`@ai-matrx/kit/format`, census H1\n// 2026-09-07). THE UNIT LAW: the unit is in the name, because the fleet's\n// ~35 twins variously took ms, seconds and minutes behind one signature.\nimport {\n formatDurationSeconds,\n formatFileSize,\n formatRelativeTime,\n} from \"@ai-matrx/kit/format\";\n\nimport type {\n FieldChoice,\n FieldFormatDef,\n FieldFormatId,\n FieldFormatOptions,\n FieldBaseType,\n} from \"./types\";\n\n/**\n * A stored `interval` is the one place a NEGATIVE duration is a real quantity\n * rather than a clock bug, so this field format opts into the package's\n * `signed` mode. Everything else about the reading is the package's clock.\n */\nfunction formatSignedDuration(totalSeconds: number): string {\n return formatDurationSeconds(totalSeconds, { signed: true });\n}\n\n// ─── shared helpers ──────────────────────────────────────────────────────────\n\n/** Coerce to a finite number, or null. Accepts numeric strings (\"1,234\", \"$5\"). */\nfunction toNumber(value: unknown): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"string\") {\n const cleaned = value.replace(/[,\\s$€£¥%]/g, \"\");\n if (cleaned === \"\") return null;\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n }\n return null;\n}\n\nfunction toText(value: unknown): string | null {\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\")\n return String(value);\n return null;\n}\n\nfunction toDate(value: unknown): Date | null {\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n if (typeof value === \"string\" && value.trim()) {\n const d = new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n }\n return null;\n}\n\nfunction toList(value: unknown): unknown[] | null {\n if (Array.isArray(value)) return value;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (trimmed.startsWith(\"[\")) {\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (Array.isArray(parsed)) return parsed;\n } catch {\n /* fall through to comma split */\n }\n }\n if (trimmed === \"\") return [];\n return trimmed.split(\",\").map((s) => s.trim());\n }\n return null;\n}\n\nfunction groupedNumber(n: number, options: FieldFormatOptions): string {\n return new Intl.NumberFormat(undefined, {\n useGrouping: options.useGrouping !== false,\n ...(options.precision != null\n ? {\n minimumFractionDigits: options.precision,\n maximumFractionDigits: options.precision,\n }\n : { maximumFractionDigits: 10 }),\n }).format(n);\n}\n\nfunction affix(text: string, options: FieldFormatOptions): string {\n return `${options.prefix ?? \"\"}${text}${options.suffix ?? \"\"}`;\n}\n\nconst DURATION_TO_SECONDS: Record<\n NonNullable<FieldFormatOptions[\"durationUnit\"]>,\n number\n> = { milliseconds: 0.001, seconds: 1, minutes: 60, hours: 3600 };\n\n/**\n * Find the option a stored value corresponds to.\n *\n * Case-insensitive on purpose: a column adopting a choice format almost always\n * has \"Active\" and \"active\" in it already, and calling one of them a mismatch\n * would be pedantry, not help. Exact match always wins.\n */\nfunction findChoice(\n choices: FieldChoice[] | undefined,\n value: string,\n): FieldChoice | undefined {\n if (!choices || choices.length === 0) return undefined;\n const exact = choices.find((c) => c.value === value);\n if (exact) return exact;\n const lowered = value.toLowerCase();\n return choices.find((c) => c.value.toLowerCase() === lowered);\n}\n\nconst EMAIL_RE = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n/** Explicit scheme, OR a host with a real dot-TLD. No whitespace either way. */\nconst URL_RE =\n /^(?:[a-z][a-z0-9+.-]*:\\/\\/\\S+|(?:[a-z0-9-]+\\.)+[a-z]{2,}(?:[:/?#]\\S*)?)$/i;\nconst HEX_RE = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;\n\n// ─── the registry ────────────────────────────────────────────────────────────\n\nconst DEFS: FieldFormatDef[] = [\n // ── Text ──────────────────────────────────────────────────────────────────\n {\n id: \"text\",\n label: \"Text\",\n description: \"Plain single-line text\",\n group: \"Text\",\n base: \"string\",\n editor: \"text\",\n // Deliberately no optionKeys — plain text is the default for every string\n // column, and giving it prefix/suffix controls puts an options row under\n // every column in the settings dialog for a setting almost nobody wants.\n format: (v, o) => {\n const t = toText(v);\n return t === null ? null : affix(t, o);\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"long_text\",\n label: \"Long text\",\n description: \"Multi-line text, edited in a larger box\",\n group: \"Text\",\n base: \"string\",\n editor: \"textarea\",\n format: (v) => toText(v),\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"markdown\",\n label: \"Markdown\",\n description: \"Rich text written as Markdown\",\n group: \"Text\",\n base: \"string\",\n editor: \"textarea\",\n rich: true,\n format: (v) => toText(v),\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"email\",\n label: \"Email\",\n description: \"Email address — click to compose\",\n group: \"Text\",\n base: \"string\",\n editor: \"email\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n return EMAIL_RE.test(t) ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"url\",\n label: \"Link\",\n description: \"Web address — click to open\",\n group: \"Text\",\n base: \"string\",\n editor: \"url\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n // `new URL()` alone is uselessly permissive here: `https://Beijing` and\n // even `https://Washington, D.C.` parse successfully, so every text value\n // would render as a broken link instead of flagging as a mismatch.\n // Require no whitespace, and either an explicit scheme or a real\n // dot-TLD host. Bare domains stay accepted; https is added on click.\n if (/\\s/.test(t)) return null;\n if (!URL_RE.test(t)) return null;\n try {\n new URL(t.includes(\"://\") ? t : `https://${t}`);\n return t;\n } catch {\n return null;\n }\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"phone\",\n label: \"Phone\",\n description: \"Phone number — click to call\",\n group: \"Text\",\n base: \"string\",\n editor: \"tel\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n // Must contain at least 7 digits to be a plausible phone number.\n return (t.match(/\\d/g) ?? []).length >= 7 ? t : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n {\n id: \"color\",\n label: \"Color\",\n description: \"Hex color with a swatch\",\n group: \"Text\",\n base: \"string\",\n editor: \"color\",\n rich: true,\n format: (v) => {\n const t = toText(v)?.trim();\n if (!t) return null;\n return HEX_RE.test(t) ? t.toLowerCase() : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw).trim()),\n },\n\n // ── Numbers ───────────────────────────────────────────────────────────────\n {\n id: \"number\",\n label: \"Number\",\n description: \"Plain number\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"useGrouping\", \"precision\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n return n === null ? null : affix(groupedNumber(n, o), o);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"decimal\",\n label: \"Decimal\",\n description: \"Number with fixed decimal places\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"precision\", \"useGrouping\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return affix(groupedNumber(n, { ...o, precision: o.precision ?? 2 }), o);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"currency\",\n label: \"Currency\",\n description: \"Money — stored as a plain number, shown with a symbol\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"currency\", \"precision\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const currency = (o.currency || \"USD\").toUpperCase();\n try {\n return new Intl.NumberFormat(undefined, {\n style: \"currency\",\n currency,\n ...(o.precision != null\n ? {\n minimumFractionDigits: o.precision,\n maximumFractionDigits: o.precision,\n }\n : {}),\n }).format(n);\n } catch {\n // Unknown/invalid ISO code — still show the money, never blank it.\n return `${groupedNumber(n, { precision: o.precision ?? 2 })} ${currency}`;\n }\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"percent\",\n label: \"Percent\",\n description: \"Percentage — stored as a plain number\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"percentScale\", \"precision\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const shown = o.percentScale === \"fraction\" ? n * 100 : n;\n return `${groupedNumber(shown, o.precision === undefined ? {} : { precision: o.precision })}%`;\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"duration\",\n label: \"Duration\",\n description: \"Length of time, shown as h:mm:ss\",\n group: \"Numbers\",\n base: \"number\",\n alsoAccepts: [\"integer\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"durationUnit\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return formatSignedDuration(n * DURATION_TO_SECONDS[o.durationUnit ?? \"seconds\"]);\n },\n parse: (raw) => toNumber(raw),\n },\n {\n id: \"integer\",\n label: \"Whole number\",\n description: \"Integer with no decimal part\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"number\",\n numericAlign: true,\n optionKeys: [\"useGrouping\", \"prefix\", \"suffix\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n return affix(groupedNumber(Math.trunc(n), { ...o, precision: 0 }), o);\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.trunc(n);\n },\n },\n {\n id: \"rating\",\n label: \"Rating\",\n description: \"Score out of a maximum, shown as stars\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"rating\",\n rich: true,\n optionKeys: [\"ratingMax\"],\n format: (v, o) => {\n const n = toNumber(v);\n if (n === null) return null;\n const max = o.ratingMax ?? 5;\n if (n < 0 || n > max) return null;\n return `${n} / ${max}`;\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.round(n);\n },\n },\n {\n id: \"file_size\",\n label: \"File size\",\n description: \"Byte count shown as KB / MB / GB\",\n group: \"Numbers\",\n base: \"integer\",\n alsoAccepts: [\"number\"],\n editor: \"number\",\n numericAlign: true,\n format: (v) => {\n const n = toNumber(v);\n return n === null ? null : formatFileSize(n);\n },\n parse: (raw) => {\n const n = toNumber(raw);\n return n === null ? null : Math.trunc(n);\n },\n },\n\n // ── Choice ────────────────────────────────────────────────────────────────\n {\n id: \"boolean\",\n label: \"Yes / No\",\n description: \"True or false\",\n group: \"Choice\",\n base: \"boolean\",\n editor: \"checkbox\",\n format: (v) => {\n if (typeof v === \"boolean\") return v ? \"Yes\" : \"No\";\n if (v === \"true\" || v === 1) return \"Yes\";\n if (v === \"false\" || v === 0) return \"No\";\n return null;\n },\n parse: (raw) => {\n if (typeof raw === \"boolean\") return raw;\n if (raw == null || raw === \"\") return null;\n return String(raw).toLowerCase() === \"true\" || raw === 1;\n },\n },\n\n // ── Dates ─────────────────────────────────────────────────────────────────\n {\n id: \"date\",\n label: \"Date\",\n description: \"Calendar date\",\n group: \"Dates\",\n base: \"date\",\n alsoAccepts: [\"datetime\", \"string\"],\n editor: \"date\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => {\n const d = toDate(v);\n if (!d) return null;\n return d.toLocaleDateString(undefined, {\n dateStyle: o.dateStyle ?? \"medium\",\n });\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"datetime\",\n label: \"Date & time\",\n description: \"Calendar date with a time of day\",\n group: \"Dates\",\n base: \"datetime\",\n alsoAccepts: [\"date\", \"string\"],\n editor: \"datetime\",\n optionKeys: [\"dateStyle\"],\n format: (v, o) => {\n const d = toDate(v);\n if (!d) return null;\n return d.toLocaleString(undefined, {\n dateStyle: o.dateStyle ?? \"medium\",\n timeStyle: \"short\",\n });\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n {\n id: \"relative_time\",\n label: \"Relative time\",\n description: 'Shown as \"3 days ago\"',\n group: \"Dates\",\n base: \"datetime\",\n alsoAccepts: [\"date\", \"string\"],\n editor: \"datetime\",\n format: (v) => {\n const d = toDate(v);\n // THE one relative stamp (@ai-matrx/kit/format). This registry carried\n // its own Intl ladder until 2026-09-12 — the last re-implementation of\n // formatRelativeTime in this repo. It ROUNDED, so a 1h50m-old row read\n // \"2 hours ago\" here and \"1h ago\" on every other surface in the product;\n // the package truncates, which is what the rest of the fleet shows.\n return d ? formatRelativeTime(d, { style: \"intl\" }) : null;\n },\n parse: (raw) => (raw === \"\" || raw == null ? null : String(raw)),\n },\n\n // ── Structured ────────────────────────────────────────────────────────────\n {\n id: \"json\",\n label: \"JSON\",\n description: \"Structured object\",\n group: \"Structured\",\n base: \"json\",\n alsoAccepts: [\"array\"],\n editor: \"json\",\n format: (v) => {\n if (v === null || v === undefined) return null;\n if (typeof v === \"object\") return JSON.stringify(v);\n return toText(v);\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (typeof raw !== \"string\") return raw;\n try {\n return JSON.parse(raw) as unknown;\n } catch {\n return raw;\n }\n },\n },\n {\n id: \"array\",\n label: \"List\",\n description: \"List of values\",\n group: \"Structured\",\n base: \"array\",\n alsoAccepts: [\"json\"],\n editor: \"json\",\n format: (v) => {\n const list = toList(v);\n return list === null ? null : list.map((i) => String(i)).join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n {\n id: \"tags\",\n label: \"Tags\",\n description: \"List of values shown as chips\",\n group: \"Structured\",\n base: \"array\",\n alsoAccepts: [\"json\", \"string\"],\n editor: \"json\",\n rich: true,\n format: (v) => {\n const list = toList(v);\n return list === null ? null : list.map((i) => String(i)).join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n\n // ─── Choice ────────────────────────────────────────────────────────────────\n //\n // An enum WITHOUT a database enum. The column stays `string` (or `array` for\n // multi), the database enforces nothing, and stripping the format leaves\n // every stored value exactly as it was. Options are a UI layer — the same\n // trick `percent` plays on a number.\n //\n // The fallback law is the whole reason this is safe to switch on over live\n // data: a value not in the option list is NOT an error and is NEVER blanked.\n // `format()` returns null for it, `<FormattedFieldValue>` renders it in amber\n // with the reason, and the user can fix it or adopt it. Declaring the options\n // is therefore how you FIND your stray values, not how you lose them.\n //\n // Options resolve through `choices.ts` — inline `options.choices`, or a\n // structured list via `options.structuredList`. This def only ever sees\n // whatever the caller resolved into `options.choices`, so it stays pure and\n // synchronous like every other format.\n {\n id: \"choice\",\n label: \"Choice\",\n description: \"One value from a list of options\",\n group: \"Choice\",\n base: \"string\",\n alsoAccepts: [\"number\", \"integer\", \"boolean\"],\n editor: \"select\",\n rich: true,\n optionKeys: [\"choices\", \"structuredList\", \"allowOther\"],\n format: (v, options) => {\n const text = toText(v);\n if (text === null) return null;\n const trimmed = text.trim();\n if (trimmed === \"\") return \"\";\n const match = findChoice(options.choices, trimmed);\n // No options resolved yet (a structured list still loading, or a column\n // whose list is unreachable) — show the raw value rather than accusing\n // every cell of being off-list.\n if (!options.choices || options.choices.length === 0) return trimmed;\n return match ? (match.label ?? match.value) : null;\n },\n parse: (raw) => {\n if (raw == null) return null;\n const text = toText(raw);\n if (text === null) return raw;\n const trimmed = text.trim();\n return trimmed === \"\" ? null : trimmed;\n },\n },\n {\n id: \"multi_choice\",\n label: \"Multi-choice\",\n description: \"Any number of values from a list of options\",\n group: \"Choice\",\n base: \"array\",\n alsoAccepts: [\"string\", \"json\"],\n editor: \"multiselect\",\n rich: true,\n optionKeys: [\"choices\", \"structuredList\", \"allowOther\"],\n format: (v, options) => {\n const list = toList(v);\n if (list === null) return null;\n if (list.length === 0) return \"\";\n const labels: string[] = [];\n let anyOffList = false;\n for (const entry of list) {\n const text = toText(entry);\n if (text === null) {\n anyOffList = true;\n continue;\n }\n const match = findChoice(options.choices, text.trim());\n if (match) labels.push(match.label ?? match.value);\n else {\n labels.push(text.trim());\n anyOffList = true;\n }\n }\n // Same leniency as `choice`: with no options resolved, nothing is off-list.\n const haveOptions = !!options.choices && options.choices.length > 0;\n if (haveOptions && anyOffList) return null;\n return labels.join(\", \");\n },\n parse: (raw) => {\n if (raw == null || raw === \"\") return null;\n if (Array.isArray(raw)) return raw;\n return toList(raw) ?? raw;\n },\n },\n\n // ─── Computed ──────────────────────────────────────────────────────────────\n //\n // A formula column stores NOTHING. `metadata.format.options.formula` holds\n // the expression; the stored cell is always null; the value is computed on\n // read from the other columns of the same row\n // (`features/data-tables/formulas.ts`). Grouped under Structured on purpose\n // — the picker's groups are a fixed vocabulary, and a formula is structure\n // over a plain string column, not a sixth family.\n //\n // `format()` here is handed the COMPUTED value, never a stored one, and its\n // only job is to render it: through `resultFormat`'s own `format()` when the\n // column declares one (a computed total shown as Currency, a computed date\n // shown as a Date), and plainly otherwise. It never returns null for an\n // empty value, because a formula legitimately produces BLANK.\n //\n // No `optionKeys`: the expression is authored in its own editor, not in the\n // generic option rail, and listing a key the picker has no control for would\n // open an empty options section under every formula column.\n {\n id: \"formula\",\n label: \"Formula\",\n description: \"Computed from the other columns in the row — never typed in\",\n group: \"Structured\",\n base: \"string\",\n editor: \"computed\",\n format: (v, o) => {\n const target = o.formula?.resultFormat;\n if (target && target !== \"formula\") {\n const delegate = FIELD_FORMATS[target];\n if (delegate) {\n const rendered = delegate.format(v, o);\n if (rendered !== null) return rendered;\n }\n }\n if (v === null || v === undefined) return \"\";\n if (typeof v === \"boolean\") return v ? \"Yes\" : \"No\";\n if (typeof v === \"number\") return groupedNumber(v, o);\n return toText(v);\n },\n // Nothing is ever stored in a formula column. Returning the raw input here\n // would be the one way a computed cell could acquire a value of its own.\n parse: () => null,\n },\n];\n\nexport const FIELD_FORMATS: Readonly<Record<FieldFormatId, FieldFormatDef>> =\n Object.freeze(\n Object.fromEntries(DEFS.map((d) => [d.id, d])) as Record<\n FieldFormatId,\n FieldFormatDef\n >,\n );\n\nexport const FIELD_FORMAT_LIST: readonly FieldFormatDef[] = Object.freeze(DEFS);\n\nexport const FIELD_FORMAT_IDS = DEFS.map((d) => d.id) as FieldFormatId[];\n\nexport function getFieldFormat(\n id: string | null | undefined,\n): FieldFormatDef | null {\n if (!id) return null;\n return FIELD_FORMATS[id as FieldFormatId] ?? null;\n}\n\n/**\n * The format a column gets when it declares none — the identity format for its\n * storage type. Every existing column therefore behaves exactly as before.\n */\nexport function defaultFormatForBase(base: string): FieldFormatId {\n switch (base as FieldBaseType) {\n case \"number\":\n return \"number\";\n case \"integer\":\n return \"integer\";\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"date\";\n case \"datetime\":\n return \"datetime\";\n case \"json\":\n return \"json\";\n case \"array\":\n return \"array\";\n default:\n return \"text\";\n }\n}\n\n/** Formats that can legally sit on a given storage type. */\nexport function formatsForBase(base: string): FieldFormatDef[] {\n return DEFS.filter(\n (d) => d.base === base || (d.alsoAccepts ?? []).includes(base as FieldBaseType),\n );\n}\n\n/** Group the pickable formats for a base type, preserving registry order. */\nexport function groupedFormatsForBase(\n base: string,\n): { group: FieldFormatDef[\"group\"]; formats: FieldFormatDef[] }[] {\n const out: { group: FieldFormatDef[\"group\"]; formats: FieldFormatDef[] }[] =\n [];\n for (const def of formatsForBase(base)) {\n const bucket = out.find((g) => g.group === def.group);\n if (bucket) bucket.formats.push(def);\n else out.push({ group: def.group, formats: [def] });\n }\n return out;\n}\n","/**\n * THE FALLBACK LAW, implemented.\n *\n * `formatFieldValue` never blanks a cell and never throws. When the declared\n * format cannot interpret the stored value it returns `ok: false` with the\n * **base-type** rendering of that same value plus a human `reason`. The UI\n * renders that text in amber (see `<FormattedFieldValue>`), so a mismatch is\n * visible instead of invisible.\n */\nimport {\n defaultFormatForBase,\n getFieldFormat,\n FIELD_FORMATS,\n} from \"./registry\";\nimport type {\n FieldFormatConfig,\n FieldFormatOptions,\n FormatResult,\n} from \"./types\";\n\nfunction isEmpty(value: unknown): boolean {\n return value === null || value === undefined || value === \"\";\n}\n\n/** Last-resort rendering — the raw value, readable, never empty-by-accident. */\nfunction baseText(value: unknown): string {\n if (isEmpty(value)) return \"\";\n if (typeof value === \"object\") {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n }\n return String(value);\n}\n\n/**\n * Read a field's declared format off its `metadata` JSONB. Returns null when\n * the field declares none (or declares one we don't know) — the caller then\n * falls back to the storage type's identity format, so an unknown or removed\n * format id degrades to plain, never to broken.\n */\nexport function readFieldFormatConfig(\n metadata: unknown,\n): FieldFormatConfig | null {\n if (!metadata || typeof metadata !== \"object\") return null;\n const raw = (metadata as Record<string, unknown>).format;\n if (!raw || typeof raw !== \"object\") return null;\n const id = (raw as Record<string, unknown>).id;\n if (typeof id !== \"string\" || !getFieldFormat(id)) return null;\n const options = (raw as Record<string, unknown>).options;\n // `exactOptionalPropertyTypes`: an absent `options` is the key being ABSENT,\n // never the key holding `undefined` — the two are different documents once\n // this config is written back out as JSON.\n return {\n id: id as FieldFormatConfig[\"id\"],\n ...(options && typeof options === \"object\"\n ? { options: options as FieldFormatOptions }\n : {}),\n };\n}\n\n/**\n * Resolve the format a field actually renders with: its declared one, or the\n * identity format for its storage type.\n */\nexport function resolveFieldFormat(\n dataType: string,\n metadata: unknown,\n): FieldFormatConfig {\n const declared = readFieldFormatConfig(metadata);\n if (declared) return declared;\n return { id: defaultFormatForBase(dataType) };\n}\n\nexport function formatFieldValue(\n value: unknown,\n config: FieldFormatConfig | null | undefined,\n /** The column's storage type — used for the fallback rendering. */\n dataType?: string,\n): FormatResult {\n if (isEmpty(value)) return { ok: true, text: \"\", empty: true };\n\n const def = config ? getFieldFormat(config.id) : null;\n const effective =\n def ?? FIELD_FORMATS[defaultFormatForBase(dataType ?? \"string\")];\n\n let formatted: string | null;\n try {\n formatted = effective.format(value, config?.options ?? {});\n } catch {\n formatted = null;\n }\n\n if (formatted !== null) return { ok: true, text: formatted, empty: false };\n\n // Mismatch. Show what IS stored, flagged.\n return {\n ok: false,\n text: baseText(value),\n empty: false,\n reason: `This value is not a valid ${effective.label.toLowerCase()} — showing the stored value instead.`,\n };\n}\n\n/** Turn raw editor input into the value to store under a format. */\nexport function parseFieldInput(\n raw: unknown,\n config: FieldFormatConfig | null | undefined,\n dataType?: string,\n): unknown {\n const def = config ? getFieldFormat(config.id) : null;\n const effective =\n def ?? FIELD_FORMATS[defaultFormatForBase(dataType ?? \"string\")];\n try {\n return effective.parse(raw, config?.options ?? {});\n } catch {\n return raw;\n }\n}\n\n/**\n * Would this stored value survive its declared format? Used to count mismatches\n * so a column can warn (\"4 values don't fit Currency\") instead of silently\n * showing amber cells.\n */\nexport function valueFitsFormat(\n value: unknown,\n config: FieldFormatConfig | null | undefined,\n dataType?: string,\n): boolean {\n return formatFieldValue(value, config, dataType).ok;\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,45 @@
1
+ import { F as FieldFormatId, a as FieldFormatDef, b as FieldFormatConfig, c as FormatResult } from '../types-BdbvlF9-.cjs';
2
+ export { d as FieldBaseType, e as FieldChoice, f as FieldEditorKind, g as FieldFormatOptions, S as StructuredListBinding } from '../types-BdbvlF9-.cjs';
3
+
4
+ declare const FIELD_FORMATS: Readonly<Record<FieldFormatId, FieldFormatDef>>;
5
+ declare const FIELD_FORMAT_LIST: readonly FieldFormatDef[];
6
+ declare const FIELD_FORMAT_IDS: FieldFormatId[];
7
+ declare function getFieldFormat(id: string | null | undefined): FieldFormatDef | null;
8
+ /**
9
+ * The format a column gets when it declares none — the identity format for its
10
+ * storage type. Every existing column therefore behaves exactly as before.
11
+ */
12
+ declare function defaultFormatForBase(base: string): FieldFormatId;
13
+ /** Formats that can legally sit on a given storage type. */
14
+ declare function formatsForBase(base: string): FieldFormatDef[];
15
+ /** Group the pickable formats for a base type, preserving registry order. */
16
+ declare function groupedFormatsForBase(base: string): {
17
+ group: FieldFormatDef["group"];
18
+ formats: FieldFormatDef[];
19
+ }[];
20
+
21
+ /**
22
+ * Read a field's declared format off its `metadata` JSONB. Returns null when
23
+ * the field declares none (or declares one we don't know) — the caller then
24
+ * falls back to the storage type's identity format, so an unknown or removed
25
+ * format id degrades to plain, never to broken.
26
+ */
27
+ declare function readFieldFormatConfig(metadata: unknown): FieldFormatConfig | null;
28
+ /**
29
+ * Resolve the format a field actually renders with: its declared one, or the
30
+ * identity format for its storage type.
31
+ */
32
+ declare function resolveFieldFormat(dataType: string, metadata: unknown): FieldFormatConfig;
33
+ declare function formatFieldValue(value: unknown, config: FieldFormatConfig | null | undefined,
34
+ /** The column's storage type — used for the fallback rendering. */
35
+ dataType?: string): FormatResult;
36
+ /** Turn raw editor input into the value to store under a format. */
37
+ declare function parseFieldInput(raw: unknown, config: FieldFormatConfig | null | undefined, dataType?: string): unknown;
38
+ /**
39
+ * Would this stored value survive its declared format? Used to count mismatches
40
+ * so a column can warn ("4 values don't fit Currency") instead of silently
41
+ * showing amber cells.
42
+ */
43
+ declare function valueFitsFormat(value: unknown, config: FieldFormatConfig | null | undefined, dataType?: string): boolean;
44
+
45
+ export { FIELD_FORMATS, FIELD_FORMAT_IDS, FIELD_FORMAT_LIST, FieldFormatConfig, FieldFormatDef, FieldFormatId, FormatResult, defaultFormatForBase, formatFieldValue, formatsForBase, getFieldFormat, groupedFormatsForBase, parseFieldInput, readFieldFormatConfig, resolveFieldFormat, valueFitsFormat };
@@ -0,0 +1,45 @@
1
+ import { F as FieldFormatId, a as FieldFormatDef, b as FieldFormatConfig, c as FormatResult } from '../types-BdbvlF9-.js';
2
+ export { d as FieldBaseType, e as FieldChoice, f as FieldEditorKind, g as FieldFormatOptions, S as StructuredListBinding } from '../types-BdbvlF9-.js';
3
+
4
+ declare const FIELD_FORMATS: Readonly<Record<FieldFormatId, FieldFormatDef>>;
5
+ declare const FIELD_FORMAT_LIST: readonly FieldFormatDef[];
6
+ declare const FIELD_FORMAT_IDS: FieldFormatId[];
7
+ declare function getFieldFormat(id: string | null | undefined): FieldFormatDef | null;
8
+ /**
9
+ * The format a column gets when it declares none — the identity format for its
10
+ * storage type. Every existing column therefore behaves exactly as before.
11
+ */
12
+ declare function defaultFormatForBase(base: string): FieldFormatId;
13
+ /** Formats that can legally sit on a given storage type. */
14
+ declare function formatsForBase(base: string): FieldFormatDef[];
15
+ /** Group the pickable formats for a base type, preserving registry order. */
16
+ declare function groupedFormatsForBase(base: string): {
17
+ group: FieldFormatDef["group"];
18
+ formats: FieldFormatDef[];
19
+ }[];
20
+
21
+ /**
22
+ * Read a field's declared format off its `metadata` JSONB. Returns null when
23
+ * the field declares none (or declares one we don't know) — the caller then
24
+ * falls back to the storage type's identity format, so an unknown or removed
25
+ * format id degrades to plain, never to broken.
26
+ */
27
+ declare function readFieldFormatConfig(metadata: unknown): FieldFormatConfig | null;
28
+ /**
29
+ * Resolve the format a field actually renders with: its declared one, or the
30
+ * identity format for its storage type.
31
+ */
32
+ declare function resolveFieldFormat(dataType: string, metadata: unknown): FieldFormatConfig;
33
+ declare function formatFieldValue(value: unknown, config: FieldFormatConfig | null | undefined,
34
+ /** The column's storage type — used for the fallback rendering. */
35
+ dataType?: string): FormatResult;
36
+ /** Turn raw editor input into the value to store under a format. */
37
+ declare function parseFieldInput(raw: unknown, config: FieldFormatConfig | null | undefined, dataType?: string): unknown;
38
+ /**
39
+ * Would this stored value survive its declared format? Used to count mismatches
40
+ * so a column can warn ("4 values don't fit Currency") instead of silently
41
+ * showing amber cells.
42
+ */
43
+ declare function valueFitsFormat(value: unknown, config: FieldFormatConfig | null | undefined, dataType?: string): boolean;
44
+
45
+ export { FIELD_FORMATS, FIELD_FORMAT_IDS, FIELD_FORMAT_LIST, FieldFormatConfig, FieldFormatDef, FieldFormatId, FormatResult, defaultFormatForBase, formatFieldValue, formatsForBase, getFieldFormat, groupedFormatsForBase, parseFieldInput, readFieldFormatConfig, resolveFieldFormat, valueFitsFormat };